@mocanvas/mocanvas 4.0.2 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import { pathWordsToSvgD, getGeoGeometry } from './chunk-OMUOD53T.js';
2
2
  import { GEO_SHAPE_KINDS, EditorPortal, useEditor, useValue, getLocaleChain, resolveUiMessage, useActions, useTools, useIsToolSelected } from '@mocanvas/editor';
3
3
  import { createContext, useState, useCallback, useMemo, useContext, useRef, useEffect, useLayoutEffect, useId } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { atom } from '@mocanvas/state';
5
6
 
6
7
  var DialogsContext = createContext(null);
7
8
  var NOOP = { addDialog: () => "", removeDialog: () => {
@@ -180,6 +181,16 @@ var TOOL_ICONS = {
180
181
  ] }),
181
182
  /* @__PURE__ */ jsx("path", { d: "M7 19.4h10.5" })
182
183
  ] }),
184
+ highlight: /* @__PURE__ */ jsxs(Fragment, { children: [
185
+ /* @__PURE__ */ jsx("path", { d: "M9.15 14.9L6.1 11.85l7.15-6.4a2.15 2.15 0 0 1 3.05 3.05z" }),
186
+ /* @__PURE__ */ jsx("path", { d: "M8.6 15.7l-2.9.6.5-2.95" }),
187
+ /* @__PURE__ */ jsx("path", { d: "M5.2 20.05h13.6", strokeWidth: 2.8 })
188
+ ] }),
189
+ laser: /* @__PURE__ */ jsxs(Fragment, { children: [
190
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2.55" }),
191
+ /* @__PURE__ */ jsx("path", { d: "M12 3.1v3.15M12 17.75v3.15M3.1 12h3.15M17.75 12h3.15" }),
192
+ /* @__PURE__ */ jsx("path", { d: "M5.7 5.7l2.2 2.2M16.1 16.1l2.2 2.2M18.3 5.7l-2.2 2.2M7.9 16.1l-2.2 2.2" })
193
+ ] }),
183
194
  text: /* @__PURE__ */ jsxs(Fragment, { children: [
184
195
  /* @__PURE__ */ jsx("path", { d: "M5 6.25V4.5h14v1.75" }),
185
196
  /* @__PURE__ */ jsx("path", { d: "M12 4.5v15" }),
@@ -603,6 +614,28 @@ function Popover({ anchorRef, open, onClose, label, cols = 5, prefer = "above",
603
614
  }
604
615
  );
605
616
  }
617
+ var LayerNestingContext = createContext(null);
618
+ function useLayerNesting() {
619
+ const parent = useContext(LayerNestingContext);
620
+ const nested = useRef(/* @__PURE__ */ new Set());
621
+ return useMemo(
622
+ () => ({
623
+ register(node) {
624
+ nested.current.add(node);
625
+ const releaseParent = parent?.register(node);
626
+ return () => {
627
+ nested.current.delete(node);
628
+ releaseParent?.();
629
+ };
630
+ },
631
+ containsNested(target) {
632
+ for (const node of nested.current) if (node.contains(target)) return true;
633
+ return false;
634
+ }
635
+ }),
636
+ [parent]
637
+ );
638
+ }
606
639
  function useAnchoredPosition(anchorRef, open, prefer) {
607
640
  const ref = useRef(null);
608
641
  const [pos, setPos] = useState(null);
@@ -618,16 +651,19 @@ function useAnchoredPosition(anchorRef, open, prefer) {
618
651
  }, [open, prefer, anchorRef]);
619
652
  return [ref, pos];
620
653
  }
621
- function useDismissable(open, layerRef, anchorRef, onClose) {
654
+ function useDismissable(open, layerRef, anchorRef, onClose, containsNested) {
622
655
  useEffect(() => {
623
656
  if (!open) return;
624
657
  const onDown = (event) => {
625
658
  const target = event.target;
626
659
  if (layerRef.current?.contains(target) || anchorRef.current?.contains(target)) return;
660
+ if (containsNested?.(target)) return;
627
661
  onClose();
628
662
  };
629
663
  const onKey = (event) => {
630
664
  if (event.key !== "Escape") return;
665
+ if (isTypingTarget(event.target)) return;
666
+ if (containsNested?.(event.target)) return;
631
667
  event.stopPropagation();
632
668
  onClose();
633
669
  anchorRef.current?.focus();
@@ -638,9 +674,14 @@ function useDismissable(open, layerRef, anchorRef, onClose) {
638
674
  document.removeEventListener("pointerdown", onDown, true);
639
675
  document.removeEventListener("keydown", onKey, true);
640
676
  };
641
- }, [open, layerRef, anchorRef, onClose]);
677
+ }, [open, layerRef, anchorRef, onClose, containsNested]);
642
678
  }
643
679
  var FOCUSABLE2 = '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"],[role="option"],button:not([disabled]),a[href],input:not([disabled])';
680
+ function isTypingTarget(target) {
681
+ const el = target;
682
+ if (!el || typeof el.tagName !== "string") return false;
683
+ return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable === true;
684
+ }
644
685
  function useMenuKeyboard(open, layerRef) {
645
686
  useEffect(() => {
646
687
  if (!open) return;
@@ -650,6 +691,7 @@ function useMenuKeyboard(open, layerRef) {
650
691
  const first = items()[0];
651
692
  first?.focus();
652
693
  const onKey = (event) => {
694
+ if (isTypingTarget(event.target)) return;
653
695
  const list = items();
654
696
  if (list.length === 0) return;
655
697
  const index = list.indexOf(layer.ownerDocument.activeElement);
@@ -683,8 +725,15 @@ function FloatingLayer({
683
725
  children
684
726
  }) {
685
727
  const [ref, pos] = useAnchoredPosition(anchorRef, open, prefer);
686
- useDismissable(open, ref, anchorRef, onClose);
728
+ const parent = useContext(LayerNestingContext);
729
+ const nesting = useLayerNesting();
730
+ useDismissable(open, ref, anchorRef, onClose, nesting.containsNested);
687
731
  useMenuKeyboard(keyboardNav && open, ref);
732
+ useEffect(() => {
733
+ const node = ref.current;
734
+ if (!open || !node || !parent) return;
735
+ return parent.register(node);
736
+ }, [open, parent, ref]);
688
737
  if (!open) return null;
689
738
  return /* @__PURE__ */ jsx(EditorPortal, { children: /* @__PURE__ */ jsx(
690
739
  "div",
@@ -695,7 +744,7 @@ function FloatingLayer({
695
744
  ...label ? { "aria-label": label } : {},
696
745
  style: pos ? { position: "fixed", left: pos.left, top: pos.top } : { position: "fixed", left: 0, top: 0, visibility: "hidden" },
697
746
  onPointerDown: (event) => event.stopPropagation(),
698
- children
747
+ children: /* @__PURE__ */ jsx(LayerNestingContext.Provider, { value: nesting, children })
699
748
  }
700
749
  ) });
701
750
  }
@@ -1114,11 +1163,13 @@ function TldrawUiTranslationProvider({ overrides, children }) {
1114
1163
  const locale = useValue("ui locale", () => editor.user.getLocale(), [editor]);
1115
1164
  const value = useMemo(() => {
1116
1165
  const messages = overrides ? Object.assign({}, ...getLocaleChain(locale).reverse().map((l) => overrides[l] ?? {})) : {};
1166
+ const locales = overrides ? Object.keys(overrides).filter((l) => Object.keys(overrides[l] ?? {}).length > 0) : [];
1117
1167
  return {
1118
1168
  locale,
1119
1169
  label: LANGUAGES.find((l) => l.locale === locale)?.label ?? locale,
1120
1170
  dir: isRtlLanguage(locale) ? "rtl" : "ltr",
1121
- messages
1171
+ messages,
1172
+ locales
1122
1173
  };
1123
1174
  }, [locale, overrides]);
1124
1175
  return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
@@ -1140,6 +1191,14 @@ function useTranslation() {
1140
1191
  }, [translation]);
1141
1192
  }
1142
1193
  var useMsg = useTranslation;
1194
+ function useAvailableTranslationLocales() {
1195
+ const translation = useMaybeCurrentTranslation();
1196
+ const locales = translation?.locales;
1197
+ return useMemo(
1198
+ () => (locales ?? []).map((locale) => LANGUAGES.find((l) => l.locale === locale) ?? { locale, label: locale }),
1199
+ [locales]
1200
+ );
1201
+ }
1143
1202
  function useDirection() {
1144
1203
  return useMaybeCurrentTranslation()?.dir ?? "ltr";
1145
1204
  }
@@ -1306,20 +1365,398 @@ function TldrawUiMenuToolItem({ toolId, label, icon, disabled }) {
1306
1365
  }
1307
1366
  );
1308
1367
  }
1309
- function DefaultKeyboardShortcutsDialogContent() {
1368
+ var debugStatsOpen = atom("debugStatsOpen", false);
1369
+ function isEditable(t) {
1370
+ return t instanceof HTMLElement && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable);
1371
+ }
1372
+ var clipboardFallback = "";
1373
+ var KEYBOARD_SHORTCUTS = [
1374
+ // ---- edit ---------------------------------------------------------------
1375
+ { id: "undo", label: "Undo", kbd: "mod+z", group: "Edit", run: (editor) => void editor.undo() },
1376
+ { id: "redo", label: "Redo", kbd: "mod+shift+z", also: ["mod+y"], group: "Edit", run: (editor) => void editor.redo() },
1377
+ { id: "select-all", label: "Select all", kbd: "mod+a", group: "Edit", run: (editor) => void editor.selectAll() },
1378
+ {
1379
+ id: "duplicate",
1380
+ label: "Duplicate",
1381
+ kbd: "mod+d",
1382
+ group: "Edit",
1383
+ run: (editor) => {
1384
+ const ids = editor.getSelectedShapeIds();
1385
+ if (ids.length === 0) return;
1386
+ editor.markHistoryStoppingPoint("duplicate");
1387
+ editor.setSelectedShapes(editor.duplicateShapes(ids));
1388
+ }
1389
+ },
1390
+ {
1391
+ id: "copy",
1392
+ label: "Copy",
1393
+ kbd: "mod+c",
1394
+ group: "Edit",
1395
+ run: (editor) => writeSelectionToClipboard(editor) ? void 0 : false
1396
+ },
1397
+ {
1398
+ id: "cut",
1399
+ label: "Cut",
1400
+ kbd: "mod+x",
1401
+ group: "Edit",
1402
+ run: (editor) => {
1403
+ if (!writeSelectionToClipboard(editor)) return false;
1404
+ editor.markHistoryStoppingPoint("cut");
1405
+ editor.deleteShapes(editor.getSelectedShapeIds());
1406
+ return void 0;
1407
+ }
1408
+ },
1409
+ {
1410
+ id: "paste",
1411
+ label: "Paste",
1412
+ kbd: "mod+v",
1413
+ group: "Edit",
1414
+ run: (editor) => {
1415
+ const paste = (text) => {
1416
+ try {
1417
+ const data = JSON.parse(text);
1418
+ if (data.type !== "application/mocanvas" || !Array.isArray(data.shapes)) return;
1419
+ editor.markHistoryStoppingPoint("paste");
1420
+ editor.putContentOntoCurrentPage(data, { point: editor.getViewportPageCenter() });
1421
+ } catch {
1422
+ }
1423
+ };
1424
+ if (navigator.clipboard?.readText) navigator.clipboard.readText().then(paste, () => clipboardFallback && paste(clipboardFallback));
1425
+ else if (clipboardFallback) paste(clipboardFallback);
1426
+ }
1427
+ },
1428
+ // ---- arrange ------------------------------------------------------------
1429
+ {
1430
+ id: "group",
1431
+ label: "Group",
1432
+ kbd: "mod+g",
1433
+ group: "Arrange",
1434
+ run: (editor) => {
1435
+ const ids = editor.getSelectedShapeIds();
1436
+ if (ids.length === 0) return;
1437
+ editor.markHistoryStoppingPoint("group");
1438
+ editor.groupShapes(ids);
1439
+ }
1440
+ },
1441
+ {
1442
+ id: "ungroup",
1443
+ label: "Ungroup",
1444
+ kbd: "mod+shift+g",
1445
+ group: "Arrange",
1446
+ run: (editor) => {
1447
+ const ids = editor.getSelectedShapeIds();
1448
+ if (ids.length === 0) return;
1449
+ editor.markHistoryStoppingPoint("ungroup");
1450
+ editor.ungroupShapes(ids);
1451
+ }
1452
+ },
1453
+ {
1454
+ id: "toggle-lock",
1455
+ label: "Lock or unlock",
1456
+ kbd: "mod+shift+l",
1457
+ group: "Arrange",
1458
+ run: (editor) => {
1459
+ editor.markHistoryStoppingPoint("lock");
1460
+ editor.toggleLock();
1461
+ }
1462
+ },
1463
+ { id: "bring-forward", label: "Bring forward", kbd: "mod+]", group: "Arrange", run: (editor) => void editor.bringForward() },
1464
+ { id: "bring-to-front", label: "Bring to front", kbd: "mod+alt+]", group: "Arrange", run: (editor) => void editor.bringToFront() },
1465
+ { id: "send-backward", label: "Send backward", kbd: "mod+[", group: "Arrange", run: (editor) => void editor.sendBackward() },
1466
+ { id: "send-to-back", label: "Send to back", kbd: "mod+alt+[", group: "Arrange", run: (editor) => void editor.sendToBack() },
1467
+ // ---- view ---------------------------------------------------------------
1468
+ { id: "zoom-in", label: "Zoom in", kbd: "mod+=", also: ["mod++", "mod+shift+="], group: "View", run: (editor) => void editor.zoomIn() },
1469
+ { id: "zoom-out", label: "Zoom out", kbd: "mod+-", group: "View", run: (editor) => void editor.zoomOut() },
1470
+ { id: "reset-zoom", label: "Reset zoom", kbd: "mod+0", group: "View", run: (editor) => void editor.resetZoom() },
1471
+ { id: "zoom-to-fit", label: "Zoom to fit", kbd: "mod+1", group: "View", run: (editor) => void editor.zoomToFit() },
1472
+ { id: "zoom-to-selection", label: "Zoom to selection", kbd: "mod+2", group: "View", run: (editor) => void editor.zoomToSelection() },
1473
+ {
1474
+ id: "toggle-grid",
1475
+ label: "Show grid",
1476
+ kbd: "mod+'",
1477
+ group: "View",
1478
+ run: (editor) => void editor.updateInstanceState({ isGridMode: !editor.getInstanceState().isGridMode })
1479
+ },
1480
+ {
1481
+ id: "toggle-focus-mode",
1482
+ label: "Focus mode",
1483
+ kbd: "mod+.",
1484
+ group: "View",
1485
+ run: (editor) => void editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode })
1486
+ },
1487
+ {
1488
+ id: "toggle-debug-stats",
1489
+ label: "Frame statistics",
1490
+ // Not ⌥D: that belongs to Align right, which the Arrange menu advertises
1491
+ // and which is one of a coherent ⌥A/H/D/W/V/S set — breaking the set so
1492
+ // one of its members opens a developer overlay is the wrong trade. The
1493
+ // overlay takes the accelerator-plus-⌥ spelling instead.
1494
+ kbd: "mod+alt+d",
1495
+ group: "View",
1496
+ run: () => void debugStatsOpen.set(!debugStatsOpen.get())
1497
+ },
1498
+ // ---- canvas -------------------------------------------------------------
1499
+ {
1500
+ id: "toggle-tool-lock",
1501
+ label: "Tool lock",
1502
+ kbd: "q",
1503
+ group: "Canvas",
1504
+ run: (editor) => void editor.updateInstanceState({ isToolLocked: !editor.getInstanceState().isToolLocked })
1505
+ }
1506
+ ];
1507
+ function writeSelectionToClipboard(editor) {
1508
+ const ids = editor.getSelectedShapeIds();
1509
+ if (ids.length === 0) return false;
1510
+ const content = editor.getContentFromCurrentPage(ids);
1511
+ if (!content) return false;
1512
+ const text = JSON.stringify({ type: "application/mocanvas", ...content });
1513
+ navigator.clipboard?.writeText(text).catch(() => {
1514
+ });
1515
+ clipboardFallback = text;
1516
+ return true;
1517
+ }
1518
+ function pressedKeyNames(event) {
1519
+ const names = [];
1520
+ const key = event.key.toLowerCase();
1521
+ if (key) names.push(key);
1522
+ const code = event.code;
1523
+ const fromCode = /^Key[A-Z]$/.test(code) ? code.slice(3).toLowerCase() : /^Digit[0-9]$/.test(code) ? code.slice(5) : PUNCTUATION_CODES[code];
1524
+ if (fromCode && !names.includes(fromCode)) names.push(fromCode);
1525
+ return names;
1526
+ }
1527
+ var PUNCTUATION_CODES = {
1528
+ Equal: "=",
1529
+ Minus: "-",
1530
+ BracketLeft: "[",
1531
+ BracketRight: "]",
1532
+ Quote: "'",
1533
+ Period: ".",
1534
+ Comma: ",",
1535
+ Slash: "/",
1536
+ Semicolon: ";",
1537
+ Backslash: "\\"
1538
+ };
1539
+ function bindingString(mod, alt, shift, key) {
1540
+ return `${mod ? "mod+" : ""}${alt ? "alt+" : ""}${shift ? "shift+" : ""}${key}`;
1541
+ }
1542
+ function normalizeKbd(kbd) {
1543
+ const parts = kbd.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
1544
+ const key = kbd.trim().endsWith("+") ? "+" : parts.pop() ?? "";
1545
+ const set = new Set(parts);
1546
+ return bindingString(set.has("mod") || set.has("cmd") || set.has("ctrl"), set.has("alt") || set.has("opt"), set.has("shift"), key);
1547
+ }
1548
+ function pressedBindings(e) {
1549
+ const isMac = /Mac|iPhone|iPad/.test(navigator.platform ?? "");
1550
+ if (isMac ? e.ctrlKey : e.metaKey) return [];
1551
+ const mod = isMac ? e.metaKey : e.ctrlKey;
1552
+ return pressedKeyNames(e).map((name) => bindingString(mod, e.altKey, e.shiftKey, name));
1553
+ }
1554
+ function buildShortcutIndex(shortcuts = KEYBOARD_SHORTCUTS) {
1555
+ const index = /* @__PURE__ */ new Map();
1556
+ for (const shortcut of shortcuts) {
1557
+ for (const binding of [shortcut.kbd, ...shortcut.also ?? []]) {
1558
+ const normalized = normalizeKbd(binding);
1559
+ if (!index.has(normalized)) index.set(normalized, shortcut);
1560
+ }
1561
+ }
1562
+ return index;
1563
+ }
1564
+ var TOOL_KEYS = {
1565
+ v: { tool: "select" },
1566
+ h: { tool: "hand" },
1567
+ r: { tool: "geo", geo: "rectangle" },
1568
+ o: { tool: "geo", geo: "ellipse" },
1569
+ d: { tool: "draw" },
1570
+ p: { tool: "draw" },
1571
+ b: { tool: "draw" },
1572
+ e: { tool: "eraser" },
1573
+ n: { tool: "note" },
1574
+ t: { tool: "text" },
1575
+ a: { tool: "arrow", optional: true },
1576
+ l: { tool: "line", optional: true },
1577
+ f: { tool: "frame", optional: true }
1578
+ };
1579
+ function useKeyboardShortcuts(editor, options = {}) {
1580
+ const bindTools = options.tools ?? true;
1581
+ useEffect(() => {
1582
+ if (!editor) return;
1583
+ const isMac = /Mac|iPhone|iPad/.test(navigator.platform ?? "");
1584
+ const index = buildShortcutIndex();
1585
+ const onKeyDown = (e) => {
1586
+ if (isEditable(e.target)) return;
1587
+ if (editor.getEditingShapeId()) return;
1588
+ const mod = isMac ? e.metaKey : e.ctrlKey;
1589
+ if (isMac ? e.ctrlKey : e.metaKey) return;
1590
+ for (const name of pressedKeyNames(e)) {
1591
+ const shortcut = index.get(bindingString(mod, e.altKey, e.shiftKey, name));
1592
+ if (!shortcut) continue;
1593
+ if (shortcut.run(editor, e) !== false) e.preventDefault();
1594
+ return;
1595
+ }
1596
+ if (mod || e.altKey || !bindTools) return;
1597
+ const tool = TOOL_KEYS[e.key.toLowerCase()];
1598
+ if (!tool) return;
1599
+ if (tool.optional && !editor.root.children?.[tool.tool]) return;
1600
+ editor.setCurrentTool(tool.tool, tool.geo ? { geo: tool.geo } : void 0);
1601
+ };
1602
+ window.addEventListener("keydown", onKeyDown);
1603
+ return () => window.removeEventListener("keydown", onKeyDown);
1604
+ }, [editor, bindTools]);
1605
+ }
1606
+ function isEditable2(t) {
1607
+ return t instanceof HTMLElement && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable);
1608
+ }
1609
+ function toolKeyMap(tools, isReadonly) {
1610
+ const map = /* @__PURE__ */ new Map();
1611
+ for (const tool of Object.values(tools)) {
1612
+ if (tool.disabled) continue;
1613
+ if (isReadonly && !tool.readonlyOk) continue;
1614
+ if (!tool.kbd) continue;
1615
+ for (const raw of tool.kbd.split(",")) {
1616
+ const key = raw.trim().toLowerCase();
1617
+ if (key === "" || key.includes("+")) continue;
1618
+ if (!map.has(key)) map.set(key, tool.id);
1619
+ }
1620
+ }
1621
+ return map;
1622
+ }
1623
+ function useToolShortcuts() {
1624
+ const editor = useEditor();
1310
1625
  const tools = useTools();
1626
+ const toolsRef = useRef(tools);
1627
+ toolsRef.current = tools;
1628
+ useEffect(() => {
1629
+ const onKeyDown = (e) => {
1630
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
1631
+ if (e.shiftKey && e.key.length === 1 && /[a-z0-9]/i.test(e.key)) return;
1632
+ if (isEditable2(e.target)) return;
1633
+ if (editor.getEditingShapeId()) return;
1634
+ const map = toolKeyMap(toolsRef.current, editor.getInstanceState().isReadonly);
1635
+ const id = map.get(e.key.toLowerCase());
1636
+ if (id === void 0) return;
1637
+ const tool = toolsRef.current[id];
1638
+ if (!tool) return;
1639
+ e.preventDefault();
1640
+ tool.onSelect("kbd");
1641
+ };
1642
+ window.addEventListener("keydown", onKeyDown);
1643
+ return () => window.removeEventListener("keydown", onKeyDown);
1644
+ }, [editor]);
1645
+ }
1646
+ function ToolShortcuts() {
1647
+ useToolShortcuts();
1648
+ return null;
1649
+ }
1650
+ function isEditable3(t) {
1651
+ return t instanceof HTMLElement && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable);
1652
+ }
1653
+ function actionKeyMap(actions, isReadonly) {
1654
+ const owned = buildShortcutIndex();
1655
+ const map = /* @__PURE__ */ new Map();
1656
+ for (const action of Object.values(actions)) {
1657
+ if (action.disabled) continue;
1658
+ if (isReadonly && !action.readonlyOk) continue;
1659
+ if (!action.kbd) continue;
1660
+ for (const raw of action.kbd.split(",")) {
1661
+ const binding = raw.trim();
1662
+ if (binding === "") continue;
1663
+ const normalized = normalizeKbd(binding);
1664
+ if (owned.has(normalized)) continue;
1665
+ if (!map.has(normalized)) map.set(normalized, action.id);
1666
+ }
1667
+ }
1668
+ return map;
1669
+ }
1670
+ function useActionShortcuts() {
1671
+ const editor = useEditor();
1311
1672
  const actions = useActions();
1312
- const toolItems = Object.values(tools).filter((tool) => tool.kbd);
1313
- const actionItems = Object.values(actions).filter((action) => action.kbd);
1673
+ const actionsRef = useRef(actions);
1674
+ actionsRef.current = actions;
1675
+ useEffect(() => {
1676
+ const onKeyDown = (e) => {
1677
+ if (isEditable3(e.target)) return;
1678
+ if (editor.getEditingShapeId()) return;
1679
+ const map = actionKeyMap(actionsRef.current, editor.getInstanceState().isReadonly);
1680
+ for (const binding of pressedBindings(e)) {
1681
+ const id = map.get(binding);
1682
+ if (id === void 0) continue;
1683
+ const action = actionsRef.current[id];
1684
+ if (!action) continue;
1685
+ e.preventDefault();
1686
+ action.onSelect("kbd");
1687
+ return;
1688
+ }
1689
+ };
1690
+ window.addEventListener("keydown", onKeyDown);
1691
+ return () => window.removeEventListener("keydown", onKeyDown);
1692
+ }, [editor]);
1693
+ }
1694
+ function ActionShortcuts() {
1695
+ useActionShortcuts();
1696
+ return null;
1697
+ }
1698
+ function useBoundToolShortcuts() {
1699
+ const tools = useTools();
1700
+ const readonly = useReadonly();
1701
+ return useMemo(() => {
1702
+ const bound = toolKeyMap(tools, readonly);
1703
+ const keysByTool = /* @__PURE__ */ new Map();
1704
+ for (const [key, toolId] of bound) {
1705
+ const keys = keysByTool.get(toolId);
1706
+ if (keys) keys.push(key);
1707
+ else keysByTool.set(toolId, [key]);
1708
+ }
1709
+ const rows = [];
1710
+ for (const tool of Object.values(tools)) {
1711
+ const keys = keysByTool.get(tool.id);
1712
+ if (!keys || keys.length === 0) continue;
1713
+ rows.push({ id: tool.id, label: tool.label, kbd: keys.join(",") });
1714
+ }
1715
+ return rows;
1716
+ }, [tools, readonly]);
1717
+ }
1718
+ function useBoundActionShortcuts() {
1719
+ const actions = useActions();
1720
+ const readonly = useReadonly();
1721
+ return useMemo(() => {
1722
+ const bound = actionKeyMap(actions, readonly);
1723
+ const keysByAction = /* @__PURE__ */ new Map();
1724
+ for (const [key, actionId] of bound) {
1725
+ const keys = keysByAction.get(actionId);
1726
+ if (keys) keys.push(key);
1727
+ else keysByAction.set(actionId, [key]);
1728
+ }
1729
+ const rows = [];
1730
+ for (const action of Object.values(actions)) {
1731
+ const keys = keysByAction.get(action.id);
1732
+ if (!keys || keys.length === 0) continue;
1733
+ rows.push({ id: action.id, label: action.label, kbd: keys.join(",") });
1734
+ }
1735
+ return rows;
1736
+ }, [actions, readonly]);
1737
+ }
1738
+ var GROUP_ORDER = ["Edit", "View", "Arrange", "Canvas"];
1739
+ function groupKeyboardShortcuts(shortcuts = KEYBOARD_SHORTCUTS) {
1740
+ return GROUP_ORDER.map((group) => ({
1741
+ group,
1742
+ rows: shortcuts.filter((s) => s.group === group).map((s) => ({ id: s.id, label: s.label, kbd: s.kbd }))
1743
+ })).filter((section) => section.rows.length > 0);
1744
+ }
1745
+ function Section({ title, rows }) {
1746
+ if (rows.length === 0) return null;
1747
+ return /* @__PURE__ */ jsxs("section", { className: "mocanvas-shortcut-section", "data-section": title, children: [
1748
+ /* @__PURE__ */ jsx("h3", { children: title }),
1749
+ rows.map((row) => /* @__PURE__ */ jsx(TldrawUiMenuItem, { id: row.id, label: row.label, kbd: row.kbd }, row.id))
1750
+ ] });
1751
+ }
1752
+ function DefaultKeyboardShortcutsDialogContent() {
1753
+ const toolRows = useBoundToolShortcuts();
1754
+ const actionRows = useBoundActionShortcuts();
1755
+ const sections = groupKeyboardShortcuts();
1314
1756
  return /* @__PURE__ */ jsxs(TldrawUiMenuContextProvider, { type: "keyboard-shortcuts", children: [
1315
- /* @__PURE__ */ jsxs("section", { className: "mocanvas-shortcut-section", children: [
1316
- /* @__PURE__ */ jsx("h3", { children: "Tools" }),
1317
- toolItems.map((tool) => /* @__PURE__ */ jsx(TldrawUiMenuItem, { id: tool.id, label: tool.label, kbd: tool.kbd }, tool.id))
1318
- ] }),
1319
- /* @__PURE__ */ jsxs("section", { className: "mocanvas-shortcut-section", children: [
1320
- /* @__PURE__ */ jsx("h3", { children: "Actions" }),
1321
- actionItems.map((action) => /* @__PURE__ */ jsx(TldrawUiMenuItem, { id: action.id, label: action.label, kbd: action.kbd }, action.id))
1322
- ] })
1757
+ /* @__PURE__ */ jsx(Section, { title: "Tools", rows: toolRows }),
1758
+ sections.map((section) => /* @__PURE__ */ jsx(Section, { title: section.group, rows: section.rows }, section.group)),
1759
+ /* @__PURE__ */ jsx(Section, { title: "Actions", rows: actionRows })
1323
1760
  ] });
1324
1761
  }
