{"version":3,"file":"templateEditor.mjs","names":[],"sources":["../../../../package/common/templateEditor/templateEditor.tsx"],"sourcesContent":["import { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport Input from 'components/common/input';\nimport { useComponentDefaults, useLabels } from 'components/common/configProvider';\nimport { ThemeColor } from 'components/common/themeProvider/color.types';\nimport { toneProps } from 'components/common/styles/tone';\n\nimport styles from './templateEditor.module.pcss';\nimport TemplateEditable from './templateEditable';\nimport TemplateEditorPalette from './templateEditorPalette';\nimport {\n  buildChipLabels,\n  buildTokenIndex,\n  DEFAULT_LABELS,\n  DEFAULT_PREFIX,\n  DEFAULT_SUFFIX,\n} from './templateEditor.utils';\nimport type { TemplateChipContext } from './templateEditor.dom';\nimport {\n  TemplateEditorPalettePosition,\n  type TemplateDragPayload,\n  type TemplateEditableApi,\n  type TemplateEditorProps,\n} from './templateEditor.types';\n\n// Tone classes rather than the theme's -lighter pastels, so a chip reads the same as a\n// Light Chip on a dark theme too. Two classes, because a chip can carry only one tone.\nconst KNOWN_TONE = toneProps(ThemeColor.Primary).className;\nconst UNKNOWN_TONE = toneProps(ThemeColor.Warn).className;\n\n/**\n * A single form field for authoring templates that mix prose with placeholders.\n *\n * The field is a contenteditable whose content React never owns: text and chips\n * are built imperatively, which is what keeps the caret stable while typing.\n * The chrome around it (floating label, error tooltip, form validation) comes\n * from `Input`, which accepts this surface as its `customInput`, so the field\n * looks and validates exactly like every other input in the library.\n */\nfunction TemplateEditor(props: TemplateEditorProps) {\n  const {\n    value = '',\n    onChange,\n    tokenGroups = [],\n    palettePosition = TemplateEditorPalettePosition.Right,\n    paletteLabel,\n    prefix = DEFAULT_PREFIX,\n    suffix = DEFAULT_SUFFIX,\n    label = '',\n    isRequired = false,\n    errorMessage = '',\n    isValid = true,\n    formValidator = null,\n    placeholder = '',\n    disabled = false,\n    readOnly = false,\n    multiline = true,\n    rows = 4,\n    autoFocus = false,\n    smartSpacing = true,\n    onFocus,\n    onBlur,\n    labels,\n    ref,\n    className,\n    style,\n    classNames,\n    styles: slotStyles,\n    ...rest\n  } = useComponentDefaults('TemplateEditor', props);\n  const text = useLabels('TemplateEditor', labels, DEFAULT_LABELS);\n  // Taken apart because `useLabels` returns a new object every render, and the\n  // chip context must stay stable for as long as the strings themselves do.\n  const { removeToken, qualifiedToken, unknownToken, unknownTokenTitle } = text;\n\n  const apiRef = useRef<TemplateEditableApi | null>(null);\n  const dragRef = useRef<TemplateDragPayload | null>(null);\n  const [previewOn, setPreviewOn] = useState(false);\n\n  const tokenIndex = useMemo(() => buildTokenIndex(tokenGroups), [tokenGroups]);\n  const chipLabels = useMemo(() => buildChipLabels(tokenGroups, qualifiedToken), [qualifiedToken, tokenGroups]);\n  const canPreview = !disabled && Array.from(tokenIndex.values()).some((token) => token.sample);\n  const preview = previewOn && canPreview;\n\n  const chipContext = useMemo<TemplateChipContext>(\n    () => ({\n      prefix,\n      suffix,\n      tokenIndex,\n      chipLabels,\n      labels: { removeToken, unknownToken, unknownTokenTitle },\n      preview,\n      chipClassName: cx(styles.templateEditorChip, classNames?.chip),\n      knownChipClassName: KNOWN_TONE,\n      unknownChipClassName: cx(styles.templateEditorChipUnknown, UNKNOWN_TONE),\n      previewChipClassName: styles.templateEditorChipPreview,\n      chipStyle: slotStyles?.chip,\n      // A remove button on a field that cannot be edited would be a dead control, and in the\n      // preview it would break up text that is meant to read as the message.\n      showRemove: !readOnly && !disabled && !preview,\n      removeClassName: cx(styles.templateEditorChipRemove, classNames?.chipRemove),\n      removeStyle: slotStyles?.chipRemove,\n    }),\n    [\n      chipLabels,\n      classNames?.chip,\n      classNames?.chipRemove,\n      disabled,\n      prefix,\n      preview,\n      readOnly,\n      removeToken,\n      slotStyles?.chip,\n      slotStyles?.chipRemove,\n      suffix,\n      tokenIndex,\n      unknownToken,\n      unknownTokenTitle,\n    ],\n  );\n\n  const handlePaletteInsert = useCallback((tokenId: string) => {\n    apiRef.current?.insertToken(tokenId);\n  }, []);\n\n  useImperativeHandle(ref, () => ({ insertToken: handlePaletteInsert }), [handlePaletteInsert]);\n\n  const isMenu = palettePosition === TemplateEditorPalettePosition.Menu;\n  const showPalette =\n    !readOnly &&\n    palettePosition !== TemplateEditorPalettePosition.Menu &&\n    palettePosition !== TemplateEditorPalettePosition.None;\n  const isBottom = palettePosition === TemplateEditorPalettePosition.Bottom;\n\n  return (\n    <div\n      {...rest}\n      className={cx(\n        styles.templateEditor,\n        isBottom ? styles.templateEditorBottom : styles.templateEditorRight,\n        classNames?.root,\n        className,\n      )}\n      style={{ ...slotStyles?.root, ...style }}\n    >\n      <div className={styles.templateEditorField}>\n        <Input\n          multiline={multiline}\n          label={label}\n          isRequired={isRequired}\n          isValid={isValid}\n          errorMessage={errorMessage}\n          formValidator={formValidator}\n          placeholder={placeholder}\n          disabled={disabled}\n          readOnly={readOnly}\n          value={value}\n          onChange={(next) => onChange?.(next)}\n          onFocus={onFocus}\n          onBlur={onBlur}\n          classNames={{\n            label: classNames?.label,\n            container: classNames?.container,\n            fieldset: classNames?.fieldset,\n          }}\n          styles={{\n            label: slotStyles?.label,\n            // Input gives a multiline container a resize grip, which here only stretched the frame:\n            // the editable grows with its text instead. Inline, to beat Input's one-class rule.\n            container: { resize: 'none', ...slotStyles?.container },\n            fieldset: slotStyles?.fieldset,\n          }}\n          customInput={\n            <TemplateEditable\n              apiRef={apiRef}\n              dragRef={dragRef}\n              chipContext={chipContext}\n              tokenGroups={tokenGroups}\n              ariaLabel={label || undefined}\n              multiline={multiline}\n              rows={rows}\n              autoFocus={autoFocus}\n              smartSpacing={smartSpacing}\n              menuLabel={isMenu ? text.insertToken : undefined}\n              previewLabel={canPreview ? text.preview : undefined}\n              onTogglePreview={() => setPreviewOn(!preview)}\n              classNames={classNames}\n              styles={slotStyles}\n            />\n          }\n        />\n      </div>\n      {showPalette && (\n        <TemplateEditorPalette\n          groups={tokenGroups}\n          label={paletteLabel}\n          labels={text}\n          position={palettePosition}\n          prefix={prefix}\n          suffix={suffix}\n          disabled={disabled}\n          dragRef={dragRef}\n          onInsert={handlePaletteInsert}\n          onHighlight={(tokenId) => apiRef.current?.highlightToken(tokenId)}\n          classNames={classNames}\n          styles={slotStyles}\n        />\n      )}\n    </div>\n  );\n}\n\nexport default TemplateEditor;\n"],"mappings":"ywBA2BA,IAAM,EAAa,EAAU,EAAW,OAAO,CAAC,CAAC,UAC3C,EAAe,EAAU,EAAW,IAAI,CAAC,CAAC,UAWhD,SAAS,EAAe,EAA4B,CAClD,GAAM,CACJ,QAAQ,GACR,WACA,cAAc,CAAC,EACf,kBAAkB,EAA8B,MAChD,eACA,SAAA,KACA,SAAA,KACA,QAAQ,GACR,cAAa,GACb,gBAAe,GACf,WAAU,GACV,iBAAgB,KAChB,eAAc,GACd,WAAW,GACX,WAAW,GACX,YAAY,GACZ,OAAO,EACP,YAAY,GACZ,eAAe,GACf,UACA,SACA,SACA,MACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,iBAAkB,CAAK,EAC1C,EAAO,EAAU,iBAAkB,EAAQ,CAAc,EAGzD,CAAE,cAAa,iBAAgB,eAAc,qBAAsB,EAEnE,EAAS,EAAmC,IAAI,EAChD,EAAU,EAAmC,IAAI,EACjD,CAAC,GAAW,IAAgB,EAAS,EAAK,EAE1C,EAAa,MAAc,EAAgB,CAAW,EAAG,CAAC,CAAW,CAAC,EACtE,EAAa,MAAc,GAAgB,EAAa,CAAc,EAAG,CAAC,EAAgB,CAAW,CAAC,EACtG,EAAa,CAAC,GAAY,MAAM,KAAK,EAAW,OAAO,CAAC,CAAC,CAAC,KAAM,GAAU,EAAM,MAAM,EACtF,EAAU,IAAa,EAEvB,GAAc,OACX,CACL,SACA,SACA,aACA,aACA,OAAQ,CAAE,cAAa,eAAc,mBAAkB,EACvD,UACA,cAAe,EAAG,EAAO,mBAAoB,GAAY,IAAI,EAC7D,mBAAoB,EACpB,qBAAsB,EAAG,EAAO,0BAA2B,CAAY,EACvE,qBAAsB,EAAO,0BAC7B,UAAW,GAAY,KAGvB,WAAY,CAAC,GAAY,CAAC,GAAY,CAAC,EACvC,gBAAiB,EAAG,EAAO,yBAA0B,GAAY,UAAU,EAC3E,YAAa,GAAY,UAC3B,GACA,CACE,EACA,GAAY,KACZ,GAAY,WACZ,EACA,EACA,EACA,EACA,EACA,GAAY,KACZ,GAAY,WACZ,EACA,EACA,EACA,CACF,CACF,EAEM,EAAsB,EAAa,GAAoB,CAC3D,EAAO,SAAS,YAAY,CAAO,CACrC,EAAG,CAAC,CAAC,EAEL,EAAoB,OAAY,CAAE,YAAa,CAAoB,GAAI,CAAC,CAAmB,CAAC,EAE5F,IAAM,GAAS,IAAoB,EAA8B,KAC3D,GACJ,CAAC,GACD,IAAoB,EAA8B,MAClD,IAAoB,EAA8B,KAC9C,GAAW,IAAoB,EAA8B,OAEnE,OACE,EAAC,MAAD,CACE,GAAI,EACJ,UAAW,EACT,EAAO,eACP,GAAW,EAAO,qBAAuB,EAAO,oBAChD,GAAY,KACZ,CACF,EACA,MAAO,CAAE,GAAG,GAAY,KAAM,GAAG,CAAM,EARzC,SAAA,CAUE,EAAC,MAAD,CAAK,UAAW,EAAO,oBACrB,SAAA,EAAC,GAAD,CACa,YACJ,QACK,cACH,WACK,gBACC,iBACF,eACH,WACA,WACH,QACP,SAAW,GAAS,IAAW,CAAI,EAC1B,UACD,SACR,WAAY,CACV,MAAO,GAAY,MACnB,UAAW,GAAY,UACvB,SAAU,GAAY,QACxB,EACA,OAAQ,CACN,MAAO,GAAY,MAGnB,UAAW,CAAE,OAAQ,OAAQ,GAAG,GAAY,SAAU,EACtD,SAAU,GAAY,QACxB,EACA,YACE,EAAC,EAAD,CACU,SACC,UACI,eACA,cACb,UAAW,GAAS,IAAA,GACT,YACL,OACK,YACG,eACd,UAAW,GAAS,EAAK,YAAc,IAAA,GACvC,aAAc,EAAa,EAAK,QAAU,IAAA,GAC1C,oBAAuB,GAAa,CAAC,CAAO,EAChC,aACZ,OAAQ,CACT,CAAA,CAEJ,CAAA,CACE,CAAA,EACJ,IACC,EAAC,EAAD,CACE,OAAQ,EACR,MAAO,EACP,OAAQ,EACR,SAAU,EACF,SACA,SACE,WACD,UACT,SAAU,EACV,YAAc,GAAY,EAAO,SAAS,eAAe,CAAO,EACpD,aACZ,OAAQ,CACT,CAAA,CAEA,GAET"}