tldraw
Version:
A tiny little drawing editor.
174 lines (173 loc) • 6.66 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import {
activeElementShouldCaptureKeys,
preventDefault,
tlmenus,
useEditor,
useEvent,
useUniqueSafeId
} from "@tldraw/editor";
import classNames from "classnames";
import { createContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { PORTRAIT_BREAKPOINT } from "../../constants.mjs";
import { useBreakpoint } from "../../context/breakpoints.mjs";
import { areShortcutsDisabled } from "../../hooks/useKeyboardShortcuts.mjs";
import { useTranslation } from "../../hooks/useTranslation/useTranslation.mjs";
import { TldrawUiButtonIcon } from "../primitives/Button/TldrawUiButtonIcon.mjs";
import {
TldrawUiPopover,
TldrawUiPopoverContent,
TldrawUiPopoverTrigger
} from "../primitives/TldrawUiPopover.mjs";
import { TldrawUiToolbar, TldrawUiToolbarButton } from "../primitives/TldrawUiToolbar.mjs";
import { TldrawUiMenuContextProvider } from "../primitives/menus/TldrawUiMenuContext.mjs";
const IsInOverflowContext = 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 }) {
const editor = useEditor();
const id = useUniqueSafeId();
const breakpoint = useBreakpoint();
const msg = useTranslation();
const rButtons = useRef([]);
const [isOpen, setIsOpen] = useState(false);
const overflowIndex = Math.min(8, 5 + breakpoint);
const [totalItems, setTotalItems] = useState(0);
const mainToolsRef = useRef(null);
const [lastActiveOverflowItem, setLastActiveOverflowItem] = useState(null);
const css = useMemo(() => {
const activeCss = lastActiveOverflowItem ? `:not([data-value="${lastActiveOverflowItem}"])` : "";
return `
#${id}_main > *:nth-child(n + ${overflowIndex + (lastActiveOverflowItem ? 1 : 2)})${activeCss} {
display: none;
}
#${id}_more > *:nth-child(-n + ${overflowIndex}) {
display: none;
}
#${id}_more > *:nth-child(-n + ${overflowIndex + 4}) {
margin-top: 0;
}
`;
}, [lastActiveOverflowItem, id, overflowIndex]);
const onDomUpdate = useEvent(() => {
if (!mainToolsRef.current) return;
const children2 = Array.from(mainToolsRef.current.children);
setTotalItems(children2.length);
const lastActiveElementIdx = children2.findIndex(
(el) => el.getAttribute("data-value") === lastActiveOverflowItem
);
if (lastActiveElementIdx <= overflowIndex) {
setLastActiveOverflowItem(null);
}
const activeElementIdx = Array.from(mainToolsRef.current.children).findIndex(
(el) => el.getAttribute("aria-pressed") === "true"
);
if (activeElementIdx === -1) return;
if (activeElementIdx >= overflowIndex) {
setLastActiveOverflowItem(children2[activeElementIdx].getAttribute("data-value"));
}
rButtons.current = Array.from(mainToolsRef.current?.children ?? []).filter(
(el) => {
if (!(el instanceof HTMLElement)) return false;
if (el.tagName.toLowerCase() !== "button") return false;
return !!(el.offsetWidth || el.offsetHeight);
}
);
});
useLayoutEffect(() => {
onDomUpdate();
});
useLayoutEffect(() => {
if (!mainToolsRef.current) return;
const mutationObserver = new MutationObserver(onDomUpdate);
mutationObserver.observe(mainToolsRef.current, {
childList: true,
subtree: true,
attributeFilter: ["data-value", "aria-pressed"]
});
return () => {
mutationObserver.disconnect();
};
}, [onDomUpdate]);
useEffect(() => {
if (!editor.options.enableToolbarKeyboardShortcuts) return;
function handleKeyDown(event) {
if (areShortcutsDisabled(editor) || activeElementShouldCaptureKeys(
true
/* allow buttons */
))
return;
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return;
const index = NUMBERED_SHORTCUT_KEYS[event.key];
if (typeof index === "number") {
preventDefault(event);
rButtons.current[index]?.click();
}
}
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [editor]);
const popoverId = "toolbar overflow";
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("style", { nonce: editor.options.nonce, children: css }),
/* @__PURE__ */ jsxs(
TldrawUiToolbar,
{
className: classNames("tlui-toolbar__tools", {
"tlui-toolbar__tools__mobile": breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM
}),
label: msg("tool-panel.title"),
children: [
/* @__PURE__ */ jsx("div", { id: `${id}_main`, ref: mainToolsRef, className: "tlui-toolbar__tools__list", children: /* @__PURE__ */ jsx(TldrawUiMenuContextProvider, { type: "toolbar", sourceId: "toolbar", children }) }),
totalItems > overflowIndex + 1 && /* @__PURE__ */ jsx(IsInOverflowContext.Provider, { value: true, children: /* @__PURE__ */ jsxs(TldrawUiPopover, { id: popoverId, open: isOpen, onOpenChange: setIsOpen, children: [
/* @__PURE__ */ jsx(TldrawUiPopoverTrigger, { children: /* @__PURE__ */ jsx(
TldrawUiToolbarButton,
{
title: msg("tool-panel.more"),
type: "tool",
className: "tlui-toolbar__overflow",
"data-testid": "tools.more-button",
children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon: "chevron-up" })
}
) }),
/* @__PURE__ */ jsx(TldrawUiPopoverContent, { side: "top", align: "center", children: /* @__PURE__ */ jsx(
TldrawUiToolbar,
{
className: "tlui-buttons__grid",
"data-testid": "tools.more-content",
label: msg("tool-panel.more"),
id: `${id}_more`,
onClick: () => {
tlmenus.deleteOpenMenu(popoverId, editor.contextId);
setIsOpen(false);
},
children: /* @__PURE__ */ jsx(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;
};
export {
IsInOverflowContext,
OverflowingToolbar,
isActiveTLUiToolItem
};
//# sourceMappingURL=OverflowingToolbar.mjs.map