{"version":3,"file":"chatComposer.mjs","names":[],"sources":["../../../../package/common/chatComposer/chatComposer.tsx"],"sourcesContent":["import { useRef, useState, type KeyboardEvent, type MouseEvent, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faPaperPlane, faStop } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { ButtonStyleType } from '../button/button.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useAutoGrow } from '../hooks/useAutoGrow';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport { mergeRefs } from '../utils/mergeRefs';\nimport type { ChatComposerLabels, ChatComposerProps } from './chatComposer.types';\nimport styles from './chatComposer.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatComposerLabels> = {\n  placeholder: 'Message',\n  send: 'Send',\n  stop: 'Stop',\n};\n\nfunction ChatComposer(props: ChatComposerProps): ReactElement {\n  const {\n    onSend,\n    value,\n    defaultValue = '',\n    onChange,\n    onKeyDown,\n    onStop,\n    disabled = false,\n    readOnly = false,\n    rows = 1,\n    startContent,\n    endContent,\n    ref,\n    labels,\n    className,\n    style,\n    classNames,\n    styles: slotStyles,\n    ...rest\n  } = useComponentDefaults('ChatComposer', props);\n  const text = useLabels('ChatComposer', labels, DEFAULT_LABELS);\n  const isCoarsePointer = useCoarsePointer();\n  const inputRef = useRef<HTMLTextAreaElement>(null);\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const current = value ?? internalValue;\n  const canSend = !disabled && !readOnly && current.trim() !== '';\n  const [isGrown, setIsGrown] = useState(false);\n\n  // Back to one row only once the field is empty. Below the buttons the field is wider, and text\n  // that wrapped beside them may fit on one line there: dropping back then would wrap it again.\n  if (isGrown && current === '') setIsGrown(false);\n  // With nothing besides the send button, a row of its own would cost more height than it frees.\n  const isStacked = isGrown && (startContent != null || endContent != null);\n\n  useAutoGrow(inputRef, current, {\n    onGrow: (grown) => {\n      // An empty field can measure taller than a line: Chrome counts a wrapping placeholder.\n      if (grown && inputRef.current?.value) setIsGrown(true);\n    },\n  });\n\n  const setValue = (next: string) => {\n    if (value === undefined) setInternalValue(next);\n    onChange?.(next);\n  };\n\n  const send = () => {\n    if (!canSend || onStop) return;\n    onSend(current.trim());\n    setValue('');\n    inputRef.current?.focus();\n  };\n\n  const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n    onKeyDown?.(event);\n    // A consumer's own use of Enter comes first, such as picking from an open mention list.\n    if (event.defaultPrevented || event.key !== 'Enter' || event.shiftKey) return;\n    // The Enter that confirms a word in a Japanese, Chinese or Korean IME. Safari sends its keydown\n    // after compositionend, with isComposing already false, and only keyCode 229 gives it away.\n    if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n    // On a touch keyboard Enter is the only way to start a new line, so there the button sends.\n    if (isCoarsePointer && !event.metaKey && !event.ctrlKey) return;\n    event.preventDefault();\n    send();\n  };\n\n  // Keeps the focus in the field when the button is pressed, so a phone keyboard stays open.\n  const keepFocus = (event: MouseEvent) => event.preventDefault();\n\n  return (\n    <div\n      className={cx(\n        styles.chatComposer,\n        isStacked && styles.chatComposerStacked,\n        disabled && styles.chatComposerDisabled,\n        className,\n      )}\n      style={style}\n    >\n      {startContent != null && <div className={styles.chatComposerSection}>{startContent}</div>}\n      <textarea\n        // Before the spread, so a name of the consumer's own wins over the placeholder.\n        aria-label={text.placeholder}\n        {...rest}\n        ref={mergeRefs(inputRef, ref)}\n        rows={rows}\n        value={current}\n        placeholder={text.placeholder}\n        disabled={disabled}\n        readOnly={readOnly}\n        className={cx(styles.chatComposerInput, classNames?.input)}\n        style={slotStyles?.input}\n        onChange={(event) => setValue(event.target.value)}\n        onKeyDown={handleKeyDown}\n      />\n      {endContent != null && <div className={styles.chatComposerSection}>{endContent}</div>}\n      {onStop ? (\n        <ActionIcon\n          icon={faStop}\n          styleType={ButtonStyleType.Filled}\n          color={ThemeColor.Primary}\n          aria-label={text.stop}\n          title={text.stop}\n          disabled={disabled}\n          className={cx(styles.chatComposerButton, classNames?.button)}\n          style={slotStyles?.button}\n          onMouseDown={keepFocus}\n          onClick={onStop}\n        />\n      ) : (\n        <ActionIcon\n          icon={faPaperPlane}\n          styleType={ButtonStyleType.Filled}\n          color={ThemeColor.Primary}\n          aria-label={text.send}\n          title={text.send}\n          disabled={!canSend}\n          className={cx(styles.chatComposerButton, classNames?.button)}\n          style={slotStyles?.button}\n          onMouseDown={keepFocus}\n          onClick={send}\n        />\n      )}\n    </div>\n  );\n}\n\nexport default ChatComposer;\n"],"mappings":"wsBAcA,IAAM,EAA+C,CACnD,YAAa,UACb,KAAM,OACN,KAAM,MACR,EAEA,SAAS,EAAa,EAAwC,CAC5D,GAAM,CACJ,SACA,QACA,eAAe,GACf,WACA,YACA,SACA,WAAW,GACX,WAAW,GACX,OAAO,EACP,eACA,aACA,MACA,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,eAAgB,CAAK,EACxC,EAAO,EAAU,eAAgB,EAAQ,CAAc,EACvD,EAAkB,EAAiB,EACnC,EAAW,EAA4B,IAAI,EAC3C,CAAC,EAAe,GAAoB,EAAS,CAAY,EACzD,EAAU,GAAS,EACnB,EAAU,CAAC,GAAY,CAAC,GAAY,EAAQ,KAAK,IAAM,GACvD,CAAC,EAAS,GAAc,EAAS,EAAK,EAIxC,GAAW,IAAY,IAAI,EAAW,EAAK,EAE/C,IAAM,EAAY,IAAY,GAAgB,MAAQ,GAAc,MAEpE,EAAY,EAAU,EAAS,CAC7B,OAAS,GAAU,CAEb,GAAS,EAAS,SAAS,OAAO,EAAW,EAAI,CACvD,CACF,CAAC,EAED,IAAM,EAAY,GAAiB,CAC7B,IAAU,IAAA,IAAW,EAAiB,CAAI,EAC9C,IAAW,CAAI,CACjB,EAEM,MAAa,CACZ,GAAW,KAChB,EAAO,EAAQ,KAAK,CAAC,EACrB,EAAS,EAAE,EACX,EAAS,SAAS,MAAM,EAC1B,EAEM,EAAiB,GAA8C,CACnE,IAAY,CAAK,EAEb,IAAM,kBAAoB,EAAM,MAAQ,SAAW,EAAM,YAGzD,EAAM,YAAY,aAAe,EAAM,UAAY,MAEnD,IAAoB,EAAM,SAAY,EAAM,WAChD,EAAM,eAAe,EACrB,EAAK,GACP,EAGM,EAAa,GAAsB,EAAM,eAAe,EAE9D,OACE,EAAC,MAAD,CACE,UAAW,EACT,EAAO,aACP,GAAa,EAAO,oBACpB,GAAY,EAAO,qBACnB,CACF,EACO,QAPT,SAAA,CASG,GAAgB,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAkB,CAAA,EACxF,EAAC,WAAD,CAEE,aAAY,EAAK,YACjB,GAAI,EACJ,IAAK,EAAU,EAAU,CAAG,EACtB,OACN,MAAO,EACP,YAAa,EAAK,YACR,WACA,WACV,UAAW,EAAG,EAAO,kBAAmB,GAAY,KAAK,EACzD,MAAO,GAAY,MACnB,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,EAChD,UAAW,CACZ,CAAA,EACA,GAAc,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAgB,CAAA,EACnF,EACC,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACF,WACV,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,EAED,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACZ,SAAU,CAAC,EACX,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,CAEA,GAET"}