@oh-just-another/state 0.57.0
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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/actions/actionClipboard.d.ts +6 -0
- package/dist/actions/actionClipboard.d.ts.map +1 -0
- package/dist/actions/actionClipboard.js +26 -0
- package/dist/actions/actionClipboard.js.map +1 -0
- package/dist/actions/actionGrouping.d.ts +5 -0
- package/dist/actions/actionGrouping.d.ts.map +1 -0
- package/dist/actions/actionGrouping.js +26 -0
- package/dist/actions/actionGrouping.js.map +1 -0
- package/dist/actions/actionHistory.d.ts +5 -0
- package/dist/actions/actionHistory.d.ts.map +1 -0
- package/dist/actions/actionHistory.js +25 -0
- package/dist/actions/actionHistory.js.map +1 -0
- package/dist/actions/actionKeyboard.d.ts +3 -0
- package/dist/actions/actionKeyboard.d.ts.map +1 -0
- package/dist/actions/actionKeyboard.js +130 -0
- package/dist/actions/actionKeyboard.js.map +1 -0
- package/dist/actions/actionLayout.d.ts +3 -0
- package/dist/actions/actionLayout.d.ts.map +1 -0
- package/dist/actions/actionLayout.js +65 -0
- package/dist/actions/actionLayout.js.map +1 -0
- package/dist/actions/actionMode.d.ts +13 -0
- package/dist/actions/actionMode.d.ts.map +1 -0
- package/dist/actions/actionMode.js +116 -0
- package/dist/actions/actionMode.js.map +1 -0
- package/dist/actions/actionSelection.d.ts +9 -0
- package/dist/actions/actionSelection.d.ts.map +1 -0
- package/dist/actions/actionSelection.js +57 -0
- package/dist/actions/actionSelection.js.map +1 -0
- package/dist/actions/actionView.d.ts +7 -0
- package/dist/actions/actionView.d.ts.map +1 -0
- package/dist/actions/actionView.js +14 -0
- package/dist/actions/actionView.js.map +1 -0
- package/dist/actions/actionZOrder.d.ts +7 -0
- package/dist/actions/actionZOrder.d.ts.map +1 -0
- package/dist/actions/actionZOrder.js +42 -0
- package/dist/actions/actionZOrder.js.map +1 -0
- package/dist/actions/actionZoom.d.ts +8 -0
- package/dist/actions/actionZoom.d.ts.map +1 -0
- package/dist/actions/actionZoom.js +61 -0
- package/dist/actions/actionZoom.js.map +1 -0
- package/dist/actions/index.d.ts +12 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/registry.d.ts +68 -0
- package/dist/actions/registry.d.ts.map +1 -0
- package/dist/actions/registry.js +300 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/types.d.ts +103 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +10 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/animation-tick.d.ts +28 -0
- package/dist/animation-tick.d.ts.map +1 -0
- package/dist/animation-tick.js +50 -0
- package/dist/animation-tick.js.map +1 -0
- package/dist/auto-compact.d.ts +27 -0
- package/dist/auto-compact.d.ts.map +1 -0
- package/dist/auto-compact.js +46 -0
- package/dist/auto-compact.js.map +1 -0
- package/dist/auto-layout-scheduler.d.ts +50 -0
- package/dist/auto-layout-scheduler.d.ts.map +1 -0
- package/dist/auto-layout-scheduler.js +94 -0
- package/dist/auto-layout-scheduler.js.map +1 -0
- package/dist/built-in-handlers.d.ts +24 -0
- package/dist/built-in-handlers.d.ts.map +1 -0
- package/dist/built-in-handlers.js +210 -0
- package/dist/built-in-handlers.js.map +1 -0
- package/dist/clipboard.d.ts +51 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +105 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/constants.d.ts +544 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +537 -0
- package/dist/constants.js.map +1 -0
- package/dist/dom-events.d.ts +13 -0
- package/dist/dom-events.d.ts.map +1 -0
- package/dist/dom-events.js +70 -0
- package/dist/dom-events.js.map +1 -0
- package/dist/editor/anchor-points.d.ts +22 -0
- package/dist/editor/anchor-points.d.ts.map +1 -0
- package/dist/editor/anchor-points.js +28 -0
- package/dist/editor/anchor-points.js.map +1 -0
- package/dist/editor/applies/create.d.ts +60 -0
- package/dist/editor/applies/create.d.ts.map +1 -0
- package/dist/editor/applies/create.js +107 -0
- package/dist/editor/applies/create.js.map +1 -0
- package/dist/editor/applies/edge.d.ts +32 -0
- package/dist/editor/applies/edge.d.ts.map +1 -0
- package/dist/editor/applies/edge.js +44 -0
- package/dist/editor/applies/edge.js.map +1 -0
- package/dist/editor/applies/link-move.d.ts +53 -0
- package/dist/editor/applies/link-move.d.ts.map +1 -0
- package/dist/editor/applies/link-move.js +160 -0
- package/dist/editor/applies/link-move.js.map +1 -0
- package/dist/editor/applies/move.d.ts +38 -0
- package/dist/editor/applies/move.d.ts.map +1 -0
- package/dist/editor/applies/move.js +83 -0
- package/dist/editor/applies/move.js.map +1 -0
- package/dist/editor/applies/resize.d.ts +68 -0
- package/dist/editor/applies/resize.d.ts.map +1 -0
- package/dist/editor/applies/resize.js +199 -0
- package/dist/editor/applies/resize.js.map +1 -0
- package/dist/editor/applies/selection.d.ts +34 -0
- package/dist/editor/applies/selection.d.ts.map +1 -0
- package/dist/editor/applies/selection.js +65 -0
- package/dist/editor/applies/selection.js.map +1 -0
- package/dist/editor/applies/snap-grid.d.ts +45 -0
- package/dist/editor/applies/snap-grid.d.ts.map +1 -0
- package/dist/editor/applies/snap-grid.js +90 -0
- package/dist/editor/applies/snap-grid.js.map +1 -0
- package/dist/editor/container-ops.d.ts +74 -0
- package/dist/editor/container-ops.d.ts.map +1 -0
- package/dist/editor/container-ops.js +262 -0
- package/dist/editor/container-ops.js.map +1 -0
- package/dist/editor/event-fanout.d.ts +48 -0
- package/dist/editor/event-fanout.d.ts.map +1 -0
- package/dist/editor/event-fanout.js +53 -0
- package/dist/editor/event-fanout.js.map +1 -0
- package/dist/editor/gesture-tx.d.ts +77 -0
- package/dist/editor/gesture-tx.d.ts.map +1 -0
- package/dist/editor/gesture-tx.js +93 -0
- package/dist/editor/gesture-tx.js.map +1 -0
- package/dist/editor/hit-test.d.ts +178 -0
- package/dist/editor/hit-test.d.ts.map +1 -0
- package/dist/editor/hit-test.js +460 -0
- package/dist/editor/hit-test.js.map +1 -0
- package/dist/editor/long-press.d.ts +34 -0
- package/dist/editor/long-press.d.ts.map +1 -0
- package/dist/editor/long-press.js +58 -0
- package/dist/editor/long-press.js.map +1 -0
- package/dist/editor/pinch.d.ts +37 -0
- package/dist/editor/pinch.d.ts.map +1 -0
- package/dist/editor/pinch.js +90 -0
- package/dist/editor/pinch.js.map +1 -0
- package/dist/editor/pointer-binding.d.ts +18 -0
- package/dist/editor/pointer-binding.d.ts.map +1 -0
- package/dist/editor/pointer-binding.js +1087 -0
- package/dist/editor/pointer-binding.js.map +1 -0
- package/dist/editor/public/annotations.d.ts +54 -0
- package/dist/editor/public/annotations.d.ts.map +1 -0
- package/dist/editor/public/annotations.js +106 -0
- package/dist/editor/public/annotations.js.map +1 -0
- package/dist/editor/public/arrange-group.d.ts +81 -0
- package/dist/editor/public/arrange-group.d.ts.map +1 -0
- package/dist/editor/public/arrange-group.js +193 -0
- package/dist/editor/public/arrange-group.js.map +1 -0
- package/dist/editor/public/brush.d.ts +33 -0
- package/dist/editor/public/brush.d.ts.map +1 -0
- package/dist/editor/public/brush.js +54 -0
- package/dist/editor/public/brush.js.map +1 -0
- package/dist/editor/public/clipboard.d.ts +31 -0
- package/dist/editor/public/clipboard.d.ts.map +1 -0
- package/dist/editor/public/clipboard.js +35 -0
- package/dist/editor/public/clipboard.js.map +1 -0
- package/dist/editor/public/image-insert.d.ts +30 -0
- package/dist/editor/public/image-insert.d.ts.map +1 -0
- package/dist/editor/public/image-insert.js +57 -0
- package/dist/editor/public/image-insert.js.map +1 -0
- package/dist/editor/public/layers.d.ts +56 -0
- package/dist/editor/public/layers.d.ts.map +1 -0
- package/dist/editor/public/layers.js +104 -0
- package/dist/editor/public/layers.js.map +1 -0
- package/dist/editor/public/link.d.ts +25 -0
- package/dist/editor/public/link.d.ts.map +1 -0
- package/dist/editor/public/link.js +74 -0
- package/dist/editor/public/link.js.map +1 -0
- package/dist/editor/public/placement.d.ts +60 -0
- package/dist/editor/public/placement.d.ts.map +1 -0
- package/dist/editor/public/placement.js +111 -0
- package/dist/editor/public/placement.js.map +1 -0
- package/dist/editor/public/selection-ops.d.ts +78 -0
- package/dist/editor/public/selection-ops.d.ts.map +1 -0
- package/dist/editor/public/selection-ops.js +226 -0
- package/dist/editor/public/selection-ops.js.map +1 -0
- package/dist/editor/public/text-edit.d.ts +17 -0
- package/dist/editor/public/text-edit.d.ts.map +1 -0
- package/dist/editor/public/text-edit.js +28 -0
- package/dist/editor/public/text-edit.js.map +1 -0
- package/dist/editor/public/z-order.d.ts +47 -0
- package/dist/editor/public/z-order.d.ts.map +1 -0
- package/dist/editor/public/z-order.js +151 -0
- package/dist/editor/public/z-order.js.map +1 -0
- package/dist/editor/public/zoom-pan.d.ts +44 -0
- package/dist/editor/public/zoom-pan.d.ts.map +1 -0
- package/dist/editor/public/zoom-pan.js +116 -0
- package/dist/editor/public/zoom-pan.js.map +1 -0
- package/dist/editor/render-orchestrator.d.ts +11 -0
- package/dist/editor/render-orchestrator.d.ts.map +1 -0
- package/dist/editor/render-orchestrator.js +473 -0
- package/dist/editor/render-orchestrator.js.map +1 -0
- package/dist/editor/resize-helpers.d.ts +30 -0
- package/dist/editor/resize-helpers.d.ts.map +1 -0
- package/dist/editor/resize-helpers.js +121 -0
- package/dist/editor/resize-helpers.js.map +1 -0
- package/dist/editor/shape-filters.d.ts +11 -0
- package/dist/editor/shape-filters.d.ts.map +1 -0
- package/dist/editor/shape-filters.js +19 -0
- package/dist/editor/shape-filters.js.map +1 -0
- package/dist/editor/shape-traits.d.ts +16 -0
- package/dist/editor/shape-traits.d.ts.map +1 -0
- package/dist/editor/shape-traits.js +29 -0
- package/dist/editor/shape-traits.js.map +1 -0
- package/dist/editor/viewport-helpers.d.ts +24 -0
- package/dist/editor/viewport-helpers.d.ts.map +1 -0
- package/dist/editor/viewport-helpers.js +63 -0
- package/dist/editor/viewport-helpers.js.map +1 -0
- package/dist/editor-events.d.ts +33 -0
- package/dist/editor-events.d.ts.map +1 -0
- package/dist/editor-events.js +2 -0
- package/dist/editor-events.js.map +1 -0
- package/dist/editor.d.ts +1841 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +4803 -0
- package/dist/editor.js.map +1 -0
- package/dist/file-drop.d.ts +99 -0
- package/dist/file-drop.d.ts.map +1 -0
- package/dist/file-drop.js +198 -0
- package/dist/file-drop.js.map +1 -0
- package/dist/frame-helpers.d.ts +30 -0
- package/dist/frame-helpers.d.ts.map +1 -0
- package/dist/frame-helpers.js +105 -0
- package/dist/frame-helpers.js.map +1 -0
- package/dist/group-helpers.d.ts +39 -0
- package/dist/group-helpers.d.ts.map +1 -0
- package/dist/group-helpers.js +116 -0
- package/dist/group-helpers.js.map +1 -0
- package/dist/handle.d.ts +73 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +184 -0
- package/dist/handle.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive.d.ts +21 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +11 -0
- package/dist/interactive.js.map +1 -0
- package/dist/link-selection.d.ts +21 -0
- package/dist/link-selection.d.ts.map +1 -0
- package/dist/link-selection.js +37 -0
- package/dist/link-selection.js.map +1 -0
- package/dist/machine.d.ts +322 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +568 -0
- package/dist/machine.js.map +1 -0
- package/dist/modes.d.ts +24 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +2 -0
- package/dist/modes.js.map +1 -0
- package/dist/overlay.d.ts +262 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +673 -0
- package/dist/overlay.js.map +1 -0
- package/dist/platform.d.ts +70 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +115 -0
- package/dist/platform.js.map +1 -0
- package/dist/selection.d.ts +18 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +31 -0
- package/dist/selection.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { isMac } from "../platform.js";
|
|
2
|
+
import { SEQUENCE_HOTKEY_WINDOW_MS } from "../constants.js";
|
|
3
|
+
import { historyActions } from "./actionHistory.js";
|
|
4
|
+
import { selectionActions } from "./actionSelection.js";
|
|
5
|
+
import { clipboardActions } from "./actionClipboard.js";
|
|
6
|
+
import { zOrderActions } from "./actionZOrder.js";
|
|
7
|
+
import { groupingActions } from "./actionGrouping.js";
|
|
8
|
+
import { zoomActions } from "./actionZoom.js";
|
|
9
|
+
import { modeActions } from "./actionMode.js";
|
|
10
|
+
import { keyboardActions } from "./actionKeyboard.js";
|
|
11
|
+
import { layoutActions } from "./actionLayout.js";
|
|
12
|
+
import { viewActions } from "./actionView.js";
|
|
13
|
+
/**
|
|
14
|
+
* Registry of actions. Hosts construct one (or use the shared
|
|
15
|
+
* `defaultActionRegistry` populated by `registerBuiltinActions`),
|
|
16
|
+
* register custom actions on top, and pass it to the hotkey listener
|
|
17
|
+
* + context menu builder. Order of registration is preserved so
|
|
18
|
+
* `dispatchHotkey` returns the first match.
|
|
19
|
+
*/
|
|
20
|
+
export class ActionRegistry {
|
|
21
|
+
entries = new Map();
|
|
22
|
+
/** Registered keys for ordered iteration on dispatch. */
|
|
23
|
+
order = [];
|
|
24
|
+
/**
|
|
25
|
+
* Rolling buffer of recent plain keypresses (key + event timestamp) for
|
|
26
|
+
* matching multi-key `sequence` actions like `g d`. Pruned to the
|
|
27
|
+
* `SEQUENCE_HOTKEY_WINDOW_MS` window on each feed; cleared on a match or
|
|
28
|
+
* when a modifier combo interrupts the chain.
|
|
29
|
+
*/
|
|
30
|
+
seqBuffer = [];
|
|
31
|
+
register(action) {
|
|
32
|
+
if (this.entries.has(action.id)) {
|
|
33
|
+
throw new Error(`Action already registered: ${action.id}`);
|
|
34
|
+
}
|
|
35
|
+
this.entries.set(action.id, action);
|
|
36
|
+
this.order.push(action.id);
|
|
37
|
+
}
|
|
38
|
+
/** Re-registering the same id overwrites it in place. */
|
|
39
|
+
replace(action) {
|
|
40
|
+
if (this.entries.has(action.id)) {
|
|
41
|
+
this.entries.set(action.id, action);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.register(action);
|
|
45
|
+
}
|
|
46
|
+
unregister(id) {
|
|
47
|
+
if (!this.entries.delete(id))
|
|
48
|
+
return;
|
|
49
|
+
const idx = this.order.indexOf(id);
|
|
50
|
+
if (idx !== -1)
|
|
51
|
+
this.order.splice(idx, 1);
|
|
52
|
+
}
|
|
53
|
+
get(id) {
|
|
54
|
+
return this.entries.get(id);
|
|
55
|
+
}
|
|
56
|
+
getAll() {
|
|
57
|
+
return this.order
|
|
58
|
+
.map((id) => this.entries.get(id))
|
|
59
|
+
.filter((a) => a !== undefined);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Dispatch by id. Returns `true` if an action ran (predicate passed
|
|
63
|
+
* AND perform was called), `false` otherwise.
|
|
64
|
+
*/
|
|
65
|
+
dispatch(id, ctx) {
|
|
66
|
+
const action = this.entries.get(id);
|
|
67
|
+
if (!action)
|
|
68
|
+
return false;
|
|
69
|
+
if (action.predicate && !action.predicate(ctx))
|
|
70
|
+
return false;
|
|
71
|
+
action.perform(ctx);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Match the keyboard event against every registered action's
|
|
76
|
+
* `hotkey` matcher(s) and dispatch the first whose predicate
|
|
77
|
+
* passes. Returns `true` on hit (caller should `preventDefault`),
|
|
78
|
+
* `false` when no action matched.
|
|
79
|
+
*
|
|
80
|
+
* Honours platform convention for `meta`: on macOS the
|
|
81
|
+
* platform's Cmd key (event.metaKey) qualifies; elsewhere it's
|
|
82
|
+
* Ctrl (event.ctrlKey).
|
|
83
|
+
*/
|
|
84
|
+
dispatchHotkey(event, ctx) {
|
|
85
|
+
const fullCtx = { ...ctx, event };
|
|
86
|
+
// 1. Multi-key sequences (g d, …) — checked first so the final key
|
|
87
|
+
// can't also fire a single-key action bound to the same letter.
|
|
88
|
+
if (this.trySequence(event, fullCtx))
|
|
89
|
+
return true;
|
|
90
|
+
for (const id of this.order) {
|
|
91
|
+
const action = this.entries.get(id);
|
|
92
|
+
if (!action)
|
|
93
|
+
continue;
|
|
94
|
+
// 2. Custom keyTest escape hatch (before declarative matchers).
|
|
95
|
+
if (action.keyTest?.(event, fullCtx)) {
|
|
96
|
+
if (action.predicate && !action.predicate(fullCtx))
|
|
97
|
+
continue;
|
|
98
|
+
action.perform(fullCtx);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// 3. Declarative single-key matchers.
|
|
102
|
+
if (!action.hotkey)
|
|
103
|
+
continue;
|
|
104
|
+
const matchers = Array.isArray(action.hotkey)
|
|
105
|
+
? action.hotkey
|
|
106
|
+
: [action.hotkey];
|
|
107
|
+
for (const m of matchers) {
|
|
108
|
+
if (!matchesHotkey(event, m))
|
|
109
|
+
continue;
|
|
110
|
+
if (action.predicate && !action.predicate(fullCtx))
|
|
111
|
+
continue;
|
|
112
|
+
action.perform(fullCtx);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Feed the event into the sequence buffer and dispatch the first action
|
|
120
|
+
* whose `sequence` the buffer's tail now completes (within the time
|
|
121
|
+
* window). Only plain keypresses (single char, no Meta/Ctrl/Alt) chain;
|
|
122
|
+
* any modifier combo clears the buffer so `g`-then-`Cmd+d` can't match.
|
|
123
|
+
*/
|
|
124
|
+
trySequence(event, ctx) {
|
|
125
|
+
const plain = event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
126
|
+
if (!plain) {
|
|
127
|
+
this.seqBuffer = [];
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const t = event.timeStamp;
|
|
131
|
+
this.seqBuffer = this.seqBuffer.filter((e) => t - e.t <= SEQUENCE_HOTKEY_WINDOW_MS);
|
|
132
|
+
// Keep key AND code so the match is layout-independent (a Cyrillic
|
|
133
|
+
// layout's physical G/D still complete a "g d" sequence via `code`).
|
|
134
|
+
this.seqBuffer.push({ key: event.key, code: event.code, t });
|
|
135
|
+
for (const id of this.order) {
|
|
136
|
+
const action = this.entries.get(id);
|
|
137
|
+
if (!action?.sequence || action.sequence.length === 0)
|
|
138
|
+
continue;
|
|
139
|
+
const seq = action.sequence;
|
|
140
|
+
const tail = this.seqBuffer.slice(-seq.length);
|
|
141
|
+
if (tail.length !== seq.length)
|
|
142
|
+
continue;
|
|
143
|
+
if (!tail.every((e, i) => matchKeyOrCodeRaw(e.key, e.code, seq[i] ?? "")))
|
|
144
|
+
continue;
|
|
145
|
+
if (action.predicate && !action.predicate(ctx))
|
|
146
|
+
continue;
|
|
147
|
+
this.seqBuffer = [];
|
|
148
|
+
action.perform(ctx);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const matchesHotkey = (event, m) => {
|
|
155
|
+
if (m.key !== undefined && !matchKeyOrCode(event, m.key))
|
|
156
|
+
return false;
|
|
157
|
+
if (m.code !== undefined && event.code !== m.code)
|
|
158
|
+
return false;
|
|
159
|
+
const want = (flag) => flag === true;
|
|
160
|
+
// Cross-platform meta: macOS uses metaKey, others use ctrlKey.
|
|
161
|
+
const metaPressed = isMac ? event.metaKey : event.ctrlKey;
|
|
162
|
+
if (want(m.meta) !== metaPressed)
|
|
163
|
+
return false;
|
|
164
|
+
if (want(m.shift) !== event.shiftKey)
|
|
165
|
+
return false;
|
|
166
|
+
if (want(m.alt) !== event.altKey)
|
|
167
|
+
return false;
|
|
168
|
+
if (m.ctrl !== undefined && want(m.ctrl) !== event.ctrlKey)
|
|
169
|
+
return false;
|
|
170
|
+
return true;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Hotkey-key match that survives non-Latin keyboard layouts.
|
|
174
|
+
*
|
|
175
|
+
* 1. First try direct `event.key.toLowerCase()` comparison.
|
|
176
|
+
* Latin-letter layouts (US, UK, German, French, Czech, Turkish,
|
|
177
|
+
* Pinyin IME, …) provide the matcher's expected letter through
|
|
178
|
+
* `event.key` even when the underlying physical key is somewhere
|
|
179
|
+
* else, so this is the right comparison for them.
|
|
180
|
+
*
|
|
181
|
+
* 2. If `event.key` IS itself a Latin letter (a-z) and didn't
|
|
182
|
+
* match — bail. The user pressed a *different* Latin letter
|
|
183
|
+
* from the one the matcher wants — e.g. Czech layout swaps
|
|
184
|
+
* physical Y / Z, so pressing Y yields `key:'y'` even though
|
|
185
|
+
* `code:'KeyZ'`; we must NOT silently rewrite that to the Z
|
|
186
|
+
* hotkey.
|
|
187
|
+
*
|
|
188
|
+
* 3. Otherwise (`event.key` is Cyrillic, Greek, Hebrew, Arabic,
|
|
189
|
+
* a CJK glyph, etc) fall back to `event.code` — the
|
|
190
|
+
* physical-key identifier that is layout-invariant. `KeyZ` is
|
|
191
|
+
* always the key in the Latin-Z position no matter what label
|
|
192
|
+
* the OS prints on it.
|
|
193
|
+
*
|
|
194
|
+
* Special keys (Escape, ArrowLeft, Enter, F1, …) match through
|
|
195
|
+
* step 1 because their `event.key` is stable across layouts.
|
|
196
|
+
*
|
|
197
|
+
* Pinyin / Cangjie / Japanese IMEs: Cmd-modified key presses
|
|
198
|
+
* usually deliver the underlying Latin letter through `event.key`
|
|
199
|
+
* (because the IME is bypassed), so they hit step 1. The few that
|
|
200
|
+
* deliver an IME glyph instead are caught by step 3.
|
|
201
|
+
*/
|
|
202
|
+
const matchKeyOrCode = (event, want) => matchKeyOrCodeRaw(event.key, event.code, want);
|
|
203
|
+
/**
|
|
204
|
+
* Layout-independent key match on raw (`key`, `code`) — the core of
|
|
205
|
+
* {@link matchKeyOrCode}, also used by sequence matching where we only kept
|
|
206
|
+
* the key/code, not the whole event. Same rules: prefer `key`, bail on a
|
|
207
|
+
* mismatching Latin letter, else fall back to the physical `code` (so a
|
|
208
|
+
* Cyrillic/Greek/etc layout still triggers a Latin-letter shortcut).
|
|
209
|
+
*/
|
|
210
|
+
const matchKeyOrCodeRaw = (key, code, want) => {
|
|
211
|
+
if (key.toLowerCase() === want.toLowerCase())
|
|
212
|
+
return true;
|
|
213
|
+
// Bail when the user pressed a *different* Latin letter — we'd
|
|
214
|
+
// otherwise hijack layouts that swap physical key positions
|
|
215
|
+
// (Czech Y/Z, French QWERTY-AZERTY, etc).
|
|
216
|
+
if (isLatinChar(key))
|
|
217
|
+
return false;
|
|
218
|
+
const expectedCode = codeForKey(want);
|
|
219
|
+
return expectedCode !== null && code === expectedCode;
|
|
220
|
+
};
|
|
221
|
+
const isLatinChar = (s) => /^[a-z]$/i.test(s);
|
|
222
|
+
/**
|
|
223
|
+
* Map a host-declared `key` literal to the `event.code` we'd see
|
|
224
|
+
* when the same physical key is pressed on any layout. Returns
|
|
225
|
+
* `null` for keys whose `code` we don't (or shouldn't) try to
|
|
226
|
+
* derive — those are matched through `event.key` only.
|
|
227
|
+
*/
|
|
228
|
+
const codeForKey = (key) => {
|
|
229
|
+
if (key.length !== 1)
|
|
230
|
+
return null;
|
|
231
|
+
const c = key.toLowerCase();
|
|
232
|
+
if (c >= "a" && c <= "z")
|
|
233
|
+
return `Key${c.toUpperCase()}`;
|
|
234
|
+
if (c >= "0" && c <= "9")
|
|
235
|
+
return `Digit${c}`;
|
|
236
|
+
// Punctuation that commonly carries a host-declared hotkey
|
|
237
|
+
// (Cmd+[, Cmd+], Cmd+/, Cmd+., Cmd+, Cmd+- Cmd+= Cmd+;).
|
|
238
|
+
switch (c) {
|
|
239
|
+
case "[":
|
|
240
|
+
return "BracketLeft";
|
|
241
|
+
case "]":
|
|
242
|
+
return "BracketRight";
|
|
243
|
+
case ",":
|
|
244
|
+
return "Comma";
|
|
245
|
+
case ".":
|
|
246
|
+
return "Period";
|
|
247
|
+
case "/":
|
|
248
|
+
return "Slash";
|
|
249
|
+
case ";":
|
|
250
|
+
return "Semicolon";
|
|
251
|
+
case "'":
|
|
252
|
+
return "Quote";
|
|
253
|
+
case "`":
|
|
254
|
+
return "Backquote";
|
|
255
|
+
case "-":
|
|
256
|
+
return "Minus";
|
|
257
|
+
case "=":
|
|
258
|
+
return "Equal";
|
|
259
|
+
case "\\":
|
|
260
|
+
return "Backslash";
|
|
261
|
+
default:
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Register the kernel's built-in actions on the given registry.
|
|
267
|
+
* Pure side-effect — call once at host bootstrap (or use
|
|
268
|
+
* `defaultActionRegistry` which has them pre-registered).
|
|
269
|
+
*
|
|
270
|
+
* Covers: undo / redo, clipboard (copy / cut / paste), selection
|
|
271
|
+
* (all / clear / duplicate / delete), z-order (front / back),
|
|
272
|
+
* grouping (group / ungroup), zoom (in / out / reset / fit),
|
|
273
|
+
* mode switching (select / hand / draw-rect / draw-ellipse /
|
|
274
|
+
* draw-edge / brush), tool-lock toggle, cancel. Anything host-
|
|
275
|
+
* specific (Save / Load / Export) is registered by the host on top.
|
|
276
|
+
*/
|
|
277
|
+
export const registerBuiltinActions = (registry) => {
|
|
278
|
+
const allBuiltins = [
|
|
279
|
+
...historyActions,
|
|
280
|
+
...selectionActions,
|
|
281
|
+
...clipboardActions,
|
|
282
|
+
...zOrderActions,
|
|
283
|
+
...groupingActions,
|
|
284
|
+
...zoomActions,
|
|
285
|
+
...modeActions,
|
|
286
|
+
...keyboardActions,
|
|
287
|
+
...layoutActions,
|
|
288
|
+
...viewActions,
|
|
289
|
+
];
|
|
290
|
+
for (const action of allBuiltins) {
|
|
291
|
+
registry.register(action);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Shared registry with built-in actions pre-registered. Hosts can add
|
|
296
|
+
* more on top via `register` or replace existing ones via `replace`.
|
|
297
|
+
*/
|
|
298
|
+
export const defaultActionRegistry = new ActionRegistry();
|
|
299
|
+
registerBuiltinActions(defaultActionRegistry);
|
|
300
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Editor } from "../editor.js";
|
|
2
|
+
/**
|
|
3
|
+
* Action architecture. Every host-facing imperative command (undo, copy,
|
|
4
|
+
* switch-mode, group, …) is wrapped as an `Action` and registered in an
|
|
5
|
+
* `ActionRegistry`. Hotkeys, context menu, and toolbar all dispatch through
|
|
6
|
+
* the same registry, so a host extension that registers a new action gets the
|
|
7
|
+
* keyboard + menu wiring for free if it provides the right metadata.
|
|
8
|
+
*/
|
|
9
|
+
export interface ActionContext {
|
|
10
|
+
/** The live editor — every action operates against it. */
|
|
11
|
+
readonly editor: Editor;
|
|
12
|
+
/**
|
|
13
|
+
* The originating event when the action was dispatched via
|
|
14
|
+
* `dispatchHotkey`. Hosts can read modifier state from it; absent
|
|
15
|
+
* when the action was triggered from UI (button / menu).
|
|
16
|
+
*/
|
|
17
|
+
readonly event?: KeyboardEvent | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Keyboard descriptor — matched against a `KeyboardEvent` in
|
|
21
|
+
* `dispatchHotkey`. `key` is case-insensitive against `event.key`;
|
|
22
|
+
* `code` uses `event.code` (layout-independent — handy for non-latin
|
|
23
|
+
* keyboards). At least one of `key` / `code` must be set.
|
|
24
|
+
*
|
|
25
|
+
* `meta` defaults to `false` when omitted. Use the same modifier
|
|
26
|
+
* idiom on macOS and Linux/Windows — `meta: true` matches Cmd on
|
|
27
|
+
* macOS and Ctrl on the others. To target Ctrl specifically (e.g.
|
|
28
|
+
* for the Linux-only convention) set `ctrl: true` and `meta: false`.
|
|
29
|
+
*/
|
|
30
|
+
export interface HotkeyMatcher {
|
|
31
|
+
readonly key?: string;
|
|
32
|
+
readonly code?: string;
|
|
33
|
+
/** Cmd on macOS, Ctrl on Linux/Windows. */
|
|
34
|
+
readonly meta?: boolean;
|
|
35
|
+
readonly shift?: boolean;
|
|
36
|
+
readonly alt?: boolean;
|
|
37
|
+
readonly ctrl?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Category groups Actions in the help dialog / menus.
|
|
41
|
+
*/
|
|
42
|
+
export type ActionCategory = "history" | "selection" | "clipboard" | "z-order" | "grouping" | "zoom" | "mode" | "layout" | "edit" | "other";
|
|
43
|
+
export interface Action {
|
|
44
|
+
/** Stable identifier — used by context menu / hotkey routing. */
|
|
45
|
+
readonly id: string;
|
|
46
|
+
/** Display label for menus / tooltips. */
|
|
47
|
+
readonly label?: string;
|
|
48
|
+
/** Category for grouping in help dialog / menus. */
|
|
49
|
+
readonly category?: ActionCategory;
|
|
50
|
+
/** Hotkey trigger; multiple matchers allowed (e.g. ⌘Z and Ctrl+Z). */
|
|
51
|
+
readonly hotkey?: HotkeyMatcher | readonly HotkeyMatcher[];
|
|
52
|
+
/**
|
|
53
|
+
* Multi-key SEQUENCE trigger — the keys (plain, no modifiers) must be
|
|
54
|
+
* pressed in order within `SEQUENCE_HOTKEY_WINDOW_MS` (e.g.
|
|
55
|
+
* `["g", "d"]` to toggle the debug panel, vim/Gmail style). Matched
|
|
56
|
+
* case-insensitively against `event.key`. Sequences are checked before
|
|
57
|
+
* single-key `hotkey` / `keyTest` so the final key can't double-fire.
|
|
58
|
+
*/
|
|
59
|
+
readonly sequence?: readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* Escape hatch for hotkeys the declarative `hotkey` / `sequence` can't
|
|
62
|
+
* express (modifier combos with custom logic, contextual triggers).
|
|
63
|
+
* Returns `true` to claim the event. Checked after `sequence` and
|
|
64
|
+
* before the declarative `hotkey` matchers.
|
|
65
|
+
*/
|
|
66
|
+
readonly keyTest?: (event: KeyboardEvent, ctx: ActionContext) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* `true` when the action makes sense in the current state — used
|
|
69
|
+
* by menus to gate visibility AND by `dispatch` to short-circuit
|
|
70
|
+
* a no-op trigger. Defaults to "always true" when omitted.
|
|
71
|
+
*/
|
|
72
|
+
readonly predicate?: (ctx: ActionContext) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Toggle state for `uiKind: "toggle"` actions (e.g. is the debug panel
|
|
75
|
+
* open, is snap on). Read by the UI to render the pressed/checked state.
|
|
76
|
+
* Pure read of editor / host state.
|
|
77
|
+
*/
|
|
78
|
+
readonly checked?: (ctx: ActionContext) => boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Serializable UI metadata so `react-ui` can render the action without
|
|
81
|
+
* the kernel importing React. `iconId` keys an icon registry on the UI
|
|
82
|
+
* side; `uiKind` picks the control shape.
|
|
83
|
+
*/
|
|
84
|
+
readonly iconId?: string;
|
|
85
|
+
readonly uiKind?: "button" | "toggle" | "menu-item";
|
|
86
|
+
/**
|
|
87
|
+
* When `true`, the action stays available in read-only / view mode.
|
|
88
|
+
* Default (absent) → hidden in view mode.
|
|
89
|
+
*/
|
|
90
|
+
readonly viewMode?: boolean;
|
|
91
|
+
/** The actual work — receives the editor and (optionally) the event. */
|
|
92
|
+
readonly perform: (ctx: ActionContext) => void;
|
|
93
|
+
}
|
|
94
|
+
/** Shared predicate — "element selection is non-empty". */
|
|
95
|
+
export declare const hasSelection: (ctx: ActionContext) => boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Predicate — "anything deletable is selected": elements OR one-or-more
|
|
98
|
+
* links. Links live in a parallel selection set (`editor.selectedLinks`), so
|
|
99
|
+
* a link-only (incl. multi-link) selection must still enable Delete/Backspace.
|
|
100
|
+
* `deleteSelected()` removes every selected link; this just opens the gate.
|
|
101
|
+
*/
|
|
102
|
+
export declare const hasSelectionOrLink: (ctx: ActionContext) => boolean;
|
|
103
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/actions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,WAAW,GACX,WAAW,GACX,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,aAAa,EAAE,CAAC;IAC3D;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD;AAED,2DAA2D;AAC3D,eAAO,MAAM,YAAY,GAAI,KAAK,aAAa,KAAG,OAAwC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,aAAa,KAAG,OACY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared predicate — "element selection is non-empty". */
|
|
2
|
+
export const hasSelection = (ctx) => ctx.editor.selection.size > 0;
|
|
3
|
+
/**
|
|
4
|
+
* Predicate — "anything deletable is selected": elements OR one-or-more
|
|
5
|
+
* links. Links live in a parallel selection set (`editor.selectedLinks`), so
|
|
6
|
+
* a link-only (incl. multi-link) selection must still enable Delete/Backspace.
|
|
7
|
+
* `deleteSelected()` removes every selected link; this just opens the gate.
|
|
8
|
+
*/
|
|
9
|
+
export const hasSelectionOrLink = (ctx) => ctx.editor.selection.size > 0 || ctx.editor.selectedLinks.size > 0;
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/actions/types.ts"],"names":[],"mappings":"AA4GA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAkB,EAAW,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC;AAE3F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAkB,EAAW,EAAE,CAChE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny requestAnimationFrame loop manager. Used by the editor for
|
|
3
|
+
* animated content (GIFs) that needs a re-render every frame even when
|
|
4
|
+
* the scene reference hasn't changed.
|
|
5
|
+
*
|
|
6
|
+
* Self-terminating: each tick consults `isAnimated()`; when it
|
|
7
|
+
* returns false the loop stops and `start()` is a no-op until
|
|
8
|
+
* called again. SSR-safe — no-op when `requestAnimationFrame` is
|
|
9
|
+
* not on `globalThis`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AnimationTick {
|
|
12
|
+
private frameId;
|
|
13
|
+
private readonly isAnimated;
|
|
14
|
+
private readonly onTick;
|
|
15
|
+
constructor(opts: {
|
|
16
|
+
readonly isAnimated: () => boolean;
|
|
17
|
+
readonly onTick: () => void;
|
|
18
|
+
});
|
|
19
|
+
/** Begin the rAF loop. No-op when already running or when
|
|
20
|
+
* `requestAnimationFrame` is unavailable (Node).
|
|
21
|
+
*/
|
|
22
|
+
start(): void;
|
|
23
|
+
/** Cancel any pending frame. Safe to call from dispose paths. */
|
|
24
|
+
stop(): void;
|
|
25
|
+
/** True while the loop is actively scheduled. */
|
|
26
|
+
get running(): boolean;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=animation-tick.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation-tick.d.ts","sourceRoot":"","sources":["../src/animation-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,IAAI,EAAE;QAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE;IAKrF;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb,iEAAiE;IACjE,IAAI,IAAI,IAAI;IAMZ,iDAAiD;IACjD,IAAI,OAAO,IAAI,OAAO,CAErB;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny requestAnimationFrame loop manager. Used by the editor for
|
|
3
|
+
* animated content (GIFs) that needs a re-render every frame even when
|
|
4
|
+
* the scene reference hasn't changed.
|
|
5
|
+
*
|
|
6
|
+
* Self-terminating: each tick consults `isAnimated()`; when it
|
|
7
|
+
* returns false the loop stops and `start()` is a no-op until
|
|
8
|
+
* called again. SSR-safe — no-op when `requestAnimationFrame` is
|
|
9
|
+
* not on `globalThis`.
|
|
10
|
+
*/
|
|
11
|
+
export class AnimationTick {
|
|
12
|
+
frameId = null;
|
|
13
|
+
isAnimated;
|
|
14
|
+
onTick;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.isAnimated = opts.isAnimated;
|
|
17
|
+
this.onTick = opts.onTick;
|
|
18
|
+
}
|
|
19
|
+
/** Begin the rAF loop. No-op when already running or when
|
|
20
|
+
* `requestAnimationFrame` is unavailable (Node).
|
|
21
|
+
*/
|
|
22
|
+
start() {
|
|
23
|
+
if (this.frameId !== null)
|
|
24
|
+
return;
|
|
25
|
+
if (typeof requestAnimationFrame === "undefined")
|
|
26
|
+
return;
|
|
27
|
+
const loop = () => {
|
|
28
|
+
if (!this.isAnimated()) {
|
|
29
|
+
this.frameId = null;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.onTick();
|
|
33
|
+
this.frameId = requestAnimationFrame(loop);
|
|
34
|
+
};
|
|
35
|
+
this.frameId = requestAnimationFrame(loop);
|
|
36
|
+
}
|
|
37
|
+
/** Cancel any pending frame. Safe to call from dispose paths. */
|
|
38
|
+
stop() {
|
|
39
|
+
if (this.frameId === null)
|
|
40
|
+
return;
|
|
41
|
+
if (typeof cancelAnimationFrame !== "undefined")
|
|
42
|
+
cancelAnimationFrame(this.frameId);
|
|
43
|
+
this.frameId = null;
|
|
44
|
+
}
|
|
45
|
+
/** True while the loop is actively scheduled. */
|
|
46
|
+
get running() {
|
|
47
|
+
return this.frameId !== null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=animation-tick.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation-tick.js","sourceRoot":"","sources":["../src/animation-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAa;IAChB,OAAO,GAAkB,IAAI,CAAC;IACrB,UAAU,CAAgB;IAC1B,MAAM,CAAa;IAEpC,YAAY,IAAyE;QACnF,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,OAAO,qBAAqB,KAAK,WAAW;YAAE,OAAO;QACzD,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,iEAAiE;IACjE,IAAI;QACF,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,OAAO,oBAAoB,KAAK,WAAW;YAAE,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,iDAAiD;IACjD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LayerId } from "@oh-just-another/types";
|
|
2
|
+
import type { Scene } from "@oh-just-another/scene";
|
|
3
|
+
/**
|
|
4
|
+
* Microtask-scheduled rebalancer for fractional `order` keys. When any
|
|
5
|
+
* shape or edge's order string grows past AUTO_COMPACT_THRESHOLD, a
|
|
6
|
+
* single compaction pass is scheduled per mutation burst so users never
|
|
7
|
+
* see the long-key state.
|
|
8
|
+
*
|
|
9
|
+
* The scheduler keeps a `pending` flag to coalesce bursts into one
|
|
10
|
+
* microtask; `runCheck()` is what the microtask invokes (and what tests
|
|
11
|
+
* call directly when they want determinism).
|
|
12
|
+
*/
|
|
13
|
+
export declare class AutoCompactScheduler {
|
|
14
|
+
private pending;
|
|
15
|
+
private readonly getScene;
|
|
16
|
+
private readonly compact;
|
|
17
|
+
constructor(opts: {
|
|
18
|
+
readonly getScene: () => Scene;
|
|
19
|
+
/** Caller-supplied compaction action — usually `editor.compactLayerZOrder(layerId, {recordHistory:false})`. */
|
|
20
|
+
readonly compact: (layerId: LayerId) => void;
|
|
21
|
+
});
|
|
22
|
+
/** Queue a check for the next microtask. Coalesces bursts. */
|
|
23
|
+
schedule(): void;
|
|
24
|
+
/** Run the actual check + compact immediately. Used by tests. */
|
|
25
|
+
runCheck(): void;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=auto-compact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-compact.d.ts","sourceRoot":"","sources":["../src/auto-compact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAGpD;;;;;;;;;GASG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAEzC,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC;QAC/B,+GAA+G;QAC/G,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KAC9C;IAKD,8DAA8D;IAC9D,QAAQ,IAAI,IAAI;IAShB,iEAAiE;IACjE,QAAQ,IAAI,IAAI;CAWjB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AUTO_COMPACT_THRESHOLD } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Microtask-scheduled rebalancer for fractional `order` keys. When any
|
|
4
|
+
* shape or edge's order string grows past AUTO_COMPACT_THRESHOLD, a
|
|
5
|
+
* single compaction pass is scheduled per mutation burst so users never
|
|
6
|
+
* see the long-key state.
|
|
7
|
+
*
|
|
8
|
+
* The scheduler keeps a `pending` flag to coalesce bursts into one
|
|
9
|
+
* microtask; `runCheck()` is what the microtask invokes (and what tests
|
|
10
|
+
* call directly when they want determinism).
|
|
11
|
+
*/
|
|
12
|
+
export class AutoCompactScheduler {
|
|
13
|
+
pending = false;
|
|
14
|
+
getScene;
|
|
15
|
+
compact;
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.getScene = opts.getScene;
|
|
18
|
+
this.compact = opts.compact;
|
|
19
|
+
}
|
|
20
|
+
/** Queue a check for the next microtask. Coalesces bursts. */
|
|
21
|
+
schedule() {
|
|
22
|
+
if (this.pending)
|
|
23
|
+
return;
|
|
24
|
+
this.pending = true;
|
|
25
|
+
queueMicrotask(() => {
|
|
26
|
+
this.pending = false;
|
|
27
|
+
this.runCheck();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Run the actual check + compact immediately. Used by tests. */
|
|
31
|
+
runCheck() {
|
|
32
|
+
const scene = this.getScene();
|
|
33
|
+
const layersToCompact = new Set();
|
|
34
|
+
for (const s of scene.elements.values()) {
|
|
35
|
+
if (s.order.length > AUTO_COMPACT_THRESHOLD)
|
|
36
|
+
layersToCompact.add(s.layerId);
|
|
37
|
+
}
|
|
38
|
+
for (const e of scene.links.values()) {
|
|
39
|
+
if (e.order.length > AUTO_COMPACT_THRESHOLD)
|
|
40
|
+
layersToCompact.add(e.layerId);
|
|
41
|
+
}
|
|
42
|
+
for (const lid of layersToCompact)
|
|
43
|
+
this.compact(lid);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=auto-compact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-compact.js","sourceRoot":"","sources":["../src/auto-compact.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAoB;IACvB,OAAO,GAAG,KAAK,CAAC;IACP,QAAQ,CAAc;IACtB,OAAO,CAA6B;IAErD,YAAY,IAIX;QACC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,8DAA8D;IAC9D,QAAQ;QACN,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iEAAiE;IACjE,QAAQ;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,eAAe,GAAG,IAAI,GAAG,EAAW,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,sBAAsB;gBAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,sBAAsB;gBAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,eAAe;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ElementId } from "@oh-just-another/types";
|
|
2
|
+
import { apply, type Patch, type Scene } from "@oh-just-another/scene";
|
|
3
|
+
/**
|
|
4
|
+
* Microtask-coalesced re-runner for shapes carrying
|
|
5
|
+
* `metadata.autoLayout`. The editor calls `schedule()` from `notify()`;
|
|
6
|
+
* the check walks every auto-layout parent, compares its children-set
|
|
7
|
+
* fingerprint against the previous run, and re-runs the layout when the
|
|
8
|
+
* set changed.
|
|
9
|
+
*
|
|
10
|
+
* Pure position edits inside an existing children set are ignored
|
|
11
|
+
* (fingerprint is sorted-ids only), so a manual nudge of a child
|
|
12
|
+
* isn't snapped back. Add / remove / reparent does trigger.
|
|
13
|
+
*/
|
|
14
|
+
export declare class AutoLayoutScheduler {
|
|
15
|
+
private readonly opts;
|
|
16
|
+
private pending;
|
|
17
|
+
private readonly signatures;
|
|
18
|
+
constructor(opts: AutoLayoutSchedulerOptions);
|
|
19
|
+
/** Queue a check for the next microtask. Coalesces bursts. */
|
|
20
|
+
schedule(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Recompute and store the signature for a parent, used after the
|
|
23
|
+
* editor's public `runLayout(parentId)` to prevent the next
|
|
24
|
+
* scheduled check from firing a redundant second run.
|
|
25
|
+
*/
|
|
26
|
+
resetSignature(parentId: ElementId): void;
|
|
27
|
+
/** Run the actual check + relayout immediately. Used by tests. */
|
|
28
|
+
runCheck(): void;
|
|
29
|
+
private signatureFor;
|
|
30
|
+
}
|
|
31
|
+
export interface AutoLayoutSchedulerOptions {
|
|
32
|
+
readonly getScene: () => Scene;
|
|
33
|
+
/**
|
|
34
|
+
* Apply the layout patch — implementation knows whether to join
|
|
35
|
+
* a running gesture transaction or push straight to history.
|
|
36
|
+
*/
|
|
37
|
+
readonly applyPatch: (patch: Patch) => void;
|
|
38
|
+
/** Editor-side hook to grow the container after the relayout. */
|
|
39
|
+
readonly growContainer: (parentId: ElementId, childId: ElementId) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Called once at the end of a check that produced at least one
|
|
42
|
+
* mutation — editor renders + fires listeners (intentionally NOT
|
|
43
|
+
* `notify()` because that would re-schedule the check and risk
|
|
44
|
+
* a microtask loop).
|
|
45
|
+
*/
|
|
46
|
+
readonly onMutated: () => void;
|
|
47
|
+
}
|
|
48
|
+
export type { Patch, Scene };
|
|
49
|
+
export { apply };
|
|
50
|
+
//# sourceMappingURL=auto-layout-scheduler.d.ts.map
|