{"version":3,"file":"emojis.mjs","names":[],"sources":["../../src/plugins/Emojis/EmojiPicker.tsx","../../src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts"],"sourcesContent":["import React, { useEffect, useState } from 'react';\nimport PickerImport from '@emoji-mart/react';\n\nimport { useMessageComposerContext, useTranslationContext } from '../../context';\nimport {\n  Button,\n  IconEmoji,\n  type PopperLikePlacement,\n  useMessageComposerController,\n} from '../../components';\nimport { usePopoverPosition } from '../../components/Dialog/hooks/usePopoverPosition';\nimport { useIsCooldownActive } from '../../components/MessageComposer/hooks/useIsCooldownActive';\n\n// @emoji-mart/react ships as CJS with the component on `exports.default`. Under\n// spec-strict ESM interop (e.g. Vite 8 / Rolldown, native Node ESM) a default\n// import yields the module namespace `{ default }` instead of the component,\n// which makes React throw \"Element type is invalid ... got: object\". Unwrap the\n// default defensively so it works regardless of interop.\nconst Picker =\n  (PickerImport as unknown as { default?: typeof PickerImport }).default ?? PickerImport;\n\nconst isShadowRoot = (node: Node): node is ShadowRoot => !!(node as ShadowRoot).host;\n\nexport type EmojiPickerProps = {\n  ButtonIconComponent?: React.ComponentType;\n  buttonClassName?: string;\n  pickerContainerClassName?: string;\n  wrapperClassName?: string;\n  closeOnEmojiSelect?: boolean;\n  /**\n   * Untyped [properties](https://github.com/missive/emoji-mart/tree/v5.5.2#options--props) to be\n   * passed down to the [emoji-mart `Picker`](https://github.com/missive/emoji-mart/tree/v5.5.2#-picker) component\n   */\n  pickerProps?: Partial<{ theme: 'auto' | 'light' | 'dark' } & Record<string, unknown>>;\n  /**\n   * Floating UI placement (default: 'top-end') for the picker popover\n   */\n  placement?: PopperLikePlacement;\n};\n\nconst defaultButtonClassName = 'str-chat__emoji-picker-button';\n\nconst classNames: Pick<\n  EmojiPickerProps,\n  'pickerContainerClassName' | 'wrapperClassName'\n> = {\n  pickerContainerClassName: 'str-chat__message-textarea-emoji-picker-container',\n  wrapperClassName: 'str-chat__message-textarea-emoji-picker',\n};\n\nexport const EmojiPicker = (props: EmojiPickerProps) => {\n  const { t } = useTranslationContext('EmojiPicker');\n  const { textareaRef } = useMessageComposerContext('EmojiPicker');\n  const { textComposer } = useMessageComposerController();\n  const isCooldownActive = useIsCooldownActive();\n  const [displayPicker, setDisplayPicker] = useState(false);\n  const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(\n    null,\n  );\n  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);\n  const { refs, strategy, x, y } = usePopoverPosition({\n    offset: 8,\n    placement: props.placement ?? 'top-end',\n  });\n\n  useEffect(() => {\n    refs.setReference(referenceElement);\n  }, [referenceElement, refs]);\n  useEffect(() => {\n    refs.setFloating(popperElement);\n  }, [popperElement, refs]);\n\n  const { pickerContainerClassName, wrapperClassName } = classNames;\n\n  const { ButtonIconComponent = IconEmoji } = props;\n  const pickerStyle = props.pickerProps?.style as React.CSSProperties | undefined;\n\n  useEffect(() => {\n    if (!popperElement || !referenceElement) return;\n\n    const handlePointerDown = (e: PointerEvent) => {\n      const target = e.target as HTMLElement;\n\n      const rootNode = target.getRootNode();\n\n      if (\n        popperElement.contains(isShadowRoot(rootNode) ? rootNode.host : target) ||\n        referenceElement.contains(target)\n      ) {\n        return;\n      }\n\n      setDisplayPicker(false);\n    };\n\n    window.addEventListener('pointerdown', handlePointerDown);\n    return () => window.removeEventListener('pointerdown', handlePointerDown);\n  }, [referenceElement, popperElement]);\n\n  return (\n    <div className={props.wrapperClassName ?? wrapperClassName}>\n      {displayPicker && (\n        <div\n          className={props.pickerContainerClassName ?? pickerContainerClassName}\n          ref={setPopperElement}\n          style={{ left: x ?? 0, position: strategy, top: y ?? 0 }}\n        >\n          <Picker\n            data={async () => (await import('@emoji-mart/data')).default}\n            onEmojiSelect={(e: { native: string }) => {\n              const textarea = textareaRef.current;\n              if (!textarea) return;\n              textComposer.insertText({ text: e.native });\n              textarea.focus();\n              if (props.closeOnEmojiSelect) {\n                setDisplayPicker(false);\n              }\n            }}\n            {...props.pickerProps}\n            style={{ ...pickerStyle, '--shadow': 'none' }}\n          />\n        </div>\n      )}\n      <Button\n        appearance='ghost'\n        aria-expanded={displayPicker}\n        aria-label={t('aria/Emoji picker')}\n        circular\n        className={props.buttonClassName ?? defaultButtonClassName}\n        disabled={isCooldownActive}\n        onClick={() => setDisplayPicker((cv) => !cv)}\n        ref={setReferenceElement}\n        size='sm'\n        type='button'\n        variant='secondary'\n      >\n        {ButtonIconComponent && <ButtonIconComponent />}\n      </Button>\n    </div>\n  );\n};\n","import mergeWith from 'lodash.mergewith';\nimport type {\n  Middleware,\n  SearchSourceOptions,\n  SearchSourceType,\n  TextComposerMiddlewareExecutorState,\n  TextComposerMiddlewareOptions,\n  TextComposerSuggestion,\n} from 'stream-chat';\nimport {\n  BaseSearchSource,\n  getTokenizedSuggestionDisplayName,\n  getTriggerCharWithToken,\n  insertItemWithTrigger,\n  replaceWordWithEntity,\n} from 'stream-chat';\nimport type {\n  EmojiSearchIndex,\n  EmojiSearchIndexResult,\n} from '../../../components/MessageComposer';\n\nexport type EmojiSuggestion<T extends EmojiSearchIndexResult = EmojiSearchIndexResult> =\n  TextComposerSuggestion<T>;\n\nclass EmojiSearchSource<\n  T extends TextComposerSuggestion<EmojiSearchIndexResult>,\n> extends BaseSearchSource<T> {\n  readonly type: SearchSourceType = 'emoji';\n  private emojiSearchIndex: EmojiSearchIndex;\n\n  constructor(emojiSearchIndex: EmojiSearchIndex, options?: SearchSourceOptions) {\n    super(options);\n    this.emojiSearchIndex = emojiSearchIndex;\n  }\n\n  async query(searchQuery: string) {\n    if (searchQuery.length === 0) {\n      return { items: [] as T[], next: null };\n    }\n    const emojis = (await this.emojiSearchIndex.search(searchQuery)) ?? [];\n\n    // emojiIndex.search sometimes returns undefined values, so filter those out first\n    return {\n      items: emojis\n        .filter(Boolean)\n        .slice(0, 7)\n        .map(({ emoticons = [], id, name, native, skins = [] }) => {\n          const [firstSkin] = skins;\n\n          return {\n            emoticons,\n            id,\n            name,\n            native: native ?? firstSkin.native,\n          };\n        }) as T[],\n      next: null, // todo: generate cursor\n    };\n  }\n\n  protected filterQueryResults(items: T[]): T[] | Promise<T[]> {\n    return items.map((item) => ({\n      ...item,\n      ...getTokenizedSuggestionDisplayName({\n        displayName: item.id,\n        searchToken: this.searchQuery,\n      }),\n    }));\n  }\n}\n\nconst DEFAULT_OPTIONS: TextComposerMiddlewareOptions = { minChars: 1, trigger: ':' };\n\nexport type EmojiMiddleware<T extends EmojiSearchIndexResult = EmojiSearchIndexResult> =\n  Middleware<\n    TextComposerMiddlewareExecutorState<EmojiSuggestion<T>>,\n    'onChange' | 'onSuggestionItemSelect'\n  >;\n\n/**\n * TextComposer middleware for mentions\n * Usage:\n *\n *  const textComposer = new TextComposer(options);\n *\n *  textComposer.use(new createTextComposerEmojiMiddleware(emojiSearchIndex, {\n *   minChars: 2\n *  }));\n *\n * @param emojiSearchIndex\n * @param {{\n *     minChars: number;\n *     trigger: string;\n *   }} options\n * @returns\n */\nexport const createTextComposerEmojiMiddleware = (\n  emojiSearchIndex: EmojiSearchIndex,\n  options?: Partial<TextComposerMiddlewareOptions>,\n): EmojiMiddleware => {\n  const finalOptions = mergeWith(DEFAULT_OPTIONS, options ?? {});\n  const emojiSearchSource = new EmojiSearchSource(emojiSearchIndex);\n  emojiSearchSource.activate();\n\n  return {\n    id: 'stream-io/emoji-middleware',\n    // eslint-disable-next-line sort-keys\n    handlers: {\n      onChange: async ({ complete, forward, next, state }) => {\n        if (!state.selection) return forward();\n\n        const triggerWithToken = getTriggerCharWithToken({\n          acceptTrailingSpaces: false,\n          text: state.text.slice(0, state.selection.end),\n          trigger: finalOptions.trigger,\n        });\n\n        const triggerWasRemoved =\n          !triggerWithToken || triggerWithToken.length < finalOptions.minChars;\n\n        if (triggerWasRemoved) {\n          const hasSuggestionsForTrigger =\n            state.suggestions?.trigger === finalOptions.trigger;\n          const newState = { ...state };\n          if (hasSuggestionsForTrigger && newState.suggestions) {\n            delete newState.suggestions;\n          }\n          return next(newState);\n        }\n\n        const newSearchTriggerred =\n          triggerWithToken && triggerWithToken === finalOptions.trigger;\n\n        if (newSearchTriggerred) {\n          emojiSearchSource.resetStateAndActivate();\n        }\n\n        const textWithReplacedWord = await replaceWordWithEntity({\n          caretPosition: state.selection.end,\n          getEntityString: async (word: string) => {\n            const { items } = await emojiSearchSource.query(word);\n\n            const emoji = items\n              .filter(Boolean)\n              .slice(0, 10)\n              .find(({ emoticons }) => !!emoticons?.includes(word));\n\n            if (!emoji) return null;\n\n            const [firstSkin] = emoji.skins ?? [];\n\n            return emoji.native ?? firstSkin.native;\n          },\n          text: state.text,\n        });\n\n        if (textWithReplacedWord !== state.text) {\n          return complete({\n            ...state,\n            suggestions: undefined, // to prevent the TextComposerMiddlewareExecutor to run the first page query\n            text: textWithReplacedWord,\n          });\n        }\n\n        return complete({\n          ...state,\n          suggestions: {\n            query: triggerWithToken.slice(1),\n            searchSource: emojiSearchSource,\n            trigger: finalOptions.trigger,\n          },\n        });\n      },\n      onSuggestionItemSelect: ({ complete, forward, state }) => {\n        const { selectedSuggestion } = state.change ?? {};\n        if (!selectedSuggestion || state.suggestions?.trigger !== finalOptions.trigger)\n          return forward();\n\n        emojiSearchSource.resetStateAndActivate();\n        return complete({\n          ...state,\n          ...insertItemWithTrigger({\n            insertText: `${'native' in selectedSuggestion ? selectedSuggestion.native : ''} `,\n            selection: state.selection,\n            text: state.text,\n            trigger: finalOptions.trigger,\n          }),\n          suggestions: undefined, // Clear suggestions after selection\n        });\n      },\n    },\n  };\n};\n"],"mappings":"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"}