UNPKG

@liveblocks/react-ui

Version:

A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

434 lines (430 loc) 13.7 kB
'use strict'; var reactDom = require('@floating-ui/react-dom'); var core = require('@liveblocks/core'); var react$1 = require('@liveblocks/react'); var _private = require('@liveblocks/react/_private'); var react = require('react'); var constants = require('../../constants.cjs'); var autoLinks = require('../../slate/plugins/auto-links.cjs'); var customLinks = require('../../slate/plugins/custom-links.cjs'); var mentions = require('../../slate/plugins/mentions.cjs'); var isText = require('../../slate/utils/is-text.cjs'); var dataTransfer = require('../../utils/data-transfer.cjs'); var exists = require('../../utils/exists.cjs'); var useInitial = require('../../utils/use-initial.cjs'); var useLatest = require('../../utils/use-latest.cjs'); var utils = require('../Comment/utils.cjs'); var contexts = require('./contexts.cjs'); function composerBodyMentionToCommentBodyMention(mention) { return { type: "mention", id: mention.id }; } function composerBodyAutoLinkToCommentBodyLink(link) { return { type: "link", url: link.url }; } function composerBodyCustomLinkToCommentBodyLink(link) { return { type: "link", url: link.url, text: link.children.map((child) => child.text).join("") }; } function commentBodyMentionToComposerBodyMention(mention) { return { type: "mention", id: mention.id, children: [{ text: "" }] }; } function commentBodyLinkToComposerBodyLink(link) { if (link.text) { return { type: "custom-link", url: link.url, children: [{ text: link.text }] }; } else { return { type: "auto-link", url: link.url, children: [{ text: link.url }] }; } } function composerBodyToCommentBody(body) { return { version: 1, content: body.map((block) => { if (block.type !== "paragraph") { return null; } const children = block.children.map((inline2) => { if (mentions.isComposerBodyMention(inline2)) { return composerBodyMentionToCommentBodyMention(inline2); } if (autoLinks.isComposerBodyAutoLink(inline2)) { return composerBodyAutoLinkToCommentBodyLink(inline2); } if (customLinks.isComposerBodyCustomLink(inline2)) { return composerBodyCustomLinkToCommentBodyLink(inline2); } if (isText.isText(inline2)) { return inline2; } return null; }).filter(exists.exists); return { ...block, children }; }).filter(exists.exists) }; } const emptyComposerBody = []; function commentBodyToComposerBody(body) { if (!body || !body?.content) { return emptyComposerBody; } return body.content.map((block) => { if (block.type !== "paragraph") { return null; } const children = block.children.map((inline2) => { if (utils.isCommentBodyMention(inline2)) { return commentBodyMentionToComposerBodyMention(inline2); } if (utils.isCommentBodyLink(inline2)) { return commentBodyLinkToComposerBodyLink(inline2); } if (utils.isCommentBodyText(inline2)) { return inline2; } return null; }).filter(exists.exists); return { ...block, children }; }).filter(exists.exists); } function getRtlFloatingAlignment(alignment) { switch (alignment) { case "start": return "end"; case "end": return "start"; default: return "center"; } } function getSideAndAlignFromFloatingPlacement(placement) { const [side, align = "center"] = placement.split("-"); return [side, align]; } function useContentZIndex() { const [content, setContent] = react.useState(null); const contentRef = react.useCallback(setContent, [setContent]); const [contentZIndex, setContentZIndex] = react.useState(); _private.useLayoutEffect(() => { if (content) { setContentZIndex(window.getComputedStyle(content).zIndex); } }, [content]); return [contentRef, contentZIndex]; } function useFloatingWithOptions({ type = "bounds", position, alignment, dir, open }) { const floatingOptions = react.useMemo(() => { const detectOverflowOptions = { padding: constants.FLOATING_ELEMENT_COLLISION_PADDING }; const middleware = [ type === "range" ? reactDom.inline(detectOverflowOptions) : null, reactDom.flip({ ...detectOverflowOptions, crossAxis: false }), reactDom.hide(detectOverflowOptions), reactDom.shift({ ...detectOverflowOptions, limiter: reactDom.limitShift() }), type === "range" ? reactDom.offset(constants.FLOATING_ELEMENT_SIDE_OFFSET) : null, reactDom.size({ ...detectOverflowOptions, apply({ availableWidth, availableHeight, elements }) { elements.floating.style.setProperty( "--lb-composer-floating-available-width", `${availableWidth}px` ); elements.floating.style.setProperty( "--lb-composer-floating-available-height", `${availableHeight}px` ); } }) ]; return { strategy: "fixed", placement: alignment === "center" ? position : `${position}-${dir === "rtl" ? getRtlFloatingAlignment(alignment) : alignment}`, middleware, whileElementsMounted: (...args) => { return reactDom.autoUpdate(...args, { animationFrame: true }); } }; }, [alignment, position, dir, type]); return reactDom.useFloating({ ...floatingOptions, open }); } function useComposerAttachmentsDropArea({ onDragEnter, onDragLeave, onDragOver, onDrop, disabled }) { const { isDisabled: isComposerDisabled } = contexts.useComposer(); const isDisabled = isComposerDisabled || disabled; const { createAttachments } = contexts.useComposerAttachmentsContext(); const [isDraggingOver, setDraggingOver] = react.useState(false); const latestIsDraggingOver = useLatest.useLatest(isDraggingOver); const handleDragEnter = react.useCallback( (event) => { onDragEnter?.(event); if (latestIsDraggingOver.current || isDisabled || event.isDefaultPrevented()) { return; } const dataTransfer = event.dataTransfer; if (!dataTransfer.types.includes("Files")) { return; } event.preventDefault(); event.stopPropagation(); setDraggingOver(true); }, [onDragEnter, isDisabled] ); const handleDragLeave = react.useCallback( (event) => { onDragLeave?.(event); if (!latestIsDraggingOver.current || isDisabled || event.isDefaultPrevented()) { return; } if (event.relatedTarget ? event.relatedTarget === event.currentTarget || event.currentTarget.contains(event.relatedTarget) : event.currentTarget !== event.target) { return; } event.preventDefault(); event.stopPropagation(); setDraggingOver(false); }, [onDragLeave, isDisabled] ); const handleDragOver = react.useCallback( (event) => { onDragOver?.(event); if (isDisabled || event.isDefaultPrevented()) { return; } event.preventDefault(); event.stopPropagation(); }, [onDragOver, isDisabled] ); const handleDrop = react.useCallback( (event) => { onDrop?.(event); if (!latestIsDraggingOver.current || isDisabled || event.isDefaultPrevented()) { return; } event.preventDefault(); event.stopPropagation(); setDraggingOver(false); const files = dataTransfer.getFiles(event.dataTransfer); createAttachments(files); }, [onDrop, isDisabled, createAttachments] ); return [ isDraggingOver, { onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, "data-drop": isDraggingOver ? "" : void 0, "data-disabled": isDisabled ? "" : void 0 } ]; } class AttachmentTooLargeError extends Error { origin; name = "AttachmentTooLargeError"; constructor(message, origin = "client") { super(message); this.origin = origin; } } function createComposerAttachmentsManager(client, roomId, options) { const attachments = /* @__PURE__ */ new Map(); const abortControllers = /* @__PURE__ */ new Map(); const eventSource = core.makeEventSource(); let cachedSnapshot = null; function notifySubscribers() { cachedSnapshot = null; eventSource.notify(); } function uploadAttachment(attachment) { const abortController = new AbortController(); abortControllers.set(attachment.id, abortController); client[core.kInternal].httpClient.uploadAttachment({ roomId, attachment, signal: abortController.signal }).then(() => { attachments.set(attachment.id, { ...attachment, status: "uploaded" }); notifySubscribers(); }).catch((error) => { if (error instanceof Error && error.name !== "AbortError" && error.name !== "TimeoutError") { attachments.set(attachment.id, { ...attachment, status: "error", error: error instanceof core.HttpError && error.status === 413 ? new AttachmentTooLargeError("File is too large.", "server") : error }); notifySubscribers(); } }); } function addAttachments(addedAttachments) { if (addedAttachments.length === 0) { return; } const newAttachments = addedAttachments.filter( (attachment) => !attachments.has(attachment.id) ); const attachmentsToUpload = []; for (const attachment of newAttachments) { if (attachment.type === "localAttachment") { if (attachment.file.size > options.maxFileSize) { attachments.set(attachment.id, { ...attachment, status: "error", error: new AttachmentTooLargeError("File is too large.", "client") }); continue; } attachments.set(attachment.id, { ...attachment, status: "uploading" }); attachmentsToUpload.push(attachment); } else { attachments.set(attachment.id, attachment); } } if (newAttachments.length > 0) { notifySubscribers(); } for (const attachment of attachmentsToUpload) { uploadAttachment(attachment); } } function removeAttachment(attachmentId) { const abortController = abortControllers.get(attachmentId); abortController?.abort(); attachments.delete(attachmentId); abortControllers.delete(attachmentId); notifySubscribers(); } function getSnapshot() { if (!cachedSnapshot) { cachedSnapshot = Array.from(attachments.values()); } return cachedSnapshot; } function clear() { abortControllers.forEach((controller) => controller.abort()); abortControllers.clear(); attachments.clear(); notifySubscribers(); } return { addAttachments, removeAttachment, getSnapshot, subscribe: eventSource.subscribe, clear }; } function preventBeforeUnloadDefault(event) { event.preventDefault(); } function useComposerAttachmentsManager(defaultAttachments, options) { const client = react$1.useClient(); const frozenDefaultAttachments = useInitial.useInitial(defaultAttachments); const frozenAttachmentsManager = useInitial.useInitial( () => createComposerAttachmentsManager(client, options.roomId, options) ); react.useEffect(() => { frozenAttachmentsManager.addAttachments(frozenDefaultAttachments); }, [frozenDefaultAttachments, frozenAttachmentsManager]); react.useEffect(() => { return () => { frozenAttachmentsManager.clear(); }; }, [frozenAttachmentsManager]); const attachments = react.useSyncExternalStore( frozenAttachmentsManager.subscribe, frozenAttachmentsManager.getSnapshot, frozenAttachmentsManager.getSnapshot ); const isUploadingAttachments = react.useMemo(() => { return attachments.some( (attachment) => attachment.type === "localAttachment" && attachment.status === "uploading" ); }, [attachments]); react.useEffect(() => { if (!isUploadingAttachments) { return; } window.addEventListener("beforeunload", preventBeforeUnloadDefault); return () => { window.removeEventListener("beforeunload", preventBeforeUnloadDefault); }; }, [isUploadingAttachments]); return { attachments, isUploadingAttachments, addAttachments: frozenAttachmentsManager.addAttachments, removeAttachment: frozenAttachmentsManager.removeAttachment, clearAttachments: frozenAttachmentsManager.clear }; } exports.AttachmentTooLargeError = AttachmentTooLargeError; exports.commentBodyLinkToComposerBodyLink = commentBodyLinkToComposerBodyLink; exports.commentBodyMentionToComposerBodyMention = commentBodyMentionToComposerBodyMention; exports.commentBodyToComposerBody = commentBodyToComposerBody; exports.composerBodyAutoLinkToCommentBodyLink = composerBodyAutoLinkToCommentBodyLink; exports.composerBodyCustomLinkToCommentBodyLink = composerBodyCustomLinkToCommentBodyLink; exports.composerBodyMentionToCommentBodyMention = composerBodyMentionToCommentBodyMention; exports.composerBodyToCommentBody = composerBodyToCommentBody; exports.getRtlFloatingAlignment = getRtlFloatingAlignment; exports.getSideAndAlignFromFloatingPlacement = getSideAndAlignFromFloatingPlacement; exports.useComposerAttachmentsDropArea = useComposerAttachmentsDropArea; exports.useComposerAttachmentsManager = useComposerAttachmentsManager; exports.useContentZIndex = useContentZIndex; exports.useFloatingWithOptions = useFloatingWithOptions; //# sourceMappingURL=utils.cjs.map