@weasel-js/routing 1.11.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
+ import { ModifierKey, ClaimableGesture, GestureSpec, InputEvent, TargetSpec, IngestItem, BodyTarget, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
2
+ import { FramedBox, View, Point } from '@weasel-js/geom';
3
+ import { CursorSpec } from '@weasel-js/cursor';
1
4
  import { MenuEntry, Rect, MenuShortcut } from '@weasel-js/menu';
2
5
  import { PrefGroup } from '@weasel-js/prefs';
3
- import { FramedBox, View, Point } from '@weasel-js/geom';
4
6
  import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
5
- import { ModifierKey, ClaimableGesture, GestureSpec, IngestItem, BodyTarget, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
6
7
  import * as _weasel_js_icons from '@weasel-js/icons';
7
8
  import { IconRef } from '@weasel-js/icons';
8
- import { CursorSpec } from '@weasel-js/cursor';
9
9
  import * as react from 'react';
10
10
  import { ReactNode } from 'react';
11
11
  import { Emitter, QueryMatch } from '@weasel-js/registry';
@@ -169,489 +169,246 @@ type ResizeAnchor = {
169
169
  y: 'min' | 'max' | 'free';
170
170
  };
171
171
 
172
+ /** The chrome an in-flight gesture paints beside the scene. */
173
+
172
174
  /**
173
- * Live state read by rule evaluation. Built once per frame on the consuming
174
- * surface — chrome-caps, the affordance pipeline, the dispatcher's
175
- * eligibility filter — and discarded.
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.
176
177
  *
177
- * Adding a new field is additive: existing rules don't change, new
178
- * selector atoms can read it.
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.
179
181
  */
180
- interface RuleCtx {
181
- readonly focused: boolean;
182
- readonly selection: readonly NodeId[];
183
- readonly multiActive: boolean;
184
- readonly modifiers: ModifierState;
185
- readonly action: {
186
- readonly kind: string | null;
187
- readonly id: string | null;
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
+ /** Where a dragged item would land: a reorder's insertion bar. */
194
+ | 'insert'
195
+ /** Where a dragged item is over and cannot land. */
196
+ | 'refused' | (string & {});
197
+ /**
198
+ * What an in-flight gesture proposes to paint *beside* the scene, returned by
199
+ * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
200
+ * shape being dragged out, the line a slice would cut along.
201
+ *
202
+ * Every variant is world geometry and nothing else — no `DrawCommand`, no
203
+ * color, no coordinate space to choose — so a surface rendering the same
204
+ * gesture some other way can still read it. The painter owns every style
205
+ * decision.
206
+ */
207
+ type OngoingOverlay = {
208
+ kind: 'marquee';
209
+ start: {
210
+ x: number;
211
+ y: number;
188
212
  };
189
- readonly hover: NodeId | null;
213
+ current: {
214
+ x: number;
215
+ y: number;
216
+ };
217
+ shiftHeld: boolean;
218
+ } | {
219
+ kind: 'lasso';
220
+ vertices: ReadonlyArray<{
221
+ x: number;
222
+ y: number;
223
+ }>;
224
+ current: {
225
+ x: number;
226
+ y: number;
227
+ };
228
+ shiftHeld: boolean;
229
+ } | {
190
230
  /**
191
- * How far the viewport is zoomed in, as one number — `1` at native scale.
192
- *
193
- * A scalar rather than the `View` this used to carry, because `zoomAtLeast`
194
- * is the only selector that ever read one and a number is all it needs. A
195
- * host whose viewport is a camera has no `View` to hand over and would
196
- * otherwise have to invent one to use eligibility at all; it omits this
197
- * instead, and `zoomAtLeast` declines rather than matching on a fiction.
231
+ * An open run of world points the gesture is proposing — a cut line, a
232
+ * routed connector. `role` is the whole of what an action says about
233
+ * appearance.
198
234
  */
199
- readonly zoom?: number;
200
- /** Active mode id. `'normal'` when no non-default mode is engaged. */
201
- readonly mode: string;
202
- /** Capability tags allowed by the active mode (the union of
203
- * `ModeDefinition.allows` plus implicit tags). The `capability:`
204
- * selector reads this to determine whether a tag is permitted. */
205
- readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
206
- /** Whether the current selection may be resized. `<SceneCanvas>` folds
207
- * `selectTool.resize.resizable` over the selection (true only when every
208
- * selected node is resizable). Read by the `resizable:` selector to gate
209
- * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
210
- * resizable — back-compat: handles show unless a consumer opts a node out. */
211
- readonly selectionResizable?: boolean;
212
- /** Whether a path is currently in anchor-edit mode. Read by the
213
- * `editingAnchors:` selector, which gates the path-edit chrome.
214
- *
215
- * This is deliberately a fact about state, not about permission: the
216
- * anchor overlay and the anchor hit-test must agree, and the thing they
217
- * must agree on is "is there an edited path right now", which no
218
- * capability or mode id answers. A mode that allows `edits-anchors`
219
- * with nothing being edited should draw no anchors. Absent is treated
220
- * as false. */
221
- readonly editingAnchors?: boolean;
222
- /** Device facts — pointer coarseness, hover capability, density.
235
+ kind: 'polyline';
236
+ /** At least two points. Anything shorter is dropped in resolution. */
237
+ points: ReadonlyArray<{
238
+ x: number;
239
+ y: number;
240
+ }>;
241
+ /** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
242
+ role?: OverlayRole;
243
+ } | {
244
+ /**
245
+ * A round brush dragged through world points: the area it has swept so
246
+ * far, and its footprint at the last point. The radius is in screen
247
+ * pixels, so the footprint keeps its size at every zoom.
248
+ */
249
+ kind: 'brush';
250
+ /** The brush's path so far, at least one point; the last is where it is now. */
251
+ points: ReadonlyArray<{
252
+ x: number;
253
+ y: number;
254
+ }>;
255
+ radiusPx: number;
256
+ } | {
257
+ /**
258
+ * Live insert-drag preview. Pre-commit there is no scene node to ghost
259
+ * via `previewIds()`/`previewPose()`, so insert paints its preview
260
+ * through the overlay instead.
223
261
  *
224
- * Absent (legacy ctx builders) is treated as
225
- * {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
226
- * density 1. That is what the kit assumed before this field existed, so
227
- * an absent profile is behavior-preserving by construction. */
228
- readonly device?: DeviceProfile;
229
- /** The view the input being dispatched landed in — a view id, or `null`
230
- * for the surface's own camera. The dispatcher fills it; absent anywhere
231
- * else a rule is evaluated. */
232
- readonly viewId?: string | null;
233
- }
234
- /** The live state a `RuleCtx` is assembled from — the chrome context plus
235
- * what the active mode allows. */
236
- interface BuildRuleCtxArgs {
237
- focused: boolean;
238
- selection: readonly NodeId[];
239
- multiActive: boolean;
240
- modifiers: ModifierState;
241
- action: {
242
- kind: string | null;
243
- id: string | null;
262
+ * `bounds` is the AABB of the current drag (start/current normalized).
263
+ * `extras` is the per-kind extras the action already collected; the
264
+ * overlay renderer narrows on `shape` and casts the field shape it
265
+ * expects.
266
+ */
267
+ kind: 'insertPreview';
268
+ /** Which preview the dispatcher's overlay draws. The kit's own shape
269
+ * kinds are the ones it knows how to render; a consumer-defined kind
270
+ * is legal and simply gets no live preview. */
271
+ shape: string;
272
+ bounds: {
273
+ x: number;
274
+ y: number;
275
+ width: number;
276
+ height: number;
244
277
  };
245
- hover: NodeId | null;
246
- /** See {@link RuleCtx.zoom}. */
247
- zoom?: number;
248
- mode: string;
249
- allowedCapabilities: ReadonlySet<CapabilityTag>;
250
- /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
251
- selectionResizable?: boolean;
252
- /** Optional — omitted means "no path is being anchor-edited". */
253
- editingAnchors?: boolean;
254
- /** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
255
- device?: DeviceProfile;
278
+ extras: unknown;
279
+ /** World-space point to paint a small "anchor" dot at, marking the
280
+ * click point — for radial shapes (polygon/star), where no vertex
281
+ * sits on it, and for any shape in center mode. */
282
+ anchorPoint?: {
283
+ x: number;
284
+ y: number;
285
+ };
286
+ /** World affines the shape will also be drawn under once it lands, by
287
+ * the container it is drawn into: the preview is painted under each. */
288
+ copies?: readonly (readonly [number, number, number, number, number, number])[];
289
+ };
290
+ /** A handle's overlays as a list: empty when it has none. */
291
+ declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
292
+
293
+ /**
294
+ * Modifier roles: the modifiers an ongoing action reads while it runs, named
295
+ * by what they do rather than which key they are, so a binding or a tuning can
296
+ * move one to another key without forking the action.
297
+ */
298
+
299
+ /** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
300
+ * on a Mac and Ctrl elsewhere. An empty list never holds it. */
301
+ type RoleKeys = readonly ModifierKey[];
302
+ /** One modifier an action reads mid-gesture. */
303
+ interface ModifierRole {
304
+ label: string;
305
+ /** The keys that hold it unless a tuning or a binding says otherwise. */
306
+ keys: RoleKeys;
256
307
  }
308
+ /** An action's roles by id, e.g. `'handle.holdAngle'`. */
309
+ type ModifierRoles = Readonly<Record<string, ModifierRole>>;
310
+ /** Keys a tuning or a binding gives some of an action's roles. */
311
+ type RoleRebinds = Readonly<Record<string, RoleKeys>>;
312
+ /** Whether any of `keys` is held. */
313
+ declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
257
314
  /**
258
- * Capability set to assume when no mode registry is wired.
315
+ * A reader for an action's own roles. It answers from `ctx.roles`, which the
316
+ * dispatcher resolves on every event, and falls back to the role's default
317
+ * keys where nothing resolved them: an invoker called directly, or a context
318
+ * a cancel-all built with no event.
319
+ */
320
+ declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
321
+ roles?: Readonly<Record<string, boolean>>;
322
+ modifiers: ModifierState;
323
+ }, id: keyof R & string) => boolean;
324
+
325
+ /**
326
+ * Invoker — pluggable invocation strategy for an Action.
259
327
  *
260
- * "No modes configured" means *everything the default mode permits*, not
261
- * *nothing permitted*. The empty set is the wrong default: it makes every
262
- * `capability:` rule evaluate false, so a kit default written the
263
- * recommended way (capability rules outlive new modes; mode rules have to
264
- * be edited) would silently hide its chrome for every consumer that hasn't
265
- * opted into modality. `NORMAL.allows` plus the implicit tags is what a
266
- * mode-less canvas actually behaves like.
328
+ * An Action's `invoker` field describes how it's run. `immediate` invokers
329
+ * fire once (delete, align, undo). `ongoing` invokers open a phase machine
330
+ * driven by the dispatcher (move, resize, pinch-zoom).
267
331
  */
268
- declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
269
- /** Gather the current canvas and mode state into the context that action
270
- * eligibility and chrome-visibility rules are evaluated against. */
271
- declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
272
332
 
333
+ /** A 2D point in either world or screen coordinates; geom's `Point`. */
334
+ type Point2 = Point;
273
335
  /**
274
- * A selector is a conjunction of key/value tests. Multiple keys at the same
275
- * level AND together. Each key maps to a selector primitive in the evaluator.
336
+ * Which UI affordance was hit at pointerdown. Populated by the dispatcher when
337
+ * the `affordanceAt` thunk is provided to `useGestureDispatcher`.
338
+ *
339
+ * `kind` is a discriminator string:
340
+ * - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
341
+ * `'handle:bottom-right'` — corner resize handles.
342
+ * - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
343
+ * edge resize handles.
344
+ * - `'rotate-handle'` — the rotation affordance.
345
+ * - `'anchor:N'` — a path anchor at index N.
276
346
  */
277
- interface Selector {
278
- selection?: {
279
- is?: number;
280
- atLeast?: number;
281
- empty?: boolean;
282
- };
283
- mode?: string | {
284
- not: string;
285
- } | {
286
- in: readonly string[];
287
- };
288
- capability?: CapabilityTag | readonly CapabilityTag[] | {
289
- in: readonly CapabilityTag[];
290
- } | {
291
- not: CapabilityTag;
347
+ interface AffordanceHit {
348
+ /** Discriminator string, e.g. `'handle:bottom-right'`. */
349
+ kind: string;
350
+ /** Id of whatever produced this hit — a kit affordance's `id`, or the
351
+ * registered layer's id. */
352
+ owner?: string;
353
+ /** `'exclusive'` means no binding may act on this point unless its target
354
+ * consults the affordance. `'shared'` (the default) competes on scope and
355
+ * specificity as bindings always have. */
356
+ strength?: 'exclusive' | 'shared';
357
+ /** Which gestures an exclusive claim bars. Omitted bars all of them. */
358
+ claimedKinds?: readonly ClaimableGesture[];
359
+ /** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
360
+ fixedPoint?: {
361
+ x: number;
362
+ y: number;
292
363
  };
293
- gesturing?: boolean;
294
- actionIs?: string;
295
- modifierHeld?: keyof ModifierState;
296
- focused?: boolean;
297
- hovering?: boolean;
298
- hoveringSelected?: boolean;
299
- zoomAtLeast?: number;
300
- /** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
301
- * mode. Absent flag is treated as `false`. */
302
- editingAnchors?: boolean;
303
- /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
304
- * (resizable), so `{ resizable: true }` passes for legacy ctx builders
305
- * that don't compute it. */
306
- resizable?: boolean;
307
- /** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
308
- * (touch, most styluses). Absent device is treated as `false`. */
309
- coarsePointer?: boolean;
310
- /** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
311
- * device is treated as `true`. */
312
- canHover?: boolean;
313
- }
314
- /**
315
- * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
316
- * over `Selector` leaves. `when` is the escape hatch — its closure is
317
- * opaque to introspection and should be avoided when a declarative form
318
- * exists. Empty `all` is true; empty `any` is false.
319
- */
320
- type Rule = Selector | {
321
- all: readonly Rule[];
322
- } | {
323
- any: readonly Rule[];
324
- } | {
325
- not: Rule;
326
- } | {
327
- when: (ctx: RuleCtx) => boolean;
328
- };
329
- /** Constant rules. Kept here so they have a single source. */
330
- declare const ALWAYS: Rule;
331
- /** A rule that never passes. `any` of nothing is false. */
332
- declare const NEVER: Rule;
333
- /**
334
- * Render a {@link Rule} as a short human-readable string, for inspectors and
335
- * diagnostics that need to say WHICH rule decided something.
336
- *
337
- * ```
338
- * { capability: 'edits-page' } → capability:edits-page
339
- * { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
340
- * { not: { selection: { empty: true } } } → not(selection:empty=true)
341
- * { when: function hasPath() { … } } → when(hasPath)
342
- * ```
343
- *
344
- * Never throws and always terminates.
345
- */
346
- declare function describeRule(rule: Rule): string;
347
- /** Evaluate a rule against a context. Pure, and cheap enough to run per
348
- * frame for every piece of chrome. */
349
- declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
350
- /**
351
- * Composable eligibility predicate with a fluent surface. `cond(ctx)`
352
- * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
353
- * Conditions wrapping new trees.
354
- *
355
- * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
356
- * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
357
- * disjunction, name the subexpression or use the top-level `or(...)`.
358
- */
359
- interface Condition {
360
- (ctx: RuleCtx): boolean;
361
- readonly rule: Rule;
362
- /** `this && other` */
363
- and(other: Condition | Rule): Condition;
364
- /** `this || other` */
365
- or(other: Condition | Rule): Condition;
366
- /** `this && !other` */
367
- andNot(other: Condition | Rule): Condition;
368
- /** `this || !other` */
369
- orNot(other: Condition | Rule): Condition;
364
+ /** Which nodes this affordance belongs to. */
365
+ targetIds?: string[];
366
+ /** Set only when `kind` matches `'handle:*'`: which corner or edge stays
367
+ * fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
368
+ anchor?: ResizeAnchor;
369
+ /** Cursor to show while the pointer hovers this affordance (no gesture in
370
+ * flight). Unset falls through to action-cursor prediction, then to the
371
+ * active tool's cursor. */
372
+ cursor?: CursorSpec;
373
+ /**
374
+ * Free-form payload from whatever produced the hit, carried through to the
375
+ * matching action untouched. Kit affordances leave it unset; it exists for
376
+ * a registered layer's own chrome, where the hit-test already resolved
377
+ * *which* of its pieces was hit.
378
+ */
379
+ payload?: unknown;
370
380
  }
371
-
372
- /**
373
- * Whether two rules can never hold at once — no `RuleCtx` passes both.
374
- *
375
- * Conservative: `true` is a proof, `false` means "may overlap". It reasons
376
- * about the selector values themselves (two different `mode`s, an empty
377
- * selection against a non-empty one, `focused: true` against `false`) and
378
- * through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
379
- * everything. The route-conflict check uses it to stay quiet about bindings
380
- * that share a tuple but are never eligible together.
381
- */
382
- declare function rulesExclusive(a: Rule, b: Rule): boolean;
383
- /**
384
- * Whether `rule` can hold while `mode` is active. Only its `mode` and
385
- * `capability` tests are settled by the mode; every other test could go either
386
- * way, so `false` is a proof and `true` means "may hold".
387
- */
388
- declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
389
-
390
- /** The detected half of a profile — everything except the derived scale. */
391
- type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
392
- /**
393
- * Handle/hit multiplier applied on a coarse pointer.
394
- *
395
- * 8px handle → 14px; 24px rotation distance → 42px. Counting the
396
- * surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
397
- * minimum-touch-target band. One constant so it is tunable in one place.
398
- */
399
- declare const COARSE_TARGET_SCALE = 1.75;
400
- /**
401
- * Fold consumer overrides over detected facts and derive `targetScale`.
402
- *
403
- * `targetScale` is re-derived AFTER the merge, so an override of
404
- * `coarsePointer` alone scales the chrome as expected. An explicit
405
- * `targetScale` override wins over the derivation.
406
- */
407
- declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
408
381
  /**
409
- * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
410
- * absent-means value for `RuleCtx.device`.
382
+ * One accumulated point on a drag trail: world-space position plus whatever
383
+ * stylus state the originating `PointerEvent` carried.
411
384
  *
412
- * Derived from `resolveDeviceProfile` rather than hand-written, so it can
413
- * never drift from what the function would compute for the same facts. A
414
- * mouse-like device is the safe default: it is what the kit has always
415
- * assumed, so an absent profile changes nothing for existing consumers.
416
- */
417
- declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
418
-
419
- /**
420
- * Modifier roles: the modifiers an ongoing action reads while it runs, named
421
- * by what they do rather than which key they are, so a binding or a tuning can
422
- * move one to another key without forking the action.
385
+ * The stylus fields are absent for mouse/touch on browsers that don't report
386
+ * them, and for synthetic events.
423
387
  */
424
-
425
- /** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
426
- * on a Mac and Ctrl elsewhere. An empty list never holds it. */
427
- type RoleKeys = readonly ModifierKey[];
428
- /** One modifier an action reads mid-gesture. */
429
- interface ModifierRole {
430
- label: string;
431
- /** The keys that hold it unless a tuning or a binding says otherwise. */
432
- keys: RoleKeys;
388
+ interface DragSample extends Point2 {
389
+ /** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
390
+ pressure?: number;
391
+ /** Degrees, ±90. Zero for mouse/touch. */
392
+ tiltX?: number;
393
+ /** Degrees, ±90. Zero for mouse/touch. */
394
+ tiltY?: number;
433
395
  }
434
- /** An action's roles by id, e.g. `'handle.holdAngle'`. */
435
- type ModifierRoles = Readonly<Record<string, ModifierRole>>;
436
- /** Keys a tuning or a binding gives some of an action's roles. */
437
- type RoleRebinds = Readonly<Record<string, RoleKeys>>;
438
- /** Whether any of `keys` is held. */
439
- declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
440
- /**
441
- * A reader for an action's own roles. It answers from `ctx.roles`, which the
442
- * dispatcher resolves on every event, and falls back to the role's default
443
- * keys where nothing resolved them: an invoker called directly, or a context
444
- * a cancel-all built with no event.
445
- */
446
- declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
447
- roles?: Readonly<Record<string, boolean>>;
448
- modifiers: ModifierState;
449
- }, id: keyof R & string) => boolean;
450
-
451
- /** The chrome an in-flight gesture paints beside the scene. */
452
-
453
- /**
454
- * What a `polyline` overlay is, in a word — the narrowest thing an action can
455
- * say about how its run should look without naming a paint.
456
- *
457
- * The named roles are the kit's; a painter maps each to a style and falls back
458
- * to plain chrome for one it does not know, so a package outside core can coin
459
- * its own and still be drawn.
460
- */
461
- type OverlayRole =
462
- /** Generic in-flight chrome — the marquee/lasso paint. */
463
- 'chrome'
464
- /** A cut the gesture would make. */
465
- | 'cut'
466
- /** An edge the gesture would author between two points. */
467
- | 'connector'
468
- /** The stretch of a path a soft-selection drag reaches. */
469
- | 'falloff'
470
- /** What a lock holds a dragged point to — a line, a circle. */
471
- | 'lock' | (string & {});
472
- /**
473
- * What an in-flight gesture proposes to paint *beside* the scene, returned by
474
- * `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
475
- * shape being dragged out, the line a slice would cut along.
476
- *
477
- * Every variant is world geometry and nothing else — no `DrawCommand`, no
478
- * color, no coordinate space to choose — so a surface rendering the same
479
- * gesture some other way can still read it. The painter owns every style
480
- * decision.
481
- */
482
- type OngoingOverlay = {
483
- kind: 'marquee';
484
- start: {
485
- x: number;
486
- y: number;
487
- };
488
- current: {
489
- x: number;
490
- y: number;
491
- };
492
- shiftHeld: boolean;
493
- } | {
494
- kind: 'lasso';
495
- vertices: ReadonlyArray<{
496
- x: number;
497
- y: number;
498
- }>;
499
- current: {
500
- x: number;
501
- y: number;
502
- };
503
- shiftHeld: boolean;
504
- } | {
505
- /**
506
- * An open run of world points the gesture is proposing — a cut line, a
507
- * routed connector. `role` is the whole of what an action says about
508
- * appearance.
509
- */
510
- kind: 'polyline';
511
- /** At least two points. Anything shorter is dropped in resolution. */
512
- points: ReadonlyArray<{
513
- x: number;
514
- y: number;
515
- }>;
516
- /** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
517
- role?: OverlayRole;
518
- } | {
519
- /**
520
- * A round brush dragged through world points: the area it has swept so
521
- * far, and its footprint at the last point. The radius is in screen
522
- * pixels, so the footprint keeps its size at every zoom.
523
- */
524
- kind: 'brush';
525
- /** The brush's path so far, at least one point; the last is where it is now. */
526
- points: ReadonlyArray<{
527
- x: number;
528
- y: number;
529
- }>;
530
- radiusPx: number;
531
- } | {
532
- /**
533
- * Live insert-drag preview. Pre-commit there is no scene node to ghost
534
- * via `previewIds()`/`previewPose()`, so insert paints its preview
535
- * through the overlay instead.
536
- *
537
- * `bounds` is the AABB of the current drag (start/current normalized).
538
- * `extras` is the per-kind extras the action already collected; the
539
- * overlay renderer narrows on `shape` and casts the field shape it
540
- * expects.
541
- */
542
- kind: 'insertPreview';
543
- /** Which preview the dispatcher's overlay draws. The kit's own shape
544
- * kinds are the ones it knows how to render; a consumer-defined kind
545
- * is legal and simply gets no live preview. */
546
- shape: string;
547
- bounds: {
548
- x: number;
549
- y: number;
550
- width: number;
551
- height: number;
552
- };
553
- extras: unknown;
554
- /** World-space point to paint a small "anchor" dot at, marking the
555
- * click point — for radial shapes (polygon/star), where no vertex
556
- * sits on it, and for any shape in center mode. */
557
- anchorPoint?: {
558
- x: number;
559
- y: number;
560
- };
561
- /** World affines the shape will also be drawn under once it lands, by
562
- * the container it is drawn into: the preview is painted under each. */
563
- copies?: readonly (readonly [number, number, number, number, number, number])[];
564
- };
565
- /** A handle's overlays as a list: empty when it has none. */
566
- declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
567
-
568
- /**
569
- * Invoker — pluggable invocation strategy for an Action.
570
- *
571
- * An Action's `invoker` field describes how it's run. `immediate` invokers
572
- * fire once (delete, align, undo). `ongoing` invokers open a phase machine
573
- * driven by the dispatcher (move, resize, pinch-zoom).
574
- */
575
-
576
- /** A 2D point in either world or screen coordinates; geom's `Point`. */
577
- type Point2 = Point;
578
- /**
579
- * Which UI affordance was hit at pointerdown. Populated by the dispatcher when
580
- * the `affordanceAt` thunk is provided to `useGestureDispatcher`.
581
- *
582
- * `kind` is a discriminator string:
583
- * - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
584
- * `'handle:bottom-right'` — corner resize handles.
585
- * - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
586
- * edge resize handles.
587
- * - `'rotate-handle'` — the rotation affordance.
588
- * - `'anchor:N'` — a path anchor at index N.
589
- */
590
- interface AffordanceHit {
591
- /** Discriminator string, e.g. `'handle:bottom-right'`. */
592
- kind: string;
593
- /** Id of whatever produced this hit — a kit affordance's `id`, or the
594
- * registered layer's id. */
595
- owner?: string;
596
- /** `'exclusive'` means no binding may act on this point unless its target
597
- * consults the affordance. `'shared'` (the default) competes on scope and
598
- * specificity as bindings always have. */
599
- strength?: 'exclusive' | 'shared';
600
- /** Which gestures an exclusive claim bars. Omitted bars all of them. */
601
- claimedKinds?: readonly ClaimableGesture[];
602
- /** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
603
- fixedPoint?: {
604
- x: number;
605
- y: number;
606
- };
607
- /** Which nodes this affordance belongs to. */
608
- targetIds?: string[];
609
- /** Set only when `kind` matches `'handle:*'`: which corner or edge stays
610
- * fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
611
- anchor?: ResizeAnchor;
612
- /** Cursor to show while the pointer hovers this affordance (no gesture in
613
- * flight). Unset falls through to action-cursor prediction, then to the
614
- * active tool's cursor. */
615
- cursor?: CursorSpec;
616
- /**
617
- * Free-form payload from whatever produced the hit, carried through to the
618
- * matching action untouched. Kit affordances leave it unset; it exists for
619
- * a registered layer's own chrome, where the hit-test already resolved
620
- * *which* of its pieces was hit.
621
- */
622
- payload?: unknown;
623
- }
624
- /**
625
- * One accumulated point on a drag trail: world-space position plus whatever
626
- * stylus state the originating `PointerEvent` carried.
627
- *
628
- * The stylus fields are absent for mouse/touch on browsers that don't report
629
- * them, and for synthetic events.
630
- */
631
- interface DragSample extends Point2 {
632
- /** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
633
- pressure?: number;
634
- /** Degrees, ±90. Zero for mouse/touch. */
635
- tiltX?: number;
636
- /** Degrees, ±90. Zero for mouse/touch. */
637
- tiltY?: number;
638
- }
639
- /** Per-invocation runtime context the dispatcher hands to an Invoker.
640
- * Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
641
- * populated only for matching gesture kinds. */
642
- interface InvocationCtx {
643
- world: Point2;
644
- /**
645
- * The pointer in client/screen pixels, for an action that has to place DOM
646
- * at the pointer. Absent on every invocation whose event carries no client
647
- * coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
648
- * read it with a fallback rather than assuming a point.
649
- *
650
- * A view-mutating drag wants `drag.screenDelta`, not the difference of two
651
- * of these: world deltas are self-referential mid-pan, and so is any delta
652
- * taken across a view change.
653
- */
654
- screen?: Point2;
396
+ /** Per-invocation runtime context the dispatcher hands to an Invoker.
397
+ * Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
398
+ * populated only for matching gesture kinds. */
399
+ interface InvocationCtx {
400
+ world: Point2;
401
+ /**
402
+ * The pointer in client/screen pixels, for an action that has to place DOM
403
+ * at the pointer. Absent on every invocation whose event carries no client
404
+ * coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
405
+ * read it with a fallback rather than assuming a point.
406
+ *
407
+ * A view-mutating drag wants `drag.screenDelta`, not the difference of two
408
+ * of these: world deltas are self-referential mid-pan, and so is any delta
409
+ * taken across a view change.
410
+ */
411
+ screen?: Point2;
655
412
  modifiers: ModifierState;
656
413
  deps: ActionDeps;
657
414
  drag?: {
@@ -777,6 +534,12 @@ interface BindingOpts {
777
534
  * binding; this tells the route-conflict check the order is the design.
778
535
  */
779
536
  yields?: boolean;
537
+ /**
538
+ * For a key binding: live while the pointer is over the surface, whichever
539
+ * canvas holds the keys, and only then. For a key that means "here" — a
540
+ * magnifier's peek — on a page with more than one surface that hears it.
541
+ */
542
+ over?: boolean;
780
543
  /**
781
544
  * Keys for some of the action's `modifierRoles` on this binding, by role
782
545
  * id, over the action's defaults and any tuning's.
@@ -822,8 +585,11 @@ interface OngoingHandle {
822
585
  *
823
586
  * `previewIds()` enumerates the displaced node ids; `previewPose(id)`
824
587
  * returns the interim pose for one of them (shape opaque — the canvas
825
- * casts to its `TPose` parameter). Returning `null`, or omitting the
826
- * method, means "no preview this gesture".
588
+ * casts to its `TPose` parameter). `previewIds` returning `null`, or
589
+ * omitted, means "no preview this gesture". An id with no `previewPose`
590
+ * is shown at its effective pose: an action that publishes its poses to
591
+ * the scene's pose overrides, as move, resize and rotate do, leaves
592
+ * `previewPose` out rather than answering the same pose twice.
827
593
  */
828
594
  previewIds?(): Iterable<string> | null;
829
595
  previewPose?(id: string): unknown | null;
@@ -873,63 +639,615 @@ interface ImmediateInvoker {
873
639
  * is undefined; descriptors should default to a sensible variant. */
874
640
  run(deps: ActionDeps, params?: Record<string, unknown>): void;
875
641
  }
876
- /** Phase-machine invocation. `start` opens the phase and returns the handle
877
- * the dispatcher pumps. */
878
- interface OngoingInvoker {
879
- timing: 'ongoing';
880
- start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
642
+ /** Phase-machine invocation. `start` opens the phase and returns the handle
643
+ * the dispatcher pumps. */
644
+ interface OngoingInvoker {
645
+ timing: 'ongoing';
646
+ start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
647
+ }
648
+ /** Pluggable invocation strategy for an Action. */
649
+ type Invoker = ImmediateInvoker | OngoingInvoker;
650
+
651
+ /**
652
+ * GestureBinding — connects a GestureSpec to an Action id (with per-binding
653
+ * options). Tools own arrays of these on their `bindings` field; ambient
654
+ * gesture-bindings are registered globally.
655
+ */
656
+
657
+ /** An interaction: a gesture spec composed with the id of the action it
658
+ * invokes. Tools declare arrays of these; the dispatcher matches an incoming
659
+ * input event against them and runs the winner's action. */
660
+ interface GestureBinding {
661
+ spec: GestureSpec;
662
+ actionId: string;
663
+ opts?: BindingOpts;
664
+ }
665
+ /**
666
+ * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
667
+ * (no per-binding opts) or an object form that pairs a spec with
668
+ * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
669
+ * Use the object form when two bindings for the same action differ only in
670
+ * a runtime parameter — the dispatcher extracts `opts.params` and passes
671
+ * them to `ImmediateInvoker.run` as its second argument.
672
+ */
673
+ type BoundGesture = GestureSpec | {
674
+ spec: GestureSpec;
675
+ opts: BindingOpts;
676
+ };
677
+ /**
678
+ * The whole of an action `actionBindings` reads. Routing needs this much and
679
+ * none of `Action`'s palette fields, so it is the narrowest type the
680
+ * dispatcher's binding walk can be written against.
681
+ */
682
+ interface BindingSource {
683
+ id: string;
684
+ /** The gesture-spec form of the binding, read by the gesture dispatcher.
685
+ * May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
686
+ * or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
687
+ * `{ spec, opts }` — use the object form for parametric actions where two
688
+ * bindings for the same action differ only by `opts.params` (e.g. `flip`
689
+ * with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
690
+ * passes them to `ImmediateInvoker.run` as its second argument. */
691
+ defaultBinding?: GestureSpec | BoundGesture[];
692
+ }
693
+ /**
694
+ * Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
695
+ * `GestureSpec` has `kind` at top level; the object form has `spec`.
696
+ */
697
+ declare function actionBindings(action: BindingSource): GestureBinding[];
698
+
699
+ /**
700
+ * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
701
+ * the actions-layer binding-scope / matchBest logic on top.
702
+ */
703
+
704
+ /** Where a binding came from, which is also its priority: a held hotkey beats
705
+ * the active tool, which beats bindings that are always in scope. */
706
+ type BindingScope = 'ambient' | 'active' | 'hotkey';
707
+ /** A binding paired with where it came from, ready to be matched against an
708
+ * event. */
709
+ interface ScopedBinding {
710
+ binding: GestureBinding;
711
+ scope: BindingScope;
712
+ /** Id of the tool or contribution that declared this binding —
713
+ * `'&'`-channel phase atoms resolve to it. `null` for a registered
714
+ * action's own binding. */
715
+ ownerId: string | null;
716
+ }
717
+ /** The binding that won a match. */
718
+ interface MatchResult {
719
+ binding: GestureBinding;
720
+ scope: BindingScope;
721
+ /** Id of the entry that declared the binding — propagated from
722
+ * `ScopedBinding` so the dispatcher can record it as the handle owner. */
723
+ ownerId: string | null;
724
+ }
725
+ /**
726
+ * True when a spec's target actually consults the affordance hit rather than
727
+ * only the body classification. `kindOf` predicates are handed the hit;
728
+ * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
729
+ * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
730
+ * `bodyTarget` / `bodyKind` and never see it.
731
+ *
732
+ * A predicate declares which it is with `readsAffordance`; one that declares
733
+ * nothing, which only untyped code can write, counts as not reading the hit.
734
+ */
735
+ declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
736
+ /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
737
+ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
738
+ /**
739
+ * Find every binding that matches the event, sorted by precedence
740
+ * (best → worst). Ordering rules:
741
+ *
742
+ * - Scopes by priority: hotkey > active > ambient.
743
+ * - Within a scope, more-specific bindings beat less-specific (see
744
+ * `specificity()`).
745
+ * - Among same-specificity bindings, first-declared wins.
746
+ */
747
+ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
748
+ /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
749
+ interface MatchOutcome {
750
+ matches: MatchResult[];
751
+ /** Bindings whose spec matched the event but which the claim barred, in
752
+ * the same order `matches` uses. Filled only when `collectBarred` is set. */
753
+ barred: MatchResult[];
754
+ /** Owner of the exclusive claim, when one applied. */
755
+ claimOwner?: string;
756
+ }
757
+ /**
758
+ * {@link matchSorted} that can also report the bindings an exclusive claim
759
+ * kept out. Collecting them costs a spec match per barred binding, so it is
760
+ * opt-in — the dispatcher asks only when it is recording.
761
+ */
762
+ declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
763
+ collectBarred?: boolean;
764
+ warn?: (message: string) => void;
765
+ }): MatchOutcome;
766
+
767
+ /**
768
+ * How one input was resolved: every binding that matched it, what each filter
769
+ * dropped, the order the rest were ranked in and why, and what the walk down
770
+ * that order did. The dispatcher fills one per input in dev builds, and
771
+ * `Dispatcher.explain` fills one on request without invoking anything.
772
+ *
773
+ * Plain data — no functions, no live objects — so it can be kept in a trace
774
+ * buffer, serialized, or built by hand as a fixture.
775
+ */
776
+ interface DispatchRecord {
777
+ kind: 'dispatch';
778
+ ts: number;
779
+ input: DispatchRecordInput;
780
+ /** Every binding whose spec matched the input, in matcher order, including
781
+ * those a filter then dropped. */
782
+ matched: readonly RecordCandidate[];
783
+ dropped: readonly DroppedCandidate[];
784
+ /** The candidates that survived both filters, best first. */
785
+ ranked: readonly RankedCandidate[];
786
+ /** True when nothing was invoked: the record answers "what would happen". */
787
+ predicted: boolean;
788
+ /** Action id of the candidate that fired (or would), else `null`. */
789
+ fired: string | null;
790
+ outcome: 'handled' | 'unhandled';
791
+ }
792
+ /** The input a {@link DispatchRecord} describes, reduced to plain data. */
793
+ interface DispatchRecordInput {
794
+ /** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
795
+ eventKind: string;
796
+ /** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
797
+ key?: string;
798
+ modifiers: {
799
+ alt: boolean;
800
+ ctrl: boolean;
801
+ meta: boolean;
802
+ shift: boolean;
803
+ };
804
+ /** The view the input was routed to; `null` for the surface's own camera. */
805
+ viewId: string | null;
806
+ /** World point, for events that carry one. */
807
+ world?: {
808
+ x: number;
809
+ y: number;
810
+ };
811
+ /** Active mode id, when the host supplies a rule context. */
812
+ mode?: string;
813
+ }
814
+ /** One matched binding. */
815
+ interface RecordCandidate {
816
+ actionId: string;
817
+ /** The binding's spec in route grammar — one per arg alternative. */
818
+ routes: readonly string[];
819
+ scope: BindingScope;
820
+ /** The tool or contribution that declared the binding; `null` for an action's own binding. */
821
+ ownerId: string | null;
822
+ /** Whether the binding's `views` names the routed view. */
823
+ namesView: boolean;
824
+ /** `specificity(spec)`: target, modifiers, phase, exact. */
825
+ specificity: readonly [number, number, number, number];
826
+ /** The action's `eligible` rule as text, when it declares one. */
827
+ eligible?: string;
828
+ }
829
+ /** A matched candidate a filter removed before ranking, and which filter. */
830
+ type DroppedCandidate =
831
+ /** An exclusive claim on the input barred bindings that do not read the
832
+ * claim's affordance. */
833
+ {
834
+ candidate: RecordCandidate;
835
+ filter: 'claim';
836
+ owner?: string;
837
+ }
838
+ /** The action's `eligible` rule does not hold. */
839
+ | {
840
+ candidate: RecordCandidate;
841
+ filter: 'ineligible';
842
+ rule: string;
843
+ };
844
+ /** One field of the `specificity` tuple, in the order it is compared. */
845
+ type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
846
+ /** The ranking step that put a candidate below the one before it. */
847
+ type PlacedBy = {
848
+ step: 'first';
849
+ }
850
+ /** The one above names the routed view; this one does not. */
851
+ | {
852
+ step: 'view';
853
+ }
854
+ /** The one above sits in a higher scope tier. */
855
+ | {
856
+ step: 'tier';
857
+ }
858
+ /** Same tier; the one above is more specific in `part`. */
859
+ | {
860
+ step: 'specificity';
861
+ part: SpecificityPart;
862
+ }
863
+ /** Tied; the one above is gated by an `eligible` rule that holds. */
864
+ | {
865
+ step: 'context';
866
+ }
867
+ /** Tied on everything; registration order. */
868
+ | {
869
+ step: 'order';
870
+ };
871
+ /** What the walk did with one ranked candidate. */
872
+ type WalkStep = {
873
+ kind: 'fired';
874
+ }
875
+ /** Predicted records only: this candidate would fire. Nothing was started,
876
+ * so an ongoing action here may still bail at `start()` for real. */
877
+ | {
878
+ kind: 'would-fire';
879
+ }
880
+ /** `enabled()` returned this reason. */
881
+ | {
882
+ kind: 'declined';
883
+ reason: string;
884
+ }
885
+ /** An ongoing action's `start()` returned an empty handle. */
886
+ | {
887
+ kind: 'empty-handle';
888
+ }
889
+ /** An ongoing action bound to a press-time `pointerDown` spec; refused. */
890
+ | {
891
+ kind: 'misbound';
892
+ }
893
+ /** Its action was already tried higher up; each runs at most once. */
894
+ | {
895
+ kind: 'duplicate';
896
+ } | {
897
+ kind: 'no-such-action';
898
+ }
899
+ /** Below the winner; never asked. */
900
+ | {
901
+ kind: 'not-asked';
902
+ }
903
+ /** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
904
+ | {
905
+ kind: 'outranked';
906
+ };
907
+ /** A candidate that survived the filters: why it sits where it does, and
908
+ * what the walk did with it. */
909
+ interface RankedCandidate {
910
+ candidate: RecordCandidate;
911
+ placedBy: PlacedBy;
912
+ walk: WalkStep;
913
+ }
914
+
915
+ /** One entry per "mode change" — a kit state transition that isn't a
916
+ * dispatched input event but explains why an input was (or wasn't) handled
917
+ * later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
918
+ interface ModeSwitchLogEntry {
919
+ kind: 'mode';
920
+ ts: number;
921
+ /** Logical mode name, e.g. `'editAnchors.editingId'`. */
922
+ mode: string;
923
+ from: string | null;
924
+ to: string | null;
925
+ /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
926
+ * `'selection-dropped target'`, etc. */
927
+ detail?: string;
928
+ }
929
+ /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
930
+ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
931
+ /** A rolling buffer that records in a dev build, and in any build while something watches it. */
932
+ interface TraceRing<T> {
933
+ /** Whether a push now keeps its entry. A caller building an entry checks first. */
934
+ recording(): boolean;
935
+ push(entry: T): void;
936
+ /** The entries pushed after `cursor`, oldest first, and the cursor to pass
937
+ * next time. Start from 0. Entries the buffer has dropped are skipped. */
938
+ since(cursor: number): {
939
+ entries: T[];
940
+ cursor: number;
941
+ };
942
+ /** Turn recording on until the returned release is called. */
943
+ watch(): () => void;
944
+ /** Hear each kept push. Returns the unsubscribe. */
945
+ subscribe(listener: () => void): () => void;
946
+ }
947
+ /** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
948
+ declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
949
+ /** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
950
+ * every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
951
+ interface DispatchTrace extends TraceRing<TraceLogEntry> {
952
+ recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
953
+ /** Offer the record for what a press at the pointer would do, built only
954
+ * when {@link DispatchTrace.live} reads it. `null` withdraws it. */
955
+ setLive(explain: (() => DispatchRecord) | null): void;
956
+ live(): DispatchRecord | null;
957
+ }
958
+ /** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
959
+ declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
960
+
961
+ /**
962
+ * Live state read by rule evaluation. Built once per frame on the consuming
963
+ * surface — chrome-caps, the affordance pipeline, the dispatcher's
964
+ * eligibility filter — and discarded.
965
+ *
966
+ * Adding a new field is additive: existing rules don't change, new
967
+ * selector atoms can read it.
968
+ */
969
+ interface RuleCtx {
970
+ readonly focused: boolean;
971
+ readonly selection: readonly NodeId[];
972
+ readonly multiActive: boolean;
973
+ readonly modifiers: ModifierState;
974
+ readonly action: {
975
+ readonly kind: string | null;
976
+ readonly id: string | null;
977
+ };
978
+ readonly hover: NodeId | null;
979
+ /**
980
+ * How far the viewport is zoomed in, as one number — `1` at native scale.
981
+ *
982
+ * A scalar rather than the `View` this used to carry, because `zoomAtLeast`
983
+ * is the only selector that ever read one and a number is all it needs. A
984
+ * host whose viewport is a camera has no `View` to hand over and would
985
+ * otherwise have to invent one to use eligibility at all; it omits this
986
+ * instead, and `zoomAtLeast` declines rather than matching on a fiction.
987
+ */
988
+ readonly zoom?: number;
989
+ /** Active mode id. `'normal'` when no non-default mode is engaged. */
990
+ readonly mode: string;
991
+ /** Capability tags allowed by the active mode (the union of
992
+ * `ModeDefinition.allows` plus implicit tags). The `capability:`
993
+ * selector reads this to determine whether a tag is permitted. */
994
+ readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
995
+ /** Whether the current selection may be resized. `<SceneCanvas>` folds
996
+ * `selectTool.resize.resizable` over the selection (true only when every
997
+ * selected node is resizable). Read by the `resizable:` selector to gate
998
+ * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
999
+ * resizable — back-compat: handles show unless a consumer opts a node out. */
1000
+ readonly selectionResizable?: boolean;
1001
+ /** Whether a path is currently in anchor-edit mode. Read by the
1002
+ * `editingAnchors:` selector, which gates the path-edit chrome.
1003
+ *
1004
+ * This is deliberately a fact about state, not about permission: the
1005
+ * anchor overlay and the anchor hit-test must agree, and the thing they
1006
+ * must agree on is "is there an edited path right now", which no
1007
+ * capability or mode id answers. A mode that allows `edits-anchors`
1008
+ * with nothing being edited should draw no anchors. Absent is treated
1009
+ * as false. */
1010
+ readonly editingAnchors?: boolean;
1011
+ /** Device facts — pointer coarseness, hover capability, density.
1012
+ *
1013
+ * Absent (legacy ctx builders) is treated as
1014
+ * {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
1015
+ * density 1. That is what the kit assumed before this field existed, so
1016
+ * an absent profile is behavior-preserving by construction. */
1017
+ readonly device?: DeviceProfile;
1018
+ /** The view the input being dispatched landed in — a view id, or `null`
1019
+ * for the surface's own camera. The dispatcher fills it; absent anywhere
1020
+ * else a rule is evaluated. */
1021
+ readonly viewId?: string | null;
1022
+ }
1023
+ /** The live state a `RuleCtx` is assembled from — the chrome context plus
1024
+ * what the active mode allows. */
1025
+ interface BuildRuleCtxArgs {
1026
+ focused: boolean;
1027
+ selection: readonly NodeId[];
1028
+ multiActive: boolean;
1029
+ modifiers: ModifierState;
1030
+ action: {
1031
+ kind: string | null;
1032
+ id: string | null;
1033
+ };
1034
+ hover: NodeId | null;
1035
+ /** See {@link RuleCtx.zoom}. */
1036
+ zoom?: number;
1037
+ mode: string;
1038
+ allowedCapabilities: ReadonlySet<CapabilityTag>;
1039
+ /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
1040
+ selectionResizable?: boolean;
1041
+ /** Optional — omitted means "no path is being anchor-edited". */
1042
+ editingAnchors?: boolean;
1043
+ /** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
1044
+ device?: DeviceProfile;
881
1045
  }
882
- /** Pluggable invocation strategy for an Action. */
883
- type Invoker = ImmediateInvoker | OngoingInvoker;
884
-
885
1046
  /**
886
- * GestureBinding — connects a GestureSpec to an Action id (with per-binding
887
- * options). Tools own arrays of these on their `bindings` field; ambient
888
- * gesture-bindings are registered globally.
1047
+ * Capability set to assume when no mode registry is wired.
1048
+ *
1049
+ * "No modes configured" means *everything the default mode permits*, not
1050
+ * *nothing permitted*. The empty set is the wrong default: it makes every
1051
+ * `capability:` rule evaluate false, so a kit default written the
1052
+ * recommended way (capability rules outlive new modes; mode rules have to
1053
+ * be edited) would silently hide its chrome for every consumer that hasn't
1054
+ * opted into modality. `NORMAL.allows` plus the implicit tags is what a
1055
+ * mode-less canvas actually behaves like.
889
1056
  */
1057
+ declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
1058
+ /** Gather the current canvas and mode state into the context that action
1059
+ * eligibility and chrome-visibility rules are evaluated against. */
1060
+ declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
890
1061
 
891
- /** An interaction: a gesture spec composed with the id of the action it
892
- * invokes. Tools declare arrays of these; the dispatcher matches an incoming
893
- * input event against them and runs the winner's action. */
894
- interface GestureBinding {
895
- spec: GestureSpec;
896
- actionId: string;
897
- opts?: BindingOpts;
1062
+ /**
1063
+ * A selector is a conjunction of key/value tests. Multiple keys at the same
1064
+ * level AND together. Each key maps to a selector primitive in the evaluator.
1065
+ */
1066
+ interface Selector {
1067
+ selection?: {
1068
+ is?: number;
1069
+ atLeast?: number;
1070
+ empty?: boolean;
1071
+ };
1072
+ mode?: string | {
1073
+ not: string;
1074
+ } | {
1075
+ in: readonly string[];
1076
+ };
1077
+ capability?: CapabilityTag | readonly CapabilityTag[] | {
1078
+ in: readonly CapabilityTag[];
1079
+ } | {
1080
+ not: CapabilityTag;
1081
+ };
1082
+ gesturing?: boolean;
1083
+ actionIs?: string;
1084
+ modifierHeld?: keyof ModifierState;
1085
+ focused?: boolean;
1086
+ hovering?: boolean;
1087
+ hoveringSelected?: boolean;
1088
+ zoomAtLeast?: number;
1089
+ /** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
1090
+ * mode. Absent flag is treated as `false`. */
1091
+ editingAnchors?: boolean;
1092
+ /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
1093
+ * (resizable), so `{ resizable: true }` passes for legacy ctx builders
1094
+ * that don't compute it. */
1095
+ resizable?: boolean;
1096
+ /** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
1097
+ * (touch, most styluses). Absent device is treated as `false`. */
1098
+ coarsePointer?: boolean;
1099
+ /** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
1100
+ * device is treated as `true`. */
1101
+ canHover?: boolean;
898
1102
  }
899
1103
  /**
900
- * A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
901
- * (no per-binding opts) or an object form that pairs a spec with
902
- * `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
903
- * Use the object form when two bindings for the same action differ only in
904
- * a runtime parameter — the dispatcher extracts `opts.params` and passes
905
- * them to `ImmediateInvoker.run` as its second argument.
1104
+ * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
1105
+ * over `Selector` leaves. `when` is the escape hatch — its closure is
1106
+ * opaque to introspection and should be avoided when a declarative form
1107
+ * exists. Empty `all` is true; empty `any` is false.
906
1108
  */
907
- type BoundGesture = GestureSpec | {
908
- spec: GestureSpec;
909
- opts: BindingOpts;
1109
+ type Rule = Selector | {
1110
+ all: readonly Rule[];
1111
+ } | {
1112
+ any: readonly Rule[];
1113
+ } | {
1114
+ not: Rule;
1115
+ } | {
1116
+ when: (ctx: RuleCtx) => boolean;
910
1117
  };
1118
+ /** Constant rules. Kept here so they have a single source. */
1119
+ declare const ALWAYS: Rule;
1120
+ /** A rule that never passes. `any` of nothing is false. */
1121
+ declare const NEVER: Rule;
911
1122
  /**
912
- * The whole of an action `actionBindings` reads. Routing needs this much and
913
- * none of `Action`'s palette fields, so it is the narrowest type the
914
- * dispatcher's binding walk can be written against.
1123
+ * Render a {@link Rule} as a short human-readable string, for inspectors and
1124
+ * diagnostics that need to say WHICH rule decided something.
1125
+ *
1126
+ * ```
1127
+ * { capability: 'edits-page' } → capability:edits-page
1128
+ * { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
1129
+ * { not: { selection: { empty: true } } } → not(selection:empty=true)
1130
+ * { when: function hasPath() { … } } → when(hasPath)
1131
+ * ```
1132
+ *
1133
+ * Never throws and always terminates.
915
1134
  */
916
- interface BindingSource {
917
- id: string;
918
- /** The gesture-spec form of the binding, read by the gesture dispatcher.
919
- * May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
920
- * or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
921
- * `{ spec, opts }` — use the object form for parametric actions where two
922
- * bindings for the same action differ only by `opts.params` (e.g. `flip`
923
- * with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
924
- * passes them to `ImmediateInvoker.run` as its second argument. */
925
- defaultBinding?: GestureSpec | BoundGesture[];
1135
+ declare function describeRule(rule: Rule): string;
1136
+ /** Evaluate a rule against a context. Pure, and cheap enough to run per
1137
+ * frame for every piece of chrome. */
1138
+ declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
1139
+ /**
1140
+ * Composable eligibility predicate with a fluent surface. `cond(ctx)`
1141
+ * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
1142
+ * Conditions wrapping new trees.
1143
+ *
1144
+ * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
1145
+ * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
1146
+ * disjunction, name the subexpression or use the top-level `or(...)`.
1147
+ */
1148
+ interface Condition {
1149
+ (ctx: RuleCtx): boolean;
1150
+ readonly rule: Rule;
1151
+ /** `this && other` */
1152
+ and(other: Condition | Rule): Condition;
1153
+ /** `this || other` */
1154
+ or(other: Condition | Rule): Condition;
1155
+ /** `this && !other` */
1156
+ andNot(other: Condition | Rule): Condition;
1157
+ /** `this || !other` */
1158
+ orNot(other: Condition | Rule): Condition;
1159
+ }
1160
+
1161
+ /**
1162
+ * Whether two rules can never hold at once — no `RuleCtx` passes both.
1163
+ *
1164
+ * Conservative: `true` is a proof, `false` means "may overlap". It reasons
1165
+ * about the selector values themselves (two different `mode`s, an empty
1166
+ * selection against a non-empty one, `focused: true` against `false`) and
1167
+ * through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
1168
+ * everything. The route-conflict check uses it to stay quiet about bindings
1169
+ * that share a tuple but are never eligible together.
1170
+ */
1171
+ declare function rulesExclusive(a: Rule, b: Rule): boolean;
1172
+ /**
1173
+ * Whether `rule` can hold while `mode` is active. Only its `mode` and
1174
+ * `capability` tests are settled by the mode; every other test could go either
1175
+ * way, so `false` is a proof and `true` means "may hold".
1176
+ */
1177
+ declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
1178
+
1179
+ /** The detected half of a profile — everything except the derived scale. */
1180
+ type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
1181
+ /**
1182
+ * Handle/hit multiplier applied on a coarse pointer.
1183
+ *
1184
+ * 8px handle → 14px; 24px rotation distance → 42px. Counting the
1185
+ * surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
1186
+ * minimum-touch-target band. One constant so it is tunable in one place.
1187
+ */
1188
+ declare const COARSE_TARGET_SCALE = 1.75;
1189
+ /**
1190
+ * Fold consumer overrides over detected facts and derive `targetScale`.
1191
+ *
1192
+ * `targetScale` is re-derived AFTER the merge, so an override of
1193
+ * `coarsePointer` alone scales the chrome as expected. An explicit
1194
+ * `targetScale` override wins over the derivation.
1195
+ */
1196
+ declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
1197
+ /**
1198
+ * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
1199
+ * absent-means value for `RuleCtx.device`.
1200
+ *
1201
+ * Derived from `resolveDeviceProfile` rather than hand-written, so it can
1202
+ * never drift from what the function would compute for the same facts. A
1203
+ * mouse-like device is the safe default: it is what the kit has always
1204
+ * assumed, so an absent profile changes nothing for existing consumers.
1205
+ */
1206
+ declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
1207
+
1208
+ /**
1209
+ * A settings form an action carries: what a surface draws in place of a bare
1210
+ * trigger for an action whose job is to change settings on the selection.
1211
+ */
1212
+
1213
+ /** What a settings form shows for the selection right now. */
1214
+ interface ActionSettingsState {
1215
+ /** The values, nested as the schema's groups nest. */
1216
+ values: Record<string, unknown>;
1217
+ /** The leaf paths where the selected items disagree. */
1218
+ mixed?: readonly string[];
926
1219
  }
927
1220
  /**
928
- * Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
929
- * `GestureSpec` has `kind` at top level; the object form has `spec`.
1221
+ * The settings form an action carries. A bar or a panel draws the action as a
1222
+ * button opening it, a menu as a submenu of its fields, and a palette as a
1223
+ * dialog; each change triggers the action with the params it makes.
930
1224
  */
931
- declare function actionBindings(action: BindingSource): GestureBinding[];
1225
+ interface ActionSettings {
1226
+ /** The form. Its `name` titles the popover, submenu or dialog. */
1227
+ schema: PrefGroup;
1228
+ /** The selection's current values, from the deps `enabled` is given. Under
1229
+ * `enabled`'s contract: pure, fast, and it must not throw. */
1230
+ read(deps: ActionDeps): ActionSettingsState;
1231
+ /** The params one field's change triggers the action with, or `null` for
1232
+ * none. Default: `{ [path]: value }`. */
1233
+ params?(path: string, value: unknown): Record<string, unknown> | null;
1234
+ }
1235
+ /** `action`'s settings as they stand under `deps`, or `null` for an action
1236
+ * with no settings. A `read` that throws shows nothing rather than breaking
1237
+ * the surface. */
1238
+ declare function readActionSettings(action: Action, deps: ActionDeps | undefined): ActionSettingsState | null;
1239
+ /** The params a change of the field at `path` to `value` triggers the action with. */
1240
+ declare function actionSettingsParams(settings: ActionSettings, path: string, value: unknown): Record<string, unknown> | null;
932
1241
 
1242
+ /** What content being routed brings to `Action.enabled`: a drop's or a
1243
+ * paste's items, or a carry's payload with no items. */
1244
+ interface Carried {
1245
+ items: readonly IngestItem[];
1246
+ carry?: {
1247
+ type: string;
1248
+ value: unknown;
1249
+ };
1250
+ }
933
1251
  /**
934
1252
  * Closed enum of reasons an action might report itself as disabled. The
935
1253
  * consumer (palette, menu, etc.) maps these symbolic values to display
@@ -972,6 +1290,11 @@ interface ActionPresentation {
972
1290
  * triggers the action with that variant's `params`. Omitted = the action
973
1291
  * is one entry. See `actionItems`. */
974
1292
  variants?: readonly ActionVariant[];
1293
+ /** A form of the settings the action changes on the selection. A surface
1294
+ * draws the action as that form instead of a bare trigger, and each change
1295
+ * triggers it with the params `settings.params` makes. Not combined with
1296
+ * `variants`. */
1297
+ settings?: ActionSettings;
975
1298
  }
976
1299
  /**
977
1300
  * One entry a parametric action contributes to a bar, menu or palette.
@@ -1037,18 +1360,17 @@ interface ActionDispatch extends BindingSource {
1037
1360
  * question it was shown enabled for. Palettes and keystrokes pass no
1038
1361
  * point; treat absent as "anywhere".
1039
1362
  *
1040
- * `carried` is what a drop or a paste being routed brings: its items. For
1041
- * a drag still in flight they hold a MIME and nothing else, since a
1042
- * browser withholds contents until the drop. An action that takes only
1043
- * some content refuses the rest here, so a drag it would not take is not
1044
- * accepted and a paste it would not take stays the page's.
1363
+ * `carried` is what a drop, a paste or a carry being routed brings. For
1364
+ * an OS drag still in flight its items hold a MIME and nothing else, since
1365
+ * a browser withholds contents until the drop. A carry has no items and
1366
+ * hands over its payload whole. An action that takes only some content
1367
+ * refuses the rest here, so a drag it would not take is not accepted and a
1368
+ * paste it would not take stays the page's.
1045
1369
  */
1046
1370
  enabled?: (deps?: ActionDeps, at?: {
1047
1371
  x: number;
1048
1372
  y: number;
1049
- }, carried?: {
1050
- items: readonly IngestItem[];
1051
- }) => true | ActionDisabledReason;
1373
+ }, carried?: Carried) => true | ActionDisabledReason;
1052
1374
  /**
1053
1375
  * Whether the action reads as held down right now: bold on the selected
1054
1376
  * text, snapping switched on. A toolbar button draws it as pressed.
@@ -1179,8 +1501,9 @@ interface MenuSources {
1179
1501
  * Draw a prefs schema as menu entries. Action leaves become items from the
1180
1502
  * registry (one per variant for a parametric action), an `actions` leaf one
1181
1503
  * item per action in its group, a boolean a checked item, an enum radio
1182
- * rows, and a number a row edited in place (all through `src.values`/`src.onChange`);
1183
- * a color shows as a swatch and a string as a disabled row, both read-only. A group drawn `as` a
1504
+ * rows, and a number and a string a row edited in place (all through `src.values`/`src.onChange`);
1505
+ * a color shows as a swatch, read-only, and so does a string as a disabled row when it is a readout
1506
+ * or nothing writes it. A group drawn `as` a
1184
1507
  * `menu`, `page`, `tab`, or `panel` is a submenu, and any other group's rows
1185
1508
  * sit inline between separators. Paint, field, compound, and app-defined leaves are left out.
1186
1509
  * `at` is the world point the menu was opened at and reaches `enabled` and
@@ -1475,7 +1798,10 @@ declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | un
1475
1798
  * Sources are thunks, read at invocation time, so an action never captures
1476
1799
  * stale state. */
1477
1800
  interface DepRegistry {
1478
- register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
1801
+ /** Adds a live source for `name` over any already there. `below` answers
1802
+ * what the registry would without this source, so one can handle part of
1803
+ * a dep and hand the rest to the default it displaced. */
1804
+ register<K extends DepName>(name: K, source: DepSource<K>): () => void;
1479
1805
  get<K extends DepName>(name: K): DepSchema[K] | undefined;
1480
1806
  /** Hear that what the registry answers may have changed: a source was
1481
1807
  * registered or released anywhere in its tree, or an owner called
@@ -1486,6 +1812,20 @@ interface DepRegistry {
1486
1812
  * so its owner is the only one who knows; chrome showing an action's
1487
1813
  * `enabled` or `active` state re-reads on this. */
1488
1814
  changed?(): void;
1815
+ /** Every name a `get` here would answer, sorted, with where its live
1816
+ * source sits. For inspection; a hand-built registry may leave it out. */
1817
+ list?(): readonly DepListing[];
1818
+ }
1819
+ /** A live source for dep `K`: a thunk read at invocation time. `below` reads
1820
+ * the source this one displaced, under it in its scope or in a scope above. */
1821
+ type DepSource<K extends DepName> = (below: () => DepSchema[K] | undefined) => DepSchema[K];
1822
+ /** One name a dep registry answers, as {@link DepRegistry.list} reports it. */
1823
+ interface DepListing {
1824
+ name: string;
1825
+ /** How many scopes above the asking one the live source was registered: 0 for its own. */
1826
+ depth: number;
1827
+ /** How many sources that scope holds under the name, the live one included. */
1828
+ stacked: number;
1489
1829
  }
1490
1830
  /** One node of the dep tree: the sources registered at it, over its parent's.
1491
1831
  * Each name holds a stack of sources, newest live, so a displaced source is
@@ -1612,222 +1952,6 @@ interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, Co
1612
1952
  def?: unknown;
1613
1953
  }
1614
1954
 
1615
- /**
1616
- * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
1617
- * the actions-layer binding-scope / matchBest logic on top.
1618
- */
1619
-
1620
- /** Where a binding came from, which is also its priority: a held hotkey beats
1621
- * the active tool, which beats bindings that are always in scope. */
1622
- type BindingScope = 'ambient' | 'active' | 'hotkey';
1623
- /** A binding paired with where it came from, ready to be matched against an
1624
- * event. */
1625
- interface ScopedBinding {
1626
- binding: GestureBinding;
1627
- scope: BindingScope;
1628
- /** Id of the tool or contribution that declared this binding —
1629
- * `'&'`-channel phase atoms resolve to it. `null` for a registered
1630
- * action's own binding. */
1631
- ownerId: string | null;
1632
- }
1633
- /** The binding that won a match. */
1634
- interface MatchResult {
1635
- binding: GestureBinding;
1636
- scope: BindingScope;
1637
- /** Id of the entry that declared the binding — propagated from
1638
- * `ScopedBinding` so the dispatcher can record it as the handle owner. */
1639
- ownerId: string | null;
1640
- }
1641
- /**
1642
- * True when a spec's target actually consults the affordance hit rather than
1643
- * only the body classification. `kindOf` predicates are handed the hit;
1644
- * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
1645
- * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
1646
- * `bodyTarget` / `bodyKind` and never see it.
1647
- *
1648
- * A predicate declares which it is with `readsAffordance`; one that declares
1649
- * nothing, which only untyped code can write, counts as not reading the hit.
1650
- */
1651
- declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
1652
- /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
1653
- declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
1654
- /**
1655
- * Find every binding that matches the event, sorted by precedence
1656
- * (best → worst). Ordering rules:
1657
- *
1658
- * - Scopes by priority: hotkey > active > ambient.
1659
- * - Within a scope, more-specific bindings beat less-specific (see
1660
- * `specificity()`).
1661
- * - Among same-specificity bindings, first-declared wins.
1662
- */
1663
- declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
1664
- /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
1665
- interface MatchOutcome {
1666
- matches: MatchResult[];
1667
- /** Bindings whose spec matched the event but which the claim barred, in
1668
- * the same order `matches` uses. Filled only when `collectBarred` is set. */
1669
- barred: MatchResult[];
1670
- /** Owner of the exclusive claim, when one applied. */
1671
- claimOwner?: string;
1672
- }
1673
- /**
1674
- * {@link matchSorted} that can also report the bindings an exclusive claim
1675
- * kept out. Collecting them costs a spec match per barred binding, so it is
1676
- * opt-in — the dispatcher asks only when it is recording.
1677
- */
1678
- declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
1679
- collectBarred?: boolean;
1680
- warn?: (message: string) => void;
1681
- }): MatchOutcome;
1682
-
1683
- /**
1684
- * How one input was resolved: every binding that matched it, what each filter
1685
- * dropped, the order the rest were ranked in and why, and what the walk down
1686
- * that order did. The dispatcher fills one per input in dev builds, and
1687
- * `Dispatcher.explain` fills one on request without invoking anything.
1688
- *
1689
- * Plain data — no functions, no live objects — so it can be kept in a trace
1690
- * buffer, serialized, or built by hand as a fixture.
1691
- */
1692
- interface DispatchRecord {
1693
- kind: 'dispatch';
1694
- ts: number;
1695
- input: DispatchRecordInput;
1696
- /** Every binding whose spec matched the input, in matcher order, including
1697
- * those a filter then dropped. */
1698
- matched: readonly RecordCandidate[];
1699
- dropped: readonly DroppedCandidate[];
1700
- /** The candidates that survived both filters, best first. */
1701
- ranked: readonly RankedCandidate[];
1702
- /** True when nothing was invoked: the record answers "what would happen". */
1703
- predicted: boolean;
1704
- /** Action id of the candidate that fired (or would), else `null`. */
1705
- fired: string | null;
1706
- outcome: 'handled' | 'unhandled';
1707
- }
1708
- /** The input a {@link DispatchRecord} describes, reduced to plain data. */
1709
- interface DispatchRecordInput {
1710
- /** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
1711
- eventKind: string;
1712
- /** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
1713
- key?: string;
1714
- modifiers: {
1715
- alt: boolean;
1716
- ctrl: boolean;
1717
- meta: boolean;
1718
- shift: boolean;
1719
- };
1720
- /** The view the input was routed to; `null` for the surface's own camera. */
1721
- viewId: string | null;
1722
- /** World point, for events that carry one. */
1723
- world?: {
1724
- x: number;
1725
- y: number;
1726
- };
1727
- /** Active mode id, when the host supplies a rule context. */
1728
- mode?: string;
1729
- }
1730
- /** One matched binding. */
1731
- interface RecordCandidate {
1732
- actionId: string;
1733
- /** The binding's spec in route grammar — one per arg alternative. */
1734
- routes: readonly string[];
1735
- scope: BindingScope;
1736
- /** The tool or contribution that declared the binding; `null` for an action's own binding. */
1737
- ownerId: string | null;
1738
- /** Whether the binding's `views` names the routed view. */
1739
- namesView: boolean;
1740
- /** `specificity(spec)`: target, modifiers, phase, exact. */
1741
- specificity: readonly [number, number, number, number];
1742
- /** The action's `eligible` rule as text, when it declares one. */
1743
- eligible?: string;
1744
- }
1745
- /** A matched candidate a filter removed before ranking, and which filter. */
1746
- type DroppedCandidate =
1747
- /** An exclusive claim on the input barred bindings that do not read the
1748
- * claim's affordance. */
1749
- {
1750
- candidate: RecordCandidate;
1751
- filter: 'claim';
1752
- owner?: string;
1753
- }
1754
- /** The action's `eligible` rule does not hold. */
1755
- | {
1756
- candidate: RecordCandidate;
1757
- filter: 'ineligible';
1758
- rule: string;
1759
- };
1760
- /** One field of the `specificity` tuple, in the order it is compared. */
1761
- type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
1762
- /** The ranking step that put a candidate below the one before it. */
1763
- type PlacedBy = {
1764
- step: 'first';
1765
- }
1766
- /** The one above names the routed view; this one does not. */
1767
- | {
1768
- step: 'view';
1769
- }
1770
- /** The one above sits in a higher scope tier. */
1771
- | {
1772
- step: 'tier';
1773
- }
1774
- /** Same tier; the one above is more specific in `part`. */
1775
- | {
1776
- step: 'specificity';
1777
- part: SpecificityPart;
1778
- }
1779
- /** Tied; the one above is gated by an `eligible` rule that holds. */
1780
- | {
1781
- step: 'context';
1782
- }
1783
- /** Tied on everything; registration order. */
1784
- | {
1785
- step: 'order';
1786
- };
1787
- /** What the walk did with one ranked candidate. */
1788
- type WalkStep = {
1789
- kind: 'fired';
1790
- }
1791
- /** Predicted records only: this candidate would fire. Nothing was started,
1792
- * so an ongoing action here may still bail at `start()` for real. */
1793
- | {
1794
- kind: 'would-fire';
1795
- }
1796
- /** `enabled()` returned this reason. */
1797
- | {
1798
- kind: 'declined';
1799
- reason: string;
1800
- }
1801
- /** An ongoing action's `start()` returned an empty handle. */
1802
- | {
1803
- kind: 'empty-handle';
1804
- }
1805
- /** An ongoing action bound to a press-time `pointerDown` spec; refused. */
1806
- | {
1807
- kind: 'misbound';
1808
- }
1809
- /** Its action was already tried higher up; each runs at most once. */
1810
- | {
1811
- kind: 'duplicate';
1812
- } | {
1813
- kind: 'no-such-action';
1814
- }
1815
- /** Below the winner; never asked. */
1816
- | {
1817
- kind: 'not-asked';
1818
- }
1819
- /** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
1820
- | {
1821
- kind: 'outranked';
1822
- };
1823
- /** A candidate that survived the filters: why it sits where it does, and
1824
- * what the walk did with it. */
1825
- interface RankedCandidate {
1826
- candidate: RecordCandidate;
1827
- placedBy: PlacedBy;
1828
- walk: WalkStep;
1829
- }
1830
-
1831
1955
  /**
1832
1956
  * The dispatcher's contract: what a host hands it per input
1833
1957
  * (`DispatcherContext`) and what it answers with (`Dispatcher`).
@@ -1866,6 +1990,19 @@ interface DispatcherContext {
1866
1990
  * reads as the root.
1867
1991
  */
1868
1992
  viewId?: string | null;
1993
+ /**
1994
+ * For a key from the page: whether this surface holds the keys, and whether
1995
+ * the pointer is over it. Bindings with `opts.over` are live only while it
1996
+ * is, the rest only while it holds them. Absent, every binding is live.
1997
+ */
1998
+ keyReach?: KeyReach;
1999
+ /** Where the dispatcher records how it resolved each input. Absent records nothing. */
2000
+ trace?: DispatchTrace;
2001
+ }
2002
+ /** Which of a surface's key bindings a key from the page reaches. */
2003
+ interface KeyReach {
2004
+ held: boolean;
2005
+ over: boolean;
1869
2006
  }
1870
2007
  /**
1871
2008
  * Handle returned by `Dispatcher.beginUiOngoing()` for driving an
@@ -2073,31 +2210,6 @@ interface Dispatcher {
2073
2210
  beginUiOngoing(actionId: string, deps: ActionDeps, params?: Record<string, unknown>): UiOngoingControl | null;
2074
2211
  }
2075
2212
 
2076
- /**
2077
- * Dev-only trace of what the dispatcher did: one record per resolved input and
2078
- * one per mode switch, in a rolling buffer the dev tools read.
2079
- */
2080
-
2081
- /** One entry per "mode change" — a kit state transition that isn't a
2082
- * dispatched input event but explains why an input was (or wasn't) handled
2083
- * later. Pushed via `recordModeSwitch()`. */
2084
- interface ModeSwitchLogEntry {
2085
- kind: 'mode';
2086
- ts: number;
2087
- /** Logical mode name, e.g. `'editAnchors.editingId'`. */
2088
- mode: string;
2089
- from: string | null;
2090
- to: string | null;
2091
- /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
2092
- * `'selection-dropped target'`, etc. */
2093
- detail?: string;
2094
- }
2095
- /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
2096
- type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
2097
- /** Push a mode-switch record into the trace log. Safe to call from
2098
- * anywhere; no-ops outside DEV. */
2099
- declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
2100
-
2101
2213
  /**
2102
2214
  * The slot an in-flight ongoing handle is keyed by:
2103
2215
  * - `key-held`: `key-held-<code>` (`key-held-Space`), so two keys that share a
@@ -2798,6 +2910,29 @@ interface ToolDef<TOverlay = KernelOverlay> {
2798
2910
  */
2799
2911
  declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
2800
2912
 
2913
+ /**
2914
+ * Tool sets: a named group of tool ids, the unit a consumer opts into whole.
2915
+ * A set names ids, never tools, because a tool is built by a hook that holds
2916
+ * its state; whatever mounts the set supplies the tool for each id.
2917
+ */
2918
+ /** A named group of tool ids. A member is an id or another set, whose ids it takes in order. */
2919
+ interface ToolSet<TId extends string = string> {
2920
+ readonly id: string;
2921
+ readonly members: readonly (TId | ToolSet<TId>)[];
2922
+ }
2923
+ /** One entry of a list of tools to mount: an id, or a set of them. */
2924
+ type ToolEntry<TId extends string = string> = TId | ToolSet<TId>;
2925
+ /** A set named `id` of `members`, in palette order. */
2926
+ declare function toolSet<TId extends string>(id: string, members: readonly ToolEntry<TId>[]): ToolSet<TId>;
2927
+ /** Whether `entry` is a set rather than a single id. */
2928
+ declare function isToolSet<TId extends string>(entry: ToolEntry<TId>): entry is ToolSet<TId>;
2929
+ /** Every id `entries` name, sets opened in place, each id once at its first place. */
2930
+ declare function toolIdsOf<TId extends string>(entries: readonly ToolEntry<TId>[]): TId[];
2931
+ /** `set` less every id `omit` names, sets opened. Keeps `set`'s id with a `-without` suffix. */
2932
+ declare function withoutTools<TId extends string>(set: ToolSet<TId>, ...omit: readonly ToolEntry<TId>[]): ToolSet<TId>;
2933
+ /** Whether `set` names `id`, directly or through a set inside it. */
2934
+ declare function toolSetHas<TId extends string>(set: ToolSet<TId>, id: string): boolean;
2935
+
2801
2936
  /**
2802
2937
  * One row in the route registry — a single `GestureBinding` on one tool,
2803
2938
  * flattened into the route grammar's vocabulary so the inspector, the
@@ -3092,12 +3227,21 @@ declare function deleteScratch<T>(store: ScratchStore, key: ScratchKey<T>): bool
3092
3227
  *
3093
3228
  * Output shape: **files first (source order), then strings (item order)**.
3094
3229
  * For paste (`itemsFromClipboardData`) string flavors are emitted in
3095
- * `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list.
3230
+ * `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list, then
3231
+ * the admitted ones in the order they were first admitted.
3096
3232
  */
3097
3233
 
3098
- /** The string flavors the kit normalizes. Everything else (browser-internal
3099
- * custom flavors, etc.) is dropped at materialization. */
3234
+ /** The string flavors the kit always keeps. Any other is dropped at
3235
+ * materialization unless {@link admitIngestMime} has admitted it. */
3100
3236
  declare const INGEST_STRING_MIMES: ReadonlySet<string>;
3237
+ /**
3238
+ * Keep string flavors of `mime` on drops, drags and pastes as well as the
3239
+ * {@link INGEST_STRING_MIMES}, so content from the app's own UI can travel
3240
+ * under a type of its own. Returns a release; the flavor is kept while any
3241
+ * admission of it is unreleased. Core admits each exact MIME a registered
3242
+ * content handler names.
3243
+ */
3244
+ declare function admitIngestMime(mime: string): () => void;
3101
3245
  /** Wrap raw `File`s (file picker, `CanvasExtensionApi.ingest`) as
3102
3246
  * `IngestItem`s, normalizing each MIME the same way the drop/paste paths
3103
3247
  * do. Synchronous — `File` objects are already fully owned. */
@@ -3202,6 +3346,8 @@ interface DepSchema {
3202
3346
  activeTool: ActiveToolContextValue;
3203
3347
  /** The menu host of the `ActionsProvider` in scope. */
3204
3348
  menuHost?: MenuHost;
3349
+ /** The canvas's dispatch trace, for a dep to record a mode switch into. */
3350
+ dispatchTrace?: DispatchTrace;
3205
3351
  }
3206
3352
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
3207
3353
  type DepName = keyof DepSchema;
@@ -3249,4 +3395,4 @@ type KernelOverlay = OverlaySchema extends {
3249
3395
  overlay: infer O;
3250
3396
  } ? O : unknown;
3251
3397
 
3252
- export { type CarryCallbacks as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionSource as E, type ActionTuning as F, type ActionTunings as G, type ActionsProp as H, type AnyTool as I, type AssemblyInputs as J, type KernelOverlay as K, type LongPressOptions as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, BROWSER_RESERVED_CHORDS as P, type BindingOpts as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type BindingScope as U, type BindingSource as V, type BodyPredicate as W, type Bounds as X, type BuildRuleCtxArgs as Y, COARSE_TARGET_SCALE as Z, type Carry as _, type DepName as a, type ResizeAnchor as a$, type CarryPayload as a0, type CarryPointer as a1, type CarryStage as a2, type Condition as a3, type Conflict as a4, type ContributionChrome as a5, type ContributionDeps as a6, type ContributionRouting as a7, DEFAULT_ALLOWED_CAPABILITIES as a8, DEFAULT_DEVICE_PROFILE as a9, type MenuSources as aA, type MenuWhen as aB, type ModeShortcutHandlers as aC, type ModeSwitchLogEntry as aD, type ModifierRole as aE, type ModifierRoles as aF, type ModifierState as aG, NEVER as aH, type OngoingHandle as aI, type OngoingInvoker as aJ, type OngoingOverlay as aK, type OpenMenu as aL, type OverlayRole as aM, type OverlaySchema as aN, type OwnedMenuDef as aO, PREDICATE_TARGET as aP, type PendingLongPress as aQ, type PlacedBy as aR, type Point2 as aS, type PointerSession as aT, type PointerSessionCallbacks as aU, type PointerSessionCancelReason as aV, type PointerSessionOptions as aW, type RankedCandidate as aX, type RecordCandidate as aY, type RegistryEntry as aZ, type ReservedChord as a_, type DebugSink as aa, type DetectedDeviceFacts as ab, type DeviceProfile as ac, type DispatchRecord as ad, type DispatchRecordInput as ae, type DispatcherContext as af, type DragSample as ag, type DroppedCandidate as ah, type Eligibility as ai, type EligibilityState as aj, type GestureBinding as ak, type GestureKitSettings as al, type HandleKind as am, type HitShape as an, type HotkeyTrigger as ao, INGEST_STRING_MIMES as ap, type ImmediateInvoker as aq, type InvocationCtx as ar, type Invoker as as, type KeyBinding as at, type LongPressState as au, type LongPressStore as av, type MatchOutcome as aw, type MatchResult as ax, type MenuHit as ay, type MenuOpenRequest as az, type DepSchema as b, isPathEditPart as b$, type ResolveAllOptions as b0, type ResolveOnlyResult as b1, type ResolvedCandidate as b2, type RoleKeys as b3, type RoleRebinds as b4, type Rule as b5, type ScopedBinding as b6, type ScratchKey as b7, type ScratchStore as b8, type SelectionApi as b9, buildToolOffhandBindings as bA, clientToCanvas as bB, clientToCanvasRect as bC, createDepRegistry as bD, createDispatcher as bE, createLongPressStore as bF, createMenuHost as bG, defineTool as bH, deleteScratch as bI, describeRule as bJ, dlog as bK, dwarn as bL, evaluate as bM, evaluateActive as bN, evaluateEnabled as bO, findConflicts as bP, getScratch as bQ, heldKeySpec as bR, historyKey as bS, isAnchor as bT, isAnchorOrControl as bU, isBalanceGrip as bV, isBody as bW, isControlHandle as bX, isCornerWidget as bY, isDebugEnabled as bZ, isEmpty as b_, type Selector as ba, type SpecificityPart as bb, TOOL_OFFHAND_ID as bc, type ToolDef as bd, type ToolKeybinding as be, type ToolModifiers as bf, type ToolOffhandBindingSpec as bg, type ToolPresentation as bh, type ToolScopes as bi, type ToolSlot as bj, type TraceLogEntry as bk, type TunedBinding as bl, type UiOngoingControl as bm, type ViewportGestureKind as bn, type WalkStep as bo, actionBindings as bp, actionCursor as bq, actionItemTitle as br, actionItems as bs, actionShortcuts as bt, actionsTagged as bu, assembleEntries as bv, browserReservedChord as bw, buildDepsFromRequires as bx, buildRouteRegistry as by, buildRuleCtx as bz, type ActionDeps as c, isResizeHandle as c0, isRotateHandle as c1, isSegment as c2, isSelectedBody as c3, isTool as c4, isUnselectedBody as c5, itemTypesFromDataTransfer as c6, itemsFromClipboardData as c7, itemsFromDataTransfer as c8, itemsFromFiles as c9, routesForSpec as cA, ruleCanHoldIn as cB, rulesExclusive as cC, scopeBindings as cD, scratchKey as cE, setScratch as cF, startCarry as cG, taggedActionItems as cH, targetConsultsAffordance as cI, tuneAction as cJ, validateActionDefaultBinding as cK, validateActionId as cL, keySpecShortcut as ca, keysHeld as cb, liveScope as cc, makeToolOffhandAction as cd, matchActionItem as ce, matchBest as cf, matchSorted as cg, matchSortedWithBarred as ch, matchesKeyBinding as ci, mergeContributions as cj, mergeMenuDefs as ck, modeShortcutSpec as cl, modeShortcuts as cm, multiTouchGestureId as cn, offhandKeyFor as co, openPointerSession as cp, overlaysOf as cq, pickMenu as cr, pointerGestureId as cs, recordModeSwitch as ct, reportRouteConflicts as cu, resolveDeviceProfile as cv, resolveMenu as cw, resolveParams as cx, resolveSchemaMenu as cy, roleReader as cz, type ActionsRegistry as d, ActionDisabledReason as e, type ActionVariant as f, type MenuDef as g, DepNode as h, type Tool as i, ActiveToolContextProvider as j, ActiveToolContextProviderIfRoot as k, type ActiveToolContextProviderProps as l, type ActiveToolContextValue as m, useOptionalActiveToolContext as n, type Dispatcher as o, type AffordanceHit as p, ALWAYS as q, type ActionDispatch as r, type ActionEnabledResult as s, type ActionEntry as t, useActiveToolContext as u, type ActionItem as v, type ActionParamSchema as w, type ActionParams as x, type ActionPresentation as y, type ActionShortcut as z };
3398
+ export { type Bounds as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionPresentation as E, type ActionSettings as F, type ActionSettingsState as G, type ActionShortcut as H, type ActionSource as I, type ActionTuning as J, type KernelOverlay as K, type LongPressOptions as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionTunings as P, type ActionsProp as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type AnyTool as U, type AssemblyInputs as V, BROWSER_RESERVED_CHORDS as W, type BindingOpts as X, type BindingScope as Y, type BindingSource as Z, type BodyPredicate as _, type DepName as a, type PointerSessionCallbacks as a$, type BuildRuleCtxArgs as a0, COARSE_TARGET_SCALE as a1, type Carried as a2, type Carry as a3, type CarryCallbacks as a4, type CarryPayload as a5, type CarryPointer as a6, type CarryStage as a7, type Condition as a8, type Conflict as a9, type KeyReach as aA, type LongPressState as aB, type LongPressStore as aC, type MatchOutcome as aD, type MatchResult as aE, type MenuHit as aF, type MenuOpenRequest as aG, type MenuSources as aH, type MenuWhen as aI, type ModeShortcutHandlers as aJ, type ModeSwitchLogEntry as aK, type ModifierRole as aL, type ModifierRoles as aM, type ModifierState as aN, NEVER as aO, type OngoingHandle as aP, type OngoingInvoker as aQ, type OngoingOverlay as aR, type OpenMenu as aS, type OverlayRole as aT, type OverlaySchema as aU, type OwnedMenuDef as aV, PREDICATE_TARGET as aW, type PendingLongPress as aX, type PlacedBy as aY, type Point2 as aZ, type PointerSession as a_, type ContributionChrome as aa, type ContributionDeps as ab, type ContributionRouting as ac, DEFAULT_ALLOWED_CAPABILITIES as ad, DEFAULT_DEVICE_PROFILE as ae, type DebugSink as af, type DepListing as ag, type DetectedDeviceFacts as ah, type DeviceProfile as ai, type DispatchRecord as aj, type DispatchRecordInput as ak, type DispatcherContext as al, type DragSample as am, type DroppedCandidate as an, type Eligibility as ao, type EligibilityState as ap, type GestureBinding as aq, type GestureKitSettings as ar, type HandleKind as as, type HitShape as at, type HotkeyTrigger as au, INGEST_STRING_MIMES as av, type ImmediateInvoker as aw, type InvocationCtx as ax, type Invoker as ay, type KeyBinding as az, type DepSource as b, evaluateActive as b$, type PointerSessionCancelReason as b0, type PointerSessionOptions as b1, type RankedCandidate as b2, type RecordCandidate as b3, type RegistryEntry as b4, type ReservedChord as b5, type ResizeAnchor as b6, type ResolveAllOptions as b7, type ResolveOnlyResult as b8, type ResolvedCandidate as b9, actionCursor as bA, actionItemTitle as bB, actionItems as bC, actionSettingsParams as bD, actionShortcuts as bE, actionsTagged as bF, admitIngestMime as bG, assembleEntries as bH, browserReservedChord as bI, buildDepsFromRequires as bJ, buildRouteRegistry as bK, buildRuleCtx as bL, buildToolOffhandBindings as bM, clientToCanvas as bN, clientToCanvasRect as bO, createDepRegistry as bP, createDispatchTrace as bQ, createDispatcher as bR, createLongPressStore as bS, createMenuHost as bT, createTraceRing as bU, defineTool as bV, deleteScratch as bW, describeRule as bX, dlog as bY, dwarn as bZ, evaluate as b_, type RoleKeys as ba, type RoleRebinds as bb, type Rule as bc, type ScopedBinding as bd, type ScratchKey as be, type ScratchStore as bf, type SelectionApi as bg, type Selector as bh, type SpecificityPart as bi, TOOL_OFFHAND_ID as bj, type ToolDef as bk, type ToolEntry as bl, type ToolKeybinding as bm, type ToolModifiers as bn, type ToolOffhandBindingSpec as bo, type ToolPresentation as bp, type ToolScopes as bq, type ToolSet as br, type ToolSlot as bs, type TraceLogEntry as bt, type TraceRing as bu, type TunedBinding as bv, type UiOngoingControl as bw, type ViewportGestureKind as bx, type WalkStep as by, actionBindings as bz, type ActionDeps as c, tuneAction as c$, evaluateEnabled as c0, findConflicts as c1, getScratch as c2, heldKeySpec as c3, historyKey as c4, isAnchor as c5, isAnchorOrControl as c6, isBalanceGrip as c7, isBody as c8, isControlHandle as c9, modeShortcutSpec as cA, modeShortcuts as cB, multiTouchGestureId as cC, offhandKeyFor as cD, openPointerSession as cE, overlaysOf as cF, pickMenu as cG, pointerGestureId as cH, readActionSettings as cI, reportRouteConflicts as cJ, resolveDeviceProfile as cK, resolveMenu as cL, resolveParams as cM, resolveSchemaMenu as cN, roleReader as cO, routesForSpec as cP, ruleCanHoldIn as cQ, rulesExclusive as cR, scopeBindings as cS, scratchKey as cT, setScratch as cU, startCarry as cV, taggedActionItems as cW, targetConsultsAffordance as cX, toolIdsOf as cY, toolSet as cZ, toolSetHas as c_, isCornerWidget as ca, isDebugEnabled as cb, isEmpty as cc, isPathEditPart as cd, isResizeHandle as ce, isRotateHandle as cf, isSegment as cg, isSelectedBody as ch, isTool as ci, isToolSet as cj, isUnselectedBody as ck, itemTypesFromDataTransfer as cl, itemsFromClipboardData as cm, itemsFromDataTransfer as cn, itemsFromFiles as co, keySpecShortcut as cp, keysHeld as cq, liveScope as cr, makeToolOffhandAction as cs, matchActionItem as ct, matchBest as cu, matchSorted as cv, matchSortedWithBarred as cw, matchesKeyBinding as cx, mergeContributions as cy, mergeMenuDefs as cz, type ActionsRegistry as d, validateActionDefaultBinding as d0, validateActionId as d1, withoutTools as d2, ActionDisabledReason as e, type ActionVariant as f, type MenuDef as g, DepNode as h, type Tool as i, ActiveToolContextProvider as j, ActiveToolContextProviderIfRoot as k, type ActiveToolContextProviderProps as l, type ActiveToolContextValue as m, useOptionalActiveToolContext as n, type Dispatcher as o, type AffordanceHit as p, type DispatchTrace as q, type DepSchema as r, ALWAYS as s, type ActionDispatch as t, useActiveToolContext as u, type ActionEnabledResult as v, type ActionEntry as w, type ActionItem as x, type ActionParamSchema as y, type ActionParams as z };