import React, { useEffect, useRef, useCallback } from 'react' let emojiMartPromise = null function ensureEmojiMart() { if (!emojiMartPromise) { emojiMartPromise = import('emoji-mart') } return emojiMartPromise } export default function EmojiMartPicker({ data, onEmojiSelect, onClickOutside, theme = 'auto', previewPosition = 'bottom', skinTonePosition = 'preview', maxFrequentRows = 4, perLine = 9, navPosition = 'top', set = 'native', locale = 'en', autoFocus = false, searchPosition, dynamicWidth, noCountryFlags, className = '', }) { const hostRef = useRef(null) const pickerRef = useRef(null) // Keep refs pointing at the latest callback props so stable wrappers // never capture a stale closure. const onEmojiSelectRef = useRef(onEmojiSelect) const onClickOutsideRef = useRef(onClickOutside) onEmojiSelectRef.current = onEmojiSelect onClickOutsideRef.current = onClickOutside // Stable wrappers with fixed identity — safe to pass once to the Picker // constructor without needing to re-initialise the element on every render. const stableOnEmojiSelect = useCallback((emoji) => { onEmojiSelectRef.current?.(emoji) }, []) const stableOnClickOutside = useCallback((e) => { onClickOutsideRef.current?.(e) }, []) useEffect(() => { let cancelled = false // emoji-mart's Picker stores callbacks in `this.props` during construction. // connectedCallback reads from `this.props`, NOT from plain element // properties set after construction, so we MUST use `new Picker(props)` // rather than `document.createElement('em-emoji-picker')` + property // assignment, which would leave onEmojiSelect as null internally. ensureEmojiMart().then(({ Picker }) => { if (cancelled || !hostRef.current) return if (!pickerRef.current) { const pickerProps = { data, onEmojiSelect: stableOnEmojiSelect, onClickOutside: stableOnClickOutside, theme, previewPosition, skinTonePosition, maxFrequentRows, perLine, navPosition, set, locale, autoFocus, } if (searchPosition !== undefined) pickerProps.searchPosition = searchPosition if (dynamicWidth !== undefined) pickerProps.dynamicWidth = dynamicWidth if (noCountryFlags !== undefined) pickerProps.noCountryFlags = noCountryFlags const el = new Picker(pickerProps) pickerRef.current = el hostRef.current.replaceChildren(el) } }) return () => { cancelled = true } // Run once on mount. Callbacks stay fresh via refs; static display options // (theme, perLine, etc.) don't change during a single picker session. // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { return () => { if (hostRef.current) { hostRef.current.replaceChildren() } pickerRef.current = null } }, []) return
}