@weasel-js/core 0.5.1 → 0.7.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 +148 -0
- package/dist/{DrawCommand-Dl0bXNfS.d.ts → DrawCommand-CJtqqt8H.d.ts} +26 -2
- package/dist/{chunk-7V6JEOXE.js → chunk-7F3SDUJ4.js} +7884 -9167
- package/dist/chunk-7F3SDUJ4.js.map +1 -0
- package/dist/chunk-FSZEXVCR.js +171 -0
- package/dist/chunk-FSZEXVCR.js.map +1 -0
- package/dist/clone.d.ts +1 -1
- package/dist/geometry-D9BDMiQi.d.ts +114 -0
- package/dist/{grid-Cf87knjU.d.ts → grid-CaSK9bHV.d.ts} +1 -1
- package/dist/index-D31EADQG.d.ts +2847 -0
- package/dist/index.css +0 -35
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +913 -2546
- package/dist/index.js +2 -2
- package/dist/insert.d.ts +2 -2
- package/dist/move.d.ts +3 -3
- package/dist/{options-BPPBWMa7.d.ts → options-DMWeTELe.d.ts} +1 -1
- package/dist/{pointSnapToGrid-D7s7QmOF.d.ts → pointSnapToGrid-C3EruUwt.d.ts} +3 -54
- package/dist/renderer.css +0 -35
- package/dist/renderer.css.map +1 -1
- package/dist/renderer.d.ts +5 -4
- package/dist/renderer.js +2 -2
- package/dist/resize.d.ts +3 -3
- package/dist/routing.d.ts +10 -8
- package/dist/routing.js +1 -1
- package/dist/{types-BjUi2vA-.d.ts → types-Dcaa0tPq.d.ts} +1 -25
- package/dist/{registerFont-CP-wCsrz.d.ts → viewToMat3-D4lrBigW.d.ts} +2 -44
- package/package.json +6 -5
- package/dist/chunk-7V6JEOXE.js.map +0 -1
- package/dist/chunk-AM6ARSPN.js +0 -517
- package/dist/chunk-AM6ARSPN.js.map +0 -1
- package/dist/fitViewToBounds-evGsnR8Q.d.ts +0 -62
- package/dist/index-DZBYMsHI.d.ts +0 -1555
- package/dist/routing.css +0 -35
- package/dist/routing.css.map +0 -1
package/dist/index-DZBYMsHI.d.ts
DELETED
|
@@ -1,1555 +0,0 @@
|
|
|
1
|
-
import { N as NodeId } from './types-Cpb4hii1.js';
|
|
2
|
-
import { Op } from '@weasel-js/history';
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { ReactElement } from 'react';
|
|
5
|
-
import { V as View } from './view-DSQgxBJB.js';
|
|
6
|
-
import { A as ActionBehavior, M as ModifierState, c as ResizeAnchor } from './types-BjUi2vA-.js';
|
|
7
|
-
import { B as Bounds } from './fitViewToBounds-evGsnR8Q.js';
|
|
8
|
-
import { D as DrawCommand, F as FillStyle, a as Stroke } from './DrawCommand-Dl0bXNfS.js';
|
|
9
|
-
import { D as DebugSink } from './types-BJ8_cyT7.js';
|
|
10
|
-
import { GestureSpec, ModifierCombo, GestureName, ParsedModifiers, ChannelRef, DescribeRouteOptions, GESTURE_DESCRIPTORS, GestureArgSpec, GestureDescriptor, ModRequirement, ModifierKey, ParsedRoute, PhaseAtom, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, RouteDescriptionPart, RouteFieldName, RouteTermLabel, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, mods, parseRoute } from '@weasel-js/gestures';
|
|
11
|
-
import { CapabilityTag } from '@weasel-js/modes';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Selection click policy. `single` always replaces; `multi` toggles when the
|
|
15
|
-
* configured extend key is held, otherwise replaces.
|
|
16
|
-
*/
|
|
17
|
-
type SelectionMode = 'single' | 'multi';
|
|
18
|
-
/** Modifier key used to extend the selection in `multi` mode. */
|
|
19
|
-
type SelectionExtendKey = 'shift' | 'meta' | 'ctrl';
|
|
20
|
-
/** API returned by {@link useSelection}. */
|
|
21
|
-
interface SelectionApi {
|
|
22
|
-
/** Current selection. Re-renders trigger when this reference changes. */
|
|
23
|
-
current: readonly NodeId[];
|
|
24
|
-
/** Imperative read for use inside event callbacks (avoids stale closures). */
|
|
25
|
-
get(): NodeId[];
|
|
26
|
-
/** Replace selection. */
|
|
27
|
-
set(ids: NodeId[]): void;
|
|
28
|
-
/** Add id (multi-mode appends; single-mode replaces). */
|
|
29
|
-
add(id: NodeId): void;
|
|
30
|
-
/** Remove id from selection. */
|
|
31
|
-
remove(id: NodeId): void;
|
|
32
|
-
/** Toggle id in/out of selection. */
|
|
33
|
-
toggle(id: NodeId): void;
|
|
34
|
-
/** Clear selection. */
|
|
35
|
-
clear(): void;
|
|
36
|
-
/** True if id is selected. */
|
|
37
|
-
contains(id: NodeId): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Apply a click to the selection per the configured mode/extend key.
|
|
40
|
-
* - `single`: replaces selection with `[id]`, regardless of modifiers.
|
|
41
|
-
* - `multi`: with the extend key held, toggles `id` in/out of the selection;
|
|
42
|
-
* otherwise replaces with `[id]`.
|
|
43
|
-
*/
|
|
44
|
-
applyClick(id: NodeId, modifiers: {
|
|
45
|
-
shift: boolean;
|
|
46
|
-
meta: boolean;
|
|
47
|
-
ctrl: boolean;
|
|
48
|
-
}): void;
|
|
49
|
-
/** Pre-built methods for spreading into an adapter that needs them. */
|
|
50
|
-
adapterMethods: {
|
|
51
|
-
getSelection: () => NodeId[];
|
|
52
|
-
setSelection: (ids: NodeId[]) => void;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/** Options for {@link useSelection}. */
|
|
56
|
-
interface UseSelectionOptions {
|
|
57
|
-
/** Default `'single'`. */
|
|
58
|
-
mode?: SelectionMode;
|
|
59
|
-
/** Default `'shift'`. Ignored in single-mode. */
|
|
60
|
-
extend?: SelectionExtendKey;
|
|
61
|
-
/** Default `[]`. */
|
|
62
|
-
initial?: readonly NodeId[];
|
|
63
|
-
/** When `true`, every mutator (`set`/`add`/`remove`/`toggle`/`clear`/
|
|
64
|
-
* `applyClick`) is a no-op — selection stays at whatever `initial`
|
|
65
|
-
* pinned it to. Useful for demos that exist to showcase a single
|
|
66
|
-
* pre-selected node (e.g. the bezier-edit curve) and don't want a
|
|
67
|
-
* stray click to deselect. */
|
|
68
|
-
lock?: boolean;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Default implementation of the `getSelection` / `setSelection` adapter
|
|
72
|
-
* contract every action hook (delete, duplicate, nudge, group, ...) requires.
|
|
73
|
-
*
|
|
74
|
-
* Owns selection state, exposes a click-policy helper (single vs multi with
|
|
75
|
-
* an extend key), and pre-builds the two adapter methods consumers otherwise
|
|
76
|
-
* hand-roll in every demo:
|
|
77
|
-
*
|
|
78
|
-
* ```tsx
|
|
79
|
-
* const selection = useSelection({ mode: 'multi' });
|
|
80
|
-
* const adapter = { ...arrayAdapter({...}), ...selection.adapterMethods };
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
declare function useSelection(opts?: UseSelectionOptions): SelectionApi;
|
|
84
|
-
|
|
85
|
-
/** Returned from a continuation closure or action handler. */
|
|
86
|
-
type Result<TScratch> = {
|
|
87
|
-
kind: 'apply';
|
|
88
|
-
ops: Op[];
|
|
89
|
-
label?: string;
|
|
90
|
-
} | {
|
|
91
|
-
kind: 'begin';
|
|
92
|
-
spec: BeginSpec<TScratch>;
|
|
93
|
-
} | {
|
|
94
|
-
kind: 'hold';
|
|
95
|
-
scratch: TScratch;
|
|
96
|
-
} | {
|
|
97
|
-
kind: 'commit';
|
|
98
|
-
ops: Op[];
|
|
99
|
-
label?: string;
|
|
100
|
-
} | {
|
|
101
|
-
kind: 'cancel';
|
|
102
|
-
} | {
|
|
103
|
-
kind: 'claim';
|
|
104
|
-
} | {
|
|
105
|
-
kind: 'none';
|
|
106
|
-
};
|
|
107
|
-
/** Spec for `begin()` — opens engaged phase with continuation handlers. */
|
|
108
|
-
interface BeginSpec<TScratch> {
|
|
109
|
-
scratch: TScratch;
|
|
110
|
-
thresholdPx?: number;
|
|
111
|
-
onMove?: (ctx: ToolCtx<TScratch>, event?: PointerEvent) => Result<TScratch>;
|
|
112
|
-
onRelease?: (ctx: ToolCtx<TScratch>, event?: PointerEvent) => Result<TScratch>;
|
|
113
|
-
onCancel?: (ctx: ToolCtx<TScratch>) => void | Result<TScratch>;
|
|
114
|
-
}
|
|
115
|
-
declare function apply<TScratch>(ops: Op[], label?: string): Result<TScratch>;
|
|
116
|
-
declare function begin<TScratch>(spec: BeginSpec<TScratch>): Result<TScratch>;
|
|
117
|
-
declare function hold<TScratch>(scratch: TScratch): Result<TScratch>;
|
|
118
|
-
declare function commit<TScratch>(ops: Op[], label?: string): Result<TScratch>;
|
|
119
|
-
declare function cancel<TScratch>(): Result<TScratch>;
|
|
120
|
-
declare function claim<TScratch>(): Result<TScratch>;
|
|
121
|
-
declare function none<TScratch>(): Result<TScratch>;
|
|
122
|
-
|
|
123
|
-
/** A 2D point in either world or screen coordinates. */
|
|
124
|
-
interface Point2 {
|
|
125
|
-
x: number;
|
|
126
|
-
y: number;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Information about which UI affordance was hit at pointerdown.
|
|
130
|
-
*
|
|
131
|
-
* Populated by the dispatcher when the `affordanceAt` thunk is provided to
|
|
132
|
-
* `useGestureDispatcher`. Tools / action invokers that only fire on a specific
|
|
133
|
-
* affordance (e.g. a resize handle) use this field as a guard — if the
|
|
134
|
-
* affordance is absent or is the wrong kind, they return `{}` and let other
|
|
135
|
-
* bindings handle the drag.
|
|
136
|
-
*
|
|
137
|
-
* `kind` is a discriminator string:
|
|
138
|
-
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
139
|
-
* `'handle:bottom-right'` — corner resize handles.
|
|
140
|
-
* - `'rotate-handle'` — the rotation affordance.
|
|
141
|
-
* - `'anchor:N'` — a path anchor at index N.
|
|
142
|
-
*
|
|
143
|
-
* `fixedPoint` is the world-space point that should remain stationary during
|
|
144
|
-
* the gesture. For resize handles this is the opposite (diagonally fixed)
|
|
145
|
-
* corner; for rotate it is the pivot.
|
|
146
|
-
*
|
|
147
|
-
* `targetIds` are the node ids this affordance belongs to.
|
|
148
|
-
*/
|
|
149
|
-
interface AffordanceHit$1 {
|
|
150
|
-
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
151
|
-
kind: string;
|
|
152
|
-
/** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
|
|
153
|
-
fixedPoint?: {
|
|
154
|
-
x: number;
|
|
155
|
-
y: number;
|
|
156
|
-
};
|
|
157
|
-
/** Which nodes this affordance belongs to. */
|
|
158
|
-
targetIds?: string[];
|
|
159
|
-
/** Set when `kind` matches `'handle:*'`. Identifies which corner stays
|
|
160
|
-
* fixed during a resize so consumers (resizeAction) don't re-parse `kind`.
|
|
161
|
-
* Other affordance kinds (rotate-handle, anchor:N, controlIn:N, controlOut:N)
|
|
162
|
-
* leave this undefined. */
|
|
163
|
-
anchor?: ResizeAnchor;
|
|
164
|
-
/** CSS cursor to show while the pointer hovers this affordance (no
|
|
165
|
-
* gesture in flight). Consumed by the hover-cursor pump in
|
|
166
|
-
* `useGestureDispatcher`; unset = the pump falls through to
|
|
167
|
-
* action-cursor prediction, then to the active tool's cursor. */
|
|
168
|
-
cursor?: string;
|
|
169
|
-
}
|
|
170
|
-
/** Per-invocation runtime context the dispatcher hands to an Invoker.
|
|
171
|
-
* Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
|
|
172
|
-
* populated only for matching gesture kinds. */
|
|
173
|
-
interface InvocationCtx {
|
|
174
|
-
world: Point2;
|
|
175
|
-
screen: Point2;
|
|
176
|
-
modifiers: ModifierState;
|
|
177
|
-
deps: ActionDeps;
|
|
178
|
-
drag?: {
|
|
179
|
-
start: Point2;
|
|
180
|
-
current: Point2;
|
|
181
|
-
delta: Point2;
|
|
182
|
-
/**
|
|
183
|
-
* Drag delta in client/screen coordinates (CSS pixels from the drag
|
|
184
|
-
* origin). Use this — never `delta` — for any action whose effect
|
|
185
|
-
* mutates the viewport itself (pan, view-zoom), because world-space
|
|
186
|
-
* deltas become self-referential as the view shifts mid-drag.
|
|
187
|
-
*
|
|
188
|
-
* Populated when the dispatcher received `clientX`/`clientY` on the
|
|
189
|
-
* underlying pointer events. Absent for legacy callers that don't
|
|
190
|
-
* provide them.
|
|
191
|
-
*/
|
|
192
|
-
screenDelta?: Point2;
|
|
193
|
-
affordance?: AffordanceHit$1;
|
|
194
|
-
/**
|
|
195
|
-
* Full pointermove history for the current drag, in world space.
|
|
196
|
-
* Accumulated by the dispatcher on every `pointermove` pump event.
|
|
197
|
-
* Available only during `onMove` and `onEnd` calls (not on `start`).
|
|
198
|
-
* Used by `lassoSelectAction` to build its polygon vertex list.
|
|
199
|
-
*/
|
|
200
|
-
points?: Point2[];
|
|
201
|
-
};
|
|
202
|
-
wheel?: {
|
|
203
|
-
deltaX: number;
|
|
204
|
-
deltaY: number;
|
|
205
|
-
deltaZ: number;
|
|
206
|
-
};
|
|
207
|
-
multiTouch?: {
|
|
208
|
-
centroid: Point2;
|
|
209
|
-
spread: number;
|
|
210
|
-
rotation: number;
|
|
211
|
-
/**
|
|
212
|
-
* Pinch-zoom geometry. Populated by the dispatcher when a multitouch
|
|
213
|
-
* handle is in flight and a pointermove-pump fires.
|
|
214
|
-
* `startSpread` is the spread at the moment the gesture began.
|
|
215
|
-
* `currentSpread` is the spread at the current frame.
|
|
216
|
-
*/
|
|
217
|
-
pinch?: {
|
|
218
|
-
startSpread: number;
|
|
219
|
-
currentSpread: number;
|
|
220
|
-
centroid: Point2;
|
|
221
|
-
};
|
|
222
|
-
};
|
|
223
|
-
key?: {
|
|
224
|
-
key: string;
|
|
225
|
-
repeat: boolean;
|
|
226
|
-
};
|
|
227
|
-
/**
|
|
228
|
-
* Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
|
|
229
|
-
* UI-driven ongoing actions (color picker, opacity slider) so handles can
|
|
230
|
-
* read the current value on `start` and updated values on `onMove`. The
|
|
231
|
-
* gesture dispatcher does not populate this field; gesture-driven actions
|
|
232
|
-
* receive params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
233
|
-
*/
|
|
234
|
-
params?: Record<string, unknown>;
|
|
235
|
-
}
|
|
236
|
-
/** Per-invocation options the dispatcher reads from a `GestureBinding`'s
|
|
237
|
-
* `opts` field and passes to `OngoingInvoker.start`. Today carries
|
|
238
|
-
* behaviors; extensible. */
|
|
239
|
-
interface BindingOpts {
|
|
240
|
-
behaviors?: ActionBehavior<unknown, unknown, unknown>[];
|
|
241
|
-
/** Per-binding action parameters. The action's invoker reads
|
|
242
|
-
* these via the second arg to `run` (or via InvocationCtx for ongoing
|
|
243
|
-
* invokers, when needed). Loose typing (Record<string, unknown>) for
|
|
244
|
-
* now; consider per-action typing later via BindingOpts<A>.
|
|
245
|
-
*
|
|
246
|
-
* params may also be a thunk evaluated each time the
|
|
247
|
-
* dispatcher (or invoker) needs the value. Thunks let tools close over
|
|
248
|
-
* refs that mutate during a gesture (e.g. polygon `sides` adjusted
|
|
249
|
-
* mid-drag via ArrowUp). For ongoing invokers that want the latest
|
|
250
|
-
* values at commit, the invoker can re-call the thunk inside `onEnd`
|
|
251
|
-
* via `resolveParams(opts?.params)`. */
|
|
252
|
-
params?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
253
|
-
}
|
|
254
|
-
/** Convention-shaped action dependencies bag. Actions declare which
|
|
255
|
-
* contexts they consume; the dispatcher composes them per call.
|
|
256
|
-
* Consumer-side contexts (e.g. ColorContext) plug in by extending. */
|
|
257
|
-
interface ActionDeps {
|
|
258
|
-
selection?: unknown;
|
|
259
|
-
view?: unknown;
|
|
260
|
-
scene?: unknown;
|
|
261
|
-
pointer?: unknown;
|
|
262
|
-
activeTool?: unknown;
|
|
263
|
-
[k: string]: unknown;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Discriminated overlay shape returned by `OngoingHandle.overlay()`.
|
|
267
|
-
* Dispatcher-side chrome surface for in-flight
|
|
268
|
-
* gestures that paint non-ghost visuals. The canvas's
|
|
269
|
-
* `useDispatcherOverlayLayer` walks every in-flight handle, calls
|
|
270
|
-
* `overlay()`, and dispatches on `kind` to draw the appropriate shape.
|
|
271
|
-
*
|
|
272
|
-
* `marquee` mirrors `AreaSelectOverlay`; `lasso` mirrors `LassoSelectOverlay`.
|
|
273
|
-
* `commands` is the generic escape hatch — actions emit arbitrary
|
|
274
|
-
* `DrawCommand[]` for previews the typed variants can't express (insert
|
|
275
|
-
* shape outlines, paste ghosts of synthetic nodes, custom chrome). World-
|
|
276
|
-
* space is the default; the layer wraps in `viewToMat3` so commands track
|
|
277
|
-
* the camera. Set `space: 'screen'` for projections you've already done
|
|
278
|
-
* yourself (rare).
|
|
279
|
-
*/
|
|
280
|
-
type OngoingOverlay = {
|
|
281
|
-
kind: 'marquee';
|
|
282
|
-
start: {
|
|
283
|
-
x: number;
|
|
284
|
-
y: number;
|
|
285
|
-
};
|
|
286
|
-
current: {
|
|
287
|
-
x: number;
|
|
288
|
-
y: number;
|
|
289
|
-
};
|
|
290
|
-
shiftHeld: boolean;
|
|
291
|
-
} | {
|
|
292
|
-
kind: 'lasso';
|
|
293
|
-
vertices: ReadonlyArray<{
|
|
294
|
-
x: number;
|
|
295
|
-
y: number;
|
|
296
|
-
}>;
|
|
297
|
-
current: {
|
|
298
|
-
x: number;
|
|
299
|
-
y: number;
|
|
300
|
-
};
|
|
301
|
-
shiftHeld: boolean;
|
|
302
|
-
} | {
|
|
303
|
-
kind: 'commands';
|
|
304
|
-
commands: readonly DrawCommand[];
|
|
305
|
-
/** Coordinate space the commands are authored in. Default `'world'`
|
|
306
|
-
* — the layer wraps them in `viewToMat3(view)` so they track the
|
|
307
|
-
* camera. `'screen'` emits them as-is (CSS pixels). */
|
|
308
|
-
space?: 'world' | 'screen';
|
|
309
|
-
} | {
|
|
310
|
-
/**
|
|
311
|
-
* Live insert-drag preview — dispatched by `insertAction` while the
|
|
312
|
-
* user is dragging out a new shape. Pre-commit there is no scene node
|
|
313
|
-
* to ghost via `previewIds()`/`previewPose()`, so insert paints its
|
|
314
|
-
* preview through the dispatcher overlay layer instead.
|
|
315
|
-
*
|
|
316
|
-
* `shape` is the kit's built-in insert kind. `bounds` is the AABB of
|
|
317
|
-
* the current drag (start/current normalized). `extras` is the
|
|
318
|
-
* per-kind extras the action already collected — the overlay
|
|
319
|
-
* renderer rebuilds the shape using the same path builders the
|
|
320
|
-
* commit factory uses, so the preview matches the eventual node.
|
|
321
|
-
*
|
|
322
|
-
* `extras` is opaque (`unknown`) at the union level; the overlay
|
|
323
|
-
* renderer narrows on `shape` and casts the field shape it expects.
|
|
324
|
-
*/
|
|
325
|
-
kind: 'insertPreview';
|
|
326
|
-
shape: 'rect' | 'ellipse' | 'line' | 'polygon' | 'star' | 'pencil';
|
|
327
|
-
bounds: {
|
|
328
|
-
x: number;
|
|
329
|
-
y: number;
|
|
330
|
-
width: number;
|
|
331
|
-
height: number;
|
|
332
|
-
};
|
|
333
|
-
extras: unknown;
|
|
334
|
-
/** World-space point to paint a small "anchor" dot at. Sells the
|
|
335
|
-
* click point as the drag's anchor — particularly useful for
|
|
336
|
-
* radial shapes (polygon/star) where no vertex sits on the
|
|
337
|
-
* click point, and for any shape in center mode where the dot
|
|
338
|
-
* marks the center the shape grows around. */
|
|
339
|
-
anchorPoint?: {
|
|
340
|
-
x: number;
|
|
341
|
-
y: number;
|
|
342
|
-
};
|
|
343
|
-
};
|
|
344
|
-
/** Handle returned from an `OngoingInvoker.start`. The dispatcher pumps
|
|
345
|
-
* `onMove` on subsequent input events of the same gesture and calls
|
|
346
|
-
* `onEnd` exactly once (with `'commit'` on natural completion or `'cancel'`
|
|
347
|
-
* on pointercancel / blur / escape). */
|
|
348
|
-
interface OngoingHandle {
|
|
349
|
-
/**
|
|
350
|
-
* Optional logical action kind — a stable, human-readable tag the
|
|
351
|
-
* dispatcher exposes via `getActiveAction()` for chrome-visibility
|
|
352
|
-
* rules and any other surface that wants to react to "what action
|
|
353
|
-
* is currently in flight" without inspecting handles directly.
|
|
354
|
-
*
|
|
355
|
-
* Examples: `'marquee'`, `'lasso'`, `'move'`, `'resize'`, `'rotate'`,
|
|
356
|
-
* `'pan'`, `'pinch'`.
|
|
357
|
-
*
|
|
358
|
-
* Distinct from the dispatcher's internal `gestureId` (`pointer-mouse`,
|
|
359
|
-
* `key-held-Space`, etc.) which keys per-pointer state and is not
|
|
360
|
-
* meaningful to consumers.
|
|
361
|
-
*
|
|
362
|
-
* When omitted, the action is "anonymous" — `getActiveAction().kind`
|
|
363
|
-
* reports `null` even though a handle is in flight. This is fine for
|
|
364
|
-
* actions that don't have visible chrome of their own.
|
|
365
|
-
*/
|
|
366
|
-
kind?: string;
|
|
367
|
-
onMove?(ctx: InvocationCtx): void;
|
|
368
|
-
onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
|
|
369
|
-
/**
|
|
370
|
-
* Optional preview surface — dispatcher-side ghost overlay.
|
|
371
|
-
*
|
|
372
|
-
* An ongoing-action implementation may populate `previewIds()` +
|
|
373
|
-
* `previewPose(id)` to expose its in-flight preview state for the
|
|
374
|
-
* canvas's preview-ghost layer (`usePreviewGhostLayer`) to render on
|
|
375
|
-
* top of the committed scene during the gesture.
|
|
376
|
-
*
|
|
377
|
-
* Returning `null` (or omitting the method entirely) means "no preview
|
|
378
|
-
* this gesture" — the canvas will skip this handle as a source.
|
|
379
|
-
*
|
|
380
|
-
* Semantics mirror the tool-side `Tool.previewIds` / `Tool.previewPose`
|
|
381
|
-
* pair: `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
382
|
-
* returns the interim pose for one of those ids (shape opaque — the
|
|
383
|
-
* canvas casts to its `TPose` parameter). The preview-ghost layer
|
|
384
|
-
* merges all sources via first-non-null semantics, with tool-side
|
|
385
|
-
* previews taking precedence over dispatcher-side (preserves
|
|
386
|
-
* backwards-compat during the registry-unification migration).
|
|
387
|
-
*/
|
|
388
|
-
previewIds?(): Iterable<string> | null;
|
|
389
|
-
previewPose?(id: string): unknown | null;
|
|
390
|
-
/**
|
|
391
|
-
* When `false`, the preview-ghost layer paints the ghost AND the
|
|
392
|
-
* source node stays visible at its committed pose. Defaults to
|
|
393
|
-
* `true` (move/resize/rotate semantics: ghost replaces the source
|
|
394
|
-
* during the gesture). Clone overrides to `false` so the original
|
|
395
|
-
* stays put and the ghost appears at the drag target.
|
|
396
|
-
*/
|
|
397
|
-
previewHidesSource?: boolean;
|
|
398
|
-
/**
|
|
399
|
-
* Optional per-id preview *data*. Falls back to the committed
|
|
400
|
-
* `node.data` when null/absent. Use when the gesture mutates
|
|
401
|
-
* `node.data` (e.g. anchor-edit on nodes that store the polygon on
|
|
402
|
-
* `data.path`) rather than (or in addition to) the pose. The preview-
|
|
403
|
-
* ghost layer assembles a synthetic node from `{ ...node, pose:
|
|
404
|
-
* previewPose ?? node.pose, data: previewData ?? node.data }` before
|
|
405
|
-
* calling the scene slot's `drawOne`.
|
|
406
|
-
*
|
|
407
|
-
* Sources compose first-non-null per axis: an action can emit only
|
|
408
|
-
* `previewPose` (translation), only `previewData` (data-only edit),
|
|
409
|
-
* or both (pose + data both change, e.g. anchor drag on a data.path
|
|
410
|
-
* node where the bounds shift).
|
|
411
|
-
*/
|
|
412
|
-
previewData?(id: string): unknown | null;
|
|
413
|
-
/**
|
|
414
|
-
* Optional chrome surface — dispatcher-side overlay layer.
|
|
415
|
-
*
|
|
416
|
-
* An ongoing-action implementation may populate `overlay()` to expose a
|
|
417
|
-
* non-ghost visual (marquee rectangle, lasso polyline) for the canvas's
|
|
418
|
-
* `useDispatcherOverlayLayer` to paint while the gesture is in flight.
|
|
419
|
-
* Returning `null` (or omitting the method) means "no overlay this
|
|
420
|
-
* gesture" — the canvas will skip this handle as a chrome source.
|
|
421
|
-
*
|
|
422
|
-
* Distinct from the `previewIds()`/`previewPose(id)` ghost surface,
|
|
423
|
-
* which paints displaced scene-node silhouettes. Marquee and lasso
|
|
424
|
-
* gestures don't displace any node, but still need on-screen feedback.
|
|
425
|
-
*/
|
|
426
|
-
overlay?(): OngoingOverlay | null;
|
|
427
|
-
}
|
|
428
|
-
/** Fire-once invocation. Runs to completion synchronously (or fires off an
|
|
429
|
-
* async side-effect; the registry doesn't wait). */
|
|
430
|
-
interface ImmediateInvoker {
|
|
431
|
-
timing: 'immediate';
|
|
432
|
-
/** `params` carries the matched binding's opts.params. When
|
|
433
|
-
* invoked via the legacy `Action.run` bridge or from the command palette
|
|
434
|
-
* with no per-binding context, `params` is undefined; descriptors should
|
|
435
|
-
* default to a sensible variant. */
|
|
436
|
-
run(deps: ActionDeps, params?: Record<string, unknown>): void;
|
|
437
|
-
}
|
|
438
|
-
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
439
|
-
* the dispatcher pumps. */
|
|
440
|
-
interface OngoingInvoker {
|
|
441
|
-
timing: 'ongoing';
|
|
442
|
-
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
443
|
-
}
|
|
444
|
-
/** Pluggable invocation strategy for an Action. Future variants
|
|
445
|
-
* (`longPress`, `twoStage`, `modal`) extend this union without touching
|
|
446
|
-
* the `Action` type. */
|
|
447
|
-
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
451
|
-
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
452
|
-
* gesture-bindings are registered globally.
|
|
453
|
-
*
|
|
454
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`.
|
|
455
|
-
*/
|
|
456
|
-
|
|
457
|
-
interface GestureBinding {
|
|
458
|
-
spec: GestureSpec;
|
|
459
|
-
actionId: string;
|
|
460
|
-
opts?: BindingOpts;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Configurable activation-key descriptor for tools that expose their
|
|
465
|
-
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
466
|
-
* only the fields meaningful to a caller-supplied tool-select key —
|
|
467
|
-
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
468
|
-
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
469
|
-
* not part of the configurable surface.
|
|
470
|
-
*/
|
|
471
|
-
interface ToolKeybinding {
|
|
472
|
-
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
473
|
-
key: string | readonly string[];
|
|
474
|
-
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
475
|
-
mod?: boolean;
|
|
476
|
-
/** Require Alt. Default `false`. */
|
|
477
|
-
alt?: boolean;
|
|
478
|
-
/**
|
|
479
|
-
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
480
|
-
* shift, `'optional'` allows either.
|
|
481
|
-
*/
|
|
482
|
-
shift?: boolean | 'optional';
|
|
483
|
-
}
|
|
484
|
-
type ActionFn<TScratch> = (ctx: ToolCtx<TScratch>, event?: PointerEvent | KeyboardEvent | WheelEvent) => Result<TScratch>;
|
|
485
|
-
type ModifierRoute<TScratch> = Partial<Record<ModifierCombo, ActionFn<TScratch>>>;
|
|
486
|
-
type RouteEntry<TScratch> = ActionFn<TScratch> | ModifierRoute<TScratch>;
|
|
487
|
-
type RouteTable<TScratch> = Partial<Record<string, RouteEntry<TScratch>>>;
|
|
488
|
-
/** Map from key-route string (e.g. `"ArrowDown"`, `"ArrowDown?shift"`) to
|
|
489
|
-
* action. Parsed by the reflection emitter and dispatcher. */
|
|
490
|
-
type KeyRouteTable<TScratch> = Partial<Record<string, ActionFn<TScratch>>>;
|
|
491
|
-
/** Wheel direction sub-table. Keys: `'up' | 'down' | '*'`. Function form
|
|
492
|
-
* is sugar for `{ '*': fn }` (matches both directions). */
|
|
493
|
-
type WheelTable<TScratch> = Partial<Record<'up' | 'down' | '*', ActionFn<TScratch>>>;
|
|
494
|
-
/** MultiTouch tap fingers sub-table. Keys: `'2' | '3' | '4'`. */
|
|
495
|
-
type MultiTouchTapTable<TScratch> = Partial<Record<'2' | '3' | '4', ActionFn<TScratch>>>;
|
|
496
|
-
interface PhaseDef<TScratch> {
|
|
497
|
-
click?: RouteTable<TScratch>;
|
|
498
|
-
/** Pre-threshold classifier route. Runs synchronously on pointerdown,
|
|
499
|
-
* before the dispatcher distinguishes click vs. drag. Use this for
|
|
500
|
-
* classification gestures that need to mutate scratch *before* the
|
|
501
|
-
* drag pipeline starts — e.g. select tool determining whether a hit
|
|
502
|
-
* belongs to the existing selection ("drag will move all") or not
|
|
503
|
-
* ("drag will move just this one").
|
|
504
|
-
*
|
|
505
|
-
* Semantics:
|
|
506
|
-
* - Return `begin(spec)` to open engaged phase with scratch. The
|
|
507
|
-
* spec's `onMove`/`onRelease` will fire if the dispatcher escalates
|
|
508
|
-
* to drag; otherwise the next click handler runs normally with the
|
|
509
|
-
* prepared scratch visible.
|
|
510
|
-
* - Return `apply(ops)` or `commit(ops)` to finish the gesture
|
|
511
|
-
* immediately (rare).
|
|
512
|
-
* - Return `none()` or omit to pass through to threshold-gated
|
|
513
|
-
* click/drag classification.
|
|
514
|
-
*
|
|
515
|
-
* Predates the imperative
|
|
516
|
-
* `pointer.onDown` channel that useSelectTool used earlier. */
|
|
517
|
-
pointerDown?: RouteTable<TScratch>;
|
|
518
|
-
dblTap?: RouteTable<TScratch>;
|
|
519
|
-
/** Right-click route table. Mirrors `click` semantics — keyed by hit-test
|
|
520
|
-
* target, modifier-aware. The dispatcher calls `preventDefault()` on the
|
|
521
|
-
* underlying `contextmenu` DOM event so tools fully own the menu. */
|
|
522
|
-
contextMenu?: RouteTable<TScratch>;
|
|
523
|
-
drag?: RouteTable<TScratch> | ActionFn<TScratch>;
|
|
524
|
-
wheel?: WheelTable<TScratch> | ActionFn<TScratch>;
|
|
525
|
-
keyDown?: KeyRouteTable<TScratch>;
|
|
526
|
-
keyUp?: KeyRouteTable<TScratch>;
|
|
527
|
-
/** Multi-finger tap. Synthesized by the dispatcher when a multitouch
|
|
528
|
-
* gesture releases without movement past the tap threshold. Keys: the
|
|
529
|
-
* fingers count as a string (`'2'`, `'3'`, `'4'`). */
|
|
530
|
-
multiTouchTap?: MultiTouchTapTable<TScratch>;
|
|
531
|
-
cursor?: string | ((ctx: ToolCtx<TScratch>) => string);
|
|
532
|
-
/** Optional overlay layer rendered while the tool is in any active slot
|
|
533
|
-
* (active, hotkey, or ambient). The factory evaluates the thunk once
|
|
534
|
-
* at translation time and emits the resulting RenderLayer on
|
|
535
|
-
* Tool.overlay. The layer's `draw` closure should read dynamic state
|
|
536
|
-
* (scratch, controller overlay snapshots) via refs/closures captured
|
|
537
|
-
* in the enclosing render scope — same pattern hand-rolled
|
|
538
|
-
* tools use today.
|
|
539
|
-
*
|
|
540
|
-
* Function form rather than a direct RenderLayer so consumers can
|
|
541
|
-
* defer construction until inside a `useMemo` factory body, where
|
|
542
|
-
* `useRef`-backed values are stable. Symmetric with `cursor`'s
|
|
543
|
-
* function form.
|
|
544
|
-
*
|
|
545
|
-
* Note: only `initial.overlay` is read. If `engaged.overlay`
|
|
546
|
-
* is set, it is ignored — phase-specific overlay routing is a future
|
|
547
|
-
* enhancement. Tools that need engagement-aware previews should gate
|
|
548
|
-
* inside the single overlay's `draw` body via `if (!scratch.somefield)
|
|
549
|
-
* return []`, which is how every hand-rolled tool already
|
|
550
|
-
* does it. */
|
|
551
|
-
overlay?: () => RenderLayer<unknown>;
|
|
552
|
-
/** Modal-claim predicate. When this resolves to `true`, the dispatcher
|
|
553
|
-
* routes every pointerdown to this tool and bypasses the affordance-layer
|
|
554
|
-
* hit-test pipeline — used by tools in modal states (pen mid-path, text
|
|
555
|
-
* mid-edit) where affordance hits would otherwise interrupt the
|
|
556
|
-
* in-progress gesture.
|
|
557
|
-
*
|
|
558
|
-
* Function form receives the live ToolCtx (scratch, view, modifiers,
|
|
559
|
-
* target). Boolean form is sugar for `() => true` / `() => false` —
|
|
560
|
-
* use the function form when the decision depends on scratch state
|
|
561
|
-
* (e.g. `(ctx) => ctx.scratch?.midPath === true`).
|
|
562
|
-
*
|
|
563
|
-
* Resolved per-call by the factory — the function fires on every
|
|
564
|
-
* pointerdown the dispatcher considers handing to this tool. Keep it
|
|
565
|
-
* cheap (no allocations, just a scratch read). */
|
|
566
|
-
claimsAll?: boolean | ((ctx: ToolCtx<TScratch>) => boolean);
|
|
567
|
-
}
|
|
568
|
-
interface ToolDef<TScratch = void> {
|
|
569
|
-
id: string;
|
|
570
|
-
/** Capability tags for modality eligibility. Forwarded onto `Tool.capabilities`. */
|
|
571
|
-
capabilities?: CapabilityTag[];
|
|
572
|
-
/** Hook name as exported from the kit barrel (e.g. `'useHandTool'`).
|
|
573
|
-
* Set by built-in hooks for inspector / debugging. Consumer-authored
|
|
574
|
-
* tools may set this to surface their hook name; omitted is fine.
|
|
575
|
-
* Introspection-only — do not make this load-bearing in production.
|
|
576
|
-
* Read off the def via `Tool.def` (the reflection escape hatch). */
|
|
577
|
-
hookName?: string;
|
|
578
|
-
presentation?: ToolPresentation<TScratch>;
|
|
579
|
-
/** Optional caller-supplied activation key. Most built-in tools have their
|
|
580
|
-
* activation key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`;
|
|
581
|
-
* this field is for tools that want their activation key to be
|
|
582
|
-
* configurable by the host (currently Lasso and Eyedropper). The dynamic
|
|
583
|
-
* loop in `useKeybindings.ts` picks this up and appends a binding entry
|
|
584
|
-
* to the consolidated `tool.activate` action (with `opts.params.toolId`
|
|
585
|
-
* set so the invoker knows which tool to switch to). */
|
|
586
|
-
keybinding?: ToolKeybinding;
|
|
587
|
-
/** Declarative held-key trigger (reflection / inspector only). When set,
|
|
588
|
-
* signals to the host that this tool can engage via a held key; the host
|
|
589
|
-
* must register the activation via the consolidated `tool.offhand` action
|
|
590
|
-
* (`makeToolOffhandAction` + `buildToolOffhandBindings`). Built-in tools
|
|
591
|
-
* declare held keys in `BUILTIN_OFFHAND_ACTIONS`; configurable-hotkey
|
|
592
|
-
* tools rely on the host to wire the binding. Setting this field does NOT
|
|
593
|
-
* automatically engage the held-key behavior. */
|
|
594
|
-
hotkey?: HotkeyTrigger;
|
|
595
|
-
onActivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
596
|
-
onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
597
|
-
cursor?: string | ((ctx: ToolCtx<TScratch>) => string);
|
|
598
|
-
/** Override the default scratch initializer. Default is `() => null`
|
|
599
|
-
* cast to `TScratch`, which works for tools whose scratch is fresh
|
|
600
|
-
* every gesture. Tools that need scratch identity to survive across
|
|
601
|
-
* gesture boundaries (e.g. the pen tool's multi-click subpath state)
|
|
602
|
-
* pass a stable-ref-returning thunk here. The factory forwards this
|
|
603
|
-
* onto the returned `Tool.initScratch`. */
|
|
604
|
-
initScratch?: () => TScratch;
|
|
605
|
-
/** Optional hit-test override. Mirrors `Tool.hitOverride` — see that
|
|
606
|
-
* field for full semantics. The factory forwards this directly onto the
|
|
607
|
-
* returned Tool. */
|
|
608
|
-
hitOverride?: (ctx: {
|
|
609
|
-
worldX: number;
|
|
610
|
-
worldY: number;
|
|
611
|
-
scratch: TScratch;
|
|
612
|
-
view: View;
|
|
613
|
-
modifiers: ToolModifiers;
|
|
614
|
-
}) => {
|
|
615
|
-
target: string;
|
|
616
|
-
extra?: unknown;
|
|
617
|
-
} | null;
|
|
618
|
-
/** Declarative gesture-bindings forwarded onto `Tool.bindings`.
|
|
619
|
-
* The new dispatcher consults these while this tool is active. */
|
|
620
|
-
bindings?: GestureBinding[];
|
|
621
|
-
initial: PhaseDef<TScratch>;
|
|
622
|
-
engaged?: PhaseDef<TScratch>;
|
|
623
|
-
}
|
|
624
|
-
/** Viewport-tool spec — strict subset of ToolDef. Drops click/dblTap,
|
|
625
|
-
* narrows drag to plain ActionFn. Mechanically derived via Pick/Omit
|
|
626
|
-
* so the subset relationship is compiler-enforced. */
|
|
627
|
-
type ViewportPhaseDef<TScratch = void> = Pick<PhaseDef<TScratch>, 'wheel' | 'keyDown' | 'keyUp' | 'cursor' | 'overlay' | 'claimsAll'> & {
|
|
628
|
-
drag?: ActionFn<TScratch>;
|
|
629
|
-
};
|
|
630
|
-
type ViewportToolDef<TScratch = void> = Omit<ToolDef<TScratch>, 'initial' | 'engaged'> & {
|
|
631
|
-
initial: ViewportPhaseDef<TScratch>;
|
|
632
|
-
engaged?: ViewportPhaseDef<TScratch>;
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
/** Common payload for any hit that references a scene node. */
|
|
636
|
-
interface NodeRef {
|
|
637
|
-
id: NodeId;
|
|
638
|
-
pose: unknown;
|
|
639
|
-
data: unknown;
|
|
640
|
-
meta?: Record<string, unknown>;
|
|
641
|
-
}
|
|
642
|
-
/** No hit — pointer landed on the background. */
|
|
643
|
-
interface EmptyHit {
|
|
644
|
-
category: 'empty';
|
|
645
|
-
kind: 'empty';
|
|
646
|
-
}
|
|
647
|
-
/** Hit on a scene node's body. */
|
|
648
|
-
interface NodeHit extends NodeRef {
|
|
649
|
-
category: 'node';
|
|
650
|
-
kind: string;
|
|
651
|
-
}
|
|
652
|
-
/** Hit on a node's affordance chrome (handle, anchor, etc.). */
|
|
653
|
-
interface AffordanceHit extends NodeRef {
|
|
654
|
-
category: 'affordance';
|
|
655
|
-
kind: string;
|
|
656
|
-
}
|
|
657
|
-
/** Hit on a tool-defined target (anchor, handle, segment, etc.) supplied
|
|
658
|
-
* via the tool's `hitOverride`. The `kind` string is the tool's own
|
|
659
|
-
* vocabulary; the dispatcher does not interpret it. */
|
|
660
|
-
interface ToolHit {
|
|
661
|
-
category: 'tool';
|
|
662
|
-
kind: string;
|
|
663
|
-
extra?: unknown;
|
|
664
|
-
}
|
|
665
|
-
/** Full discriminated union — every routed action's `ctx.target`. */
|
|
666
|
-
type HitResult = EmptyHit | NodeHit | AffordanceHit | ToolHit;
|
|
667
|
-
/** Convenience: any hit that references a node (i.e., not EmptyHit). */
|
|
668
|
-
type NodeRefHit = NodeHit | AffordanceHit;
|
|
669
|
-
|
|
670
|
-
/** Phase the route was resolved against. Mirrors the spec's two-phase
|
|
671
|
-
* vocabulary — `initial` (idle, scratch null) or `engaged` (mid-gesture). */
|
|
672
|
-
type RoutePhase = 'initial' | 'engaged';
|
|
673
|
-
/** Gesture channel the route fired on. */
|
|
674
|
-
type RouteGesture = GestureName;
|
|
675
|
-
/** Snapshot of one route resolution, emitted by the factory on each
|
|
676
|
-
* successful lookup. Captured by the dispatcher as the "last resolved
|
|
677
|
-
* route" for debug-overlay consumers. */
|
|
678
|
-
interface RouteResolvedInfo {
|
|
679
|
-
toolId: string;
|
|
680
|
-
phase: RoutePhase;
|
|
681
|
-
gesture: RouteGesture;
|
|
682
|
-
/** Argument captured at match time for arg-bearing gestures
|
|
683
|
-
* (`wheel` direction, `keyDown`/`keyUp` key, `multiTouchTap` fingers).
|
|
684
|
-
* Undefined for no-arg gestures. */
|
|
685
|
-
arg: string | undefined;
|
|
686
|
-
/** Route-table key that matched (post-precedence). E.g. 'rect:selected',
|
|
687
|
-
* '*:selected', 'rect', '*', 'empty'. For function-form `drag` (no
|
|
688
|
-
* table), this is '*'. For keyDown/keyUp, it's the key name ('Escape',
|
|
689
|
-
* 'Enter', etc.). */
|
|
690
|
-
matchedKey: string;
|
|
691
|
-
modifiers: ModifierCombo;
|
|
692
|
-
/** The full HitResult at resolution time (snapshot — safe to read). */
|
|
693
|
-
target: HitResult;
|
|
694
|
-
/** Monotonic timestamp (ms since page load via performance.now()).
|
|
695
|
-
* Used for "resolved Nms ago" displays. */
|
|
696
|
-
timestamp: number;
|
|
697
|
-
}
|
|
698
|
-
/** Render-friendly one-line string. Used by ToolDebugOverlay; exported
|
|
699
|
-
* so non-React consumers can format the same way. */
|
|
700
|
-
declare function formatRouteResolved(info: RouteResolvedInfo): string;
|
|
701
|
-
|
|
702
|
-
/** Outcome of a channel handler. `'claim'` stops dispatch for this event;
|
|
703
|
-
* `'pass'` lets the next slot try. Handlers that return nothing are
|
|
704
|
-
* treated as `'pass'`. */
|
|
705
|
-
type Decision = 'claim' | 'pass' | void;
|
|
706
|
-
/** Modifier-key snapshot at event dispatch time. `space` is included
|
|
707
|
-
* because tools commonly use space as a hotkey-slot trigger and may
|
|
708
|
-
* also want to read it as a flag mid-gesture. */
|
|
709
|
-
interface ToolModifiers {
|
|
710
|
-
alt: boolean;
|
|
711
|
-
shift: boolean;
|
|
712
|
-
meta: boolean;
|
|
713
|
-
ctrl: boolean;
|
|
714
|
-
space: boolean;
|
|
715
|
-
}
|
|
716
|
-
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
717
|
-
* via the tool's `TScratch` parameter; it survives across a single
|
|
718
|
-
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
719
|
-
* gesture start by `initScratch()`. */
|
|
720
|
-
interface ToolCtx<TScratch = unknown> {
|
|
721
|
-
worldX: number;
|
|
722
|
-
worldY: number;
|
|
723
|
-
modifiers: ToolModifiers;
|
|
724
|
-
/** Hit-test result for the current event. Populated by the dispatcher
|
|
725
|
-
* before each handler call. Tools that don't use declarative routing
|
|
726
|
-
* can ignore this. Optional for migration; will become
|
|
727
|
-
* required once the routing migration is complete. */
|
|
728
|
-
target?: HitResult;
|
|
729
|
-
selection: SelectionApi;
|
|
730
|
-
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
731
|
-
* cast to a known shape. This layer doesn't constrain it. */
|
|
732
|
-
adapter: unknown;
|
|
733
|
-
applyOps: (ops: Op[], label: string) => void;
|
|
734
|
-
/** Current viewport. Reflects camera-position semantics — see
|
|
735
|
-
* `View` JSDoc. */
|
|
736
|
-
view: View;
|
|
737
|
-
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
738
|
-
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
739
|
-
* state. View changes are not undoable. */
|
|
740
|
-
setView: (next: View) => void;
|
|
741
|
-
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
742
|
-
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
743
|
-
* anchors. */
|
|
744
|
-
canvasRect: DOMRect;
|
|
745
|
-
/** Screen-space pointer coords relative to `canvasRect`. Useful for
|
|
746
|
-
* viewport tools that pan/zoom in screen space (e.g. hand-pan
|
|
747
|
-
* computes deltas in pixels, not world units). Optional — populated
|
|
748
|
-
* by the dispatcher on pointer events; absent on keyboard events. */
|
|
749
|
-
screenPoint?: {
|
|
750
|
-
x: number;
|
|
751
|
-
y: number;
|
|
752
|
-
};
|
|
753
|
-
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
754
|
-
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
755
|
-
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
756
|
-
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
757
|
-
debug?: DebugSink;
|
|
758
|
-
/** Kit-internal: route-resolution reporter. The dispatcher populates
|
|
759
|
-
* this; the declarative routing factory calls it after each successful
|
|
760
|
-
* resolveRoute() hit so the dispatcher can publish the last-resolved
|
|
761
|
-
* snapshot to debug-overlay consumers. Underscore prefix signals
|
|
762
|
-
* "do not consume in tool code." */
|
|
763
|
-
__reportRoute?: (info: RouteResolvedInfo) => void;
|
|
764
|
-
scratch: TScratch;
|
|
765
|
-
}
|
|
766
|
-
interface PointerChannel<TScratch> {
|
|
767
|
-
onDown?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
768
|
-
onClick?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
769
|
-
}
|
|
770
|
-
interface DragChannel<TScratch> {
|
|
771
|
-
onStart?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
772
|
-
onMove?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
773
|
-
onEnd?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
774
|
-
onCancel?: (ctx: ToolCtx<TScratch>) => void;
|
|
775
|
-
}
|
|
776
|
-
interface KeyboardChannel<TScratch> {
|
|
777
|
-
onDown?: (e: KeyboardEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
778
|
-
onUp?: (e: KeyboardEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
779
|
-
}
|
|
780
|
-
interface WheelChannel<TScratch> {
|
|
781
|
-
onWheel?: (e: WheelEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
782
|
-
}
|
|
783
|
-
/** Double-tap (double-click) channel. Fires on the pointerup of the second
|
|
784
|
-
* sub-threshold tap when it follows a previous sub-threshold tap within the
|
|
785
|
-
* dispatcher's `dblTap.windowMs` and `dblTap.maxDistance` (CSS px). The
|
|
786
|
-
* scratch handed to the handler is fresh — `dblTap` is not part of a drag
|
|
787
|
-
* pipeline, so `initScratch()` runs immediately before the call. A `'claim'`
|
|
788
|
-
* return suppresses the regular `pointer.onClick` for this gesture. */
|
|
789
|
-
interface DblTapChannel<TScratch> {
|
|
790
|
-
onTap?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
|
|
791
|
-
}
|
|
792
|
-
/** Hotkey-slot trigger key. The slot is engaged while this key is held —
|
|
793
|
-
* hence "hotkey": active as long as the key is hot. `null` (or omitted)
|
|
794
|
-
* means the tool is not eligible for the hotkey slot. */
|
|
795
|
-
type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift';
|
|
796
|
-
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
797
|
-
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
798
|
-
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
799
|
-
type ToolBounds = Bounds;
|
|
800
|
-
/** Presentation metadata for tool palettes / menus. Optional on every
|
|
801
|
-
* tool — consumers that render a palette (`<ToolPalette>`) read these
|
|
802
|
-
* fields to display the tool; consumers that don't can ignore them.
|
|
803
|
-
*
|
|
804
|
-
* Note: cursor is NOT here. The top-level `Tool.cursor` field below is
|
|
805
|
-
* already plumbed through `<Canvas>` to `style.cursor` on the host. */
|
|
806
|
-
interface ToolPresentation<TScratch = unknown> {
|
|
807
|
-
/** Human-readable label, distinct from the `id`. Falls back to `id`. */
|
|
808
|
-
label?: string;
|
|
809
|
-
/** Inline-SVG icon component output. May be a static `ReactNode` or a
|
|
810
|
-
* function of scratch state (rare; useful for shape-aware affordances). */
|
|
811
|
-
icon?: React.ReactNode | ((scratch?: TScratch) => React.ReactNode);
|
|
812
|
-
/** Palette grouping key. Tools sharing a group render contiguously
|
|
813
|
-
* with separators between groups. Free-form string; the kit
|
|
814
|
-
* recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
|
|
815
|
-
group?: string;
|
|
816
|
-
/** Display override for the keyboard shortcut. When omitted the palette
|
|
817
|
-
* derives one from `Tool.keybinding` via its own formatter. */
|
|
818
|
-
shortcut?: string;
|
|
819
|
-
}
|
|
820
|
-
/** Full Tool record. */
|
|
821
|
-
interface Tool<TScratch = unknown> {
|
|
822
|
-
id: string;
|
|
823
|
-
/**
|
|
824
|
-
* App-level capability tags for modality. The `weasel-modes` package's
|
|
825
|
-
* `eligibleForMode(mode, capabilities)` predicate consumes these to decide
|
|
826
|
-
* whether the tool is usable in the active mode. Tags are extensible
|
|
827
|
-
* strings — apps can define their own. Untagged tools are treated as
|
|
828
|
-
* ineligible by all modes except those whose `allows` list includes
|
|
829
|
-
* every implicit-or-declared tag (i.e. `normal` in the default preset).
|
|
830
|
-
*/
|
|
831
|
-
capabilities?: CapabilityTag[];
|
|
832
|
-
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
833
|
-
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
834
|
-
* is for tools that want their activation key to be configurable by the
|
|
835
|
-
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
836
|
-
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
837
|
-
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
838
|
-
* the invoker knows which tool to switch to). */
|
|
839
|
-
keybinding?: ToolKeybinding;
|
|
840
|
-
initScratch?: () => TScratch;
|
|
841
|
-
onActivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
842
|
-
onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
843
|
-
pointer?: PointerChannel<TScratch>;
|
|
844
|
-
drag?: DragChannel<TScratch>;
|
|
845
|
-
keyboard?: KeyboardChannel<TScratch>;
|
|
846
|
-
wheel?: WheelChannel<TScratch>;
|
|
847
|
-
/** Double-tap channel — fires when two sub-threshold taps land within
|
|
848
|
-
* `dblTap.windowMs` / `dblTap.maxDistance` of each other. Lets tools
|
|
849
|
-
* enter modal modes (e.g. select → edit-anchors) without consumers
|
|
850
|
-
* attaching `onDoubleClick` to a wrapper DOM node. */
|
|
851
|
-
dblTap?: DblTapChannel<TScratch>;
|
|
852
|
-
/**
|
|
853
|
-
* State-aware predicate. When true, this tool claims every pointerdown
|
|
854
|
-
* and bypasses the affordance layer hit-test pipeline. Used by tools
|
|
855
|
-
* in modal states (pen mid-path, text mid-edit) where affordance hits
|
|
856
|
-
* would otherwise interrupt the in-progress gesture.
|
|
857
|
-
*
|
|
858
|
-
* Default: undefined (treated as false). Called once per pointerdown
|
|
859
|
-
* with the tool's current ctx (scratch + view + modifiers).
|
|
860
|
-
*/
|
|
861
|
-
claimsAll?: (ctx: ToolCtx<TScratch>) => boolean;
|
|
862
|
-
cursor?: string | ((ctx: ToolCtx<TScratch>) => string);
|
|
863
|
-
/** Presentation metadata for tool palettes. See `ToolPresentation`. */
|
|
864
|
-
presentation?: ToolPresentation<TScratch>;
|
|
865
|
-
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
866
|
-
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
867
|
-
* reflect live gesture state without reaching into hook internals. The
|
|
868
|
-
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
869
|
-
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
870
|
-
* the use site. */
|
|
871
|
-
previewPose?: (id: string) => unknown;
|
|
872
|
-
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
873
|
-
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
874
|
-
* can compute bounds without round-tripping through a geometry adapter. */
|
|
875
|
-
previewBounds?: (id: string) => ToolBounds | null;
|
|
876
|
-
/** Returns ids whose committed scene-render should be suppressed while this
|
|
877
|
-
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
878
|
-
* preview ghosts replace the committed pose). The standard scene slot
|
|
879
|
-
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
880
|
-
* `null` when no gesture is in flight. */
|
|
881
|
-
previewIds?: () => Iterable<string> | null;
|
|
882
|
-
/** Optional overlay layer rendered on top of the scene/chrome whenever
|
|
883
|
-
* this tool is in any active slot (active, hotkey, or ambient).
|
|
884
|
-
* The layer's `draw` function reads from this tool's scratch via React
|
|
885
|
-
* closure (re-evaluated each render). Return early from `draw` to render
|
|
886
|
-
* nothing — typically gated on a scratch field like
|
|
887
|
-
* `if (!scratch.overlay) return`. */
|
|
888
|
-
overlay?: RenderLayer<unknown>;
|
|
889
|
-
/** Declarative gesture-bindings the
|
|
890
|
-
* dispatcher consults while this tool is active. Empty/undefined keeps
|
|
891
|
-
* legacy imperative-channel behavior. See
|
|
892
|
-
* `docs/superpowers/specs/2026-05-16-registry-unification-design.md`. */
|
|
893
|
-
bindings?: GestureBinding[];
|
|
894
|
-
/**
|
|
895
|
-
* Optional. When set, the dispatcher consults this before its built-in
|
|
896
|
-
* node/empty hit-test. If it returns a value, that target replaces the
|
|
897
|
-
* default `target` on the routed action's ctx. The string `target` is
|
|
898
|
-
* the tool's own vocabulary — the dispatcher does not interpret it.
|
|
899
|
-
*
|
|
900
|
-
* Used for tools that need richer sub-object hit categories (e.g., pen
|
|
901
|
-
* edit-mode's anchor/handle/segment vs the default node/empty).
|
|
902
|
-
*/
|
|
903
|
-
hitOverride?(ctx: {
|
|
904
|
-
worldX: number;
|
|
905
|
-
worldY: number;
|
|
906
|
-
scratch: TScratch;
|
|
907
|
-
view: View;
|
|
908
|
-
modifiers: ToolModifiers;
|
|
909
|
-
}): {
|
|
910
|
-
target: string;
|
|
911
|
-
extra?: unknown;
|
|
912
|
-
} | null;
|
|
913
|
-
/** Reflection escape hatch: when this `Tool` was produced by `defineTool`,
|
|
914
|
-
* the source `ToolDef` is attached here so introspection consumers
|
|
915
|
-
* (`buildActionRegistry`, `findConflicts`, the toolkit-builder UI, the
|
|
916
|
-
* reflection demo) can walk the declarative source rather than the
|
|
917
|
-
* translated runtime channels. Tools constructed without `defineTool`
|
|
918
|
-
* may leave this undefined. Typed as `unknown` to keep this file from
|
|
919
|
-
* importing the routing types — consumers cast at the use site. */
|
|
920
|
-
def?: unknown;
|
|
921
|
-
}
|
|
922
|
-
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
923
|
-
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
924
|
-
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
925
|
-
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
926
|
-
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
927
|
-
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
928
|
-
type AnyTool = Tool<any>;
|
|
929
|
-
|
|
930
|
-
/**
|
|
931
|
-
* Read-only state that affordances consult on every render and hit-test
|
|
932
|
-
* call. Built once per Canvas render via `buildChromeState`; affordances
|
|
933
|
-
* must not cache it across calls.
|
|
934
|
-
*/
|
|
935
|
-
interface ChromeState {
|
|
936
|
-
/** Currently selected ids. Live; reflects useSelection's React state. */
|
|
937
|
-
readonly selection: readonly NodeId[];
|
|
938
|
-
/** True when the canvas is in multi-mode AND >= 2 ids are selected. */
|
|
939
|
-
readonly multiActive: boolean;
|
|
940
|
-
/** Bounds for any selection member id. Honors active-tool overlay state
|
|
941
|
-
* (move/resize/rotate ghosts → ghost bounds; otherwise → committed
|
|
942
|
-
* pose bounds). Returns null for unknown ids or ids whose bounds aren't
|
|
943
|
-
* computable. */
|
|
944
|
-
boundsOf(id: string): Bounds | null;
|
|
945
|
-
/** Multi-union AABB when `multiActive`. Computed lazily from `boundsOf`
|
|
946
|
-
* over every selected id; null otherwise. */
|
|
947
|
-
readonly unionBounds: Bounds | null;
|
|
948
|
-
/** Active modifier state at the moment of the call. */
|
|
949
|
-
readonly modifiers: ModifierState;
|
|
950
|
-
/** True iff the node's pose-descriptor declares it can carry a rotation.
|
|
951
|
-
* Affordances consult this to decide whether to expose rotate cursors /
|
|
952
|
-
* drag-bands. Defaults to `true` when the descriptor doesn't declare
|
|
953
|
-
* (back-compat) or when the id is unknown — the rotation gesture will
|
|
954
|
-
* no-op visually for poses without AABB fields, but the affordance
|
|
955
|
-
* doesn't lie about the cursor. Optional on the interface so unit-test
|
|
956
|
-
* call sites that construct `ChromeState` by hand keep compiling;
|
|
957
|
-
* affordances should treat an absent predicate as "true". */
|
|
958
|
-
canRotate?(id: string): boolean;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* @experimental
|
|
963
|
-
* A single interactive piece of chrome. Pure functions; the kit composes
|
|
964
|
-
* multiple affordances into a single RenderLayer per tool via
|
|
965
|
-
* `composeAffordanceLayer`.
|
|
966
|
-
*
|
|
967
|
-
* Affordances declare interactive regions in a target's *local* frame.
|
|
968
|
-
* The framework (`composeAffordanceLayer`) composes the target's bounds
|
|
969
|
-
* transform (rotation around the AABB center, when present) for both paint
|
|
970
|
-
* and hit-test, so the affordance never touches rotation, view.scale, or
|
|
971
|
-
* world↔screen math.
|
|
972
|
-
*/
|
|
973
|
-
interface Affordance {
|
|
974
|
-
/** Stable id for debug overlays + visibility maps. */
|
|
975
|
-
id: string;
|
|
976
|
-
/** Enumerate this affordance's interactive regions. Each region lives in
|
|
977
|
-
* some target id's local frame (or in the world frame when `targetId`
|
|
978
|
-
* is `null`). Returning `[]` means "no chrome for this state" (no
|
|
979
|
-
* selection, multi-mode disabled, etc.). */
|
|
980
|
-
regions(state: ChromeState): readonly AffordanceRegion[];
|
|
981
|
-
/** Optional non-interactive decoration (e.g., a leader line drawn from
|
|
982
|
-
* a bounds edge to a handle — visual only, not draggable). Receives
|
|
983
|
-
* raw state + view because the decoration may live outside any single
|
|
984
|
-
* target's local frame. Most affordances leave this undefined. */
|
|
985
|
-
decorate?(state: ChromeState, view: View): DrawCommand[];
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* @experimental
|
|
989
|
-
* One interactive region produced by an affordance. The framework owns
|
|
990
|
-
* the local↔world transform for `targetId` (when non-null), so `shape`,
|
|
991
|
-
* `paint.sizePx`, and `hitRadiusPx` are always specified in coordinates
|
|
992
|
-
* the affordance can reason about directly.
|
|
993
|
-
*/
|
|
994
|
-
interface AffordanceRegion<TScratch = unknown> {
|
|
995
|
-
/** Stable id, e.g. `corner-min-min`. Used for debug overlays + a11y. */
|
|
996
|
-
id: string;
|
|
997
|
-
/** Target id whose `state.boundsOf(targetId)` defines this region's
|
|
998
|
-
* local frame. `bounds.rotation` (if present) is the only transform
|
|
999
|
-
* applied — translation is the AABB origin; scale is identity. Pass
|
|
1000
|
-
* `null` for affordances anchored to the viewport / world frame
|
|
1001
|
-
* (identity transform). */
|
|
1002
|
-
targetId: string | null;
|
|
1003
|
-
/** Region geometry, expressed in the target's local frame.
|
|
1004
|
-
*
|
|
1005
|
-
* - `point` — circular hit (`hitRadiusPx` is screen-space).
|
|
1006
|
-
* - `rect` — axis-aligned rect (target rotation applies).
|
|
1007
|
-
* - `annulus` — outer ellipse minus inner rect cutout. Used for
|
|
1008
|
-
* invisible zones that sit *around* the AABB (e.g. rotate-on-
|
|
1009
|
-
* hover band). The outer ellipse is defined by world-space
|
|
1010
|
-
* semi-axes `rx` / `ry` around `(cx, cy)`; the inner rect is the
|
|
1011
|
-
* same target-local rect that defines the AABB. Hit-test:
|
|
1012
|
-
* inside outer ellipse AND outside inner rect. */
|
|
1013
|
-
shape: {
|
|
1014
|
-
kind: 'point';
|
|
1015
|
-
x: number;
|
|
1016
|
-
y: number;
|
|
1017
|
-
hitRadiusPx: number;
|
|
1018
|
-
} | {
|
|
1019
|
-
kind: 'rect';
|
|
1020
|
-
x: number;
|
|
1021
|
-
y: number;
|
|
1022
|
-
width: number;
|
|
1023
|
-
height: number;
|
|
1024
|
-
} | {
|
|
1025
|
-
kind: 'annulus';
|
|
1026
|
-
/** Outer-ellipse center (target-local). */
|
|
1027
|
-
cx: number;
|
|
1028
|
-
cy: number;
|
|
1029
|
-
/** Outer-ellipse semi-axes (target-local). */
|
|
1030
|
-
rx: number;
|
|
1031
|
-
ry: number;
|
|
1032
|
-
/** Inner rect (target-local) — the cutout. Typically the
|
|
1033
|
-
* selection's AABB. */
|
|
1034
|
-
innerX: number;
|
|
1035
|
-
innerY: number;
|
|
1036
|
-
innerWidth: number;
|
|
1037
|
-
innerHeight: number;
|
|
1038
|
-
};
|
|
1039
|
-
/** Optional paint. World position is derived from `shape` + target
|
|
1040
|
-
* transform; visual size stays in screen pixels (so handles don't
|
|
1041
|
-
* warp under zoom or non-uniform scale). Omit for hit-only regions.
|
|
1042
|
-
*
|
|
1043
|
-
* - `square` — small fixed-size square (only valid over `point` shapes).
|
|
1044
|
-
* - `annulus` — fill + stroke the annulus ring (only valid over
|
|
1045
|
-
* `annulus` shapes). Uses even-odd fill rule to punch the inner-rect
|
|
1046
|
-
* cutout.
|
|
1047
|
-
* - `custom` — emit arbitrary draw commands; receives a {@link CustomPaintContext}. */
|
|
1048
|
-
paint?: {
|
|
1049
|
-
kind: 'square';
|
|
1050
|
-
sizePx: number;
|
|
1051
|
-
fill?: FillStyle;
|
|
1052
|
-
stroke?: Stroke;
|
|
1053
|
-
} | {
|
|
1054
|
-
kind: 'annulus';
|
|
1055
|
-
fill?: FillStyle;
|
|
1056
|
-
stroke?: Stroke;
|
|
1057
|
-
insetPx?: number;
|
|
1058
|
-
} | {
|
|
1059
|
-
kind: 'custom';
|
|
1060
|
-
draw: (ctx: CustomPaintContext) => DrawCommand[];
|
|
1061
|
-
};
|
|
1062
|
-
/** Declared CSS cursor for this region. NOTE: nothing consumes this
|
|
1063
|
-
* field yet — the hover-cursor pump lives on the dispatcher pipeline
|
|
1064
|
-
* (`useGestureDispatcher`) and reads `AffordanceHit.cursor` from
|
|
1065
|
-
* `buildAffordanceAt`'s synthesized hits, which do not go through
|
|
1066
|
-
* region objects. Honoring this field is part of the pending
|
|
1067
|
-
* affordance-layer unification (see the selection-overlay note in
|
|
1068
|
-
* `docs/taxonomy.md`). */
|
|
1069
|
-
cursor?: string;
|
|
1070
|
-
/** Drag binding produced when this region is hit. Lazily called so
|
|
1071
|
-
* affordances don't pay binding-construction cost on every paint frame —
|
|
1072
|
-
* state snapshots (e.g., capturing per-leaf poses at click time) belong
|
|
1073
|
-
* inside `bind()`, not inside `regions()`. */
|
|
1074
|
-
bind(): AffordanceBinding<TScratch>;
|
|
1075
|
-
}
|
|
1076
|
-
/** Context passed to a region's `paint.kind === 'custom'` draw callback.
|
|
1077
|
-
* Provides both the world-space anchor (already transformed) and the
|
|
1078
|
-
* original local shape, for affordances that want to do additional
|
|
1079
|
-
* geometry themselves. */
|
|
1080
|
-
interface CustomPaintContext {
|
|
1081
|
-
/** World-space mapping of `shape`. For `point`, only `x`/`y` are set.
|
|
1082
|
-
* For `rect`, all four fields are set. */
|
|
1083
|
-
world: {
|
|
1084
|
-
x: number;
|
|
1085
|
-
y: number;
|
|
1086
|
-
width?: number;
|
|
1087
|
-
height?: number;
|
|
1088
|
-
};
|
|
1089
|
-
/** The original local shape (same object identity as `region.shape`). */
|
|
1090
|
-
local: AffordanceRegion['shape'];
|
|
1091
|
-
view: View;
|
|
1092
|
-
state: ChromeState;
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* @experimental
|
|
1096
|
-
* Result of an affordance hit — what the dispatcher wires up as the gesture.
|
|
1097
|
-
* Nominates the drag channel and (optionally) initial scratch state.
|
|
1098
|
-
*/
|
|
1099
|
-
interface AffordanceBinding<TScratch = unknown> {
|
|
1100
|
-
drag: DragChannel<TScratch>;
|
|
1101
|
-
/** Initial scratch passed to drag.onStart. Lets the affordance pre-fill
|
|
1102
|
-
* state from what its region's binding already computed (anchor: 'br',
|
|
1103
|
-
* targetId: 'g1', etc.) so the tool's onStart doesn't re-derive it. */
|
|
1104
|
-
initialScratch?: TScratch;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/**
|
|
1108
|
-
* Canvas size in CSS pixels — passed to `draw` for layers that anchor to
|
|
1109
|
-
* canvas edges (e.g. the debug overlay's layer-list panel). The GL backend
|
|
1110
|
-
* supplies it explicitly so layers don't have to know about DPR.
|
|
1111
|
-
*/
|
|
1112
|
-
interface Dims {
|
|
1113
|
-
width: number;
|
|
1114
|
-
height: number;
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* A single named render sub-layer within a canvas renderer.
|
|
1118
|
-
*
|
|
1119
|
-
* @template TData - The data object passed to each draw call.
|
|
1120
|
-
*/
|
|
1121
|
-
interface RenderLayer<TData> {
|
|
1122
|
-
/** Unique identifier used in visibility maps and ordering arrays. */
|
|
1123
|
-
id: string;
|
|
1124
|
-
/** Human-readable name for UI toggles. */
|
|
1125
|
-
label: string;
|
|
1126
|
-
/**
|
|
1127
|
-
* Emit a DrawCommand tree for the GL backend to dispatch.
|
|
1128
|
-
*
|
|
1129
|
-
* For world-space layers (the default), emit commands in WORLD COORDS —
|
|
1130
|
-
* `drawLayers` automatically wraps them in `{ kind: 'group', transform:
|
|
1131
|
-
* viewToMat3(view), ... }` before handing them to the renderer. Do NOT
|
|
1132
|
-
* apply the view transform yourself.
|
|
1133
|
-
*
|
|
1134
|
-
* For screen-space layers (`space: 'screen'`), emit commands in CSS-pixel
|
|
1135
|
-
* coords directly; `drawLayers` passes them through unchanged. If part
|
|
1136
|
-
* of a screen-space layer's output needs to track the view, wrap that
|
|
1137
|
-
* subset manually with `viewToMat3(view)`.
|
|
1138
|
-
*/
|
|
1139
|
-
draw: (data: TData, view: View, dims: Dims) => DrawCommand[];
|
|
1140
|
-
/**
|
|
1141
|
-
* Whether the layer is shown when no explicit visibility entry exists.
|
|
1142
|
-
* Defaults to `true` when absent.
|
|
1143
|
-
*/
|
|
1144
|
-
defaultVisible?: boolean;
|
|
1145
|
-
/**
|
|
1146
|
-
* When true, the layer is always drawn regardless of the visibility map.
|
|
1147
|
-
* Useful for layers that must never be hidden (e.g. base grid).
|
|
1148
|
-
*/
|
|
1149
|
-
alwaysOn?: boolean;
|
|
1150
|
-
/**
|
|
1151
|
-
* Coordinate space the layer draws in.
|
|
1152
|
-
*
|
|
1153
|
-
* - `'world'` (default): the layer's `draw` returns world-space commands;
|
|
1154
|
-
* `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`
|
|
1155
|
-
* automatically.
|
|
1156
|
-
* - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)
|
|
1157
|
-
* commands; `drawLayers` passes them through unchanged. World-anchored
|
|
1158
|
-
* chrome inside a screen-space layer must call `worldToScreen` or wrap
|
|
1159
|
-
* the relevant subset with `viewToMat3(view)` manually.
|
|
1160
|
-
*/
|
|
1161
|
-
space?: 'world' | 'screen';
|
|
1162
|
-
/**
|
|
1163
|
-
* Optional hit-test. When defined, the dispatcher consults this on
|
|
1164
|
-
* pointerdown (top-down layer order) before falling through to the
|
|
1165
|
-
* active tool's slot walk. First non-null result wins; null means
|
|
1166
|
-
* "I don't claim this hit, try the next layer."
|
|
1167
|
-
*
|
|
1168
|
-
* Coordinates are world-space. The `data` arg is the layer's
|
|
1169
|
-
* configured data slot (same as `draw`); `view` and `dims` mirror
|
|
1170
|
-
* `draw`'s arguments.
|
|
1171
|
-
*/
|
|
1172
|
-
hitTest?: (worldX: number, worldY: number, data: TData, view: View, dims: Dims,
|
|
1173
|
-
/** Chrome-caps visibility predicate. When supplied, the layer must
|
|
1174
|
-
* not return a hit from any chrome element whose id reports
|
|
1175
|
-
* `false`. Absent → every element is hittable. */
|
|
1176
|
-
isVisible?: (id: string) => boolean) => AffordanceBinding | null;
|
|
1177
|
-
/**
|
|
1178
|
-
* Called on every pointermove when no gesture is currently captured.
|
|
1179
|
-
* Lets layers (e.g. HUD widgets) track hover state without participating
|
|
1180
|
-
* in the drag pipeline. Coords are world-space; the layer is responsible
|
|
1181
|
-
* for any further conversion (e.g. world→screen for screen-space layers)
|
|
1182
|
-
* and for its own throttling.
|
|
1183
|
-
*/
|
|
1184
|
-
onUncapturedMove?: (worldX: number, worldY: number, evt: PointerEvent, view: View, dims: Dims) => void;
|
|
1185
|
-
/**
|
|
1186
|
-
* Called when the cursor leaves the canvas element. Lets layers clear
|
|
1187
|
-
* any hover state they're holding.
|
|
1188
|
-
*/
|
|
1189
|
-
onUncapturedLeave?: () => void;
|
|
1190
|
-
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Walk visible layers and concatenate their emitted DrawCommand arrays into
|
|
1193
|
-
* one flat list, ready to feed to `WeaselRenderer.render(commands)`.
|
|
1194
|
-
*
|
|
1195
|
-
* Visibility resolution order:
|
|
1196
|
-
* 1. `alwaysOn` — always drawn, ignores visibility map.
|
|
1197
|
-
* 2. Explicit entry in `visibility` map — overrides default.
|
|
1198
|
-
* 3. `layer.defaultVisible` — falls back to `true` when absent.
|
|
1199
|
-
*
|
|
1200
|
-
* Transform composition: world-space layers (the default; `space` unset or
|
|
1201
|
-
* `'world'`) have their commands wrapped in a `kind: 'group'` with
|
|
1202
|
-
* `viewToMat3(view)` before they reach the renderer. Screen-space layers
|
|
1203
|
-
* (`space: 'screen'`) pass through unchanged.
|
|
1204
|
-
*/
|
|
1205
|
-
declare function drawLayers<TData>(layers: RenderLayer<TData>[], data: TData, visibility: Record<string, boolean>, order: string[] | undefined, view: View | undefined, dims: Dims): DrawCommand[];
|
|
1206
|
-
|
|
1207
|
-
interface SlotsState {
|
|
1208
|
-
hotkey: AnyTool | null;
|
|
1209
|
-
active: AnyTool | null;
|
|
1210
|
-
ambient: AnyTool[];
|
|
1211
|
-
/** Optional fallback tool consulted ONLY for `pointer.onClick` when neither
|
|
1212
|
-
* the active slot nor any ambient tool claimed the click. Lets a non-select
|
|
1213
|
-
* tool (pen, ellipse, etc.) stay active while letting unhandled clicks fall
|
|
1214
|
-
* through to the select tool for click-to-select. Not consulted for
|
|
1215
|
-
* pointerdown, drag, keyboard, wheel, or dblTap. */
|
|
1216
|
-
fallback?: AnyTool | null;
|
|
1217
|
-
}
|
|
1218
|
-
/** @internal */
|
|
1219
|
-
interface ToolsDispatcherOptions {
|
|
1220
|
-
/** Called on every event to read the current slot occupants. The
|
|
1221
|
-
* dispatcher keeps no copy — `useTools` owns slot state and updates
|
|
1222
|
-
* it as the user activates / engages tools. */
|
|
1223
|
-
getSlots: () => SlotsState;
|
|
1224
|
-
/** Called once per channel-handler invocation to construct the ctx
|
|
1225
|
-
* the handler receives. `<Canvas>` supplies world coords, modifiers,
|
|
1226
|
-
* selection, adapter, and applyOps; the dispatcher injects scratch. */
|
|
1227
|
-
getCtx: (overrides?: {
|
|
1228
|
-
clientX?: number;
|
|
1229
|
-
clientY?: number;
|
|
1230
|
-
modifiers?: {
|
|
1231
|
-
alt: boolean;
|
|
1232
|
-
shift: boolean;
|
|
1233
|
-
meta: boolean;
|
|
1234
|
-
ctrl: boolean;
|
|
1235
|
-
};
|
|
1236
|
-
}) => Omit<ToolCtx, 'scratch'>;
|
|
1237
|
-
/** Pixel distance the pointer must travel before a click is reclassified
|
|
1238
|
-
* as a drag. Default 4. */
|
|
1239
|
-
threshold?: number;
|
|
1240
|
-
/** Optional callback fired whenever an in-flight gesture starts or ends
|
|
1241
|
-
* (including phase transitions pending → drag → end/cancel). `useTools`
|
|
1242
|
-
* uses this to bump a render tick so consumers reading
|
|
1243
|
-
* `dispatcher.getActiveScratch()` (e.g. function-form `cursor`) re-resolve
|
|
1244
|
-
* on real DOM events. */
|
|
1245
|
-
onGestureChange?: () => void;
|
|
1246
|
-
/** Called whenever a declarative route resolves to an ActionFn. The
|
|
1247
|
-
* dispatcher caches the most recent invocation; consumers read it via
|
|
1248
|
-
* `getLastRoute()`. Useful for the kit's ToolDebugOverlay. */
|
|
1249
|
-
onRouteResolved?: (info: RouteResolvedInfo) => void;
|
|
1250
|
-
/** Time source for double-tap detection. Defaults to `Date.now`. Override
|
|
1251
|
-
* in tests to drive the clock deterministically. */
|
|
1252
|
-
now?: () => number;
|
|
1253
|
-
/** Double-tap detection thresholds. */
|
|
1254
|
-
dblTap?: {
|
|
1255
|
-
/** Maximum interval between the two taps (ms). Default 300. */
|
|
1256
|
-
windowMs?: number;
|
|
1257
|
-
/** Maximum CSS-px distance between the two tap positions. Default 8. */
|
|
1258
|
-
maxDistance?: number;
|
|
1259
|
-
};
|
|
1260
|
-
/**
|
|
1261
|
-
* Optional. Returns the hit-test pipeline inputs:
|
|
1262
|
-
* - `layers`: visible RenderLayers ordered TOP-DOWN (highest z-index first).
|
|
1263
|
-
* - `chromeState`: passed as the `data` arg to each layer's hitTest call.
|
|
1264
|
-
* - `view` / `dims`: passed to layer.hitTest for screen-space conversion.
|
|
1265
|
-
*
|
|
1266
|
-
* When supplied, the dispatcher consults each layer's hitTest on
|
|
1267
|
-
* pointerdown (top-down order) before the existing slot walk. Returns
|
|
1268
|
-
* null (or omit the callback entirely) to disable the layer pipeline —
|
|
1269
|
-
* legacy behavior.
|
|
1270
|
-
*/
|
|
1271
|
-
getHitTestContext?: () => {
|
|
1272
|
-
layers: readonly RenderLayer<unknown>[];
|
|
1273
|
-
chromeState: ChromeState;
|
|
1274
|
-
view: View;
|
|
1275
|
-
dims: {
|
|
1276
|
-
width: number;
|
|
1277
|
-
height: number;
|
|
1278
|
-
};
|
|
1279
|
-
/** Chrome-caps visibility predicate, keyed by affordance id. When
|
|
1280
|
-
* supplied, the dispatcher passes it through to each layer's
|
|
1281
|
-
* `hitTest` so a hidden chrome element is also unhittable. Absent
|
|
1282
|
-
* → all affordances are hittable (legacy behavior). */
|
|
1283
|
-
isVisible?: (id: string) => boolean;
|
|
1284
|
-
} | null;
|
|
1285
|
-
/** Optional. Returns the scene node at the given world coords, or null
|
|
1286
|
-
* for empty. The dispatcher uses this to populate `ctx.target` on
|
|
1287
|
-
* pointer events (down/move/up/click/dblTap), so declarative routing
|
|
1288
|
-
* factories can dispatch on target.kind ('rect'/'text'/'path'/'empty').
|
|
1289
|
-
* Omit to leave the dispatcher with the always-empty fallback — see
|
|
1290
|
-
* `nodeHitFor` for the rationale. */
|
|
1291
|
-
getNodeAtPoint?: (worldX: number, worldY: number) => {
|
|
1292
|
-
id: NodeId;
|
|
1293
|
-
kind: string;
|
|
1294
|
-
pose: unknown;
|
|
1295
|
-
data: unknown;
|
|
1296
|
-
meta?: Record<string, unknown>;
|
|
1297
|
-
} | null;
|
|
1298
|
-
}
|
|
1299
|
-
interface ToolsDispatcher {
|
|
1300
|
-
onPointerDown: (e: PointerEvent) => void;
|
|
1301
|
-
onPointerMove: (e: PointerEvent) => void;
|
|
1302
|
-
onPointerUp: (e: PointerEvent) => void;
|
|
1303
|
-
onKeyDown: (e: KeyboardEvent) => void;
|
|
1304
|
-
onKeyUp: (e: KeyboardEvent) => void;
|
|
1305
|
-
onWheel: (e: WheelEvent) => void;
|
|
1306
|
-
/** Force-cancel any in-flight gesture (used on explicit tool switch). */
|
|
1307
|
-
cancelGesture: () => void;
|
|
1308
|
-
/** Whether a gesture is currently in flight. Used by `useTools` to
|
|
1309
|
-
* decide whether a modifier-key press should engage the hotkey slot
|
|
1310
|
-
* (no, if mid-gesture). */
|
|
1311
|
-
hasActiveGesture: () => boolean;
|
|
1312
|
-
/** Scratch of the in-flight gesture, or `null` when idle. Exposed so
|
|
1313
|
-
* consumers (cursor resolution, debug overlays) can read what the active
|
|
1314
|
-
* tool is currently tracking. Read-only — do NOT mutate via this getter. */
|
|
1315
|
-
getActiveScratch: () => unknown;
|
|
1316
|
-
/** Most recent route resolution emitted by a declarative tool, or null
|
|
1317
|
-
* if none has fired yet. Snapshot — safe to read on every render. */
|
|
1318
|
-
getLastRoute: () => RouteResolvedInfo | null;
|
|
1319
|
-
/** Resolve a synthetic (phase, gesture, hit, modifiers) query against the
|
|
1320
|
-
* current slot occupants WITHOUT executing the matched action. Walks slots
|
|
1321
|
-
* in real precedence order (hotkey > active > ambient) and consults each
|
|
1322
|
-
* tool's attached `def` (the declarative `ToolDef` produced by
|
|
1323
|
-
* `defineTool`). Tools without an attached `def` (imperative-only) are
|
|
1324
|
-
* invisible to this query. Returns the first match, or null if no slot
|
|
1325
|
-
* resolves the query. Pure: no scratch mutation, no scene mutation, no
|
|
1326
|
-
* RouteResolvedInfo emission. */
|
|
1327
|
-
resolveOnly: (query: ResolveQuery) => ResolveResult | null;
|
|
1328
|
-
}
|
|
1329
|
-
/** Synthetic resolution query — what the static widget asks "if a pointer
|
|
1330
|
-
* event landed on `hit` with these `modifiers`, which declarative route
|
|
1331
|
-
* would the dispatcher fire in this phase + gesture?" */
|
|
1332
|
-
interface ResolveQuery {
|
|
1333
|
-
phase: 'initial' | 'engaged';
|
|
1334
|
-
gesture: 'click' | 'drag' | 'pointerDown' | 'dblTap' | 'wheel';
|
|
1335
|
-
hit: HitResult;
|
|
1336
|
-
modifiers: ToolModifiers;
|
|
1337
|
-
}
|
|
1338
|
-
/** Successful resolution: which tool, in which slot, matched which route-table
|
|
1339
|
-
* key. `matchedKey` is `'*'` for function-form drag (no table to discriminate)
|
|
1340
|
-
* and for wheel routes (single ActionFn). */
|
|
1341
|
-
interface ResolveResult {
|
|
1342
|
-
toolId: string;
|
|
1343
|
-
slot: 'hotkey' | 'active' | 'ambient';
|
|
1344
|
-
gesture: 'click' | 'drag' | 'pointerDown' | 'dblTap' | 'wheel';
|
|
1345
|
-
phase: 'initial' | 'engaged';
|
|
1346
|
-
matchedKey: string;
|
|
1347
|
-
}
|
|
1348
|
-
declare function createToolsDispatcher(opts: ToolsDispatcherOptions): ToolsDispatcher;
|
|
1349
|
-
|
|
1350
|
-
/** Result of a successful `resolveRoute` lookup: the resolved `ActionFn`
|
|
1351
|
-
* plus the route-table key that matched after precedence resolution.
|
|
1352
|
-
* Consumers (currently only `defineTool`) use `matchedKey` for debug
|
|
1353
|
-
* reflection — answering "why did this route fire?" by surfacing the
|
|
1354
|
-
* exact post-precedence key. */
|
|
1355
|
-
interface RouteMatch<TScratch> {
|
|
1356
|
-
action: ActionFn<TScratch>;
|
|
1357
|
-
matchedKey: string;
|
|
1358
|
-
}
|
|
1359
|
-
/** Resolve a route entry to an `ActionFn` (or undefined) given the current
|
|
1360
|
-
* hit-test result and modifier snapshot. Implements the four-level
|
|
1361
|
-
* target precedence (exact → subkind-wildcard → base-kind → universal)
|
|
1362
|
-
* and the modifier sub-table exact-match + 'default' fallback. Returns
|
|
1363
|
-
* the matched route-table key alongside the action so callers can attach
|
|
1364
|
-
* it to debug-overlay reflection. */
|
|
1365
|
-
declare function resolveRoute<TScratch>(table: RouteTable<TScratch>, hit: HitResult, modifiers: ToolModifiers): RouteMatch<TScratch> | undefined;
|
|
1366
|
-
|
|
1367
|
-
/** Translate a declarative `ToolDef<TScratch>` into the existing imperative
|
|
1368
|
-
* `Tool<TScratch>` shape. The dispatcher consumes the resulting Tool as
|
|
1369
|
-
* it does today; declarative authoring is the only change. */
|
|
1370
|
-
declare function defineTool<TScratch = void>(def: ToolDef<TScratch>): Tool<TScratch>;
|
|
1371
|
-
|
|
1372
|
-
/** Translate a viewport-tool spec. Viewport tools have no targets,
|
|
1373
|
-
* so route tables aren't allowed — drag is a function-form ActionFn.
|
|
1374
|
-
* This factory delegates to defineTool after lifting the ViewportPhaseDef
|
|
1375
|
-
* shape to the more permissive PhaseDef shape (which accepts both
|
|
1376
|
-
* function-form and route-table drags). */
|
|
1377
|
-
declare function defineViewportTool<TScratch = void>(def: ViewportToolDef<TScratch>): Tool<TScratch>;
|
|
1378
|
-
|
|
1379
|
-
/**
|
|
1380
|
-
* Build an `ActionFn` that forwards `ctx` + the raw DOM event to a
|
|
1381
|
-
* consumer-supplied callback and claims the gesture.
|
|
1382
|
-
*
|
|
1383
|
-
* Useful for tools that want to expose an escape-hatch hook for app
|
|
1384
|
-
* code without losing routing: route the gesture declaratively, then
|
|
1385
|
-
* fall through to the consumer when the kit doesn't have a better
|
|
1386
|
-
* default. The consumer-payload shape is whatever the tool wants —
|
|
1387
|
-
* `buildPayload` produces it from `ctx` and the raw event.
|
|
1388
|
-
*
|
|
1389
|
-
* When `callbackRef.current` is null/undefined, returns `none()` so
|
|
1390
|
-
* the routing engine continues to the next route (e.g. an ambient
|
|
1391
|
-
* tool's handler).
|
|
1392
|
-
*
|
|
1393
|
-
* Typical use, inside a `useFooTool` hook:
|
|
1394
|
-
*
|
|
1395
|
-
* const onDoubleTapRef = useRef(options.onDoubleTap);
|
|
1396
|
-
* onDoubleTapRef.current = options.onDoubleTap;
|
|
1397
|
-
* const forwardDblTap = forwardActionTo<FooScratch, DblTapPayload>(
|
|
1398
|
-
* onDoubleTapRef,
|
|
1399
|
-
* (ctx, e) => ({
|
|
1400
|
-
* worldX: ctx.worldX,
|
|
1401
|
-
* worldY: ctx.worldY,
|
|
1402
|
-
* event: e as PointerEvent,
|
|
1403
|
-
* }),
|
|
1404
|
-
* );
|
|
1405
|
-
* // ...later, in the route table:
|
|
1406
|
-
* dblTap: { '*': forwardDblTap, empty: forwardDblTap }
|
|
1407
|
-
*/
|
|
1408
|
-
declare function forwardActionTo<TScratch, TPayload>(callbackRef: {
|
|
1409
|
-
readonly current: ((payload: TPayload) => void) | null | undefined;
|
|
1410
|
-
}, buildPayload: (ctx: Parameters<ActionFn<TScratch>>[0], event: Parameters<ActionFn<TScratch>>[1]) => TPayload): ActionFn<TScratch>;
|
|
1411
|
-
|
|
1412
|
-
/** One row in the action registry — uniquely identifies a routing slot
|
|
1413
|
-
* on one tool. Multiple rows can share (gesture, arg, target, modifiers)
|
|
1414
|
-
* if different tools declare them; consumers walk the list and group
|
|
1415
|
-
* client-side. */
|
|
1416
|
-
interface RegistryEntry {
|
|
1417
|
-
toolId: string;
|
|
1418
|
-
phase: RoutePhase;
|
|
1419
|
-
/** Structured v3 modifier requirements. Empty object = "no modifiers
|
|
1420
|
-
* held" (the strict default). */
|
|
1421
|
-
modifiers: ParsedModifiers;
|
|
1422
|
-
gesture: GestureName;
|
|
1423
|
-
/** Resolved arg value for arg-bearing gestures (wheel direction,
|
|
1424
|
-
* keyDown/keyUp key, multiTouchTap fingers). Undefined for gestures
|
|
1425
|
-
* whose descriptor has no `arg`. */
|
|
1426
|
-
arg: string | undefined;
|
|
1427
|
-
/** Route-table key for hit-test gestures (click/dblTap/drag/contextMenu/
|
|
1428
|
-
* pointerDown). Undefined when the descriptor has `hasTarget: false`. */
|
|
1429
|
-
target: string | undefined;
|
|
1430
|
-
}
|
|
1431
|
-
declare function buildActionRegistry(tools: readonly ToolDef<unknown>[]): RegistryEntry[];
|
|
1432
|
-
|
|
1433
|
-
/** Two or more tools declare the same exact (phase, gesture, arg, target,
|
|
1434
|
-
* modifiers) tuple — the dispatcher's slot precedence picks one
|
|
1435
|
-
* arbitrarily (well, deterministically by slot order, but the author
|
|
1436
|
-
* probably didn't intend the duplication). */
|
|
1437
|
-
interface Conflict {
|
|
1438
|
-
phase: RoutePhase;
|
|
1439
|
-
gesture: RouteGesture;
|
|
1440
|
-
arg: string | undefined;
|
|
1441
|
-
target: string | undefined;
|
|
1442
|
-
modifiers: ParsedModifiers;
|
|
1443
|
-
/** All tool ids that registered the same tuple. At least 2 by
|
|
1444
|
-
* construction. Order matches the input tools[] order. */
|
|
1445
|
-
toolIds: string[];
|
|
1446
|
-
}
|
|
1447
|
-
/** Detect exact-tuple overlaps across a tool registration set.
|
|
1448
|
-
*
|
|
1449
|
-
* Intentionally NOT flagged:
|
|
1450
|
-
* - Wildcard vs. specific (e.g., `click['*']` + `click['rect']`) — that's
|
|
1451
|
-
* the cascading-fallback pattern; the lookup engine resolves cleanly.
|
|
1452
|
-
* - Different modifier sub-keys on the same target — they fire on
|
|
1453
|
-
* different inputs.
|
|
1454
|
-
* - Ambient stacking where a tool returns `pass`/`none()` from its
|
|
1455
|
-
* ActionFn to intentionally let another ambient tool run. We'd need
|
|
1456
|
-
* to evaluate the ActionFn to detect intent; consumers can suppress
|
|
1457
|
-
* known-intentional compositions in their UI layer.
|
|
1458
|
-
*/
|
|
1459
|
-
declare function findConflicts(tools: readonly ToolDef<unknown>[]): Conflict[];
|
|
1460
|
-
|
|
1461
|
-
/** Reactive snapshot of the most recently resolved route. Re-renders
|
|
1462
|
-
* the consuming component whenever a new route resolves. Returns null
|
|
1463
|
-
* when no route has resolved yet (or after `cancelGesture()`).
|
|
1464
|
-
*
|
|
1465
|
-
* Pass the kit's ToolsDispatcher reference. The hook subscribes via
|
|
1466
|
-
* the dispatcher's existing onRouteResolved option — if the consumer's
|
|
1467
|
-
* dispatcher wasn't constructed with this hook in mind, the consumer
|
|
1468
|
-
* must thread an extra subscription. (Future improvement: kit-shipped
|
|
1469
|
-
* dispatcher context.) */
|
|
1470
|
-
declare function useToolDebugInfo(dispatcher: ToolsDispatcher): RouteResolvedInfo | null;
|
|
1471
|
-
|
|
1472
|
-
interface ToolDebugOverlayProps {
|
|
1473
|
-
/** Most recent route resolution. Pass the result of useToolDebugInfo(). */
|
|
1474
|
-
info: RouteResolvedInfo | null;
|
|
1475
|
-
/** Optional override for the "idle" placeholder text shown when info is null. */
|
|
1476
|
-
emptyLabel?: string;
|
|
1477
|
-
}
|
|
1478
|
-
/** Dev-tools-style panel showing the most recently resolved route.
|
|
1479
|
-
* Mount in a corner of the viewport (typically bottom-right). Pure
|
|
1480
|
-
* presentation — no dispatcher coupling; the parent threads the info. */
|
|
1481
|
-
declare function ToolDebugOverlay({ info, emptyLabel, }: ToolDebugOverlayProps): ReactElement;
|
|
1482
|
-
|
|
1483
|
-
type index_ActionFn<TScratch> = ActionFn<TScratch>;
|
|
1484
|
-
type index_AffordanceHit = AffordanceHit;
|
|
1485
|
-
type index_BeginSpec<TScratch> = BeginSpec<TScratch>;
|
|
1486
|
-
declare const index_ChannelRef: typeof ChannelRef;
|
|
1487
|
-
type index_Conflict = Conflict;
|
|
1488
|
-
declare const index_DescribeRouteOptions: typeof DescribeRouteOptions;
|
|
1489
|
-
type index_EmptyHit = EmptyHit;
|
|
1490
|
-
declare const index_GESTURE_DESCRIPTORS: typeof GESTURE_DESCRIPTORS;
|
|
1491
|
-
declare const index_GestureArgSpec: typeof GestureArgSpec;
|
|
1492
|
-
declare const index_GestureDescriptor: typeof GestureDescriptor;
|
|
1493
|
-
declare const index_GestureName: typeof GestureName;
|
|
1494
|
-
type index_HitResult = HitResult;
|
|
1495
|
-
declare const index_ModRequirement: typeof ModRequirement;
|
|
1496
|
-
declare const index_ModifierCombo: typeof ModifierCombo;
|
|
1497
|
-
declare const index_ModifierKey: typeof ModifierKey;
|
|
1498
|
-
type index_ModifierRoute<TScratch> = ModifierRoute<TScratch>;
|
|
1499
|
-
type index_NodeHit = NodeHit;
|
|
1500
|
-
type index_NodeRef = NodeRef;
|
|
1501
|
-
type index_NodeRefHit = NodeRefHit;
|
|
1502
|
-
declare const index_ParsedModifiers: typeof ParsedModifiers;
|
|
1503
|
-
declare const index_ParsedRoute: typeof ParsedRoute;
|
|
1504
|
-
declare const index_PhaseAtom: typeof PhaseAtom;
|
|
1505
|
-
type index_PhaseDef<TScratch> = PhaseDef<TScratch>;
|
|
1506
|
-
declare const index_RESERVED_ID_NAMES: typeof RESERVED_ID_NAMES;
|
|
1507
|
-
declare const index_RESERVED_ID_PREFIXES: typeof RESERVED_ID_PREFIXES;
|
|
1508
|
-
declare const index_ROUTE_FIELD_DEFINITIONS: typeof ROUTE_FIELD_DEFINITIONS;
|
|
1509
|
-
declare const index_ROUTE_TERMS: typeof ROUTE_TERMS;
|
|
1510
|
-
type index_RegistryEntry = RegistryEntry;
|
|
1511
|
-
type index_Result<TScratch> = Result<TScratch>;
|
|
1512
|
-
declare const index_RouteDescriptionPart: typeof RouteDescriptionPart;
|
|
1513
|
-
type index_RouteEntry<TScratch> = RouteEntry<TScratch>;
|
|
1514
|
-
declare const index_RouteFieldName: typeof RouteFieldName;
|
|
1515
|
-
type index_RouteGesture = RouteGesture;
|
|
1516
|
-
type index_RoutePhase = RoutePhase;
|
|
1517
|
-
type index_RouteResolvedInfo = RouteResolvedInfo;
|
|
1518
|
-
type index_RouteTable<TScratch> = RouteTable<TScratch>;
|
|
1519
|
-
declare const index_RouteTermLabel: typeof RouteTermLabel;
|
|
1520
|
-
declare const index_ToolDebugOverlay: typeof ToolDebugOverlay;
|
|
1521
|
-
type index_ToolDebugOverlayProps = ToolDebugOverlayProps;
|
|
1522
|
-
type index_ToolDef<TScratch = void> = ToolDef<TScratch>;
|
|
1523
|
-
type index_ToolKeybinding = ToolKeybinding;
|
|
1524
|
-
type index_ViewportPhaseDef<TScratch = void> = ViewportPhaseDef<TScratch>;
|
|
1525
|
-
type index_ViewportToolDef<TScratch = void> = ViewportToolDef<TScratch>;
|
|
1526
|
-
declare const index_apply: typeof apply;
|
|
1527
|
-
declare const index_begin: typeof begin;
|
|
1528
|
-
declare const index_buildActionRegistry: typeof buildActionRegistry;
|
|
1529
|
-
declare const index_cancel: typeof cancel;
|
|
1530
|
-
declare const index_canonicalModifiers: typeof canonicalModifiers;
|
|
1531
|
-
declare const index_claim: typeof claim;
|
|
1532
|
-
declare const index_collapseShiftPairs: typeof collapseShiftPairs;
|
|
1533
|
-
declare const index_commit: typeof commit;
|
|
1534
|
-
declare const index_defineTool: typeof defineTool;
|
|
1535
|
-
declare const index_defineViewportTool: typeof defineViewportTool;
|
|
1536
|
-
declare const index_describeRoute: typeof describeRoute;
|
|
1537
|
-
declare const index_describeRouteParts: typeof describeRouteParts;
|
|
1538
|
-
declare const index_findConflicts: typeof findConflicts;
|
|
1539
|
-
declare const index_formatPhaseAtom: typeof formatPhaseAtom;
|
|
1540
|
-
declare const index_formatRoute: typeof formatRoute;
|
|
1541
|
-
declare const index_formatRouteResolved: typeof formatRouteResolved;
|
|
1542
|
-
declare const index_forwardActionTo: typeof forwardActionTo;
|
|
1543
|
-
declare const index_getGestureDescriptor: typeof getGestureDescriptor;
|
|
1544
|
-
declare const index_hold: typeof hold;
|
|
1545
|
-
declare const index_isKnownGestureName: typeof isKnownGestureName;
|
|
1546
|
-
declare const index_mods: typeof mods;
|
|
1547
|
-
declare const index_none: typeof none;
|
|
1548
|
-
declare const index_parseRoute: typeof parseRoute;
|
|
1549
|
-
declare const index_resolveRoute: typeof resolveRoute;
|
|
1550
|
-
declare const index_useToolDebugInfo: typeof useToolDebugInfo;
|
|
1551
|
-
declare namespace index {
|
|
1552
|
-
export { type index_ActionFn as ActionFn, type index_AffordanceHit as AffordanceHit, type index_BeginSpec as BeginSpec, index_ChannelRef as ChannelRef, type index_Conflict as Conflict, index_DescribeRouteOptions as DescribeRouteOptions, type index_EmptyHit as EmptyHit, index_GESTURE_DESCRIPTORS as GESTURE_DESCRIPTORS, index_GestureArgSpec as GestureArgSpec, index_GestureDescriptor as GestureDescriptor, index_GestureName as GestureName, type index_HitResult as HitResult, index_ModRequirement as ModRequirement, index_ModifierCombo as ModifierCombo, index_ModifierKey as ModifierKey, type index_ModifierRoute as ModifierRoute, type index_NodeHit as NodeHit, type index_NodeRef as NodeRef, type index_NodeRefHit as NodeRefHit, index_ParsedModifiers as ParsedModifiers, index_ParsedRoute as ParsedRoute, index_PhaseAtom as PhaseAtom, type index_PhaseDef as PhaseDef, index_RESERVED_ID_NAMES as RESERVED_ID_NAMES, index_RESERVED_ID_PREFIXES as RESERVED_ID_PREFIXES, index_ROUTE_FIELD_DEFINITIONS as ROUTE_FIELD_DEFINITIONS, index_ROUTE_TERMS as ROUTE_TERMS, type index_RegistryEntry as RegistryEntry, type index_Result as Result, index_RouteDescriptionPart as RouteDescriptionPart, type index_RouteEntry as RouteEntry, index_RouteFieldName as RouteFieldName, type index_RouteGesture as RouteGesture, type index_RoutePhase as RoutePhase, type index_RouteResolvedInfo as RouteResolvedInfo, type index_RouteTable as RouteTable, index_RouteTermLabel as RouteTermLabel, index_ToolDebugOverlay as ToolDebugOverlay, type index_ToolDebugOverlayProps as ToolDebugOverlayProps, type index_ToolDef as ToolDef, type index_ToolKeybinding as ToolKeybinding, type index_ViewportPhaseDef as ViewportPhaseDef, type index_ViewportToolDef as ViewportToolDef, index_apply as apply, index_begin as begin, index_buildActionRegistry as buildActionRegistry, index_cancel as cancel, index_canonicalModifiers as canonicalModifiers, index_claim as claim, index_collapseShiftPairs as collapseShiftPairs, index_commit as commit, index_defineTool as defineTool, index_defineViewportTool as defineViewportTool, index_describeRoute as describeRoute, index_describeRouteParts as describeRouteParts, index_findConflicts as findConflicts, index_formatPhaseAtom as formatPhaseAtom, index_formatRoute as formatRoute, index_formatRouteResolved as formatRouteResolved, index_forwardActionTo as forwardActionTo, index_getGestureDescriptor as getGestureDescriptor, index_hold as hold, index_isKnownGestureName as isKnownGestureName, index_mods as mods, index_none as none, index_parseRoute as parseRoute, index_resolveRoute as resolveRoute, index_useToolDebugInfo as useToolDebugInfo };
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
export { type NodeHit as $, type ActionDeps as A, type BindingOpts as B, type ChromeState as C, type Dims as D, type ToolSlot as E, createToolsDispatcher as F, type GestureBinding as G, type HotkeyTrigger as H, type Invoker as I, drawLayers as J, type KeyboardChannel as K, index as L, useSelection as M, type ActionFn as N, type OngoingHandle as O, type Point2 as P, type AffordanceHit as Q, type RenderLayer as R, type SelectionApi as S, type Tool as T, type UseSelectionOptions as U, type BeginSpec as V, type WheelChannel as W, type Conflict as X, type EmptyHit as Y, type HitResult as Z, type ModifierRoute as _, type AffordanceHit$1 as a, type NodeRef as a0, type NodeRefHit as a1, type PhaseDef as a2, type RegistryEntry as a3, type Result as a4, type RouteEntry as a5, type RouteGesture as a6, type RoutePhase as a7, type RouteResolvedInfo as a8, type RouteTable as a9, ToolDebugOverlay as aa, type ToolDebugOverlayProps as ab, type ToolDef as ac, type ViewportPhaseDef as ad, type ViewportToolDef as ae, apply as af, begin as ag, buildActionRegistry as ah, cancel as ai, claim as aj, commit as ak, defineTool as al, defineViewportTool as am, findConflicts as an, formatRouteResolved as ao, forwardActionTo as ap, hold as aq, none as ar, resolveRoute as as, useToolDebugInfo as at, type AnyTool as b, type ToolsDispatcher as c, type ToolCtx as d, type ToolsDispatcherOptions as e, type ToolKeybinding as f, type Affordance as g, type AffordanceBinding as h, type AffordanceRegion as i, type CustomPaintContext as j, type DblTapChannel as k, type Decision as l, type DragChannel as m, type ImmediateInvoker as n, type InvocationCtx as o, type OngoingInvoker as p, type OngoingOverlay as q, type PointerChannel as r, type ResolveQuery as s, type ResolveResult as t, type RouteMatch as u, type SelectionExtendKey as v, type SelectionMode as w, type ToolHit as x, type ToolModifiers as y, type ToolPresentation as z };
|