tldraw
Version:
A tiny little drawing editor.
302 lines (301 loc) • 13.1 kB
JavaScript
;
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