@liveblocks/react-ui 2.8.1 → 2.8.2-tiptap1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Comment.js +1 -1
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Comment.mjs +1 -1
- package/dist/components/Comment.mjs.map +1 -1
- package/dist/components/InboxNotification.js +23 -16
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/InboxNotification.mjs +23 -16
- package/dist/components/InboxNotification.mjs.map +1 -1
- package/dist/components/internal/InboxNotificationThread.js +3 -0
- package/dist/components/internal/InboxNotificationThread.js.map +1 -1
- package/dist/components/internal/InboxNotificationThread.mjs +3 -0
- package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
- package/dist/primitives/EmojiPicker/index.js +14 -3
- package/dist/primitives/EmojiPicker/index.js.map +1 -1
- package/dist/primitives/EmojiPicker/index.mjs +14 -3
- package/dist/primitives/EmojiPicker/index.mjs.map +1 -1
- package/dist/primitives/EmojiPicker/utils.js +3 -2
- package/dist/primitives/EmojiPicker/utils.js.map +1 -1
- package/dist/primitives/EmojiPicker/utils.mjs +3 -2
- package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
- package/dist/slate/plugins/auto-links.js +16 -9
- package/dist/slate/plugins/auto-links.js.map +1 -1
- package/dist/slate/plugins/auto-links.mjs +16 -9
- package/dist/slate/plugins/auto-links.mjs.map +1 -1
- package/dist/slate/plugins/paste.js.map +1 -1
- package/dist/slate/plugins/paste.mjs.map +1 -1
- package/dist/slate/utils/is-empty.js +1 -1
- package/dist/slate/utils/is-empty.js.map +1 -1
- package/dist/slate/utils/is-empty.mjs +1 -1
- package/dist/slate/utils/is-empty.mjs.map +1 -1
- package/dist/utils/find-last-index.js +2 -1
- package/dist/utils/find-last-index.js.map +1 -1
- package/dist/utils/find-last-index.mjs +2 -1
- package/dist/utils/find-last-index.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../src/primitives/EmojiPicker/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type { ChangeEvent, KeyboardEvent, SyntheticEvent } from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n CalculateViewLocationParams,\n GroupedVirtuosoHandle,\n ListProps as VirtuosoListProps,\n ScrollerProps,\n TopItemListProps,\n} from \"react-virtuoso\";\nimport { GroupedVirtuoso } from \"react-virtuoso\";\n\nimport { isKey } from \"../../utils/is-key\";\nimport {\n cancelIdleCallback,\n requestIdleCallback,\n} from \"../../utils/request-idle-callback\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useTransition } from \"../../utils/use-transition\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport { Emoji as EmojiPrimitive } from \"../internal/Emoji\";\nimport { EmojiPickerContext, useEmojiPicker } from \"./contexts\";\nimport type {\n EmojiData,\n EmojiPickerContentComponents,\n EmojiPickerContentEmojiRowAttributes,\n EmojiPickerContentProps,\n EmojiPickerData,\n EmojiPickerRootProps,\n EmojiPickerSearchProps,\n EmojiPickerSelectionDirection,\n} from \"./types\";\nimport { filterEmojis, generateEmojiPickerData, getEmojiData } from \"./utils\";\n\nconst DEFAULT_COLUMNS = 10;\nconst DEFAULT_LOCALE = \"en\";\nconst LOADING_MINIMUM_TIMEOUT = 100;\n\nconst EMOJIPICKER_ROOT_NAME = \"EmojiPickerRoot\";\nconst EMOJIPICKER_CONTENT_NAME = \"EmojiPickerContent\";\nconst EMOJIPICKER_SEARCH_NAME = \"EmojiPickerSearch\";\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * Surrounds the emoji picker, it handles emoji data and coordinates\n * `EmojiPicker.Search` and `EmojiPicker.Content`.\n *\n * @example\n * <EmojiPicker.Root>\n * <EmojiPicker.Search />\n * <EmojiPicker.Content />\n * </EmojiPicker.Root>\n */\nfunction EmojiPickerRoot({\n columns = DEFAULT_COLUMNS,\n locale = DEFAULT_LOCALE,\n onEmojiSelect,\n children,\n}: EmojiPickerRootProps) {\n const emojiData = useRef<EmojiData>();\n const search = useRef(\"\");\n const [, startEmojisTransition] = useTransition();\n const [data, setData] = useState<EmojiPickerData>();\n const [error, setError] = useState<Error>();\n const [selectedColumnIndex, setSelectedColumnIndex] = useState(0);\n const [selectedRowIndex, setSelectedRowIndex] = useState(0);\n const [interaction, setInteraction] = useState<\n \"keyboard\" | \"pointer\" | \"none\"\n >(\"none\");\n\n const selectCurrentEmoji = useCallback(() => {\n if (onEmojiSelect) {\n const emoji = data?.rows[selectedRowIndex]?.[selectedColumnIndex];\n\n if (emoji) {\n onEmojiSelect(emoji.emoji);\n }\n }\n }, [data?.rows, onEmojiSelect, selectedColumnIndex, selectedRowIndex]);\n\n const resetSelection = useCallback(() => {\n setSelectedColumnIndex(0);\n setSelectedRowIndex(0);\n }, []);\n\n const setPointerSelection = useCallback(\n (columnIndex: number, rowIndex: number) => {\n setInteraction(\"pointer\");\n setSelectedColumnIndex(columnIndex);\n setSelectedRowIndex(rowIndex);\n },\n []\n );\n\n const moveSelection = useCallback(\n (\n direction: EmojiPickerSelectionDirection,\n event: KeyboardEvent<HTMLInputElement>\n ) => {\n if (!data) {\n return;\n }\n\n event.preventDefault();\n\n if (interaction === \"none\") {\n setInteraction(\"keyboard\");\n return;\n }\n\n setInteraction(\"keyboard\");\n\n switch (direction) {\n // If first column, move to last column of previous row (if available)\n // Otherwise, move to previous column\n case \"left\": {\n if (selectedColumnIndex === 0) {\n const previousRowIndex = selectedRowIndex - 1;\n const previousRow = data.rows[previousRowIndex];\n\n if (previousRow) {\n setSelectedRowIndex(previousRowIndex);\n setSelectedColumnIndex(previousRow.length - 1);\n }\n } else {\n setSelectedColumnIndex(selectedColumnIndex - 1);\n }\n\n break;\n }\n\n // If last column, move to first column of next row (if available)\n // Otherwise, move to next column\n case \"right\": {\n const currentRow = data.rows[selectedRowIndex];\n\n if (selectedColumnIndex === currentRow.length - 1) {\n const nextRowIndex = selectedRowIndex + 1;\n const nextRow = data.rows[nextRowIndex];\n\n if (nextRow) {\n setSelectedRowIndex(nextRowIndex);\n setSelectedColumnIndex(0);\n }\n } else {\n setSelectedColumnIndex(selectedColumnIndex + 1);\n }\n\n break;\n }\n\n // Move to same column of previous row\n // If same column is not available, move to last column of previous row\n case \"up\": {\n const previousRow = data.rows[selectedRowIndex - 1];\n\n if (previousRow) {\n setSelectedRowIndex(selectedRowIndex - 1);\n\n if (!previousRow[selectedColumnIndex]) {\n setSelectedColumnIndex(previousRow.length - 1);\n }\n }\n\n break;\n }\n\n // Move to same column of next row\n // If same column is not available, move to last column of next row\n case \"down\": {\n const nextRow = data.rows[selectedRowIndex + 1];\n\n if (nextRow) {\n setSelectedRowIndex(selectedRowIndex + 1);\n\n if (!nextRow[selectedColumnIndex]) {\n setSelectedColumnIndex(nextRow.length - 1);\n }\n }\n\n break;\n }\n }\n },\n [data, interaction, selectedColumnIndex, selectedRowIndex]\n );\n\n const updateEmojis = useCallback(() => {\n if (!emojiData.current) {\n return;\n }\n\n startEmojisTransition(() => {\n setData(() => {\n if (!emojiData.current) {\n return;\n }\n\n const filteredEmojis = filterEmojis(\n emojiData.current.emojis,\n search.current\n );\n\n return generateEmojiPickerData(\n filteredEmojis,\n emojiData.current.categories,\n columns\n );\n });\n resetSelection();\n });\n }, [columns, resetSelection]);\n\n const handleSearch = useCallback(\n (value: string) => {\n search.current = value;\n updateEmojis();\n },\n [updateEmojis]\n );\n\n const initializeEmojiData = useCallback(\n async (locale: string) => {\n try {\n emojiData.current = await getEmojiData(locale);\n updateEmojis();\n } catch (error) {\n setError(error as Error);\n }\n },\n [updateEmojis]\n );\n\n useEffect(() => {\n let idleCallbackId: number;\n const timeoutId = setTimeout(() => {\n idleCallbackId = requestIdleCallback(() => {\n initializeEmojiData(locale);\n });\n }, LOADING_MINIMUM_TIMEOUT);\n\n return () => {\n clearTimeout(timeoutId);\n cancelIdleCallback(idleCallbackId);\n };\n }, [locale]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (interaction === \"none\") {\n resetSelection();\n }\n }, [interaction]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <EmojiPickerContext.Provider\n value={{\n data: data as EmojiPickerData,\n error: error as undefined,\n isLoading: (!data && !error) as false,\n columns,\n onSearch: handleSearch,\n onEmojiSelect,\n selectCurrentEmoji,\n selectedRowIndex,\n selectedColumnIndex,\n moveSelection,\n setPointerSelection,\n interaction,\n setInteraction,\n }}\n >\n {children}\n </EmojiPickerContext.Provider>\n );\n}\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * The search input of the emoji picker. It also affects the focus and selection\n * within `EmojiPicker.Content`.\n *\n * @example\n * <EmojiPicker.Search />\n */\nconst EmojiPickerSearch = forwardRef<HTMLInputElement, EmojiPickerSearchProps>(\n ({ asChild, value, defaultValue, onChange, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"input\";\n const {\n onSearch,\n selectCurrentEmoji,\n moveSelection,\n interaction,\n setInteraction,\n } = useEmojiPicker();\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n onChange?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const value = event.target.value;\n setInteraction(value ? \"keyboard\" : \"none\");\n onSearch(value);\n },\n [onChange, onSearch, setInteraction]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isKey(event, \"ArrowLeft\")) {\n moveSelection(\"left\", event);\n } else if (isKey(event, \"ArrowRight\")) {\n moveSelection(\"right\", event);\n } else if (isKey(event, \"ArrowUp\")) {\n moveSelection(\"up\", event);\n } else if (isKey(event, \"ArrowDown\")) {\n moveSelection(\"down\", event);\n } else if (isKey(event, \"Enter\")) {\n if (interaction !== \"none\") {\n event.preventDefault();\n selectCurrentEmoji();\n }\n }\n },\n [interaction, moveSelection, selectCurrentEmoji]\n );\n\n useEffect(() => {\n onSearch(\n value ? String(value) : defaultValue ? String(defaultValue) : \"\"\n );\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <Component\n type=\"search\"\n value={value}\n defaultValue={defaultValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n);\n\nconst defaultContentComponents: EmojiPickerContentComponents = {\n CategoryHeader: ({ category, ...props }) => <div {...props}>{category}</div>,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Row: ({ children, attributes, ...props }) => <div {...props}>{children}</div>,\n Emoji: ({ emoji, ...props }) => (\n <button {...props}>\n <EmojiPrimitive emoji={emoji} />\n </button>\n ),\n Loading: (props) => <div {...props} />,\n Empty: (props) => <div {...props} />,\n Grid: (props) => <div {...props} />,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Error: ({ error, ...props }) => <div {...props} />,\n};\n\nconst placeholderRowAttributes: EmojiPickerContentEmojiRowAttributes = {\n rowIndex: -1,\n categoryRowIndex: -1,\n categoryRowsCount: 0,\n};\n\n// About `data-testid={undefined}`: Virtuoso bakes test IDs into the components we pass\n// to it, so we manually remove them.\n\nconst VirtuosoScroller = forwardRef<HTMLDivElement, ScrollerProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div {...props} tabIndex={-1} data-testid={undefined} ref={forwardedRef}>\n {children}\n </div>\n );\n }\n);\n\nconst VirtuosoTopList = forwardRef<HTMLDivElement, TopItemListProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div {...props} data-testid={undefined} ref={forwardedRef}>\n {children}\n </div>\n );\n }\n);\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * The main content of the emoji picker, either displaying the emoji grid or various\n * alternative states (loading, empty, and error).\n *\n * @example\n * <EmojiPicker.Content\n * components={{\n * Loading: EmojiPickerLoading,\n * Empty: EmojiPickerEmpty,\n * Error: EmojiPickerError,\n * CategoryHeader: EmojiPickerCategoryHeader,\n * Grid: EmojiPickerGrid,\n * Row: EmojiPickerRow,\n * Emoji: EmojiPickerEmoji,\n * }}\n * />\n */\nconst EmojiPickerContent = forwardRef<HTMLDivElement, EmojiPickerContentProps>(\n ({ components, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"div\";\n const virtuosoRef = useRef<GroupedVirtuosoHandle>(null);\n const placeholderContainerRef = useRef<HTMLDivElement>(null);\n const rowScrollMarginTopRef = useRef<number>(0);\n const rowScrollMarginBottomRef = useRef<number>(0);\n const categoryHeaderHeightRef = useRef<number>(0);\n const {\n data,\n error,\n isLoading,\n columns,\n onEmojiSelect,\n selectedColumnIndex,\n selectedRowIndex,\n setPointerSelection,\n interaction,\n setInteraction,\n } = useEmojiPicker();\n const selectedEmoji = useMemo(\n () => data?.rows[selectedRowIndex]?.[selectedColumnIndex],\n [data?.rows, selectedColumnIndex, selectedRowIndex]\n );\n const { Loading, Empty, Error, CategoryHeader, Grid, Row, Emoji } = useMemo(\n () => ({ ...defaultContentComponents, ...components }),\n [components]\n );\n const VirtuosoList = useMemo(\n () =>\n forwardRef<HTMLDivElement, VirtuosoListProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div\n role=\"grid\"\n aria-colcount={columns}\n {...props}\n data-testid={undefined}\n ref={forwardedRef}\n >\n {children}\n </div>\n );\n }\n ),\n [columns]\n );\n const placeholderColumns = useMemo(\n () => Array<string>(columns).fill(\"🌫️\"),\n [columns]\n );\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleEmojiPointerLeave = useCallback(() => {\n if (interaction === \"pointer\") {\n setInteraction(\"none\");\n }\n }, [interaction, setInteraction]);\n\n useLayoutEffect(() => {\n if (!placeholderContainerRef.current) {\n return;\n }\n\n const row = placeholderContainerRef.current.childNodes[0];\n const categoryHeader = placeholderContainerRef.current.childNodes[1];\n\n if (row instanceof HTMLElement) {\n const style = window.getComputedStyle(row);\n\n rowScrollMarginTopRef.current = parseFloat(style.scrollMarginTop);\n rowScrollMarginBottomRef.current = parseFloat(style.scrollMarginBottom);\n }\n\n if (categoryHeader instanceof HTMLElement) {\n categoryHeaderHeightRef.current = categoryHeader.offsetHeight;\n }\n }, []);\n\n // Customize `scrollIntoView` behavior to take into account category headers and margins\n const calculateViewLocation = useCallback(\n ({\n itemTop,\n itemBottom,\n viewportTop,\n viewportBottom,\n locationParams: { behavior, align, ...params },\n }: CalculateViewLocationParams) => {\n if (\n itemTop -\n (categoryHeaderHeightRef.current + rowScrollMarginTopRef.current) <\n viewportTop\n ) {\n return {\n ...params,\n behavior,\n align: align ?? \"start\",\n };\n }\n\n if (itemBottom > viewportBottom) {\n return {\n ...params,\n behavior,\n align: align ?? \"end\",\n offset: rowScrollMarginBottomRef.current,\n };\n }\n\n return null;\n },\n []\n );\n\n useEffect(() => {\n if (interaction === \"keyboard\") {\n virtuosoRef.current?.scrollIntoView({\n index: selectedRowIndex,\n behavior: \"auto\",\n calculateViewLocation,\n });\n }\n }, [interaction, selectedRowIndex, calculateViewLocation]);\n\n return (\n <Component {...props} ref={forwardedRef}>\n <div\n style={{\n visibility: \"hidden\",\n height: 0,\n }}\n ref={placeholderContainerRef}\n >\n {/* Virtualized rows are absolutely positioned so they won't make\n the container automatically pick up their width. To achieve\n an automatic width, we add a relative (but hidden) full row. */}\n <Row attributes={placeholderRowAttributes}>\n {placeholderColumns.map((placeholder, index) => (\n <Emoji emoji={placeholder} key={index} />\n ))}\n </Row>\n {/* We also add a hidden category header to get its computed height. */}\n <CategoryHeader category=\"Category\" />\n </div>\n {isLoading ? (\n <Loading />\n ) : error ? (\n <Error error={error} />\n ) : data.count === 0 ? (\n <Empty />\n ) : (\n <Grid>\n <GroupedVirtuoso\n ref={virtuosoRef}\n components={{\n Scroller: VirtuosoScroller,\n List: VirtuosoList,\n TopItemList: VirtuosoTopList,\n }}\n groupCounts={data.categoriesRowCounts}\n groupContent={(groupIndex) => {\n return (\n <CategoryHeader category={data.categories[groupIndex]} />\n );\n }}\n itemContent={(rowIndex, groupIndex) => {\n const categoryRowIndex =\n data.categoriesRowIndices[groupIndex].indexOf(rowIndex);\n const categoryRowsCount = data.categoriesRowCounts[groupIndex];\n\n return (\n <Row\n attributes={{\n rowIndex,\n categoryRowIndex,\n categoryRowsCount,\n }}\n >\n {data.rows[rowIndex].map((emoji, columnIndex) => {\n const isSelected =\n interaction !== \"none\" &&\n selectedColumnIndex === columnIndex &&\n selectedRowIndex === rowIndex;\n\n return (\n <Emoji\n key={emoji.emoji}\n role=\"gridcell\"\n aria-colindex={columnIndex}\n aria-selected={isSelected || undefined}\n data-selected={isSelected || undefined}\n onMouseDown={preventDefault}\n tabIndex={-1}\n onPointerEnter={() => {\n setPointerSelection(columnIndex, rowIndex);\n }}\n onPointerLeave={handleEmojiPointerLeave}\n onClick={(event) => {\n onEmojiSelect?.(emoji.emoji);\n event.stopPropagation();\n }}\n emoji={emoji.emoji}\n />\n );\n })}\n </Row>\n );\n }}\n />\n </Grid>\n )}\n {selectedEmoji && interaction !== \"none\" && (\n <div aria-live=\"polite\" style={visuallyHidden}>\n {selectedEmoji.name}\n </div>\n )}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n EmojiPickerRoot.displayName = EMOJIPICKER_ROOT_NAME;\n EmojiPickerContent.displayName = EMOJIPICKER_CONTENT_NAME;\n EmojiPickerSearch.displayName = EMOJIPICKER_SEARCH_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as EmojiPicker.*\nexport {\n EmojiPickerContent as Content,\n EmojiPickerRoot as Root,\n EmojiPickerSearch as Search,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;AA2CA;AACA;AACA;AAEA;AACA;AACA;AAkBA;AAAyB;AACb;AACD;AACT;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACE;AACE;AAEA;AACE;AAAyB;AAC3B;AACF;AAGF;AACE;AACA;AAAqB;AAGvB;AAA4B;AAExB;AACA;AACA;AAA4B;AAC9B;AACC;AAGH;AAAsB;AAKlB;AACE;AAAA;AAGF;AAEA;AACE;AACA;AAAA;AAGF;AAEA;AAAmB;AAIf;AACE;AACA;AAEA;AACE;AACA;AAA6C;AAC/C;AAEA;AAA8C;AAGhD;AAAA;AACF;AAKE;AAEA;AACE;AACA;AAEA;AACE;AACA;AAAwB;AAC1B;AAEA;AAA8C;AAGhD;AAAA;AACF;AAKE;AAEA;AACE;AAEA;AACE;AAA6C;AAC/C;AAGF;AAAA;AACF;AAKE;AAEA;AACE;AAEA;AACE;AAAyC;AAC3C;AAGF;AAAA;AACF;AACF;AACF;AACyD;AAG3D;AACE;AACE;AAAA;AAGF;AACE;AACE;AACE;AAAA;AAGF;AAAuB;AACH;AACX;AAGT;AAAO;AACL;AACkB;AAClB;AACF;AAEF;AAAe;AAChB;AAGH;AAAqB;AAEjB;AACA;AAAa;AACf;AACa;AAGf;AAA4B;AAExB;AACE;AACA;AAAa;AAEb;AAAuB;AACzB;AACF;AACa;AAGf;AACE;AACA;AACE;AACE;AAA0B;AAC3B;AAGH;AACE;AACA;AAAiC;AACnC;AAGF;AACE;AACE;AAAe;AACjB;AAGF;AACG;AACQ;AACL;AACA;AACsB;AACtB;AACU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAKN;AAeA;AAA0B;AAEtB;AACA;AAAM;AACJ;AACA;AACA;AACA;AACA;AAGF;AAAqB;AAEjB;AAEA;AACE;AAAA;AAGF;AACA;AACA;AAAc;AAChB;AACmC;AAGrC;AAAsB;AAElB;AACE;AAAA;AAGF;AACE;AAA2B;AAE3B;AAA4B;AAE5B;AAAyB;AAEzB;AAA2B;AAE3B;AACE;AACA;AAAmB;AACrB;AACF;AACF;AAC+C;AAGjD;AACE;AAAA;AACgE;AAChE;AAGF;AACG;AACM;AACL;AACA;AACU;AACC;AACP;AACC;AACP;AAGN;AAEA;AAA+D;AAChB;AAAQ;AAAiB;AAExB;AAAQ;AAAiB;AAEpE;AAAW;AACT;AAAe;AAClB;AAEmB;AAAQ;AAAO;AACjB;AAAQ;AAAO;AAChB;AAAQ;AAAO;AAEA;AAAQ;AAC3C;AAEA;AAAuE;AAC3D;AACQ;AAEpB;AAKA;AAAyB;AAErB;AACG;AAAQ;AAAiB;AAAiB;AAAgB;AAE3D;AAGN;AAEA;AAAwB;AAEpB;AACG;AAAQ;AAAoB;AAAgB;AAE7C;AAGN;AAyBA;AAA2B;AAEvB;AACA;AACA;AACA;AACA;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AAAsB;AACiB;AACa;AAEpD;AAAoE;AACd;AACzC;AAEb;AAAqB;AAEjB;AAEI;AACG;AACM;AACU;AACX;AACS;AACR;AAGP;AAEJ;AACF;AACM;AAEV;AAA2B;AACc;AAC/B;AAGV;AACE;AAAqB;AAGvB;AACE;AACE;AAAqB;AACvB;AAGF;AACE;AACE;AAAA;AAGF;AACA;AAEA;AACE;AAEA;AACA;AAAsE;AAGxE;AACE;AAAiD;AACnD;AAIF;AAA8B;AAC3B;AACC;AACA;AACA;AACA;AAC6C;AAE7C;AAKE;AAAO;AACF;AACH;AACgB;AAClB;AAGF;AACE;AAAO;AACF;AACH;AACgB;AACiB;AACnC;AAGF;AAAO;AACT;AACC;AAGH;AACE;AACE;AAAoC;AAC3B;AACG;AACV;AACD;AACH;AAGF;AACG;AAAc;AAAY;AACxB;AACQ;AACO;AACJ;AACV;AACK;AAKJ;AAAgB;AAEZ;AAAa;AAAkB;AAInC;AAAwB;AAKxB;AAAM;AAKJ;AACM;AACO;AACA;AACJ;AACO;AACf;AACkB;AAEhB;AACG;AAAyC;AAAa;AAE3D;AAEE;AAEA;AAEA;AACG;AACa;AACV;AACA;AACA;AACF;AAGE;AAKA;AACG;AACY;AACN;AACU;AACc;AACA;AAChB;AACH;AAER;AAAyC;AAC3C;AACgB;AAEd;AACA;AAAsB;AACxB;AACa;AACf;AAGN;AAEJ;AAKH;AAAc;AAAgB;AAInC;AAGN;AAEA;AACE;AACA;AACA;AACF;;"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../src/primitives/EmojiPicker/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type { ChangeEvent, KeyboardEvent, SyntheticEvent } from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n CalculateViewLocationParams,\n GroupedVirtuosoHandle,\n ListProps as VirtuosoListProps,\n ScrollerProps,\n TopItemListProps,\n} from \"react-virtuoso\";\nimport { GroupedVirtuoso } from \"react-virtuoso\";\n\nimport { isKey } from \"../../utils/is-key\";\nimport {\n cancelIdleCallback,\n requestIdleCallback,\n} from \"../../utils/request-idle-callback\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useTransition } from \"../../utils/use-transition\";\nimport { visuallyHidden } from \"../../utils/visually-hidden\";\nimport { Emoji as EmojiPrimitive } from \"../internal/Emoji\";\nimport { EmojiPickerContext, useEmojiPicker } from \"./contexts\";\nimport type {\n EmojiData,\n EmojiPickerContentComponents,\n EmojiPickerContentEmojiRowAttributes,\n EmojiPickerContentProps,\n EmojiPickerData,\n EmojiPickerRootProps,\n EmojiPickerSearchProps,\n EmojiPickerSelectionDirection,\n} from \"./types\";\nimport { filterEmojis, generateEmojiPickerData, getEmojiData } from \"./utils\";\n\nconst DEFAULT_COLUMNS = 10;\nconst DEFAULT_LOCALE = \"en\";\nconst LOADING_MINIMUM_TIMEOUT = 100;\n\nconst EMOJIPICKER_ROOT_NAME = \"EmojiPickerRoot\";\nconst EMOJIPICKER_CONTENT_NAME = \"EmojiPickerContent\";\nconst EMOJIPICKER_SEARCH_NAME = \"EmojiPickerSearch\";\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * Surrounds the emoji picker, it handles emoji data and coordinates\n * `EmojiPicker.Search` and `EmojiPicker.Content`.\n *\n * @example\n * <EmojiPicker.Root>\n * <EmojiPicker.Search />\n * <EmojiPicker.Content />\n * </EmojiPicker.Root>\n */\nfunction EmojiPickerRoot({\n columns = DEFAULT_COLUMNS,\n locale = DEFAULT_LOCALE,\n onEmojiSelect,\n children,\n}: EmojiPickerRootProps) {\n const emojiData = useRef<EmojiData>();\n const search = useRef(\"\");\n const [, startEmojisTransition] = useTransition();\n const [data, setData] = useState<EmojiPickerData>();\n const [error, setError] = useState<Error>();\n const [selectedColumnIndex, setSelectedColumnIndex] = useState(0);\n const [selectedRowIndex, setSelectedRowIndex] = useState(0);\n const [interaction, setInteraction] = useState<\n \"keyboard\" | \"pointer\" | \"none\"\n >(\"none\");\n\n const selectCurrentEmoji = useCallback(() => {\n if (onEmojiSelect) {\n const emoji = data?.rows[selectedRowIndex]?.[selectedColumnIndex];\n\n if (emoji) {\n onEmojiSelect(emoji.emoji);\n }\n }\n }, [data?.rows, onEmojiSelect, selectedColumnIndex, selectedRowIndex]);\n\n const resetSelection = useCallback(() => {\n setSelectedColumnIndex(0);\n setSelectedRowIndex(0);\n }, []);\n\n const setPointerSelection = useCallback(\n (columnIndex: number, rowIndex: number) => {\n setInteraction(\"pointer\");\n setSelectedColumnIndex(columnIndex);\n setSelectedRowIndex(rowIndex);\n },\n []\n );\n\n const moveSelection = useCallback(\n (\n direction: EmojiPickerSelectionDirection,\n event: KeyboardEvent<HTMLInputElement>\n ) => {\n if (!data) {\n return;\n }\n\n event.preventDefault();\n\n if (interaction === \"none\") {\n setInteraction(\"keyboard\");\n return;\n }\n\n setInteraction(\"keyboard\");\n\n switch (direction) {\n // If first column, move to last column of previous row (if available)\n // Otherwise, move to previous column\n case \"left\": {\n if (selectedColumnIndex === 0) {\n const previousRowIndex = selectedRowIndex - 1;\n const previousRow = data.rows[previousRowIndex];\n\n if (previousRow) {\n setSelectedRowIndex(previousRowIndex);\n setSelectedColumnIndex(previousRow.length - 1);\n }\n } else {\n setSelectedColumnIndex(selectedColumnIndex - 1);\n }\n\n break;\n }\n\n // If last column, move to first column of next row (if available)\n // Otherwise, move to next column\n case \"right\": {\n const currentRow = data.rows[selectedRowIndex];\n\n if (!currentRow) {\n return;\n }\n\n if (selectedColumnIndex === currentRow.length - 1) {\n const nextRowIndex = selectedRowIndex + 1;\n const nextRow = data.rows[nextRowIndex];\n\n if (nextRow) {\n setSelectedRowIndex(nextRowIndex);\n setSelectedColumnIndex(0);\n }\n } else {\n setSelectedColumnIndex(selectedColumnIndex + 1);\n }\n\n break;\n }\n\n // Move to same column of previous row\n // If same column is not available, move to last column of previous row\n case \"up\": {\n const previousRow = data.rows[selectedRowIndex - 1];\n\n if (previousRow) {\n setSelectedRowIndex(selectedRowIndex - 1);\n\n if (!previousRow[selectedColumnIndex]) {\n setSelectedColumnIndex(previousRow.length - 1);\n }\n }\n\n break;\n }\n\n // Move to same column of next row\n // If same column is not available, move to last column of next row\n case \"down\": {\n const nextRow = data.rows[selectedRowIndex + 1];\n\n if (nextRow) {\n setSelectedRowIndex(selectedRowIndex + 1);\n\n if (!nextRow[selectedColumnIndex]) {\n setSelectedColumnIndex(nextRow.length - 1);\n }\n }\n\n break;\n }\n }\n },\n [data, interaction, selectedColumnIndex, selectedRowIndex]\n );\n\n const updateEmojis = useCallback(() => {\n if (!emojiData.current) {\n return;\n }\n\n startEmojisTransition(() => {\n setData(() => {\n if (!emojiData.current) {\n return;\n }\n\n const filteredEmojis = filterEmojis(\n emojiData.current.emojis,\n search.current\n );\n\n return generateEmojiPickerData(\n filteredEmojis,\n emojiData.current.categories,\n columns\n );\n });\n resetSelection();\n });\n }, [columns, resetSelection]);\n\n const handleSearch = useCallback(\n (value: string) => {\n search.current = value;\n updateEmojis();\n },\n [updateEmojis]\n );\n\n const initializeEmojiData = useCallback(\n async (locale: string) => {\n try {\n emojiData.current = await getEmojiData(locale);\n updateEmojis();\n } catch (error) {\n setError(error as Error);\n }\n },\n [updateEmojis]\n );\n\n useEffect(() => {\n let idleCallbackId: number;\n const timeoutId = setTimeout(() => {\n idleCallbackId = requestIdleCallback(() => {\n initializeEmojiData(locale);\n });\n }, LOADING_MINIMUM_TIMEOUT);\n\n return () => {\n clearTimeout(timeoutId);\n cancelIdleCallback(idleCallbackId);\n };\n }, [locale]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (interaction === \"none\") {\n resetSelection();\n }\n }, [interaction]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <EmojiPickerContext.Provider\n value={{\n data: data as EmojiPickerData,\n error: error as undefined,\n isLoading: (!data && !error) as false,\n columns,\n onSearch: handleSearch,\n onEmojiSelect,\n selectCurrentEmoji,\n selectedRowIndex,\n selectedColumnIndex,\n moveSelection,\n setPointerSelection,\n interaction,\n setInteraction,\n }}\n >\n {children}\n </EmojiPickerContext.Provider>\n );\n}\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * The search input of the emoji picker. It also affects the focus and selection\n * within `EmojiPicker.Content`.\n *\n * @example\n * <EmojiPicker.Search />\n */\nconst EmojiPickerSearch = forwardRef<HTMLInputElement, EmojiPickerSearchProps>(\n ({ asChild, value, defaultValue, onChange, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"input\";\n const {\n onSearch,\n selectCurrentEmoji,\n moveSelection,\n interaction,\n setInteraction,\n } = useEmojiPicker();\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n onChange?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const value = event.target.value;\n setInteraction(value ? \"keyboard\" : \"none\");\n onSearch(value);\n },\n [onChange, onSearch, setInteraction]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.isDefaultPrevented()) {\n return;\n }\n\n if (isKey(event, \"ArrowLeft\")) {\n moveSelection(\"left\", event);\n } else if (isKey(event, \"ArrowRight\")) {\n moveSelection(\"right\", event);\n } else if (isKey(event, \"ArrowUp\")) {\n moveSelection(\"up\", event);\n } else if (isKey(event, \"ArrowDown\")) {\n moveSelection(\"down\", event);\n } else if (isKey(event, \"Enter\")) {\n if (interaction !== \"none\") {\n event.preventDefault();\n selectCurrentEmoji();\n }\n }\n },\n [interaction, moveSelection, selectCurrentEmoji]\n );\n\n useEffect(() => {\n onSearch(\n value ? String(value) : defaultValue ? String(defaultValue) : \"\"\n );\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return (\n <Component\n type=\"search\"\n value={value}\n defaultValue={defaultValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n);\n\nconst defaultContentComponents: EmojiPickerContentComponents = {\n CategoryHeader: ({ category, ...props }) => <div {...props}>{category}</div>,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Row: ({ children, attributes, ...props }) => <div {...props}>{children}</div>,\n Emoji: ({ emoji, ...props }) => (\n <button {...props}>\n <EmojiPrimitive emoji={emoji} />\n </button>\n ),\n Loading: (props) => <div {...props} />,\n Empty: (props) => <div {...props} />,\n Grid: (props) => <div {...props} />,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Error: ({ error, ...props }) => <div {...props} />,\n};\n\nconst placeholderRowAttributes: EmojiPickerContentEmojiRowAttributes = {\n rowIndex: -1,\n categoryRowIndex: -1,\n categoryRowsCount: 0,\n};\n\n// About `data-testid={undefined}`: Virtuoso bakes test IDs into the components we pass\n// to it, so we manually remove them.\n\nconst VirtuosoScroller = forwardRef<HTMLDivElement, ScrollerProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div {...props} tabIndex={-1} data-testid={undefined} ref={forwardedRef}>\n {children}\n </div>\n );\n }\n);\n\nconst VirtuosoTopList = forwardRef<HTMLDivElement, TopItemListProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div {...props} data-testid={undefined} ref={forwardedRef}>\n {children}\n </div>\n );\n }\n);\n\n/**\n * @private\n * The EmojiPicker primitive is undocumented for now and subject to change,\n * use at your own risk. If you have any feedback on it, please let us know!\n * See how we use it in the default components to learn how to use it:\n * https://github.com/liveblocks/liveblocks/blob/main/packages/liveblocks-react-ui/src/components/internal/EmojiPicker.tsx.\n *\n * The main content of the emoji picker, either displaying the emoji grid or various\n * alternative states (loading, empty, and error).\n *\n * @example\n * <EmojiPicker.Content\n * components={{\n * Loading: EmojiPickerLoading,\n * Empty: EmojiPickerEmpty,\n * Error: EmojiPickerError,\n * CategoryHeader: EmojiPickerCategoryHeader,\n * Grid: EmojiPickerGrid,\n * Row: EmojiPickerRow,\n * Emoji: EmojiPickerEmoji,\n * }}\n * />\n */\nconst EmojiPickerContent = forwardRef<HTMLDivElement, EmojiPickerContentProps>(\n ({ components, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"div\";\n const virtuosoRef = useRef<GroupedVirtuosoHandle>(null);\n const placeholderContainerRef = useRef<HTMLDivElement>(null);\n const rowScrollMarginTopRef = useRef<number>(0);\n const rowScrollMarginBottomRef = useRef<number>(0);\n const categoryHeaderHeightRef = useRef<number>(0);\n const {\n data,\n error,\n isLoading,\n columns,\n onEmojiSelect,\n selectedColumnIndex,\n selectedRowIndex,\n setPointerSelection,\n interaction,\n setInteraction,\n } = useEmojiPicker();\n const selectedEmoji = useMemo(\n () => data?.rows[selectedRowIndex]?.[selectedColumnIndex],\n [data?.rows, selectedColumnIndex, selectedRowIndex]\n );\n const { Loading, Empty, Error, CategoryHeader, Grid, Row, Emoji } = useMemo(\n () => ({ ...defaultContentComponents, ...components }),\n [components]\n );\n const VirtuosoList = useMemo(\n () =>\n forwardRef<HTMLDivElement, VirtuosoListProps>(\n ({ children, ...props }, forwardedRef) => {\n return (\n <div\n role=\"grid\"\n aria-colcount={columns}\n {...props}\n data-testid={undefined}\n ref={forwardedRef}\n >\n {children}\n </div>\n );\n }\n ),\n [columns]\n );\n const placeholderColumns = useMemo(\n () => Array<string>(columns).fill(\"🌫️\"),\n [columns]\n );\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const handleEmojiPointerLeave = useCallback(() => {\n if (interaction === \"pointer\") {\n setInteraction(\"none\");\n }\n }, [interaction, setInteraction]);\n\n useLayoutEffect(() => {\n if (!placeholderContainerRef.current) {\n return;\n }\n\n const row = placeholderContainerRef.current.childNodes[0];\n const categoryHeader = placeholderContainerRef.current.childNodes[1];\n\n if (row instanceof HTMLElement) {\n const style = window.getComputedStyle(row);\n\n rowScrollMarginTopRef.current = parseFloat(style.scrollMarginTop);\n rowScrollMarginBottomRef.current = parseFloat(style.scrollMarginBottom);\n }\n\n if (categoryHeader instanceof HTMLElement) {\n categoryHeaderHeightRef.current = categoryHeader.offsetHeight;\n }\n }, []);\n\n // Customize `scrollIntoView` behavior to take into account category headers and margins\n const calculateViewLocation = useCallback(\n ({\n itemTop,\n itemBottom,\n viewportTop,\n viewportBottom,\n locationParams: { behavior, align, ...params },\n }: CalculateViewLocationParams) => {\n if (\n itemTop -\n (categoryHeaderHeightRef.current + rowScrollMarginTopRef.current) <\n viewportTop\n ) {\n return {\n ...params,\n behavior,\n align: align ?? \"start\",\n };\n }\n\n if (itemBottom > viewportBottom) {\n return {\n ...params,\n behavior,\n align: align ?? \"end\",\n offset: rowScrollMarginBottomRef.current,\n };\n }\n\n return null;\n },\n []\n );\n\n useEffect(() => {\n if (interaction === \"keyboard\") {\n virtuosoRef.current?.scrollIntoView({\n index: selectedRowIndex,\n behavior: \"auto\",\n calculateViewLocation,\n });\n }\n }, [interaction, selectedRowIndex, calculateViewLocation]);\n\n return (\n <Component {...props} ref={forwardedRef}>\n <div\n style={{\n visibility: \"hidden\",\n height: 0,\n }}\n ref={placeholderContainerRef}\n >\n {/* Virtualized rows are absolutely positioned so they won't make\n the container automatically pick up their width. To achieve\n an automatic width, we add a relative (but hidden) full row. */}\n <Row attributes={placeholderRowAttributes}>\n {placeholderColumns.map((placeholder, index) => (\n <Emoji emoji={placeholder} key={index} />\n ))}\n </Row>\n {/* We also add a hidden category header to get its computed height. */}\n <CategoryHeader category=\"Category\" />\n </div>\n {isLoading ? (\n <Loading />\n ) : error ? (\n <Error error={error} />\n ) : data.count === 0 ? (\n <Empty />\n ) : (\n <Grid>\n <GroupedVirtuoso\n ref={virtuosoRef}\n components={{\n Scroller: VirtuosoScroller,\n List: VirtuosoList,\n TopItemList: VirtuosoTopList,\n }}\n groupCounts={data.categoriesRowCounts}\n groupContent={(groupIndex) => {\n const category = data.categories[groupIndex];\n\n if (!category) {\n return null;\n }\n\n return <CategoryHeader category={category} />;\n }}\n itemContent={(rowIndex, groupIndex) => {\n const categoryRow = data.rows[rowIndex];\n const categoryRowIndex =\n data.categoriesRowIndices[groupIndex]?.indexOf(rowIndex);\n const categoryRowsCount = data.categoriesRowCounts[groupIndex];\n\n if (\n categoryRow === undefined ||\n categoryRowIndex === undefined ||\n categoryRowsCount === undefined\n ) {\n return null;\n }\n\n return (\n <Row\n attributes={{\n rowIndex,\n categoryRowIndex,\n categoryRowsCount,\n }}\n >\n {categoryRow.map((emoji, columnIndex) => {\n const isSelected =\n interaction !== \"none\" &&\n selectedColumnIndex === columnIndex &&\n selectedRowIndex === rowIndex;\n\n return (\n <Emoji\n key={emoji.emoji}\n role=\"gridcell\"\n aria-colindex={columnIndex}\n aria-selected={isSelected || undefined}\n data-selected={isSelected || undefined}\n onMouseDown={preventDefault}\n tabIndex={-1}\n onPointerEnter={() => {\n setPointerSelection(columnIndex, rowIndex);\n }}\n onPointerLeave={handleEmojiPointerLeave}\n onClick={(event) => {\n onEmojiSelect?.(emoji.emoji);\n event.stopPropagation();\n }}\n emoji={emoji.emoji}\n />\n );\n })}\n </Row>\n );\n }}\n />\n </Grid>\n )}\n {selectedEmoji && interaction !== \"none\" && (\n <div aria-live=\"polite\" style={visuallyHidden}>\n {selectedEmoji.name}\n </div>\n )}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n EmojiPickerRoot.displayName = EMOJIPICKER_ROOT_NAME;\n EmojiPickerContent.displayName = EMOJIPICKER_CONTENT_NAME;\n EmojiPickerSearch.displayName = EMOJIPICKER_SEARCH_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as EmojiPicker.*\nexport {\n EmojiPickerContent as Content,\n EmojiPickerRoot as Root,\n EmojiPickerSearch as Search,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;AA2CA;AACA;AACA;AAEA;AACA;AACA;AAkBA;AAAyB;AACb;AACD;AACT;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACE;AACE;AAEA;AACE;AAAyB;AAC3B;AACF;AAGF;AACE;AACA;AAAqB;AAGvB;AAA4B;AAExB;AACA;AACA;AAA4B;AAC9B;AACC;AAGH;AAAsB;AAKlB;AACE;AAAA;AAGF;AAEA;AACE;AACA;AAAA;AAGF;AAEA;AAAmB;AAIf;AACE;AACA;AAEA;AACE;AACA;AAA6C;AAC/C;AAEA;AAA8C;AAGhD;AAAA;AACF;AAKE;AAEA;AACE;AAAA;AAGF;AACE;AACA;AAEA;AACE;AACA;AAAwB;AAC1B;AAEA;AAA8C;AAGhD;AAAA;AACF;AAKE;AAEA;AACE;AAEA;AACE;AAA6C;AAC/C;AAGF;AAAA;AACF;AAKE;AAEA;AACE;AAEA;AACE;AAAyC;AAC3C;AAGF;AAAA;AACF;AACF;AACF;AACyD;AAG3D;AACE;AACE;AAAA;AAGF;AACE;AACE;AACE;AAAA;AAGF;AAAuB;AACH;AACX;AAGT;AAAO;AACL;AACkB;AAClB;AACF;AAEF;AAAe;AAChB;AAGH;AAAqB;AAEjB;AACA;AAAa;AACf;AACa;AAGf;AAA4B;AAExB;AACE;AACA;AAAa;AAEb;AAAuB;AACzB;AACF;AACa;AAGf;AACE;AACA;AACE;AACE;AAA0B;AAC3B;AAGH;AACE;AACA;AAAiC;AACnC;AAGF;AACE;AACE;AAAe;AACjB;AAGF;AACG;AACQ;AACL;AACA;AACsB;AACtB;AACU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAKN;AAeA;AAA0B;AAEtB;AACA;AAAM;AACJ;AACA;AACA;AACA;AACA;AAGF;AAAqB;AAEjB;AAEA;AACE;AAAA;AAGF;AACA;AACA;AAAc;AAChB;AACmC;AAGrC;AAAsB;AAElB;AACE;AAAA;AAGF;AACE;AAA2B;AAE3B;AAA4B;AAE5B;AAAyB;AAEzB;AAA2B;AAE3B;AACE;AACA;AAAmB;AACrB;AACF;AACF;AAC+C;AAGjD;AACE;AAAA;AACgE;AAChE;AAGF;AACG;AACM;AACL;AACA;AACU;AACC;AACP;AACC;AACP;AAGN;AAEA;AAA+D;AAChB;AAAQ;AAAiB;AAExB;AAAQ;AAAiB;AAEpE;AAAW;AACT;AAAe;AAClB;AAEmB;AAAQ;AAAO;AACjB;AAAQ;AAAO;AAChB;AAAQ;AAAO;AAEA;AAAQ;AAC3C;AAEA;AAAuE;AAC3D;AACQ;AAEpB;AAKA;AAAyB;AAErB;AACG;AAAQ;AAAiB;AAAiB;AAAgB;AAE3D;AAGN;AAEA;AAAwB;AAEpB;AACG;AAAQ;AAAoB;AAAgB;AAE7C;AAGN;AAyBA;AAA2B;AAEvB;AACA;AACA;AACA;AACA;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AAAsB;AACiB;AACa;AAEpD;AAAoE;AACd;AACzC;AAEb;AAAqB;AAEjB;AAEI;AACG;AACM;AACU;AACX;AACS;AACR;AAGP;AAEJ;AACF;AACM;AAEV;AAA2B;AACc;AAC/B;AAGV;AACE;AAAqB;AAGvB;AACE;AACE;AAAqB;AACvB;AAGF;AACE;AACE;AAAA;AAGF;AACA;AAEA;AACE;AAEA;AACA;AAAsE;AAGxE;AACE;AAAiD;AACnD;AAIF;AAA8B;AAC3B;AACC;AACA;AACA;AACA;AAC6C;AAE7C;AAKE;AAAO;AACF;AACH;AACgB;AAClB;AAGF;AACE;AAAO;AACF;AACH;AACgB;AACiB;AACnC;AAGF;AAAO;AACT;AACC;AAGH;AACE;AACE;AAAoC;AAC3B;AACG;AACV;AACD;AACH;AAGF;AACG;AAAc;AAAY;AACxB;AACQ;AACO;AACJ;AACV;AACK;AAKJ;AAAgB;AAEZ;AAAa;AAAkB;AAInC;AAAwB;AAKxB;AAAM;AAKJ;AACM;AACO;AACA;AACJ;AACO;AACf;AACkB;AAEhB;AAEA;AACE;AAAO;AAGT;AAAQ;AAAe;AAAoB;AAC7C;AAEE;AACA;AAEA;AAEA;AAKE;AAAO;AAGT;AACG;AACa;AACV;AACA;AACA;AACF;AAGE;AAKA;AACG;AACY;AACN;AACU;AACc;AACA;AAChB;AACH;AAER;AAAyC;AAC3C;AACgB;AAEd;AACA;AAAsB;AACxB;AACa;AACf;AAGN;AAEJ;AAKH;AAAc;AAAgB;AAInC;AAGN;AAEA;AACE;AACA;AACA;AACF;;"}
|
|
@@ -209,9 +209,10 @@ function getEmojiSessionMetadata(emojis) {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
const descendingVersions = [...versions.keys()].sort((a, b) => b - a);
|
|
212
|
+
const highestVersion = descendingVersions[0] ?? 0;
|
|
212
213
|
const canvasContext = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
|
|
213
214
|
if (!canvasContext) {
|
|
214
|
-
return { emojiVersion:
|
|
215
|
+
return { emojiVersion: highestVersion, countryFlags: true };
|
|
215
216
|
}
|
|
216
217
|
canvasContext.font = `${Math.floor(
|
|
217
218
|
EMOJI_DETECTION_CANVAS_HEIGHT / 2
|
|
@@ -234,7 +235,7 @@ function getEmojiSessionMetadata(emojis) {
|
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
return {
|
|
237
|
-
emojiVersion:
|
|
238
|
+
emojiVersion: highestVersion,
|
|
238
239
|
countryFlags: supportsCountryFlags
|
|
239
240
|
};
|
|
240
241
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../../src/primitives/EmojiPicker/utils.ts"],"sourcesContent":["import { chunk } from \"@liveblocks/core\";\nimport type {\n Emoji as EmojibaseEmoji,\n Locale as EmojibaseLocale,\n MessagesDataset as EmojibaseMessagesDataset,\n} from \"emojibase\";\n\nimport { EMOJI_FONT_FAMILY } from \"../../constants\";\nimport { capitalize } from \"../../utils/capitalize\";\nimport type {\n Emoji,\n EmojiCategory,\n EmojiData,\n EmojiPickerData,\n EmojiPickerRow,\n} from \"./types\";\n\nconst EMOJIBASE_VERSION = \"15.3.0\";\nconst EMOJIBASE_CDN_URL = `https://cdn.jsdelivr.net/npm/emojibase-data@${EMOJIBASE_VERSION}`;\nconst EMOJIBASE_EMOJIS_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/data.json`;\nconst EMOJIBASE_MESSAGES_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/messages.json`;\nconst EMOJIBASE_LOCALES: EmojibaseLocale[] = [\n \"bn\",\n \"da\",\n \"de\",\n \"en\",\n \"en-gb\",\n \"es\",\n \"es-mx\",\n \"et\",\n \"fi\",\n \"fr\",\n \"hi\",\n \"hu\",\n \"it\",\n \"ja\",\n \"ko\",\n \"lt\",\n \"ms\",\n \"nb\",\n \"nl\",\n \"pl\",\n \"pt\",\n \"ru\",\n \"sv\",\n \"th\",\n \"uk\",\n \"zh\",\n \"zh-hant\",\n];\nconst EMOJIBASE_DEFAULT_LOCALE: EmojibaseLocale = \"en\";\n\nconst CACHE_EMOJI_DATA_KEY = (locale: string) => `lb-emoji-data-${locale}`;\nconst CACHE_EMOJI_METADATA_KEY = (locale: string) =>\n `lb-emoji-metadata-${locale}`;\nconst CACHE_EMOJI_SESSION_METADATA_KEY = \"lb-emoji-metadata\";\n\nconst EMOJI_DETECTION_CANVAS_WIDTH = 20;\nconst EMOJI_DETECTION_CANVAS_HEIGHT = 25;\nconst EMOJI_DETECTION_COUNTRY_FLAG = \"🇫🇷\";\n\ntype EmojiMetadata = {\n emojisEtag: string | null;\n messagesEtag: string | null;\n};\n\ntype EmojiSessionMetadata = {\n emojiVersion: number;\n countryFlags: boolean;\n};\n\nfunction generateRangeIndices(start: number, end: number) {\n const range: number[] = [];\n\n for (let i = start; i <= end; i++) {\n range.push(i);\n }\n\n return range;\n}\n\nfunction getStorageItem<T>(storage: Storage, key: string) {\n const item = storage.getItem(key);\n\n return item ? (JSON.parse(item) as T) : null;\n}\n\nfunction setStorageItem<T>(storage: Storage, key: string, value: T) {\n storage.setItem(key, JSON.stringify(value));\n}\n\nasync function fetchEtag(url: string) {\n try {\n const response = await fetch(url, { method: \"HEAD\" });\n\n return response.headers.get(\"etag\");\n } catch (error) {\n return null;\n }\n}\n\nfunction getEmojibaseSupportedLocale(locale: string): EmojibaseLocale {\n return EMOJIBASE_LOCALES.includes(locale as EmojibaseLocale)\n ? (locale as EmojibaseLocale)\n : EMOJIBASE_DEFAULT_LOCALE;\n}\n\nasync function fetchEmojibaseData(locale: EmojibaseLocale) {\n const [{ emojis, emojisEtag }, { messages, messagesEtag }] =\n await Promise.all([\n fetch(EMOJIBASE_EMOJIS_URL(locale)).then(async (response) => {\n return {\n emojis: (await response.json()) as EmojibaseEmoji[],\n emojisEtag: response.headers.get(\"etag\"),\n };\n }),\n fetch(EMOJIBASE_MESSAGES_URL(locale)).then(async (response) => {\n return {\n messages: (await response.json()) as EmojibaseMessagesDataset,\n messagesEtag: response.headers.get(\"etag\"),\n };\n }),\n ]);\n\n return {\n emojis,\n messages,\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojibaseEtags(locale: EmojibaseLocale) {\n const [emojisEtag, messagesEtag] = await Promise.all([\n fetchEtag(EMOJIBASE_EMOJIS_URL(locale)),\n fetchEtag(EMOJIBASE_MESSAGES_URL(locale)),\n ]);\n\n return {\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojiData(locale: EmojibaseLocale): Promise<EmojiData> {\n const { emojis, emojisEtag, messages, messagesEtag } =\n await fetchEmojibaseData(locale);\n const countryFlagsSubgroup = messages.subgroups.find(\n (subgroup) => subgroup.key === \"subdivision-flag\"\n );\n\n // Filter out component/modifier category and emojis\n const filteredGroups = messages.groups.filter(\n (group) => group.key !== \"component\"\n );\n const filteredEmojis = emojis.filter((emoji) => {\n return \"group\" in emoji;\n });\n\n // Pick and compact the data\n const categories = filteredGroups.map((group) => ({\n key: group.order,\n name: capitalize(group.message),\n }));\n const skinTones = messages.skinTones.map((skinTone) => ({\n key: skinTone.key,\n name: capitalize(skinTone.message),\n }));\n const compactEmojis = filteredEmojis.map((emoji) => {\n const compactEmoji: Emoji = {\n emoji: emoji.emoji,\n category: emoji.group!,\n version: emoji.version,\n name: capitalize(emoji.label),\n tags: emoji.tags,\n };\n\n if (countryFlagsSubgroup && emoji.subgroup === countryFlagsSubgroup.order) {\n compactEmoji.countryFlag = true;\n }\n\n return compactEmoji;\n });\n\n const emojiData = {\n emojis: compactEmojis,\n categories,\n skinTones,\n };\n\n // Cache the data and metadata\n setStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(locale),\n emojiData\n );\n setStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(locale),\n {\n emojisEtag,\n messagesEtag,\n }\n );\n\n return emojiData;\n}\n\n// Adapted from https://github.com/koala-interactive/is-emoji-supported/tree/master\nfunction detectEmojiSupport(\n canvasContext: CanvasRenderingContext2D,\n emoji: string\n): boolean {\n canvasContext.clearRect(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH * 2,\n EMOJI_DETECTION_CANVAS_HEIGHT\n );\n\n // Draw in red on the left\n canvasContext.fillStyle = \"#f00\";\n canvasContext.fillText(emoji, 0, 22);\n\n // Draw in blue on right\n canvasContext.fillStyle = \"#00f\";\n canvasContext.fillText(emoji, EMOJI_DETECTION_CANVAS_WIDTH, 22);\n\n const pixels = canvasContext.getImageData(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH,\n EMOJI_DETECTION_CANVAS_HEIGHT\n ).data;\n const pixelCount = pixels.length;\n let i = 0;\n\n // Search for the first visible pixel\n for (; i < pixelCount && !pixels[i + 3]; i += 4);\n\n // No visible pixel\n if (i >= pixelCount) {\n return false;\n }\n\n // Emojis have an immutable color, so we check the color of the emoji in two\n // different colors, the result should be the same\n const x =\n EMOJI_DETECTION_CANVAS_WIDTH + ((i / 4) % EMOJI_DETECTION_CANVAS_WIDTH);\n const y = Math.floor(i / 4 / EMOJI_DETECTION_CANVAS_WIDTH);\n const pixel = canvasContext.getImageData(x, y, 1, 1).data;\n\n if (pixels[i] !== pixel[0] || pixels[i + 2] !== pixel[2]) {\n return false;\n }\n\n // Unsupported ZWJ sequence emojis show up as separate emojis\n if (canvasContext.measureText(emoji).width >= EMOJI_DETECTION_CANVAS_WIDTH) {\n return false;\n }\n\n return true;\n}\n\nfunction getEmojiFontFamily() {\n try {\n const element = document.createElement(\"span\");\n element.style.display = \"none\";\n element.dataset.emoji = \"\";\n\n document.body.appendChild(element);\n\n const computedFontFamily = window.getComputedStyle(element).fontFamily;\n\n document.body.removeChild(element);\n\n return computedFontFamily;\n } catch {\n return EMOJI_FONT_FAMILY;\n }\n}\n\nfunction getEmojiSessionMetadata(emojis: Emoji[]): EmojiSessionMetadata {\n const versions = new Map<number, string>();\n\n for (const emoji of emojis) {\n if (!versions.has(emoji.version)) {\n versions.set(emoji.version, emoji.emoji);\n }\n }\n\n const descendingVersions = [...versions.keys()].sort((a, b) => b - a);\n\n const canvasContext = document\n .createElement(\"canvas\")\n .getContext(\"2d\", { willReadFrequently: true });\n\n if (!canvasContext) {\n return { emojiVersion: descendingVersions[0], countryFlags: true };\n }\n\n canvasContext.font = `${Math.floor(\n EMOJI_DETECTION_CANVAS_HEIGHT / 2\n )}px ${getEmojiFontFamily()}`;\n canvasContext.textBaseline = \"top\";\n canvasContext.canvas.width = EMOJI_DETECTION_CANVAS_WIDTH * 2;\n canvasContext.canvas.height = EMOJI_DETECTION_CANVAS_HEIGHT;\n\n const supportsCountryFlags = detectEmojiSupport(\n canvasContext,\n EMOJI_DETECTION_COUNTRY_FLAG\n );\n\n for (const version of descendingVersions) {\n const emoji = versions.get(version)!;\n const isSupported = detectEmojiSupport(canvasContext, emoji);\n\n if (isSupported) {\n return {\n emojiVersion: version,\n countryFlags: supportsCountryFlags,\n };\n }\n }\n\n return {\n emojiVersion: descendingVersions[0],\n countryFlags: supportsCountryFlags,\n };\n}\n\nexport async function getEmojiData(locale: string): Promise<EmojiData> {\n const emojibaseLocale = getEmojibaseSupportedLocale(locale);\n\n const sessionMetadata = getStorageItem<EmojiSessionMetadata>(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY\n );\n const cachedData = getStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(emojibaseLocale)\n );\n let data: EmojiData;\n\n // If there is data already cached, check if the ETags are the same.\n // If they are, return the cached data, otherwise fetch it again.\n if (cachedData) {\n // ETags only need to be checked once per session\n if (sessionMetadata) {\n data = cachedData;\n } else {\n const { emojisEtag, messagesEtag } =\n await fetchEmojibaseEtags(emojibaseLocale);\n const cachedMetadata = getStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(emojibaseLocale)\n );\n\n if (\n cachedMetadata &&\n emojisEtag === cachedMetadata.emojisEtag &&\n messagesEtag === cachedMetadata.messagesEtag\n ) {\n data = cachedData;\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n }\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n\n const newSessionMetadata =\n sessionMetadata ?? getEmojiSessionMetadata(data.emojis);\n setStorageItem(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY,\n newSessionMetadata\n );\n\n // Filter out unsupported emojis\n const filteredEmojis = data.emojis.filter((emoji) => {\n const isSupportedVersion = emoji.version <= newSessionMetadata.emojiVersion;\n\n return emoji.countryFlag\n ? isSupportedVersion && newSessionMetadata.countryFlags\n : isSupportedVersion;\n });\n\n return {\n emojis: filteredEmojis,\n categories: data.categories,\n skinTones: data.skinTones,\n };\n}\n\nexport function filterEmojis(emojis: Emoji[], search?: string) {\n if (!search) {\n return emojis;\n }\n\n const searchText = search.toLowerCase().trim();\n\n return emojis.filter(\n (emoji) =>\n emoji.name.toLowerCase().includes(searchText) ||\n emoji.tags?.some((tag) => tag.toLowerCase().includes(searchText))\n );\n}\n\nexport function generateEmojiPickerData(\n emojis: Emoji[],\n categories: EmojiCategory[],\n columns: number\n): EmojiPickerData {\n let currentIndex = 0;\n const rows: EmojiPickerRow[] = [];\n const indexedEmojis = emojis.map((emoji, index) => ({ ...emoji, index }));\n const categoriesRowCounts: number[] = [];\n const categoriesRowIndices: number[][] = [];\n const categoriesNames: string[] = [];\n const categorizedEmojis = categories\n .map((category) => ({\n ...category,\n emojis: indexedEmojis.filter((emoji) => emoji.category === category.key),\n }))\n .filter((category) => category.emojis.length > 0);\n\n for (const category of categorizedEmojis) {\n const categoryRows = chunk(category.emojis, columns);\n const nextIndex = currentIndex + categoryRows.length;\n\n rows.push(...categoryRows);\n categoriesNames.push(category.name);\n categoriesRowCounts.push(categoryRows.length);\n categoriesRowIndices.push(\n generateRangeIndices(currentIndex, nextIndex - 1)\n );\n currentIndex = nextIndex;\n }\n\n return {\n count: emojis.length,\n rows,\n categories: categoriesNames,\n categoriesRowCounts,\n categoriesRowIndices,\n };\n}\n"],"names":["capitalize","EMOJI_FONT_FAMILY","chunk"],"mappings":";;;;;;AAiBA,MAAM,iBAAoB,GAAA,QAAA,CAAA;AAC1B,MAAM,oBAAoB,CAA+C,4CAAA,EAAA,iBAAA,CAAA,CAAA,CAAA;AACzE,MAAM,oBAAuB,GAAA,CAAC,MAC5B,KAAA,CAAA,EAAG,iBAAqB,CAAA,CAAA,EAAA,MAAA,CAAA,UAAA,CAAA,CAAA;AAC1B,MAAM,sBAAyB,GAAA,CAAC,MAC9B,KAAA,CAAA,EAAG,iBAAqB,CAAA,CAAA,EAAA,MAAA,CAAA,cAAA,CAAA,CAAA;AAC1B,MAAM,iBAAuC,GAAA;AAAA,EAC3C,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AACF,CAAA,CAAA;AACA,MAAM,wBAA4C,GAAA,IAAA,CAAA;AAElD,MAAM,oBAAA,GAAuB,CAAC,MAAA,KAAmB,CAAiB,cAAA,EAAA,MAAA,CAAA,CAAA,CAAA;AAClE,MAAM,wBAAA,GAA2B,CAAC,MAAA,KAChC,CAAqB,kBAAA,EAAA,MAAA,CAAA,CAAA,CAAA;AACvB,MAAM,gCAAmC,GAAA,mBAAA,CAAA;AAEzC,MAAM,4BAA+B,GAAA,EAAA,CAAA;AACrC,MAAM,6BAAgC,GAAA,EAAA,CAAA;AACtC,MAAM,4BAA+B,GAAA,oBAAA,CAAA;AAYrC,SAAS,oBAAA,CAAqB,OAAe,GAAa,EAAA;AACxD,EAAA,MAAM,QAAkB,EAAC,CAAA;AAEzB,EAAA,KAAA,IAAS,CAAI,GAAA,KAAA,EAAO,CAAK,IAAA,GAAA,EAAK,CAAK,EAAA,EAAA;AACjC,IAAA,KAAA,CAAM,KAAK,CAAC,CAAA,CAAA;AAAA,GACd;AAEA,EAAO,OAAA,KAAA,CAAA;AACT,CAAA;AAEA,SAAS,cAAA,CAAkB,SAAkB,GAAa,EAAA;AACxD,EAAM,MAAA,IAAA,GAAO,OAAQ,CAAA,OAAA,CAAQ,GAAG,CAAA,CAAA;AAEhC,EAAA,OAAO,IAAQ,GAAA,IAAA,CAAK,KAAM,CAAA,IAAI,CAAU,GAAA,IAAA,CAAA;AAC1C,CAAA;AAEA,SAAS,cAAA,CAAkB,OAAkB,EAAA,GAAA,EAAa,KAAU,EAAA;AAClE,EAAA,OAAA,CAAQ,OAAQ,CAAA,GAAA,EAAK,IAAK,CAAA,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA;AAC5C,CAAA;AAEA,eAAe,UAAU,GAAa,EAAA;AACpC,EAAI,IAAA;AACF,IAAA,MAAM,WAAW,MAAM,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA,CAAA;AAEpD,IAAO,OAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,MAAM,CAAA,CAAA;AAAA,WAC3B,KAAP,EAAA;AACA,IAAO,OAAA,IAAA,CAAA;AAAA,GACT;AACF,CAAA;AAEA,SAAS,4BAA4B,MAAiC,EAAA;AACpE,EAAA,OAAO,iBAAkB,CAAA,QAAA,CAAS,MAAyB,CAAA,GACtD,MACD,GAAA,wBAAA,CAAA;AACN,CAAA;AAEA,eAAe,mBAAmB,MAAyB,EAAA;AACzD,EAAA,MAAM,CAAC,EAAE,MAAQ,EAAA,UAAA,EAAc,EAAA,EAAE,QAAU,EAAA,YAAA,EAAc,CAAA,GACvD,MAAM,OAAA,CAAQ,GAAI,CAAA;AAAA,IAChB,MAAM,oBAAqB,CAAA,MAAM,CAAC,CAAE,CAAA,IAAA,CAAK,OAAO,QAAa,KAAA;AAC3D,MAAO,OAAA;AAAA,QACL,MAAA,EAAS,MAAM,QAAA,CAAS,IAAK,EAAA;AAAA,QAC7B,UAAY,EAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,MAAM,CAAA;AAAA,OACzC,CAAA;AAAA,KACD,CAAA;AAAA,IACD,MAAM,sBAAuB,CAAA,MAAM,CAAC,CAAE,CAAA,IAAA,CAAK,OAAO,QAAa,KAAA;AAC7D,MAAO,OAAA;AAAA,QACL,QAAA,EAAW,MAAM,QAAA,CAAS,IAAK,EAAA;AAAA,QAC/B,YAAc,EAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,MAAM,CAAA;AAAA,OAC3C,CAAA;AAAA,KACD,CAAA;AAAA,GACF,CAAA,CAAA;AAEH,EAAO,OAAA;AAAA,IACL,MAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA,IACA,YAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEA,eAAe,oBAAoB,MAAyB,EAAA;AAC1D,EAAA,MAAM,CAAC,UAAY,EAAA,YAAY,CAAI,GAAA,MAAM,QAAQ,GAAI,CAAA;AAAA,IACnD,SAAA,CAAU,oBAAqB,CAAA,MAAM,CAAC,CAAA;AAAA,IACtC,SAAA,CAAU,sBAAuB,CAAA,MAAM,CAAC,CAAA;AAAA,GACzC,CAAA,CAAA;AAED,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,YAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEA,eAAe,eAAe,MAA6C,EAAA;AACzE,EAAM,MAAA,EAAE,QAAQ,UAAY,EAAA,QAAA,EAAU,cACpC,GAAA,MAAM,mBAAmB,MAAM,CAAA,CAAA;AACjC,EAAM,MAAA,oBAAA,GAAuB,SAAS,SAAU,CAAA,IAAA;AAAA,IAC9C,CAAC,QAAa,KAAA,QAAA,CAAS,GAAQ,KAAA,kBAAA;AAAA,GACjC,CAAA;AAGA,EAAM,MAAA,cAAA,GAAiB,SAAS,MAAO,CAAA,MAAA;AAAA,IACrC,CAAC,KAAU,KAAA,KAAA,CAAM,GAAQ,KAAA,WAAA;AAAA,GAC3B,CAAA;AACA,EAAA,MAAM,cAAiB,GAAA,MAAA,CAAO,MAAO,CAAA,CAAC,KAAU,KAAA;AAC9C,IAAA,OAAO,OAAW,IAAA,KAAA,CAAA;AAAA,GACnB,CAAA,CAAA;AAGD,EAAA,MAAM,UAAa,GAAA,cAAA,CAAe,GAAI,CAAA,CAAC,KAAW,MAAA;AAAA,IAChD,KAAK,KAAM,CAAA,KAAA;AAAA,IACX,IAAA,EAAMA,qBAAW,CAAA,KAAA,CAAM,OAAO,CAAA;AAAA,GAC9B,CAAA,CAAA,CAAA;AACF,EAAA,MAAM,SAAY,GAAA,QAAA,CAAS,SAAU,CAAA,GAAA,CAAI,CAAC,QAAc,MAAA;AAAA,IACtD,KAAK,QAAS,CAAA,GAAA;AAAA,IACd,IAAA,EAAMA,qBAAW,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,GACjC,CAAA,CAAA,CAAA;AACF,EAAA,MAAM,aAAgB,GAAA,cAAA,CAAe,GAAI,CAAA,CAAC,KAAU,KAAA;AAClD,IAAA,MAAM,YAAsB,GAAA;AAAA,MAC1B,OAAO,KAAM,CAAA,KAAA;AAAA,MACb,UAAU,KAAM,CAAA,KAAA;AAAA,MAChB,SAAS,KAAM,CAAA,OAAA;AAAA,MACf,IAAA,EAAMA,qBAAW,CAAA,KAAA,CAAM,KAAK,CAAA;AAAA,MAC5B,MAAM,KAAM,CAAA,IAAA;AAAA,KACd,CAAA;AAEA,IAAA,IAAI,oBAAwB,IAAA,KAAA,CAAM,QAAa,KAAA,oBAAA,CAAqB,KAAO,EAAA;AACzE,MAAA,YAAA,CAAa,WAAc,GAAA,IAAA,CAAA;AAAA,KAC7B;AAEA,IAAO,OAAA,YAAA,CAAA;AAAA,GACR,CAAA,CAAA;AAED,EAAA,MAAM,SAAY,GAAA;AAAA,IAChB,MAAQ,EAAA,aAAA;AAAA,IACR,UAAA;AAAA,IACA,SAAA;AAAA,GACF,CAAA;AAGA,EAAA,cAAA;AAAA,IACE,YAAA;AAAA,IACA,qBAAqB,MAAM,CAAA;AAAA,IAC3B,SAAA;AAAA,GACF,CAAA;AACA,EAAA,cAAA;AAAA,IACE,YAAA;AAAA,IACA,yBAAyB,MAAM,CAAA;AAAA,IAC/B;AAAA,MACE,UAAA;AAAA,MACA,YAAA;AAAA,KACF;AAAA,GACF,CAAA;AAEA,EAAO,OAAA,SAAA,CAAA;AACT,CAAA;AAGA,SAAS,kBAAA,CACP,eACA,KACS,EAAA;AACT,EAAc,aAAA,CAAA,SAAA;AAAA,IACZ,CAAA;AAAA,IACA,CAAA;AAAA,IACA,4BAA+B,GAAA,CAAA;AAAA,IAC/B,6BAAA;AAAA,GACF,CAAA;AAGA,EAAA,aAAA,CAAc,SAAY,GAAA,MAAA,CAAA;AAC1B,EAAc,aAAA,CAAA,QAAA,CAAS,KAAO,EAAA,CAAA,EAAG,EAAE,CAAA,CAAA;AAGnC,EAAA,aAAA,CAAc,SAAY,GAAA,MAAA,CAAA;AAC1B,EAAc,aAAA,CAAA,QAAA,CAAS,KAAO,EAAA,4BAAA,EAA8B,EAAE,CAAA,CAAA;AAE9D,EAAA,MAAM,SAAS,aAAc,CAAA,YAAA;AAAA,IAC3B,CAAA;AAAA,IACA,CAAA;AAAA,IACA,4BAAA;AAAA,IACA,6BAAA;AAAA,GACA,CAAA,IAAA,CAAA;AACF,EAAA,MAAM,aAAa,MAAO,CAAA,MAAA,CAAA;AAC1B,EAAA,IAAI,CAAI,GAAA,CAAA,CAAA;AAGR,EAAA,OAAO,IAAI,UAAc,IAAA,CAAC,MAAO,CAAA,CAAA,GAAI,IAAI,CAAK,IAAA,CAAA;AAAE,IAAA,CAAA;AAGhD,EAAA,IAAI,KAAK,UAAY,EAAA;AACnB,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAIA,EAAM,MAAA,CAAA,GACJ,4BAAiC,GAAA,CAAA,GAAI,CAAK,GAAA,4BAAA,CAAA;AAC5C,EAAA,MAAM,CAAI,GAAA,IAAA,CAAK,KAAM,CAAA,CAAA,GAAI,IAAI,4BAA4B,CAAA,CAAA;AACzD,EAAA,MAAM,QAAQ,aAAc,CAAA,YAAA,CAAa,GAAG,CAAG,EAAA,CAAA,EAAG,CAAC,CAAE,CAAA,IAAA,CAAA;AAErD,EAAI,IAAA,MAAA,CAAO,OAAO,KAAM,CAAA,CAAA,CAAA,IAAM,OAAO,CAAI,GAAA,CAAA,CAAA,KAAO,MAAM,CAAI,CAAA,EAAA;AACxD,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAGA,EAAA,IAAI,aAAc,CAAA,WAAA,CAAY,KAAK,CAAA,CAAE,SAAS,4BAA8B,EAAA;AAC1E,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAEA,EAAO,OAAA,IAAA,CAAA;AACT,CAAA;AAEA,SAAS,kBAAqB,GAAA;AAC5B,EAAI,IAAA;AACF,IAAM,MAAA,OAAA,GAAU,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC7C,IAAA,OAAA,CAAQ,MAAM,OAAU,GAAA,MAAA,CAAA;AACxB,IAAA,OAAA,CAAQ,QAAQ,KAAQ,GAAA,EAAA,CAAA;AAExB,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,OAAO,CAAA,CAAA;AAEjC,IAAA,MAAM,kBAAqB,GAAA,MAAA,CAAO,gBAAiB,CAAA,OAAO,CAAE,CAAA,UAAA,CAAA;AAE5D,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,OAAO,CAAA,CAAA;AAEjC,IAAO,OAAA,kBAAA,CAAA;AAAA,GACP,CAAA,MAAA;AACA,IAAO,OAAAC,2BAAA,CAAA;AAAA,GACT;AACF,CAAA;AAEA,SAAS,wBAAwB,MAAuC,EAAA;AACtE,EAAM,MAAA,QAAA,uBAAe,GAAoB,EAAA,CAAA;AAEzC,EAAA,KAAA,MAAW,SAAS,MAAQ,EAAA;AAC1B,IAAA,IAAI,CAAC,QAAA,CAAS,GAAI,CAAA,KAAA,CAAM,OAAO,CAAG,EAAA;AAChC,MAAA,QAAA,CAAS,GAAI,CAAA,KAAA,CAAM,OAAS,EAAA,KAAA,CAAM,KAAK,CAAA,CAAA;AAAA,KACzC;AAAA,GACF;AAEA,EAAA,MAAM,kBAAqB,GAAA,CAAC,GAAG,QAAA,CAAS,IAAK,EAAC,CAAE,CAAA,IAAA,CAAK,CAAC,CAAA,EAAG,CAAM,KAAA,CAAA,GAAI,CAAC,CAAA,CAAA;AAEpE,EAAM,MAAA,aAAA,GAAgB,QACnB,CAAA,aAAA,CAAc,QAAQ,CAAA,CACtB,WAAW,IAAM,EAAA,EAAE,kBAAoB,EAAA,IAAA,EAAM,CAAA,CAAA;AAEhD,EAAA,IAAI,CAAC,aAAe,EAAA;AAClB,IAAA,OAAO,EAAE,YAAA,EAAc,kBAAmB,CAAA,CAAA,CAAA,EAAI,cAAc,IAAK,EAAA,CAAA;AAAA,GACnE;AAEA,EAAc,aAAA,CAAA,IAAA,GAAO,GAAG,IAAK,CAAA,KAAA;AAAA,IAC3B,6BAAgC,GAAA,CAAA;AAAA,SAC3B,kBAAmB,EAAA,CAAA,CAAA,CAAA;AAC1B,EAAA,aAAA,CAAc,YAAe,GAAA,KAAA,CAAA;AAC7B,EAAc,aAAA,CAAA,MAAA,CAAO,QAAQ,4BAA+B,GAAA,CAAA,CAAA;AAC5D,EAAA,aAAA,CAAc,OAAO,MAAS,GAAA,6BAAA,CAAA;AAE9B,EAAA,MAAM,oBAAuB,GAAA,kBAAA;AAAA,IAC3B,aAAA;AAAA,IACA,4BAAA;AAAA,GACF,CAAA;AAEA,EAAA,KAAA,MAAW,WAAW,kBAAoB,EAAA;AACxC,IAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAClC,IAAM,MAAA,WAAA,GAAc,kBAAmB,CAAA,aAAA,EAAe,KAAK,CAAA,CAAA;AAE3D,IAAA,IAAI,WAAa,EAAA;AACf,MAAO,OAAA;AAAA,QACL,YAAc,EAAA,OAAA;AAAA,QACd,YAAc,EAAA,oBAAA;AAAA,OAChB,CAAA;AAAA,KACF;AAAA,GACF;AAEA,EAAO,OAAA;AAAA,IACL,cAAc,kBAAmB,CAAA,CAAA,CAAA;AAAA,IACjC,YAAc,EAAA,oBAAA;AAAA,GAChB,CAAA;AACF,CAAA;AAEA,eAAsB,aAAa,MAAoC,EAAA;AACrE,EAAM,MAAA,eAAA,GAAkB,4BAA4B,MAAM,CAAA,CAAA;AAE1D,EAAA,MAAM,eAAkB,GAAA,cAAA;AAAA,IACtB,cAAA;AAAA,IACA,gCAAA;AAAA,GACF,CAAA;AACA,EAAA,MAAM,UAAa,GAAA,cAAA;AAAA,IACjB,YAAA;AAAA,IACA,qBAAqB,eAAe,CAAA;AAAA,GACtC,CAAA;AACA,EAAI,IAAA,IAAA,CAAA;AAIJ,EAAA,IAAI,UAAY,EAAA;AAEd,IAAA,IAAI,eAAiB,EAAA;AACnB,MAAO,IAAA,GAAA,UAAA,CAAA;AAAA,KACF,MAAA;AACL,MAAA,MAAM,EAAE,UAAY,EAAA,YAAA,EAClB,GAAA,MAAM,oBAAoB,eAAe,CAAA,CAAA;AAC3C,MAAA,MAAM,cAAiB,GAAA,cAAA;AAAA,QACrB,YAAA;AAAA,QACA,yBAAyB,eAAe,CAAA;AAAA,OAC1C,CAAA;AAEA,MAAA,IACE,kBACA,UAAe,KAAA,cAAA,CAAe,UAC9B,IAAA,YAAA,KAAiB,eAAe,YAChC,EAAA;AACA,QAAO,IAAA,GAAA,UAAA,CAAA;AAAA,OACF,MAAA;AACL,QAAO,IAAA,GAAA,MAAM,eAAe,eAAe,CAAA,CAAA;AAAA,OAC7C;AAAA,KACF;AAAA,GACK,MAAA;AACL,IAAO,IAAA,GAAA,MAAM,eAAe,eAAe,CAAA,CAAA;AAAA,GAC7C;AAEA,EAAA,MAAM,kBACJ,GAAA,eAAA,IAAmB,uBAAwB,CAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACxD,EAAA,cAAA;AAAA,IACE,cAAA;AAAA,IACA,gCAAA;AAAA,IACA,kBAAA;AAAA,GACF,CAAA;AAGA,EAAA,MAAM,cAAiB,GAAA,IAAA,CAAK,MAAO,CAAA,MAAA,CAAO,CAAC,KAAU,KAAA;AACnD,IAAM,MAAA,kBAAA,GAAqB,KAAM,CAAA,OAAA,IAAW,kBAAmB,CAAA,YAAA,CAAA;AAE/D,IAAA,OAAO,KAAM,CAAA,WAAA,GACT,kBAAsB,IAAA,kBAAA,CAAmB,YACzC,GAAA,kBAAA,CAAA;AAAA,GACL,CAAA,CAAA;AAED,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,cAAA;AAAA,IACR,YAAY,IAAK,CAAA,UAAA;AAAA,IACjB,WAAW,IAAK,CAAA,SAAA;AAAA,GAClB,CAAA;AACF,CAAA;AAEgB,SAAA,YAAA,CAAa,QAAiB,MAAiB,EAAA;AAC7D,EAAA,IAAI,CAAC,MAAQ,EAAA;AACX,IAAO,OAAA,MAAA,CAAA;AAAA,GACT;AAEA,EAAA,MAAM,UAAa,GAAA,MAAA,CAAO,WAAY,EAAA,CAAE,IAAK,EAAA,CAAA;AAE7C,EAAA,OAAO,MAAO,CAAA,MAAA;AAAA,IACZ,CAAC,KACC,KAAA,KAAA,CAAM,KAAK,WAAY,EAAA,CAAE,SAAS,UAAU,CAAA,IAC5C,MAAM,IAAM,EAAA,IAAA,CAAK,CAAC,GAAQ,KAAA,GAAA,CAAI,aAAc,CAAA,QAAA,CAAS,UAAU,CAAC,CAAA;AAAA,GACpE,CAAA;AACF,CAAA;AAEgB,SAAA,uBAAA,CACd,MACA,EAAA,UAAA,EACA,OACiB,EAAA;AACjB,EAAA,IAAI,YAAe,GAAA,CAAA,CAAA;AACnB,EAAA,MAAM,OAAyB,EAAC,CAAA;AAChC,EAAM,MAAA,aAAA,GAAgB,MAAO,CAAA,GAAA,CAAI,CAAC,KAAA,EAAO,WAAW,EAAE,GAAG,KAAO,EAAA,KAAA,EAAQ,CAAA,CAAA,CAAA;AACxE,EAAA,MAAM,sBAAgC,EAAC,CAAA;AACvC,EAAA,MAAM,uBAAmC,EAAC,CAAA;AAC1C,EAAA,MAAM,kBAA4B,EAAC,CAAA;AACnC,EAAA,MAAM,iBAAoB,GAAA,UAAA,CACvB,GAAI,CAAA,CAAC,QAAc,MAAA;AAAA,IAClB,GAAG,QAAA;AAAA,IACH,MAAA,EAAQ,cAAc,MAAO,CAAA,CAAC,UAAU,KAAM,CAAA,QAAA,KAAa,SAAS,GAAG,CAAA;AAAA,GACzE,CAAE,EACD,MAAO,CAAA,CAAC,aAAa,QAAS,CAAA,MAAA,CAAO,SAAS,CAAC,CAAA,CAAA;AAElD,EAAA,KAAA,MAAW,YAAY,iBAAmB,EAAA;AACxC,IAAA,MAAM,YAAe,GAAAC,UAAA,CAAM,QAAS,CAAA,MAAA,EAAQ,OAAO,CAAA,CAAA;AACnD,IAAM,MAAA,SAAA,GAAY,eAAe,YAAa,CAAA,MAAA,CAAA;AAE9C,IAAK,IAAA,CAAA,IAAA,CAAK,GAAG,YAAY,CAAA,CAAA;AACzB,IAAgB,eAAA,CAAA,IAAA,CAAK,SAAS,IAAI,CAAA,CAAA;AAClC,IAAoB,mBAAA,CAAA,IAAA,CAAK,aAAa,MAAM,CAAA,CAAA;AAC5C,IAAqB,oBAAA,CAAA,IAAA;AAAA,MACnB,oBAAA,CAAqB,YAAc,EAAA,SAAA,GAAY,CAAC,CAAA;AAAA,KAClD,CAAA;AACA,IAAe,YAAA,GAAA,SAAA,CAAA;AAAA,GACjB;AAEA,EAAO,OAAA;AAAA,IACL,OAAO,MAAO,CAAA,MAAA;AAAA,IACd,IAAA;AAAA,IACA,UAAY,EAAA,eAAA;AAAA,IACZ,mBAAA;AAAA,IACA,oBAAA;AAAA,GACF,CAAA;AACF;;;;;;"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/primitives/EmojiPicker/utils.ts"],"sourcesContent":["import { chunk } from \"@liveblocks/core\";\nimport type {\n Emoji as EmojibaseEmoji,\n Locale as EmojibaseLocale,\n MessagesDataset as EmojibaseMessagesDataset,\n} from \"emojibase\";\n\nimport { EMOJI_FONT_FAMILY } from \"../../constants\";\nimport { capitalize } from \"../../utils/capitalize\";\nimport type {\n Emoji,\n EmojiCategory,\n EmojiData,\n EmojiPickerData,\n EmojiPickerRow,\n} from \"./types\";\n\nconst EMOJIBASE_VERSION = \"15.3.0\";\nconst EMOJIBASE_CDN_URL = `https://cdn.jsdelivr.net/npm/emojibase-data@${EMOJIBASE_VERSION}`;\nconst EMOJIBASE_EMOJIS_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/data.json`;\nconst EMOJIBASE_MESSAGES_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/messages.json`;\nconst EMOJIBASE_LOCALES: EmojibaseLocale[] = [\n \"bn\",\n \"da\",\n \"de\",\n \"en\",\n \"en-gb\",\n \"es\",\n \"es-mx\",\n \"et\",\n \"fi\",\n \"fr\",\n \"hi\",\n \"hu\",\n \"it\",\n \"ja\",\n \"ko\",\n \"lt\",\n \"ms\",\n \"nb\",\n \"nl\",\n \"pl\",\n \"pt\",\n \"ru\",\n \"sv\",\n \"th\",\n \"uk\",\n \"zh\",\n \"zh-hant\",\n];\nconst EMOJIBASE_DEFAULT_LOCALE: EmojibaseLocale = \"en\";\n\nconst CACHE_EMOJI_DATA_KEY = (locale: string) => `lb-emoji-data-${locale}`;\nconst CACHE_EMOJI_METADATA_KEY = (locale: string) =>\n `lb-emoji-metadata-${locale}`;\nconst CACHE_EMOJI_SESSION_METADATA_KEY = \"lb-emoji-metadata\";\n\nconst EMOJI_DETECTION_CANVAS_WIDTH = 20;\nconst EMOJI_DETECTION_CANVAS_HEIGHT = 25;\nconst EMOJI_DETECTION_COUNTRY_FLAG = \"🇫🇷\";\n\ntype EmojiMetadata = {\n emojisEtag: string | null;\n messagesEtag: string | null;\n};\n\ntype EmojiSessionMetadata = {\n emojiVersion: number;\n countryFlags: boolean;\n};\n\nfunction generateRangeIndices(start: number, end: number) {\n const range: number[] = [];\n\n for (let i = start; i <= end; i++) {\n range.push(i);\n }\n\n return range;\n}\n\nfunction getStorageItem<T>(storage: Storage, key: string) {\n const item = storage.getItem(key);\n\n return item ? (JSON.parse(item) as T) : null;\n}\n\nfunction setStorageItem<T>(storage: Storage, key: string, value: T) {\n storage.setItem(key, JSON.stringify(value));\n}\n\nasync function fetchEtag(url: string) {\n try {\n const response = await fetch(url, { method: \"HEAD\" });\n\n return response.headers.get(\"etag\");\n } catch (error) {\n return null;\n }\n}\n\nfunction getEmojibaseSupportedLocale(locale: string): EmojibaseLocale {\n return EMOJIBASE_LOCALES.includes(locale as EmojibaseLocale)\n ? (locale as EmojibaseLocale)\n : EMOJIBASE_DEFAULT_LOCALE;\n}\n\nasync function fetchEmojibaseData(locale: EmojibaseLocale) {\n const [{ emojis, emojisEtag }, { messages, messagesEtag }] =\n await Promise.all([\n fetch(EMOJIBASE_EMOJIS_URL(locale)).then(async (response) => {\n return {\n emojis: (await response.json()) as EmojibaseEmoji[],\n emojisEtag: response.headers.get(\"etag\"),\n };\n }),\n fetch(EMOJIBASE_MESSAGES_URL(locale)).then(async (response) => {\n return {\n messages: (await response.json()) as EmojibaseMessagesDataset,\n messagesEtag: response.headers.get(\"etag\"),\n };\n }),\n ]);\n\n return {\n emojis,\n messages,\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojibaseEtags(locale: EmojibaseLocale) {\n const [emojisEtag, messagesEtag] = await Promise.all([\n fetchEtag(EMOJIBASE_EMOJIS_URL(locale)),\n fetchEtag(EMOJIBASE_MESSAGES_URL(locale)),\n ]);\n\n return {\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojiData(locale: EmojibaseLocale): Promise<EmojiData> {\n const { emojis, emojisEtag, messages, messagesEtag } =\n await fetchEmojibaseData(locale);\n const countryFlagsSubgroup = messages.subgroups.find(\n (subgroup) => subgroup.key === \"subdivision-flag\"\n );\n\n // Filter out component/modifier category and emojis\n const filteredGroups = messages.groups.filter(\n (group) => group.key !== \"component\"\n );\n const filteredEmojis = emojis.filter((emoji) => {\n return \"group\" in emoji;\n });\n\n // Pick and compact the data\n const categories = filteredGroups.map((group) => ({\n key: group.order,\n name: capitalize(group.message),\n }));\n const skinTones = messages.skinTones.map((skinTone) => ({\n key: skinTone.key,\n name: capitalize(skinTone.message),\n }));\n const compactEmojis = filteredEmojis.map((emoji) => {\n const compactEmoji: Emoji = {\n emoji: emoji.emoji,\n category: emoji.group!,\n version: emoji.version,\n name: capitalize(emoji.label),\n tags: emoji.tags,\n };\n\n if (countryFlagsSubgroup && emoji.subgroup === countryFlagsSubgroup.order) {\n compactEmoji.countryFlag = true;\n }\n\n return compactEmoji;\n });\n\n const emojiData = {\n emojis: compactEmojis,\n categories,\n skinTones,\n };\n\n // Cache the data and metadata\n setStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(locale),\n emojiData\n );\n setStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(locale),\n {\n emojisEtag,\n messagesEtag,\n }\n );\n\n return emojiData;\n}\n\n// Adapted from https://github.com/koala-interactive/is-emoji-supported/tree/master\nfunction detectEmojiSupport(\n canvasContext: CanvasRenderingContext2D,\n emoji: string\n): boolean {\n canvasContext.clearRect(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH * 2,\n EMOJI_DETECTION_CANVAS_HEIGHT\n );\n\n // Draw in red on the left\n canvasContext.fillStyle = \"#f00\";\n canvasContext.fillText(emoji, 0, 22);\n\n // Draw in blue on right\n canvasContext.fillStyle = \"#00f\";\n canvasContext.fillText(emoji, EMOJI_DETECTION_CANVAS_WIDTH, 22);\n\n const pixels = canvasContext.getImageData(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH,\n EMOJI_DETECTION_CANVAS_HEIGHT\n ).data;\n const pixelCount = pixels.length;\n let i = 0;\n\n // Search for the first visible pixel\n for (; i < pixelCount && !pixels[i + 3]; i += 4);\n\n // No visible pixel\n if (i >= pixelCount) {\n return false;\n }\n\n // Emojis have an immutable color, so we check the color of the emoji in two\n // different colors, the result should be the same\n const x =\n EMOJI_DETECTION_CANVAS_WIDTH + ((i / 4) % EMOJI_DETECTION_CANVAS_WIDTH);\n const y = Math.floor(i / 4 / EMOJI_DETECTION_CANVAS_WIDTH);\n const pixel = canvasContext.getImageData(x, y, 1, 1).data;\n\n if (pixels[i] !== pixel[0] || pixels[i + 2] !== pixel[2]) {\n return false;\n }\n\n // Unsupported ZWJ sequence emojis show up as separate emojis\n if (canvasContext.measureText(emoji).width >= EMOJI_DETECTION_CANVAS_WIDTH) {\n return false;\n }\n\n return true;\n}\n\nfunction getEmojiFontFamily() {\n try {\n const element = document.createElement(\"span\");\n element.style.display = \"none\";\n element.dataset.emoji = \"\";\n\n document.body.appendChild(element);\n\n const computedFontFamily = window.getComputedStyle(element).fontFamily;\n\n document.body.removeChild(element);\n\n return computedFontFamily;\n } catch {\n return EMOJI_FONT_FAMILY;\n }\n}\n\nfunction getEmojiSessionMetadata(emojis: Emoji[]): EmojiSessionMetadata {\n const versions = new Map<number, string>();\n\n for (const emoji of emojis) {\n if (!versions.has(emoji.version)) {\n versions.set(emoji.version, emoji.emoji);\n }\n }\n\n const descendingVersions = [...versions.keys()].sort((a, b) => b - a);\n const highestVersion = descendingVersions[0] ?? 0;\n\n const canvasContext = document\n .createElement(\"canvas\")\n .getContext(\"2d\", { willReadFrequently: true });\n\n if (!canvasContext) {\n return { emojiVersion: highestVersion, countryFlags: true };\n }\n\n canvasContext.font = `${Math.floor(\n EMOJI_DETECTION_CANVAS_HEIGHT / 2\n )}px ${getEmojiFontFamily()}`;\n canvasContext.textBaseline = \"top\";\n canvasContext.canvas.width = EMOJI_DETECTION_CANVAS_WIDTH * 2;\n canvasContext.canvas.height = EMOJI_DETECTION_CANVAS_HEIGHT;\n\n const supportsCountryFlags = detectEmojiSupport(\n canvasContext,\n EMOJI_DETECTION_COUNTRY_FLAG\n );\n\n for (const version of descendingVersions) {\n const emoji = versions.get(version)!;\n const isSupported = detectEmojiSupport(canvasContext, emoji);\n\n if (isSupported) {\n return {\n emojiVersion: version,\n countryFlags: supportsCountryFlags,\n };\n }\n }\n\n return {\n emojiVersion: highestVersion,\n countryFlags: supportsCountryFlags,\n };\n}\n\nexport async function getEmojiData(locale: string): Promise<EmojiData> {\n const emojibaseLocale = getEmojibaseSupportedLocale(locale);\n\n const sessionMetadata = getStorageItem<EmojiSessionMetadata>(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY\n );\n const cachedData = getStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(emojibaseLocale)\n );\n let data: EmojiData;\n\n // If there is data already cached, check if the ETags are the same.\n // If they are, return the cached data, otherwise fetch it again.\n if (cachedData) {\n // ETags only need to be checked once per session\n if (sessionMetadata) {\n data = cachedData;\n } else {\n const { emojisEtag, messagesEtag } =\n await fetchEmojibaseEtags(emojibaseLocale);\n const cachedMetadata = getStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(emojibaseLocale)\n );\n\n if (\n cachedMetadata &&\n emojisEtag === cachedMetadata.emojisEtag &&\n messagesEtag === cachedMetadata.messagesEtag\n ) {\n data = cachedData;\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n }\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n\n const newSessionMetadata =\n sessionMetadata ?? getEmojiSessionMetadata(data.emojis);\n setStorageItem(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY,\n newSessionMetadata\n );\n\n // Filter out unsupported emojis\n const filteredEmojis = data.emojis.filter((emoji) => {\n const isSupportedVersion = emoji.version <= newSessionMetadata.emojiVersion;\n\n return emoji.countryFlag\n ? isSupportedVersion && newSessionMetadata.countryFlags\n : isSupportedVersion;\n });\n\n return {\n emojis: filteredEmojis,\n categories: data.categories,\n skinTones: data.skinTones,\n };\n}\n\nexport function filterEmojis(emojis: Emoji[], search?: string) {\n if (!search) {\n return emojis;\n }\n\n const searchText = search.toLowerCase().trim();\n\n return emojis.filter(\n (emoji) =>\n emoji.name.toLowerCase().includes(searchText) ||\n emoji.tags?.some((tag) => tag.toLowerCase().includes(searchText))\n );\n}\n\nexport function generateEmojiPickerData(\n emojis: Emoji[],\n categories: EmojiCategory[],\n columns: number\n): EmojiPickerData {\n let currentIndex = 0;\n const rows: EmojiPickerRow[] = [];\n const indexedEmojis = emojis.map((emoji, index) => ({ ...emoji, index }));\n const categoriesRowCounts: number[] = [];\n const categoriesRowIndices: number[][] = [];\n const categoriesNames: string[] = [];\n const categorizedEmojis = categories\n .map((category) => ({\n ...category,\n emojis: indexedEmojis.filter((emoji) => emoji.category === category.key),\n }))\n .filter((category) => category.emojis.length > 0);\n\n for (const category of categorizedEmojis) {\n const categoryRows = chunk(category.emojis, columns);\n const nextIndex = currentIndex + categoryRows.length;\n\n rows.push(...categoryRows);\n categoriesNames.push(category.name);\n categoriesRowCounts.push(categoryRows.length);\n categoriesRowIndices.push(\n generateRangeIndices(currentIndex, nextIndex - 1)\n );\n currentIndex = nextIndex;\n }\n\n return {\n count: emojis.length,\n rows,\n categories: categoriesNames,\n categoriesRowCounts,\n categoriesRowIndices,\n };\n}\n"],"names":["capitalize","EMOJI_FONT_FAMILY","chunk"],"mappings":"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"}
|
|
@@ -207,9 +207,10 @@ function getEmojiSessionMetadata(emojis) {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
const descendingVersions = [...versions.keys()].sort((a, b) => b - a);
|
|
210
|
+
const highestVersion = descendingVersions[0] ?? 0;
|
|
210
211
|
const canvasContext = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
|
|
211
212
|
if (!canvasContext) {
|
|
212
|
-
return { emojiVersion:
|
|
213
|
+
return { emojiVersion: highestVersion, countryFlags: true };
|
|
213
214
|
}
|
|
214
215
|
canvasContext.font = `${Math.floor(
|
|
215
216
|
EMOJI_DETECTION_CANVAS_HEIGHT / 2
|
|
@@ -232,7 +233,7 @@ function getEmojiSessionMetadata(emojis) {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
return {
|
|
235
|
-
emojiVersion:
|
|
236
|
+
emojiVersion: highestVersion,
|
|
236
237
|
countryFlags: supportsCountryFlags
|
|
237
238
|
};
|
|
238
239
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sources":["../../../src/primitives/EmojiPicker/utils.ts"],"sourcesContent":["import { chunk } from \"@liveblocks/core\";\nimport type {\n Emoji as EmojibaseEmoji,\n Locale as EmojibaseLocale,\n MessagesDataset as EmojibaseMessagesDataset,\n} from \"emojibase\";\n\nimport { EMOJI_FONT_FAMILY } from \"../../constants\";\nimport { capitalize } from \"../../utils/capitalize\";\nimport type {\n Emoji,\n EmojiCategory,\n EmojiData,\n EmojiPickerData,\n EmojiPickerRow,\n} from \"./types\";\n\nconst EMOJIBASE_VERSION = \"15.3.0\";\nconst EMOJIBASE_CDN_URL = `https://cdn.jsdelivr.net/npm/emojibase-data@${EMOJIBASE_VERSION}`;\nconst EMOJIBASE_EMOJIS_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/data.json`;\nconst EMOJIBASE_MESSAGES_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/messages.json`;\nconst EMOJIBASE_LOCALES: EmojibaseLocale[] = [\n \"bn\",\n \"da\",\n \"de\",\n \"en\",\n \"en-gb\",\n \"es\",\n \"es-mx\",\n \"et\",\n \"fi\",\n \"fr\",\n \"hi\",\n \"hu\",\n \"it\",\n \"ja\",\n \"ko\",\n \"lt\",\n \"ms\",\n \"nb\",\n \"nl\",\n \"pl\",\n \"pt\",\n \"ru\",\n \"sv\",\n \"th\",\n \"uk\",\n \"zh\",\n \"zh-hant\",\n];\nconst EMOJIBASE_DEFAULT_LOCALE: EmojibaseLocale = \"en\";\n\nconst CACHE_EMOJI_DATA_KEY = (locale: string) => `lb-emoji-data-${locale}`;\nconst CACHE_EMOJI_METADATA_KEY = (locale: string) =>\n `lb-emoji-metadata-${locale}`;\nconst CACHE_EMOJI_SESSION_METADATA_KEY = \"lb-emoji-metadata\";\n\nconst EMOJI_DETECTION_CANVAS_WIDTH = 20;\nconst EMOJI_DETECTION_CANVAS_HEIGHT = 25;\nconst EMOJI_DETECTION_COUNTRY_FLAG = \"🇫🇷\";\n\ntype EmojiMetadata = {\n emojisEtag: string | null;\n messagesEtag: string | null;\n};\n\ntype EmojiSessionMetadata = {\n emojiVersion: number;\n countryFlags: boolean;\n};\n\nfunction generateRangeIndices(start: number, end: number) {\n const range: number[] = [];\n\n for (let i = start; i <= end; i++) {\n range.push(i);\n }\n\n return range;\n}\n\nfunction getStorageItem<T>(storage: Storage, key: string) {\n const item = storage.getItem(key);\n\n return item ? (JSON.parse(item) as T) : null;\n}\n\nfunction setStorageItem<T>(storage: Storage, key: string, value: T) {\n storage.setItem(key, JSON.stringify(value));\n}\n\nasync function fetchEtag(url: string) {\n try {\n const response = await fetch(url, { method: \"HEAD\" });\n\n return response.headers.get(\"etag\");\n } catch (error) {\n return null;\n }\n}\n\nfunction getEmojibaseSupportedLocale(locale: string): EmojibaseLocale {\n return EMOJIBASE_LOCALES.includes(locale as EmojibaseLocale)\n ? (locale as EmojibaseLocale)\n : EMOJIBASE_DEFAULT_LOCALE;\n}\n\nasync function fetchEmojibaseData(locale: EmojibaseLocale) {\n const [{ emojis, emojisEtag }, { messages, messagesEtag }] =\n await Promise.all([\n fetch(EMOJIBASE_EMOJIS_URL(locale)).then(async (response) => {\n return {\n emojis: (await response.json()) as EmojibaseEmoji[],\n emojisEtag: response.headers.get(\"etag\"),\n };\n }),\n fetch(EMOJIBASE_MESSAGES_URL(locale)).then(async (response) => {\n return {\n messages: (await response.json()) as EmojibaseMessagesDataset,\n messagesEtag: response.headers.get(\"etag\"),\n };\n }),\n ]);\n\n return {\n emojis,\n messages,\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojibaseEtags(locale: EmojibaseLocale) {\n const [emojisEtag, messagesEtag] = await Promise.all([\n fetchEtag(EMOJIBASE_EMOJIS_URL(locale)),\n fetchEtag(EMOJIBASE_MESSAGES_URL(locale)),\n ]);\n\n return {\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojiData(locale: EmojibaseLocale): Promise<EmojiData> {\n const { emojis, emojisEtag, messages, messagesEtag } =\n await fetchEmojibaseData(locale);\n const countryFlagsSubgroup = messages.subgroups.find(\n (subgroup) => subgroup.key === \"subdivision-flag\"\n );\n\n // Filter out component/modifier category and emojis\n const filteredGroups = messages.groups.filter(\n (group) => group.key !== \"component\"\n );\n const filteredEmojis = emojis.filter((emoji) => {\n return \"group\" in emoji;\n });\n\n // Pick and compact the data\n const categories = filteredGroups.map((group) => ({\n key: group.order,\n name: capitalize(group.message),\n }));\n const skinTones = messages.skinTones.map((skinTone) => ({\n key: skinTone.key,\n name: capitalize(skinTone.message),\n }));\n const compactEmojis = filteredEmojis.map((emoji) => {\n const compactEmoji: Emoji = {\n emoji: emoji.emoji,\n category: emoji.group!,\n version: emoji.version,\n name: capitalize(emoji.label),\n tags: emoji.tags,\n };\n\n if (countryFlagsSubgroup && emoji.subgroup === countryFlagsSubgroup.order) {\n compactEmoji.countryFlag = true;\n }\n\n return compactEmoji;\n });\n\n const emojiData = {\n emojis: compactEmojis,\n categories,\n skinTones,\n };\n\n // Cache the data and metadata\n setStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(locale),\n emojiData\n );\n setStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(locale),\n {\n emojisEtag,\n messagesEtag,\n }\n );\n\n return emojiData;\n}\n\n// Adapted from https://github.com/koala-interactive/is-emoji-supported/tree/master\nfunction detectEmojiSupport(\n canvasContext: CanvasRenderingContext2D,\n emoji: string\n): boolean {\n canvasContext.clearRect(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH * 2,\n EMOJI_DETECTION_CANVAS_HEIGHT\n );\n\n // Draw in red on the left\n canvasContext.fillStyle = \"#f00\";\n canvasContext.fillText(emoji, 0, 22);\n\n // Draw in blue on right\n canvasContext.fillStyle = \"#00f\";\n canvasContext.fillText(emoji, EMOJI_DETECTION_CANVAS_WIDTH, 22);\n\n const pixels = canvasContext.getImageData(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH,\n EMOJI_DETECTION_CANVAS_HEIGHT\n ).data;\n const pixelCount = pixels.length;\n let i = 0;\n\n // Search for the first visible pixel\n for (; i < pixelCount && !pixels[i + 3]; i += 4);\n\n // No visible pixel\n if (i >= pixelCount) {\n return false;\n }\n\n // Emojis have an immutable color, so we check the color of the emoji in two\n // different colors, the result should be the same\n const x =\n EMOJI_DETECTION_CANVAS_WIDTH + ((i / 4) % EMOJI_DETECTION_CANVAS_WIDTH);\n const y = Math.floor(i / 4 / EMOJI_DETECTION_CANVAS_WIDTH);\n const pixel = canvasContext.getImageData(x, y, 1, 1).data;\n\n if (pixels[i] !== pixel[0] || pixels[i + 2] !== pixel[2]) {\n return false;\n }\n\n // Unsupported ZWJ sequence emojis show up as separate emojis\n if (canvasContext.measureText(emoji).width >= EMOJI_DETECTION_CANVAS_WIDTH) {\n return false;\n }\n\n return true;\n}\n\nfunction getEmojiFontFamily() {\n try {\n const element = document.createElement(\"span\");\n element.style.display = \"none\";\n element.dataset.emoji = \"\";\n\n document.body.appendChild(element);\n\n const computedFontFamily = window.getComputedStyle(element).fontFamily;\n\n document.body.removeChild(element);\n\n return computedFontFamily;\n } catch {\n return EMOJI_FONT_FAMILY;\n }\n}\n\nfunction getEmojiSessionMetadata(emojis: Emoji[]): EmojiSessionMetadata {\n const versions = new Map<number, string>();\n\n for (const emoji of emojis) {\n if (!versions.has(emoji.version)) {\n versions.set(emoji.version, emoji.emoji);\n }\n }\n\n const descendingVersions = [...versions.keys()].sort((a, b) => b - a);\n\n const canvasContext = document\n .createElement(\"canvas\")\n .getContext(\"2d\", { willReadFrequently: true });\n\n if (!canvasContext) {\n return { emojiVersion: descendingVersions[0], countryFlags: true };\n }\n\n canvasContext.font = `${Math.floor(\n EMOJI_DETECTION_CANVAS_HEIGHT / 2\n )}px ${getEmojiFontFamily()}`;\n canvasContext.textBaseline = \"top\";\n canvasContext.canvas.width = EMOJI_DETECTION_CANVAS_WIDTH * 2;\n canvasContext.canvas.height = EMOJI_DETECTION_CANVAS_HEIGHT;\n\n const supportsCountryFlags = detectEmojiSupport(\n canvasContext,\n EMOJI_DETECTION_COUNTRY_FLAG\n );\n\n for (const version of descendingVersions) {\n const emoji = versions.get(version)!;\n const isSupported = detectEmojiSupport(canvasContext, emoji);\n\n if (isSupported) {\n return {\n emojiVersion: version,\n countryFlags: supportsCountryFlags,\n };\n }\n }\n\n return {\n emojiVersion: descendingVersions[0],\n countryFlags: supportsCountryFlags,\n };\n}\n\nexport async function getEmojiData(locale: string): Promise<EmojiData> {\n const emojibaseLocale = getEmojibaseSupportedLocale(locale);\n\n const sessionMetadata = getStorageItem<EmojiSessionMetadata>(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY\n );\n const cachedData = getStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(emojibaseLocale)\n );\n let data: EmojiData;\n\n // If there is data already cached, check if the ETags are the same.\n // If they are, return the cached data, otherwise fetch it again.\n if (cachedData) {\n // ETags only need to be checked once per session\n if (sessionMetadata) {\n data = cachedData;\n } else {\n const { emojisEtag, messagesEtag } =\n await fetchEmojibaseEtags(emojibaseLocale);\n const cachedMetadata = getStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(emojibaseLocale)\n );\n\n if (\n cachedMetadata &&\n emojisEtag === cachedMetadata.emojisEtag &&\n messagesEtag === cachedMetadata.messagesEtag\n ) {\n data = cachedData;\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n }\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n\n const newSessionMetadata =\n sessionMetadata ?? getEmojiSessionMetadata(data.emojis);\n setStorageItem(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY,\n newSessionMetadata\n );\n\n // Filter out unsupported emojis\n const filteredEmojis = data.emojis.filter((emoji) => {\n const isSupportedVersion = emoji.version <= newSessionMetadata.emojiVersion;\n\n return emoji.countryFlag\n ? isSupportedVersion && newSessionMetadata.countryFlags\n : isSupportedVersion;\n });\n\n return {\n emojis: filteredEmojis,\n categories: data.categories,\n skinTones: data.skinTones,\n };\n}\n\nexport function filterEmojis(emojis: Emoji[], search?: string) {\n if (!search) {\n return emojis;\n }\n\n const searchText = search.toLowerCase().trim();\n\n return emojis.filter(\n (emoji) =>\n emoji.name.toLowerCase().includes(searchText) ||\n emoji.tags?.some((tag) => tag.toLowerCase().includes(searchText))\n );\n}\n\nexport function generateEmojiPickerData(\n emojis: Emoji[],\n categories: EmojiCategory[],\n columns: number\n): EmojiPickerData {\n let currentIndex = 0;\n const rows: EmojiPickerRow[] = [];\n const indexedEmojis = emojis.map((emoji, index) => ({ ...emoji, index }));\n const categoriesRowCounts: number[] = [];\n const categoriesRowIndices: number[][] = [];\n const categoriesNames: string[] = [];\n const categorizedEmojis = categories\n .map((category) => ({\n ...category,\n emojis: indexedEmojis.filter((emoji) => emoji.category === category.key),\n }))\n .filter((category) => category.emojis.length > 0);\n\n for (const category of categorizedEmojis) {\n const categoryRows = chunk(category.emojis, columns);\n const nextIndex = currentIndex + categoryRows.length;\n\n rows.push(...categoryRows);\n categoriesNames.push(category.name);\n categoriesRowCounts.push(categoryRows.length);\n categoriesRowIndices.push(\n generateRangeIndices(currentIndex, nextIndex - 1)\n );\n currentIndex = nextIndex;\n }\n\n return {\n count: emojis.length,\n rows,\n categories: categoriesNames,\n categoriesRowCounts,\n categoriesRowIndices,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","sources":["../../../src/primitives/EmojiPicker/utils.ts"],"sourcesContent":["import { chunk } from \"@liveblocks/core\";\nimport type {\n Emoji as EmojibaseEmoji,\n Locale as EmojibaseLocale,\n MessagesDataset as EmojibaseMessagesDataset,\n} from \"emojibase\";\n\nimport { EMOJI_FONT_FAMILY } from \"../../constants\";\nimport { capitalize } from \"../../utils/capitalize\";\nimport type {\n Emoji,\n EmojiCategory,\n EmojiData,\n EmojiPickerData,\n EmojiPickerRow,\n} from \"./types\";\n\nconst EMOJIBASE_VERSION = \"15.3.0\";\nconst EMOJIBASE_CDN_URL = `https://cdn.jsdelivr.net/npm/emojibase-data@${EMOJIBASE_VERSION}`;\nconst EMOJIBASE_EMOJIS_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/data.json`;\nconst EMOJIBASE_MESSAGES_URL = (locale: EmojibaseLocale) =>\n `${EMOJIBASE_CDN_URL}/${locale}/messages.json`;\nconst EMOJIBASE_LOCALES: EmojibaseLocale[] = [\n \"bn\",\n \"da\",\n \"de\",\n \"en\",\n \"en-gb\",\n \"es\",\n \"es-mx\",\n \"et\",\n \"fi\",\n \"fr\",\n \"hi\",\n \"hu\",\n \"it\",\n \"ja\",\n \"ko\",\n \"lt\",\n \"ms\",\n \"nb\",\n \"nl\",\n \"pl\",\n \"pt\",\n \"ru\",\n \"sv\",\n \"th\",\n \"uk\",\n \"zh\",\n \"zh-hant\",\n];\nconst EMOJIBASE_DEFAULT_LOCALE: EmojibaseLocale = \"en\";\n\nconst CACHE_EMOJI_DATA_KEY = (locale: string) => `lb-emoji-data-${locale}`;\nconst CACHE_EMOJI_METADATA_KEY = (locale: string) =>\n `lb-emoji-metadata-${locale}`;\nconst CACHE_EMOJI_SESSION_METADATA_KEY = \"lb-emoji-metadata\";\n\nconst EMOJI_DETECTION_CANVAS_WIDTH = 20;\nconst EMOJI_DETECTION_CANVAS_HEIGHT = 25;\nconst EMOJI_DETECTION_COUNTRY_FLAG = \"🇫🇷\";\n\ntype EmojiMetadata = {\n emojisEtag: string | null;\n messagesEtag: string | null;\n};\n\ntype EmojiSessionMetadata = {\n emojiVersion: number;\n countryFlags: boolean;\n};\n\nfunction generateRangeIndices(start: number, end: number) {\n const range: number[] = [];\n\n for (let i = start; i <= end; i++) {\n range.push(i);\n }\n\n return range;\n}\n\nfunction getStorageItem<T>(storage: Storage, key: string) {\n const item = storage.getItem(key);\n\n return item ? (JSON.parse(item) as T) : null;\n}\n\nfunction setStorageItem<T>(storage: Storage, key: string, value: T) {\n storage.setItem(key, JSON.stringify(value));\n}\n\nasync function fetchEtag(url: string) {\n try {\n const response = await fetch(url, { method: \"HEAD\" });\n\n return response.headers.get(\"etag\");\n } catch (error) {\n return null;\n }\n}\n\nfunction getEmojibaseSupportedLocale(locale: string): EmojibaseLocale {\n return EMOJIBASE_LOCALES.includes(locale as EmojibaseLocale)\n ? (locale as EmojibaseLocale)\n : EMOJIBASE_DEFAULT_LOCALE;\n}\n\nasync function fetchEmojibaseData(locale: EmojibaseLocale) {\n const [{ emojis, emojisEtag }, { messages, messagesEtag }] =\n await Promise.all([\n fetch(EMOJIBASE_EMOJIS_URL(locale)).then(async (response) => {\n return {\n emojis: (await response.json()) as EmojibaseEmoji[],\n emojisEtag: response.headers.get(\"etag\"),\n };\n }),\n fetch(EMOJIBASE_MESSAGES_URL(locale)).then(async (response) => {\n return {\n messages: (await response.json()) as EmojibaseMessagesDataset,\n messagesEtag: response.headers.get(\"etag\"),\n };\n }),\n ]);\n\n return {\n emojis,\n messages,\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojibaseEtags(locale: EmojibaseLocale) {\n const [emojisEtag, messagesEtag] = await Promise.all([\n fetchEtag(EMOJIBASE_EMOJIS_URL(locale)),\n fetchEtag(EMOJIBASE_MESSAGES_URL(locale)),\n ]);\n\n return {\n emojisEtag,\n messagesEtag,\n };\n}\n\nasync function fetchEmojiData(locale: EmojibaseLocale): Promise<EmojiData> {\n const { emojis, emojisEtag, messages, messagesEtag } =\n await fetchEmojibaseData(locale);\n const countryFlagsSubgroup = messages.subgroups.find(\n (subgroup) => subgroup.key === \"subdivision-flag\"\n );\n\n // Filter out component/modifier category and emojis\n const filteredGroups = messages.groups.filter(\n (group) => group.key !== \"component\"\n );\n const filteredEmojis = emojis.filter((emoji) => {\n return \"group\" in emoji;\n });\n\n // Pick and compact the data\n const categories = filteredGroups.map((group) => ({\n key: group.order,\n name: capitalize(group.message),\n }));\n const skinTones = messages.skinTones.map((skinTone) => ({\n key: skinTone.key,\n name: capitalize(skinTone.message),\n }));\n const compactEmojis = filteredEmojis.map((emoji) => {\n const compactEmoji: Emoji = {\n emoji: emoji.emoji,\n category: emoji.group!,\n version: emoji.version,\n name: capitalize(emoji.label),\n tags: emoji.tags,\n };\n\n if (countryFlagsSubgroup && emoji.subgroup === countryFlagsSubgroup.order) {\n compactEmoji.countryFlag = true;\n }\n\n return compactEmoji;\n });\n\n const emojiData = {\n emojis: compactEmojis,\n categories,\n skinTones,\n };\n\n // Cache the data and metadata\n setStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(locale),\n emojiData\n );\n setStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(locale),\n {\n emojisEtag,\n messagesEtag,\n }\n );\n\n return emojiData;\n}\n\n// Adapted from https://github.com/koala-interactive/is-emoji-supported/tree/master\nfunction detectEmojiSupport(\n canvasContext: CanvasRenderingContext2D,\n emoji: string\n): boolean {\n canvasContext.clearRect(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH * 2,\n EMOJI_DETECTION_CANVAS_HEIGHT\n );\n\n // Draw in red on the left\n canvasContext.fillStyle = \"#f00\";\n canvasContext.fillText(emoji, 0, 22);\n\n // Draw in blue on right\n canvasContext.fillStyle = \"#00f\";\n canvasContext.fillText(emoji, EMOJI_DETECTION_CANVAS_WIDTH, 22);\n\n const pixels = canvasContext.getImageData(\n 0,\n 0,\n EMOJI_DETECTION_CANVAS_WIDTH,\n EMOJI_DETECTION_CANVAS_HEIGHT\n ).data;\n const pixelCount = pixels.length;\n let i = 0;\n\n // Search for the first visible pixel\n for (; i < pixelCount && !pixels[i + 3]; i += 4);\n\n // No visible pixel\n if (i >= pixelCount) {\n return false;\n }\n\n // Emojis have an immutable color, so we check the color of the emoji in two\n // different colors, the result should be the same\n const x =\n EMOJI_DETECTION_CANVAS_WIDTH + ((i / 4) % EMOJI_DETECTION_CANVAS_WIDTH);\n const y = Math.floor(i / 4 / EMOJI_DETECTION_CANVAS_WIDTH);\n const pixel = canvasContext.getImageData(x, y, 1, 1).data;\n\n if (pixels[i] !== pixel[0] || pixels[i + 2] !== pixel[2]) {\n return false;\n }\n\n // Unsupported ZWJ sequence emojis show up as separate emojis\n if (canvasContext.measureText(emoji).width >= EMOJI_DETECTION_CANVAS_WIDTH) {\n return false;\n }\n\n return true;\n}\n\nfunction getEmojiFontFamily() {\n try {\n const element = document.createElement(\"span\");\n element.style.display = \"none\";\n element.dataset.emoji = \"\";\n\n document.body.appendChild(element);\n\n const computedFontFamily = window.getComputedStyle(element).fontFamily;\n\n document.body.removeChild(element);\n\n return computedFontFamily;\n } catch {\n return EMOJI_FONT_FAMILY;\n }\n}\n\nfunction getEmojiSessionMetadata(emojis: Emoji[]): EmojiSessionMetadata {\n const versions = new Map<number, string>();\n\n for (const emoji of emojis) {\n if (!versions.has(emoji.version)) {\n versions.set(emoji.version, emoji.emoji);\n }\n }\n\n const descendingVersions = [...versions.keys()].sort((a, b) => b - a);\n const highestVersion = descendingVersions[0] ?? 0;\n\n const canvasContext = document\n .createElement(\"canvas\")\n .getContext(\"2d\", { willReadFrequently: true });\n\n if (!canvasContext) {\n return { emojiVersion: highestVersion, countryFlags: true };\n }\n\n canvasContext.font = `${Math.floor(\n EMOJI_DETECTION_CANVAS_HEIGHT / 2\n )}px ${getEmojiFontFamily()}`;\n canvasContext.textBaseline = \"top\";\n canvasContext.canvas.width = EMOJI_DETECTION_CANVAS_WIDTH * 2;\n canvasContext.canvas.height = EMOJI_DETECTION_CANVAS_HEIGHT;\n\n const supportsCountryFlags = detectEmojiSupport(\n canvasContext,\n EMOJI_DETECTION_COUNTRY_FLAG\n );\n\n for (const version of descendingVersions) {\n const emoji = versions.get(version)!;\n const isSupported = detectEmojiSupport(canvasContext, emoji);\n\n if (isSupported) {\n return {\n emojiVersion: version,\n countryFlags: supportsCountryFlags,\n };\n }\n }\n\n return {\n emojiVersion: highestVersion,\n countryFlags: supportsCountryFlags,\n };\n}\n\nexport async function getEmojiData(locale: string): Promise<EmojiData> {\n const emojibaseLocale = getEmojibaseSupportedLocale(locale);\n\n const sessionMetadata = getStorageItem<EmojiSessionMetadata>(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY\n );\n const cachedData = getStorageItem<EmojiData>(\n localStorage,\n CACHE_EMOJI_DATA_KEY(emojibaseLocale)\n );\n let data: EmojiData;\n\n // If there is data already cached, check if the ETags are the same.\n // If they are, return the cached data, otherwise fetch it again.\n if (cachedData) {\n // ETags only need to be checked once per session\n if (sessionMetadata) {\n data = cachedData;\n } else {\n const { emojisEtag, messagesEtag } =\n await fetchEmojibaseEtags(emojibaseLocale);\n const cachedMetadata = getStorageItem<EmojiMetadata>(\n localStorage,\n CACHE_EMOJI_METADATA_KEY(emojibaseLocale)\n );\n\n if (\n cachedMetadata &&\n emojisEtag === cachedMetadata.emojisEtag &&\n messagesEtag === cachedMetadata.messagesEtag\n ) {\n data = cachedData;\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n }\n } else {\n data = await fetchEmojiData(emojibaseLocale);\n }\n\n const newSessionMetadata =\n sessionMetadata ?? getEmojiSessionMetadata(data.emojis);\n setStorageItem(\n sessionStorage,\n CACHE_EMOJI_SESSION_METADATA_KEY,\n newSessionMetadata\n );\n\n // Filter out unsupported emojis\n const filteredEmojis = data.emojis.filter((emoji) => {\n const isSupportedVersion = emoji.version <= newSessionMetadata.emojiVersion;\n\n return emoji.countryFlag\n ? isSupportedVersion && newSessionMetadata.countryFlags\n : isSupportedVersion;\n });\n\n return {\n emojis: filteredEmojis,\n categories: data.categories,\n skinTones: data.skinTones,\n };\n}\n\nexport function filterEmojis(emojis: Emoji[], search?: string) {\n if (!search) {\n return emojis;\n }\n\n const searchText = search.toLowerCase().trim();\n\n return emojis.filter(\n (emoji) =>\n emoji.name.toLowerCase().includes(searchText) ||\n emoji.tags?.some((tag) => tag.toLowerCase().includes(searchText))\n );\n}\n\nexport function generateEmojiPickerData(\n emojis: Emoji[],\n categories: EmojiCategory[],\n columns: number\n): EmojiPickerData {\n let currentIndex = 0;\n const rows: EmojiPickerRow[] = [];\n const indexedEmojis = emojis.map((emoji, index) => ({ ...emoji, index }));\n const categoriesRowCounts: number[] = [];\n const categoriesRowIndices: number[][] = [];\n const categoriesNames: string[] = [];\n const categorizedEmojis = categories\n .map((category) => ({\n ...category,\n emojis: indexedEmojis.filter((emoji) => emoji.category === category.key),\n }))\n .filter((category) => category.emojis.length > 0);\n\n for (const category of categorizedEmojis) {\n const categoryRows = chunk(category.emojis, columns);\n const nextIndex = currentIndex + categoryRows.length;\n\n rows.push(...categoryRows);\n categoriesNames.push(category.name);\n categoriesRowCounts.push(categoryRows.length);\n categoriesRowIndices.push(\n generateRangeIndices(currentIndex, nextIndex - 1)\n );\n currentIndex = nextIndex;\n }\n\n return {\n count: emojis.length,\n rows,\n categories: categoriesNames,\n categoriesRowCounts,\n categoriesRowIndices,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -63,10 +63,12 @@ function isSeparator(char) {
|
|
|
63
63
|
return PUNCTUATION_OR_SPACE.test(char);
|
|
64
64
|
}
|
|
65
65
|
function endsWithSeparator(textContent) {
|
|
66
|
-
|
|
66
|
+
const lastCharacter = textContent[textContent.length - 1];
|
|
67
|
+
return lastCharacter !== void 0 ? isSeparator(lastCharacter) : false;
|
|
67
68
|
}
|
|
68
69
|
function startsWithSeparator(textContent) {
|
|
69
|
-
|
|
70
|
+
const firstCharacter = textContent[0];
|
|
71
|
+
return firstCharacter !== void 0 ? isSeparator(firstCharacter) : false;
|
|
70
72
|
}
|
|
71
73
|
function endsWithPeriodOrQuestionMark(textContent) {
|
|
72
74
|
return textContent[textContent.length - 1] === "." || textContent[textContent.length - 1] === "?";
|
|
@@ -106,8 +108,10 @@ function isNextNodeValid(editor, path) {
|
|
|
106
108
|
function isContentAroundValid(editor, entry, start, end) {
|
|
107
109
|
const [node, path] = entry;
|
|
108
110
|
const text = node.text;
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
+
const contentBefore = text[start - 1];
|
|
112
|
+
const contentBeforeIsValid = start > 0 && contentBefore ? isSeparator(contentBefore) : isPreviousNodeValid(editor, path);
|
|
113
|
+
const contentAfter = text[end];
|
|
114
|
+
const contentAfterIsValid = end < text.length && contentAfter ? isSeparator(contentAfter) : isNextNodeValid(editor, path);
|
|
111
115
|
return contentBeforeIsValid && contentAfterIsValid;
|
|
112
116
|
}
|
|
113
117
|
const handleLinkEdit = (editor, entry) => {
|
|
@@ -121,7 +125,8 @@ const handleLinkEdit = (editor, entry) => {
|
|
|
121
125
|
}
|
|
122
126
|
const text = slate.Node.string(node);
|
|
123
127
|
const match = URL_REGEX.exec(text);
|
|
124
|
-
|
|
128
|
+
const matchContent = match?.[0];
|
|
129
|
+
if (!match || matchContent !== text) {
|
|
125
130
|
slate.Transforms.unwrapNodes(editor, { at: path });
|
|
126
131
|
return;
|
|
127
132
|
}
|
|
@@ -171,24 +176,26 @@ const handleLinkEdit = (editor, entry) => {
|
|
|
171
176
|
return;
|
|
172
177
|
}
|
|
173
178
|
if (node.url !== text) {
|
|
174
|
-
slate.Transforms.setNodes(editor, { url:
|
|
179
|
+
slate.Transforms.setNodes(editor, { url: matchContent }, { at: path });
|
|
175
180
|
return;
|
|
176
181
|
}
|
|
177
182
|
};
|
|
178
183
|
const handleLinkCreate = (editor, entry) => {
|
|
179
184
|
const [node, path] = entry;
|
|
180
185
|
const match = URL_REGEX.exec(node.text);
|
|
181
|
-
|
|
186
|
+
const matchContent = match?.[0];
|
|
187
|
+
if (!match || matchContent === void 0) {
|
|
182
188
|
return;
|
|
189
|
+
}
|
|
183
190
|
const start = match.index;
|
|
184
|
-
const end = start +
|
|
191
|
+
const end = start + matchContent.length;
|
|
185
192
|
if (!isContentAroundValid(editor, entry, start, end))
|
|
186
193
|
return;
|
|
187
194
|
slate.Transforms.wrapNodes(
|
|
188
195
|
editor,
|
|
189
196
|
{
|
|
190
197
|
type: "auto-link",
|
|
191
|
-
url:
|
|
198
|
+
url: matchContent,
|
|
192
199
|
children: []
|
|
193
200
|
},
|
|
194
201
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-links.js","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry, Text } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\nimport { isPlainText, isText } from \"../utils/is-text\";\nimport { isComposerBodyCustomLink } from \"./custom-links\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n // Prevent auto links from being created inside custom links\n if (isComposerBodyCustomLink(node)) {\n return;\n }\n\n if (isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n // Prevent auto links from being created inside custom links\n if (isComposerBodyCustomLink(parentNode)) {\n return;\n } else if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n\n // Prevent rich text within auto links by removing all marks of inner text nodes\n if (!isPlainText(node)) {\n const marks = Object.keys(node).filter((key) => key !== \"text\");\n\n Transforms.unsetNodes(editor, marks, { at: path });\n }\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s()]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\nconst PARENTHESES = /[()]/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[textContent.length - 1]);\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[0]);\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to get the \"logical length\" of a URL, taking into account things like opening/closing parentheses\n * @param url The URL to check\n * @returns The \"logical length\" of the URL\n */\nfunction getUrlLogicalLength(url: string): number {\n if (!PARENTHESES.test(url)) {\n return url.length;\n }\n\n let logicalLength = 0;\n let parenthesesCount = 0;\n\n for (const character of url) {\n if (character === \"(\") {\n parenthesesCount++;\n }\n\n if (character === \")\") {\n parenthesesCount--;\n\n if (parenthesesCount < 0) {\n break;\n }\n }\n\n logicalLength++;\n }\n\n return logicalLength;\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBeforeIsValid =\n start > 0\n ? isSeparator(text[start - 1])\n : isPreviousNodeValid(editor, path);\n\n const contentAfterIsValid =\n end < text.length ? isSeparator(text[end]) : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || match[0] !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Allow some conditions to shorten the URL (e.g. supporting parentheses but only if they are balanced)\n const logicalLength = getUrlLogicalLength(text);\n\n if (logicalLength < text.length) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const logicalText = text.slice(0, logicalLength);\n\n // Keep the \"logical\" text and wrap it in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: logicalText,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: logicalText.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 5: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 6: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: match[0] }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n if (!match) return;\n\n const start = match.index;\n const end = start + match[0].length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: match[0],\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":["isComposerBodyCustomLink","isText","Node","Path","isPlainText","Transforms","Range","Editor","Element"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auto-links.js","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry, Text } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\nimport { isPlainText, isText } from \"../utils/is-text\";\nimport { isComposerBodyCustomLink } from \"./custom-links\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n // Prevent auto links from being created inside custom links\n if (isComposerBodyCustomLink(node)) {\n return;\n }\n\n if (isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n // Prevent auto links from being created inside custom links\n if (isComposerBodyCustomLink(parentNode)) {\n return;\n } else if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n\n // Prevent rich text within auto links by removing all marks of inner text nodes\n if (!isPlainText(node)) {\n const marks = Object.keys(node).filter((key) => key !== \"text\");\n\n Transforms.unsetNodes(editor, marks, { at: path });\n }\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s()]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\nconst PARENTHESES = /[()]/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n const lastCharacter = textContent[textContent.length - 1];\n\n return lastCharacter !== undefined ? isSeparator(lastCharacter) : false;\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n const firstCharacter = textContent[0];\n\n return firstCharacter !== undefined ? isSeparator(firstCharacter) : false;\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to get the \"logical length\" of a URL, taking into account things like opening/closing parentheses\n * @param url The URL to check\n * @returns The \"logical length\" of the URL\n */\nfunction getUrlLogicalLength(url: string): number {\n if (!PARENTHESES.test(url)) {\n return url.length;\n }\n\n let logicalLength = 0;\n let parenthesesCount = 0;\n\n for (const character of url) {\n if (character === \"(\") {\n parenthesesCount++;\n }\n\n if (character === \")\") {\n parenthesesCount--;\n\n if (parenthesesCount < 0) {\n break;\n }\n }\n\n logicalLength++;\n }\n\n return logicalLength;\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBefore = text[start - 1];\n const contentBeforeIsValid =\n start > 0 && contentBefore\n ? isSeparator(contentBefore)\n : isPreviousNodeValid(editor, path);\n\n const contentAfter = text[end];\n const contentAfterIsValid =\n end < text.length && contentAfter\n ? isSeparator(contentAfter)\n : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n const matchContent = match?.[0];\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || matchContent !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Allow some conditions to shorten the URL (e.g. supporting parentheses but only if they are balanced)\n const logicalLength = getUrlLogicalLength(text);\n\n if (logicalLength < text.length) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const logicalText = text.slice(0, logicalLength);\n\n // Keep the \"logical\" text and wrap it in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: logicalText,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: logicalText.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 5: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 6: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: matchContent }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n const matchContent = match?.[0];\n\n if (!match || matchContent === undefined) {\n return;\n }\n\n const start = match.index;\n const end = start + matchContent.length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: matchContent,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":["isComposerBodyCustomLink","isText","Node","Path","isPlainText","Transforms","Range","Editor","Element"],"mappings":"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"}
|