@weasel-js/routing 1.14.1 → 1.15.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.
@@ -1,11 +1,12 @@
1
- import { ModifierKey, ClaimableGesture, GestureSpec, InputEvent, TargetSpec, IngestItem, BodyTarget, BindingOverrideInput, BodyKind, KeyHeldSpec, TargetPredicate, KeySpec, ClickSpec, DoubleClickSpec, DragSpec, ContextMenuSpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
1
+ import { History } from '@weasel-js/history';
2
+ import { PrefGroup, PrefLeaf, PrefNumber } from '@weasel-js/prefs';
2
3
  import { FramedBox, View, Point } from '@weasel-js/geom';
3
- import { CursorSpec } from '@weasel-js/cursor';
4
- import { MenuEntry, Rect, MenuSide, MenuShortcut } from '@weasel-js/menu';
5
- import { PrefGroup, PrefLeaf } from '@weasel-js/prefs';
6
4
  import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
5
+ import { ModifierKey, ClaimableGesture, GestureSpec, IngestItem, InputEvent, TargetSpec, BodyTarget, BindingOverrideInput, BodyKind, KeyHeldSpec, TargetPredicate, KeySpec, ClickSpec, DoubleClickSpec, DragSpec, ContextMenuSpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
7
6
  import * as _weasel_js_icons from '@weasel-js/icons';
8
7
  import { IconRef } from '@weasel-js/icons';
8
+ import { CursorSpec } from '@weasel-js/cursor';
9
+ import { MenuEntry, Rect, MenuSide, MenuShortcut } from '@weasel-js/menu';
9
10
  import * as react from 'react';
10
11
  import { ReactNode } from 'react';
11
12
  import { Emitter, QueryMatch } from '@weasel-js/registry';
@@ -169,285 +170,537 @@ type ResizeAnchor = {
169
170
  y: 'min' | 'max' | 'free';
170
171
  };
171
172
 
172
- /** The chrome an in-flight gesture paints beside the scene. */
173
-
174
- /**
175
- * What a `polyline` overlay is, in a word — the narrowest thing an action can
176
- * say about how its run should look without naming a paint.
177
- *
178
- * The named roles are the kit's; a painter maps each to a style and falls back
179
- * to plain chrome for one it does not know, so a package outside core can coin
180
- * its own and still be drawn.
181
- */
182
- type OverlayRole =
183
- /** Generic in-flight chrome — the marquee/lasso paint. */
184
- 'chrome'
185
- /** A cut the gesture would make. */
186
- | 'cut'
187
- /** An edge the gesture would author between two points. */
188
- | 'connector'
189
- /** The stretch of a path a soft-selection drag reaches. */
190
- | 'falloff'
191
- /** What a lock holds a dragged point to — a line, a circle. */
192
- | 'lock'
193
- /** What a dragged item would be dropped on: the box it would go into. */
194
- | 'target'
195
- /** Where a dragged item would land: a reorder's insertion bar. */
196
- | 'insert'
197
- /** Where a dragged item is over and cannot land. */
198
- | 'refused' | (string & {});
199
173
  /**
200
- * What an in-flight gesture proposes to paint *beside* the scene, returned by
201
- * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
202
- * shape being dragged out, the line a slice would cut along.
174
+ * Live state read by rule evaluation. Built once per frame on the consuming
175
+ * surface — chrome-caps, the affordance pipeline, the dispatcher's
176
+ * eligibility filter — and discarded.
203
177
  *
204
- * Every variant is world geometry and nothing else — no `DrawCommand`, no
205
- * color, no coordinate space to choose — so a surface rendering the same
206
- * gesture some other way can still read it. The painter owns every style
207
- * decision.
178
+ * Adding a new field is additive: existing rules don't change, new
179
+ * selector atoms can read it.
208
180
  */
209
- type OngoingOverlay = {
210
- kind: 'marquee';
211
- start: {
212
- x: number;
213
- y: number;
214
- };
215
- current: {
216
- x: number;
217
- y: number;
218
- };
219
- shiftHeld: boolean;
220
- } | {
221
- kind: 'lasso';
222
- vertices: ReadonlyArray<{
223
- x: number;
224
- y: number;
225
- }>;
226
- current: {
227
- x: number;
228
- y: number;
181
+ interface RuleCtx {
182
+ readonly focused: boolean;
183
+ readonly selection: readonly NodeId[];
184
+ readonly multiActive: boolean;
185
+ readonly modifiers: ModifierState;
186
+ readonly action: {
187
+ readonly kind: string | null;
188
+ readonly id: string | null;
229
189
  };
230
- shiftHeld: boolean;
231
- } | {
232
- /**
233
- * An open run of world points the gesture is proposing — a cut line, a
234
- * routed connector. `role` is the whole of what an action says about
235
- * appearance.
236
- */
237
- kind: 'polyline';
238
- /** At least two points. Anything shorter is dropped in resolution. */
239
- points: ReadonlyArray<{
240
- x: number;
241
- y: number;
242
- }>;
243
- /** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
244
- role?: OverlayRole;
245
- } | {
246
- /**
247
- * A round brush dragged through world points: the area it has swept so
248
- * far, and its footprint at the last point. The radius is in screen
249
- * pixels, so the footprint keeps its size at every zoom.
250
- */
251
- kind: 'brush';
252
- /** The brush's path so far, at least one point; the last is where it is now. */
253
- points: ReadonlyArray<{
254
- x: number;
255
- y: number;
256
- }>;
257
- radiusPx: number;
258
- } | {
190
+ readonly hover: NodeId | null;
259
191
  /**
260
- * Live insert-drag preview. Pre-commit there is no scene node to ghost
261
- * via `previewIds()`/`previewPose()`, so insert paints its preview
262
- * through the overlay instead.
192
+ * How far the viewport is zoomed in, as one number — `1` at native scale.
263
193
  *
264
- * `bounds` is the AABB of the current drag (start/current normalized).
265
- * `extras` is the per-kind extras the action already collected; the
266
- * overlay renderer narrows on `shape` and casts the field shape it
267
- * expects.
268
- */
269
- kind: 'insertPreview';
270
- /** Which preview the dispatcher's overlay draws. The kit's own shape
271
- * kinds are the ones it knows how to render; a consumer-defined kind
272
- * is legal and simply gets no live preview. */
273
- shape: string;
274
- bounds: {
275
- x: number;
276
- y: number;
277
- width: number;
278
- height: number;
279
- };
280
- extras: unknown;
281
- /** World-space point to paint a small "anchor" dot at, marking the
282
- * click point — for radial shapes (polygon/star), where no vertex
283
- * sits on it, and for any shape in center mode. */
284
- anchorPoint?: {
285
- x: number;
286
- y: number;
287
- };
288
- /** World affines the shape will also be drawn under once it lands, by
289
- * the container it is drawn into: the preview is painted under each. */
290
- copies?: readonly (readonly [number, number, number, number, number, number])[];
291
- } | {
292
- /**
293
- * Why the gesture cannot land where it is, in words for the user: a
294
- * refusal's reason. A surface shows `text` beside the pointer, which
295
- * `at` is, in world coordinates.
194
+ * A scalar rather than the `View` this used to carry, because `zoomAtLeast`
195
+ * is the only selector that ever read one and a number is all it needs. A
196
+ * host whose viewport is a camera has no `View` to hand over and would
197
+ * otherwise have to invent one to use eligibility at all; it omits this
198
+ * instead, and `zoomAtLeast` declines rather than matching on a fiction.
296
199
  */
297
- kind: 'reason';
298
- at: {
299
- x: number;
300
- y: number;
301
- };
302
- text: string;
303
- };
304
- /** A handle's overlays as a list: empty when it has none. */
305
- declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
306
-
307
- /**
308
- * Modifier roles: the modifiers an ongoing action reads while it runs, named
309
- * by what they do rather than which key they are, so a binding or a tuning can
310
- * move one to another key without forking the action.
311
- */
312
-
313
- /** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
314
- * on a Mac and Ctrl elsewhere. An empty list never holds it. */
315
- type RoleKeys = readonly ModifierKey[];
316
- /** One modifier an action reads mid-gesture. */
317
- interface ModifierRole {
318
- label: string;
319
- /** The keys that hold it unless a tuning or a binding says otherwise. */
320
- keys: RoleKeys;
200
+ readonly zoom?: number;
201
+ /** Active mode id. `'normal'` when no non-default mode is engaged. */
202
+ readonly mode: string;
203
+ /** Capability tags allowed by the active mode (the union of
204
+ * `ModeDefinition.allows` plus implicit tags). The `capability:`
205
+ * selector reads this to determine whether a tag is permitted. */
206
+ readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
207
+ /** Whether the current selection may be resized. `<SceneCanvas>` folds
208
+ * `selectTool.resize.resizable` over the selection (true only when every
209
+ * selected node is resizable). Read by the `resizable:` selector to gate
210
+ * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
211
+ * resizable — back-compat: handles show unless a consumer opts a node out. */
212
+ readonly selectionResizable?: boolean;
213
+ /** Whether a path is currently in anchor-edit mode. Read by the
214
+ * `editingAnchors:` selector, which gates the path-edit chrome.
215
+ *
216
+ * This is deliberately a fact about state, not about permission: the
217
+ * anchor overlay and the anchor hit-test must agree, and the thing they
218
+ * must agree on is "is there an edited path right now", which no
219
+ * capability or mode id answers. A mode that allows `edits-anchors`
220
+ * with nothing being edited should draw no anchors. Absent is treated
221
+ * as false. */
222
+ readonly editingAnchors?: boolean;
223
+ /** Device facts — pointer coarseness, hover capability, density.
224
+ *
225
+ * Absent (legacy ctx builders) is treated as
226
+ * {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
227
+ * density 1. That is what the kit assumed before this field existed, so
228
+ * an absent profile is behavior-preserving by construction. */
229
+ readonly device?: DeviceProfile;
230
+ /** The view the input being dispatched landed in — a view id, or `null`
231
+ * for the surface's own camera. The dispatcher fills it; absent anywhere
232
+ * else a rule is evaluated. */
233
+ readonly viewId?: string | null;
234
+ /** Chrome ids the active tool hides (`ToolDef.hides`): never visible,
235
+ * whatever their rules say. Absent hides nothing. */
236
+ readonly hiddenChrome?: ReadonlySet<string>;
321
237
  }
322
- /** An action's roles by id, e.g. `'handle.holdAngle'`. */
323
- type ModifierRoles = Readonly<Record<string, ModifierRole>>;
324
- /** Keys a tuning or a binding gives some of an action's roles. */
325
- type RoleRebinds = Readonly<Record<string, RoleKeys>>;
326
- /** Whether any of `keys` is held. */
327
- declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
328
- /**
329
- * A reader for an action's own roles. It answers from `ctx.roles`, which the
330
- * dispatcher resolves on every event, and falls back to the role's default
331
- * keys where nothing resolved them: an invoker called directly, or a context
332
- * a cancel-all built with no event.
333
- */
334
- declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
335
- roles?: Readonly<Record<string, boolean>>;
238
+ /** The live state a `RuleCtx` is assembled from — the chrome context plus
239
+ * what the active mode allows. */
240
+ interface BuildRuleCtxArgs {
241
+ focused: boolean;
242
+ selection: readonly NodeId[];
243
+ multiActive: boolean;
336
244
  modifiers: ModifierState;
337
- }, id: keyof R & string) => boolean;
338
-
245
+ action: {
246
+ kind: string | null;
247
+ id: string | null;
248
+ };
249
+ hover: NodeId | null;
250
+ /** See {@link RuleCtx.zoom}. */
251
+ zoom?: number;
252
+ mode: string;
253
+ allowedCapabilities: ReadonlySet<CapabilityTag>;
254
+ /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
255
+ selectionResizable?: boolean;
256
+ /** Optional — omitted means "no path is being anchor-edited". */
257
+ editingAnchors?: boolean;
258
+ /** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
259
+ device?: DeviceProfile;
260
+ /** Optional — omitted hides nothing. See {@link RuleCtx.hiddenChrome}. */
261
+ hiddenChrome?: ReadonlySet<string>;
262
+ }
339
263
  /**
340
- * Invoker — pluggable invocation strategy for an Action.
264
+ * Capability set to assume when no mode registry is wired.
341
265
  *
342
- * An Action's `invoker` field describes how it's run. `immediate` invokers
343
- * fire once (delete, align, undo). `ongoing` invokers open a phase machine
344
- * driven by the dispatcher (move, resize, pinch-zoom).
266
+ * "No modes configured" means *everything the default mode permits*, not
267
+ * *nothing permitted*. The empty set is the wrong default: it makes every
268
+ * `capability:` rule evaluate false, so a kit default written the
269
+ * recommended way (capability rules outlive new modes; mode rules have to
270
+ * be edited) would silently hide its chrome for every consumer that hasn't
271
+ * opted into modality. `NORMAL.allows` plus the implicit tags is what a
272
+ * mode-less canvas actually behaves like.
345
273
  */
274
+ declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
275
+ /** Gather the current canvas and mode state into the context that action
276
+ * eligibility and chrome-visibility rules are evaluated against. */
277
+ declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
346
278
 
347
- /** A 2D point in either world or screen coordinates; geom's `Point`. */
348
- type Point2 = Point;
349
279
  /**
350
- * A point in the space the dispatcher's `clientToWorld` maps the pointer into:
351
- * the world, on a 2D canvas. A host with a camera of its own, such as a
352
- * `@weasel-js/kernel3d` scene, passes identity, so there it is the pane's
353
- * screen point and the deps reading it cast a ray through the camera they
354
- * close over. Every world point the dispatcher hands out, and every dep
355
- * payload it reaches, is one of these.
280
+ * A selector is a conjunction of key/value tests. Multiple keys at the same
281
+ * level AND together. Each key maps to a selector primitive in the evaluator.
356
282
  */
357
- type WorldPoint = Point2;
283
+ interface Selector {
284
+ selection?: {
285
+ is?: number;
286
+ atLeast?: number;
287
+ empty?: boolean;
288
+ };
289
+ mode?: string | {
290
+ not: string;
291
+ } | {
292
+ in: readonly string[];
293
+ };
294
+ capability?: CapabilityTag | readonly CapabilityTag[] | {
295
+ in: readonly CapabilityTag[];
296
+ } | {
297
+ not: CapabilityTag;
298
+ };
299
+ gesturing?: boolean;
300
+ actionIs?: string;
301
+ modifierHeld?: keyof ModifierState;
302
+ focused?: boolean;
303
+ hovering?: boolean;
304
+ hoveringSelected?: boolean;
305
+ zoomAtLeast?: number;
306
+ /** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
307
+ * mode. Absent flag is treated as `false`. */
308
+ editingAnchors?: boolean;
309
+ /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
310
+ * (resizable), so `{ resizable: true }` passes for legacy ctx builders
311
+ * that don't compute it. */
312
+ resizable?: boolean;
313
+ /** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
314
+ * (touch, most styluses). Absent device is treated as `false`. */
315
+ coarsePointer?: boolean;
316
+ /** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
317
+ * device is treated as `true`. */
318
+ canHover?: boolean;
319
+ }
358
320
  /**
359
- * Which UI affordance was hit at pointerdown. Populated by the dispatcher when
360
- * the `affordanceAt` thunk is provided to `useGestureDispatcher`.
321
+ * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
322
+ * over `Selector` leaves. `when` is the escape hatch — its closure is
323
+ * opaque to introspection and should be avoided when a declarative form
324
+ * exists. Empty `all` is true; empty `any` is false.
325
+ */
326
+ type Rule = Selector | {
327
+ all: readonly Rule[];
328
+ } | {
329
+ any: readonly Rule[];
330
+ } | {
331
+ not: Rule;
332
+ } | {
333
+ when: (ctx: RuleCtx) => boolean;
334
+ };
335
+ /** Constant rules. Kept here so they have a single source. */
336
+ declare const ALWAYS: Rule;
337
+ /** A rule that never passes. `any` of nothing is false. */
338
+ declare const NEVER: Rule;
339
+ /**
340
+ * Render a {@link Rule} as a short human-readable string, for inspectors and
341
+ * diagnostics that need to say WHICH rule decided something.
361
342
  *
362
- * `kind` is a discriminator string:
363
- * - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
364
- * `'handle:bottom-right'` — corner resize handles.
365
- * - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
366
- * edge resize handles.
367
- * - `'rotate-handle'` — the rotation affordance.
368
- * - `'anchor:N'` — a path anchor at index N.
343
+ * ```
344
+ * { capability: 'edits-page' } → capability:edits-page
345
+ * { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
346
+ * { not: { selection: { empty: true } } } → not(selection:empty=true)
347
+ * { when: function hasPath() { … } } → when(hasPath)
348
+ * ```
349
+ *
350
+ * Never throws and always terminates.
369
351
  */
370
- interface AffordanceHit {
371
- /** Discriminator string, e.g. `'handle:bottom-right'`. */
372
- kind: string;
373
- /** Id of whatever produced this hit — a kit affordance's `id`, or the
374
- * registered layer's id. */
375
- owner?: string;
376
- /** `'exclusive'` means no binding may act on this point unless its target
377
- * consults the affordance. `'shared'` (the default) competes on scope and
378
- * specificity as bindings always have. */
379
- strength?: 'exclusive' | 'shared';
380
- /** Which gestures an exclusive claim bars. Omitted bars all of them. */
381
- claimedKinds?: readonly ClaimableGesture[];
382
- /** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
383
- fixedPoint?: {
384
- x: number;
385
- y: number;
386
- };
387
- /** Which nodes this affordance belongs to. */
388
- targetIds?: string[];
389
- /** Set only when `kind` matches `'handle:*'`: which corner or edge stays
390
- * fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
391
- anchor?: ResizeAnchor;
392
- /** Cursor to show while the pointer hovers this affordance (no gesture in
393
- * flight). Unset falls through to action-cursor prediction, then to the
394
- * active tool's cursor. */
395
- cursor?: CursorSpec;
396
- /**
397
- * Free-form payload from whatever produced the hit, carried through to the
398
- * matching action untouched. Kit affordances leave it unset; it exists for
399
- * a registered layer's own chrome, where the hit-test already resolved
400
- * *which* of its pieces was hit.
401
- */
402
- payload?: unknown;
352
+ declare function describeRule(rule: Rule): string;
353
+ /** Evaluate a rule against a context. Pure, and cheap enough to run per
354
+ * frame for every piece of chrome. */
355
+ declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
356
+ /**
357
+ * Composable eligibility predicate with a fluent surface. `cond(ctx)`
358
+ * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
359
+ * Conditions wrapping new trees.
360
+ *
361
+ * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
362
+ * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
363
+ * disjunction, name the subexpression or use the top-level `or(...)`.
364
+ */
365
+ interface Condition {
366
+ (ctx: RuleCtx): boolean;
367
+ readonly rule: Rule;
368
+ /** `this && other` */
369
+ and(other: Condition | Rule): Condition;
370
+ /** `this || other` */
371
+ or(other: Condition | Rule): Condition;
372
+ /** `this && !other` */
373
+ andNot(other: Condition | Rule): Condition;
374
+ /** `this || !other` */
375
+ orNot(other: Condition | Rule): Condition;
403
376
  }
377
+
404
378
  /**
405
- * One accumulated point on a drag trail: world-space position plus whatever
406
- * stylus state the originating `PointerEvent` carried.
379
+ * Whether two rules can never hold at once — no `RuleCtx` passes both.
407
380
  *
408
- * The stylus fields are absent for mouse/touch on browsers that don't report
409
- * them, and for synthetic events.
381
+ * Conservative: `true` is a proof, `false` means "may overlap". It reasons
382
+ * about the selector values themselves (two different `mode`s, an empty
383
+ * selection against a non-empty one, `focused: true` against `false`) and
384
+ * through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
385
+ * everything. The route-conflict check uses it to stay quiet about bindings
386
+ * that share a tuple but are never eligible together.
410
387
  */
411
- interface DragSample extends WorldPoint {
412
- /** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
413
- pressure?: number;
414
- /** Degrees, ±90. Zero for mouse/touch. */
415
- tiltX?: number;
416
- /** Degrees, ±90. Zero for mouse/touch. */
417
- tiltY?: number;
388
+ declare function rulesExclusive(a: Rule, b: Rule): boolean;
389
+ /**
390
+ * Whether `rule` can hold while `mode` is active. Only its `mode` and
391
+ * `capability` tests are settled by the mode; every other test could go either
392
+ * way, so `false` is a proof and `true` means "may hold".
393
+ */
394
+ declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
395
+
396
+ /** The detected half of a profile — everything except the derived scale. */
397
+ type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
398
+ /**
399
+ * Handle/hit multiplier applied on a coarse pointer.
400
+ *
401
+ * 8px handle → 14px; 24px rotation distance → 42px. Counting the
402
+ * surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
403
+ * minimum-touch-target band. One constant so it is tunable in one place.
404
+ */
405
+ declare const COARSE_TARGET_SCALE = 1.75;
406
+ /**
407
+ * Fold consumer overrides over detected facts and derive `targetScale`.
408
+ *
409
+ * `targetScale` is re-derived AFTER the merge, so an override of
410
+ * `coarsePointer` alone scales the chrome as expected. An explicit
411
+ * `targetScale` override wins over the derivation.
412
+ */
413
+ declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
414
+ /**
415
+ * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
416
+ * absent-means value for `RuleCtx.device`.
417
+ *
418
+ * Derived from `resolveDeviceProfile` rather than hand-written, so it can
419
+ * never drift from what the function would compute for the same facts. A
420
+ * mouse-like device is the safe default: it is what the kit has always
421
+ * assumed, so an absent profile changes nothing for existing consumers.
422
+ */
423
+ declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
424
+
425
+ /**
426
+ * Modifier roles: the modifiers an ongoing action reads while it runs, named
427
+ * by what they do rather than which key they are, so a binding or a tuning can
428
+ * move one to another key without forking the action.
429
+ */
430
+
431
+ /** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
432
+ * on a Mac and Ctrl elsewhere. An empty list never holds it. */
433
+ type RoleKeys = readonly ModifierKey[];
434
+ /** One modifier an action reads mid-gesture. */
435
+ interface ModifierRole {
436
+ label: string;
437
+ /** The keys that hold it unless a tuning or a binding says otherwise. */
438
+ keys: RoleKeys;
418
439
  }
419
- /** Per-invocation runtime context the dispatcher hands to an Invoker.
420
- * Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
421
- * populated only for matching gesture kinds. */
422
- interface InvocationCtx {
423
- world: WorldPoint;
424
- /**
425
- * The pointer in client/screen pixels, for an action that has to place DOM
426
- * at the pointer. Absent on every invocation whose event carries no client
427
- * coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
428
- * read it with a fallback rather than assuming a point.
429
- *
430
- * A view-mutating drag wants `drag.screenDelta`, not the difference of two
431
- * of these: world deltas are self-referential mid-pan, and so is any delta
432
- * taken across a view change.
433
- */
434
- screen?: Point2;
440
+ /** An action's roles by id, e.g. `'handle.holdAngle'`. */
441
+ type ModifierRoles = Readonly<Record<string, ModifierRole>>;
442
+ /** Keys a tuning or a binding gives some of an action's roles. */
443
+ type RoleRebinds = Readonly<Record<string, RoleKeys>>;
444
+ /** Whether any of `keys` is held. */
445
+ declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
446
+ /**
447
+ * A reader for an action's own roles. It answers from `ctx.roles`, which the
448
+ * dispatcher resolves on every event, and falls back to the role's default
449
+ * keys where nothing resolved them: an invoker called directly, or a context
450
+ * a cancel-all built with no event.
451
+ */
452
+ declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
453
+ roles?: Readonly<Record<string, boolean>>;
435
454
  modifiers: ModifierState;
436
- deps: ActionDeps;
437
- drag?: {
438
- start: WorldPoint;
439
- current: WorldPoint;
440
- delta: Point2;
441
- /**
442
- * Drag delta in the surface's own CSS pixels from the drag origin: client
443
- * pixels, or through the host's `clientToLocal` where it supplies one, so
444
- * a canvas under a scaled ancestor pans with the pointer. Use this — never
445
- * `delta` — for any action whose effect
446
- * mutates the viewport itself (pan, view-zoom), because world-space
447
- * deltas become self-referential as the view shifts mid-drag.
448
- *
449
- * Absent when the underlying pointer events carried no
450
- * `clientX`/`clientY`.
455
+ }, id: keyof R & string) => boolean;
456
+
457
+ /** The chrome an in-flight gesture paints beside the scene. */
458
+
459
+ /**
460
+ * What a `polyline` overlay is, in a word — the narrowest thing an action can
461
+ * say about how its run should look without naming a paint.
462
+ *
463
+ * The named roles are the kit's; a painter maps each to a style and falls back
464
+ * to plain chrome for one it does not know, so a package outside core can coin
465
+ * its own and still be drawn.
466
+ */
467
+ type OverlayRole =
468
+ /** Generic in-flight chrome — the marquee/lasso paint. */
469
+ 'chrome'
470
+ /** A cut the gesture would make. */
471
+ | 'cut'
472
+ /** An edge the gesture would author between two points. */
473
+ | 'connector'
474
+ /** The stretch of a path a soft-selection drag reaches. */
475
+ | 'falloff'
476
+ /** What a lock holds a dragged point to — a line, a circle. */
477
+ | 'lock'
478
+ /** What a dragged item would be dropped on: the box it would go into. */
479
+ | 'target'
480
+ /** Where a dragged item would land: a reorder's insertion bar. */
481
+ | 'insert'
482
+ /** Where a dragged item is over and cannot land. */
483
+ | 'refused' | (string & {});
484
+ /**
485
+ * What an in-flight gesture proposes to paint *beside* the scene, returned by
486
+ * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
487
+ * shape being dragged out, the line a slice would cut along.
488
+ *
489
+ * Every variant is world geometry and nothing else — no `DrawCommand`, no
490
+ * color, no coordinate space to choose — so a surface rendering the same
491
+ * gesture some other way can still read it. The painter owns every style
492
+ * decision.
493
+ */
494
+ type OngoingOverlay = {
495
+ kind: 'marquee';
496
+ start: {
497
+ x: number;
498
+ y: number;
499
+ };
500
+ current: {
501
+ x: number;
502
+ y: number;
503
+ };
504
+ shiftHeld: boolean;
505
+ } | {
506
+ kind: 'lasso';
507
+ vertices: ReadonlyArray<{
508
+ x: number;
509
+ y: number;
510
+ }>;
511
+ current: {
512
+ x: number;
513
+ y: number;
514
+ };
515
+ shiftHeld: boolean;
516
+ } | {
517
+ /**
518
+ * An open run of world points the gesture is proposing — a cut line, a
519
+ * routed connector. `role` is the whole of what an action says about
520
+ * appearance.
521
+ */
522
+ kind: 'polyline';
523
+ /** At least two points. Anything shorter is dropped in resolution. */
524
+ points: ReadonlyArray<{
525
+ x: number;
526
+ y: number;
527
+ }>;
528
+ /** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
529
+ role?: OverlayRole;
530
+ } | {
531
+ /**
532
+ * A round brush dragged through world points: the area it has swept so
533
+ * far, and its footprint at the last point. The radius is in screen
534
+ * pixels, so the footprint keeps its size at every zoom.
535
+ */
536
+ kind: 'brush';
537
+ /** The brush's path so far, at least one point; the last is where it is now. */
538
+ points: ReadonlyArray<{
539
+ x: number;
540
+ y: number;
541
+ }>;
542
+ radiusPx: number;
543
+ } | {
544
+ /**
545
+ * Live insert-drag preview. Pre-commit there is no scene node to ghost
546
+ * via `previewIds()`/`previewPose()`, so insert paints its preview
547
+ * through the overlay instead.
548
+ *
549
+ * `bounds` is the AABB of the current drag (start/current normalized).
550
+ * `extras` is the per-kind extras the action already collected; the
551
+ * overlay renderer narrows on `shape` and casts the field shape it
552
+ * expects.
553
+ */
554
+ kind: 'insertPreview';
555
+ /** Which preview the dispatcher's overlay draws. The kit's own shape
556
+ * kinds are the ones it knows how to render; a consumer-defined kind
557
+ * is legal and simply gets no live preview. */
558
+ shape: string;
559
+ bounds: {
560
+ x: number;
561
+ y: number;
562
+ width: number;
563
+ height: number;
564
+ };
565
+ extras: unknown;
566
+ /** World-space point to paint a small "anchor" dot at, marking the
567
+ * click point — for radial shapes (polygon/star), where no vertex
568
+ * sits on it, and for any shape in center mode. */
569
+ anchorPoint?: {
570
+ x: number;
571
+ y: number;
572
+ };
573
+ /** World affines the shape will also be drawn under once it lands, by
574
+ * the container it is drawn into: the preview is painted under each. */
575
+ copies?: readonly (readonly [number, number, number, number, number, number])[];
576
+ } | {
577
+ /**
578
+ * Why the gesture cannot land where it is, in words for the user: a
579
+ * refusal's reason. A surface shows `text` beside the pointer, which
580
+ * `at` is, in world coordinates.
581
+ */
582
+ kind: 'reason';
583
+ at: {
584
+ x: number;
585
+ y: number;
586
+ };
587
+ text: string;
588
+ };
589
+ /** A handle's overlays as a list: empty when it has none. */
590
+ declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
591
+
592
+ /**
593
+ * Invoker — pluggable invocation strategy for an Action.
594
+ *
595
+ * An Action's `invoker` field describes how it's run. `immediate` invokers
596
+ * fire once (delete, align, undo). `ongoing` invokers open a phase machine
597
+ * driven by the dispatcher (move, resize, pinch-zoom).
598
+ */
599
+
600
+ /** A 2D point in either world or screen coordinates; geom's `Point`. */
601
+ type Point2 = Point;
602
+ /**
603
+ * A point in the space the dispatcher's `clientToWorld` maps the pointer into:
604
+ * the world, on a 2D canvas. A host with a camera of its own, such as a
605
+ * `@weasel-js/kernel3d` scene, passes identity, so there it is the pane's
606
+ * screen point and the deps reading it cast a ray through the camera they
607
+ * close over. Every world point the dispatcher hands out, and every dep
608
+ * payload it reaches, is one of these.
609
+ */
610
+ type WorldPoint = Point2;
611
+ /**
612
+ * Which UI affordance was hit at pointerdown. Populated by the dispatcher when
613
+ * the `affordanceAt` thunk is provided to `useGestureDispatcher`.
614
+ *
615
+ * `kind` is a discriminator string:
616
+ * - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
617
+ * `'handle:bottom-right'` — corner resize handles.
618
+ * - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
619
+ * edge resize handles.
620
+ * - `'rotate-handle'` — the rotation affordance.
621
+ * - `'anchor:N'` — a path anchor at index N.
622
+ */
623
+ interface AffordanceHit {
624
+ /** Discriminator string, e.g. `'handle:bottom-right'`. */
625
+ kind: string;
626
+ /** Id of whatever produced this hit — a kit affordance's `id`, or the
627
+ * registered layer's id. */
628
+ owner?: string;
629
+ /** `'exclusive'` means no binding may act on this point unless its target
630
+ * consults the affordance. `'shared'` (the default) competes on scope and
631
+ * specificity as bindings always have. */
632
+ strength?: 'exclusive' | 'shared';
633
+ /** Which gestures an exclusive claim bars. Omitted bars all of them. */
634
+ claimedKinds?: readonly ClaimableGesture[];
635
+ /** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
636
+ fixedPoint?: {
637
+ x: number;
638
+ y: number;
639
+ };
640
+ /** Which nodes this affordance belongs to. */
641
+ targetIds?: string[];
642
+ /** Set only when `kind` matches `'handle:*'`: which corner or edge stays
643
+ * fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
644
+ anchor?: ResizeAnchor;
645
+ /** Cursor to show while the pointer hovers this affordance (no gesture in
646
+ * flight). Unset falls through to action-cursor prediction, then to the
647
+ * active tool's cursor. */
648
+ cursor?: CursorSpec;
649
+ /**
650
+ * Free-form payload from whatever produced the hit, carried through to the
651
+ * matching action untouched. Kit affordances leave it unset; it exists for
652
+ * a registered layer's own chrome, where the hit-test already resolved
653
+ * *which* of its pieces was hit.
654
+ */
655
+ payload?: unknown;
656
+ }
657
+ /**
658
+ * One accumulated point on a drag trail: world-space position plus whatever
659
+ * stylus state the originating `PointerEvent` carried.
660
+ *
661
+ * The stylus fields are absent for mouse/touch on browsers that don't report
662
+ * them, and for synthetic events.
663
+ */
664
+ interface DragSample extends WorldPoint {
665
+ /** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
666
+ pressure?: number;
667
+ /** Degrees, ±90. Zero for mouse/touch. */
668
+ tiltX?: number;
669
+ /** Degrees, ±90. Zero for mouse/touch. */
670
+ tiltY?: number;
671
+ }
672
+ /** Per-invocation runtime context the dispatcher hands to an Invoker.
673
+ * Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
674
+ * populated only for matching gesture kinds. */
675
+ interface InvocationCtx {
676
+ world: WorldPoint;
677
+ /**
678
+ * The pointer in client/screen pixels, for an action that has to place DOM
679
+ * at the pointer. Absent on every invocation whose event carries no client
680
+ * coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
681
+ * read it with a fallback rather than assuming a point.
682
+ *
683
+ * A view-mutating drag wants `drag.screenDelta`, not the difference of two
684
+ * of these: world deltas are self-referential mid-pan, and so is any delta
685
+ * taken across a view change.
686
+ */
687
+ screen?: Point2;
688
+ modifiers: ModifierState;
689
+ deps: ActionDeps;
690
+ drag?: {
691
+ start: WorldPoint;
692
+ current: WorldPoint;
693
+ delta: Point2;
694
+ /**
695
+ * Drag delta in the surface's own CSS pixels from the drag origin: client
696
+ * pixels, or through the host's `clientToLocal` where it supplies one, so
697
+ * a canvas under a scaled ancestor pans with the pointer. Use this — never
698
+ * `delta` — for any action whose effect
699
+ * mutates the viewport itself (pan, view-zoom), because world-space
700
+ * deltas become self-referential as the view shifts mid-drag.
701
+ *
702
+ * Absent when the underlying pointer events carried no
703
+ * `clientX`/`clientY`.
451
704
  */
452
705
  screenDelta?: Point2;
453
706
  affordance?: AffordanceHit;
@@ -568,6 +821,8 @@ interface BindingOpts {
568
821
  * For a key binding: live while the pointer is over the surface, whichever
569
822
  * canvas holds the keys, and only then. For a key that means "here" — a
570
823
  * magnifier's peek — on a page with more than one surface that hears it.
824
+ * A held key follows the pointer: one already down when it arrives presses
825
+ * the binding, and leaving releases it.
571
826
  */
572
827
  over?: boolean;
573
828
  /**
@@ -619,6 +874,17 @@ interface OngoingHandle {
619
874
  */
620
875
  onMove?(ctx: InvocationCtx): void | 'pass';
621
876
  onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
877
+ /**
878
+ * Commit this gesture again, as it ended, with `params` in place of the ones it read: what lets a drag of an
879
+ * action declaring `params` be the tree's last operation, kept when its `onEnd('commit')` leaves one history
880
+ * entry. The keeper calls it after undoing that entry and putting back the selection the gesture started on.
881
+ * Recompute the commit from what the gesture recorded (its start state, the pointer as it ended or every move
882
+ * when the path taken matters, the modifiers held), never the live pointer, and commit one entry as `onEnd`
883
+ * did. `params` is every param the gesture resolved at its end with the panel's values laid over them; read
884
+ * them instead of `opts.params`. It may run any number of times. A gesture that cannot be recomputed
885
+ * faithfully leaves it out, and its action gets no panel.
886
+ */
887
+ replay?(params: Record<string, unknown>): void;
622
888
  /**
623
889
  * Optional preview surface: the in-flight state the canvas's preview-ghost
624
890
  * layer renders on top of the committed scene during the gesture.
@@ -674,648 +940,33 @@ interface OngoingHandle {
674
940
  }
675
941
  /** Fire-once invocation. Runs to completion synchronously (or fires off an
676
942
  * async side-effect; the registry doesn't wait). */
677
- interface ImmediateInvoker {
678
- timing: 'immediate';
679
- /** `params` carries the matched binding's opts.params. Invoked from the
680
- * command palette, or anywhere else with no per-binding context, `params`
681
- * is undefined; descriptors should default to a sensible variant.
682
- *
683
- * Return `'pass'` when, having looked at the event, there is nothing to do
684
- * with it — a scroll already at the end of its range — and the dispatcher
685
- * walks on to the next matching binding, as for a declined `enabled()`.
686
- * Prediction cannot run the invoker, so it still names this action. */
687
- run(deps: ActionDeps, params?: Record<string, unknown>): void | 'pass';
688
- }
689
- /** Phase-machine invocation. `start` opens the phase and returns the handle
690
- * the dispatcher pumps. */
691
- interface OngoingInvoker {
692
- timing: 'ongoing';
693
- start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
694
- /**
695
- * Screen travel, in CSS pixels, a pointer drag makes before the dispatcher calls `start`; a
696
- * binding's own `dragThresholdPx` wins over it. Until then the gesture is held for this action and
697
- * nothing else takes it, and a release short of it ends the gesture with `start` never called.
698
- * Unset, the action starts once the press is a drag at all (`gestures.dragPx`).
699
- */
700
- dragThresholdPx?: number;
701
- }
702
- /** Pluggable invocation strategy for an Action. */
703
- type Invoker = ImmediateInvoker | OngoingInvoker;
704
-
705
- /**
706
- * GestureBinding — connects a GestureSpec to an Action id (with per-binding
707
- * options). Tools own arrays of these on their `bindings` field; ambient
708
- * gesture-bindings are registered globally.
709
- */
710
-
711
- /** An interaction: a gesture spec composed with the id of the action it
712
- * invokes. Tools declare arrays of these; the dispatcher matches an incoming
713
- * input event against them and runs the winner's action. */
714
- interface GestureBinding {
715
- spec: GestureSpec;
716
- actionId: string;
717
- opts?: BindingOpts;
718
- }
719
- /**
720
- * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
721
- * (no per-binding opts) or an object form that pairs a spec with
722
- * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
723
- * Use the object form when two bindings for the same action differ only in
724
- * a runtime parameter — the dispatcher extracts `opts.params` and passes
725
- * them to `ImmediateInvoker.run` as its second argument.
726
- */
727
- type BoundGesture = GestureSpec | {
728
- spec: GestureSpec;
729
- opts: BindingOpts;
730
- };
731
- /**
732
- * The whole of an action `actionBindings` reads. Routing needs this much and
733
- * none of `Action`'s palette fields, so it is the narrowest type the
734
- * dispatcher's binding walk can be written against.
735
- */
736
- interface BindingSource {
737
- id: string;
738
- /** The gesture-spec form of the binding, read by the gesture dispatcher.
739
- * May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
740
- * or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
741
- * `{ spec, opts }` — use the object form for parametric actions where two
742
- * bindings for the same action differ only by `opts.params` (e.g. `flip`
743
- * with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
744
- * passes them to `ImmediateInvoker.run` as its second argument. */
745
- defaultBinding?: GestureSpec | BoundGesture[];
746
- }
747
- /**
748
- * Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
749
- * `GestureSpec` has `kind` at top level; the object form has `spec`.
750
- */
751
- declare function actionBindings(action: BindingSource): GestureBinding[];
752
-
753
- /** What a key no open keymap binds does: `'modal'` swallows it and leaves the
754
- * keymap open; `'cascade'` closes the keymap and dispatches it as if none were. */
755
- type KeymapUnbound = 'modal' | 'cascade';
756
- /** What holds a level opened with `{ hold: true }` open: no key releases it, so it closes on Escape. */
757
- declare const KEYMAP_HOLD = "hold";
758
- /** One open level of a {@link KeymapStack}. */
759
- interface OpenKeymap {
760
- readonly id: string;
761
- readonly label: string;
762
- /** The keymap's bindings as declared, for a hint to list. */
763
- readonly bindings: readonly GestureBinding[];
764
- /** The gesture id of the leader holding this level open, while it is held,
765
- * or {@link KEYMAP_HOLD}. */
766
- readonly heldBy?: string;
767
- /** Whether input arrived while the leader was held, so its release closes the level. */
768
- readonly usedWhileHeld: boolean;
769
- }
770
- /** What the keymaps made of the raw input in progress: `'taken'` when one of
771
- * them handled it, `'cascaded'` when it went on past them, `null` before either. */
772
- type KeymapVerdict = 'taken' | 'cascaded' | null;
773
- /** The keymaps open on one surface, innermost last; a canvas's `keymaps` dep. */
774
- interface KeymapStack {
775
- /** Every open level, outermost first. */
776
- open(): readonly OpenKeymap[];
777
- innermost(): OpenKeymap | null;
778
- /** Open a level. `heldBy` names what holds it open; `bindings` are what a hint lists. */
779
- push(id: string, label: string, opts?: {
780
- heldBy?: string;
781
- bindings?: readonly GestureBinding[];
782
- }): void;
783
- /** Mark the levels the last raw input opened as held by `heldBy`: the key
784
- * that opened them is still down. False when it opened none. */
785
- holdOpened(heldBy: string): boolean;
786
- /** The leader `heldBy` came up: close its level, those above it and the
787
- * tapped ones beneath it if input arrived meanwhile, else leave it open
788
- * for one more input. */
789
- release(heldBy: string): void;
790
- /** Close the innermost level: Escape. */
791
- closeInnermost(): void;
792
- /** One of the keymaps took this input, or a pointer gesture went past them.
793
- * Marks held levels used and closes once-levels down to the innermost held one. */
794
- inputSeen(): void;
795
- /** A key no keymap binds was swallowed: held levels count it as input, and nothing closes. */
796
- swallowed(): void;
797
- /** This input went on past the keymaps: as `inputSeen`, remembered as a cascade. */
798
- cascaded(): void;
799
- /** What the keymaps made of the input in progress. */
800
- verdict(): KeymapVerdict;
801
- closeAll(): void;
802
- /** Run `fn` as one raw input: closes made inside apply when it returns. Nests. */
803
- batch<T>(fn: () => T): T;
804
- subscribe(listener: () => void): () => void;
805
- getVersion(): number;
806
- }
807
- /** An empty {@link KeymapStack}. `useGestureDispatcher` makes one per surface. */
808
- declare function createKeymapStack(): KeymapStack;
809
-
810
- /**
811
- * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
812
- * the actions-layer binding-scope / matchBest logic on top.
813
- */
814
-
815
- /** Where a binding came from, which is also its priority: a held hotkey beats
816
- * the active tool, which beats bindings that are always in scope. */
817
- type BindingScope = 'ambient' | 'active' | 'hotkey';
818
- /** A binding paired with where it came from, ready to be matched against an
819
- * event. */
820
- interface ScopedBinding {
821
- binding: GestureBinding;
822
- scope: BindingScope;
823
- /** Id of the tool or contribution that declared this binding —
824
- * `'&'`-channel phase atoms resolve to it. `null` for a registered
825
- * action's own binding. */
826
- ownerId: string | null;
827
- }
828
- /** The binding that won a match. */
829
- interface MatchResult {
830
- binding: GestureBinding;
831
- scope: BindingScope;
832
- /** Id of the entry that declared the binding — propagated from
833
- * `ScopedBinding` so the dispatcher can record it as the handle owner. */
834
- ownerId: string | null;
835
- }
836
- /**
837
- * True when a spec's target actually consults the affordance hit rather than
838
- * only the body classification. `kindOf` predicates are handed the hit;
839
- * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
840
- * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
841
- * `bodyTarget` / `bodyKind` and never see it.
842
- *
843
- * A predicate declares which it is with `readsAffordance`; one that declares
844
- * nothing, which only untyped code can write, counts as not reading the hit.
845
- */
846
- declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
847
- /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
848
- declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
849
- /**
850
- * Find every binding that matches the event, sorted by precedence
851
- * (best → worst). Ordering rules:
852
- *
853
- * - Scopes by priority: hotkey > active > ambient.
854
- * - Within a scope, more-specific bindings beat less-specific (see
855
- * `specificity()`).
856
- * - Among same-specificity bindings, first-declared wins.
857
- */
858
- declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
859
- /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
860
- interface MatchOutcome {
861
- matches: MatchResult[];
862
- /** Bindings whose spec matched the event but which the claim barred, in
863
- * the same order `matches` uses. Filled only when `collectBarred` is set. */
864
- barred: MatchResult[];
865
- /** Owner of the exclusive claim, when one applied. */
866
- claimOwner?: string;
867
- }
868
- /**
869
- * {@link matchSorted} that can also report the bindings an exclusive claim
870
- * kept out. Collecting them costs a spec match per barred binding, so it is
871
- * opt-in — the dispatcher asks only when it is recording.
872
- */
873
- declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
874
- collectBarred?: boolean;
875
- warn?: (message: string) => void;
876
- }): MatchOutcome;
877
-
878
- /**
879
- * How one input was resolved: every binding that matched it, what each filter
880
- * dropped, the order the rest were ranked in and why, and what the walk down
881
- * that order did. The dispatcher fills one per input in dev builds, and
882
- * `Dispatcher.explain` fills one on request without invoking anything.
883
- *
884
- * Plain data — no functions, no live objects — so it can be kept in a trace
885
- * buffer, serialized, or built by hand as a fixture.
886
- */
887
- interface DispatchRecord {
888
- kind: 'dispatch';
889
- ts: number;
890
- input: DispatchRecordInput;
891
- /** Every binding whose spec matched the input, in matcher order, including
892
- * those a filter then dropped. */
893
- matched: readonly RecordCandidate[];
894
- dropped: readonly DroppedCandidate[];
895
- /** The candidates that survived both filters, best first. */
896
- ranked: readonly RankedCandidate[];
897
- /** True when nothing was invoked: the record answers "what would happen". */
898
- predicted: boolean;
899
- /** Action id of the candidate that fired (or would), else `null`. */
900
- fired: string | null;
901
- outcome: 'handled' | 'unhandled';
902
- }
903
- /** The input a {@link DispatchRecord} describes, reduced to plain data. */
904
- interface DispatchRecordInput {
905
- /** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
906
- eventKind: string;
907
- /** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
908
- key?: string;
909
- modifiers: {
910
- alt: boolean;
911
- ctrl: boolean;
912
- meta: boolean;
913
- shift: boolean;
914
- };
915
- /** The view the input was routed to; `null` for the surface's own camera. */
916
- viewId: string | null;
917
- /** World point, for events that carry one. */
918
- world?: {
919
- x: number;
920
- y: number;
921
- };
922
- /** Active mode id, when the host supplies a rule context. */
923
- mode?: string;
924
- }
925
- /** One matched binding. */
926
- interface RecordCandidate {
927
- actionId: string;
928
- /** The binding's spec in route grammar — one per arg alternative. */
929
- routes: readonly string[];
930
- scope: BindingScope;
931
- /** The tool or contribution that declared the binding; `null` for an action's own binding. */
932
- ownerId: string | null;
933
- /** Whether the binding's `views` names the routed view. */
934
- namesView: boolean;
935
- /** `specificity(spec)`: target, modifiers, phase, exact. */
936
- specificity: readonly [number, number, number, number];
937
- /** The action's `eligible` rule as text, when it declares one. */
938
- eligible?: string;
939
- }
940
- /** A matched candidate a filter removed before ranking, and which filter. */
941
- type DroppedCandidate =
942
- /** An exclusive claim on the input barred bindings that do not read the
943
- * claim's affordance. */
944
- {
945
- candidate: RecordCandidate;
946
- filter: 'claim';
947
- owner?: string;
948
- }
949
- /** The action's `eligible` rule does not hold. */
950
- | {
951
- candidate: RecordCandidate;
952
- filter: 'ineligible';
953
- rule: string;
954
- };
955
- /** One field of the `specificity` tuple, in the order it is compared. */
956
- type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
957
- /** The ranking step that put a candidate below the one before it. */
958
- type PlacedBy = {
959
- step: 'first';
960
- }
961
- /** The one above names the routed view; this one does not. */
962
- | {
963
- step: 'view';
964
- }
965
- /** The one above sits in a higher scope tier. */
966
- | {
967
- step: 'tier';
968
- }
969
- /** Same tier; the one above is more specific in `part`. */
970
- | {
971
- step: 'specificity';
972
- part: SpecificityPart;
973
- }
974
- /** Tied; the one above is gated by an `eligible` rule that holds. */
975
- | {
976
- step: 'context';
977
- }
978
- /** Tied on everything; registration order. */
979
- | {
980
- step: 'order';
981
- };
982
- /** What the walk did with one ranked candidate. */
983
- type WalkStep = {
984
- kind: 'fired';
985
- }
986
- /** Predicted records only: this candidate would fire. Nothing was started,
987
- * so an ongoing action here may still bail at `start()` for real. */
988
- | {
989
- kind: 'would-fire';
990
- }
991
- /** `enabled()` returned this reason. */
992
- | {
993
- kind: 'declined';
994
- reason: string;
995
- }
996
- /** An ongoing action's `start()` returned an empty handle, or an
997
- * immediate one's `run()` returned `'pass'`. */
998
- | {
999
- kind: 'empty-handle';
1000
- }
1001
- /** An ongoing action bound to a press-time `pointerDown` spec; refused. */
1002
- | {
1003
- kind: 'misbound';
1004
- }
1005
- /** Its action was already tried higher up; each runs at most once. */
1006
- | {
1007
- kind: 'duplicate';
1008
- } | {
1009
- kind: 'no-such-action';
1010
- }
1011
- /** Below the winner; never asked. */
1012
- | {
1013
- kind: 'not-asked';
1014
- }
1015
- /** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
1016
- | {
1017
- kind: 'outranked';
1018
- };
1019
- /** A candidate that survived the filters: why it sits where it does, and
1020
- * what the walk did with it. */
1021
- interface RankedCandidate {
1022
- candidate: RecordCandidate;
1023
- placedBy: PlacedBy;
1024
- walk: WalkStep;
1025
- }
1026
-
1027
- /** One entry per "mode change" — a kit state transition that isn't a
1028
- * dispatched input event but explains why an input was (or wasn't) handled
1029
- * later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
1030
- interface ModeSwitchLogEntry {
1031
- kind: 'mode';
1032
- ts: number;
1033
- /** Logical mode name, e.g. `'editAnchors.editingId'`. */
1034
- mode: string;
1035
- from: string | null;
1036
- to: string | null;
1037
- /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
1038
- * `'selection-dropped target'`, etc. */
1039
- detail?: string;
1040
- }
1041
- /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
1042
- type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
1043
- /** A rolling buffer that records in a dev build, and in any build while something watches it. */
1044
- interface TraceRing<T> {
1045
- /** Whether a push now keeps its entry. A caller building an entry checks first. */
1046
- recording(): boolean;
1047
- push(entry: T): void;
1048
- /** The entries pushed after `cursor`, oldest first, and the cursor to pass
1049
- * next time. Start from 0. Entries the buffer has dropped are skipped. */
1050
- since(cursor: number): {
1051
- entries: T[];
1052
- cursor: number;
1053
- };
1054
- /** Turn recording on until the returned release is called. */
1055
- watch(): () => void;
1056
- /** Hear each kept push. Returns the unsubscribe. */
1057
- subscribe(listener: () => void): () => void;
1058
- }
1059
- /** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
1060
- declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
1061
- /** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
1062
- * every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
1063
- interface DispatchTrace extends TraceRing<TraceLogEntry> {
1064
- recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1065
- /** Offer the record for what a press at the pointer would do, built only
1066
- * when {@link DispatchTrace.live} reads it. `null` withdraws it. */
1067
- setLive(explain: (() => DispatchRecord) | null): void;
1068
- live(): DispatchRecord | null;
1069
- }
1070
- /** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
1071
- declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
1072
-
1073
- /**
1074
- * Live state read by rule evaluation. Built once per frame on the consuming
1075
- * surface — chrome-caps, the affordance pipeline, the dispatcher's
1076
- * eligibility filter — and discarded.
1077
- *
1078
- * Adding a new field is additive: existing rules don't change, new
1079
- * selector atoms can read it.
1080
- */
1081
- interface RuleCtx {
1082
- readonly focused: boolean;
1083
- readonly selection: readonly NodeId[];
1084
- readonly multiActive: boolean;
1085
- readonly modifiers: ModifierState;
1086
- readonly action: {
1087
- readonly kind: string | null;
1088
- readonly id: string | null;
1089
- };
1090
- readonly hover: NodeId | null;
1091
- /**
1092
- * How far the viewport is zoomed in, as one number — `1` at native scale.
1093
- *
1094
- * A scalar rather than the `View` this used to carry, because `zoomAtLeast`
1095
- * is the only selector that ever read one and a number is all it needs. A
1096
- * host whose viewport is a camera has no `View` to hand over and would
1097
- * otherwise have to invent one to use eligibility at all; it omits this
1098
- * instead, and `zoomAtLeast` declines rather than matching on a fiction.
1099
- */
1100
- readonly zoom?: number;
1101
- /** Active mode id. `'normal'` when no non-default mode is engaged. */
1102
- readonly mode: string;
1103
- /** Capability tags allowed by the active mode (the union of
1104
- * `ModeDefinition.allows` plus implicit tags). The `capability:`
1105
- * selector reads this to determine whether a tag is permitted. */
1106
- readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
1107
- /** Whether the current selection may be resized. `<SceneCanvas>` folds
1108
- * `selectTool.resize.resizable` over the selection (true only when every
1109
- * selected node is resizable). Read by the `resizable:` selector to gate
1110
- * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
1111
- * resizable — back-compat: handles show unless a consumer opts a node out. */
1112
- readonly selectionResizable?: boolean;
1113
- /** Whether a path is currently in anchor-edit mode. Read by the
1114
- * `editingAnchors:` selector, which gates the path-edit chrome.
1115
- *
1116
- * This is deliberately a fact about state, not about permission: the
1117
- * anchor overlay and the anchor hit-test must agree, and the thing they
1118
- * must agree on is "is there an edited path right now", which no
1119
- * capability or mode id answers. A mode that allows `edits-anchors`
1120
- * with nothing being edited should draw no anchors. Absent is treated
1121
- * as false. */
1122
- readonly editingAnchors?: boolean;
1123
- /** Device facts — pointer coarseness, hover capability, density.
1124
- *
1125
- * Absent (legacy ctx builders) is treated as
1126
- * {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
1127
- * density 1. That is what the kit assumed before this field existed, so
1128
- * an absent profile is behavior-preserving by construction. */
1129
- readonly device?: DeviceProfile;
1130
- /** The view the input being dispatched landed in — a view id, or `null`
1131
- * for the surface's own camera. The dispatcher fills it; absent anywhere
1132
- * else a rule is evaluated. */
1133
- readonly viewId?: string | null;
1134
- }
1135
- /** The live state a `RuleCtx` is assembled from — the chrome context plus
1136
- * what the active mode allows. */
1137
- interface BuildRuleCtxArgs {
1138
- focused: boolean;
1139
- selection: readonly NodeId[];
1140
- multiActive: boolean;
1141
- modifiers: ModifierState;
1142
- action: {
1143
- kind: string | null;
1144
- id: string | null;
1145
- };
1146
- hover: NodeId | null;
1147
- /** See {@link RuleCtx.zoom}. */
1148
- zoom?: number;
1149
- mode: string;
1150
- allowedCapabilities: ReadonlySet<CapabilityTag>;
1151
- /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
1152
- selectionResizable?: boolean;
1153
- /** Optional — omitted means "no path is being anchor-edited". */
1154
- editingAnchors?: boolean;
1155
- /** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
1156
- device?: DeviceProfile;
1157
- }
1158
- /**
1159
- * Capability set to assume when no mode registry is wired.
1160
- *
1161
- * "No modes configured" means *everything the default mode permits*, not
1162
- * *nothing permitted*. The empty set is the wrong default: it makes every
1163
- * `capability:` rule evaluate false, so a kit default written the
1164
- * recommended way (capability rules outlive new modes; mode rules have to
1165
- * be edited) would silently hide its chrome for every consumer that hasn't
1166
- * opted into modality. `NORMAL.allows` plus the implicit tags is what a
1167
- * mode-less canvas actually behaves like.
1168
- */
1169
- declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
1170
- /** Gather the current canvas and mode state into the context that action
1171
- * eligibility and chrome-visibility rules are evaluated against. */
1172
- declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
1173
-
1174
- /**
1175
- * A selector is a conjunction of key/value tests. Multiple keys at the same
1176
- * level AND together. Each key maps to a selector primitive in the evaluator.
1177
- */
1178
- interface Selector {
1179
- selection?: {
1180
- is?: number;
1181
- atLeast?: number;
1182
- empty?: boolean;
1183
- };
1184
- mode?: string | {
1185
- not: string;
1186
- } | {
1187
- in: readonly string[];
1188
- };
1189
- capability?: CapabilityTag | readonly CapabilityTag[] | {
1190
- in: readonly CapabilityTag[];
1191
- } | {
1192
- not: CapabilityTag;
1193
- };
1194
- gesturing?: boolean;
1195
- actionIs?: string;
1196
- modifierHeld?: keyof ModifierState;
1197
- focused?: boolean;
1198
- hovering?: boolean;
1199
- hoveringSelected?: boolean;
1200
- zoomAtLeast?: number;
1201
- /** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
1202
- * mode. Absent flag is treated as `false`. */
1203
- editingAnchors?: boolean;
1204
- /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
1205
- * (resizable), so `{ resizable: true }` passes for legacy ctx builders
1206
- * that don't compute it. */
1207
- resizable?: boolean;
1208
- /** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
1209
- * (touch, most styluses). Absent device is treated as `false`. */
1210
- coarsePointer?: boolean;
1211
- /** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
1212
- * device is treated as `true`. */
1213
- canHover?: boolean;
1214
- }
1215
- /**
1216
- * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
1217
- * over `Selector` leaves. `when` is the escape hatch — its closure is
1218
- * opaque to introspection and should be avoided when a declarative form
1219
- * exists. Empty `all` is true; empty `any` is false.
1220
- */
1221
- type Rule = Selector | {
1222
- all: readonly Rule[];
1223
- } | {
1224
- any: readonly Rule[];
1225
- } | {
1226
- not: Rule;
1227
- } | {
1228
- when: (ctx: RuleCtx) => boolean;
1229
- };
1230
- /** Constant rules. Kept here so they have a single source. */
1231
- declare const ALWAYS: Rule;
1232
- /** A rule that never passes. `any` of nothing is false. */
1233
- declare const NEVER: Rule;
1234
- /**
1235
- * Render a {@link Rule} as a short human-readable string, for inspectors and
1236
- * diagnostics that need to say WHICH rule decided something.
1237
- *
1238
- * ```
1239
- * { capability: 'edits-page' } → capability:edits-page
1240
- * { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
1241
- * { not: { selection: { empty: true } } } → not(selection:empty=true)
1242
- * { when: function hasPath() { … } } → when(hasPath)
1243
- * ```
1244
- *
1245
- * Never throws and always terminates.
1246
- */
1247
- declare function describeRule(rule: Rule): string;
1248
- /** Evaluate a rule against a context. Pure, and cheap enough to run per
1249
- * frame for every piece of chrome. */
1250
- declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
1251
- /**
1252
- * Composable eligibility predicate with a fluent surface. `cond(ctx)`
1253
- * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
1254
- * Conditions wrapping new trees.
1255
- *
1256
- * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
1257
- * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
1258
- * disjunction, name the subexpression or use the top-level `or(...)`.
1259
- */
1260
- interface Condition {
1261
- (ctx: RuleCtx): boolean;
1262
- readonly rule: Rule;
1263
- /** `this && other` */
1264
- and(other: Condition | Rule): Condition;
1265
- /** `this || other` */
1266
- or(other: Condition | Rule): Condition;
1267
- /** `this && !other` */
1268
- andNot(other: Condition | Rule): Condition;
1269
- /** `this || !other` */
1270
- orNot(other: Condition | Rule): Condition;
1271
- }
1272
-
1273
- /**
1274
- * Whether two rules can never hold at once — no `RuleCtx` passes both.
1275
- *
1276
- * Conservative: `true` is a proof, `false` means "may overlap". It reasons
1277
- * about the selector values themselves (two different `mode`s, an empty
1278
- * selection against a non-empty one, `focused: true` against `false`) and
1279
- * through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
1280
- * everything. The route-conflict check uses it to stay quiet about bindings
1281
- * that share a tuple but are never eligible together.
1282
- */
1283
- declare function rulesExclusive(a: Rule, b: Rule): boolean;
1284
- /**
1285
- * Whether `rule` can hold while `mode` is active. Only its `mode` and
1286
- * `capability` tests are settled by the mode; every other test could go either
1287
- * way, so `false` is a proof and `true` means "may hold".
1288
- */
1289
- declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
1290
-
1291
- /** The detected half of a profile — everything except the derived scale. */
1292
- type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
1293
- /**
1294
- * Handle/hit multiplier applied on a coarse pointer.
1295
- *
1296
- * 8px handle → 14px; 24px rotation distance → 42px. Counting the
1297
- * surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
1298
- * minimum-touch-target band. One constant so it is tunable in one place.
1299
- */
1300
- declare const COARSE_TARGET_SCALE = 1.75;
1301
- /**
1302
- * Fold consumer overrides over detected facts and derive `targetScale`.
1303
- *
1304
- * `targetScale` is re-derived AFTER the merge, so an override of
1305
- * `coarsePointer` alone scales the chrome as expected. An explicit
1306
- * `targetScale` override wins over the derivation.
1307
- */
1308
- declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
1309
- /**
1310
- * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
1311
- * absent-means value for `RuleCtx.device`.
1312
- *
1313
- * Derived from `resolveDeviceProfile` rather than hand-written, so it can
1314
- * never drift from what the function would compute for the same facts. A
1315
- * mouse-like device is the safe default: it is what the kit has always
1316
- * assumed, so an absent profile changes nothing for existing consumers.
1317
- */
1318
- declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
943
+ interface ImmediateInvoker {
944
+ timing: 'immediate';
945
+ /** `params` carries the matched binding's opts.params. Invoked from the
946
+ * command palette, or anywhere else with no per-binding context, `params`
947
+ * is undefined; descriptors should default to a sensible variant.
948
+ *
949
+ * Return `'pass'` when, having looked at the event, there is nothing to do
950
+ * with it — a scroll already at the end of its range — and the dispatcher
951
+ * walks on to the next matching binding, as for a declined `enabled()`.
952
+ * Prediction cannot run the invoker, so it still names this action. */
953
+ run(deps: ActionDeps, params?: Record<string, unknown>): void | 'pass';
954
+ }
955
+ /** Phase-machine invocation. `start` opens the phase and returns the handle
956
+ * the dispatcher pumps. */
957
+ interface OngoingInvoker {
958
+ timing: 'ongoing';
959
+ start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
960
+ /**
961
+ * Screen travel, in CSS pixels, a pointer drag makes before the dispatcher calls `start`; a
962
+ * binding's own `dragThresholdPx` wins over it. Until then the gesture is held for this action and
963
+ * nothing else takes it, and a release short of it ends the gesture with `start` never called.
964
+ * Unset, the action starts once the press is a drag at all (`gestures.dragPx`).
965
+ */
966
+ dragThresholdPx?: number;
967
+ }
968
+ /** Pluggable invocation strategy for an Action. */
969
+ type Invoker = ImmediateInvoker | OngoingInvoker;
1319
970
 
1320
971
  /**
1321
972
  * A settings form an action carries: what a surface draws in place of a bare
@@ -1351,6 +1002,54 @@ declare function readActionSettings(action: Action, deps: ActionDeps | undefined
1351
1002
  /** The params a change of the field at `path` to `value` triggers the action with. */
1352
1003
  declare function actionSettingsParams(settings: ActionSettings, path: string, value: unknown): Record<string, unknown> | null;
1353
1004
 
1005
+ /**
1006
+ * GestureBinding — connects a GestureSpec to an Action id (with per-binding
1007
+ * options). Tools own arrays of these on their `bindings` field; ambient
1008
+ * gesture-bindings are registered globally.
1009
+ */
1010
+
1011
+ /** An interaction: a gesture spec composed with the id of the action it
1012
+ * invokes. Tools declare arrays of these; the dispatcher matches an incoming
1013
+ * input event against them and runs the winner's action. */
1014
+ interface GestureBinding {
1015
+ spec: GestureSpec;
1016
+ actionId: string;
1017
+ opts?: BindingOpts;
1018
+ }
1019
+ /**
1020
+ * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
1021
+ * (no per-binding opts) or an object form that pairs a spec with
1022
+ * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
1023
+ * Use the object form when two bindings for the same action differ only in
1024
+ * a runtime parameter — the dispatcher extracts `opts.params` and passes
1025
+ * them to `ImmediateInvoker.run` as its second argument.
1026
+ */
1027
+ type BoundGesture = GestureSpec | {
1028
+ spec: GestureSpec;
1029
+ opts: BindingOpts;
1030
+ };
1031
+ /**
1032
+ * The whole of an action `actionBindings` reads. Routing needs this much and
1033
+ * none of `Action`'s palette fields, so it is the narrowest type the
1034
+ * dispatcher's binding walk can be written against.
1035
+ */
1036
+ interface BindingSource {
1037
+ id: string;
1038
+ /** The gesture-spec form of the binding, read by the gesture dispatcher.
1039
+ * May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
1040
+ * or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
1041
+ * `{ spec, opts }` — use the object form for parametric actions where two
1042
+ * bindings for the same action differ only by `opts.params` (e.g. `flip`
1043
+ * with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
1044
+ * passes them to `ImmediateInvoker.run` as its second argument. */
1045
+ defaultBinding?: GestureSpec | BoundGesture[];
1046
+ }
1047
+ /**
1048
+ * Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
1049
+ * `GestureSpec` has `kind` at top level; the object form has `spec`.
1050
+ */
1051
+ declare function actionBindings(action: BindingSource): GestureBinding[];
1052
+
1354
1053
  /** What content being routed brings to `Action.enabled`: a drop's or a
1355
1054
  * paste's items, or a carry's payload with no items. */
1356
1055
  interface Carried {
@@ -1411,6 +1110,15 @@ interface ActionPresentation {
1411
1110
  * triggers it with the params `settings.params` makes. Not combined with
1412
1111
  * `variants`. */
1413
1112
  settings?: ActionSettings;
1113
+ /** The params the invoker reads, as a form: one leaf per param, keyed by its name, with its label, range,
1114
+ * step, and default. A run that commits one history entry is kept as the tree's last operation, which a panel
1115
+ * draws as this form so the params can be changed after the fact: any immediate run, and a gesture whose
1116
+ * handle can `replay` (see `OngoingHandle.replay`). `ActionParamSchema` stays what types a tuning; this is
1117
+ * what a person sees.
1118
+ *
1119
+ * A function answers the form for one run from the params it resolved, for an action whose params depend
1120
+ * on what it was asked to do (`insert`'s on the kind of shape), or `null` for a run with none to show. */
1121
+ params?: PrefGroup | ((params: Readonly<Record<string, unknown>>) => PrefGroup | null);
1414
1122
  }
1415
1123
  /**
1416
1124
  * One entry a parametric action contributes to a bar, menu or palette.
@@ -1562,20 +1270,445 @@ interface ActionDispatch extends BindingSource {
1562
1270
  /**
1563
1271
  * Single registered action.
1564
1272
  */
1565
- interface Action extends ActionDispatch, ActionPresentation {
1566
- }
1273
+ interface Action extends ActionDispatch, ActionPresentation {
1274
+ }
1275
+ /**
1276
+ * The whole of an actions registry the dispatcher reads. It walks the list
1277
+ * once per event to build its actionId lookup and never mutates it, so the
1278
+ * routing side depends on this rather than on `ActionsRegistry`, whose
1279
+ * register / mute / trigger surface is the React provider's.
1280
+ */
1281
+ interface ActionSource {
1282
+ list(): readonly Action[];
1283
+ }
1284
+ /** `action`'s hover cursor for an invocation given `params`: its `cursor`,
1285
+ * asked when it is a function. */
1286
+ declare function actionCursor(action: Pick<Action, 'cursor'> | undefined, params?: Record<string, unknown>): CursorSpec | undefined;
1287
+
1288
+ /**
1289
+ * Tuning: what a host changes about an action it did not write — the params
1290
+ * every invocation starts from, and bindings added beside the action's own —
1291
+ * without restating the descriptor. `ActionsRegistry.tune` holds tunings and
1292
+ * applies them to whatever descriptor is live for the id.
1293
+ */
1294
+
1295
+ /** An action's params, or a thunk read each time the action asks for them. */
1296
+ type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
1297
+ /** A binding added for an action: a gesture alone, or a gesture with the
1298
+ * options it passes — the {@link BoundGesture} shape, its params typed. */
1299
+ type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
1300
+ spec: GestureSpec;
1301
+ opts: Omit<BindingOpts, 'params'> & {
1302
+ params?: ActionParams<P>;
1303
+ };
1304
+ };
1305
+ /** What a host changes about one action. */
1306
+ interface ActionTuning<P = Record<string, unknown>> {
1307
+ /** Params every invocation starts from. What a binding or a `trigger` call
1308
+ * passes is laid over them, so these fill in what it leaves out. A thunk is
1309
+ * read on every call, and an ongoing action resolving its `opts.params`
1310
+ * mid-gesture sees the value as it is then. */
1311
+ params?: ActionParams<P>;
1312
+ /** Bindings added after the action's own `defaultBinding`. */
1313
+ bindings?: readonly TunedBinding<P>[];
1314
+ /** Keys for some of the action's `modifierRoles`, by role id. A binding's
1315
+ * own `opts.roles` is laid over these. */
1316
+ roles?: RoleRebinds;
1317
+ }
1318
+ /** `action` with each tuning applied in turn, a later one's params over an
1319
+ * earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
1320
+ declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
1321
+
1322
+ /**
1323
+ * The last operation: the latest run of an action that declares `params` and
1324
+ * committed one history entry, kept so its params can be changed after the
1325
+ * fact. A change undoes that entry and runs the action again in its place: an
1326
+ * immediate action is run again with the params it was handed, and a gesture
1327
+ * is replayed by its handle (`OngoingHandle.replay`).
1328
+ */
1329
+
1330
+ /** One operation that can still be adjusted. */
1331
+ interface LastOperation {
1332
+ id: string;
1333
+ /** What a panel titles it: the form's name, else the action's label. */
1334
+ label: string;
1335
+ /** The action's `params` form. */
1336
+ schema: PrefGroup;
1337
+ /** What it ran with, one value per leaf of `schema`, by the leaf's path. */
1338
+ values: Readonly<Record<string, unknown>>;
1339
+ }
1340
+ /** The last operation of one actions tree, and the two things to do with it. */
1341
+ interface LastOperationStore {
1342
+ /** The operation, or `null` once anything else reached the history, or it was dismissed. */
1343
+ get(): LastOperation | null;
1344
+ subscribe(listener: () => void): () => void;
1345
+ /**
1346
+ * Undo the operation's entry, put back the selection it ran on, and run it again with `patch` laid over its
1347
+ * values (a gesture is replayed by its handle), keeping the entry that makes in place of the old one: the
1348
+ * history, or the journal open on it, still holds one entry for it. The merged values become the action's
1349
+ * params for the rest of the session, under any tuning. Returns whether it ran.
1350
+ */
1351
+ adjust(patch: Readonly<Record<string, unknown>>): boolean;
1352
+ /** Forget the operation; the scene and the history stay as they are. */
1353
+ dismiss(): void;
1354
+ }
1355
+ /** Every selection a host holds, captured: calling what `take` returns puts all of it back. */
1356
+ interface SelectionSnapshotDep {
1357
+ take(): () => void;
1358
+ }
1359
+ /** What the store reads from the scope an operation ran in. */
1360
+ interface LastOperationHost {
1361
+ history(): History | null;
1362
+ takeSelection(): (() => void) | null;
1363
+ /** The params a run of `id` handed `given` starts with: every tuning's, `given` laid over them. */
1364
+ paramsOf(id: string, given: Record<string, unknown> | undefined): Record<string, unknown>;
1365
+ /** Run the action live for `id` here with `params`, as `trigger` would. */
1366
+ rerun(id: string, params: Record<string, unknown>): void;
1367
+ }
1368
+ /** The store one actions tree keeps at its root. `onSession` is told when an action's session params change. */
1369
+ declare class LastOperationKeeper implements LastOperationStore {
1370
+ private readonly onSession;
1371
+ private held;
1372
+ private busy;
1373
+ private readonly listeners;
1374
+ private readonly session;
1375
+ constructor(onSession: () => void);
1376
+ readonly get: () => LastOperation | null;
1377
+ readonly subscribe: (listener: () => void) => (() => void);
1378
+ readonly dismiss: () => void;
1379
+ readonly adjust: (patch: Readonly<Record<string, unknown>>) => boolean;
1380
+ /** The tuning holding `id`'s session params, laid under every other; `null` before it has any. */
1381
+ sessionTuning(id: string): ActionTuning | null;
1382
+ /** `action` with its run kept as the last operation when it commits one entry: an immediate action declaring
1383
+ * `params`, or an ongoing one declaring them whose gesture's handle can `replay`, and in either case only a
1384
+ * run its `params` answers a form for. Any other action comes back as it is. */
1385
+ wrap(action: Action, host: LastOperationHost): Action;
1386
+ /** `invoker` with each gesture whose handle can `replay` kept as the last operation when its commit leaves
1387
+ * one entry. The selection is taken at the press and the params at the release, as the commit read them. */
1388
+ private ongoing;
1389
+ private hold;
1390
+ private remember;
1391
+ }
1392
+
1393
+ /** What a key no open keymap binds does: `'modal'` swallows it and leaves the
1394
+ * keymap open; `'cascade'` closes the keymap and dispatches it as if none were. */
1395
+ type KeymapUnbound = 'modal' | 'cascade';
1396
+ /** What holds a level opened with `{ hold: true }` open: no key releases it, so it closes on Escape. */
1397
+ declare const KEYMAP_HOLD = "hold";
1398
+ /** One open level of a {@link KeymapStack}. */
1399
+ interface OpenKeymap {
1400
+ readonly id: string;
1401
+ readonly label: string;
1402
+ /** The keymap's bindings as declared, for a hint to list. */
1403
+ readonly bindings: readonly GestureBinding[];
1404
+ /** The gesture id of the leader holding this level open, while it is held,
1405
+ * or {@link KEYMAP_HOLD}. */
1406
+ readonly heldBy?: string;
1407
+ /** Whether input arrived while the leader was held, so its release closes the level. */
1408
+ readonly usedWhileHeld: boolean;
1409
+ }
1410
+ /** What the keymaps made of the raw input in progress: `'taken'` when one of
1411
+ * them handled it, `'cascaded'` when it went on past them, `null` before either. */
1412
+ type KeymapVerdict = 'taken' | 'cascaded' | null;
1413
+ /** The keymaps open on one surface, innermost last; a canvas's `keymaps` dep. */
1414
+ interface KeymapStack {
1415
+ /** Every open level, outermost first. */
1416
+ open(): readonly OpenKeymap[];
1417
+ innermost(): OpenKeymap | null;
1418
+ /** Open a level. `heldBy` names what holds it open; `bindings` are what a hint lists. */
1419
+ push(id: string, label: string, opts?: {
1420
+ heldBy?: string;
1421
+ bindings?: readonly GestureBinding[];
1422
+ }): void;
1423
+ /** Mark the levels the last raw input opened as held by `heldBy`: the key
1424
+ * that opened them is still down. False when it opened none. */
1425
+ holdOpened(heldBy: string): boolean;
1426
+ /** The leader `heldBy` came up: close its level, those above it and the
1427
+ * tapped ones beneath it if input arrived meanwhile, else leave it open
1428
+ * for one more input. */
1429
+ release(heldBy: string): void;
1430
+ /** Close the innermost level: Escape. */
1431
+ closeInnermost(): void;
1432
+ /** One of the keymaps took this input, or a pointer gesture went past them.
1433
+ * Marks held levels used and closes once-levels down to the innermost held one. */
1434
+ inputSeen(): void;
1435
+ /** A key no keymap binds was swallowed: held levels count it as input, and nothing closes. */
1436
+ swallowed(): void;
1437
+ /** This input went on past the keymaps: as `inputSeen`, remembered as a cascade. */
1438
+ cascaded(): void;
1439
+ /** What the keymaps made of the input in progress. */
1440
+ verdict(): KeymapVerdict;
1441
+ closeAll(): void;
1442
+ /** Run `fn` as one raw input: closes made inside apply when it returns. Nests. */
1443
+ batch<T>(fn: () => T): T;
1444
+ subscribe(listener: () => void): () => void;
1445
+ getVersion(): number;
1446
+ }
1447
+ /** An empty {@link KeymapStack}. `useGestureDispatcher` makes one per surface. */
1448
+ declare function createKeymapStack(): KeymapStack;
1449
+
1450
+ /**
1451
+ * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
1452
+ * the actions-layer binding-scope / matchBest logic on top.
1453
+ */
1454
+
1455
+ /** Where a binding came from, which is also its priority: a held hotkey beats
1456
+ * the active tool, which beats bindings that are always in scope. */
1457
+ type BindingScope = 'ambient' | 'active' | 'hotkey';
1458
+ /** A binding paired with where it came from, ready to be matched against an
1459
+ * event. */
1460
+ interface ScopedBinding {
1461
+ binding: GestureBinding;
1462
+ scope: BindingScope;
1463
+ /** Id of the tool or contribution that declared this binding —
1464
+ * `'&'`-channel phase atoms resolve to it. `null` for a registered
1465
+ * action's own binding. */
1466
+ ownerId: string | null;
1467
+ }
1468
+ /** The binding that won a match. */
1469
+ interface MatchResult {
1470
+ binding: GestureBinding;
1471
+ scope: BindingScope;
1472
+ /** Id of the entry that declared the binding — propagated from
1473
+ * `ScopedBinding` so the dispatcher can record it as the handle owner. */
1474
+ ownerId: string | null;
1475
+ }
1476
+ /**
1477
+ * True when a spec's target actually consults the affordance hit rather than
1478
+ * only the body classification. `kindOf` predicates are handed the hit;
1479
+ * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
1480
+ * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
1481
+ * `bodyTarget` / `bodyKind` and never see it.
1482
+ *
1483
+ * A predicate declares which it is with `readsAffordance`; one that declares
1484
+ * nothing, which only untyped code can write, counts as not reading the hit.
1485
+ */
1486
+ declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
1487
+ /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
1488
+ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
1489
+ /**
1490
+ * Find every binding that matches the event, sorted by precedence
1491
+ * (best → worst). Ordering rules:
1492
+ *
1493
+ * - Scopes by priority: hotkey > active > ambient.
1494
+ * - Within a scope, more-specific bindings beat less-specific (see
1495
+ * `specificity()`).
1496
+ * - Among same-specificity bindings, first-declared wins.
1497
+ */
1498
+ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
1499
+ /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
1500
+ interface MatchOutcome {
1501
+ matches: MatchResult[];
1502
+ /** Bindings whose spec matched the event but which the claim barred, in
1503
+ * the same order `matches` uses. Filled only when `collectBarred` is set. */
1504
+ barred: MatchResult[];
1505
+ /** Owner of the exclusive claim, when one applied. */
1506
+ claimOwner?: string;
1507
+ }
1508
+ /**
1509
+ * {@link matchSorted} that can also report the bindings an exclusive claim
1510
+ * kept out. Collecting them costs a spec match per barred binding, so it is
1511
+ * opt-in — the dispatcher asks only when it is recording.
1512
+ */
1513
+ declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
1514
+ collectBarred?: boolean;
1515
+ warn?: (message: string) => void;
1516
+ }): MatchOutcome;
1517
+
1567
1518
  /**
1568
- * The whole of an actions registry the dispatcher reads. It walks the list
1569
- * once per event to build its actionId lookup and never mutates it, so the
1570
- * routing side depends on this rather than on `ActionsRegistry`, whose
1571
- * register / mute / trigger surface is the React provider's.
1519
+ * How one input was resolved: every binding that matched it, what each filter
1520
+ * dropped, the order the rest were ranked in and why, and what the walk down
1521
+ * that order did. The dispatcher fills one per input in dev builds, and
1522
+ * `Dispatcher.explain` fills one on request without invoking anything.
1523
+ *
1524
+ * Plain data — no functions, no live objects — so it can be kept in a trace
1525
+ * buffer, serialized, or built by hand as a fixture.
1572
1526
  */
1573
- interface ActionSource {
1574
- list(): readonly Action[];
1527
+ interface DispatchRecord {
1528
+ kind: 'dispatch';
1529
+ ts: number;
1530
+ input: DispatchRecordInput;
1531
+ /** Every binding whose spec matched the input, in matcher order, including
1532
+ * those a filter then dropped. */
1533
+ matched: readonly RecordCandidate[];
1534
+ dropped: readonly DroppedCandidate[];
1535
+ /** The candidates that survived both filters, best first. */
1536
+ ranked: readonly RankedCandidate[];
1537
+ /** True when nothing was invoked: the record answers "what would happen". */
1538
+ predicted: boolean;
1539
+ /** Action id of the candidate that fired (or would), else `null`. */
1540
+ fired: string | null;
1541
+ outcome: 'handled' | 'unhandled';
1575
1542
  }
1576
- /** `action`'s hover cursor for an invocation given `params`: its `cursor`,
1577
- * asked when it is a function. */
1578
- declare function actionCursor(action: Pick<Action, 'cursor'> | undefined, params?: Record<string, unknown>): CursorSpec | undefined;
1543
+ /** The input a {@link DispatchRecord} describes, reduced to plain data. */
1544
+ interface DispatchRecordInput {
1545
+ /** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
1546
+ eventKind: string;
1547
+ /** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
1548
+ key?: string;
1549
+ modifiers: {
1550
+ alt: boolean;
1551
+ ctrl: boolean;
1552
+ meta: boolean;
1553
+ shift: boolean;
1554
+ };
1555
+ /** The view the input was routed to; `null` for the surface's own camera. */
1556
+ viewId: string | null;
1557
+ /** World point, for events that carry one. */
1558
+ world?: {
1559
+ x: number;
1560
+ y: number;
1561
+ };
1562
+ /** Active mode id, when the host supplies a rule context. */
1563
+ mode?: string;
1564
+ }
1565
+ /** One matched binding. */
1566
+ interface RecordCandidate {
1567
+ actionId: string;
1568
+ /** The binding's spec in route grammar — one per arg alternative. */
1569
+ routes: readonly string[];
1570
+ scope: BindingScope;
1571
+ /** The tool or contribution that declared the binding; `null` for an action's own binding. */
1572
+ ownerId: string | null;
1573
+ /** Whether the binding's `views` names the routed view. */
1574
+ namesView: boolean;
1575
+ /** `specificity(spec)`: target, modifiers, phase, exact. */
1576
+ specificity: readonly [number, number, number, number];
1577
+ /** The action's `eligible` rule as text, when it declares one. */
1578
+ eligible?: string;
1579
+ }
1580
+ /** A matched candidate a filter removed before ranking, and which filter. */
1581
+ type DroppedCandidate =
1582
+ /** An exclusive claim on the input barred bindings that do not read the
1583
+ * claim's affordance. */
1584
+ {
1585
+ candidate: RecordCandidate;
1586
+ filter: 'claim';
1587
+ owner?: string;
1588
+ }
1589
+ /** The action's `eligible` rule does not hold. */
1590
+ | {
1591
+ candidate: RecordCandidate;
1592
+ filter: 'ineligible';
1593
+ rule: string;
1594
+ };
1595
+ /** One field of the `specificity` tuple, in the order it is compared. */
1596
+ type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
1597
+ /** The ranking step that put a candidate below the one before it. */
1598
+ type PlacedBy = {
1599
+ step: 'first';
1600
+ }
1601
+ /** The one above names the routed view; this one does not. */
1602
+ | {
1603
+ step: 'view';
1604
+ }
1605
+ /** The one above sits in a higher scope tier. */
1606
+ | {
1607
+ step: 'tier';
1608
+ }
1609
+ /** Same tier; the one above is more specific in `part`. */
1610
+ | {
1611
+ step: 'specificity';
1612
+ part: SpecificityPart;
1613
+ }
1614
+ /** Tied; the one above is gated by an `eligible` rule that holds. */
1615
+ | {
1616
+ step: 'context';
1617
+ }
1618
+ /** Tied on everything; registration order. */
1619
+ | {
1620
+ step: 'order';
1621
+ };
1622
+ /** What the walk did with one ranked candidate. */
1623
+ type WalkStep = {
1624
+ kind: 'fired';
1625
+ }
1626
+ /** Predicted records only: this candidate would fire. Nothing was started,
1627
+ * so an ongoing action here may still bail at `start()` for real. */
1628
+ | {
1629
+ kind: 'would-fire';
1630
+ }
1631
+ /** `enabled()` returned this reason. */
1632
+ | {
1633
+ kind: 'declined';
1634
+ reason: string;
1635
+ }
1636
+ /** An ongoing action's `start()` returned an empty handle, or an
1637
+ * immediate one's `run()` returned `'pass'`. */
1638
+ | {
1639
+ kind: 'empty-handle';
1640
+ }
1641
+ /** An ongoing action bound to a press-time `pointerDown` spec; refused. */
1642
+ | {
1643
+ kind: 'misbound';
1644
+ }
1645
+ /** Its action was already tried higher up; each runs at most once. */
1646
+ | {
1647
+ kind: 'duplicate';
1648
+ } | {
1649
+ kind: 'no-such-action';
1650
+ }
1651
+ /** Below the winner; never asked. */
1652
+ | {
1653
+ kind: 'not-asked';
1654
+ }
1655
+ /** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
1656
+ | {
1657
+ kind: 'outranked';
1658
+ };
1659
+ /** A candidate that survived the filters: why it sits where it does, and
1660
+ * what the walk did with it. */
1661
+ interface RankedCandidate {
1662
+ candidate: RecordCandidate;
1663
+ placedBy: PlacedBy;
1664
+ walk: WalkStep;
1665
+ }
1666
+
1667
+ /** One entry per "mode change" — a kit state transition that isn't a
1668
+ * dispatched input event but explains why an input was (or wasn't) handled
1669
+ * later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
1670
+ interface ModeSwitchLogEntry {
1671
+ kind: 'mode';
1672
+ ts: number;
1673
+ /** Logical mode name, e.g. `'editAnchors.editingId'`. */
1674
+ mode: string;
1675
+ from: string | null;
1676
+ to: string | null;
1677
+ /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
1678
+ * `'selection-dropped target'`, etc. */
1679
+ detail?: string;
1680
+ }
1681
+ /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
1682
+ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
1683
+ /** A rolling buffer that records in a dev build, and in any build while something watches it. */
1684
+ interface TraceRing<T> {
1685
+ /** Whether a push now keeps its entry. A caller building an entry checks first. */
1686
+ recording(): boolean;
1687
+ push(entry: T): void;
1688
+ /** The entries pushed after `cursor`, oldest first, and the cursor to pass
1689
+ * next time. Start from 0. Entries the buffer has dropped are skipped. */
1690
+ since(cursor: number): {
1691
+ entries: T[];
1692
+ cursor: number;
1693
+ };
1694
+ /** Turn recording on until the returned release is called. */
1695
+ watch(): () => void;
1696
+ /** Hear each kept push. Returns the unsubscribe. */
1697
+ subscribe(listener: () => void): () => void;
1698
+ }
1699
+ /** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
1700
+ declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
1701
+ /** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
1702
+ * every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
1703
+ interface DispatchTrace extends TraceRing<TraceLogEntry> {
1704
+ recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1705
+ /** Offer the record for what a press at the pointer would do, built only
1706
+ * when {@link DispatchTrace.live} reads it. `null` withdraws it. */
1707
+ setLive(explain: (() => DispatchRecord) | null): void;
1708
+ live(): DispatchRecord | null;
1709
+ }
1710
+ /** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
1711
+ declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
1579
1712
 
1580
1713
  /** Which canvas hit a menu answers. Every field given must match. */
1581
1714
  interface MenuWhen {
@@ -1691,6 +1824,60 @@ declare function createMenuHost(src: MenuSources & {
1691
1824
  activeTool(): string | null;
1692
1825
  }): MenuHost;
1693
1826
 
1827
+ /**
1828
+ * Tool options: the settings a tool's user adjusts while it is active, such as
1829
+ * a brush's size and strength. A tool declares them as a prefs group whose
1830
+ * number leaves are params of the action it binds; the host holds their values
1831
+ * (`toolOptionValues` dep) and `<SceneCanvas>` tunes the action from them. A tool
1832
+ * whose options name a size or a strength also gets step actions for them,
1833
+ * bound to `[` `]` and `Shift+[` `Shift+]` while it is the active tool.
1834
+ */
1835
+
1836
+ /** How a step action moves one option: by a factor, or by an amount. */
1837
+ interface ToolOptionStep {
1838
+ /** The option's key in `prefs`. */
1839
+ option: string;
1840
+ /** Each step multiplies (or divides) the value by this. */
1841
+ times?: number;
1842
+ /** Each step adds (or takes away) this. Used when `times` is absent. */
1843
+ plus?: number;
1844
+ }
1845
+ /** What a tool declares about its options. See {@link ToolDef.options}. */
1846
+ interface ToolOptions {
1847
+ /** The action the options are params of: each leaf of `prefs` is the param
1848
+ * of the same key. */
1849
+ actionId: string;
1850
+ /** The options, as number leaves keyed by param, with their bounds and
1851
+ * defaults. A host draws them from this (a group drawn `as: 'bar'` makes an
1852
+ * options row) and keeps their values in its prefs. */
1853
+ prefs: PrefGroup;
1854
+ /** The option `[` and `]` step: how large the tool acts. */
1855
+ size?: ToolOptionStep;
1856
+ /** The option `Shift+[` and `Shift+]` step: how hard it acts. */
1857
+ strength?: ToolOptionStep;
1858
+ }
1859
+ /** Where a host keeps tool options' values: the `toolOptionValues` dep. */
1860
+ interface ToolOptionValues {
1861
+ /** `toolId`'s values, sparse: an option missing from it reads its default.
1862
+ * The same object until one of them changes. */
1863
+ get(toolId: string): Readonly<Record<string, number>>;
1864
+ set(toolId: string, option: string, value: number): void;
1865
+ subscribe(listener: () => void): () => void;
1866
+ }
1867
+ /** A {@link ToolOptionValues} held in memory, for a host that keeps no prefs. */
1868
+ declare function createToolOptionValues(): ToolOptionValues;
1869
+ /** Every option's value for `toolId`: what `values` holds, else its default. */
1870
+ declare function toolOptionParams(toolId: string, options: ToolOptions, values: ToolOptionValues | undefined): Record<string, number>;
1871
+ /** `value` moved one step up (`dir` 1) or down (-1), within the leaf's bounds
1872
+ * and rounded to its `step`. */
1873
+ declare function steppedOption(leaf: PrefNumber, value: number, step: ToolOptionStep, dir: 1 | -1): number;
1874
+ /** The step actions for `toolId`'s size and strength options, and the key
1875
+ * bindings that reach them. */
1876
+ declare function toolOptionSteps(toolId: string, toolLabel: string, options: ToolOptions): {
1877
+ actions: Action[];
1878
+ bindings: GestureBinding[];
1879
+ };
1880
+
1694
1881
  /**
1695
1882
  * The tree both input registries hang on. A provider is a root; every canvas
1696
1883
  * mounts a scope under the registry in scope; a yoke sits between the two. A
@@ -1791,40 +1978,6 @@ declare function ActiveToolContextProviderIfRoot({ children, }: {
1791
1978
  children: ReactNode;
1792
1979
  }): react.JSX.Element;
1793
1980
 
1794
- /**
1795
- * Tuning: what a host changes about an action it did not write — the params
1796
- * every invocation starts from, and bindings added beside the action's own —
1797
- * without restating the descriptor. `ActionsRegistry.tune` holds tunings and
1798
- * applies them to whatever descriptor is live for the id.
1799
- */
1800
-
1801
- /** An action's params, or a thunk read each time the action asks for them. */
1802
- type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
1803
- /** A binding added for an action: a gesture alone, or a gesture with the
1804
- * options it passes — the {@link BoundGesture} shape, its params typed. */
1805
- type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
1806
- spec: GestureSpec;
1807
- opts: Omit<BindingOpts, 'params'> & {
1808
- params?: ActionParams<P>;
1809
- };
1810
- };
1811
- /** What a host changes about one action. */
1812
- interface ActionTuning<P = Record<string, unknown>> {
1813
- /** Params every invocation starts from. What a binding or a `trigger` call
1814
- * passes is laid over them, so these fill in what it leaves out. A thunk is
1815
- * read on every call, and an ongoing action resolving its `opts.params`
1816
- * mid-gesture sees the value as it is then. */
1817
- params?: ActionParams<P>;
1818
- /** Bindings added after the action's own `defaultBinding`. */
1819
- bindings?: readonly TunedBinding<P>[];
1820
- /** Keys for some of the action's `modifierRoles`, by role id. A binding's
1821
- * own `opts.roles` is laid over these. */
1822
- roles?: RoleRebinds;
1823
- }
1824
- /** `action` with each tuning applied in turn, a later one's params over an
1825
- * earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
1826
- declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
1827
-
1828
1981
  /**
1829
1982
  * The entries an action contributes to a bar, menu or palette — one per
1830
1983
  * variant, or the action itself when it declares none.
@@ -3045,7 +3198,9 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
3045
3198
  * The bindings ride the hotkey tier, so leaving a mode outranks what the same
3046
3199
  * key does otherwise (Escape clearing the selection), and they yield while a
3047
3200
  * gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
3048
- * typed into an editable element never reach them.
3201
+ * typed into an editable element never reach them. A mode's `exit` and
3202
+ * `cancel`, pressed while a tool other than its `defaultTool` is active,
3203
+ * return to that tool instead (`returnToModeDefault`), so the next press leaves.
3049
3204
  */
3050
3205
  declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
3051
3206
 
@@ -3203,6 +3358,10 @@ interface Tool<TOverlay = KernelOverlay> extends Contribution<TOverlay> {
3203
3358
  * `opts.params.toolId` set so the invoker knows which tool to switch to).
3204
3359
  * A tool with none has no activation key. */
3205
3360
  keybinding?: ToolKeybinding;
3361
+ /** The settings its user adjusts while it is active; see `ToolDef.options`. */
3362
+ options?: ToolOptions;
3363
+ /** Chrome hidden while this is the active tool; see `ToolDef.hides`. */
3364
+ hides?: readonly string[];
3206
3365
  cursor?: CursorSpec | (() => CursorSpec);
3207
3366
  /** The tool became live: it took the active slot, or its `hotkey` went
3208
3367
  * down, while it held neither. A lifecycle moment has no pointer, hit or
@@ -3250,6 +3409,17 @@ interface ToolDef<TOverlay = KernelOverlay> {
3250
3409
  actions?: Action[];
3251
3410
  /** Menus this tool adds while it is active. */
3252
3411
  menus?: MenuDef[];
3412
+ /**
3413
+ * The settings its user adjusts while it is active, as params of the action
3414
+ * it binds: a brush's size and strength. `<SceneCanvas>` tunes that action
3415
+ * from the `toolOptionValues` dep, and `defineTool` adds step actions for the
3416
+ * size and strength, keyed to `[` `]` and `Shift+[` `Shift+]` while this is
3417
+ * the active tool. See {@link import('./toolOptions').ToolOptions}.
3418
+ */
3419
+ options?: ToolOptions;
3420
+ /** Chrome ids (`'selection.resize-handles'`) hidden, and not hit-tested,
3421
+ * while this is the active tool: chrome its own controls stand in for. */
3422
+ hides?: readonly string[];
3253
3423
  /** Hook name as exported from the kit barrel (e.g. `'useHandTool'`).
3254
3424
  * Set by built-in hooks for inspector / debugging. Consumer-authored
3255
3425
  * tools may set this to surface their hook name; omitted is fine.
@@ -3306,6 +3476,15 @@ interface ToolDef<TOverlay = KernelOverlay> {
3306
3476
  */
3307
3477
  declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
3308
3478
 
3479
+ /**
3480
+ * Make the active mode's default tool active when another tool holds the
3481
+ * slot, and say whether it did. A key that leaves a mode calls this first, so
3482
+ * one press returns to the mode's tool and the next leaves: the mode shortcuts'
3483
+ * `exit` and `cancel`, and core's `exitPathEdit`. Reads the `activeTool` and
3484
+ * `modeDefaultTool` deps, which the caller must require.
3485
+ */
3486
+ declare function returnToModeDefault(deps: ActionDeps | undefined): boolean;
3487
+
3309
3488
  /**
3310
3489
  * Tool sets: a named group of tool ids, the unit a consumer opts into whole.
3311
3490
  * A set names ids, never tools, because a tool is built by a hook that holds
@@ -3754,12 +3933,20 @@ declare function pickMenu(defs: readonly MenuDef[], hit: MenuHit): MenuDef | und
3754
3933
  interface DepSchema {
3755
3934
  /** Currently active tool id + hotkey-hold stack. */
3756
3935
  activeTool: ActiveToolContextValue;
3936
+ /** Where the host keeps its tools' option values (`ToolDef.options`).
3937
+ * Absent: every option is its default, and the step actions do nothing. */
3938
+ toolOptionValues?: ToolOptionValues;
3757
3939
  /** The menu host of the `ActionsProvider` in scope. */
3758
3940
  menuHost?: MenuHost;
3759
3941
  /** The canvas's dispatch trace, for a dep to record a mode switch into. */
3760
3942
  dispatchTrace?: DispatchTrace;
3761
3943
  /** The keymaps open on the canvas: what a leader opens and a hint reads. */
3762
3944
  keymaps?: KeymapStack;
3945
+ /** Every selection the host holds, captured so the last operation can put it back before it reruns. */
3946
+ selectionSnapshot?: SelectionSnapshotDep;
3947
+ /** The tool the active mode starts on (`ModeDefinition.defaultTool`), when the canvas mounts it;
3948
+ * else null. `<SceneCanvas>` sources it. */
3949
+ modeDefaultTool?: () => string | null;
3763
3950
  }
3764
3951
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
3765
3952
  type DepName = keyof DepSchema;
@@ -3807,4 +3994,4 @@ type KernelOverlay = OverlaySchema extends {
3807
3994
  overlay: infer O;
3808
3995
  } ? O : unknown;
3809
3996
 
3810
- export { type BindingOverrides as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionParams as E, type ActionPresentation as F, type ActionSettings as G, type ActionSettingsState as H, type ActionShortcut as I, type ActionSource as J, type KeymapDescription as K, type LongPressOptions as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionTuning as P, type ActionTunings as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ActionUnavailable as U, type ActionsProp as V, type WorldPoint as W, type AnyTool as X, type AssemblyInputs as Y, BROWSER_RESERVED_CHORDS as Z, type BindingOpts as _, type DepName as a, type ModeSwitchLogEntry as a$, type BindingScope as a0, type BindingSource as a1, type BodyPredicate as a2, type Bounds as a3, type BuildRuleCtxArgs as a4, COARSE_TARGET_SCALE as a5, type CaptureGestureOptions as a6, type CaptureKeyOptions as a7, type CaptureKind as a8, type CapturedSpec as a9, type HandleKind as aA, type HitShape as aB, type HotkeyTrigger as aC, INGEST_STRING_MIMES as aD, type ImmediateInvoker as aE, type ImmediateParams as aF, type InvocationCtx as aG, type Invoker as aH, KEYMAP_HOLD as aI, type KeyBinding as aJ, type KeyReach as aK, type KeymapDef as aL, type KeymapRow as aM, type KeymapStack as aN, type KeymapUnbound as aO, type KeymapVerdict as aP, type LongPressState as aQ, type LongPressStore as aR, type MatchOutcome as aS, type MatchResult as aT, type MenuAccess as aU, type MenuCustomAt as aV, type MenuHit as aW, type MenuOpenRequest as aX, type MenuSources as aY, type MenuWhen as aZ, type ModeShortcutHandlers as a_, type Carried as aa, type Carry as ab, type CarryCallbacks as ac, type CarryPayload as ad, type CarryPointer as ae, type CarryStage as af, type Condition as ag, type Conflict as ah, type ContributionChrome as ai, type ContributionDeps as aj, type ContributionRouting as ak, DEFAULT_ALLOWED_CAPABILITIES as al, DEFAULT_DEVICE_PROFILE as am, type DebugSink as an, type DepListing as ao, type DetectedDeviceFacts as ap, type DeviceProfile as aq, type DispatchRecord as ar, type DispatchRecordInput as as, type DispatcherContext as at, type DragSample as au, type DroppedCandidate as av, type Eligibility as aw, type EligibilityState as ax, type GestureBinding as ay, type GestureKitSettings as az, type DepSource as b, assembleEntries as b$, type ModifierRole as b0, type ModifierRoles as b1, type ModifierState as b2, NEVER as b3, type OngoingHandle as b4, type OngoingInvoker as b5, type OngoingOverlay as b6, type OpenKeymap as b7, type OpenMenu as b8, type OverlayRole as b9, type SelectionApi as bA, type Selector as bB, type SpecificityPart as bC, TOOL_OFFHAND_ID as bD, type ToolDef as bE, type ToolEntry as bF, type ToolKeybinding as bG, type ToolModifiers as bH, type ToolOffhandBindingSpec as bI, type ToolPresentation as bJ, type ToolScopes as bK, type ToolSet as bL, type ToolSlot as bM, type TraceLogEntry as bN, type TraceRing as bO, type TunedBinding as bP, type UiOngoingControl as bQ, type ViewportGestureKind as bR, type WalkStep as bS, actionBindings as bT, actionCursor as bU, actionItemTitle as bV, actionItems as bW, actionSettingsParams as bX, actionShortcuts as bY, actionsTagged as bZ, admitIngestMime as b_, type OverlaySchema as ba, type OwnedMenuDef as bb, PREDICATE_TARGET as bc, type PendingIngestItem as bd, type PendingLongPress as be, type PlacedBy as bf, type Point2 as bg, type PointerSession as bh, type PointerSessionCallbacks as bi, type PointerSessionCancelReason as bj, type PointerSessionOptions as bk, type RankedCandidate as bl, type RecordCandidate as bm, type RegistryEntry as bn, type ReservedChord as bo, type ResizeAnchor as bp, type ResolveAllOptions as bq, type ResolveOnlyResult as br, type ResolvedBindingOverrides as bs, type ResolvedCandidate as bt, type RoleKeys as bu, type RoleRebinds as bv, type Rule as bw, type ScopedBinding as bx, type ScratchKey as by, type ScratchStore as bz, type ActionDeps as c, matchSortedWithBarred as c$, bindingId as c0, browserReservedChord as c1, buildDepsFromRequires as c2, buildRouteRegistry as c3, buildRuleCtx as c4, buildToolOffhandBindings as c5, captureGesture as c6, captureKey as c7, clientToCanvas as c8, clientToCanvasRect as c9, isControlHandle as cA, isCornerWidget as cB, isDebugEnabled as cC, isEmpty as cD, isPathEditPart as cE, isResizeHandle as cF, isRotateHandle as cG, isSegment as cH, isSelectedBody as cI, isTool as cJ, isToolSet as cK, isUnselectedBody as cL, itemTypesFromDataTransfer as cM, itemsFromClipboardData as cN, itemsFromDataTransfer as cO, itemsFromFiles as cP, keySpecShortcut as cQ, keymapContribution as cR, keymapLeaderBindingId as cS, keymapLeaderId as cT, keymapShortcuts as cU, keysHeld as cV, liveScope as cW, makeToolOffhandAction as cX, matchActionItem as cY, matchBest as cZ, matchSorted as c_, createDepRegistry as ca, createDispatchTrace as cb, createDispatcher as cc, createKeymapStack as cd, createLongPressStore as ce, createMenuHost as cf, createTraceRing as cg, defineTool as ch, deleteScratch as ci, describeKeymap as cj, describeRule as ck, dlog as cl, dwarn as cm, evaluate as cn, evaluateActive as co, evaluateEnabled as cp, findConflicts as cq, getScratch as cr, heldKeySpec as cs, historyKey as ct, holdPreview as cu, holdPreviewId as cv, isAnchor as cw, isAnchorOrControl as cx, isBalanceGrip as cy, isBody as cz, type ActionsRegistry as d, matchesKeyBinding as d0, mergeContributions as d1, mergeMenuDefs as d2, modeShortcutSpec as d3, modeShortcuts as d4, modifierStateOf as d5, multiTouchGestureId as d6, offhandKeyFor as d7, openPointerSession as d8, overlaysOf as d9, validateActionDefaultBinding as dA, validateActionId as dB, withoutTools as dC, overriddenBindings as da, pendingKindOf as db, pickMenu as dc, pointerGestureId as dd, readActionSettings as de, reportRouteConflicts as df, resolveBindingOverrides as dg, resolveDeviceProfile as dh, resolveMenu as di, resolveParams as dj, resolveSchemaMenu as dk, roleReader as dl, routesForSpec as dm, ruleCanHoldIn as dn, rulesExclusive as dp, scopeBindings as dq, scratchKey as dr, setScratch as ds, startCarry as dt, taggedActionItems as du, targetConsultsAffordance as dv, toolIdsOf as dw, toolSet as dx, toolSetHas as dy, tuneAction as dz, ActionDisabledReason as e, type ActionVariant as f, type MenuDef as g, DepNode as h, type KernelOverlay as i, type Tool as j, ActiveToolContextProvider as k, ActiveToolContextProviderIfRoot as l, type ActiveToolContextProviderProps as m, type ActiveToolContextValue as n, useOptionalActiveToolContext as o, type Dispatcher as p, type AffordanceHit as q, type DispatchTrace as r, type DepSchema as s, ALWAYS as t, useActiveToolContext as u, type ActionDispatch as v, type ActionEnabledResult as w, type ActionEntry as x, type ActionItem as y, type ActionParamSchema as z };
3997
+ export { type AssemblyInputs as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionEntry as E, type ActionItem as F, type ActionParamSchema as G, type ActionParams as H, type ActionPresentation as I, type ActionSettings as J, type KeymapDescription as K, LastOperationKeeper as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionSettingsState as P, type ActionShortcut as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ActionSource as U, type ActionTuning as V, type WorldPoint as W, type ActionTunings as X, type ActionUnavailable as Y, type ActionsProp as Z, type AnyTool as _, type DepName as a, type MenuSources as a$, BROWSER_RESERVED_CHORDS as a0, type BindingOpts as a1, type BindingOverrides as a2, type BindingScope as a3, type BindingSource as a4, type BodyPredicate as a5, type Bounds as a6, type BuildRuleCtxArgs as a7, COARSE_TARGET_SCALE as a8, type CaptureGestureOptions as a9, type EligibilityState as aA, type GestureBinding as aB, type GestureKitSettings as aC, type HandleKind as aD, type HitShape as aE, type HotkeyTrigger as aF, INGEST_STRING_MIMES as aG, type ImmediateInvoker as aH, type ImmediateParams as aI, type InvocationCtx as aJ, type Invoker as aK, KEYMAP_HOLD as aL, type KeyBinding as aM, type KeyReach as aN, type KeymapDef as aO, type KeymapRow as aP, type KeymapStack as aQ, type KeymapUnbound as aR, type KeymapVerdict as aS, type LongPressState as aT, type LongPressStore as aU, type MatchOutcome as aV, type MatchResult as aW, type MenuAccess as aX, type MenuCustomAt as aY, type MenuHit as aZ, type MenuOpenRequest as a_, type CaptureKeyOptions as aa, type CaptureKind as ab, type CapturedSpec as ac, type Carried as ad, type Carry as ae, type CarryCallbacks as af, type CarryPayload as ag, type CarryPointer as ah, type CarryStage as ai, type Condition as aj, type Conflict as ak, type ContributionChrome as al, type ContributionDeps as am, type ContributionRouting as an, DEFAULT_ALLOWED_CAPABILITIES as ao, DEFAULT_DEVICE_PROFILE as ap, type DebugSink as aq, type DepListing as ar, type DetectedDeviceFacts as as, type DeviceProfile as at, type DispatchRecord as au, type DispatchRecordInput as av, type DispatcherContext as aw, type DragSample as ax, type DroppedCandidate as ay, type Eligibility as az, type DepSource as b, actionCursor as b$, type MenuWhen as b0, type ModeShortcutHandlers as b1, type ModeSwitchLogEntry as b2, type ModifierRole as b3, type ModifierRoles as b4, type ModifierState as b5, NEVER as b6, type OngoingHandle as b7, type OngoingInvoker as b8, type OngoingOverlay as b9, type ScopedBinding as bA, type ScratchKey as bB, type ScratchStore as bC, type SelectionApi as bD, type SelectionSnapshotDep as bE, type Selector as bF, type SpecificityPart as bG, TOOL_OFFHAND_ID as bH, type ToolDef as bI, type ToolEntry as bJ, type ToolKeybinding as bK, type ToolModifiers as bL, type ToolOffhandBindingSpec as bM, type ToolOptionStep as bN, type ToolOptionValues as bO, type ToolOptions as bP, type ToolPresentation as bQ, type ToolScopes as bR, type ToolSet as bS, type ToolSlot as bT, type TraceLogEntry as bU, type TraceRing as bV, type TunedBinding as bW, type UiOngoingControl as bX, type ViewportGestureKind as bY, type WalkStep as bZ, actionBindings as b_, type OpenKeymap as ba, type OpenMenu as bb, type OverlayRole as bc, type OverlaySchema as bd, type OwnedMenuDef as be, PREDICATE_TARGET as bf, type PendingIngestItem as bg, type PendingLongPress as bh, type PlacedBy as bi, type Point2 as bj, type PointerSession as bk, type PointerSessionCallbacks as bl, type PointerSessionCancelReason as bm, type PointerSessionOptions as bn, type RankedCandidate as bo, type RecordCandidate as bp, type RegistryEntry as bq, type ReservedChord as br, type ResizeAnchor as bs, type ResolveAllOptions as bt, type ResolveOnlyResult as bu, type ResolvedBindingOverrides as bv, type ResolvedCandidate as bw, type RoleKeys as bx, type RoleRebinds as by, type Rule as bz, type ActionDeps as c, keymapLeaderId as c$, actionItemTitle as c0, actionItems as c1, actionSettingsParams as c2, actionShortcuts as c3, actionsTagged as c4, admitIngestMime as c5, assembleEntries as c6, bindingId as c7, browserReservedChord as c8, buildDepsFromRequires as c9, heldKeySpec as cA, historyKey as cB, holdPreview as cC, holdPreviewId as cD, isAnchor as cE, isAnchorOrControl as cF, isBalanceGrip as cG, isBody as cH, isControlHandle as cI, isCornerWidget as cJ, isDebugEnabled as cK, isEmpty as cL, isPathEditPart as cM, isResizeHandle as cN, isRotateHandle as cO, isSegment as cP, isSelectedBody as cQ, isTool as cR, isToolSet as cS, isUnselectedBody as cT, itemTypesFromDataTransfer as cU, itemsFromClipboardData as cV, itemsFromDataTransfer as cW, itemsFromFiles as cX, keySpecShortcut as cY, keymapContribution as cZ, keymapLeaderBindingId as c_, buildRouteRegistry as ca, buildRuleCtx as cb, buildToolOffhandBindings as cc, captureGesture as cd, captureKey as ce, clientToCanvas as cf, clientToCanvasRect as cg, createDepRegistry as ch, createDispatchTrace as ci, createDispatcher as cj, createKeymapStack as ck, createLongPressStore as cl, createMenuHost as cm, createToolOptionValues as cn, createTraceRing as co, defineTool as cp, deleteScratch as cq, describeKeymap as cr, describeRule as cs, dlog as ct, dwarn as cu, evaluate as cv, evaluateActive as cw, evaluateEnabled as cx, findConflicts as cy, getScratch as cz, type ActionsRegistry as d, keymapShortcuts as d0, keysHeld as d1, liveScope as d2, makeToolOffhandAction as d3, matchActionItem as d4, matchBest as d5, matchSorted as d6, matchSortedWithBarred as d7, matchesKeyBinding as d8, mergeContributions as d9, scratchKey as dA, setScratch as dB, startCarry as dC, steppedOption as dD, taggedActionItems as dE, targetConsultsAffordance as dF, toolIdsOf as dG, toolOptionParams as dH, toolOptionSteps as dI, toolSet as dJ, toolSetHas as dK, tuneAction as dL, validateActionDefaultBinding as dM, validateActionId as dN, withoutTools as dO, mergeMenuDefs as da, modeShortcutSpec as db, modeShortcuts as dc, modifierStateOf as dd, multiTouchGestureId as de, offhandKeyFor as df, openPointerSession as dg, overlaysOf as dh, overriddenBindings as di, pendingKindOf as dj, pickMenu as dk, pointerGestureId as dl, readActionSettings as dm, reportRouteConflicts as dn, resolveBindingOverrides as dp, resolveDeviceProfile as dq, resolveMenu as dr, resolveParams as ds, resolveSchemaMenu as dt, returnToModeDefault as du, roleReader as dv, routesForSpec as dw, ruleCanHoldIn as dx, rulesExclusive as dy, scopeBindings as dz, ActionDisabledReason as e, type ActionVariant as f, type LastOperationStore as g, type LastOperation as h, type MenuDef as i, DepNode as j, type KernelOverlay as k, type Tool as l, ActiveToolContextProvider as m, ActiveToolContextProviderIfRoot as n, type ActiveToolContextProviderProps as o, type ActiveToolContextValue as p, useOptionalActiveToolContext as q, type Dispatcher as r, type AffordanceHit as s, type DispatchTrace as t, useActiveToolContext as u, type LongPressOptions as v, type DepSchema as w, ALWAYS as x, type ActionDispatch as y, type ActionEnabledResult as z };