@weasel-js/core 0.5.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 +381 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/DrawCommand-Dl0bXNfS.d.ts +500 -0
- package/dist/chunk-775XXAHR.js +216 -0
- package/dist/chunk-775XXAHR.js.map +1 -0
- package/dist/chunk-7V6JEOXE.js +27754 -0
- package/dist/chunk-7V6JEOXE.js.map +1 -0
- package/dist/chunk-AM6ARSPN.js +517 -0
- package/dist/chunk-AM6ARSPN.js.map +1 -0
- package/dist/chunk-BGGZ4CVF.js +248 -0
- package/dist/chunk-BGGZ4CVF.js.map +1 -0
- package/dist/chunk-BHVYVFGV.js +29 -0
- package/dist/chunk-BHVYVFGV.js.map +1 -0
- package/dist/chunk-CQNKCG34.js +831 -0
- package/dist/chunk-CQNKCG34.js.map +1 -0
- package/dist/chunk-GVCNT7UH.js +47 -0
- package/dist/chunk-GVCNT7UH.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-UGFFCMQP.js +28 -0
- package/dist/chunk-UGFFCMQP.js.map +1 -0
- package/dist/chunk-VOVKONXA.js +103 -0
- package/dist/chunk-VOVKONXA.js.map +1 -0
- package/dist/chunk-Y52N27PF.js +39 -0
- package/dist/chunk-Y52N27PF.js.map +1 -0
- package/dist/clipboard.d.ts +91 -0
- package/dist/clipboard.js +6 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/clone.d.ts +8 -0
- package/dist/clone.js +6 -0
- package/dist/clone.js.map +1 -0
- package/dist/fitViewToBounds-evGsnR8Q.d.ts +62 -0
- package/dist/grid-Cf87knjU.d.ts +153 -0
- package/dist/index-DZBYMsHI.d.ts +1555 -0
- package/dist/index.css +121 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +10200 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/insert.d.ts +33 -0
- package/dist/insert.js +81 -0
- package/dist/insert.js.map +1 -0
- package/dist/move.d.ts +69 -0
- package/dist/move.js +145 -0
- package/dist/move.js.map +1 -0
- package/dist/options-BPPBWMa7.d.ts +64 -0
- package/dist/patterns-builtin.d.ts +55 -0
- package/dist/patterns-builtin.js +100 -0
- package/dist/patterns-builtin.js.map +1 -0
- package/dist/pointSnapToGrid-D7s7QmOF.d.ts +185 -0
- package/dist/registerFont-CP-wCsrz.d.ts +109 -0
- package/dist/registerTexture-BzHTLhD9.d.ts +25 -0
- package/dist/renderer.css +121 -0
- package/dist/renderer.css.map +1 -0
- package/dist/renderer.d.ts +376 -0
- package/dist/renderer.js +13 -0
- package/dist/renderer.js.map +1 -0
- package/dist/resize.d.ts +78 -0
- package/dist/resize.js +5 -0
- package/dist/resize.js.map +1 -0
- package/dist/routing.css +35 -0
- package/dist/routing.css.map +1 -0
- package/dist/routing.d.ts +14 -0
- package/dist/routing.js +4 -0
- package/dist/routing.js.map +1 -0
- package/dist/types-B6MMiodD.d.ts +59 -0
- package/dist/types-BJ8_cyT7.d.ts +130 -0
- package/dist/types-B_-khFM0.d.ts +331 -0
- package/dist/types-BjUi2vA-.d.ts +355 -0
- package/dist/types-Cpb4hii1.d.ts +445 -0
- package/dist/types-D2tTKEU0.d.ts +18 -0
- package/dist/view-DSQgxBJB.d.ts +63 -0
- package/package.json +96 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { __export } from './chunk-PZ5AY32C.js';
|
|
2
|
+
import { parseRoute, mods, isKnownGestureName, getGestureDescriptor, formatRoute, formatPhaseAtom, describeRouteParts, describeRoute, collapseShiftPairs, canonicalModifiers, ROUTE_TERMS, ROUTE_FIELD_DEFINITIONS, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, GESTURE_DESCRIPTORS, modifierComboToParsed } from '@weasel-js/gestures';
|
|
3
|
+
export { GESTURE_DESCRIPTORS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, mods, parseRoute } from '@weasel-js/gestures';
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/tools/routing/index.ts
|
|
8
|
+
var routing_exports = {};
|
|
9
|
+
__export(routing_exports, {
|
|
10
|
+
GESTURE_DESCRIPTORS: () => GESTURE_DESCRIPTORS,
|
|
11
|
+
RESERVED_ID_NAMES: () => RESERVED_ID_NAMES,
|
|
12
|
+
RESERVED_ID_PREFIXES: () => RESERVED_ID_PREFIXES,
|
|
13
|
+
ROUTE_FIELD_DEFINITIONS: () => ROUTE_FIELD_DEFINITIONS,
|
|
14
|
+
ROUTE_TERMS: () => ROUTE_TERMS,
|
|
15
|
+
ToolDebugOverlay: () => ToolDebugOverlay,
|
|
16
|
+
apply: () => apply,
|
|
17
|
+
begin: () => begin,
|
|
18
|
+
buildActionRegistry: () => buildActionRegistry,
|
|
19
|
+
cancel: () => cancel,
|
|
20
|
+
canonicalModifiers: () => canonicalModifiers,
|
|
21
|
+
claim: () => claim,
|
|
22
|
+
collapseShiftPairs: () => collapseShiftPairs,
|
|
23
|
+
commit: () => commit,
|
|
24
|
+
defineTool: () => defineTool,
|
|
25
|
+
defineViewportTool: () => defineViewportTool,
|
|
26
|
+
describeRoute: () => describeRoute,
|
|
27
|
+
describeRouteParts: () => describeRouteParts,
|
|
28
|
+
findConflicts: () => findConflicts,
|
|
29
|
+
formatPhaseAtom: () => formatPhaseAtom,
|
|
30
|
+
formatRoute: () => formatRoute,
|
|
31
|
+
formatRouteResolved: () => formatRouteResolved,
|
|
32
|
+
forwardActionTo: () => forwardActionTo,
|
|
33
|
+
getGestureDescriptor: () => getGestureDescriptor,
|
|
34
|
+
hold: () => hold,
|
|
35
|
+
isKnownGestureName: () => isKnownGestureName,
|
|
36
|
+
mods: () => mods,
|
|
37
|
+
none: () => none,
|
|
38
|
+
parseRoute: () => parseRoute,
|
|
39
|
+
resolveRoute: () => resolveRoute,
|
|
40
|
+
useToolDebugInfo: () => useToolDebugInfo
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/tools/routing/result.ts
|
|
44
|
+
function apply(ops, label) {
|
|
45
|
+
return label !== void 0 ? { kind: "apply", ops, label } : { kind: "apply", ops };
|
|
46
|
+
}
|
|
47
|
+
function begin(spec) {
|
|
48
|
+
return { kind: "begin", spec };
|
|
49
|
+
}
|
|
50
|
+
function hold(scratch) {
|
|
51
|
+
return { kind: "hold", scratch };
|
|
52
|
+
}
|
|
53
|
+
function commit(ops, label) {
|
|
54
|
+
return label !== void 0 ? { kind: "commit", ops, label } : { kind: "commit", ops };
|
|
55
|
+
}
|
|
56
|
+
function cancel() {
|
|
57
|
+
return { kind: "cancel" };
|
|
58
|
+
}
|
|
59
|
+
function claim() {
|
|
60
|
+
return { kind: "claim" };
|
|
61
|
+
}
|
|
62
|
+
function none() {
|
|
63
|
+
return { kind: "none" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/tools/routing/lookup.ts
|
|
67
|
+
function resolveRoute(table, hit, modifiers) {
|
|
68
|
+
const candidateKeys = buildCandidateKeys(hit);
|
|
69
|
+
for (const key of candidateKeys) {
|
|
70
|
+
const entry = table[key];
|
|
71
|
+
if (entry == null) continue;
|
|
72
|
+
const resolved = resolveEntry(entry, modifiers);
|
|
73
|
+
if (resolved) return { action: resolved, matchedKey: key };
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function buildCandidateKeys(hit) {
|
|
78
|
+
if (hit.category === "empty") return ["empty", "*"];
|
|
79
|
+
const kind = hit.kind;
|
|
80
|
+
const colon = kind.indexOf(":");
|
|
81
|
+
if (colon < 0) {
|
|
82
|
+
return [kind, "*"];
|
|
83
|
+
}
|
|
84
|
+
const baseKind = kind.substring(0, colon);
|
|
85
|
+
const subKind = kind.substring(colon + 1);
|
|
86
|
+
return [kind, `*:${subKind}`, baseKind, "*"];
|
|
87
|
+
}
|
|
88
|
+
function resolveEntry(entry, modifiers) {
|
|
89
|
+
if (typeof entry === "function") return entry;
|
|
90
|
+
const subTable = entry;
|
|
91
|
+
const wanted = modifiersToKey(modifiers);
|
|
92
|
+
return subTable[wanted] ?? subTable.default;
|
|
93
|
+
}
|
|
94
|
+
function modifiersToKey(modifiers) {
|
|
95
|
+
const active = [];
|
|
96
|
+
if (modifiers.meta || modifiers.ctrl) active.push("mod");
|
|
97
|
+
if (modifiers.shift) active.push("shift");
|
|
98
|
+
if (modifiers.alt) active.push("alt");
|
|
99
|
+
return mods(...active);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/tools/routing/defineTool.ts
|
|
103
|
+
function validateId(id, kind) {
|
|
104
|
+
if (id.length === 0) {
|
|
105
|
+
throw new Error(`weasel: ${kind} id may not be empty`);
|
|
106
|
+
}
|
|
107
|
+
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (RESERVED_ID_NAMES.has(id)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function defineTool(def) {
|
|
119
|
+
validateId(def.id, "tool");
|
|
120
|
+
let activeSpec = null;
|
|
121
|
+
const phaseOf = (ctx) => {
|
|
122
|
+
return ctx.scratch != null && def.engaged ? def.engaged : def.initial;
|
|
123
|
+
};
|
|
124
|
+
const phaseNameOf = (ctx) => {
|
|
125
|
+
return ctx.scratch != null && def.engaged ? "engaged" : "initial";
|
|
126
|
+
};
|
|
127
|
+
const modifiersToCanonicalKey = (m) => {
|
|
128
|
+
const active = [];
|
|
129
|
+
if (m.meta || m.ctrl) active.push("mod");
|
|
130
|
+
if (m.shift) active.push("shift");
|
|
131
|
+
if (m.alt) active.push("alt");
|
|
132
|
+
return mods(...active);
|
|
133
|
+
};
|
|
134
|
+
const report = (ctx, phase, gesture, matchedKey, arg = void 0) => {
|
|
135
|
+
const info = {
|
|
136
|
+
toolId: def.id,
|
|
137
|
+
phase,
|
|
138
|
+
gesture,
|
|
139
|
+
arg,
|
|
140
|
+
matchedKey,
|
|
141
|
+
modifiers: modifiersToCanonicalKey(ctx.modifiers),
|
|
142
|
+
target: ctx.target ?? { category: "empty", kind: "empty" },
|
|
143
|
+
timestamp: performance.now()
|
|
144
|
+
};
|
|
145
|
+
ctx.__reportRoute?.(info);
|
|
146
|
+
};
|
|
147
|
+
const applyResult = (ctx, result) => {
|
|
148
|
+
if (result == null) return "pass";
|
|
149
|
+
switch (result.kind) {
|
|
150
|
+
case "apply":
|
|
151
|
+
ctx.applyOps(result.ops, result.label);
|
|
152
|
+
return "claim";
|
|
153
|
+
case "begin": {
|
|
154
|
+
ctx.scratch = result.spec.scratch;
|
|
155
|
+
activeSpec = result.spec;
|
|
156
|
+
return "claim";
|
|
157
|
+
}
|
|
158
|
+
case "hold":
|
|
159
|
+
ctx.scratch = result.scratch;
|
|
160
|
+
return "claim";
|
|
161
|
+
case "commit":
|
|
162
|
+
ctx.applyOps(result.ops, result.label);
|
|
163
|
+
ctx.scratch = null;
|
|
164
|
+
activeSpec = null;
|
|
165
|
+
return "claim";
|
|
166
|
+
case "cancel":
|
|
167
|
+
ctx.scratch = null;
|
|
168
|
+
activeSpec = null;
|
|
169
|
+
return "claim";
|
|
170
|
+
case "claim":
|
|
171
|
+
return "claim";
|
|
172
|
+
case "none":
|
|
173
|
+
return "pass";
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const onClick = def.initial.click || def.engaged?.click ? (_e, ctx) => {
|
|
177
|
+
if ((_e.button ?? 0) !== 0) return "pass";
|
|
178
|
+
const phase = phaseOf(ctx);
|
|
179
|
+
if (!phase.click) return "pass";
|
|
180
|
+
if (!ctx.target) return "pass";
|
|
181
|
+
let match = resolveRoute(phase.click, ctx.target, ctx.modifiers);
|
|
182
|
+
if (!match && ctx.target.category === "empty") {
|
|
183
|
+
const star = phase.click["*"];
|
|
184
|
+
if (typeof star === "function") match = { action: star, matchedKey: "*" };
|
|
185
|
+
}
|
|
186
|
+
if (!match) return "pass";
|
|
187
|
+
report(ctx, phaseNameOf(ctx), "click", match.matchedKey);
|
|
188
|
+
return applyResult(ctx, match.action(ctx, _e));
|
|
189
|
+
} : void 0;
|
|
190
|
+
const onDown = def.initial.pointerDown || def.engaged?.pointerDown ? (_e, ctx) => {
|
|
191
|
+
if ((_e.button ?? 0) !== 0) return "pass";
|
|
192
|
+
const phase = phaseOf(ctx);
|
|
193
|
+
if (!phase.pointerDown) return "pass";
|
|
194
|
+
if (!ctx.target) return "pass";
|
|
195
|
+
let match = resolveRoute(phase.pointerDown, ctx.target, ctx.modifiers);
|
|
196
|
+
if (!match && ctx.target.category === "empty") {
|
|
197
|
+
const star = phase.pointerDown["*"];
|
|
198
|
+
if (typeof star === "function") match = { action: star, matchedKey: "*" };
|
|
199
|
+
}
|
|
200
|
+
if (!match) return "pass";
|
|
201
|
+
report(ctx, phaseNameOf(ctx), "click", match.matchedKey);
|
|
202
|
+
return applyResult(ctx, match.action(ctx, _e));
|
|
203
|
+
} : void 0;
|
|
204
|
+
const dragRoute = def.initial.drag;
|
|
205
|
+
const onDragStart = dragRoute ? (_e, ctx) => {
|
|
206
|
+
if (typeof dragRoute === "function") {
|
|
207
|
+
report(ctx, phaseNameOf(ctx), "drag", "*");
|
|
208
|
+
return applyResult(ctx, dragRoute(ctx, _e));
|
|
209
|
+
}
|
|
210
|
+
if (!ctx.target) return "pass";
|
|
211
|
+
const match = resolveRoute(dragRoute, ctx.target, ctx.modifiers);
|
|
212
|
+
if (!match) return "pass";
|
|
213
|
+
report(ctx, phaseNameOf(ctx), "drag", match.matchedKey);
|
|
214
|
+
return applyResult(ctx, match.action(ctx, _e));
|
|
215
|
+
} : void 0;
|
|
216
|
+
const onDragMove = (_e, ctx) => {
|
|
217
|
+
if (!activeSpec?.onMove) return "pass";
|
|
218
|
+
return applyResult(ctx, activeSpec.onMove(ctx, _e));
|
|
219
|
+
};
|
|
220
|
+
const onDragEnd = (_e, ctx) => {
|
|
221
|
+
if (!activeSpec?.onRelease) {
|
|
222
|
+
ctx.scratch = null;
|
|
223
|
+
activeSpec = null;
|
|
224
|
+
return "claim";
|
|
225
|
+
}
|
|
226
|
+
return applyResult(ctx, activeSpec.onRelease(ctx, _e));
|
|
227
|
+
};
|
|
228
|
+
const onDragCancel = (ctx) => {
|
|
229
|
+
if (activeSpec?.onCancel) {
|
|
230
|
+
const r = activeSpec.onCancel(ctx);
|
|
231
|
+
if (r) applyResult(ctx, r);
|
|
232
|
+
}
|
|
233
|
+
ctx.scratch = null;
|
|
234
|
+
activeSpec = null;
|
|
235
|
+
};
|
|
236
|
+
const buildKeyHandler = (gesture, pick) => (e, ctx) => {
|
|
237
|
+
const table = pick(phaseOf(ctx));
|
|
238
|
+
if (!table) return "pass";
|
|
239
|
+
const action = table[e.key];
|
|
240
|
+
if (!action) return "pass";
|
|
241
|
+
report(ctx, phaseNameOf(ctx), gesture, e.key, e.key);
|
|
242
|
+
return applyResult(ctx, action(ctx, e));
|
|
243
|
+
};
|
|
244
|
+
const resolveCursor = (ctx) => {
|
|
245
|
+
const phaseCursor = phaseOf(ctx).cursor;
|
|
246
|
+
if (phaseCursor != null) {
|
|
247
|
+
return typeof phaseCursor === "function" ? phaseCursor(ctx) : phaseCursor;
|
|
248
|
+
}
|
|
249
|
+
if (def.cursor != null) {
|
|
250
|
+
return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
|
|
251
|
+
}
|
|
252
|
+
return "";
|
|
253
|
+
};
|
|
254
|
+
const claimsAll = (ctx) => {
|
|
255
|
+
const v = phaseOf(ctx).claimsAll;
|
|
256
|
+
if (v === void 0) return false;
|
|
257
|
+
return typeof v === "function" ? v(ctx) : v;
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
id: def.id,
|
|
261
|
+
capabilities: def.capabilities,
|
|
262
|
+
def,
|
|
263
|
+
presentation: def.presentation,
|
|
264
|
+
keybinding: def.keybinding,
|
|
265
|
+
onActivate: def.onActivate,
|
|
266
|
+
onDeactivate: def.onDeactivate,
|
|
267
|
+
initScratch: def.initScratch ?? (() => null),
|
|
268
|
+
cursor: resolveCursor,
|
|
269
|
+
claimsAll,
|
|
270
|
+
hitOverride: def.hitOverride,
|
|
271
|
+
bindings: def.bindings,
|
|
272
|
+
pointer: onClick || onDown ? { ...onClick ? { onClick } : {}, ...onDown ? { onDown } : {} } : void 0,
|
|
273
|
+
drag: onDragStart ? {
|
|
274
|
+
onStart: onDragStart,
|
|
275
|
+
onMove: onDragMove,
|
|
276
|
+
onEnd: onDragEnd,
|
|
277
|
+
onCancel: onDragCancel
|
|
278
|
+
} : void 0,
|
|
279
|
+
dblTap: def.initial.dblTap || def.engaged?.dblTap ? {
|
|
280
|
+
onTap: (_e, ctx) => {
|
|
281
|
+
const table = phaseOf(ctx).dblTap;
|
|
282
|
+
if (!table) return "pass";
|
|
283
|
+
if (!ctx.target) return "pass";
|
|
284
|
+
let match = resolveRoute(table, ctx.target, ctx.modifiers);
|
|
285
|
+
if (!match && ctx.target.category === "empty") {
|
|
286
|
+
const star = table["*"];
|
|
287
|
+
if (typeof star === "function") match = { action: star, matchedKey: "*" };
|
|
288
|
+
}
|
|
289
|
+
if (!match) return "pass";
|
|
290
|
+
report(ctx, phaseNameOf(ctx), "dblTap", match.matchedKey);
|
|
291
|
+
return applyResult(ctx, match.action(ctx, _e));
|
|
292
|
+
}
|
|
293
|
+
} : void 0,
|
|
294
|
+
keyboard: def.initial.keyDown || def.engaged?.keyDown || def.initial.keyUp || def.engaged?.keyUp ? {
|
|
295
|
+
onDown: buildKeyHandler("keyDown", (p) => p.keyDown),
|
|
296
|
+
onUp: buildKeyHandler("keyUp", (p) => p.keyUp)
|
|
297
|
+
} : void 0,
|
|
298
|
+
wheel: def.initial.wheel || def.engaged?.wheel ? {
|
|
299
|
+
onWheel: (_e, ctx) => {
|
|
300
|
+
const entry = phaseOf(ctx).wheel;
|
|
301
|
+
if (!entry) return "pass";
|
|
302
|
+
const direction = _e.deltaY < 0 ? "up" : "down";
|
|
303
|
+
const action = typeof entry === "function" ? entry : entry[direction] ?? entry["*"];
|
|
304
|
+
if (!action) return "pass";
|
|
305
|
+
report(ctx, phaseNameOf(ctx), "wheel", "*", direction);
|
|
306
|
+
return applyResult(ctx, action(ctx, _e));
|
|
307
|
+
}
|
|
308
|
+
} : void 0,
|
|
309
|
+
overlay: def.initial.overlay ? def.initial.overlay() : void 0
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/tools/routing/defineViewportTool.ts
|
|
314
|
+
function defineViewportTool(def) {
|
|
315
|
+
const liftPhase = (phase) => ({
|
|
316
|
+
wheel: phase.wheel,
|
|
317
|
+
keyDown: phase.keyDown,
|
|
318
|
+
keyUp: phase.keyUp,
|
|
319
|
+
cursor: phase.cursor,
|
|
320
|
+
overlay: phase.overlay,
|
|
321
|
+
claimsAll: phase.claimsAll,
|
|
322
|
+
drag: phase.drag
|
|
323
|
+
});
|
|
324
|
+
return defineTool({
|
|
325
|
+
id: def.id,
|
|
326
|
+
capabilities: def.capabilities,
|
|
327
|
+
hookName: def.hookName,
|
|
328
|
+
presentation: def.presentation,
|
|
329
|
+
keybinding: def.keybinding,
|
|
330
|
+
onActivate: def.onActivate,
|
|
331
|
+
onDeactivate: def.onDeactivate,
|
|
332
|
+
cursor: def.cursor,
|
|
333
|
+
initial: liftPhase(def.initial),
|
|
334
|
+
engaged: def.engaged ? liftPhase(def.engaged) : void 0
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/tools/routing/forwardAction.ts
|
|
339
|
+
function forwardActionTo(callbackRef, buildPayload) {
|
|
340
|
+
return (ctx, event) => {
|
|
341
|
+
const cb = callbackRef.current;
|
|
342
|
+
if (!cb) return none();
|
|
343
|
+
cb(buildPayload(ctx, event));
|
|
344
|
+
return claim();
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/tools/routing/reflection/registry.ts
|
|
349
|
+
function buildActionRegistry(tools) {
|
|
350
|
+
const out = [];
|
|
351
|
+
for (const tool of tools) {
|
|
352
|
+
walkPhase(tool.id, "initial", tool.initial, out);
|
|
353
|
+
if (tool.engaged) walkPhase(tool.id, "engaged", tool.engaged, out);
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function walkPhase(toolId, phase, phaseDef, out) {
|
|
358
|
+
if (phaseDef.click) walkRouteTable(toolId, phase, "click", phaseDef.click, out);
|
|
359
|
+
if (phaseDef.pointerDown) walkRouteTable(toolId, phase, "pointerDown", phaseDef.pointerDown, out);
|
|
360
|
+
if (phaseDef.dblTap) walkRouteTable(toolId, phase, "dblTap", phaseDef.dblTap, out);
|
|
361
|
+
if (phaseDef.contextMenu) walkRouteTable(toolId, phase, "contextMenu", phaseDef.contextMenu, out);
|
|
362
|
+
if (phaseDef.drag) walkDrag(toolId, phase, phaseDef.drag, out);
|
|
363
|
+
if (phaseDef.wheel) walkWheel(toolId, phase, phaseDef.wheel, out);
|
|
364
|
+
if (phaseDef.keyDown) walkKeyMap(toolId, phase, "keyDown", phaseDef.keyDown, out);
|
|
365
|
+
if (phaseDef.keyUp) walkKeyMap(toolId, phase, "keyUp", phaseDef.keyUp, out);
|
|
366
|
+
if (phaseDef.multiTouchTap) walkMultiTouchTap(toolId, phase, phaseDef.multiTouchTap, out);
|
|
367
|
+
}
|
|
368
|
+
function walkRouteTable(toolId, phase, gesture, table, out) {
|
|
369
|
+
for (const target of Object.keys(table)) {
|
|
370
|
+
const entry = table[target];
|
|
371
|
+
if (entry == null) continue;
|
|
372
|
+
if (typeof entry === "function") {
|
|
373
|
+
out.push({ toolId, phase, gesture, arg: void 0, target, modifiers: {} });
|
|
374
|
+
} else {
|
|
375
|
+
walkModifierRoute(toolId, phase, gesture, target, entry, out);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function walkModifierRoute(toolId, phase, gesture, target, sub, out) {
|
|
380
|
+
for (const modKey of Object.keys(sub)) {
|
|
381
|
+
if (sub[modKey] == null) continue;
|
|
382
|
+
out.push({
|
|
383
|
+
toolId,
|
|
384
|
+
phase,
|
|
385
|
+
gesture,
|
|
386
|
+
arg: void 0,
|
|
387
|
+
target,
|
|
388
|
+
modifiers: modifierComboToParsed(modKey)
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function walkDrag(toolId, phase, drag, out) {
|
|
393
|
+
if (typeof drag === "function") {
|
|
394
|
+
out.push({ toolId, phase, gesture: "drag", arg: void 0, target: void 0, modifiers: {} });
|
|
395
|
+
} else {
|
|
396
|
+
walkRouteTable(toolId, phase, "drag", drag, out);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function walkWheel(toolId, phase, wheel, out) {
|
|
400
|
+
if (typeof wheel === "function") {
|
|
401
|
+
out.push({ toolId, phase, gesture: "wheel", arg: "*", target: void 0, modifiers: {} });
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
for (const dir of Object.keys(wheel)) {
|
|
405
|
+
if (wheel[dir] == null) continue;
|
|
406
|
+
out.push({ toolId, phase, gesture: "wheel", arg: dir, target: void 0, modifiers: {} });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function walkKeyMap(toolId, phase, gesture, table, out) {
|
|
410
|
+
for (const keyRoute of Object.keys(table)) {
|
|
411
|
+
if (table[keyRoute] == null) continue;
|
|
412
|
+
out.push({ toolId, phase, gesture, arg: keyRoute, target: void 0, modifiers: {} });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function walkMultiTouchTap(toolId, phase, table, out) {
|
|
416
|
+
for (const fingers of Object.keys(table)) {
|
|
417
|
+
if (table[fingers] == null) continue;
|
|
418
|
+
out.push({ toolId, phase, gesture: "multiTouchTap", arg: fingers, target: void 0, modifiers: {} });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/tools/routing/reflection/conflicts.ts
|
|
423
|
+
function findConflicts(tools) {
|
|
424
|
+
const entries = buildActionRegistry(tools);
|
|
425
|
+
const groups = /* @__PURE__ */ new Map();
|
|
426
|
+
for (const entry of entries) {
|
|
427
|
+
const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ""}|${entry.target ?? ""}|${canonicalModifiers(entry.modifiers)}`;
|
|
428
|
+
const bucket = groups.get(key);
|
|
429
|
+
if (bucket) bucket.push(entry);
|
|
430
|
+
else groups.set(key, [entry]);
|
|
431
|
+
}
|
|
432
|
+
const conflicts = [];
|
|
433
|
+
for (const bucket of groups.values()) {
|
|
434
|
+
if (bucket.length < 2) continue;
|
|
435
|
+
const first = bucket[0];
|
|
436
|
+
conflicts.push({
|
|
437
|
+
phase: first.phase,
|
|
438
|
+
gesture: first.gesture,
|
|
439
|
+
arg: first.arg,
|
|
440
|
+
target: first.target,
|
|
441
|
+
modifiers: first.modifiers,
|
|
442
|
+
toolIds: bucket.map((e) => e.toolId)
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return conflicts;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/tools/routing/reflection/route-resolved.ts
|
|
449
|
+
function formatRouteResolved(info) {
|
|
450
|
+
const argPart = info.arg !== void 0 ? `(${info.arg})` : "";
|
|
451
|
+
const mod = info.modifiers === "default" ? "" : ` mods=${info.modifiers}`;
|
|
452
|
+
return `${info.toolId} [${info.phase}] ${info.gesture}${argPart} \u2192 ${info.matchedKey}${mod}`;
|
|
453
|
+
}
|
|
454
|
+
function useToolDebugInfo(dispatcher) {
|
|
455
|
+
const [info, setInfo] = useState(
|
|
456
|
+
() => dispatcher.getLastRoute()
|
|
457
|
+
);
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
let raf = 0;
|
|
460
|
+
let last = dispatcher.getLastRoute();
|
|
461
|
+
const tick = () => {
|
|
462
|
+
const next = dispatcher.getLastRoute();
|
|
463
|
+
if (next !== last) {
|
|
464
|
+
last = next;
|
|
465
|
+
setInfo(next);
|
|
466
|
+
}
|
|
467
|
+
raf = requestAnimationFrame(tick);
|
|
468
|
+
};
|
|
469
|
+
raf = requestAnimationFrame(tick);
|
|
470
|
+
return () => cancelAnimationFrame(raf);
|
|
471
|
+
}, [dispatcher]);
|
|
472
|
+
return info;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/tools/routing/reflection/ToolDebugOverlay.module.css
|
|
476
|
+
var ToolDebugOverlay_default = {};
|
|
477
|
+
function ToolDebugOverlay({
|
|
478
|
+
info,
|
|
479
|
+
emptyLabel = "No route resolved yet"
|
|
480
|
+
}) {
|
|
481
|
+
if (!info) {
|
|
482
|
+
return /* @__PURE__ */ jsx("div", { className: ToolDebugOverlay_default.overlay, "data-state": "empty", children: /* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.empty, children: emptyLabel }) });
|
|
483
|
+
}
|
|
484
|
+
const modText = info.modifiers === "default" ? "\u2014" : info.modifiers;
|
|
485
|
+
const gestureText = info.arg !== void 0 ? `${info.gesture}(${info.arg})` : info.gesture;
|
|
486
|
+
const targetText = info.target.category === "empty" ? "empty" : `${info.target.kind}${"id" in info.target ? `(${String(info.target.id)})` : ""}`;
|
|
487
|
+
return /* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.overlay, "data-state": "resolved", children: [
|
|
488
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
489
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "tool" }),
|
|
490
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: info.toolId })
|
|
491
|
+
] }),
|
|
492
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
493
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "phase" }),
|
|
494
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: info.phase })
|
|
495
|
+
] }),
|
|
496
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
497
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "gesture" }),
|
|
498
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: gestureText })
|
|
499
|
+
] }),
|
|
500
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
501
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "matched" }),
|
|
502
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: info.matchedKey })
|
|
503
|
+
] }),
|
|
504
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
505
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "target" }),
|
|
506
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: targetText })
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ jsxs("div", { className: ToolDebugOverlay_default.row, children: [
|
|
509
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.label, children: "mods" }),
|
|
510
|
+
/* @__PURE__ */ jsx("span", { className: ToolDebugOverlay_default.value, children: modText })
|
|
511
|
+
] })
|
|
512
|
+
] });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export { ToolDebugOverlay, apply, begin, buildActionRegistry, cancel, claim, commit, defineTool, defineViewportTool, findConflicts, formatRouteResolved, forwardActionTo, hold, none, resolveRoute, routing_exports, useToolDebugInfo };
|
|
516
|
+
//# sourceMappingURL=chunk-AM6ARSPN.js.map
|
|
517
|
+
//# sourceMappingURL=chunk-AM6ARSPN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/routing/index.ts","../src/tools/routing/result.ts","../src/tools/routing/lookup.ts","../src/tools/routing/defineTool.ts","../src/tools/routing/defineViewportTool.ts","../src/tools/routing/forwardAction.ts","../src/tools/routing/reflection/registry.ts","../src/tools/routing/reflection/conflicts.ts","../src/tools/routing/reflection/route-resolved.ts","../src/tools/routing/reflection/useToolDebugInfo.ts","../src/tools/routing/reflection/ToolDebugOverlay.module.css","../src/tools/routing/reflection/ToolDebugOverlay.tsx"],"names":[],"mappings":"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","file":"chunk-AM6ARSPN.js","sourcesContent":["export type {\n HitResult, EmptyHit, NodeHit, AffordanceHit, NodeRef, NodeRefHit,\n} from './hitResult';\nexport type { Result, BeginSpec } from './result';\nexport { apply, begin, hold, commit, cancel, claim, none } from './result';\nexport type { ModifierCombo } from './modifiers';\nexport { mods } from './modifiers';\nexport type {\n ToolDef, PhaseDef, RouteTable, RouteEntry, ModifierRoute, ActionFn,\n ViewportToolDef, ViewportPhaseDef, ToolKeybinding,\n} from './types';\nexport { resolveRoute } from './lookup';\nexport { parseRoute, formatRoute, formatPhaseAtom, collapseShiftPairs, describeRoute, describeRouteParts, ROUTE_TERMS, ROUTE_FIELD_DEFINITIONS, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './routeGrammar';\nexport type { ParsedRoute, ParsedModifiers, ModifierKey, ModRequirement, PhaseAtom, ChannelRef, DescribeRouteOptions, RouteDescriptionPart, RouteTermLabel, RouteFieldName } from './routeGrammar';\nexport { getGestureDescriptor, isKnownGestureName, GESTURE_DESCRIPTORS } from './gestures';\nexport type { GestureName, GestureDescriptor, GestureArgSpec } from './gestures';\nexport { defineTool } from './defineTool';\nexport { defineViewportTool } from './defineViewportTool';\nexport { forwardActionTo } from './forwardAction';\n\n// Reflection consumers — registry / conflict checker / debug overlay.\nexport * from './reflection';\nexport { canonicalModifiers } from './reflection/modifierComboToParsed';\n","import type { Op } from '../../core/ops/types';\nimport type { ToolCtx } from '../types';\n\n/** Returned from a continuation closure or action handler. */\nexport type Result<TScratch> =\n | { kind: 'apply'; ops: Op[]; label?: string }\n | { kind: 'begin'; spec: BeginSpec<TScratch> }\n | { kind: 'hold'; scratch: TScratch }\n | { kind: 'commit'; ops: Op[]; label?: string }\n | { kind: 'cancel' }\n | { kind: 'claim' }\n | { kind: 'none' };\n\n/** Spec for `begin()` — opens engaged phase with continuation handlers. */\nexport interface BeginSpec<TScratch> {\n scratch: TScratch;\n thresholdPx?: number;\n onMove?: (ctx: ToolCtx<TScratch>, event?: PointerEvent) => Result<TScratch>;\n onRelease?: (ctx: ToolCtx<TScratch>, event?: PointerEvent) => Result<TScratch>;\n onCancel?: (ctx: ToolCtx<TScratch>) => void | Result<TScratch>;\n}\n\nexport function apply<TScratch>(ops: Op[], label?: string): Result<TScratch> {\n return label !== undefined\n ? { kind: 'apply', ops, label }\n : { kind: 'apply', ops };\n}\n\nexport function begin<TScratch>(spec: BeginSpec<TScratch>): Result<TScratch> {\n return { kind: 'begin', spec };\n}\n\nexport function hold<TScratch>(scratch: TScratch): Result<TScratch> {\n return { kind: 'hold', scratch };\n}\n\nexport function commit<TScratch>(ops: Op[], label?: string): Result<TScratch> {\n return label !== undefined\n ? { kind: 'commit', ops, label }\n : { kind: 'commit', ops };\n}\n\nexport function cancel<TScratch>(): Result<TScratch> {\n return { kind: 'cancel' };\n}\n\nexport function claim<TScratch>(): Result<TScratch> {\n return { kind: 'claim' };\n}\n\nexport function none<TScratch>(): Result<TScratch> {\n return { kind: 'none' };\n}\n","import type { ActionFn, RouteTable, RouteEntry, ModifierRoute } from './types';\nimport type { HitResult } from './hitResult';\nimport type { ToolModifiers } from '../types';\nimport { mods, type ModifierCombo } from './modifiers';\n\n/** Result of a successful `resolveRoute` lookup: the resolved `ActionFn`\n * plus the route-table key that matched after precedence resolution.\n * Consumers (currently only `defineTool`) use `matchedKey` for debug\n * reflection — answering \"why did this route fire?\" by surfacing the\n * exact post-precedence key. */\nexport interface RouteMatch<TScratch> {\n action: ActionFn<TScratch>;\n matchedKey: string;\n}\n\n/** Resolve a route entry to an `ActionFn` (or undefined) given the current\n * hit-test result and modifier snapshot. Implements the four-level\n * target precedence (exact → subkind-wildcard → base-kind → universal)\n * and the modifier sub-table exact-match + 'default' fallback. Returns\n * the matched route-table key alongside the action so callers can attach\n * it to debug-overlay reflection. */\nexport function resolveRoute<TScratch>(\n table: RouteTable<TScratch>,\n hit: HitResult,\n modifiers: ToolModifiers,\n): RouteMatch<TScratch> | undefined {\n const candidateKeys = buildCandidateKeys(hit);\n for (const key of candidateKeys) {\n const entry = table[key];\n if (entry == null) continue;\n const resolved = resolveEntry(entry, modifiers);\n if (resolved) return { action: resolved, matchedKey: key };\n }\n return undefined;\n}\n\n/** Produce the ordered list of route-table keys to try for `hit`. */\nfunction buildCandidateKeys(hit: HitResult): string[] {\n if (hit.category === 'empty') return ['empty', '*'];\n const kind = hit.kind;\n const colon = kind.indexOf(':');\n if (colon < 0) {\n // No subkind — try exact then universal.\n return [kind, '*'];\n }\n const baseKind = kind.substring(0, colon);\n const subKind = kind.substring(colon + 1);\n // Order: exact → subkind-wildcard → base-kind → universal.\n return [kind, `*:${subKind}`, baseKind, '*'];\n}\n\n/** Resolve a RouteEntry (function or sub-table) using modifiers. */\nfunction resolveEntry<TScratch>(\n entry: RouteEntry<TScratch>,\n modifiers: ToolModifiers,\n): ActionFn<TScratch> | undefined {\n if (typeof entry === 'function') return entry;\n const subTable = entry as ModifierRoute<TScratch>;\n const wanted = modifiersToKey(modifiers);\n return subTable[wanted] ?? subTable.default;\n}\n\n/** Translate the runtime ToolModifiers snapshot to the canonical\n * ModifierCombo for sub-table lookup. */\nfunction modifiersToKey(modifiers: ToolModifiers): ModifierCombo {\n const active: Array<'mod' | 'shift' | 'alt'> = [];\n // 'mod' is the platform-natural primary modifier:\n // Cmd on Mac (meta), Ctrl elsewhere.\n if (modifiers.meta || modifiers.ctrl) active.push('mod');\n if (modifiers.shift) active.push('shift');\n if (modifiers.alt) active.push('alt');\n return mods(...active);\n}\n","// src/tools/routing/defineTool.ts\nimport type { Tool, ToolCtx, ToolModifiers } from '../types';\nimport type { ToolDef, PhaseDef, ActionFn } from './types';\nimport type { Result, BeginSpec } from './result';\nimport { resolveRoute } from './lookup';\nimport { mods, type ModifierCombo } from './modifiers';\nimport { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES } from './routeGrammar';\nimport type {\n RouteResolvedInfo,\n RoutePhase,\n RouteGesture,\n} from './reflection/route-resolved';\n\n/** Validate that `id` is usable as a tool / channel id in the route\n * grammar. Rejects ids that start with a reserved sigil (would shadow\n * future grammar extensions) and ids that collide with phase keywords\n * (would parse as the bare-phase shorthand). */\nfunction validateId(id: string, kind: 'tool' | 'action'): void {\n if (id.length === 0) {\n throw new Error(`weasel: ${kind} id may not be empty`);\n }\n if (RESERVED_ID_PREFIXES.has(id[0]!)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" starts with reserved sigil \"${id[0]}\" ` +\n `(reserved set: ${[...RESERVED_ID_PREFIXES].join(' ')})`,\n );\n }\n if (RESERVED_ID_NAMES.has(id)) {\n throw new Error(\n `weasel: ${kind} id \"${id}\" collides with a reserved phase keyword ` +\n `(reserved: ${[...RESERVED_ID_NAMES].join(', ')})`,\n );\n }\n}\n\n/** Translate a declarative `ToolDef<TScratch>` into the existing imperative\n * `Tool<TScratch>` shape. The dispatcher consumes the resulting Tool as\n * it does today; declarative authoring is the only change. */\nexport function defineTool<TScratch = void>(\n def: ToolDef<TScratch>,\n): Tool<TScratch> {\n validateId(def.id, 'tool');\n // Stores the active BeginSpec between gesture events. The factory\n // closure is per-tool-instance, and a tool can have at most one active\n // gesture at a time, so a single mutable slot is sufficient. We can't\n // key off ctx identity because the dispatcher spreads a fresh ctx\n // object (`{ ...base, scratch }`) on every pointer event — a\n // WeakMap<ctx, …> would always miss on onMove/onEnd.\n let activeSpec: BeginSpec<TScratch> | null = null;\n\n // Phase state derives from ctx.scratch presence: scratch !== null means\n // engaged. The translated handlers consult def.engaged when scratch is\n // set, def.initial otherwise.\n const phaseOf = (ctx: ToolCtx<TScratch>): PhaseDef<TScratch> => {\n return ctx.scratch != null && def.engaged ? def.engaged : def.initial;\n };\n\n // Which phase the resolution belongs to — `initial` vs `engaged` —\n // mirrors phaseOf but yields the spec's vocabulary string for the\n // reflection callback rather than the PhaseDef object.\n const phaseNameOf = (ctx: ToolCtx<TScratch>): RoutePhase => {\n return ctx.scratch != null && def.engaged ? 'engaged' : 'initial';\n };\n\n // Translate a ToolModifiers runtime snapshot to the canonical\n // ModifierCombo string used in route sub-tables. Kept local because the\n // helper in lookup.ts is module-private and the reflection callback is\n // the only consumer outside it.\n const modifiersToCanonicalKey = (m: ToolModifiers): ModifierCombo => {\n const active: Array<'mod' | 'shift' | 'alt'> = [];\n if (m.meta || m.ctrl) active.push('mod');\n if (m.shift) active.push('shift');\n if (m.alt) active.push('alt');\n return mods(...active);\n };\n\n // Emit a RouteResolvedInfo snapshot through the dispatcher-supplied\n // reporter. No-op when the dispatcher hasn't wired __reportRoute (e.g.\n // bare-bones test harnesses).\n const report = (\n ctx: ToolCtx<TScratch>,\n phase: RoutePhase,\n gesture: RouteGesture,\n matchedKey: string,\n arg: string | undefined = undefined,\n ): void => {\n const info: RouteResolvedInfo = {\n toolId: def.id,\n phase,\n gesture,\n arg,\n matchedKey,\n modifiers: modifiersToCanonicalKey(ctx.modifiers),\n target: ctx.target ?? { category: 'empty', kind: 'empty' },\n timestamp: performance.now(),\n };\n ctx.__reportRoute?.(info);\n };\n\n // Apply a Result by mutating ctx and/or dispatching ops. Returns the\n // dispatch decision ('claim' or 'pass') based on whether the handler\n // produced an effect.\n const applyResult = (\n ctx: ToolCtx<TScratch>,\n result: Result<TScratch> | void,\n ): 'claim' | 'pass' => {\n if (result == null) return 'pass';\n switch (result.kind) {\n case 'apply':\n ctx.applyOps(result.ops, result.label as string);\n return 'claim';\n case 'begin': {\n // Open engaged: install scratch and stash continuation closures\n // in activeSpec so they survive across pointer events.\n (ctx as { scratch: unknown }).scratch = result.spec.scratch;\n activeSpec = result.spec;\n return 'claim';\n }\n case 'hold':\n // Update scratch; activeSpec stays so continuations remain wired.\n (ctx as { scratch: unknown }).scratch = result.scratch;\n return 'claim';\n case 'commit':\n ctx.applyOps(result.ops, result.label as string);\n (ctx as { scratch: unknown }).scratch = null;\n activeSpec = null;\n return 'claim';\n case 'cancel':\n (ctx as { scratch: unknown }).scratch = null;\n activeSpec = null;\n return 'claim';\n case 'claim':\n return 'claim';\n case 'none':\n return 'pass';\n }\n };\n\n // Build pointer.onClick handler from click route table.\n const onClick = def.initial.click || def.engaged?.click\n ? (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n // Route-table clicks only act on the primary button (left / touch /\n // pen); secondary / middle buttons return 'pass' so they don't mutate\n // selection and the context menu / ambient pan tools stay free. An\n // absent `button` (synthetic / programmatic events) counts as primary.\n if ((_e.button ?? 0) !== 0) return 'pass';\n const phase = phaseOf(ctx);\n if (!phase.click) return 'pass';\n if (!ctx.target) return 'pass';\n // Primary lookup: four-level target precedence via resolveRoute.\n let match = resolveRoute(phase.click, ctx.target, ctx.modifiers);\n // Universal fallback: '*' matches any hit type (including empty) when\n // no more-specific route was found. The lookup engine excludes empty\n // hits from '*' to prevent accidental catch-alls; here the factory\n // re-checks explicitly so engaged-phase catch-alls like { '*': addAnchor }\n // respond to background clicks as intended by the tool author.\n if (!match && ctx.target.category === 'empty') {\n const star = phase.click['*'];\n if (typeof star === 'function') match = { action: star, matchedKey: '*' };\n }\n if (!match) return 'pass';\n report(ctx, phaseNameOf(ctx), 'click', match.matchedKey);\n return applyResult(ctx, match.action(ctx, _e));\n }\n : undefined;\n\n // Build pointer.onDown handler from pointerDown route table. Mirrors\n // onClick but runs at pointerdown (pre-threshold). Returns 'pass' for\n // unrouted targets and for routes that return none(), so the\n // dispatcher can continue to its threshold-gated click vs. drag\n // classification.\n const onDown = def.initial.pointerDown || def.engaged?.pointerDown\n ? (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n // Route-table pointerDown classifiers (selection, etc.) only act on the\n // primary button. `pointerdown` reports `button === 0` for the left\n // mouse button and for touch / pen contact; secondary (right) / middle\n // mouse buttons return 'pass' so ambient tools that explicitly want them\n // (e.g. a right-drag pan tool) can claim, and the browser context menu\n // is left intact. An absent `button` (synthetic events) counts as primary.\n if ((_e.button ?? 0) !== 0) return 'pass';\n const phase = phaseOf(ctx);\n if (!phase.pointerDown) return 'pass';\n if (!ctx.target) return 'pass';\n let match = resolveRoute(phase.pointerDown, ctx.target, ctx.modifiers);\n // Universal fallback for empty hits — mirrors onClick semantics\n // so engaged-phase '*' routes (e.g. pen's empty-canvas anchor\n // add) respond to pointerdown on background.\n if (!match && ctx.target.category === 'empty') {\n const star = phase.pointerDown['*'];\n if (typeof star === 'function') match = { action: star, matchedKey: '*' };\n }\n if (!match) return 'pass';\n // pointerDown shares the 'click' gesture channel for reflection\n // purposes — it's a pre-threshold classifier on the same pointer\n // surface, and overlay consumers don't need to distinguish.\n report(ctx, phaseNameOf(ctx), 'click', match.matchedKey);\n return applyResult(ctx, match.action(ctx, _e));\n }\n : undefined;\n\n // Build drag handlers. drag can be either a route table or a function.\n const dragRoute = def.initial.drag;\n const onDragStart = dragRoute\n ? (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n // Function-form drag has an implicit matched key of '*' — there's\n // no route table to discriminate against.\n if (typeof dragRoute === 'function') {\n report(ctx, phaseNameOf(ctx), 'drag', '*');\n return applyResult(ctx, dragRoute(ctx, _e));\n }\n if (!ctx.target) return 'pass';\n const match = resolveRoute(dragRoute, ctx.target, ctx.modifiers);\n if (!match) return 'pass';\n report(ctx, phaseNameOf(ctx), 'drag', match.matchedKey);\n return applyResult(ctx, match.action(ctx, _e));\n }\n : undefined;\n\n const onDragMove = (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n if (!activeSpec?.onMove) return 'pass';\n return applyResult(ctx, activeSpec.onMove(ctx, _e));\n };\n\n const onDragEnd = (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n if (!activeSpec?.onRelease) {\n // No explicit onRelease — close engaged without applying.\n (ctx as { scratch: unknown }).scratch = null;\n activeSpec = null;\n return 'claim';\n }\n return applyResult(ctx, activeSpec.onRelease(ctx, _e));\n };\n\n const onDragCancel = (ctx: ToolCtx<TScratch>): void => {\n if (activeSpec?.onCancel) {\n const r = activeSpec.onCancel(ctx);\n if (r) applyResult(ctx, r);\n }\n (ctx as { scratch: unknown }).scratch = null;\n activeSpec = null;\n };\n\n // Keyboard / wheel handlers — straightforward route lookups.\n const buildKeyHandler = (\n gesture: 'keyDown' | 'keyUp',\n pick: (phase: PhaseDef<TScratch>) => Partial<Record<string, ActionFn<TScratch>>> | undefined,\n ) => (e: KeyboardEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n const table = pick(phaseOf(ctx));\n if (!table) return 'pass';\n const action = table[e.key];\n if (!action) return 'pass';\n // Keyboard routes are flat string→ActionFn maps — matched key is the\n // pressed key itself ('Escape', 'Enter', ...).\n report(ctx, phaseNameOf(ctx), gesture, e.key, e.key);\n return applyResult(ctx, action(ctx, e));\n };\n\n // cursor: phase override beats top-level. Returns '' (not undefined) so\n // the function-form signature satisfies Tool.cursor: (ctx) => string.\n const resolveCursor = (ctx: ToolCtx<TScratch>): string => {\n const phaseCursor = phaseOf(ctx).cursor;\n if (phaseCursor != null) {\n return typeof phaseCursor === 'function' ? phaseCursor(ctx) : phaseCursor;\n }\n if (def.cursor != null) {\n return typeof def.cursor === 'function' ? def.cursor(ctx) : def.cursor;\n }\n return '';\n };\n\n // claimsAll lives on the active phase, optionally. Boolean and\n // function forms both supported; the function form receives the\n // live ctx (scratch, view, target, modifiers) and is re-evaluated\n // on every pointerdown the dispatcher considers.\n const claimsAll = (ctx: ToolCtx<TScratch>): boolean => {\n const v = phaseOf(ctx).claimsAll;\n if (v === undefined) return false;\n return typeof v === 'function' ? v(ctx) : v;\n };\n\n return {\n id: def.id,\n capabilities: def.capabilities,\n def,\n presentation: def.presentation,\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n initScratch: def.initScratch ?? (() => null as unknown as TScratch),\n cursor: resolveCursor,\n claimsAll,\n hitOverride: def.hitOverride,\n bindings: def.bindings,\n pointer: (onClick || onDown)\n ? { ...(onClick ? { onClick } : {}), ...(onDown ? { onDown } : {}) }\n : undefined,\n drag: onDragStart ? {\n onStart: onDragStart,\n onMove: onDragMove,\n onEnd: onDragEnd,\n onCancel: onDragCancel,\n } : undefined,\n dblTap: def.initial.dblTap || def.engaged?.dblTap\n ? {\n onTap: (_e: PointerEvent, ctx: ToolCtx<TScratch>): 'claim' | 'pass' => {\n const table = phaseOf(ctx).dblTap;\n if (!table) return 'pass';\n if (!ctx.target) return 'pass';\n let match = resolveRoute(table, ctx.target, ctx.modifiers);\n // Universal fallback for empty hits — mirrors onClick / onDown\n // semantics so consumers writing `dblTap: { '*': fn }` to\n // handle \"double-tap anywhere\" get the callback on\n // empty-canvas double-taps.\n if (!match && ctx.target.category === 'empty') {\n const star = table['*'];\n if (typeof star === 'function') match = { action: star, matchedKey: '*' };\n }\n if (!match) return 'pass';\n report(ctx, phaseNameOf(ctx), 'dblTap', match.matchedKey);\n return applyResult(ctx, match.action(ctx, _e));\n },\n }\n : undefined,\n keyboard: (def.initial.keyDown || def.engaged?.keyDown || def.initial.keyUp || def.engaged?.keyUp)\n ? {\n onDown: buildKeyHandler('keyDown', (p) => p.keyDown),\n onUp: buildKeyHandler('keyUp', (p) => p.keyUp),\n }\n : undefined,\n wheel: def.initial.wheel || def.engaged?.wheel\n ? {\n onWheel: (_e: WheelEvent, ctx: ToolCtx<TScratch>) => {\n const entry = phaseOf(ctx).wheel;\n if (!entry) return 'pass';\n // Function form is sugar for `{ '*': fn }`.\n const direction: 'up' | 'down' = _e.deltaY < 0 ? 'up' : 'down';\n const action = typeof entry === 'function'\n ? entry\n : (entry[direction] ?? entry['*']);\n if (!action) return 'pass';\n report(ctx, phaseNameOf(ctx), 'wheel', '*', direction);\n return applyResult(ctx, action(ctx, _e));\n },\n }\n : undefined,\n overlay: def.initial.overlay ? def.initial.overlay() : undefined,\n };\n}\n","import type { Tool } from '../types';\nimport type { ViewportToolDef, PhaseDef, ActionFn } from './types';\nimport { defineTool } from './defineTool';\n\n/** Translate a viewport-tool spec. Viewport tools have no targets,\n * so route tables aren't allowed — drag is a function-form ActionFn.\n * This factory delegates to defineTool after lifting the ViewportPhaseDef\n * shape to the more permissive PhaseDef shape (which accepts both\n * function-form and route-table drags). */\nexport function defineViewportTool<TScratch = void>(\n def: ViewportToolDef<TScratch>,\n): Tool<TScratch> {\n const liftPhase = (phase: ViewportToolDef<TScratch>['initial']): PhaseDef<TScratch> => ({\n wheel: phase.wheel,\n keyDown: phase.keyDown,\n keyUp: phase.keyUp,\n cursor: phase.cursor,\n overlay: phase.overlay,\n claimsAll: phase.claimsAll,\n drag: phase.drag as ActionFn<TScratch> | undefined,\n });\n\n return defineTool<TScratch>({\n id: def.id,\n capabilities: def.capabilities,\n hookName: def.hookName,\n presentation: def.presentation,\n keybinding: def.keybinding,\n onActivate: def.onActivate,\n onDeactivate: def.onDeactivate,\n cursor: def.cursor,\n initial: liftPhase(def.initial),\n engaged: def.engaged ? liftPhase(def.engaged) : undefined,\n });\n}\n","import type { ActionFn } from './types';\nimport { claim, none } from './result';\n\n/**\n * Build an `ActionFn` that forwards `ctx` + the raw DOM event to a\n * consumer-supplied callback and claims the gesture.\n *\n * Useful for tools that want to expose an escape-hatch hook for app\n * code without losing routing: route the gesture declaratively, then\n * fall through to the consumer when the kit doesn't have a better\n * default. The consumer-payload shape is whatever the tool wants —\n * `buildPayload` produces it from `ctx` and the raw event.\n *\n * When `callbackRef.current` is null/undefined, returns `none()` so\n * the routing engine continues to the next route (e.g. an ambient\n * tool's handler).\n *\n * Typical use, inside a `useFooTool` hook:\n *\n * const onDoubleTapRef = useRef(options.onDoubleTap);\n * onDoubleTapRef.current = options.onDoubleTap;\n * const forwardDblTap = forwardActionTo<FooScratch, DblTapPayload>(\n * onDoubleTapRef,\n * (ctx, e) => ({\n * worldX: ctx.worldX,\n * worldY: ctx.worldY,\n * event: e as PointerEvent,\n * }),\n * );\n * // ...later, in the route table:\n * dblTap: { '*': forwardDblTap, empty: forwardDblTap }\n */\nexport function forwardActionTo<TScratch, TPayload>(\n callbackRef: { readonly current: ((payload: TPayload) => void) | null | undefined },\n buildPayload: (\n ctx: Parameters<ActionFn<TScratch>>[0],\n event: Parameters<ActionFn<TScratch>>[1],\n ) => TPayload,\n): ActionFn<TScratch> {\n return (ctx, event) => {\n const cb = callbackRef.current;\n if (!cb) return none();\n cb(buildPayload(ctx, event));\n return claim();\n };\n}\n","import type {\n ToolDef, PhaseDef, RouteTable, ModifierRoute, ActionFn,\n WheelTable, KeyRouteTable, MultiTouchTapTable,\n} from '../types';\nimport type { ModifierCombo } from '../modifiers';\nimport type { ParsedModifiers } from '../routeGrammar';\nimport { getGestureDescriptor, type GestureName } from '../gestures';\nimport type { RoutePhase } from './route-resolved';\nimport { modifierComboToParsed } from './modifierComboToParsed';\n\n/** One row in the action registry — uniquely identifies a routing slot\n * on one tool. Multiple rows can share (gesture, arg, target, modifiers)\n * if different tools declare them; consumers walk the list and group\n * client-side. */\nexport interface RegistryEntry {\n toolId: string;\n phase: RoutePhase;\n /** Structured v3 modifier requirements. Empty object = \"no modifiers\n * held\" (the strict default). */\n modifiers: ParsedModifiers;\n gesture: GestureName;\n /** Resolved arg value for arg-bearing gestures (wheel direction,\n * keyDown/keyUp key, multiTouchTap fingers). Undefined for gestures\n * whose descriptor has no `arg`. */\n arg: string | undefined;\n /** Route-table key for hit-test gestures (click/dblTap/drag/contextMenu/\n * pointerDown). Undefined when the descriptor has `hasTarget: false`. */\n target: string | undefined;\n}\n\nexport function buildActionRegistry(\n tools: readonly ToolDef<unknown>[],\n): RegistryEntry[] {\n const out: RegistryEntry[] = [];\n for (const tool of tools) {\n walkPhase(tool.id, 'initial', tool.initial, out);\n if (tool.engaged) walkPhase(tool.id, 'engaged', tool.engaged, out);\n }\n return out;\n}\n\nfunction walkPhase(\n toolId: string,\n phase: RoutePhase,\n phaseDef: PhaseDef<unknown>,\n out: RegistryEntry[],\n): void {\n if (phaseDef.click) walkRouteTable(toolId, phase, 'click', phaseDef.click, out);\n if (phaseDef.pointerDown) walkRouteTable(toolId, phase, 'pointerDown', phaseDef.pointerDown, out);\n if (phaseDef.dblTap) walkRouteTable(toolId, phase, 'dblTap', phaseDef.dblTap, out);\n if (phaseDef.contextMenu) walkRouteTable(toolId, phase, 'contextMenu', phaseDef.contextMenu, out);\n if (phaseDef.drag) walkDrag(toolId, phase, phaseDef.drag, out);\n if (phaseDef.wheel) walkWheel(toolId, phase, phaseDef.wheel, out);\n if (phaseDef.keyDown) walkKeyMap(toolId, phase, 'keyDown', phaseDef.keyDown, out);\n if (phaseDef.keyUp) walkKeyMap(toolId, phase, 'keyUp', phaseDef.keyUp, out);\n if (phaseDef.multiTouchTap) walkMultiTouchTap(toolId, phase, phaseDef.multiTouchTap, out);\n}\n\nfunction walkRouteTable(\n toolId: string,\n phase: RoutePhase,\n gesture: GestureName,\n table: RouteTable<unknown>,\n out: RegistryEntry[],\n): void {\n for (const target of Object.keys(table)) {\n const entry = table[target];\n if (entry == null) continue;\n if (typeof entry === 'function') {\n out.push({ toolId, phase, gesture, arg: undefined, target, modifiers: {} });\n } else {\n walkModifierRoute(toolId, phase, gesture, target, entry, out);\n }\n }\n}\n\nfunction walkModifierRoute(\n toolId: string,\n phase: RoutePhase,\n gesture: GestureName,\n target: string,\n sub: ModifierRoute<unknown>,\n out: RegistryEntry[],\n): void {\n for (const modKey of Object.keys(sub) as ModifierCombo[]) {\n if (sub[modKey] == null) continue;\n out.push({\n toolId, phase, gesture,\n arg: undefined, target,\n modifiers: modifierComboToParsed(modKey),\n });\n }\n}\n\nfunction walkDrag(\n toolId: string,\n phase: RoutePhase,\n drag: RouteTable<unknown> | ActionFn<unknown>,\n out: RegistryEntry[],\n): void {\n if (typeof drag === 'function') {\n // Function-form drag — targetless (continues from the original pointerdown).\n out.push({ toolId, phase, gesture: 'drag', arg: undefined, target: undefined, modifiers: {} });\n } else {\n walkRouteTable(toolId, phase, 'drag', drag, out);\n }\n}\n\nfunction walkWheel(\n toolId: string,\n phase: RoutePhase,\n wheel: WheelTable<unknown> | ActionFn<unknown>,\n out: RegistryEntry[],\n): void {\n if (typeof wheel === 'function') {\n out.push({ toolId, phase, gesture: 'wheel', arg: '*', target: undefined, modifiers: {} });\n return;\n }\n for (const dir of Object.keys(wheel) as Array<'up' | 'down' | '*'>) {\n if (wheel[dir] == null) continue;\n out.push({ toolId, phase, gesture: 'wheel', arg: dir, target: undefined, modifiers: {} });\n }\n}\n\nfunction walkKeyMap(\n toolId: string,\n phase: RoutePhase,\n gesture: 'keyDown' | 'keyUp',\n table: KeyRouteTable<unknown>,\n out: RegistryEntry[],\n): void {\n for (const keyRoute of Object.keys(table)) {\n if (table[keyRoute] == null) continue;\n out.push({ toolId, phase, gesture, arg: keyRoute, target: undefined, modifiers: {} });\n }\n}\n\nfunction walkMultiTouchTap(\n toolId: string,\n phase: RoutePhase,\n table: MultiTouchTapTable<unknown>,\n out: RegistryEntry[],\n): void {\n for (const fingers of Object.keys(table) as Array<'2' | '3' | '4'>) {\n if (table[fingers] == null) continue;\n out.push({ toolId, phase, gesture: 'multiTouchTap', arg: fingers, target: undefined, modifiers: {} });\n }\n}\n\n// Re-export for downstream consumers.\nexport { getGestureDescriptor };\nexport type { GestureName };\n","import type { ToolDef } from '../types';\nimport type { ParsedModifiers } from '../routeGrammar';\nimport type { RoutePhase, RouteGesture } from './route-resolved';\nimport { buildActionRegistry, type RegistryEntry } from './registry';\nimport { canonicalModifiers } from './modifierComboToParsed';\n\n/** Two or more tools declare the same exact (phase, gesture, arg, target,\n * modifiers) tuple — the dispatcher's slot precedence picks one\n * arbitrarily (well, deterministically by slot order, but the author\n * probably didn't intend the duplication). */\nexport interface Conflict {\n phase: RoutePhase;\n gesture: RouteGesture;\n arg: string | undefined;\n target: string | undefined;\n modifiers: ParsedModifiers;\n /** All tool ids that registered the same tuple. At least 2 by\n * construction. Order matches the input tools[] order. */\n toolIds: string[];\n}\n\n/** Detect exact-tuple overlaps across a tool registration set.\n *\n * Intentionally NOT flagged:\n * - Wildcard vs. specific (e.g., `click['*']` + `click['rect']`) — that's\n * the cascading-fallback pattern; the lookup engine resolves cleanly.\n * - Different modifier sub-keys on the same target — they fire on\n * different inputs.\n * - Ambient stacking where a tool returns `pass`/`none()` from its\n * ActionFn to intentionally let another ambient tool run. We'd need\n * to evaluate the ActionFn to detect intent; consumers can suppress\n * known-intentional compositions in their UI layer.\n */\nexport function findConflicts(\n tools: readonly ToolDef<unknown>[],\n): Conflict[] {\n const entries = buildActionRegistry(tools);\n const groups = new Map<string, RegistryEntry[]>();\n for (const entry of entries) {\n const key = `${entry.phase}|${entry.gesture}|${entry.arg ?? ''}|${entry.target ?? ''}|${canonicalModifiers(entry.modifiers)}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(entry);\n else groups.set(key, [entry]);\n }\n const conflicts: Conflict[] = [];\n for (const bucket of groups.values()) {\n if (bucket.length < 2) continue;\n // Multiple registrations from the same tool can't share a key (Object\n // literals dedupe), so bucket.length >= 2 implies >= 2 distinct toolIds.\n const first = bucket[0];\n conflicts.push({\n phase: first.phase,\n gesture: first.gesture,\n arg: first.arg,\n target: first.target,\n modifiers: first.modifiers,\n toolIds: bucket.map((e) => e.toolId),\n });\n }\n return conflicts;\n}\n","// src/tools/routing/reflection/route-resolved.ts\nimport type { HitResult } from '../hitResult';\nimport type { ModifierCombo } from '../modifiers';\nimport type { GestureName } from '../gestures';\n\n/** Phase the route was resolved against. Mirrors the spec's two-phase\n * vocabulary — `initial` (idle, scratch null) or `engaged` (mid-gesture). */\nexport type RoutePhase = 'initial' | 'engaged';\n\n/** Gesture channel the route fired on. */\nexport type RouteGesture = GestureName;\n\n/** Snapshot of one route resolution, emitted by the factory on each\n * successful lookup. Captured by the dispatcher as the \"last resolved\n * route\" for debug-overlay consumers. */\nexport interface RouteResolvedInfo {\n toolId: string;\n phase: RoutePhase;\n gesture: RouteGesture;\n /** Argument captured at match time for arg-bearing gestures\n * (`wheel` direction, `keyDown`/`keyUp` key, `multiTouchTap` fingers).\n * Undefined for no-arg gestures. */\n arg: string | undefined;\n /** Route-table key that matched (post-precedence). E.g. 'rect:selected',\n * '*:selected', 'rect', '*', 'empty'. For function-form `drag` (no\n * table), this is '*'. For keyDown/keyUp, it's the key name ('Escape',\n * 'Enter', etc.). */\n matchedKey: string;\n modifiers: ModifierCombo;\n /** The full HitResult at resolution time (snapshot — safe to read). */\n target: HitResult;\n /** Monotonic timestamp (ms since page load via performance.now()).\n * Used for \"resolved Nms ago\" displays. */\n timestamp: number;\n}\n\n/** Render-friendly one-line string. Used by ToolDebugOverlay; exported\n * so non-React consumers can format the same way. */\nexport function formatRouteResolved(info: RouteResolvedInfo): string {\n const argPart = info.arg !== undefined ? `(${info.arg})` : '';\n const mod = info.modifiers === 'default' ? '' : ` mods=${info.modifiers}`;\n return `${info.toolId} [${info.phase}] ${info.gesture}${argPart} → ${info.matchedKey}${mod}`;\n}\n","// src/tools/routing/reflection/useToolDebugInfo.ts\nimport { useEffect, useState } from 'react';\nimport type { ToolsDispatcher } from '../../dispatcher';\nimport type { RouteResolvedInfo } from './route-resolved';\n\n/** Reactive snapshot of the most recently resolved route. Re-renders\n * the consuming component whenever a new route resolves. Returns null\n * when no route has resolved yet (or after `cancelGesture()`).\n *\n * Pass the kit's ToolsDispatcher reference. The hook subscribes via\n * the dispatcher's existing onRouteResolved option — if the consumer's\n * dispatcher wasn't constructed with this hook in mind, the consumer\n * must thread an extra subscription. (Future improvement: kit-shipped\n * dispatcher context.) */\nexport function useToolDebugInfo(\n dispatcher: ToolsDispatcher,\n): RouteResolvedInfo | null {\n const [info, setInfo] = useState<RouteResolvedInfo | null>(() =>\n dispatcher.getLastRoute(),\n );\n useEffect(() => {\n // Poll-on-tick fallback: the dispatcher's onRouteResolved option is\n // owned by createToolsDispatcher (set once at construction). If the\n // consumer didn't wire it to this hook, we still want some signal.\n // Strategy: use requestAnimationFrame loop while mounted to read\n // getLastRoute(). Cheap (single function call per frame); the kit's\n // event loop is the same one driving renders, so no extra work.\n let raf = 0;\n let last = dispatcher.getLastRoute();\n const tick = (): void => {\n const next = dispatcher.getLastRoute();\n if (next !== last) {\n last = next;\n setInfo(next);\n }\n raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [dispatcher]);\n return info;\n}\n","/* src/tools/routing/reflection/ToolDebugOverlay.module.css */\n.overlay {\n position: absolute;\n bottom: 12px;\n right: 12px;\n min-width: 180px;\n padding: 8px 10px;\n background: rgba(20, 20, 24, 0.86);\n color: #eaeaea;\n font: 11px/1.4 ui-monospace, \"SF Mono\", Menlo, monospace;\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n pointer-events: none;\n user-select: none;\n}\n\n.row {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n}\n\n.label {\n color: #888;\n}\n\n.value {\n color: #f5f5f5;\n}\n\n.empty {\n color: #888;\n font-style: italic;\n}\n","// src/tools/routing/reflection/ToolDebugOverlay.tsx\nimport type { ReactElement } from 'react';\nimport type { RouteResolvedInfo } from './route-resolved';\nimport styles from './ToolDebugOverlay.module.css';\n\nexport interface ToolDebugOverlayProps {\n /** Most recent route resolution. Pass the result of useToolDebugInfo(). */\n info: RouteResolvedInfo | null;\n /** Optional override for the \"idle\" placeholder text shown when info is null. */\n emptyLabel?: string;\n}\n\n/** Dev-tools-style panel showing the most recently resolved route.\n * Mount in a corner of the viewport (typically bottom-right). Pure\n * presentation — no dispatcher coupling; the parent threads the info. */\nexport function ToolDebugOverlay({\n info,\n emptyLabel = 'No route resolved yet',\n}: ToolDebugOverlayProps): ReactElement {\n if (!info) {\n return (\n <div className={styles.overlay} data-state=\"empty\">\n <span className={styles.empty}>{emptyLabel}</span>\n </div>\n );\n }\n const modText = info.modifiers === 'default' ? '—' : info.modifiers;\n const gestureText = info.arg !== undefined ? `${info.gesture}(${info.arg})` : info.gesture;\n const targetText = info.target.category === 'empty'\n ? 'empty'\n : `${info.target.kind}${'id' in info.target ? `(${String(info.target.id)})` : ''}`;\n return (\n <div className={styles.overlay} data-state=\"resolved\">\n <div className={styles.row}>\n <span className={styles.label}>tool</span>\n <span className={styles.value}>{info.toolId}</span>\n </div>\n <div className={styles.row}>\n <span className={styles.label}>phase</span>\n <span className={styles.value}>{info.phase}</span>\n </div>\n <div className={styles.row}>\n <span className={styles.label}>gesture</span>\n <span className={styles.value}>{gestureText}</span>\n </div>\n <div className={styles.row}>\n <span className={styles.label}>matched</span>\n <span className={styles.value}>{info.matchedKey}</span>\n </div>\n <div className={styles.row}>\n <span className={styles.label}>target</span>\n <span className={styles.value}>{targetText}</span>\n </div>\n <div className={styles.row}>\n <span className={styles.label}>mods</span>\n <span className={styles.value}>{modText}</span>\n </div>\n </div>\n );\n}\n"]}
|