UNPKG

tldraw

Version:

A tiny little drawing editor.

336 lines (335 loc) • 10.3 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { exhaustiveSwitchError, getPointerInfo, preventDefault, useEditor, Vec } from "@tldraw/editor"; import { ContextMenu as _ContextMenu } from "radix-ui"; import { useMemo, useState } from "react"; import { unwrapLabel } from "../../../context/actions.mjs"; import { useReadonly } from "../../../hooks/useReadonly.mjs"; import { useTranslation } from "../../../hooks/useTranslation/useTranslation.mjs"; import { kbdStr } from "../../../kbd-utils.mjs"; import { Spinner } from "../../Spinner.mjs"; import { TldrawUiButton } from "../Button/TldrawUiButton.mjs"; import { TldrawUiButtonIcon } from "../Button/TldrawUiButtonIcon.mjs"; import { TldrawUiButtonLabel } from "../Button/TldrawUiButtonLabel.mjs"; import { TldrawUiDropdownMenuItem } from "../TldrawUiDropdownMenu.mjs"; import { TldrawUiKbd } from "../TldrawUiKbd.mjs"; import { TldrawUiToolbarButton } from "../TldrawUiToolbar.mjs"; import { hideAllTooltips } from "../TldrawUiTooltip.mjs"; import { useTldrawUiMenuContext } from "./TldrawUiMenuContext.mjs"; function TldrawUiMenuItem({ disabled = false, spinner = false, readonlyOk = false, id, kbd, label, icon, iconLeft, onSelect, noClose, isSelected, onDragStart }) { const { type: menuType, sourceId } = useTldrawUiMenuContext(); const msg = useTranslation(); const [disableClicks, setDisableClicks] = useState(false); const isReadonlyMode = useReadonly(); if (isReadonlyMode && !readonlyOk) return null; const labelToUse = unwrapLabel(label, menuType); const kbdToUse = kbd ? kbdStr(kbd) : void 0; const labelStr = labelToUse ? msg(labelToUse) : void 0; const titleStr = labelStr && kbdToUse ? `${labelStr} ${kbdToUse}` : labelStr; switch (menuType) { case "menu": { return /* @__PURE__ */ jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsxs( TldrawUiButton, { type: "menu", "data-testid": `${sourceId}.${id}`, disabled, onClick: (e) => { if (noClose) { preventDefault(e); } if (disableClicks) { setDisableClicks(false); } else { onSelect(sourceId); } }, children: [ iconLeft && /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon: iconLeft, small: true }), /* @__PURE__ */ jsx(TldrawUiButtonLabel, { children: labelStr }), kbd && /* @__PURE__ */ jsx(TldrawUiKbd, { children: kbd }) ] } ) }); } case "context-menu": { if (disabled) return null; return /* @__PURE__ */ jsxs( _ContextMenu.Item, { dir: "ltr", draggable: false, className: "tlui-button tlui-button__menu", "data-testid": `${sourceId}.${id}`, onSelect: (e) => { if (noClose) preventDefault(e); if (disableClicks) { setDisableClicks(false); } else { onSelect(sourceId); } }, children: [ /* @__PURE__ */ jsx("span", { className: "tlui-button__label", draggable: false, children: labelStr }), iconLeft && /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon: iconLeft, small: true }), kbd && /* @__PURE__ */ jsx(TldrawUiKbd, { children: kbd }), spinner && /* @__PURE__ */ jsx(Spinner, {}) ] } ); } case "small-icons": case "icons": { return /* @__PURE__ */ jsx( TldrawUiToolbarButton, { "data-testid": `${sourceId}.${id}`, type: "icon", title: titleStr, disabled, onClick: () => onSelect(sourceId), children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon, small: true }) } ); } case "keyboard-shortcuts": { if (!kbd) { console.warn( `Menu item '${label}' isn't shown in the keyboard shortcuts dialog because it doesn't have a keyboard shortcut.` ); return null; } return /* @__PURE__ */ jsxs("div", { className: "tlui-shortcuts-dialog__key-pair", "data-testid": `${sourceId}.${id}`, children: [ /* @__PURE__ */ jsx("div", { className: "tlui-shortcuts-dialog__key-pair__key", children: labelStr }), /* @__PURE__ */ jsx("div", { className: "tlui-shortcuts-dialog__key-pair__value", children: /* @__PURE__ */ jsx(TldrawUiKbd, { visibleOnMobileLayout: true, children: kbd }) }) ] }); } case "helper-buttons": { return /* @__PURE__ */ jsxs( TldrawUiButton, { type: "low", "data-testid": `${sourceId}.${id}`, onClick: () => onSelect(sourceId), children: [ /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon }), /* @__PURE__ */ jsx(TldrawUiButtonLabel, { children: labelStr }) ] } ); } case "toolbar": { if (onDragStart) { return /* @__PURE__ */ jsx( DraggableToolbarButton, { id, icon, onSelect, onDragStart, labelStr, titleStr, disabled, isSelected } ); } return /* @__PURE__ */ jsx( TldrawUiToolbarButton, { "aria-label": labelStr, "aria-pressed": isSelected ? "true" : "false", "data-testid": `tools.${id}`, "data-value": id, disabled, onClick: () => onSelect("toolbar"), onTouchStart: (e) => { preventDefault(e); onSelect("toolbar"); }, title: titleStr, type: "tool", children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon }) } ); } case "toolbar-overflow": { if (onDragStart) { return /* @__PURE__ */ jsx( DraggableToolbarButton, { id, icon, onSelect, onDragStart, labelStr, titleStr, disabled, isSelected, overflow: true } ); } return /* @__PURE__ */ jsx( TldrawUiToolbarButton, { "aria-label": labelStr, "aria-pressed": isSelected ? "true" : "false", isActive: isSelected, "data-testid": `tools.more.${id}`, "data-value": id, disabled, onClick: () => onSelect("toolbar"), title: titleStr, type: "icon", children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon }) } ); } default: { throw exhaustiveSwitchError(menuType); } } } function useDraggableEvents(onDragStart, onSelect) { const editor = useEditor(); const events = useMemo(() => { let state = { name: "idle" }; function handlePointerDown(e) { state = { name: "pointing", screenSpaceStart: { x: e.clientX, y: e.clientY } }; e.currentTarget.setPointerCapture(e.pointerId); } function handlePointerMove(e) { if (e.isSpecialRedispatchedEvent) return; if (state.name === "pointing") { const distanceSq = Vec.Dist2(state.screenSpaceStart, { x: e.clientX, y: e.clientY }); if (distanceSq > (editor.getInstanceState().isCoarsePointer ? editor.options.uiCoarseDragDistanceSquared : editor.options.uiDragDistanceSquared)) { const screenSpaceStart = state.screenSpaceStart; state = { name: "dragging", screenSpaceStart }; editor.run(() => { editor.setCurrentTool("select"); editor.dispatch({ type: "pointer", target: "canvas", name: "pointer_down", ...getPointerInfo(editor, e), point: screenSpaceStart }); editor.selectNone(); onDragStart?.("toolbar", { type: "pointer", target: "canvas", name: "pointer_move", ...getPointerInfo(editor, e), point: screenSpaceStart }); hideAllTooltips(); editor.getContainer().focus(); }); } } } function handlePointerUp(e) { if (e.isSpecialRedispatchedEvent) return; e.currentTarget.releasePointerCapture(e.pointerId); editor.dispatch({ type: "pointer", target: "canvas", name: "pointer_up", ...getPointerInfo(editor, e) }); } function handleClick() { if (state.name === "dragging" || state.name === "dragged") { state = { name: "idle" }; return true; } state = { name: "idle" }; onSelect?.("toolbar"); } return { onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onClick: handleClick }; }, [onDragStart, editor, onSelect]); return events; } function DraggableToolbarButton({ id, labelStr, titleStr, disabled, isSelected, icon, onSelect, onDragStart, overflow }) { const events = useDraggableEvents(onDragStart, onSelect); if (overflow) { return /* @__PURE__ */ jsx( TldrawUiToolbarButton, { "aria-label": labelStr, "aria-pressed": isSelected ? "true" : "false", isActive: isSelected, className: "tlui-button-grid__button", "data-testid": `tools.more.${id}`, "data-value": id, disabled, title: titleStr, type: "icon", ...events, children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon }) } ); } return /* @__PURE__ */ jsx( TldrawUiToolbarButton, { "aria-label": labelStr, "aria-pressed": isSelected ? "true" : "false", "data-testid": `tools.${id}`, "data-value": id, disabled, onTouchStart: (e) => { preventDefault(e); onSelect("toolbar"); }, title: titleStr, type: "tool", ...events, children: /* @__PURE__ */ jsx(TldrawUiButtonIcon, { icon }) } ); } export { TldrawUiMenuItem }; //# sourceMappingURL=TldrawUiMenuItem.mjs.map