{"version":3,"file":"emojis.mjs","sources":["../../src/plugins/Emojis/EmojiPicker.tsx","../../src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts"],"sourcesContent":["import React, { useEffect, useState } from 'react';\nimport Picker 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\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"],"names":[],"mappings":"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"}