{"version":3,"file":"message-composer.cjs","names":[],"sources":["../src/inputs/message-composer/message-composer.ts"],"sourcesContent":["import {\n  bind,\n  define,\n  getHost,\n  html,\n  live,\n  onCleanup,\n  onElement,\n  prop,\n  ref,\n  useEmit,\n  useField,\n  useSlots,\n} from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport {\n  bindRefCallback,\n  createAutoResize,\n  createComposerControl,\n  createTextField,\n  lifecycleSignal,\n  type SendShortcut,\n} from '../../core';\nimport {\n  disablableBundle,\n  loadableBundle,\n  MESSAGE_COMPOSER_SIZE_PRESET,\n  sizableBundle,\n  themableBundle,\n} from '../../shared';\nimport {\n  coarsePointerMixin,\n  colorThemeMixin,\n  disabledLoadingMixin,\n  fieldVariantMixin,\n  forcedColorsFocusMixin,\n  forcedColorsMixin,\n  reducedMotionMixin,\n  sizeVariantMixin,\n} from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { errorAttr } from '../shared/field-binding';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport { renderFieldStatusRegion, renderStatusIcon } from '../shared/templates';\nimport '../../content/icon/icon';\nimport '../button/button';\nimport componentStyles from './message-composer.css?inline';\n\nexport type { SendShortcut } from '../../core';\n\nconst DEFAULT_SEND_LABEL = 'Send message';\nconst SEND_ICON = 'arrow-up';\n\n/** Events emitted by the message composer */\nexport type OreMessageComposerEvents = {\n  input: InputEvent;\n  /**\n   * Fired for a send attempt (the resolved `send-shortcut` or the send button) while the\n   * composer isn't blank/disabled/loading. Cancelable — call `preventDefault()` to keep the\n   * current text in the field instead of the default clear + refocus.\n   */\n  send: { originalEvent: KeyboardEvent | MouseEvent; value: string };\n};\n\n/** Message composer properties */\nexport type OreMessageComposerProps = {\n  /** Clear the value and refocus after a non-cancelled `send` (default `true`) */\n  'clear-on-send'?: boolean;\n  /** Theme color (card focus ring + default send button color) */\n  color?: ThemeColor;\n  /** Disable the whole composer (field, slots, and send action) */\n  disabled?: boolean;\n  /** Error message shown below the field */\n  error?: string;\n  /** Stretch the composer to the full width of its container */\n  fullwidth?: boolean;\n  /** Helper text shown below the field */\n  helper?: string;\n  /** Accessible name for the field — never rendered visually, only as `aria-label` */\n  label?: string;\n  /** Blocks further sends (e.g. a send is already in flight) without disabling editing */\n  loading?: boolean;\n  /** Maximum character count; shows a counter */\n  maxlength?: number;\n  /** Form field name */\n  name?: string;\n  /** Placeholder text */\n  placeholder?: string;\n  /** Make the field read-only */\n  readonly?: boolean;\n  /**\n   * JS-only callback fired with the inner `<textarea>` element when it mounts, and with\n   * `null` when it unmounts. Set as a JS property: `composer.ref = (el) => { ... }`.\n   */\n  ref?: ((el: HTMLTextAreaElement | null) => void) | null;\n  /** Require a non-blank value for native form validation */\n  required?: boolean;\n  /** Number of visible rows before auto-resize grows the field */\n  rows?: number;\n  /** Lucide icon name for the default send button (default `'arrow-up'`) */\n  'send-icon'?: string;\n  /** Accessible label for the send button (default `'Send message'`) */\n  'send-label'?: string;\n  /** Keyboard shortcut that sends: 'enter' (Shift+Enter for a newline) or 'mod+enter' (Enter always inserts a newline; Ctrl/Cmd+Enter sends) */\n  'send-shortcut'?: SendShortcut;\n  /** Component size */\n  size?: ComponentSize;\n  /**\n   * Shows an inline green check icon inside the field to confirm the value has\n   * passed validation. Ignored while `error` is set — an error always wins.\n   */\n  success?: boolean;\n  /** Current text value */\n  value?: string;\n  /** Visual variant of the card — same variant set as `ore-textarea`, applied to the card. */\n  variant?: Exclude<VisualVariant, 'frost' | 'text'>;\n};\n\n/**\n * A message/comment composer — a card with an auto-resizing field on top and a toolbar row\n * below it, built directly on the same core primitives as `ore-textarea` (`createTextField`,\n * `createAutoResize`) rather than composing `ore-textarea` itself. Nesting a fully-styled sibling\n * component and suppressing most of its chrome (as an earlier version of this component did) is\n * a leaky composition — this owns its single `<textarea>` outright, so there's exactly one\n * implementation of the field's appearance to reason about, not two fighting each other.\n *\n * Handles the send gesture (Enter to send, Shift+Enter for a newline, or a Ctrl/Cmd+Enter\n * alternative), IME-safe composition, and clear-and-refocus after sending.\n *\n * @element ore-message-composer\n *\n * @attr {string} value - Current text value\n * @attr {string} placeholder - Placeholder text\n * @attr {string} label - Accessible name for the field (not rendered visually)\n * @attr {string} name - Form field name\n * @attr {string} helper - Helper text shown below the field\n * @attr {string} error - Error message shown below the field\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {boolean} disabled - Disable the whole composer\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} required - Require a non-blank value for native form validation\n * @attr {boolean} loading - Blocks further sends without disabling editing\n * @attr {number} maxlength - Max character count; shows a counter\n * @attr {number} rows - Visible rows before auto-resize grows the field (default 1)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant of the card: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {boolean} fullwidth - Stretch to the full width of the container\n * @attr {string} send-icon - Lucide icon name for the default send button (default 'arrow-up')\n * @attr {string} send-label - Accessible label for the send button (default 'Send message')\n * @attr {string} send-shortcut - 'enter' (default) | 'mod+enter'\n * @attr {boolean} clear-on-send - Clear the value and refocus after a non-cancelled send (default true)\n *\n * @fires input - Fired on every keystroke. detail: { value, originalEvent }\n * @fires send - Fired on a send attempt. Cancelable. detail: { value, originalEvent }\n *\n * @slot prefix - Content at the start of the toolbar row (e.g. an attach menu)\n * @slot suffix - Content at the end of the toolbar row, before the send button (e.g. a model picker)\n * @slot send - Replaces the default send button entirely — the only supported way to customize it\n *\n * @cssprop --message-composer-bg - Card background color\n * @cssprop --message-composer-border-color - Card border color\n * @cssprop --message-composer-radius - Card border radius\n * @cssprop --message-composer-padding - Card inner padding\n * @cssprop --message-composer-gap - Gap between the field/toolbar rows and between toolbar items\n * @cssprop --message-composer-placeholder-color - Field placeholder text color\n * @cssprop --message-composer-min-height - Minimum field height (default one line)\n * @cssprop --message-composer-hover-bg - Card background on hover (flat/ghost variants)\n * @cssprop --message-composer-hover-border-color - Card border on hover (flat/bordered variants)\n * @cssprop --message-composer-focus-bg - Card background when focused (flat variant)\n * @cssprop --message-composer-focus-border-color - Card border when focused (flat variant)\n *\n * @part composer - Root card container\n * @part field - The native `<textarea>` element\n * @part status-icon - The inline error/success icon shown inside the field\n * @part helper - Helper text element\n * @part error - Error text element (`role=\"alert\"`)\n * @part toolbar - Toolbar row below the field\n * @part toolbar-start - Toolbar group holding the `prefix` slot\n * @part toolbar-end - Toolbar group holding the `suffix` slot and the send button\n * @part send-button - The default send button (absent when the `send` slot is used)\n *\n * @example\n * ```html\n * <ore-message-composer placeholder=\"Message…\" send-shortcut=\"mod+enter\"></ore-message-composer>\n * ```\n */\nexport const MESSAGE_COMPOSER_TAG = 'ore-message-composer' as const;\ndefine<OreMessageComposerProps>(MESSAGE_COMPOSER_TAG, {\n  formAssociated: true,\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    ...disablableBundle,\n    ...loadableBundle,\n    'clear-on-send': prop.bool(true),\n    error: prop.string(),\n    fullwidth: prop.bool(false),\n    helper: prop.string(),\n    label: prop.string(),\n    maxlength: prop.json(undefined as number | undefined),\n    name: prop.string(),\n    placeholder: prop.string(),\n    readonly: prop.bool(false),\n    ref: prop.data<((el: HTMLTextAreaElement | null) => void) | null>(),\n    required: prop.bool(false),\n    rows: prop.json(undefined as number | undefined),\n    'send-icon': prop.string(),\n    'send-label': prop.string(),\n    'send-shortcut': prop.oneOf(['enter', 'mod+enter'] as const, 'enter'),\n    success: prop.bool(false),\n    value: prop.string(),\n    variant: prop.string<Exclude<VisualVariant, 'frost' | 'text'>>(),\n  },\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreMessageComposerEvents>();\n    const slots = useSlots<'prefix' | 'send' | 'suffix'>();\n\n    const isDisabled = props.disabled;\n\n    const abortSignal = lifecycleSignal(onCleanup);\n    const textareaRef = ref<HTMLTextAreaElement>();\n    const autoResize = createAutoResize();\n\n    const tf = createTextField({\n      disabled: isDisabled,\n      error: props.error,\n      helper: props.helper,\n      maxLength: props.maxlength,\n      onInput: (_event, value) => {\n        setFieldValue(el, value);\n        dispatchNativeFieldEvent(el, 'input');\n      },\n      prefix: 'composer',\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    // Directly form-associated now that the field is owned here rather than borrowed from a\n    // nested `ore-textarea` (which previously registered with the ancestor `<form>` on this\n    // component's behalf).\n    tf.attachFormField(\n      useField<string>({\n        disabled: tf.disabled,\n        onReset: () => {\n          tf.reset();\n          autoResize.recompute();\n        },\n        toFormValue: (v) => v,\n        validationMessage: tf.validationMessage,\n        validity: tf.validity,\n        value: tf.value,\n      }),\n    );\n\n    function attemptSend(event: KeyboardEvent | MouseEvent): void {\n      const notPrevented = emit('send', { originalEvent: event, value: tf.value.value.trim() });\n\n      // `preventDefault()` on `send` means \"keep the current text — skip the default clear\n      // + refocus\" (see the event's own doc comment): both halves of that default are gated on\n      // the same condition, not just the clear.\n      if (notPrevented && props['clear-on-send'].value !== false) {\n        tf.clear();\n        autoResize.recompute();\n        textareaRef.value?.focus();\n      }\n    }\n\n    const composer = createComposerControl({\n      disabled: isDisabled,\n      loading: computed(() => Boolean(props.loading.value)),\n      onSend: attemptSend,\n      sendShortcut: props['send-shortcut'],\n      value: tf.value,\n    });\n\n    onElement(textareaRef, (textareaEl) => {\n      const unwireField = tf.wire(textareaEl);\n      const unwireAutoResize = autoResize.wire(textareaEl);\n      const unwireRef = bindRefCallback(props.ref, textareaEl);\n      const handleKeydown = (e: KeyboardEvent) => composer.handleKeydown(e);\n\n      textareaEl.addEventListener('keydown', handleKeydown);\n\n      return () => {\n        textareaEl.removeEventListener('keydown', handleKeydown);\n        unwireField();\n        unwireAutoResize();\n        unwireRef();\n      };\n    });\n\n    bind({\n      attr: {\n        error: errorAttr(tf.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 && !tf.errorText.value ? true : undefined),\n        variant: props.variant,\n      },\n    });\n\n    return html`\n      <div class=\"composer\" part=\"composer\">\n        <textarea\n          class=\"field\"\n          part=\"field\"\n          ref=\"${textareaRef}\"\n          rows=\"${() => props.rows.value ?? 1}\"\n          name=\"${() => props.name.value ?? ''}\"\n          placeholder=\"${() => props.placeholder.value ?? 'Message…'}\"\n          maxlength=\"${() => props.maxlength.value}\"\n          ?disabled=\"${isDisabled}\"\n          ?readonly=\"${() => Boolean(props.readonly.value)}\"\n          ?required=\"${() => Boolean(props.required.value)}\"\n          value=\"${live(tf.value)}\"\n          aria-label=\"${() => props.label.value || 'Message'}\"\n          aria-keyshortcuts=\"${composer.keyShortcutsHint}\"\n          aria-describedby=\"${tf.ariaDescribedBy}\"\n          aria-errormessage=\"${tf.ariaErrorMessage}\"\n          aria-invalid=\"${tf.ariaInvalid}\"></textarea>\n        ${renderStatusIcon(tf.errorText)} ${renderFieldStatusRegion(tf)}\n        <div class=\"toolbar\" part=\"toolbar\">\n          <div class=\"toolbar-start\" part=\"toolbar-start\">\n            <slot name=\"prefix\"></slot>\n          </div>\n          <div class=\"toolbar-end\" part=\"toolbar-end\">\n            <slot name=\"suffix\"></slot>\n            <slot name=\"send\"></slot>\n            ${() =>\n              slots.has('send').value\n                ? ''\n                : html`\n                    <ore-button\n                      class=\"send-btn\"\n                      part=\"send-button\"\n                      type=\"button\"\n                      icon-only\n                      variant=\"solid\"\n                      color=\"${() => props.color.value || 'primary'}\"\n                      size=\"${props.size}\"\n                      label=\"${() => props['send-label'].value || DEFAULT_SEND_LABEL}\"\n                      ?loading=\"${() => Boolean(props.loading.value)}\"\n                      ?disabled=\"${() => !composer.canSend.value}\"\n                      @click=\"${(e: MouseEvent) => composer.send(e)}\">\n                      <ore-icon name=\"${() => props['send-icon'].value || SEND_ICON}\" size=\"16\"></ore-icon>\n                    </ore-button>\n                  `}\n          </div>\n        </div>\n      </div>\n    `;\n  },\n  shadow: { delegatesFocus: true },\n  styles: [\n    colorThemeMixin,\n    coarsePointerMixin,\n    reducedMotionMixin,\n    disabledLoadingMixin,\n    forcedColorsMixin,\n    forcedColorsFocusMixin('.field'),\n    sizeVariantMixin(MESSAGE_COMPOSER_SIZE_PRESET),\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: '.composer', text: '.field', tokenPrefix: 'message-composer' }),\n  ],\n});\n"],"mappings":"ozBAkDA,IAAM,EAAqB,eACrB,EAAY,WAwIL,EAAuB,wBACpC,EAAA,EAAA,OAAA,CAAgC,EAAsB,CACpD,eAAgB,GAChB,MAAO,CACL,GAAG,EAAA,eACH,GAAG,EAAA,cACH,GAAG,EAAA,iBACH,GAAG,EAAA,eACH,gBAAiB,EAAA,KAAK,KAAK,EAAI,EAC/B,MAAO,EAAA,KAAK,OAAO,EACnB,UAAW,EAAA,KAAK,KAAK,EAAK,EAC1B,OAAQ,EAAA,KAAK,OAAO,EACpB,MAAO,EAAA,KAAK,OAAO,EACnB,UAAW,EAAA,KAAK,KAAK,IAAA,EAA+B,EACpD,KAAM,EAAA,KAAK,OAAO,EAClB,YAAa,EAAA,KAAK,OAAO,EACzB,SAAU,EAAA,KAAK,KAAK,EAAK,EACzB,IAAK,EAAA,KAAK,KAAwD,EAClE,SAAU,EAAA,KAAK,KAAK,EAAK,EACzB,KAAM,EAAA,KAAK,KAAK,IAAA,EAA+B,EAC/C,YAAa,EAAA,KAAK,OAAO,EACzB,aAAc,EAAA,KAAK,OAAO,EAC1B,gBAAiB,EAAA,KAAK,MAAM,CAAC,QAAS,WAAW,EAAY,OAAO,EACpE,QAAS,EAAA,KAAK,KAAK,EAAK,EACxB,MAAO,EAAA,KAAK,OAAO,EACnB,QAAS,EAAA,KAAK,OAAiD,CACjE,EACA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAAkC,EACzC,GAAA,EAAQ,EAAA,SAAA,CAAuC,EAE/C,EAAa,EAAM,SAEnB,EAAc,EAAA,gBAAgB,EAAA,SAAS,EACvC,GAAA,EAAc,EAAA,IAAA,CAAyB,EACvC,EAAa,EAAA,iBAAiB,EAE9B,EAAK,EAAA,gBAAgB,CACzB,SAAU,EACV,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,UAAW,EAAM,UACjB,SAAU,EAAQ,IAAU,CAC1B,EAAA,cAAc,EAAI,CAAK,EACvB,EAAA,yBAAyB,EAAI,OAAO,CACtC,EACA,OAAQ,WACR,SAAU,EAAM,SAChB,SAAU,EAAM,SAChB,OAAQ,EACR,MAAO,EAAM,KACf,CAAC,EAED,EAAA,iBACE,MACM,EAAG,MAAM,MACd,GAAU,CACT,EAAG,MAAM,MAAQ,CACnB,CACF,EAKA,EAAG,iBAAA,EACD,EAAA,SAAA,CAAiB,CACf,SAAU,EAAG,SACb,YAAe,CACb,EAAG,MAAM,EACT,EAAW,UAAU,CACvB,EACA,YAAc,GAAM,EACpB,kBAAmB,EAAG,kBACtB,SAAU,EAAG,SACb,MAAO,EAAG,KACZ,CAAC,CACH,EAEA,SAAS,EAAY,EAAyC,CACvC,EAAK,OAAQ,CAAE,cAAe,EAAO,MAAO,EAAG,MAAM,MAAM,KAAK,CAAE,CAKnF,GAAgB,EAAM,gBAAgB,CAAC,QAAU,KACnD,EAAG,MAAM,EACT,EAAW,UAAU,EACrB,EAAY,OAAO,MAAM,EAE7B,CAEA,IAAM,EAAW,EAAA,sBAAsB,CACrC,SAAU,EACV,SAAA,EAAS,EAAA,SAAA,KAAe,EAAQ,EAAM,QAAQ,KAAM,EACpD,OAAQ,EACR,aAAc,EAAM,iBACpB,MAAO,EAAG,KACZ,CAAC,EA6BD,OA3BA,EAAA,EAAA,UAAA,CAAU,EAAc,GAAe,CACrC,IAAM,EAAc,EAAG,KAAK,CAAU,EAChC,EAAmB,EAAW,KAAK,CAAU,EAC7C,EAAY,EAAA,gBAAgB,EAAM,IAAK,CAAU,EACjD,EAAiB,GAAqB,EAAS,cAAc,CAAC,EAIpE,OAFA,EAAW,iBAAiB,UAAW,CAAa,MAEvC,CACX,EAAW,oBAAoB,UAAW,CAAa,EACvD,EAAY,EACZ,EAAiB,EACjB,EAAU,CACZ,CACF,CAAC,GAED,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,MAAO,EAAA,UAAU,EAAG,SAAS,EAC7B,KAAM,EAAM,KAGZ,YAAgB,EAAM,QAAQ,OAAS,CAAC,EAAG,UAAU,MAAQ,GAAO,IAAA,GACpE,QAAS,EAAM,OACjB,CACF,CAAC,EAEM,EAAA,IAAI;;;;;iBAKE,EAAY;sBACL,EAAM,KAAK,OAAS,EAAE;sBACtB,EAAM,KAAK,OAAS,GAAG;6BAChB,EAAM,YAAY,OAAS,WAAW;2BACxC,EAAM,UAAU,MAAM;uBAC5B,EAAW;2BACL,EAAQ,EAAM,SAAS,MAAO;2BAC9B,EAAQ,EAAM,SAAS,MAAO;oBACxC,EAAA,EAAA,KAAA,CAAK,EAAG,KAAK,EAAE;4BACJ,EAAM,MAAM,OAAS,UAAU;+BAC9B,EAAS,iBAAiB;8BAC3B,EAAG,gBAAgB;+BAClB,EAAG,iBAAiB;0BACzB,EAAG,YAAY;UAC/B,EAAA,iBAAiB,EAAG,SAAS,EAAE,GAAG,EAAA,wBAAwB,CAAE,EAAE;;;;;;;;kBAS1D,EAAM,IAAI,MAAM,CAAC,CAAC,MACd,GACA,EAAA,IAAI;;;;;;;mCAOe,EAAM,MAAM,OAAS,UAAU;8BACtC,EAAM,KAAK;mCACJ,EAAM,aAAa,CAAC,OAAS,EAAmB;sCAC7C,EAAQ,EAAM,QAAQ,MAAO;uCAC5B,CAAC,EAAS,QAAQ,MAAM;gCAChC,GAAkB,EAAS,KAAK,CAAC,EAAE;4CACtB,EAAM,YAAY,CAAC,OAAS,EAAU;;oBAEhE;;;;KAKlB,EACA,OAAQ,CAAE,eAAgB,EAAK,EAC/B,OAAQ,CACN,EAAA,gBACA,EAAA,mBACA,EAAA,mBACA,EAAA,qBACA,EAAA,kBACA,EAAA,uBAAuB,QAAQ,EAC/B,EAAA,iBAAiB,EAAA,4BAA4B,EAC7C,EAAA,QAKA,EAAA,kBAAkB,CAAE,UAAW,YAAa,KAAM,SAAU,YAAa,kBAAmB,CAAC,CAC/F,CACF,CAAC"}