{"version":3,"file":"textarea.cjs","names":[],"sources":["../src/inputs/textarea/textarea.ts"],"sourcesContent":["import {\n  bind,\n  define,\n  getHost,\n  html,\n  live,\n  onCleanup,\n  onElement,\n  prop,\n  ref,\n  useField,\n  watchEffect,\n} from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { bindRefCallback, createAutoResize, createTextField, lifecycleSignal } from '../../core';\nimport type { TextFieldProps } from '../../shared';\nimport type { VisualVariant } from '../../types';\nimport '../../content/icon/icon';\nimport { disablableBundle, roundableBundle, sizableBundle, TEXTAREA_SIZE_PRESET, themableBundle } from '../../shared';\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 { renderFieldStatusRegion, renderStatusIcon } from '../shared/templates';\nimport componentStyles from './textarea.css?inline';\n\n/** Textarea component properties */\n\nexport type OreTextareaEvents = {\n  change: Event;\n  input: InputEvent;\n};\n\nexport type OreTextareaProps = TextFieldProps<Exclude<VisualVariant, 'frost' | 'text'>> & {\n  /** Allow auto-grow with content */\n  'auto-resize'?: boolean;\n  /**\n   * Shows an inline spinner inside the field and forces the inner `<textarea>` into\n   * `disabled` for the duration — use while an async validation/submission request\n   * is in flight to prevent double-submits.\n   */\n  loading?: boolean;\n  /** Maximum character count; shows a counter when set */\n  maxlength?: number;\n  /** Disable a manual resize handle */\n  'no-resize'?: boolean;\n  /**\n   * JS-only callback fired with the inner `<textarea>` element when it mounts,\n   * and with `null` when it unmounts. Intended for composed components that\n   * need imperative access to the raw element.\n   * Set as a JS property: `bitTextarea.ref = (el) => { ... }`.\n   */\n  ref?: ((el: HTMLTextAreaElement | null) => void) | null;\n  /** Resize direction override */\n  resize?: 'none' | 'horizontal' | 'both' | 'vertical';\n  /** Number of visible text rows */\n  rows?: number;\n};\n\n/**\n * A multi-line text input with label, helper text, character counter, and auto-resize.\n *\n * @element ore-textarea\n *\n * @attr {string} label - Label text\n * @attr {string} label-placement - 'inset' | 'outside'\n * @attr {string} value - Current value\n * @attr {string} placeholder - Placeholder text\n * @attr {string} name - Form field name\n * @attr {number} rows - Visible row count\n * @attr {number} maxlength - Max character count (shows counter)\n * @attr {string} helper - Helper text below the textarea\n * @attr {string} error - Error message\n * @attr {boolean} disabled - Disable interaction\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} required - Required field\n * @attr {boolean} no-resize - Disable manual resize\n * @attr {boolean} auto-resize - Grow with content\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} resize - Resize direction: 'none' | 'horizontal' | 'both' | 'vertical'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost'\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires input - Fired on every keystroke with current value.\n * @fires change - Fired on blur with changed value.\n *\n * @slot helper - Complex helper content\n *\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 *\n * @cssprop --textarea-bg - Background color\n * @cssprop --textarea-border-color - Border color\n * @cssprop --textarea-placeholder-color - Placeholder text color\n * @cssprop --textarea-radius - Border radius\n * @cssprop --textarea-padding - Inner padding (block inline)\n * @cssprop --textarea-gap - Gap between label and field\n * @cssprop --textarea-font-size - Font size\n * @cssprop --textarea-min-height - Minimum field height\n * @cssprop --textarea-max-height - Maximum field height (none = unlimited)\n * @cssprop --textarea-resize - CSS resize direction ('vertical' | 'horizontal' | 'both' | 'none')\n * @cssprop --textarea-hover-bg - Field background on hover (flat/ghost variants)\n * @cssprop --textarea-hover-border-color - Field border on hover (flat/bordered variants)\n * @cssprop --textarea-focus-bg - Field background when focused (flat variant)\n * @cssprop --textarea-focus-border-color - Field border when focused (flat variant)\n *\n * @part wrapper - Outer wrapper element.\n * @part label - Label element.\n * @part field - Field container.\n * @part textarea - The native `<textarea>` element.\n * @part helper - The helper text element.\n * @part error - The error text element (`role=\"alert\"`).\n *\n * @example\n * ```html\n * <ore-textarea></ore-textarea>\n * <ore-textarea label=\"Bio\" success></ore-textarea>\n * <ore-textarea label=\"Bio\" loading></ore-textarea>\n * ```\n */\nexport const TEXTAREA_TAG = 'ore-textarea' as const;\ndefine<OreTextareaProps>(TEXTAREA_TAG, {\n  formAssociated: true,\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    ...disablableBundle,\n    ...roundableBundle,\n    'auto-resize': prop.bool(false),\n    error: prop.string(),\n    fullwidth: prop.bool(false),\n    helper: prop.string(),\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    name: prop.string(),\n    'no-resize': prop.bool(false),\n    placeholder: prop.string(),\n    readonly: prop.bool(false),\n    ref: prop.data<((el: HTMLTextAreaElement | null) => void) | null>(),\n    required: prop.bool(false),\n    resize: prop.string<'none' | 'both' | 'horizontal' | 'vertical'>(),\n    rows: prop.json(undefined as number | undefined),\n    success: prop.bool(false),\n    value: prop.string(),\n    variant: prop.string<'flat' | 'solid' | 'bordered' | 'outline' | 'ghost'>(),\n  },\n  setup(props) {\n    const el = getHost();\n    const watch = watchEffect;\n\n    const textareaRef = ref<HTMLTextAreaElement>();\n    const autoResize = createAutoResize({ enabled: props['auto-resize'] });\n    // `loading` behaves like a temporary `disabled` — see ore-input's identical computation.\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      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: 'textarea',\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: () => {\n          tf.reset();\n          requestAnimationFrame(() => autoResize.recompute());\n        },\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      counter,\n      errorId,\n      errorText,\n      fieldId: textareaId,\n      helperText,\n      labelId,\n      labelVisible,\n    } = tf;\n\n    onElement(textareaRef, (textareaEl) => {\n      const unwireEl = tf.wire(textareaEl);\n      const unwireAutoResize = autoResize.wire(textareaEl);\n      const unwireRef = bindRefCallback(props.ref, textareaEl);\n\n      const stopLayoutEffect = watch(() => {\n        textareaEl.style.resize =\n          props['auto-resize'].value || props['no-resize'].value ? 'none' : props.resize.value || 'vertical';\n\n        // Deferred a frame: on mount (or right after `auto-resize` flips true) the browser\n        // hasn't necessarily finished laying out the textarea yet, so `scrollHeight` read\n        // synchronously here could be stale.\n        requestAnimationFrame(() => autoResize.recompute());\n      });\n\n      return () => {\n        unwireRef();\n        unwireEl();\n        unwireAutoResize();\n        stopLayoutEffect();\n      };\n    });\n\n    bind({\n      attr: {\n        error: errorAttr(errorText),\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    return html`\n      <div class=\"textarea-wrapper\" part=\"wrapper\">\n        <label class=\"label\" part=\"label\" for=\"${textareaId}\" id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">\n          ${props.label}\n        </label>\n        <div class=\"field\" part=\"field\">\n          <textarea\n            part=\"textarea\"\n            ref=\"${textareaRef}\"\n            id=\"${textareaId}\"\n            name=\"${props.name}\"\n            placeholder=\"${props.placeholder}\"\n            rows=\"${props.rows}\"\n            maxlength=\"${props.maxlength}\"\n            ?disabled=\"${isDisabled}\"\n            ?readonly=\"${props.readonly}\"\n            ?required=\"${props.required}\"\n            value=\"${live(tf.value)}\"\n            aria-describedby=\"${ariaDescribedBy}\"\n            aria-errormessage=\"${ariaErrorMessage}\"\n            aria-invalid=\"${ariaInvalid}\"\n            aria-labelledby=\"${ariaLabelledBy}\"\n            aria-busy=\"${() => (props.loading.value ? 'true' : null)}\"></textarea>\n          ${renderStatusIcon(errorText)}\n          <span class=\"field-spinner\" part=\"spinner\" role=\"status\" aria-label=\"Loading\"></span>\n        </div>\n        ${renderFieldStatusRegion({ assistiveId, counter, errorId, errorText, helperText })}\n      </div>\n    `;\n  },\n  shadow: { delegatesFocus: true },\n  styles: [\n    colorThemeMixin,\n    coarsePointerMixin,\n    reducedMotionMixin,\n    roundedVariantMixin,\n    disabledLoadingMixin,\n    sizeVariantMixin(TEXTAREA_SIZE_PRESET),\n    forcedColorsFocusMixin('textarea'),\n    componentStyles,\n    // Must come after `componentStyles` — see `ore-input`'s identical ordering note for why\n    // (`@layer` precedence is fixed by which layer name is *first* referenced across this whole\n    // array; `componentStyles` establishes `refine.base`, which this mixin's `refine.variants`\n    // rules need to win over).\n    fieldVariantMixin({ container: '.field', text: 'textarea', tokenPrefix: 'textarea' }),\n  ],\n});\n"],"mappings":"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"}