@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
JavaScript
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
;