tldraw
Version:
A tiny little drawing editor.
172 lines (171 loc) • 7.21 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var DefaultRichTextToolbar_exports = {};
__export(DefaultRichTextToolbar_exports, {
DefaultRichTextToolbar: () => DefaultRichTextToolbar
});
module.exports = __toCommonJS(DefaultRichTextToolbar_exports);
var import_jsx_runtime = require("react/jsx-runtime");
var import_core = require("@tiptap/core");
var import_editor = require("@tldraw/editor");
var import_react = require("react");
var import_useTranslation = require("../../hooks/useTranslation/useTranslation");
var import_TldrawUiContextualToolbar = require("../primitives/TldrawUiContextualToolbar");
var import_DefaultRichTextToolbarContent = require("./DefaultRichTextToolbarContent");
var import_LinkEditor = require("./LinkEditor");
const DefaultRichTextToolbar = (0, import_editor.track)(function DefaultRichTextToolbar2({
children
}) {
const editor = (0, import_editor.useEditor)();
const textEditor = (0, import_editor.useValue)("textEditor", () => editor.getRichTextEditor(), [editor]);
if (editor.getInstanceState().isCoarsePointer || !textEditor) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextualToolbarInner, { textEditor, children });
});
function ContextualToolbarInner({
textEditor,
children
}) {
const { isEditingLink, onEditLinkStart, onEditLinkClose } = useEditingLinkBehavior(textEditor);
const [currentSelection, setCurrentSelection] = (0, import_react.useState)(null);
const previousSelectionBounds = (0, import_react.useRef)(void 0);
const isMousingDown = useIsMousingDownOnTextEditor(textEditor);
const msg = (0, import_useTranslation.useTranslation)();
const getSelectionBounds = (0, import_react.useCallback)(() => {
if (isEditingLink) {
return previousSelectionBounds.current;
}
const selection = window.getSelection();
if (!currentSelection || !selection || selection.rangeCount === 0 || selection.isCollapsed)
return;
const rangeBoxes = [];
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
rangeBoxes.push((0, import_TldrawUiContextualToolbar.rectToBox)(range.getBoundingClientRect()));
}
const bounds = import_editor.Box.Common(rangeBoxes);
previousSelectionBounds.current = bounds;
return bounds;
}, [currentSelection, isEditingLink]);
(0, import_react.useEffect)(() => {
const handleSelectionUpdate = ({ editor: textEditor2 }) => setCurrentSelection(textEditor2.state.selection);
textEditor.on("selectionUpdate", handleSelectionUpdate);
handleSelectionUpdate({ editor: textEditor });
return () => {
textEditor.off("selectionUpdate", handleSelectionUpdate);
};
}, [textEditor]);
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_TldrawUiContextualToolbar.TldrawUiContextualToolbar,
{
className: "tlui-rich-text__toolbar",
getSelectionBounds,
isMousingDown,
changeOnlyWhenYChanges: true,
label: msg("tool.rich-text-toolbar-title"),
children: children ? children : isEditingLink ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_LinkEditor.LinkEditor,
{
textEditor,
value: textEditor.isActive("link") ? textEditor.getAttributes("link").href : "",
onClose: onEditLinkClose
}
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_DefaultRichTextToolbarContent.DefaultRichTextToolbarContent, { textEditor, onEditLinkStart })
}
);
}
function useEditingLinkBehavior(textEditor) {
const [isEditingLink, setIsEditingLink] = (0, import_react.useState)(false);
(0, import_react.useEffect)(() => {
if (!textEditor) {
setIsEditingLink(false);
return;
}
const handleClick = () => {
const isLinkActive = textEditor.isActive("link");
setIsEditingLink(isLinkActive);
};
textEditor.view.dom.addEventListener("click", handleClick);
return () => {
if (textEditor.isInitialized) {
textEditor.view.dom.removeEventListener("click", handleClick);
}
};
}, [textEditor, isEditingLink]);
(0, import_react.useEffect)(() => {
if (!textEditor) {
return;
}
if (textEditor.isActive("link")) {
try {
const { from, to } = (0, import_core.getMarkRange)(
textEditor.state.doc.resolve(textEditor.state.selection.from),
textEditor.schema.marks.link
);
if (textEditor.state.selection.empty) {
textEditor.commands.setTextSelection({ from, to });
}
} catch {
}
}
}, [textEditor, isEditingLink]);
const onEditLinkStart = (0, import_react.useCallback)(() => {
setIsEditingLink(true);
}, []);
const onEditLinkCancel = (0, import_react.useCallback)(() => {
setIsEditingLink(false);
}, []);
const onEditLinkClose = (0, import_react.useCallback)(() => {
setIsEditingLink(false);
if (!textEditor) return;
const from = textEditor.state.selection.from;
textEditor.commands.setTextSelection({ from, to: from });
}, [textEditor]);
return { isEditingLink, onEditLinkStart, onEditLinkClose, onEditLinkCancel };
}
function useIsMousingDownOnTextEditor(textEditor) {
const [isMousingDown, setIsMousingDown] = (0, import_react.useState)(false);
(0, import_react.useEffect)(() => {
if (!textEditor) return;
const handlePointingStateChange = (0, import_editor.debounce)(({ isPointing }) => {
setIsMousingDown(isPointing);
}, 16);
const handlePointingDown = () => handlePointingStateChange({ isPointing: true });
const handlePointingUp = () => handlePointingStateChange({ isPointing: false });
const touchDownEvents = ["touchstart", "pointerdown", "mousedown"];
const touchUpEvents = ["touchend", "pointerup", "mouseup"];
touchDownEvents.forEach((eventName) => {
textEditor.view.dom.addEventListener(eventName, handlePointingDown);
});
touchUpEvents.forEach((eventName) => {
document.body.addEventListener(eventName, handlePointingUp);
});
return () => {
touchDownEvents.forEach((eventName) => {
if (textEditor.isInitialized) {
textEditor.view.dom.removeEventListener(eventName, handlePointingDown);
}
});
touchUpEvents.forEach((eventName) => {
document.body.removeEventListener(eventName, handlePointingUp);
});
};
}, [textEditor]);
return isMousingDown;
}
//# sourceMappingURL=DefaultRichTextToolbar.js.map