UNPKG

tldraw

Version:

A tiny little drawing editor.

302 lines (301 loc) • 13.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var OverflowingToolbar_exports = {}; __export(OverflowingToolbar_exports, { IsInOverflowContext: () => IsInOverflowContext, OverflowingToolbar: () => OverflowingToolbar, isActiveTLUiToolItem: () => isActiveTLUiToolItem }); module.exports = __toCommonJS(OverflowingToolbar_exports); var import_jsx_runtime = require("react/jsx-runtime"); var import_editor = require("@tldraw/editor"); var import_classnames = __toESM(require("classnames"), 1); var import_react = require("react"); var import_constants = require("../../constants"); var import_breakpoints = require("../../context/breakpoints"); var import_useKeyboardShortcuts = require("../../hooks/useKeyboardShortcuts"); var import_useTranslation = require("../../hooks/useTranslation/useTranslation"); var import_TldrawUiButtonIcon = require("../primitives/Button/TldrawUiButtonIcon"); var import_TldrawUiPopover = require("../primitives/TldrawUiPopover"); var import_TldrawUiToolbar = require("../primitives/TldrawUiToolbar"); var import_layout = require("../primitives/layout"); var import_TldrawUiMenuContext = require("../primitives/menus/TldrawUiMenuContext"); const IsInOverflowContext = (0, import_react.createContext)(false); const NUMBERED_SHORTCUT_KEYS = { "1": 0, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6, "8": 7, "9": 8, "0": 9 }; function OverflowingToolbar({ children, orientation, sizingParentClassName, minItems, minSizePx, maxItems, maxSizePx }) { const editor = (0, import_editor.useEditor)(); const id = (0, import_editor.useUniqueSafeId)(); const breakpoint = (0, import_breakpoints.useBreakpoint)(); const msg = (0, import_useTranslation.useTranslation)(); const rButtons = (0, import_react.useRef)([]); const [isOpen, setIsOpen] = (0, import_react.useState)(false); const mainToolsRef = (0, import_react.useRef)(null); const [overflowTools, setOverflowTools] = (0, import_react.useState)(null); const [lastActiveOverflowItem, setLastActiveOverflowItem] = (0, import_react.useState)(null); const [shouldShowOverflow, setShouldShowOverflow] = (0, import_react.useState)(false); const onDomUpdate = (0, import_editor.useEvent)(() => { if (!mainToolsRef.current) return; const sizeProp = orientation === "horizontal" ? "offsetWidth" : "offsetHeight"; const mainItems = collectItems(mainToolsRef.current.children); const overflowItems = overflowTools ? collectItems(overflowTools.children) : null; function collectItems(collection) { const items = []; for (const child of collection) { if (child.classList.contains("tlui-main-toolbar__group")) { items.push({ type: "group", items: collectItems(child.children), element: child }); } else if (!child.hasAttribute("data-radix-popper-content-wrapper")) { items.push({ type: "item", element: child }); } } return items; } const sizingParent = findParentWithClassName(mainToolsRef.current, sizingParentClassName); const size = sizingParent[sizeProp]; const itemsToShow = Math.floor( (0, import_editor.modulate)(size, [minSizePx, maxSizePx], [minItems, maxItems], true) ); let mainItemCount = 0; let newActiveOverflowItem = null; let shouldInvalidateLastActiveOverflowItem = false; const numberedButtons = []; function visitItems(mainItems2, overflowItems2) { if (overflowItems2) (0, import_editor.assert)(mainItems2.length === overflowItems2.length); let didShowAnyInMain = false; let didShowAnyInOverflow2 = false; for (let i = 0; i < mainItems2.length; i++) { const mainItem = mainItems2[i]; const overflowItem = overflowItems2?.[i]; if (mainItem.type === "item") { const isLastActiveOverflowItem = mainItem.element.getAttribute("data-value") === lastActiveOverflowItem; let shouldShowInMain; if (lastActiveOverflowItem) { shouldShowInMain = mainItemCount < itemsToShow || isLastActiveOverflowItem; } else { shouldShowInMain = mainItemCount <= itemsToShow; } const shouldShowInOverflow = mainItemCount >= itemsToShow; didShowAnyInMain ||= shouldShowInMain; didShowAnyInOverflow2 ||= shouldShowInOverflow; setAttribute( mainItem.element, "data-toolbar-visible", shouldShowInMain ? "true" : "false" ); if (overflowItem) { (0, import_editor.assert)(overflowItem.type === "item"); setAttribute( overflowItem.element, "data-toolbar-visible", shouldShowInOverflow ? "true" : "false" ); } if (shouldShowInOverflow && mainItem.element.getAttribute("aria-pressed") === "true") { newActiveOverflowItem = mainItem.element.getAttribute("data-value"); } if (shouldShowInMain && mainItem.element.tagName === "BUTTON") { numberedButtons.push(mainItem.element); } if (!shouldShowInOverflow && isLastActiveOverflowItem) { shouldInvalidateLastActiveOverflowItem = true; } mainItemCount++; } else { let result, overflowGroup; if (overflowItem) { (0, import_editor.assert)(overflowItem.type === "group"); overflowGroup = overflowItem; result = visitItems(mainItem.items, overflowGroup.items); } else { result = visitItems(mainItem.items, null); } didShowAnyInMain ||= result.didShowAnyInMain; didShowAnyInOverflow2 ||= result.didShowAnyInOverflow; setAttribute( mainItem.element, "data-toolbar-visible", result.didShowAnyInMain ? "true" : "false" ); if (overflowGroup) { setAttribute( overflowGroup.element, "data-toolbar-visible", result.didShowAnyInOverflow ? "true" : "false" ); } } } return { didShowAnyInMain, didShowAnyInOverflow: didShowAnyInOverflow2 }; } const { didShowAnyInOverflow } = visitItems(mainItems, overflowItems); setShouldShowOverflow(didShowAnyInOverflow); if (newActiveOverflowItem) { setLastActiveOverflowItem(newActiveOverflowItem); } else if (shouldInvalidateLastActiveOverflowItem) { setLastActiveOverflowItem(null); } rButtons.current = numberedButtons; }); (0, import_react.useLayoutEffect)(() => { onDomUpdate(); }); (0, import_react.useLayoutEffect)(() => { if (!mainToolsRef.current) return; const mutationObserver = new MutationObserver(onDomUpdate); mutationObserver.observe(mainToolsRef.current, { childList: true, subtree: true, attributes: true, characterData: true }); const sizingParent = findParentWithClassName(mainToolsRef.current, sizingParentClassName); const resizeObserver = new ResizeObserver(onDomUpdate); resizeObserver.observe(sizingParent); return () => { mutationObserver.disconnect(); resizeObserver.disconnect(); }; }, [onDomUpdate, sizingParentClassName]); (0, import_react.useEffect)(() => { if (!editor.options.enableToolbarKeyboardShortcuts) return; function handleKeyDown(event) { if ((0, import_useKeyboardShortcuts.areShortcutsDisabled)(editor) || (0, import_editor.activeElementShouldCaptureKeys)( false /* includeButton */ )) { return; } if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return; const index = NUMBERED_SHORTCUT_KEYS[event.key]; if (typeof index === "number") { (0, import_editor.preventDefault)(event); rButtons.current[index]?.click(); } } document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); }; }, [editor]); const popoverId = "toolbar overflow"; const Layout = orientation === "horizontal" ? import_layout.TldrawUiRow : import_layout.TldrawUiColumn; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( import_TldrawUiToolbar.TldrawUiToolbar, { orientation, className: (0, import_classnames.default)("tlui-main-toolbar__tools", { "tlui-main-toolbar__tools__mobile": breakpoint < import_constants.PORTRAIT_BREAKPOINT.TABLET_SM }), label: msg("tool-panel.title"), children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Layout, { id: `${id}_main`, ref: mainToolsRef, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiMenuContext.TldrawUiMenuContextProvider, { type: "toolbar", sourceId: "toolbar", children }) }), shouldShowOverflow && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IsInOverflowContext.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_TldrawUiPopover.TldrawUiPopover, { id: popoverId, open: isOpen, onOpenChange: setIsOpen, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiPopover.TldrawUiPopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { title: msg("tool-panel.more"), type: "tool", className: "tlui-main-toolbar__overflow", "data-testid": "tools.more-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiButtonIcon.TldrawUiButtonIcon, { icon: orientation === "horizontal" ? "chevron-up" : "chevron-right" } ) } ) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiPopover.TldrawUiPopoverContent, { side: orientation === "horizontal" ? "top" : "right", align: orientation === "horizontal" ? "center" : "end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbar, { orientation: "grid", className: "tlui-main-toolbar__overflow-content", ref: setOverflowTools, "data-testid": "tools.more-content", label: msg("tool-panel.more"), id: `${id}_more`, onClick: () => { import_editor.tlmenus.deleteOpenMenu(popoverId, editor.contextId); setIsOpen(false); }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiMenuContext.TldrawUiMenuContextProvider, { type: "toolbar-overflow", sourceId: "toolbar", children }) } ) } ) ] }) }) ] } ) }); } const isActiveTLUiToolItem = (item, activeToolId, geoState) => { return item.meta?.geo ? activeToolId === "geo" && geoState === item.meta?.geo : activeToolId === item.id; }; function findParentWithClassName(startingElement, className) { let element = startingElement; while (element) { if (element.classList.contains(className)) { return element; } element = element.parentElement; } throw new Error("Could not find parent with class name " + className); } function setAttribute(element, name, value) { if (element.getAttribute(name) === value) return; element.setAttribute(name, value); } //# sourceMappingURL=OverflowingToolbar.js.map