{"version":3,"file":"input.cjs","names":[],"sources":["../src/inputs/input/input.ts"],"sourcesContent":["import { bind, define, getHost, html, live, onCleanup, onElement, prop, ref, useField, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { bindRefCallback, createTextField, lifecycleSignal } from '../../core';\nimport type { TextFieldProps } from '../../shared';\nimport { disablableBundle, FIELD_SIZE_PRESET, roundableBundle, sizableBundle, themableBundle } from '../../shared';\nimport type { InputType, VisualVariant } from '../../types';\nimport '../../content/icon/icon';\nimport {\n  coarsePointerMixin,\n  colorThemeMixin,\n  disabledLoadingMixin,\n  fieldVariantMixin,\n  forcedColorsFocusMixin,\n  reducedMotionMixin,\n  roundedVariantMixin,\n  sizeVariantMixin,\n} from '../../styles';\nimport { errorAttr } from '../shared/field-binding';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderStatusIcon } from '../shared/templates';\nimport componentStyles from './input.css?inline';\n\n/** Input component properties */\n\nexport type OreInputEvents = {\n  change: Event;\n  input: InputEvent;\n};\n\nexport type OreInputProps = TextFieldProps<Exclude<VisualVariant, 'frost'>> & {\n  /** Autocomplete hint */\n  autocomplete?: string;\n  /** Show a clear (×) button when the field has a value */\n  clearable?: boolean;\n  /** Virtual keyboard hint for mobile devices */\n  inputmode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';\n  /**\n   * Shows an inline spinner inside the field and forces the inner `<input>` into\n   * `disabled` for the duration — use while an async validation/submission request\n   * is in flight (e.g. checking username availability) to prevent double-submits.\n   */\n  loading?: boolean;\n  /** Maximum character length — shows a counter below the input */\n  maxlength?: number;\n  /** Minimum character length */\n  minlength?: number;\n  /** HTML pattern attribute for client-side validation */\n  pattern?: string;\n  /**\n   * JS-only callback fired with the inner `<input>` element when it mounts,\n   * and with `null` when it unmounts. Intended for composed components that\n   * need imperative access to the raw input element.\n   * Set as a JS property: `bitInput.ref = (el) => { ... }`.\n   */\n  ref?: ((el: HTMLInputElement | null) => void) | null;\n  /** HTML input type */\n  type?: InputType;\n};\n\nconst VALID_INPUT_TYPES = [\n  'text',\n  'email',\n  'password',\n  'search',\n  'url',\n  'tel',\n  'number',\n  'date',\n  'time',\n  'datetime-local',\n  'month',\n  'week',\n] as const;\n\n/**\n * A customizable text input component with multiple variants, label placements, and form features.\n *\n * @element ore-input\n *\n * @attr {string} label - Label text\n * @attr {string} label-placement - Label placement: 'inset' | 'outside'\n * @attr {string} type - HTML input type: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'\n * @attr {string} value - Current input value\n * @attr {string} placeholder - Placeholder text\n * @attr {string} name - Form field name\n * @attr {string} helper - Helper text displayed below the input (fallback when the `helper` slot is empty)\n * @attr {string} error - Error message — marks the field as invalid (fallback when the `error` slot is empty)\n * @attr {boolean} disabled - Disable input interaction\n * @attr {boolean} readonly - Make the input read-only\n * @attr {boolean} required - Mark the field as required\n * @attr {boolean} loading - Show an inline spinner and force the field disabled\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text'\n * @attr {string} size - Input size: 'sm' | 'md' | 'lg'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires input - Emitted when input value changes (on every keystroke).\n * @fires change - Emitted when input loses focus with changed value.\n *\n * @slot prefix - Content before the input (e.g., icons)\n * @slot suffix - Content after the input (e.g., clear button, validation icon)\n * @slot label - Replaces the label text — slotted content takes precedence over the `label` prop\n * @slot helper - Replaces the helper text — slotted content takes precedence over the `helper` prop\n * @slot error - Replaces the error text — slotted content takes precedence over the `error` prop\n *\n * @part wrapper - The input wrapper element\n * @part label - The label element (inset or outside)\n * @part field - The field container element\n * @part input-row - The input row container element\n * @part input - The input element\n * @part status-icon - The inline error/success icon shown inside the field\n * @part spinner - The inline loading spinner shown inside the field while `loading`\n * @part helper - The helper text element\n *\n * @cssprop --input-bg - Background color\n * @cssprop --input-color - Text color\n * @cssprop --input-border-color - Border color\n * @cssprop --input-placeholder-color - Placeholder text color\n * @cssprop --input-radius - Border radius\n * @cssprop --input-padding - Inner padding (block inline)\n * @cssprop --input-label-block-inset - Inset from the field block start for an inset label\n * @cssprop --input-label-inset - Inset from the field inline start for an inset label\n * @cssprop --input-gap - Gap between prefix/suffix icons and input text\n * @cssprop --input-font-size - Font size\n * @cssprop --input-height - Field height\n * @cssprop --input-hover-bg - Field background on hover (flat/ghost variants)\n * @cssprop --input-hover-border-color - Field border on hover (flat/bordered variants)\n * @cssprop --input-focus-bg - Field background when focused (flat variant)\n * @cssprop --input-focus-border-color - Field border when focused (flat/text variants)\n *\n * @example\n * ```html\n * <ore-input type=\"email\" label=\"Email\" placeholder=\"you@example.com\" />\n * <ore-input label=\"Name\" variant=\"bordered\" color=\"primary\" />\n * <ore-input label=\"Username\" error=\"That username is taken\" />\n * <ore-input label=\"Username\" success />\n * <ore-input label=\"Username\" loading />\n * ```\n */\nexport const INPUT_TAG = 'ore-input' as const;\ndefine<OreInputProps>(INPUT_TAG, {\n  formAssociated: true,\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    ...disablableBundle,\n    ...roundableBundle,\n    autocomplete: prop.string(),\n    clearable: prop.bool(false),\n    error: prop.string(),\n    fullwidth: prop.bool(false),\n    helper: prop.string(),\n    inputmode: prop.string<'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'>(),\n    label: prop.string(),\n    'label-placement': prop.oneOf(['inset', 'outside'] as const, 'inset'),\n    loading: prop.bool(false),\n    maxlength: prop.json(undefined as number | undefined),\n    minlength: prop.json(undefined as number | undefined),\n    name: prop.string(),\n    pattern: prop.string(),\n    placeholder: prop.string(),\n    readonly: prop.bool(false),\n    ref: prop.data<((el: HTMLInputElement | null) => void) | null>(),\n    required: prop.bool(false),\n    success: prop.bool(false),\n    type: prop.oneOf(VALID_INPUT_TYPES, 'text'),\n    value: prop.string(),\n    variant: prop.string<'flat' | 'text' | 'solid' | 'bordered' | 'outline' | 'ghost'>(),\n  },\n  setup(props) {\n    const el = getHost();\n    const slots = useSlots();\n\n    const showPassword = signal(false);\n    const inputRef = ref<HTMLInputElement>();\n\n    const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n    // `loading` behaves like a temporary `disabled` — forces the inner <input> out of\n    // constraint validation and interaction for the duration, layered on top of any real\n    // `loading` is layered on top of the consumer's explicit `disabled` prop.\n    const isDisabled = computed(() => props.disabled.value || props.loading.value);\n\n    const abortSignal = lifecycleSignal(onCleanup);\n    const tf = createTextField({\n      disabled: isDisabled,\n      error: props.error,\n      hasLabel,\n      helper: props.helper,\n      label: props.label,\n      labelPlacement: props['label-placement'],\n      maxLength: props.maxlength,\n      onChange: (_event: Event, value: string) => {\n        setFieldValue(el, value);\n        dispatchNativeFieldEvent(el, 'change');\n      },\n      onInput: (_event: Event, value: string) => {\n        setFieldValue(el, value);\n        dispatchNativeFieldEvent(el, 'input');\n      },\n      prefix: 'input',\n      readonly: props.readonly,\n      required: props.required,\n      signal: abortSignal,\n      value: props.value,\n    });\n\n    defineFieldValue(\n      el,\n      () => tf.value.value,\n      (value) => {\n        tf.value.value = value;\n      },\n    );\n\n    tf.attachFormField(\n      useField<string>({\n        disabled: tf.disabled,\n        onReset: tf.reset,\n        toFormValue: (v) => v,\n        validationMessage: tf.validationMessage,\n        validity: tf.validity,\n        value: tf.value,\n      }),\n    );\n\n    const {\n      ariaDescribedBy,\n      ariaErrorMessage,\n      ariaInvalid,\n      ariaLabelledBy,\n      assistiveId,\n      clear: clearValue,\n      counter,\n      errorId,\n      errorText,\n      fieldId: inputId,\n      helperText,\n      labelId,\n      labelVisible,\n      value: fieldValue,\n      wire,\n    } = tf;\n\n    onElement(inputRef, (el) => {\n      wire(el, abortSignal);\n\n      const unwireRef = bindRefCallback(props.ref, el);\n\n      return () => {\n        unwireRef();\n      };\n    });\n\n    const clear = (event?: Event): void => {\n      clearValue(event);\n      inputRef.value?.focus();\n    };\n\n    const resolvedInputType = (): string =>\n      props.type.value === 'password' && showPassword.value ? 'text' : (props.type.value ?? 'text');\n\n    bind({\n      attr: {\n        error: errorAttr(errorText),\n        'has-value': () => (fieldValue.value ? true : undefined),\n        size: props.size,\n        // Reflects `success` only once `error` is confirmed empty — keeps the two host\n        // attributes mutually exclusive even if a consumer sets both props at once.\n        success: () => (props.success.value && !errorText.value ? true : undefined),\n        variant: props.variant,\n      },\n    });\n\n    const labelHidden = () => !labelVisible.value;\n    const passwordToggleLabel = () => (showPassword.value ? 'Hide password' : 'Show password');\n    const passwordTogglePressed = () => String(showPassword.value);\n    const passwordToggleIcon = () =>\n      showPassword.value\n        ? html`\n            <ore-icon name=\"eye-off\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n          `\n        : html`\n            <ore-icon name=\"eye\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n          `;\n    const helperHidden = () => !!errorText.value || !helperText.value;\n    const errorHidden = () => !errorText.value;\n    const counterNearLimit = () => (counter?.value.counterNearLimit && !counter?.value.counterAtLimit ? '' : null);\n    const counterAtLimit = () => (counter?.value.counterAtLimit ? '' : null);\n    const counterHidden = () => !counter;\n    const counterText = () => counter?.value.counterText ?? '';\n\n    const clearTabIndex = () => (fieldValue.value ? '0' : '-1');\n    const pwdToggleTabIndex = () => (props.type.value === 'password' ? '0' : '-1');\n\n    const togglePassword = () => {\n      showPassword.value = !showPassword.value;\n      inputRef.value?.focus();\n    };\n\n    return html`\n      <div class=\"input-wrapper\" part=\"wrapper\">\n        <label class=\"label\" for=\"${inputId}\" id=\"${labelId}\" part=\"label\" ?hidden=\"${labelHidden}\">\n          <slot name=\"label\">${props.label}</slot>\n        </label>\n        <div class=\"field\" part=\"field\">\n          <div class=\"input-row\" part=\"input-row\">\n            <slot name=\"prefix\"></slot>\n            <input\n              part=\"input\"\n              id=\"${inputId}\"\n              type=\"${resolvedInputType}\"\n              name=\"${props.name}\"\n              placeholder=\"${props.placeholder}\"\n              autocomplete=\"${props.autocomplete}\"\n              inputmode=\"${props.inputmode}\"\n              maxlength=\"${props.maxlength}\"\n              minlength=\"${props.minlength}\"\n              pattern=\"${props.pattern}\"\n              ?disabled=\"${isDisabled}\"\n              ?readonly=\"${props.readonly}\"\n              ?required=\"${props.required}\"\n              value=\"${live(fieldValue)}\"\n              aria-labelledby=\"${ariaLabelledBy}\"\n              aria-describedby=\"${ariaDescribedBy}\"\n              aria-errormessage=\"${ariaErrorMessage}\"\n              aria-invalid=\"${ariaInvalid}\"\n              aria-busy=\"${() => (props.loading.value ? 'true' : null)}\"\n              ref=\"${inputRef}\" />\n            <slot name=\"suffix\"></slot>\n            ${renderStatusIcon(errorText)}\n            <span class=\"field-spinner\" part=\"spinner\" role=\"status\" aria-label=\"Loading\"></span>\n            <button\n              class=\"pwd-toggle-btn\"\n              part=\"pwd-toggle\"\n              type=\"button\"\n              aria-label=\"${passwordToggleLabel}\"\n              aria-pressed=\"${passwordTogglePressed}\"\n              tabindex=\"${pwdToggleTabIndex}\"\n              @click=\"${togglePassword}\">\n              ${passwordToggleIcon}\n            </button>\n            <button\n              aria-label=\"Clear\"\n              class=\"clear-btn\"\n              part=\"clear\"\n              type=\"button\"\n              tabindex=\"${clearTabIndex}\"\n              @click=\"${clear}\">\n              <ore-icon aria-hidden=\"true\" name=\"x\" size=\"12\" stroke-width=\"2.5\"></ore-icon>\n            </button>\n          </div>\n        </div>\n        <div class=\"helper-text\" aria-live=\"polite\" id=\"${assistiveId}\" part=\"helper\" ?hidden=\"${helperHidden}\">\n          <slot name=\"helper\">${() => helperText.value}</slot>\n        </div>\n        <div class=\"helper-text\" id=\"${errorId}\" role=\"alert\" part=\"error\" ?hidden=\"${errorHidden}\">\n          <slot name=\"error\">${() => errorText.value}</slot>\n        </div>\n        <div\n          class=\"char-counter\"\n          part=\"char-counter\"\n          data-near-limit=\"${counterNearLimit}\"\n          data-at-limit=\"${counterAtLimit}\"\n          ?hidden=\"${counterHidden}\">\n          ${counterText}\n        </div>\n      </div>\n    `;\n  },\n  shadow: { delegatesFocus: true },\n  styles: [\n    colorThemeMixin,\n    coarsePointerMixin,\n    reducedMotionMixin,\n    roundedVariantMixin,\n    disabledLoadingMixin,\n    sizeVariantMixin(FIELD_SIZE_PRESET),\n    forcedColorsFocusMixin('input'),\n    componentStyles,\n    // Must come after `componentStyles` — `@layer` precedence is fixed by which layer name is\n    // *first* referenced across this whole array, and `componentStyles` is what establishes\n    // `refine.base` (the unconditional `--_bg`/`--_border-color` defaults this mixin's\n    // `refine.variants` rules need to win over). Placed earlier, `refine.variants` would end up\n    // registered as the *lower*-priority layer, and every variant would silently render as the\n    // base default — exactly the bug this ordering fixes.\n    fieldVariantMixin({ container: '.field', text: 'input', tokenPrefix: 'input' }),\n  ],\n});\n"],"mappings":"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"}