@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.
- package/ARCHITECTURE.md +8 -2
- package/MIGRATION.md +48 -22
- package/README.md +27 -2
- package/UI.md +24 -2
- package/dist/{chunk-TWCVRVK2.js → chunk-3F2HKY37.js} +3 -3
- package/dist/{chunk-TWCVRVK2.js.map → chunk-3F2HKY37.js.map} +1 -1
- package/dist/{chunk-DMCI6V5Y.js → chunk-HRUFJLK5.js} +3 -3
- package/dist/{chunk-DMCI6V5Y.js.map → chunk-HRUFJLK5.js.map} +1 -1
- package/dist/{chunk-OCYJAMXT.js → chunk-S7FPGQAA.js} +22 -3
- package/dist/chunk-S7FPGQAA.js.map +1 -0
- package/dist/{chunk-QDUQZXE4.js → chunk-YMUR46N6.js} +456 -19
- package/dist/chunk-YMUR46N6.js.map +1 -0
- package/dist/{export-VQGOVONC.js → export-UA6X35ZW.js} +4 -4
- package/dist/{export-VQGOVONC.js.map → export-UA6X35ZW.js.map} +1 -1
- package/dist/index.d.ts +302 -46
- package/dist/index.js +435 -416
- package/dist/index.js.map +1 -1
- package/dist/{ui-4C5V5GYT.css → mocanvas.css} +73 -241
- package/dist/mocanvas.css.map +1 -0
- package/dist/panel-shortcuts-4GIFCQQR.js +4 -0
- package/dist/{panel-shortcuts-R2PDTDUN.js.map → panel-shortcuts-4GIFCQQR.js.map} +1 -1
- package/dist/urlContent-WYYVQYYN.js +4 -0
- package/dist/{urlContent-GC44EU52.js.map → urlContent-WYYVQYYN.js.map} +1 -1
- package/package.json +8 -4
- package/dist/chunk-OCYJAMXT.js.map +0 -1
- package/dist/chunk-QDUQZXE4.js.map +0 -1
- package/dist/panel-shortcuts-R2PDTDUN.js +0 -4
- package/dist/urlContent-GC44EU52.js +0 -4
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
1313
|
-
|
|
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__ */
|
|
1316
|
-
|
|
1317
|
-
|
|
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-
|
|
1340
|
-
//# sourceMappingURL=chunk-
|
|
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
|