@liveblocks/react-ui 2.8.0 → 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/Thread.js +2 -0
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/Thread.mjs +2 -0
- package/dist/components/Thread.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/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/primitives/Composer/utils.js +2 -7
- package/dist/primitives/Composer/utils.js.map +1 -1
- package/dist/primitives/Composer/utils.mjs +2 -7
- package/dist/primitives/Composer/utils.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":"utils.js","sources":["../../../src/primitives/Composer/utils.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport {\n type CommentAttachment,\n type CommentBody,\n type CommentBodyLink,\n type CommentBodyMention,\n type CommentLocalAttachment,\n type CommentMixedAttachment,\n CommentsApiError,\n makeEventSource,\n type OpaqueRoom,\n} from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport type { DragEvent } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { isComposerBodyAutoLink } from \"../../slate/plugins/auto-links\";\nimport { isComposerBodyCustomLink } from \"../../slate/plugins/custom-links\";\nimport { isComposerBodyMention } from \"../../slate/plugins/mentions\";\nimport { isText } from \"../../slate/utils/is-text\";\nimport type {\n ComposerBody,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n ComposerBodyText,\n Direction,\n} from \"../../types\";\nimport { getFiles } from \"../../utils/data-transfer\";\nimport { exists } from \"../../utils/exists\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLatest } from \"../../utils/use-latest\";\nimport {\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n} from \"../Comment/utils\";\nimport { useComposer, useComposerAttachmentsContext } from \"./contexts\";\nimport type { SuggestionsPosition } from \"./types\";\n\nexport function composerBodyMentionToCommentBodyMention(\n mention: ComposerBodyMention\n): CommentBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n };\n}\n\nexport function composerBodyAutoLinkToCommentBodyLink(\n link: ComposerBodyAutoLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n };\n}\n\nexport function composerBodyCustomLinkToCommentBodyLink(\n link: ComposerBodyCustomLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n text: link.children.map((child) => child.text).join(\"\"),\n };\n}\n\nexport function commentBodyMentionToComposerBodyMention(\n mention: CommentBodyMention\n): ComposerBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n children: [{ text: \"\" }],\n };\n}\n\nexport function commentBodyLinkToComposerBodyLink(\n link: CommentBodyLink\n): ComposerBodyAutoLink | ComposerBodyCustomLink {\n if (link.text) {\n return {\n type: \"custom-link\",\n url: link.url,\n children: [{ text: link.text }],\n };\n } else {\n return {\n type: \"auto-link\",\n url: link.url,\n children: [{ text: link.url }],\n };\n }\n}\n\nexport function composerBodyToCommentBody(body: ComposerBody): CommentBody {\n return {\n version: 1,\n content: body\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isComposerBodyMention(inline)) {\n return composerBodyMentionToCommentBodyMention(inline);\n }\n\n if (isComposerBodyAutoLink(inline)) {\n return composerBodyAutoLinkToCommentBodyLink(inline);\n }\n\n if (isComposerBodyCustomLink(inline)) {\n return composerBodyCustomLinkToCommentBodyLink(inline);\n }\n\n if (isText(inline)) {\n return inline;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists),\n };\n}\n\nconst emptyComposerBody: ComposerBody = [];\n\nexport function commentBodyToComposerBody(body: CommentBody): ComposerBody {\n if (!body || !body?.content) {\n return emptyComposerBody;\n }\n\n return body.content\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isCommentBodyMention(inline)) {\n return commentBodyMentionToComposerBodyMention(inline);\n }\n\n if (isCommentBodyLink(inline)) {\n return commentBodyLinkToComposerBodyLink(inline);\n }\n\n if (isCommentBodyText(inline)) {\n return inline as ComposerBodyText;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists);\n}\n\nexport function getPlacementFromPosition(\n position: SuggestionsPosition,\n direction: Direction = \"ltr\"\n): Placement {\n return `${position}-${direction === \"rtl\" ? \"end\" : \"start\"}`;\n}\n\nexport function getSideAndAlignFromPlacement(placement: Placement) {\n const [side, align = \"center\"] = placement.split(\"-\");\n\n return [side, align] as const;\n}\n\nexport function useComposerAttachmentsDropArea<\n T extends HTMLElement = HTMLElement,\n>({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n}: {\n onDragEnter?: (event: DragEvent<T>) => void;\n onDragLeave?: (event: DragEvent<T>) => void;\n onDragOver?: (event: DragEvent<T>) => void;\n onDrop?: (event: DragEvent<T>) => void;\n disabled?: boolean;\n}) {\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const { createAttachments } = useComposerAttachmentsContext();\n const [isDraggingOver, setDraggingOver] = useState(false);\n const latestIsDraggingOver = useLatest(isDraggingOver);\n\n const handleDragEnter = useCallback(\n (event: DragEvent<T>) => {\n onDragEnter?.(event);\n\n if (\n latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n const dataTransfer = event.dataTransfer;\n\n if (!dataTransfer.types.includes(\"Files\")) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(true);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragEnter, isDisabled]\n );\n\n const handleDragLeave = useCallback(\n (event: DragEvent<T>) => {\n onDragLeave?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n // Ignore drag leave events that are not actually leaving the drop area\n if (\n event.relatedTarget\n ? event.relatedTarget === event.currentTarget ||\n event.currentTarget.contains(event.relatedTarget as HTMLElement)\n : event.currentTarget !== event.target\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragLeave, isDisabled]\n );\n\n const handleDragOver = useCallback(\n (event: DragEvent<T>) => {\n onDragOver?.(event);\n\n if (isDisabled || event.isDefaultPrevented()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onDragOver, isDisabled]\n );\n\n const handleDrop = useCallback(\n (event: DragEvent<T>) => {\n onDrop?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n\n const files = getFiles(event.dataTransfer);\n\n createAttachments(files);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDrop, isDisabled, createAttachments]\n );\n\n return [\n isDraggingOver,\n {\n onDragEnter: handleDragEnter,\n onDragLeave: handleDragLeave,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n \"data-drop\": isDraggingOver ? \"\" : undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n } as const,\n ] as const;\n}\n\ninterface ComposerAttachmentsManagerOptions {\n maxFileSize: number;\n}\n\nexport class AttachmentTooLargeError extends Error {\n origin: \"client\" | \"server\";\n name = \"AttachmentTooLargeError\";\n\n constructor(message: string, origin: \"client\" | \"server\" = \"client\") {\n super(message);\n this.origin = origin;\n }\n}\n\nfunction createComposerAttachmentsManager(\n room: OpaqueRoom,\n options: ComposerAttachmentsManagerOptions\n) {\n const attachments: Map<string, CommentMixedAttachment> = new Map();\n const abortControllers: Map<string, AbortController> = new Map();\n const eventSource = makeEventSource<void>();\n let cachedSnapshot: CommentMixedAttachment[] | null = null;\n\n function notifySubscribers() {\n // Invalidate the cached snapshot\n cachedSnapshot = null;\n eventSource.notify();\n }\n\n function uploadAttachment(attachment: CommentLocalAttachment) {\n const abortController = new AbortController();\n abortControllers.set(attachment.id, abortController);\n\n room\n .uploadAttachment(attachment, {\n signal: abortController.signal,\n })\n .then(() => {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploaded\",\n });\n notifySubscribers();\n })\n .catch((error) => {\n if (\n error instanceof Error &&\n error.name !== \"AbortError\" &&\n error.name !== \"TimeoutError\"\n ) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error:\n error instanceof CommentsApiError && error.status === 413\n ? new AttachmentTooLargeError(\"File is too large.\", \"server\")\n : error,\n });\n notifySubscribers();\n }\n });\n }\n\n function addAttachments(addedAttachments: CommentMixedAttachment[]) {\n if (addedAttachments.length === 0) {\n return;\n }\n\n // Ignore attachments that are already in the manager\n const newAttachments = addedAttachments.filter(\n (attachment) => !attachments.has(attachment.id)\n );\n\n const attachmentsToUpload: CommentLocalAttachment[] = [];\n\n // Add all the new attachments to the manager\n for (const attachment of newAttachments) {\n if (attachment.type === \"localAttachment\") {\n // The file is too large to be uploaded\n if (attachment.file.size > options.maxFileSize) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error: new AttachmentTooLargeError(\"File is too large.\", \"client\"),\n });\n\n continue;\n }\n\n // Otherwise, mark the attachment to be uploaded\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploading\",\n });\n attachmentsToUpload.push(attachment);\n } else {\n attachments.set(attachment.id, attachment);\n }\n }\n\n // Notify subscribers about the new attachments that were added\n if (newAttachments.length > 0) {\n notifySubscribers();\n }\n\n // Upload all the new local attachments\n for (const attachment of attachmentsToUpload) {\n uploadAttachment(attachment);\n }\n }\n\n function removeAttachment(attachmentId: string) {\n const abortController = abortControllers.get(attachmentId);\n\n abortController?.abort();\n\n attachments.delete(attachmentId);\n abortControllers.delete(attachmentId);\n\n notifySubscribers();\n }\n\n function getSnapshot() {\n if (!cachedSnapshot) {\n cachedSnapshot = Array.from(attachments.values());\n }\n\n return cachedSnapshot;\n }\n\n // Clear all attachments and abort all ongoing uploads\n function clear() {\n abortControllers.forEach((controller) => controller.abort());\n abortControllers.clear();\n attachments.clear();\n\n notifySubscribers();\n }\n\n function dispose() {\n clear();\n eventSource.clear();\n }\n\n return {\n addAttachments,\n removeAttachment,\n getSnapshot,\n subscribe: eventSource.subscribe,\n clear,\n dispose,\n };\n}\n\nfunction preventBeforeUnloadDefault(event: BeforeUnloadEvent) {\n event.preventDefault();\n}\n\nexport function useComposerAttachmentsManager(\n defaultAttachments: CommentAttachment[],\n options: ComposerAttachmentsManagerOptions\n) {\n const room = useRoom();\n const frozenDefaultAttachments = useInitial(defaultAttachments);\n const frozenAttachmentsManager = useInitial(() =>\n createComposerAttachmentsManager(room, options)\n );\n\n // Initialize default attachments on mount\n useEffect(() => {\n frozenAttachmentsManager.addAttachments(frozenDefaultAttachments);\n }, [frozenDefaultAttachments, frozenAttachmentsManager]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n frozenAttachmentsManager.dispose();\n };\n }, [frozenAttachmentsManager]);\n\n const attachments = useSyncExternalStore(\n frozenAttachmentsManager.subscribe,\n frozenAttachmentsManager.getSnapshot,\n frozenAttachmentsManager.getSnapshot\n );\n\n const isUploadingAttachments = useMemo(() => {\n return attachments.some(\n (attachment) =>\n attachment.type === \"localAttachment\" &&\n attachment.status === \"uploading\"\n );\n }, [attachments]);\n\n useEffect(() => {\n if (!isUploadingAttachments) {\n return;\n }\n\n window.addEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n\n return () => {\n window.removeEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n };\n }, [isUploadingAttachments]);\n\n return {\n attachments,\n isUploadingAttachments,\n addAttachments: frozenAttachmentsManager.addAttachments,\n removeAttachment: frozenAttachmentsManager.removeAttachment,\n clearAttachments: frozenAttachmentsManager.clear,\n };\n}\n"],"names":["isComposerBodyMention","isComposerBodyAutoLink","isComposerBodyCustomLink","isText","exists","isCommentBodyMention","isCommentBodyLink","isCommentBodyText","useComposer","useComposerAttachmentsContext","useState","useLatest","useCallback","getFiles","makeEventSource","CommentsApiError","useRoom","useInitial","useEffect","useSyncExternalStore","useMemo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/primitives/Composer/utils.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport {\n type CommentAttachment,\n type CommentBody,\n type CommentBodyLink,\n type CommentBodyMention,\n type CommentLocalAttachment,\n type CommentMixedAttachment,\n CommentsApiError,\n makeEventSource,\n type OpaqueRoom,\n} from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport type { DragEvent } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { isComposerBodyAutoLink } from \"../../slate/plugins/auto-links\";\nimport { isComposerBodyCustomLink } from \"../../slate/plugins/custom-links\";\nimport { isComposerBodyMention } from \"../../slate/plugins/mentions\";\nimport { isText } from \"../../slate/utils/is-text\";\nimport type {\n ComposerBody,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n ComposerBodyText,\n Direction,\n} from \"../../types\";\nimport { getFiles } from \"../../utils/data-transfer\";\nimport { exists } from \"../../utils/exists\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLatest } from \"../../utils/use-latest\";\nimport {\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n} from \"../Comment/utils\";\nimport { useComposer, useComposerAttachmentsContext } from \"./contexts\";\nimport type { SuggestionsPosition } from \"./types\";\n\nexport function composerBodyMentionToCommentBodyMention(\n mention: ComposerBodyMention\n): CommentBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n };\n}\n\nexport function composerBodyAutoLinkToCommentBodyLink(\n link: ComposerBodyAutoLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n };\n}\n\nexport function composerBodyCustomLinkToCommentBodyLink(\n link: ComposerBodyCustomLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n text: link.children.map((child) => child.text).join(\"\"),\n };\n}\n\nexport function commentBodyMentionToComposerBodyMention(\n mention: CommentBodyMention\n): ComposerBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n children: [{ text: \"\" }],\n };\n}\n\nexport function commentBodyLinkToComposerBodyLink(\n link: CommentBodyLink\n): ComposerBodyAutoLink | ComposerBodyCustomLink {\n if (link.text) {\n return {\n type: \"custom-link\",\n url: link.url,\n children: [{ text: link.text }],\n };\n } else {\n return {\n type: \"auto-link\",\n url: link.url,\n children: [{ text: link.url }],\n };\n }\n}\n\nexport function composerBodyToCommentBody(body: ComposerBody): CommentBody {\n return {\n version: 1,\n content: body\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isComposerBodyMention(inline)) {\n return composerBodyMentionToCommentBodyMention(inline);\n }\n\n if (isComposerBodyAutoLink(inline)) {\n return composerBodyAutoLinkToCommentBodyLink(inline);\n }\n\n if (isComposerBodyCustomLink(inline)) {\n return composerBodyCustomLinkToCommentBodyLink(inline);\n }\n\n if (isText(inline)) {\n return inline;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists),\n };\n}\n\nconst emptyComposerBody: ComposerBody = [];\n\nexport function commentBodyToComposerBody(body: CommentBody): ComposerBody {\n if (!body || !body?.content) {\n return emptyComposerBody;\n }\n\n return body.content\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isCommentBodyMention(inline)) {\n return commentBodyMentionToComposerBodyMention(inline);\n }\n\n if (isCommentBodyLink(inline)) {\n return commentBodyLinkToComposerBodyLink(inline);\n }\n\n if (isCommentBodyText(inline)) {\n return inline as ComposerBodyText;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists);\n}\n\nexport function getPlacementFromPosition(\n position: SuggestionsPosition,\n direction: Direction = \"ltr\"\n): Placement {\n return `${position}-${direction === \"rtl\" ? \"end\" : \"start\"}`;\n}\n\nexport function getSideAndAlignFromPlacement(placement: Placement) {\n const [side, align = \"center\"] = placement.split(\"-\");\n\n return [side, align] as const;\n}\n\nexport function useComposerAttachmentsDropArea<\n T extends HTMLElement = HTMLElement,\n>({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n}: {\n onDragEnter?: (event: DragEvent<T>) => void;\n onDragLeave?: (event: DragEvent<T>) => void;\n onDragOver?: (event: DragEvent<T>) => void;\n onDrop?: (event: DragEvent<T>) => void;\n disabled?: boolean;\n}) {\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const { createAttachments } = useComposerAttachmentsContext();\n const [isDraggingOver, setDraggingOver] = useState(false);\n const latestIsDraggingOver = useLatest(isDraggingOver);\n\n const handleDragEnter = useCallback(\n (event: DragEvent<T>) => {\n onDragEnter?.(event);\n\n if (\n latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n const dataTransfer = event.dataTransfer;\n\n if (!dataTransfer.types.includes(\"Files\")) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(true);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragEnter, isDisabled]\n );\n\n const handleDragLeave = useCallback(\n (event: DragEvent<T>) => {\n onDragLeave?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n // Ignore drag leave events that are not actually leaving the drop area\n if (\n event.relatedTarget\n ? event.relatedTarget === event.currentTarget ||\n event.currentTarget.contains(event.relatedTarget as HTMLElement)\n : event.currentTarget !== event.target\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragLeave, isDisabled]\n );\n\n const handleDragOver = useCallback(\n (event: DragEvent<T>) => {\n onDragOver?.(event);\n\n if (isDisabled || event.isDefaultPrevented()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onDragOver, isDisabled]\n );\n\n const handleDrop = useCallback(\n (event: DragEvent<T>) => {\n onDrop?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n\n const files = getFiles(event.dataTransfer);\n\n createAttachments(files);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDrop, isDisabled, createAttachments]\n );\n\n return [\n isDraggingOver,\n {\n onDragEnter: handleDragEnter,\n onDragLeave: handleDragLeave,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n \"data-drop\": isDraggingOver ? \"\" : undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n } as const,\n ] as const;\n}\n\ninterface ComposerAttachmentsManagerOptions {\n maxFileSize: number;\n}\n\nexport class AttachmentTooLargeError extends Error {\n origin: \"client\" | \"server\";\n name = \"AttachmentTooLargeError\";\n\n constructor(message: string, origin: \"client\" | \"server\" = \"client\") {\n super(message);\n this.origin = origin;\n }\n}\n\nfunction createComposerAttachmentsManager(\n room: OpaqueRoom,\n options: ComposerAttachmentsManagerOptions\n) {\n const attachments: Map<string, CommentMixedAttachment> = new Map();\n const abortControllers: Map<string, AbortController> = new Map();\n const eventSource = makeEventSource<void>();\n let cachedSnapshot: CommentMixedAttachment[] | null = null;\n\n function notifySubscribers() {\n // Invalidate the cached snapshot\n cachedSnapshot = null;\n eventSource.notify();\n }\n\n function uploadAttachment(attachment: CommentLocalAttachment) {\n const abortController = new AbortController();\n abortControllers.set(attachment.id, abortController);\n\n room\n .uploadAttachment(attachment, {\n signal: abortController.signal,\n })\n .then(() => {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploaded\",\n });\n notifySubscribers();\n })\n .catch((error) => {\n if (\n error instanceof Error &&\n error.name !== \"AbortError\" &&\n error.name !== \"TimeoutError\"\n ) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error:\n error instanceof CommentsApiError && error.status === 413\n ? new AttachmentTooLargeError(\"File is too large.\", \"server\")\n : error,\n });\n notifySubscribers();\n }\n });\n }\n\n function addAttachments(addedAttachments: CommentMixedAttachment[]) {\n if (addedAttachments.length === 0) {\n return;\n }\n\n // Ignore attachments that are already in the manager\n const newAttachments = addedAttachments.filter(\n (attachment) => !attachments.has(attachment.id)\n );\n\n const attachmentsToUpload: CommentLocalAttachment[] = [];\n\n // Add all the new attachments to the manager\n for (const attachment of newAttachments) {\n if (attachment.type === \"localAttachment\") {\n // The file is too large to be uploaded\n if (attachment.file.size > options.maxFileSize) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error: new AttachmentTooLargeError(\"File is too large.\", \"client\"),\n });\n\n continue;\n }\n\n // Otherwise, mark the attachment to be uploaded\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploading\",\n });\n attachmentsToUpload.push(attachment);\n } else {\n attachments.set(attachment.id, attachment);\n }\n }\n\n // Notify subscribers about the new attachments that were added\n if (newAttachments.length > 0) {\n notifySubscribers();\n }\n\n // Upload all the new local attachments\n for (const attachment of attachmentsToUpload) {\n uploadAttachment(attachment);\n }\n }\n\n function removeAttachment(attachmentId: string) {\n const abortController = abortControllers.get(attachmentId);\n\n abortController?.abort();\n\n attachments.delete(attachmentId);\n abortControllers.delete(attachmentId);\n\n notifySubscribers();\n }\n\n function getSnapshot() {\n if (!cachedSnapshot) {\n cachedSnapshot = Array.from(attachments.values());\n }\n\n return cachedSnapshot;\n }\n\n // Clear all attachments and abort all ongoing uploads\n function clear() {\n abortControllers.forEach((controller) => controller.abort());\n abortControllers.clear();\n attachments.clear();\n\n notifySubscribers();\n }\n\n return {\n addAttachments,\n removeAttachment,\n getSnapshot,\n subscribe: eventSource.subscribe,\n clear,\n };\n}\n\nfunction preventBeforeUnloadDefault(event: BeforeUnloadEvent) {\n event.preventDefault();\n}\n\nexport function useComposerAttachmentsManager(\n defaultAttachments: CommentAttachment[],\n options: ComposerAttachmentsManagerOptions\n) {\n const room = useRoom();\n const frozenDefaultAttachments = useInitial(defaultAttachments);\n const frozenAttachmentsManager = useInitial(() =>\n createComposerAttachmentsManager(room, options)\n );\n\n // Initialize default attachments on mount\n useEffect(() => {\n frozenAttachmentsManager.addAttachments(frozenDefaultAttachments);\n }, [frozenDefaultAttachments, frozenAttachmentsManager]);\n\n // Clear on unmount\n useEffect(() => {\n return () => {\n frozenAttachmentsManager.clear();\n };\n }, [frozenAttachmentsManager]);\n\n const attachments = useSyncExternalStore(\n frozenAttachmentsManager.subscribe,\n frozenAttachmentsManager.getSnapshot,\n frozenAttachmentsManager.getSnapshot\n );\n\n const isUploadingAttachments = useMemo(() => {\n return attachments.some(\n (attachment) =>\n attachment.type === \"localAttachment\" &&\n attachment.status === \"uploading\"\n );\n }, [attachments]);\n\n useEffect(() => {\n if (!isUploadingAttachments) {\n return;\n }\n\n window.addEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n\n return () => {\n window.removeEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n };\n }, [isUploadingAttachments]);\n\n return {\n attachments,\n isUploadingAttachments,\n addAttachments: frozenAttachmentsManager.addAttachments,\n removeAttachment: frozenAttachmentsManager.removeAttachment,\n clearAttachments: frozenAttachmentsManager.clear,\n };\n}\n"],"names":["isComposerBodyMention","isComposerBodyAutoLink","isComposerBodyCustomLink","isText","exists","isCommentBodyMention","isCommentBodyLink","isCommentBodyText","useComposer","useComposerAttachmentsContext","useState","useLatest","useCallback","getFiles","makeEventSource","CommentsApiError","useRoom","useInitial","useEffect","useSyncExternalStore","useMemo"],"mappings":"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"}
|
|
@@ -288,17 +288,12 @@ function createComposerAttachmentsManager(room, options) {
|
|
|
288
288
|
attachments.clear();
|
|
289
289
|
notifySubscribers();
|
|
290
290
|
}
|
|
291
|
-
function dispose() {
|
|
292
|
-
clear();
|
|
293
|
-
eventSource.clear();
|
|
294
|
-
}
|
|
295
291
|
return {
|
|
296
292
|
addAttachments,
|
|
297
293
|
removeAttachment,
|
|
298
294
|
getSnapshot,
|
|
299
295
|
subscribe: eventSource.subscribe,
|
|
300
|
-
clear
|
|
301
|
-
dispose
|
|
296
|
+
clear
|
|
302
297
|
};
|
|
303
298
|
}
|
|
304
299
|
function preventBeforeUnloadDefault(event) {
|
|
@@ -315,7 +310,7 @@ function useComposerAttachmentsManager(defaultAttachments, options) {
|
|
|
315
310
|
}, [frozenDefaultAttachments, frozenAttachmentsManager]);
|
|
316
311
|
useEffect(() => {
|
|
317
312
|
return () => {
|
|
318
|
-
frozenAttachmentsManager.
|
|
313
|
+
frozenAttachmentsManager.clear();
|
|
319
314
|
};
|
|
320
315
|
}, [frozenAttachmentsManager]);
|
|
321
316
|
const attachments = useSyncExternalStore(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sources":["../../../src/primitives/Composer/utils.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport {\n type CommentAttachment,\n type CommentBody,\n type CommentBodyLink,\n type CommentBodyMention,\n type CommentLocalAttachment,\n type CommentMixedAttachment,\n CommentsApiError,\n makeEventSource,\n type OpaqueRoom,\n} from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport type { DragEvent } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { isComposerBodyAutoLink } from \"../../slate/plugins/auto-links\";\nimport { isComposerBodyCustomLink } from \"../../slate/plugins/custom-links\";\nimport { isComposerBodyMention } from \"../../slate/plugins/mentions\";\nimport { isText } from \"../../slate/utils/is-text\";\nimport type {\n ComposerBody,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n ComposerBodyText,\n Direction,\n} from \"../../types\";\nimport { getFiles } from \"../../utils/data-transfer\";\nimport { exists } from \"../../utils/exists\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLatest } from \"../../utils/use-latest\";\nimport {\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n} from \"../Comment/utils\";\nimport { useComposer, useComposerAttachmentsContext } from \"./contexts\";\nimport type { SuggestionsPosition } from \"./types\";\n\nexport function composerBodyMentionToCommentBodyMention(\n mention: ComposerBodyMention\n): CommentBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n };\n}\n\nexport function composerBodyAutoLinkToCommentBodyLink(\n link: ComposerBodyAutoLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n };\n}\n\nexport function composerBodyCustomLinkToCommentBodyLink(\n link: ComposerBodyCustomLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n text: link.children.map((child) => child.text).join(\"\"),\n };\n}\n\nexport function commentBodyMentionToComposerBodyMention(\n mention: CommentBodyMention\n): ComposerBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n children: [{ text: \"\" }],\n };\n}\n\nexport function commentBodyLinkToComposerBodyLink(\n link: CommentBodyLink\n): ComposerBodyAutoLink | ComposerBodyCustomLink {\n if (link.text) {\n return {\n type: \"custom-link\",\n url: link.url,\n children: [{ text: link.text }],\n };\n } else {\n return {\n type: \"auto-link\",\n url: link.url,\n children: [{ text: link.url }],\n };\n }\n}\n\nexport function composerBodyToCommentBody(body: ComposerBody): CommentBody {\n return {\n version: 1,\n content: body\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isComposerBodyMention(inline)) {\n return composerBodyMentionToCommentBodyMention(inline);\n }\n\n if (isComposerBodyAutoLink(inline)) {\n return composerBodyAutoLinkToCommentBodyLink(inline);\n }\n\n if (isComposerBodyCustomLink(inline)) {\n return composerBodyCustomLinkToCommentBodyLink(inline);\n }\n\n if (isText(inline)) {\n return inline;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists),\n };\n}\n\nconst emptyComposerBody: ComposerBody = [];\n\nexport function commentBodyToComposerBody(body: CommentBody): ComposerBody {\n if (!body || !body?.content) {\n return emptyComposerBody;\n }\n\n return body.content\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isCommentBodyMention(inline)) {\n return commentBodyMentionToComposerBodyMention(inline);\n }\n\n if (isCommentBodyLink(inline)) {\n return commentBodyLinkToComposerBodyLink(inline);\n }\n\n if (isCommentBodyText(inline)) {\n return inline as ComposerBodyText;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists);\n}\n\nexport function getPlacementFromPosition(\n position: SuggestionsPosition,\n direction: Direction = \"ltr\"\n): Placement {\n return `${position}-${direction === \"rtl\" ? \"end\" : \"start\"}`;\n}\n\nexport function getSideAndAlignFromPlacement(placement: Placement) {\n const [side, align = \"center\"] = placement.split(\"-\");\n\n return [side, align] as const;\n}\n\nexport function useComposerAttachmentsDropArea<\n T extends HTMLElement = HTMLElement,\n>({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n}: {\n onDragEnter?: (event: DragEvent<T>) => void;\n onDragLeave?: (event: DragEvent<T>) => void;\n onDragOver?: (event: DragEvent<T>) => void;\n onDrop?: (event: DragEvent<T>) => void;\n disabled?: boolean;\n}) {\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const { createAttachments } = useComposerAttachmentsContext();\n const [isDraggingOver, setDraggingOver] = useState(false);\n const latestIsDraggingOver = useLatest(isDraggingOver);\n\n const handleDragEnter = useCallback(\n (event: DragEvent<T>) => {\n onDragEnter?.(event);\n\n if (\n latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n const dataTransfer = event.dataTransfer;\n\n if (!dataTransfer.types.includes(\"Files\")) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(true);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragEnter, isDisabled]\n );\n\n const handleDragLeave = useCallback(\n (event: DragEvent<T>) => {\n onDragLeave?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n // Ignore drag leave events that are not actually leaving the drop area\n if (\n event.relatedTarget\n ? event.relatedTarget === event.currentTarget ||\n event.currentTarget.contains(event.relatedTarget as HTMLElement)\n : event.currentTarget !== event.target\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragLeave, isDisabled]\n );\n\n const handleDragOver = useCallback(\n (event: DragEvent<T>) => {\n onDragOver?.(event);\n\n if (isDisabled || event.isDefaultPrevented()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onDragOver, isDisabled]\n );\n\n const handleDrop = useCallback(\n (event: DragEvent<T>) => {\n onDrop?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n\n const files = getFiles(event.dataTransfer);\n\n createAttachments(files);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDrop, isDisabled, createAttachments]\n );\n\n return [\n isDraggingOver,\n {\n onDragEnter: handleDragEnter,\n onDragLeave: handleDragLeave,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n \"data-drop\": isDraggingOver ? \"\" : undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n } as const,\n ] as const;\n}\n\ninterface ComposerAttachmentsManagerOptions {\n maxFileSize: number;\n}\n\nexport class AttachmentTooLargeError extends Error {\n origin: \"client\" | \"server\";\n name = \"AttachmentTooLargeError\";\n\n constructor(message: string, origin: \"client\" | \"server\" = \"client\") {\n super(message);\n this.origin = origin;\n }\n}\n\nfunction createComposerAttachmentsManager(\n room: OpaqueRoom,\n options: ComposerAttachmentsManagerOptions\n) {\n const attachments: Map<string, CommentMixedAttachment> = new Map();\n const abortControllers: Map<string, AbortController> = new Map();\n const eventSource = makeEventSource<void>();\n let cachedSnapshot: CommentMixedAttachment[] | null = null;\n\n function notifySubscribers() {\n // Invalidate the cached snapshot\n cachedSnapshot = null;\n eventSource.notify();\n }\n\n function uploadAttachment(attachment: CommentLocalAttachment) {\n const abortController = new AbortController();\n abortControllers.set(attachment.id, abortController);\n\n room\n .uploadAttachment(attachment, {\n signal: abortController.signal,\n })\n .then(() => {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploaded\",\n });\n notifySubscribers();\n })\n .catch((error) => {\n if (\n error instanceof Error &&\n error.name !== \"AbortError\" &&\n error.name !== \"TimeoutError\"\n ) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error:\n error instanceof CommentsApiError && error.status === 413\n ? new AttachmentTooLargeError(\"File is too large.\", \"server\")\n : error,\n });\n notifySubscribers();\n }\n });\n }\n\n function addAttachments(addedAttachments: CommentMixedAttachment[]) {\n if (addedAttachments.length === 0) {\n return;\n }\n\n // Ignore attachments that are already in the manager\n const newAttachments = addedAttachments.filter(\n (attachment) => !attachments.has(attachment.id)\n );\n\n const attachmentsToUpload: CommentLocalAttachment[] = [];\n\n // Add all the new attachments to the manager\n for (const attachment of newAttachments) {\n if (attachment.type === \"localAttachment\") {\n // The file is too large to be uploaded\n if (attachment.file.size > options.maxFileSize) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error: new AttachmentTooLargeError(\"File is too large.\", \"client\"),\n });\n\n continue;\n }\n\n // Otherwise, mark the attachment to be uploaded\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploading\",\n });\n attachmentsToUpload.push(attachment);\n } else {\n attachments.set(attachment.id, attachment);\n }\n }\n\n // Notify subscribers about the new attachments that were added\n if (newAttachments.length > 0) {\n notifySubscribers();\n }\n\n // Upload all the new local attachments\n for (const attachment of attachmentsToUpload) {\n uploadAttachment(attachment);\n }\n }\n\n function removeAttachment(attachmentId: string) {\n const abortController = abortControllers.get(attachmentId);\n\n abortController?.abort();\n\n attachments.delete(attachmentId);\n abortControllers.delete(attachmentId);\n\n notifySubscribers();\n }\n\n function getSnapshot() {\n if (!cachedSnapshot) {\n cachedSnapshot = Array.from(attachments.values());\n }\n\n return cachedSnapshot;\n }\n\n // Clear all attachments and abort all ongoing uploads\n function clear() {\n abortControllers.forEach((controller) => controller.abort());\n abortControllers.clear();\n attachments.clear();\n\n notifySubscribers();\n }\n\n function dispose() {\n clear();\n eventSource.clear();\n }\n\n return {\n addAttachments,\n removeAttachment,\n getSnapshot,\n subscribe: eventSource.subscribe,\n clear,\n dispose,\n };\n}\n\nfunction preventBeforeUnloadDefault(event: BeforeUnloadEvent) {\n event.preventDefault();\n}\n\nexport function useComposerAttachmentsManager(\n defaultAttachments: CommentAttachment[],\n options: ComposerAttachmentsManagerOptions\n) {\n const room = useRoom();\n const frozenDefaultAttachments = useInitial(defaultAttachments);\n const frozenAttachmentsManager = useInitial(() =>\n createComposerAttachmentsManager(room, options)\n );\n\n // Initialize default attachments on mount\n useEffect(() => {\n frozenAttachmentsManager.addAttachments(frozenDefaultAttachments);\n }, [frozenDefaultAttachments, frozenAttachmentsManager]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n frozenAttachmentsManager.dispose();\n };\n }, [frozenAttachmentsManager]);\n\n const attachments = useSyncExternalStore(\n frozenAttachmentsManager.subscribe,\n frozenAttachmentsManager.getSnapshot,\n frozenAttachmentsManager.getSnapshot\n );\n\n const isUploadingAttachments = useMemo(() => {\n return attachments.some(\n (attachment) =>\n attachment.type === \"localAttachment\" &&\n attachment.status === \"uploading\"\n );\n }, [attachments]);\n\n useEffect(() => {\n if (!isUploadingAttachments) {\n return;\n }\n\n window.addEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n\n return () => {\n window.removeEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n };\n }, [isUploadingAttachments]);\n\n return {\n attachments,\n isUploadingAttachments,\n addAttachments: frozenAttachmentsManager.addAttachments,\n removeAttachment: frozenAttachmentsManager.removeAttachment,\n clearAttachments: frozenAttachmentsManager.clear,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","sources":["../../../src/primitives/Composer/utils.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport {\n type CommentAttachment,\n type CommentBody,\n type CommentBodyLink,\n type CommentBodyMention,\n type CommentLocalAttachment,\n type CommentMixedAttachment,\n CommentsApiError,\n makeEventSource,\n type OpaqueRoom,\n} from \"@liveblocks/core\";\nimport { useRoom } from \"@liveblocks/react\";\nimport type { DragEvent } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nimport { isComposerBodyAutoLink } from \"../../slate/plugins/auto-links\";\nimport { isComposerBodyCustomLink } from \"../../slate/plugins/custom-links\";\nimport { isComposerBodyMention } from \"../../slate/plugins/mentions\";\nimport { isText } from \"../../slate/utils/is-text\";\nimport type {\n ComposerBody,\n ComposerBodyAutoLink,\n ComposerBodyCustomLink,\n ComposerBodyMention,\n ComposerBodyText,\n Direction,\n} from \"../../types\";\nimport { getFiles } from \"../../utils/data-transfer\";\nimport { exists } from \"../../utils/exists\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLatest } from \"../../utils/use-latest\";\nimport {\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n} from \"../Comment/utils\";\nimport { useComposer, useComposerAttachmentsContext } from \"./contexts\";\nimport type { SuggestionsPosition } from \"./types\";\n\nexport function composerBodyMentionToCommentBodyMention(\n mention: ComposerBodyMention\n): CommentBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n };\n}\n\nexport function composerBodyAutoLinkToCommentBodyLink(\n link: ComposerBodyAutoLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n };\n}\n\nexport function composerBodyCustomLinkToCommentBodyLink(\n link: ComposerBodyCustomLink\n): CommentBodyLink {\n return {\n type: \"link\",\n url: link.url,\n text: link.children.map((child) => child.text).join(\"\"),\n };\n}\n\nexport function commentBodyMentionToComposerBodyMention(\n mention: CommentBodyMention\n): ComposerBodyMention {\n return {\n type: \"mention\",\n id: mention.id,\n children: [{ text: \"\" }],\n };\n}\n\nexport function commentBodyLinkToComposerBodyLink(\n link: CommentBodyLink\n): ComposerBodyAutoLink | ComposerBodyCustomLink {\n if (link.text) {\n return {\n type: \"custom-link\",\n url: link.url,\n children: [{ text: link.text }],\n };\n } else {\n return {\n type: \"auto-link\",\n url: link.url,\n children: [{ text: link.url }],\n };\n }\n}\n\nexport function composerBodyToCommentBody(body: ComposerBody): CommentBody {\n return {\n version: 1,\n content: body\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isComposerBodyMention(inline)) {\n return composerBodyMentionToCommentBodyMention(inline);\n }\n\n if (isComposerBodyAutoLink(inline)) {\n return composerBodyAutoLinkToCommentBodyLink(inline);\n }\n\n if (isComposerBodyCustomLink(inline)) {\n return composerBodyCustomLinkToCommentBodyLink(inline);\n }\n\n if (isText(inline)) {\n return inline;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists),\n };\n}\n\nconst emptyComposerBody: ComposerBody = [];\n\nexport function commentBodyToComposerBody(body: CommentBody): ComposerBody {\n if (!body || !body?.content) {\n return emptyComposerBody;\n }\n\n return body.content\n .map((block) => {\n // All root blocks are paragraphs at the moment\n if (block.type !== \"paragraph\") {\n return null;\n }\n\n const children = block.children\n .map((inline) => {\n if (isCommentBodyMention(inline)) {\n return commentBodyMentionToComposerBodyMention(inline);\n }\n\n if (isCommentBodyLink(inline)) {\n return commentBodyLinkToComposerBodyLink(inline);\n }\n\n if (isCommentBodyText(inline)) {\n return inline as ComposerBodyText;\n }\n\n return null;\n })\n .filter(exists);\n\n return {\n ...block,\n children,\n };\n })\n .filter(exists);\n}\n\nexport function getPlacementFromPosition(\n position: SuggestionsPosition,\n direction: Direction = \"ltr\"\n): Placement {\n return `${position}-${direction === \"rtl\" ? \"end\" : \"start\"}`;\n}\n\nexport function getSideAndAlignFromPlacement(placement: Placement) {\n const [side, align = \"center\"] = placement.split(\"-\");\n\n return [side, align] as const;\n}\n\nexport function useComposerAttachmentsDropArea<\n T extends HTMLElement = HTMLElement,\n>({\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n disabled,\n}: {\n onDragEnter?: (event: DragEvent<T>) => void;\n onDragLeave?: (event: DragEvent<T>) => void;\n onDragOver?: (event: DragEvent<T>) => void;\n onDrop?: (event: DragEvent<T>) => void;\n disabled?: boolean;\n}) {\n const { isDisabled: isComposerDisabled } = useComposer();\n const isDisabled = isComposerDisabled || disabled;\n const { createAttachments } = useComposerAttachmentsContext();\n const [isDraggingOver, setDraggingOver] = useState(false);\n const latestIsDraggingOver = useLatest(isDraggingOver);\n\n const handleDragEnter = useCallback(\n (event: DragEvent<T>) => {\n onDragEnter?.(event);\n\n if (\n latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n const dataTransfer = event.dataTransfer;\n\n if (!dataTransfer.types.includes(\"Files\")) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(true);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragEnter, isDisabled]\n );\n\n const handleDragLeave = useCallback(\n (event: DragEvent<T>) => {\n onDragLeave?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n // Ignore drag leave events that are not actually leaving the drop area\n if (\n event.relatedTarget\n ? event.relatedTarget === event.currentTarget ||\n event.currentTarget.contains(event.relatedTarget as HTMLElement)\n : event.currentTarget !== event.target\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDragLeave, isDisabled]\n );\n\n const handleDragOver = useCallback(\n (event: DragEvent<T>) => {\n onDragOver?.(event);\n\n if (isDisabled || event.isDefaultPrevented()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n },\n [onDragOver, isDisabled]\n );\n\n const handleDrop = useCallback(\n (event: DragEvent<T>) => {\n onDrop?.(event);\n\n if (\n !latestIsDraggingOver.current ||\n isDisabled ||\n event.isDefaultPrevented()\n ) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n setDraggingOver(false);\n\n const files = getFiles(event.dataTransfer);\n\n createAttachments(files);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [onDrop, isDisabled, createAttachments]\n );\n\n return [\n isDraggingOver,\n {\n onDragEnter: handleDragEnter,\n onDragLeave: handleDragLeave,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n \"data-drop\": isDraggingOver ? \"\" : undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n } as const,\n ] as const;\n}\n\ninterface ComposerAttachmentsManagerOptions {\n maxFileSize: number;\n}\n\nexport class AttachmentTooLargeError extends Error {\n origin: \"client\" | \"server\";\n name = \"AttachmentTooLargeError\";\n\n constructor(message: string, origin: \"client\" | \"server\" = \"client\") {\n super(message);\n this.origin = origin;\n }\n}\n\nfunction createComposerAttachmentsManager(\n room: OpaqueRoom,\n options: ComposerAttachmentsManagerOptions\n) {\n const attachments: Map<string, CommentMixedAttachment> = new Map();\n const abortControllers: Map<string, AbortController> = new Map();\n const eventSource = makeEventSource<void>();\n let cachedSnapshot: CommentMixedAttachment[] | null = null;\n\n function notifySubscribers() {\n // Invalidate the cached snapshot\n cachedSnapshot = null;\n eventSource.notify();\n }\n\n function uploadAttachment(attachment: CommentLocalAttachment) {\n const abortController = new AbortController();\n abortControllers.set(attachment.id, abortController);\n\n room\n .uploadAttachment(attachment, {\n signal: abortController.signal,\n })\n .then(() => {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploaded\",\n });\n notifySubscribers();\n })\n .catch((error) => {\n if (\n error instanceof Error &&\n error.name !== \"AbortError\" &&\n error.name !== \"TimeoutError\"\n ) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error:\n error instanceof CommentsApiError && error.status === 413\n ? new AttachmentTooLargeError(\"File is too large.\", \"server\")\n : error,\n });\n notifySubscribers();\n }\n });\n }\n\n function addAttachments(addedAttachments: CommentMixedAttachment[]) {\n if (addedAttachments.length === 0) {\n return;\n }\n\n // Ignore attachments that are already in the manager\n const newAttachments = addedAttachments.filter(\n (attachment) => !attachments.has(attachment.id)\n );\n\n const attachmentsToUpload: CommentLocalAttachment[] = [];\n\n // Add all the new attachments to the manager\n for (const attachment of newAttachments) {\n if (attachment.type === \"localAttachment\") {\n // The file is too large to be uploaded\n if (attachment.file.size > options.maxFileSize) {\n attachments.set(attachment.id, {\n ...attachment,\n status: \"error\",\n error: new AttachmentTooLargeError(\"File is too large.\", \"client\"),\n });\n\n continue;\n }\n\n // Otherwise, mark the attachment to be uploaded\n attachments.set(attachment.id, {\n ...attachment,\n status: \"uploading\",\n });\n attachmentsToUpload.push(attachment);\n } else {\n attachments.set(attachment.id, attachment);\n }\n }\n\n // Notify subscribers about the new attachments that were added\n if (newAttachments.length > 0) {\n notifySubscribers();\n }\n\n // Upload all the new local attachments\n for (const attachment of attachmentsToUpload) {\n uploadAttachment(attachment);\n }\n }\n\n function removeAttachment(attachmentId: string) {\n const abortController = abortControllers.get(attachmentId);\n\n abortController?.abort();\n\n attachments.delete(attachmentId);\n abortControllers.delete(attachmentId);\n\n notifySubscribers();\n }\n\n function getSnapshot() {\n if (!cachedSnapshot) {\n cachedSnapshot = Array.from(attachments.values());\n }\n\n return cachedSnapshot;\n }\n\n // Clear all attachments and abort all ongoing uploads\n function clear() {\n abortControllers.forEach((controller) => controller.abort());\n abortControllers.clear();\n attachments.clear();\n\n notifySubscribers();\n }\n\n return {\n addAttachments,\n removeAttachment,\n getSnapshot,\n subscribe: eventSource.subscribe,\n clear,\n };\n}\n\nfunction preventBeforeUnloadDefault(event: BeforeUnloadEvent) {\n event.preventDefault();\n}\n\nexport function useComposerAttachmentsManager(\n defaultAttachments: CommentAttachment[],\n options: ComposerAttachmentsManagerOptions\n) {\n const room = useRoom();\n const frozenDefaultAttachments = useInitial(defaultAttachments);\n const frozenAttachmentsManager = useInitial(() =>\n createComposerAttachmentsManager(room, options)\n );\n\n // Initialize default attachments on mount\n useEffect(() => {\n frozenAttachmentsManager.addAttachments(frozenDefaultAttachments);\n }, [frozenDefaultAttachments, frozenAttachmentsManager]);\n\n // Clear on unmount\n useEffect(() => {\n return () => {\n frozenAttachmentsManager.clear();\n };\n }, [frozenAttachmentsManager]);\n\n const attachments = useSyncExternalStore(\n frozenAttachmentsManager.subscribe,\n frozenAttachmentsManager.getSnapshot,\n frozenAttachmentsManager.getSnapshot\n );\n\n const isUploadingAttachments = useMemo(() => {\n return attachments.some(\n (attachment) =>\n attachment.type === \"localAttachment\" &&\n attachment.status === \"uploading\"\n );\n }, [attachments]);\n\n useEffect(() => {\n if (!isUploadingAttachments) {\n return;\n }\n\n window.addEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n\n return () => {\n window.removeEventListener(\"beforeunload\", preventBeforeUnloadDefault);\n };\n }, [isUploadingAttachments]);\n\n return {\n attachments,\n isUploadingAttachments,\n addAttachments: frozenAttachmentsManager.addAttachments,\n removeAttachment: frozenAttachmentsManager.removeAttachment,\n clearAttachments: frozenAttachmentsManager.clear,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -80,6 +80,9 @@ function EmojiPickerRoot({
|
|
|
80
80
|
}
|
|
81
81
|
case "right": {
|
|
82
82
|
const currentRow = data.rows[selectedRowIndex];
|
|
83
|
+
if (!currentRow) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
83
86
|
if (selectedColumnIndex === currentRow.length - 1) {
|
|
84
87
|
const nextRowIndex = selectedRowIndex + 1;
|
|
85
88
|
const nextRow = data.rows[nextRowIndex];
|
|
@@ -432,20 +435,28 @@ const EmojiPickerContent = React.forwardRef(
|
|
|
432
435
|
},
|
|
433
436
|
groupCounts: data.categoriesRowCounts,
|
|
434
437
|
groupContent: (groupIndex) => {
|
|
438
|
+
const category = data.categories[groupIndex];
|
|
439
|
+
if (!category) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
435
442
|
return /* @__PURE__ */ React.createElement(CategoryHeader, {
|
|
436
|
-
category
|
|
443
|
+
category
|
|
437
444
|
});
|
|
438
445
|
},
|
|
439
446
|
itemContent: (rowIndex, groupIndex) => {
|
|
440
|
-
const
|
|
447
|
+
const categoryRow = data.rows[rowIndex];
|
|
448
|
+
const categoryRowIndex = data.categoriesRowIndices[groupIndex]?.indexOf(rowIndex);
|
|
441
449
|
const categoryRowsCount = data.categoriesRowCounts[groupIndex];
|
|
450
|
+
if (categoryRow === void 0 || categoryRowIndex === void 0 || categoryRowsCount === void 0) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
442
453
|
return /* @__PURE__ */ React.createElement(Row, {
|
|
443
454
|
attributes: {
|
|
444
455
|
rowIndex,
|
|
445
456
|
categoryRowIndex,
|
|
446
457
|
categoryRowsCount
|
|
447
458
|
}
|
|
448
|
-
},
|
|
459
|
+
}, categoryRow.map((emoji, columnIndex) => {
|
|
449
460
|
const isSelected = interaction !== "none" && selectedColumnIndex === columnIndex && selectedRowIndex === rowIndex;
|
|
450
461
|
return /* @__PURE__ */ React.createElement(Emoji, {
|
|
451
462
|
key: emoji.emoji,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","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.js","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;;;;"}
|
|
@@ -78,6 +78,9 @@ function EmojiPickerRoot({
|
|
|
78
78
|
}
|
|
79
79
|
case "right": {
|
|
80
80
|
const currentRow = data.rows[selectedRowIndex];
|
|
81
|
+
if (!currentRow) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
81
84
|
if (selectedColumnIndex === currentRow.length - 1) {
|
|
82
85
|
const nextRowIndex = selectedRowIndex + 1;
|
|
83
86
|
const nextRow = data.rows[nextRowIndex];
|
|
@@ -430,20 +433,28 @@ const EmojiPickerContent = forwardRef(
|
|
|
430
433
|
},
|
|
431
434
|
groupCounts: data.categoriesRowCounts,
|
|
432
435
|
groupContent: (groupIndex) => {
|
|
436
|
+
const category = data.categories[groupIndex];
|
|
437
|
+
if (!category) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
433
440
|
return /* @__PURE__ */ React__default.createElement(CategoryHeader, {
|
|
434
|
-
category
|
|
441
|
+
category
|
|
435
442
|
});
|
|
436
443
|
},
|
|
437
444
|
itemContent: (rowIndex, groupIndex) => {
|
|
438
|
-
const
|
|
445
|
+
const categoryRow = data.rows[rowIndex];
|
|
446
|
+
const categoryRowIndex = data.categoriesRowIndices[groupIndex]?.indexOf(rowIndex);
|
|
439
447
|
const categoryRowsCount = data.categoriesRowCounts[groupIndex];
|
|
448
|
+
if (categoryRow === void 0 || categoryRowIndex === void 0 || categoryRowsCount === void 0) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
440
451
|
return /* @__PURE__ */ React__default.createElement(Row, {
|
|
441
452
|
attributes: {
|
|
442
453
|
rowIndex,
|
|
443
454
|
categoryRowIndex,
|
|
444
455
|
categoryRowsCount
|
|
445
456
|
}
|
|
446
|
-
},
|
|
457
|
+
}, categoryRow.map((emoji, columnIndex) => {
|
|
447
458
|
const isSelected = interaction !== "none" && selectedColumnIndex === columnIndex && selectedRowIndex === rowIndex;
|
|
448
459
|
return /* @__PURE__ */ React__default.createElement(Emoji, {
|
|
449
460
|
key: emoji.emoji,
|
|
@@ -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;;"}
|