1325
1762
  function DefaultKeyboardShortcutsDialog({ children }) {
@@ -1335,6 +1772,6 @@ function KeyboardShortcutsDialogContents(_props) {
1335
1772
  return /* @__PURE__ */ jsx(DefaultKeyboardShortcutsDialog, {});
1336
1773
  }
1337
1774
 
1338
- export { DefaultDialogs, DefaultKeyboardShortcutsDialog, DefaultKeyboardShortcutsDialogContent, ExampleDialog, FloatingLayer, GEO_BOX, GEO_ICON_PATHS, ICONS, ICON_GRID, ICON_NAMES, Icon, KeyboardShortcutsDialogContents, LANGUAGES, Popover, RTL_LANGUAGES, TldrawUiButton, TldrawUiButtonCheck, TldrawUiButtonIcon, TldrawUiButtonLabel, TldrawUiDialogBody, TldrawUiDialogCloseButton, TldrawUiDialogFooter, TldrawUiDialogHeader, TldrawUiDialogTitle, TldrawUiDialogsProvider, TldrawUiDropdownMenuCheckboxItem, TldrawUiDropdownMenuContent, TldrawUiDropdownMenuGroup, TldrawUiDropdownMenuIndicator, TldrawUiDropdownMenuItem, TldrawUiDropdownMenuRoot, TldrawUiDropdownMenuSub, TldrawUiDropdownMenuSubContent, TldrawUiDropdownMenuSubTrigger, TldrawUiDropdownMenuTrigger, TldrawUiEventsProvider, TldrawUiIcon, TldrawUiKbd, TldrawUiMenuActionCheckboxItem, TldrawUiMenuActionItem, TldrawUiMenuCheckboxItem, TldrawUiMenuContextProvider, TldrawUiMenuGroup, TldrawUiMenuItem, TldrawUiMenuSubmenu, TldrawUiMenuToolItem, TldrawUiTranslationProvider, UiTooltip, getDefaultTranslationLocale, getGeoIconBox, isRtlLanguage, kbdToKeys, placeNear, useActionState, useAnchoredPosition, useCanApplySelectionAction, useCanRedo, useCanUndo, useCurrentTranslation, useDialogs, useDirection, useDismissable, useHasLockedShapes, useIsDarkMode, useIsGridMode, useMaybeCurrentTranslation, useMenuKeyboard, useMsg, useReadonly, useRelevantStyles, useTldrawUiMenuContext, useTranslation, useUiEvents, useUnlockedSelectedShapesCount };
1339
- //# sourceMappingURL=chunk-QDUQZXE4.js.map
1340
- //# sourceMappingURL=chunk-QDUQZXE4.js.map
1775
+ export { ActionShortcuts, DefaultDialogs, DefaultKeyboardShortcutsDialog, DefaultKeyboardShortcutsDialogContent, ExampleDialog, FloatingLayer, GEO_BOX, GEO_ICON_PATHS, ICONS, ICON_GRID, ICON_NAMES, Icon, KEYBOARD_SHORTCUTS, KeyboardShortcutsDialogContents, LANGUAGES, Popover, RTL_LANGUAGES, TldrawUiButton, TldrawUiButtonCheck, TldrawUiButtonIcon, TldrawUiButtonLabel, TldrawUiDialogBody, TldrawUiDialogCloseButton, TldrawUiDialogFooter, TldrawUiDialogHeader, TldrawUiDialogTitle, TldrawUiDialogsProvider, TldrawUiDropdownMenuCheckboxItem, TldrawUiDropdownMenuContent, TldrawUiDropdownMenuGroup, TldrawUiDropdownMenuIndicator, TldrawUiDropdownMenuItem, TldrawUiDropdownMenuRoot, TldrawUiDropdownMenuSub, TldrawUiDropdownMenuSubContent, TldrawUiDropdownMenuSubTrigger, TldrawUiDropdownMenuTrigger, TldrawUiEventsProvider, TldrawUiIcon, TldrawUiKbd, TldrawUiMenuActionCheckboxItem, TldrawUiMenuActionItem, TldrawUiMenuCheckboxItem, TldrawUiMenuContextProvider, TldrawUiMenuGroup, TldrawUiMenuItem, TldrawUiMenuSubmenu, TldrawUiMenuToolItem, TldrawUiTranslationProvider, ToolShortcuts, UiTooltip, actionKeyMap, buildShortcutIndex, debugStatsOpen, getDefaultTranslationLocale, getGeoIconBox, groupKeyboardShortcuts, isRtlLanguage, kbdToKeys, normalizeKbd, placeNear, toolKeyMap, useActionShortcuts, useActionState, useAnchoredPosition, useAvailableTranslationLocales, useBoundActionShortcuts, useBoundToolShortcuts, useCanApplySelectionAction, useCanRedo, useCanUndo, useCurrentTranslation, useDialogs, useDirection, useDismissable, useHasLockedShapes, useIsDarkMode, useIsGridMode, useKeyboardShortcuts, useMaybeCurrentTranslation, useMenuKeyboard, useMsg, useReadonly, useRelevantStyles, useTldrawUiMenuContext, useToolShortcuts, useTranslation, useUiEvents, useUnlockedSelectedShapesCount };
1776
+ //# sourceMappingURL=chunk-YMUR46N6.js.map
1777
+ //# sourceMappingURL=chunk-YMUR46N6.js.map