@weasel-js/routing 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
+ import { ModifierKey, ClaimableGesture, GestureSpec, InputEvent, TargetSpec, IngestItem, BodyTarget, BindingOverrideInput, KeyHeldSpec, TargetPredicate, KeySpec, ClickSpec, DoubleClickSpec, DragSpec, ContextMenuSpec, 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,423 +169,180 @@ 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.
276
- */
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;
292
- };
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;
370
- }
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
- /**
409
- * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
410
- * absent-means value for `RuleCtx.device`.
411
- *
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.
423
- */
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;
433
- }
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.
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.
589
346
  */
590
347
  interface AffordanceHit {
591
348
  /** Discriminator string, e.g. `'handle:bottom-right'`. */
@@ -747,6 +504,11 @@ interface InvocationCtx {
747
504
  /** Per-invocation options the dispatcher reads from a `GestureBinding`'s
748
505
  * `opts` field and passes to `OngoingInvoker.start`. */
749
506
  interface BindingOpts {
507
+ /** The id a binding override names this binding by, `<owner>.<role>`:
508
+ * `undo.key`, `rect.drag`, `nudge.left.big`. Without one it is its owner and
509
+ * its position (`<entry>#<n>`, `<action>@<n>`), which moves when the defaults
510
+ * are reordered, so a binding a host may persist an override for declares one. */
511
+ id?: string;
750
512
  /** Gesture behaviors the action's own invoker runs. Routing carries the
751
513
  * array to the action and never reads an element, so the element type is
752
514
  * open here; `@weasel-js/core` narrows it. */
@@ -777,6 +539,12 @@ interface BindingOpts {
777
539
  * binding; this tells the route-conflict check the order is the design.
778
540
  */
779
541
  yields?: boolean;
542
+ /**
543
+ * For a key binding: live while the pointer is over the surface, whichever
544
+ * canvas holds the keys, and only then. For a key that means "here" — a
545
+ * magnifier's peek — on a page with more than one surface that hears it.
546
+ */
547
+ over?: boolean;
780
548
  /**
781
549
  * Keys for some of the action's `modifierRoles` on this binding, by role
782
550
  * id, over the action's defaults and any tuning's.
@@ -822,8 +590,11 @@ interface OngoingHandle {
822
590
  *
823
591
  * `previewIds()` enumerates the displaced node ids; `previewPose(id)`
824
592
  * 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".
593
+ * casts to its `TPose` parameter). `previewIds` returning `null`, or
594
+ * omitted, means "no preview this gesture". An id with no `previewPose`
595
+ * is shown at its effective pose: an action that publishes its poses to
596
+ * the scene's pose overrides, as move, resize and rotate do, leaves
597
+ * `previewPose` out rather than answering the same pose twice.
827
598
  */
828
599
  previewIds?(): Iterable<string> | null;
829
600
  previewPose?(id: string): unknown | null;
@@ -930,902 +701,1355 @@ interface BindingSource {
930
701
  */
931
702
  declare function actionBindings(action: BindingSource): GestureBinding[];
932
703
 
704
+ /** What a key no open keymap binds does: `'modal'` swallows it and leaves the
705
+ * keymap open; `'cascade'` closes the keymap and dispatches it as if none were. */
706
+ type KeymapUnbound = 'modal' | 'cascade';
707
+ /** What holds a level opened with `{ hold: true }` open: no key releases it, so it closes on Escape. */
708
+ declare const KEYMAP_HOLD = "hold";
709
+ /** One open level of a {@link KeymapStack}. */
710
+ interface OpenKeymap {
711
+ readonly id: string;
712
+ readonly label: string;
713
+ /** The keymap's bindings as declared, for a hint to list. */
714
+ readonly bindings: readonly GestureBinding[];
715
+ /** The gesture id of the leader holding this level open, while it is held,
716
+ * or {@link KEYMAP_HOLD}. */
717
+ readonly heldBy?: string;
718
+ /** Whether input arrived while the leader was held, so its release closes the level. */
719
+ readonly usedWhileHeld: boolean;
720
+ }
721
+ /** What the keymaps made of the raw input in progress: `'taken'` when one of
722
+ * them handled it, `'cascaded'` when it went on past them, `null` before either. */
723
+ type KeymapVerdict = 'taken' | 'cascaded' | null;
724
+ /** The keymaps open on one surface, innermost last; a canvas's `keymaps` dep. */
725
+ interface KeymapStack {
726
+ /** Every open level, outermost first. */
727
+ open(): readonly OpenKeymap[];
728
+ innermost(): OpenKeymap | null;
729
+ /** Open a level. `heldBy` names what holds it open; `bindings` are what a hint lists. */
730
+ push(id: string, label: string, opts?: {
731
+ heldBy?: string;
732
+ bindings?: readonly GestureBinding[];
733
+ }): void;
734
+ /** Mark the levels the last raw input opened as held by `heldBy`: the key
735
+ * that opened them is still down. False when it opened none. */
736
+ holdOpened(heldBy: string): boolean;
737
+ /** The leader `heldBy` came up: close its level, those above it and the
738
+ * tapped ones beneath it if input arrived meanwhile, else leave it open
739
+ * for one more input. */
740
+ release(heldBy: string): void;
741
+ /** Close the innermost level: Escape. */
742
+ closeInnermost(): void;
743
+ /** One of the keymaps took this input, or a pointer gesture went past them.
744
+ * Marks held levels used and closes once-levels down to the innermost held one. */
745
+ inputSeen(): void;
746
+ /** A key no keymap binds was swallowed: held levels count it as input, and nothing closes. */
747
+ swallowed(): void;
748
+ /** This input went on past the keymaps: as `inputSeen`, remembered as a cascade. */
749
+ cascaded(): void;
750
+ /** What the keymaps made of the input in progress. */
751
+ verdict(): KeymapVerdict;
752
+ closeAll(): void;
753
+ /** Run `fn` as one raw input: closes made inside apply when it returns. Nests. */
754
+ batch<T>(fn: () => T): T;
755
+ subscribe(listener: () => void): () => void;
756
+ getVersion(): number;
757
+ }
758
+ /** An empty {@link KeymapStack}. `useGestureDispatcher` makes one per surface. */
759
+ declare function createKeymapStack(): KeymapStack;
760
+
933
761
  /**
934
- * Closed enum of reasons an action might report itself as disabled. The
935
- * consumer (palette, menu, etc.) maps these symbolic values to display
936
- * strings via its own label map.
937
- */
938
- declare const ActionDisabledReason: {
939
- readonly SelectionRequired: "selection-required";
940
- readonly SceneEmpty: "scene-empty";
941
- readonly NotApplicable: "not-applicable";
942
- /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
943
- readonly PredicateThrew: "predicate-threw";
944
- };
945
- /** Why an action is unavailable right now. */
946
- type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];
947
- /**
948
- * What a palette, menu or toolbar renders for an action. No part of this is
949
- * read during dispatch.
762
+ * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
763
+ * the actions-layer binding-scope / matchBest logic on top.
950
764
  */
951
- interface ActionPresentation {
952
- label: string;
953
- /** The action's icon, by qualified name: `icon('group')` in the package that draws it. */
954
- icon?: IconRef;
955
- /** Grouping key for palette/menu surfaces. Free-form string; the kit
956
- * ships defaults for `'align'` (six edges/centers), `'distribute'`
957
- * (two axes), and recommends `'pathfinder'` for boolean ops. */
958
- group?: string;
959
- /** Words the action is also found by and gathered under. Where `group` is
960
- * the one place an action sits, an action carries any number of tags: a
961
- * palette search matches them as it matches the label, and
962
- * `actionsTagged` / `taggedActionItems` gather every action or entry
963
- * carrying one, for a bar or a menu built from a tag. */
964
- tags?: readonly string[];
965
- /** Display override for the keyboard shortcut. When omitted, palette
966
- * surfaces derive a label from `defaultBinding` via their own
967
- * formatter. */
968
- shortcut?: string;
969
- /** The distinct things a parametric action does, one per set of params —
970
- * `flip` flips horizontally or vertically, `reorder.forward` moves one step
971
- * or all the way. A bar, menu or palette shows one entry per variant and
972
- * triggers the action with that variant's `params`. Omitted = the action
973
- * is one entry. See `actionItems`. */
974
- variants?: readonly ActionVariant[];
765
+
766
+ /** Where a binding came from, which is also its priority: a held hotkey beats
767
+ * the active tool, which beats bindings that are always in scope. */
768
+ type BindingScope = 'ambient' | 'active' | 'hotkey';
769
+ /** A binding paired with where it came from, ready to be matched against an
770
+ * event. */
771
+ interface ScopedBinding {
772
+ binding: GestureBinding;
773
+ scope: BindingScope;
774
+ /** Id of the tool or contribution that declared this binding —
775
+ * `'&'`-channel phase atoms resolve to it. `null` for a registered
776
+ * action's own binding. */
777
+ ownerId: string | null;
975
778
  }
779
+ /** The binding that won a match. */
780
+ interface MatchResult {
781
+ binding: GestureBinding;
782
+ scope: BindingScope;
783
+ /** Id of the entry that declared the binding — propagated from
784
+ * `ScopedBinding` so the dispatcher can record it as the handle owner. */
785
+ ownerId: string | null;
786
+ }
787
+ /**
788
+ * True when a spec's target actually consults the affordance hit rather than
789
+ * only the body classification. `kindOf` predicates are handed the hit;
790
+ * `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
791
+ * `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
792
+ * `bodyTarget` / `bodyKind` and never see it.
793
+ *
794
+ * A predicate declares which it is with `readsAffordance`; one that declares
795
+ * nothing, which only untyped code can write, counts as not reading the hit.
796
+ */
797
+ declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
798
+ /** The first of {@link matchSorted}'s matches, or null when nothing matches. */
799
+ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
976
800
  /**
977
- * One entry a parametric action contributes to a bar, menu or palette.
801
+ * Find every binding that matches the event, sorted by precedence
802
+ * (best → worst). Ordering rules:
803
+ *
804
+ * - Scopes by priority: hotkey > active > ambient.
805
+ * - Within a scope, more-specific bindings beat less-specific (see
806
+ * `specificity()`).
807
+ * - Among same-specificity bindings, first-declared wins.
978
808
  */
979
- interface ActionVariant {
980
- /** Unique within the action; joined to its id to make the entry's key. */
981
- key: string;
982
- label: string;
983
- /** Falls back to the action's own `icon`. */
984
- icon?: IconRef;
985
- /** Tags this variant carries besides its action's, which it carries too. */
986
- tags?: readonly string[];
987
- /** Passed to `trigger`, and matched against each binding's
988
- * `opts.params` to find the variant's shortcuts. */
989
- params: Readonly<Record<string, unknown>>;
809
+ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
810
+ /** {@link matchSorted}, plus what an exclusive claim on the event barred. */
811
+ interface MatchOutcome {
812
+ matches: MatchResult[];
813
+ /** Bindings whose spec matched the event but which the claim barred, in
814
+ * the same order `matches` uses. Filled only when `collectBarred` is set. */
815
+ barred: MatchResult[];
816
+ /** Owner of the exclusive claim, when one applied. */
817
+ claimOwner?: string;
990
818
  }
991
819
  /**
992
- * What routing consults: the bindings that reach the action, the deps its
993
- * invoker reads, the gates it passes, and the cursors it claims.
820
+ * {@link matchSorted} that can also report the bindings an exclusive claim
821
+ * kept out. Collecting them costs a spec match per barred binding, so it is
822
+ * opt-in — the dispatcher asks only when it is recording.
994
823
  */
995
- interface ActionDispatch extends BindingSource {
996
- /** Names of the deps this action's invoker reads (keys of `DepSchema`).
997
- * The dispatcher (and `trigger`, when `requires` is present) resolves
998
- * each name against the `DepRegistry` at invocation time and passes the
999
- * resulting bag to the invoker. Dev builds warn when the invoker reads a
1000
- * dep it didn't declare here — see `buildDepsFromRequires`. */
1001
- requires?: readonly DepName[];
1002
- /** Pluggable invocation strategy. The gesture dispatcher routes matched
1003
- * bindings through `invoker.start` / `invoker.run` depending on timing.
1004
- * All kit-standard descriptors ship one; consumer-supplied actions
1005
- * without an invoker can still register but won't be triggered. */
1006
- invoker?: Invoker;
1007
- /** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey
1008
- * `BindingScope` instead of the ambient scope — meaning it beats any
1009
- * active-tool binding on the same input shape. Use for tool-switch
1010
- * shortcuts and global held-key triggers. Default: ambient. */
1011
- scope?: 'hotkey';
1012
- /**
1013
- * Optional predicate saying whether the action can run right now. Return
1014
- * `true` when it can, or an `ActionDisabledReason` when it cannot. The
1015
- * command palette greys out a disabled row, skips it in keyboard nav,
1016
- * ignores clicks, and shows the reason next to the label. The dispatcher
1017
- * skips a disabled action too, on a key as on a pointer, and falls through
1018
- * to the next binding that matches. `ActionsRegistry.trigger` asks too, but
1019
- * with no point, so a `run` whose answer depends on `at` should still
1020
- * self-guard.
1021
- *
1022
- * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
1023
- * must not throw. A throw is caught and treated as disabled with reason
1024
- * `ActionDisabledReason.PredicateThrew`.
1025
- *
1026
- * The palette evaluates `enabled` once when opened and does NOT re-evaluate
1027
- * on selection changes while open.
1028
- *
1029
- * `deps` is the same bag passed to `ImmediateInvoker.run`.
1030
- *
1031
- * `at` is the world point of the event being routed, when it has one — a
1032
- * click, a press, a long-press. An action that only applies somewhere
1033
- * (on a path segment, say) refuses other points here, so the dispatcher
1034
- * falls through to the next binding and hover prediction shows the
1035
- * action's `cursor` only where it would act. `trigger` passes the
1036
- * `worldX`/`worldY` its params carry, so a menu row is run under the
1037
- * question it was shown enabled for. Palettes and keystrokes pass no
1038
- * point; treat absent as "anywhere".
1039
- *
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.
1045
- */
1046
- enabled?: (deps?: ActionDeps, at?: {
824
+ declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
825
+ collectBarred?: boolean;
826
+ warn?: (message: string) => void;
827
+ }): MatchOutcome;
828
+
829
+ /**
830
+ * How one input was resolved: every binding that matched it, what each filter
831
+ * dropped, the order the rest were ranked in and why, and what the walk down
832
+ * that order did. The dispatcher fills one per input in dev builds, and
833
+ * `Dispatcher.explain` fills one on request without invoking anything.
834
+ *
835
+ * Plain data — no functions, no live objects — so it can be kept in a trace
836
+ * buffer, serialized, or built by hand as a fixture.
837
+ */
838
+ interface DispatchRecord {
839
+ kind: 'dispatch';
840
+ ts: number;
841
+ input: DispatchRecordInput;
842
+ /** Every binding whose spec matched the input, in matcher order, including
843
+ * those a filter then dropped. */
844
+ matched: readonly RecordCandidate[];
845
+ dropped: readonly DroppedCandidate[];
846
+ /** The candidates that survived both filters, best first. */
847
+ ranked: readonly RankedCandidate[];
848
+ /** True when nothing was invoked: the record answers "what would happen". */
849
+ predicted: boolean;
850
+ /** Action id of the candidate that fired (or would), else `null`. */
851
+ fired: string | null;
852
+ outcome: 'handled' | 'unhandled';
853
+ }
854
+ /** The input a {@link DispatchRecord} describes, reduced to plain data. */
855
+ interface DispatchRecordInput {
856
+ /** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
857
+ eventKind: string;
858
+ /** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
859
+ key?: string;
860
+ modifiers: {
861
+ alt: boolean;
862
+ ctrl: boolean;
863
+ meta: boolean;
864
+ shift: boolean;
865
+ };
866
+ /** The view the input was routed to; `null` for the surface's own camera. */
867
+ viewId: string | null;
868
+ /** World point, for events that carry one. */
869
+ world?: {
1047
870
  x: number;
1048
871
  y: number;
1049
- }, carried?: {
1050
- items: readonly IngestItem[];
1051
- }) => true | ActionDisabledReason;
1052
- /**
1053
- * Whether the action reads as held down right now: bold on the selected
1054
- * text, snapping switched on. A toolbar button draws it as pressed.
1055
- * Omitted = the action has no pressed state, which is not the same as
1056
- * one that is off. Never read during dispatch.
1057
- *
1058
- * Under the same contract as `enabled`: pure, fast, and it must not
1059
- * throw. `deps` is the bag `enabled` is given.
1060
- */
1061
- active?: (deps?: ActionDeps) => boolean;
1062
- /**
1063
- * Show what running the action now would do, and change nothing: the
1064
- * result an operation would make, drawn over the canvas. A toolbar asks
1065
- * while its button is hovered or focused, through
1066
- * `ActionsRegistry.preview`, and calls what this returns when that ends.
1067
- * Asked only while `enabled` says yes, with the deps and params `run`
1068
- * would get. Omitted = the action has nothing to show.
1069
- */
1070
- preview?: (deps: ActionDeps, params?: Record<string, unknown>) => (() => void) | void;
1071
- /**
1072
- * Declarative eligibility rule, evaluated against the current
1073
- * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =
1074
- * always eligible.
1075
- *
1076
- * Accepts either a fluent `Condition` (callable with `.rule`) or a
1077
- * raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.
1078
- *
1079
- * Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)
1080
- * over `mode:` rules — capability rules survive new modes being added
1081
- * that allow the same capability.
1082
- */
1083
- eligible?: Rule | Condition;
1084
- /**
1085
- * CSS cursor shown while the pointer hovers a spot where this action
1086
- * would win the drag — or, when the drag's winner declares no cursor,
1087
- * the click. The hover-cursor pump runs the same match walk a real press
1088
- * takes, so the hint and the actual target stay in sync. Omitted = no
1089
- * override (the active tool's `Tool.cursor` shows). Affordance hits are
1090
- * resolved earlier in the pump via `AffordanceRegion.cursor` and never
1091
- * reach this field.
1092
- *
1093
- * A function is asked each time with the params the winning binding would
1094
- * start the action with, tunings included, so a cursor can follow them — a
1095
- * brush ring as wide as the brush. Read it through {@link actionCursor}.
1096
- * Prediction runs `enabled()` but cannot run the invoker, so an action that
1097
- * matches yet bails at `start()` (empty handle) may still show its cursor —
1098
- * keep `enabled` accurate for actions that declare one.
1099
- */
1100
- cursor?: CursorSpec | ((params: Record<string, unknown> | undefined) => CursorSpec | undefined);
1101
- /**
1102
- * CSS cursor shown while THIS action's ongoing handle is in flight —
1103
- * grabbing while panning, `move` while dragging a selection, `crosshair`
1104
- * while pulling a marquee.
1105
- *
1106
- * Separate from `cursor` because the two answer different questions:
1107
- * `cursor` is a prediction ("a drag from here would pan"), this is a state
1108
- * ("you are panning"). An action can declare either, both, or neither;
1109
- * with only `cursor` set, the hover hint holds for the duration of the
1110
- * gesture.
1111
- */
1112
- activeCursor?: CursorSpec;
1113
- /**
1114
- * The modifiers an ongoing action reads while it runs, by role: Shift
1115
- * holding a handle's angle, Alt breaking its mirror. The dispatcher resolves
1116
- * each into `InvocationCtx.roles` on the press, every move, and the release,
1117
- * so a binding's `opts.roles` or a tuning's `roles` can move one to another
1118
- * key. Read them with {@link roleReader}. A binding's own `mods` are matched
1119
- * once, at the press; these are read throughout.
1120
- */
1121
- modifierRoles?: ModifierRoles;
872
+ };
873
+ /** Active mode id, when the host supplies a rule context. */
874
+ mode?: string;
875
+ }
876
+ /** One matched binding. */
877
+ interface RecordCandidate {
878
+ actionId: string;
879
+ /** The binding's spec in route grammar — one per arg alternative. */
880
+ routes: readonly string[];
881
+ scope: BindingScope;
882
+ /** The tool or contribution that declared the binding; `null` for an action's own binding. */
883
+ ownerId: string | null;
884
+ /** Whether the binding's `views` names the routed view. */
885
+ namesView: boolean;
886
+ /** `specificity(spec)`: target, modifiers, phase, exact. */
887
+ specificity: readonly [number, number, number, number];
888
+ /** The action's `eligible` rule as text, when it declares one. */
889
+ eligible?: string;
890
+ }
891
+ /** A matched candidate a filter removed before ranking, and which filter. */
892
+ type DroppedCandidate =
893
+ /** An exclusive claim on the input barred bindings that do not read the
894
+ * claim's affordance. */
895
+ {
896
+ candidate: RecordCandidate;
897
+ filter: 'claim';
898
+ owner?: string;
899
+ }
900
+ /** The action's `eligible` rule does not hold. */
901
+ | {
902
+ candidate: RecordCandidate;
903
+ filter: 'ineligible';
904
+ rule: string;
905
+ };
906
+ /** One field of the `specificity` tuple, in the order it is compared. */
907
+ type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
908
+ /** The ranking step that put a candidate below the one before it. */
909
+ type PlacedBy = {
910
+ step: 'first';
911
+ }
912
+ /** The one above names the routed view; this one does not. */
913
+ | {
914
+ step: 'view';
915
+ }
916
+ /** The one above sits in a higher scope tier. */
917
+ | {
918
+ step: 'tier';
919
+ }
920
+ /** Same tier; the one above is more specific in `part`. */
921
+ | {
922
+ step: 'specificity';
923
+ part: SpecificityPart;
924
+ }
925
+ /** Tied; the one above is gated by an `eligible` rule that holds. */
926
+ | {
927
+ step: 'context';
928
+ }
929
+ /** Tied on everything; registration order. */
930
+ | {
931
+ step: 'order';
932
+ };
933
+ /** What the walk did with one ranked candidate. */
934
+ type WalkStep = {
935
+ kind: 'fired';
1122
936
  }
1123
- /**
1124
- * Single registered action.
1125
- */
1126
- interface Action extends ActionDispatch, ActionPresentation {
937
+ /** Predicted records only: this candidate would fire. Nothing was started,
938
+ * so an ongoing action here may still bail at `start()` for real. */
939
+ | {
940
+ kind: 'would-fire';
1127
941
  }
1128
- /**
1129
- * The whole of an actions registry the dispatcher reads. It walks the list
1130
- * once per event to build its actionId lookup and never mutates it, so the
1131
- * routing side depends on this rather than on `ActionsRegistry`, whose
1132
- * register / mute / trigger surface is the React provider's.
1133
- */
1134
- interface ActionSource {
1135
- list(): readonly Action[];
942
+ /** `enabled()` returned this reason. */
943
+ | {
944
+ kind: 'declined';
945
+ reason: string;
1136
946
  }
1137
- /** `action`'s hover cursor for an invocation given `params`: its `cursor`,
1138
- * asked when it is a function. */
1139
- declare function actionCursor(action: Pick<Action, 'cursor'> | undefined, params?: Record<string, unknown>): CursorSpec | undefined;
1140
-
1141
- /** Which canvas hit a menu answers. Every field given must match. */
1142
- interface MenuWhen {
1143
- bodyTarget?: BodyTarget | readonly BodyTarget[];
1144
- bodyKind?: string | readonly string[];
947
+ /** An ongoing action's `start()` returned an empty handle. */
948
+ | {
949
+ kind: 'empty-handle';
1145
950
  }
1146
- /** A menu as data: a prefs schema drawn as a menu (`resolveSchemaMenu`).
1147
- * A def without `when` is opened only by id, from chrome. */
1148
- interface MenuDef {
1149
- id: string;
1150
- when?: MenuWhen;
1151
- menu: PrefGroup;
951
+ /** An ongoing action bound to a press-time `pointerDown` spec; refused. */
952
+ | {
953
+ kind: 'misbound';
1152
954
  }
1153
- /** What a right-click landed on. */
1154
- interface MenuHit {
1155
- bodyTarget?: BodyTarget;
1156
- bodyKind?: string;
955
+ /** Its action was already tried higher up; each runs at most once. */
956
+ | {
957
+ kind: 'duplicate';
958
+ } | {
959
+ kind: 'no-such-action';
1157
960
  }
1158
- /** A def with who contributed it: a tool id, or `null` for ambient. */
1159
- interface OwnedMenuDef {
1160
- def: MenuDef;
1161
- owner: string | null;
961
+ /** Below the winner; never asked. */
962
+ | {
963
+ kind: 'not-asked';
1162
964
  }
1163
-
1164
- /** What resolving reads: the registered actions, each one's deps, how to run
1165
- * one, and the values behind the schema's value leaves. */
1166
- interface MenuSources {
1167
- actions(): readonly Action[];
1168
- depsFor(action: Action): ActionDeps | undefined;
1169
- trigger(id: string, params?: Record<string, unknown>): boolean;
1170
- /** The value at a leaf's dotted path. Booleans, enums, and numbers are left
1171
- * out of a menu without both this and `onChange`, colors and strings without
1172
- * this, and `showIf`/`enableIf` are checked only with it. */
1173
- values?(path: string): unknown;
1174
- onChange?(path: string, value: unknown): void;
1175
- /** The schema an `alias` leaf's `of` is read in. Default: the menu's own. */
1176
- over?: PrefGroup;
965
+ /** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
966
+ | {
967
+ kind: 'outranked';
968
+ };
969
+ /** A candidate that survived the filters: why it sits where it does, and
970
+ * what the walk did with it. */
971
+ interface RankedCandidate {
972
+ candidate: RecordCandidate;
973
+ placedBy: PlacedBy;
974
+ walk: WalkStep;
1177
975
  }
1178
- /**
1179
- * Draw a prefs schema as menu entries. Action leaves become items from the
1180
- * registry (one per variant for a parametric action), an `actions` leaf one
1181
- * 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
1184
- * `menu`, `page`, `tab`, or `panel` is a submenu, and any other group's rows
1185
- * sit inline between separators. Paint, field, compound, and app-defined leaves are left out.
1186
- * `at` is the world point the menu was opened at and reaches `enabled` and
1187
- * every action's params; `params` go to every action, under its own.
1188
- */
1189
- declare function resolveSchemaMenu(schema: PrefGroup, src: MenuSources, at?: Point, params?: Record<string, unknown>): MenuEntry[];
1190
- /** Turn a declared menu into model entries; see {@link resolveSchemaMenu}. */
1191
- declare function resolveMenu(def: MenuDef, src: MenuSources, at?: Point, params?: Record<string, unknown>): MenuEntry[];
1192
976
 
1193
- /** Ask to open a menu: by id (chrome) or by what was hit (canvas). */
1194
- interface MenuOpenRequest {
1195
- menu?: string;
1196
- hit?: MenuHit;
1197
- /** Viewport point the menu is placed at, unless `under` is set. */
1198
- client: Point;
1199
- /** Viewport rect of the button the menu hangs from: placed below it, or above when below does not fit. */
1200
- under?: Rect;
1201
- /** World point, passed to `enabled` and to every item. */
1202
- world?: Point;
1203
- params?: Record<string, unknown>;
1204
- source?: 'pointer' | 'keyboard';
977
+ /** One entry per "mode change" — a kit state transition that isn't a
978
+ * dispatched input event but explains why an input was (or wasn't) handled
979
+ * later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
980
+ interface ModeSwitchLogEntry {
981
+ kind: 'mode';
982
+ ts: number;
983
+ /** Logical mode name, e.g. `'editAnchors.editingId'`. */
984
+ mode: string;
985
+ from: string | null;
986
+ to: string | null;
987
+ /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
988
+ * `'selection-dropped target'`, etc. */
989
+ detail?: string;
1205
990
  }
1206
- /** The menu on screen. `seq` changes on every open, so a renderer remounts. */
1207
- interface OpenMenu {
1208
- seq: number;
1209
- entries: readonly MenuEntry[];
1210
- client: Point;
1211
- under?: Rect;
1212
- world?: Point;
1213
- source: 'pointer' | 'keyboard';
991
+ /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
992
+ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
993
+ /** A rolling buffer that records in a dev build, and in any build while something watches it. */
994
+ interface TraceRing<T> {
995
+ /** Whether a push now keeps its entry. A caller building an entry checks first. */
996
+ recording(): boolean;
997
+ push(entry: T): void;
998
+ /** The entries pushed after `cursor`, oldest first, and the cursor to pass
999
+ * next time. Start from 0. Entries the buffer has dropped are skipped. */
1000
+ since(cursor: number): {
1001
+ entries: T[];
1002
+ cursor: number;
1003
+ };
1004
+ /** Turn recording on until the returned release is called. */
1005
+ watch(): () => void;
1006
+ /** Hear each kept push. Returns the unsubscribe. */
1007
+ subscribe(listener: () => void): () => void;
1214
1008
  }
1215
- /** Holds the declared menus and the one open menu. */
1216
- interface MenuHost {
1217
- /** Declare a menu; `owner` is a tool id, or `null` for always. Returns a release. */
1218
- define(def: MenuDef, owner?: string | null): () => void;
1219
- /** Pick, resolve and show. False when nothing matched or nothing resolved. */
1220
- open(req: MenuOpenRequest): boolean;
1221
- /** Whether `open(req)` would show anything. Changes no state and notifies no one. */
1222
- has(req: MenuOpenRequest): boolean;
1223
- close(): void;
1224
- current(): OpenMenu | null;
1225
- subscribe(fn: () => void): () => void;
1009
+ /** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
1010
+ declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
1011
+ /** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
1012
+ * every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
1013
+ interface DispatchTrace extends TraceRing<TraceLogEntry> {
1014
+ recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1015
+ /** Offer the record for what a press at the pointer would do, built only
1016
+ * when {@link DispatchTrace.live} reads it. `null` withdraws it. */
1017
+ setLive(explain: (() => DispatchRecord) | null): void;
1018
+ live(): DispatchRecord | null;
1226
1019
  }
1227
- /** Creates the host that decides which context menu is open, from the menus registered to it. */
1228
- declare function createMenuHost(src: MenuSources & {
1229
- activeTool(): string | null;
1230
- }): MenuHost;
1020
+ /** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
1021
+ declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
1231
1022
 
1232
1023
  /**
1233
- * The tree both input registries hang on. A provider is a root; every canvas
1234
- * mounts a scope under the registry in scope; a yoke sits between the two. A
1235
- * registration lives in the node it was made in, and a lookup starts at a leaf
1236
- * and walks up, so one canvas never answers for another.
1024
+ * Live state read by rule evaluation. Built once per frame on the consuming
1025
+ * surface — chrome-caps, the affordance pipeline, the dispatcher's
1026
+ * eligibility filter — and discarded.
1027
+ *
1028
+ * Adding a new field is additive: existing rules don't change, new
1029
+ * selector atoms can read it.
1237
1030
  */
1238
-
1239
- /** What every node of one tree shares: a version and the listeners to it. */
1240
- interface ScopeTree {
1241
- version: number;
1242
- readonly listeners: Emitter;
1031
+ interface RuleCtx {
1032
+ readonly focused: boolean;
1033
+ readonly selection: readonly NodeId[];
1034
+ readonly multiActive: boolean;
1035
+ readonly modifiers: ModifierState;
1036
+ readonly action: {
1037
+ readonly kind: string | null;
1038
+ readonly id: string | null;
1039
+ };
1040
+ readonly hover: NodeId | null;
1041
+ /**
1042
+ * How far the viewport is zoomed in, as one number — `1` at native scale.
1043
+ *
1044
+ * A scalar rather than the `View` this used to carry, because `zoomAtLeast`
1045
+ * is the only selector that ever read one and a number is all it needs. A
1046
+ * host whose viewport is a camera has no `View` to hand over and would
1047
+ * otherwise have to invent one to use eligibility at all; it omits this
1048
+ * instead, and `zoomAtLeast` declines rather than matching on a fiction.
1049
+ */
1050
+ readonly zoom?: number;
1051
+ /** Active mode id. `'normal'` when no non-default mode is engaged. */
1052
+ readonly mode: string;
1053
+ /** Capability tags allowed by the active mode (the union of
1054
+ * `ModeDefinition.allows` plus implicit tags). The `capability:`
1055
+ * selector reads this to determine whether a tag is permitted. */
1056
+ readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
1057
+ /** Whether the current selection may be resized. `<SceneCanvas>` folds
1058
+ * `selectTool.resize.resizable` over the selection (true only when every
1059
+ * selected node is resizable). Read by the `resizable:` selector to gate
1060
+ * `selection.resize-handles`. Absent (legacy ctx builders) is treated as
1061
+ * resizable — back-compat: handles show unless a consumer opts a node out. */
1062
+ readonly selectionResizable?: boolean;
1063
+ /** Whether a path is currently in anchor-edit mode. Read by the
1064
+ * `editingAnchors:` selector, which gates the path-edit chrome.
1065
+ *
1066
+ * This is deliberately a fact about state, not about permission: the
1067
+ * anchor overlay and the anchor hit-test must agree, and the thing they
1068
+ * must agree on is "is there an edited path right now", which no
1069
+ * capability or mode id answers. A mode that allows `edits-anchors`
1070
+ * with nothing being edited should draw no anchors. Absent is treated
1071
+ * as false. */
1072
+ readonly editingAnchors?: boolean;
1073
+ /** Device facts — pointer coarseness, hover capability, density.
1074
+ *
1075
+ * Absent (legacy ctx builders) is treated as
1076
+ * {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
1077
+ * density 1. That is what the kit assumed before this field existed, so
1078
+ * an absent profile is behavior-preserving by construction. */
1079
+ readonly device?: DeviceProfile;
1080
+ /** The view the input being dispatched landed in — a view id, or `null`
1081
+ * for the surface's own camera. The dispatcher fills it; absent anywhere
1082
+ * else a rule is evaluated. */
1083
+ readonly viewId?: string | null;
1243
1084
  }
1244
- declare class ScopeNode<N extends ScopeNode<N>> {
1245
- readonly parent: N | null;
1246
- readonly descends: boolean;
1247
- readonly tree: ScopeTree;
1248
- private readonly children;
1249
- private activeChild;
1250
- /** `descends`: whether reads made here answer from the active child down —
1251
- * true for a root or a yoke, which chrome reads through; false for a scope,
1252
- * which answers for itself even with a scope nested inside it. */
1253
- constructor(parent: N | null, descends: boolean);
1254
- /** Bump the tree's version and tell every listener in it. */
1255
- changed(): void;
1256
- /** Join the parent's children, newest last; returns the leave. */
1257
- mount(): () => void;
1258
- /** Make this node the one its ancestors resolve through. */
1259
- activate(): void;
1260
- /** The node a lookup made here starts from: down the active children, or the
1261
- * newest where nothing has been activated yet. A yoke with no members yet
1262
- * is passed over — there is no canvas behind it to answer. */
1263
- leaf(): N;
1264
- private newestMember;
1265
- /** Where a read made through this node starts. */
1266
- start(): N;
1267
- /** Whether this node is on the path a read from the root takes. */
1268
- isActive(): boolean;
1269
- /** This node, then each ancestor up to the root. */
1270
- chain(): Generator<N>;
1085
+ /** The live state a `RuleCtx` is assembled from — the chrome context plus
1086
+ * what the active mode allows. */
1087
+ interface BuildRuleCtxArgs {
1088
+ focused: boolean;
1089
+ selection: readonly NodeId[];
1090
+ multiActive: boolean;
1091
+ modifiers: ModifierState;
1092
+ action: {
1093
+ kind: string | null;
1094
+ id: string | null;
1095
+ };
1096
+ hover: NodeId | null;
1097
+ /** See {@link RuleCtx.zoom}. */
1098
+ zoom?: number;
1099
+ mode: string;
1100
+ allowedCapabilities: ReadonlySet<CapabilityTag>;
1101
+ /** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
1102
+ selectionResizable?: boolean;
1103
+ /** Optional — omitted means "no path is being anchor-edited". */
1104
+ editingAnchors?: boolean;
1105
+ /** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
1106
+ device?: DeviceProfile;
1271
1107
  }
1108
+ /**
1109
+ * Capability set to assume when no mode registry is wired.
1110
+ *
1111
+ * "No modes configured" means *everything the default mode permits*, not
1112
+ * *nothing permitted*. The empty set is the wrong default: it makes every
1113
+ * `capability:` rule evaluate false, so a kit default written the
1114
+ * recommended way (capability rules outlive new modes; mode rules have to
1115
+ * be edited) would silently hide its chrome for every consumer that hasn't
1116
+ * opted into modality. `NORMAL.allows` plus the implicit tags is what a
1117
+ * mode-less canvas actually behaves like.
1118
+ */
1119
+ declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
1120
+ /** Gather the current canvas and mode state into the context that action
1121
+ * eligibility and chrome-visibility rules are evaluated against. */
1122
+ declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
1272
1123
 
1273
- /** Which tool is active, plus the stack of tools temporarily held active by a
1274
- * hotkey (space-for-hand and the like). The dispatcher reads this to decide
1275
- * whose bindings are in scope. */
1276
- interface ActiveToolContextValue {
1277
- /** The focused tool's id, or `null` when no tool holds the active slot —
1278
- * a canvas can run on ambient bindings alone. */
1279
- active: string | null;
1280
- hotkeyStack: string[];
1281
- setActive(id: string | null): void;
1282
- pushHotkey(id: string): void;
1283
- /**
1284
- * Disengage a hotkey-held tool. With `id`, removes that tool's own entry
1285
- * wherever it now sits — holds do not have to come up in the order they went
1286
- * down, and popping the top would disengage a tool whose key is still held.
1287
- * With no id, pops the top.
1288
- */
1289
- popHotkey(id?: string): void;
1290
- }
1291
1124
  /**
1292
- * One node of the tool tree. A node that `owns` a tool holds its state; one
1293
- * that does not — a scope inside a yoke — reads and writes its yoke's.
1125
+ * A selector is a conjunction of key/value tests. Multiple keys at the same
1126
+ * level AND together. Each key maps to a selector primitive in the evaluator.
1294
1127
  */
1295
- declare class ToolNode extends ScopeNode<ToolNode> {
1296
- private state;
1297
- private value;
1298
- constructor(parent: ToolNode | null, descends: boolean, owns: boolean, initialActive: string | null);
1299
- /** The node whose tool a read here resolves to. */
1300
- owner(): ToolNode;
1301
- private update;
1302
- /** A stable value per owner and state, for `useSyncExternalStore`. */
1303
- snapshot(): ActiveToolContextValue;
1304
- subscribe: (listener: () => void) => (() => void);
1305
- }
1306
- /** Props for `<ActiveToolContextProvider>`. */
1307
- interface ActiveToolContextProviderProps {
1308
- children: ReactNode;
1309
- /** Seeds the active slot, and wins over the first `useTools` call's
1310
- * `active`. Omitted, the slot starts empty and that call seeds it. */
1311
- initialActive?: string | null;
1128
+ interface Selector {
1129
+ selection?: {
1130
+ is?: number;
1131
+ atLeast?: number;
1132
+ empty?: boolean;
1133
+ };
1134
+ mode?: string | {
1135
+ not: string;
1136
+ } | {
1137
+ in: readonly string[];
1138
+ };
1139
+ capability?: CapabilityTag | readonly CapabilityTag[] | {
1140
+ in: readonly CapabilityTag[];
1141
+ } | {
1142
+ not: CapabilityTag;
1143
+ };
1144
+ gesturing?: boolean;
1145
+ actionIs?: string;
1146
+ modifierHeld?: keyof ModifierState;
1147
+ focused?: boolean;
1148
+ hovering?: boolean;
1149
+ hoveringSelected?: boolean;
1150
+ zoomAtLeast?: number;
1151
+ /** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
1152
+ * mode. Absent flag is treated as `false`. */
1153
+ editingAnchors?: boolean;
1154
+ /** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
1155
+ * (resizable), so `{ resizable: true }` passes for legacy ctx builders
1156
+ * that don't compute it. */
1157
+ resizable?: boolean;
1158
+ /** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
1159
+ * (touch, most styluses). Absent device is treated as `false`. */
1160
+ coarsePointer?: boolean;
1161
+ /** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
1162
+ * device is treated as `true`. */
1163
+ canHover?: boolean;
1312
1164
  }
1313
- /** Provides root active-tool state. Every canvas below has a tool of its own;
1314
- * reads and writes here reach the one last used. */
1315
- declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
1316
- /** The active-tool state in scope. Throws outside a provider; use
1317
- * `useOptionalActiveToolContext` where one is not guaranteed. */
1318
- declare function useActiveToolContext(): ActiveToolContextValue;
1319
1165
  /**
1320
- * Like `useActiveToolContext`, but returns `null` when no
1321
- * `<ActiveToolContextProvider>` is in scope instead of throwing.
1166
+ * Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
1167
+ * over `Selector` leaves. `when` is the escape hatch — its closure is
1168
+ * opaque to introspection and should be avoided when a declarative form
1169
+ * exists. Empty `all` is true; empty `any` is false.
1322
1170
  */
1323
- declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
1171
+ type Rule = Selector | {
1172
+ all: readonly Rule[];
1173
+ } | {
1174
+ any: readonly Rule[];
1175
+ } | {
1176
+ not: Rule;
1177
+ } | {
1178
+ when: (ctx: RuleCtx) => boolean;
1179
+ };
1180
+ /** Constant rules. Kept here so they have a single source. */
1181
+ declare const ALWAYS: Rule;
1182
+ /** A rule that never passes. `any` of nothing is false. */
1183
+ declare const NEVER: Rule;
1324
1184
  /**
1325
- * Mounts an `<ActiveToolContextProvider>` only when no parent provider is in
1326
- * scope; otherwise renders children unwrapped.
1185
+ * Render a {@link Rule} as a short human-readable string, for inspectors and
1186
+ * diagnostics that need to say WHICH rule decided something.
1187
+ *
1188
+ * ```
1189
+ * { capability: 'edits-page' } → capability:edits-page
1190
+ * { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
1191
+ * { not: { selection: { empty: true } } } → not(selection:empty=true)
1192
+ * { when: function hasPath() { … } } → when(hasPath)
1193
+ * ```
1194
+ *
1195
+ * Never throws and always terminates.
1327
1196
  */
1328
- declare function ActiveToolContextProviderIfRoot({ children, }: {
1329
- children: ReactNode;
1330
- }): react.JSX.Element;
1331
-
1197
+ declare function describeRule(rule: Rule): string;
1198
+ /** Evaluate a rule against a context. Pure, and cheap enough to run per
1199
+ * frame for every piece of chrome. */
1200
+ declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
1332
1201
  /**
1333
- * Tuning: what a host changes about an action it did not write — the params
1334
- * every invocation starts from, and bindings added beside the action's own —
1335
- * without restating the descriptor. `ActionsRegistry.tune` holds tunings and
1336
- * applies them to whatever descriptor is live for the id.
1202
+ * Composable eligibility predicate with a fluent surface. `cond(ctx)`
1203
+ * evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
1204
+ * Conditions wrapping new trees.
1205
+ *
1206
+ * **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
1207
+ * `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
1208
+ * disjunction, name the subexpression or use the top-level `or(...)`.
1337
1209
  */
1338
-
1339
- /** An action's params, or a thunk read each time the action asks for them. */
1340
- type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
1341
- /** A binding added for an action: a gesture alone, or a gesture with the
1342
- * options it passes — the {@link BoundGesture} shape, its params typed. */
1343
- type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
1344
- spec: GestureSpec;
1345
- opts: Omit<BindingOpts, 'params'> & {
1346
- params?: ActionParams<P>;
1347
- };
1348
- };
1349
- /** What a host changes about one action. */
1350
- interface ActionTuning<P = Record<string, unknown>> {
1351
- /** Params every invocation starts from. What a binding or a `trigger` call
1352
- * passes is laid over them, so these fill in what it leaves out. A thunk is
1353
- * read on every call, and an ongoing action resolving its `opts.params`
1354
- * mid-gesture sees the value as it is then. */
1355
- params?: ActionParams<P>;
1356
- /** Bindings added after the action's own `defaultBinding`. */
1357
- bindings?: readonly TunedBinding<P>[];
1358
- /** Keys for some of the action's `modifierRoles`, by role id. A binding's
1359
- * own `opts.roles` is laid over these. */
1360
- roles?: RoleRebinds;
1210
+ interface Condition {
1211
+ (ctx: RuleCtx): boolean;
1212
+ readonly rule: Rule;
1213
+ /** `this && other` */
1214
+ and(other: Condition | Rule): Condition;
1215
+ /** `this || other` */
1216
+ or(other: Condition | Rule): Condition;
1217
+ /** `this && !other` */
1218
+ andNot(other: Condition | Rule): Condition;
1219
+ /** `this || !other` */
1220
+ orNot(other: Condition | Rule): Condition;
1361
1221
  }
1362
- /** `action` with each tuning applied in turn, a later one's params over an
1363
- * earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
1364
- declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
1365
1222
 
1366
1223
  /**
1367
- * The entries an action contributes to a bar, menu or palette — one per
1368
- * variant, or the action itself when it declares none.
1224
+ * Whether two rules can never hold at once — no `RuleCtx` passes both.
1225
+ *
1226
+ * Conservative: `true` is a proof, `false` means "may overlap". It reasons
1227
+ * about the selector values themselves (two different `mode`s, an empty
1228
+ * selection against a non-empty one, `focused: true` against `false`) and
1229
+ * through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
1230
+ * everything. The route-conflict check uses it to stay quiet about bindings
1231
+ * that share a tuple but are never eligible together.
1369
1232
  */
1370
-
1371
- /** One renderable entry: an action, optionally with params. */
1372
- interface ActionItem {
1373
- /** `action.id`, or `action.id:variant.key` — unique across a registry. */
1374
- key: string;
1375
- action: Action;
1376
- label: string;
1377
- icon?: IconRef;
1378
- /** The action's tags, then the variant's own. */
1379
- tags: readonly string[];
1380
- /** What to pass to `trigger(action.id, params)`; absent for a plain action. */
1381
- params?: Readonly<Record<string, unknown>>;
1382
- }
1383
- /** Expand `action` into the entries a surface renders. An
1384
- * action without an immediate invoker has none: `trigger` cannot start a
1385
- * drag, so a button for one would do nothing. */
1386
- declare function actionItems(action: Action): ActionItem[];
1387
- /** How a palette titles `item`: a variant reads as its action's
1388
- * label and its own ("New: A4"), unless its label already holds the action's
1389
- * as whole words ("Zoom In", "Reset Zoom"). */
1390
- declare function actionItemTitle(item: ActionItem): string;
1391
-
1233
+ declare function rulesExclusive(a: Rule, b: Rule): boolean;
1392
1234
  /**
1393
- * Project an action's keyboard bindings into the shape a shortcut chip
1394
- * renders from (`formatShortcut` / `formatShortcutParts` in `@weasel-js/ui`).
1235
+ * Whether `rule` can hold while `mode` is active. Only its `mode` and
1236
+ * `capability` tests are settled by the mode; every other test could go either
1237
+ * way, so `false` is a proof and `true` means "may hold".
1395
1238
  */
1239
+ declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
1396
1240
 
1397
- /** One keyboard shortcut, flattened for display: the chip a menu row draws
1398
- * (`MenuShortcut` in `@weasel-js/menu`, `ShortcutInput` in `@weasel-js/gestures`). */
1399
- type ActionShortcut = MenuShortcut;
1241
+ /** The detected half of a profile — everything except the derived scale. */
1242
+ type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
1400
1243
  /**
1401
- * The chip form of a single gesture spec, or `undefined` when the spec has
1402
- * none.
1244
+ * Handle/hit multiplier applied on a coarse pointer.
1403
1245
  *
1404
- * - A spec's `key` may list spellings of one keycap (`['[', '{']` — the
1405
- * shifted bracket reports as `'{'`). The first is the shortcut's name.
1406
- * - A modifier declared `'optional'` matches held or unheld, so it isn't
1407
- * part of what the user has to press.
1246
+ * 8px handle → 14px; 24px rotation distance → 42px. Counting the
1247
+ * surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
1248
+ * minimum-touch-target band. One constant so it is tunable in one place.
1249
+ */
1250
+ declare const COARSE_TARGET_SCALE = 1.75;
1251
+ /**
1252
+ * Fold consumer overrides over detected facts and derive `targetScale`.
1408
1253
  *
1409
- * Non-keyboard specs (drag, wheel, click) have no chip form. Neither does a
1410
- * *required* `ctrl` or `meta`: the display shape carries only `mod`.
1254
+ * `targetScale` is re-derived AFTER the merge, so an override of
1255
+ * `coarsePointer` alone scales the chrome as expected. An explicit
1256
+ * `targetScale` override wins over the derivation.
1411
1257
  */
1412
- declare function keySpecShortcut(spec: GestureSpec): ActionShortcut | undefined;
1258
+ declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
1413
1259
  /**
1414
- * Every keyboard shortcut an action answers to, in declaration order.
1415
- * Specs `keySpecShortcut` has no chip for are skipped, and bindings it leaves
1416
- * identical are emitted once; an action bound only to those returns empty.
1260
+ * Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
1261
+ * absent-means value for `RuleCtx.device`.
1417
1262
  *
1418
- * With `params` — an `ActionItem`'s — only the bindings that pass those same
1419
- * values count, so each variant of `flip` gets its own key.
1263
+ * Derived from `resolveDeviceProfile` rather than hand-written, so it can
1264
+ * never drift from what the function would compute for the same facts. A
1265
+ * mouse-like device is the safe default: it is what the kit has always
1266
+ * assumed, so an absent profile changes nothing for existing consumers.
1420
1267
  */
1421
- declare function actionShortcuts(action: Action, params?: Readonly<Record<string, unknown>>): readonly ActionShortcut[];
1268
+ declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
1422
1269
 
1423
1270
  /**
1424
- * Finding actions by tag: gathering the ones that carry it, and a person's
1425
- * search matching it as it matches a label.
1271
+ * A settings form an action carries: what a surface draws in place of a bare
1272
+ * trigger for an action whose job is to change settings on the selection.
1426
1273
  */
1427
1274
 
1428
- /** The actions carrying `tag`, themselves or on any variant, in
1429
- * the order given. Drags included, which have no entries to show. */
1430
- declare function actionsTagged(actions: readonly Action[], tag: string): Action[];
1431
- /** The entries carrying `tag`: what a bar or a menu built from
1432
- * a tag shows. A variant that does not carry it is left out. */
1433
- declare function taggedActionItems(actions: readonly Action[], tag: string): ActionItem[];
1434
- /** Whether `item` answers a search for `query`, by its palette title or
1435
- * by a tag, and which. */
1436
- declare function matchActionItem(item: ActionItem, query: string): QueryMatch | null;
1437
-
1275
+ /** What a settings form shows for the selection right now. */
1276
+ interface ActionSettingsState {
1277
+ /** The values, nested as the schema's groups nest. */
1278
+ values: Record<string, unknown>;
1279
+ /** The leaf paths where the selected items disagree. */
1280
+ mixed?: readonly string[];
1281
+ }
1438
1282
  /**
1439
- * Palette-side evaluation of `Action.enabled`. The dispatcher calls the
1440
- * predicate raw as a gate; this is the surface that also enforces the
1441
- * contract the predicate's JSDoc states — no throwing, under 4ms in dev.
1283
+ * The settings form an action carries. A bar or a panel draws the action as a
1284
+ * button opening it, a menu as a submenu of its fields, and a palette as a
1285
+ * dialog; each change triggers the action with the params it makes.
1442
1286
  */
1287
+ interface ActionSettings {
1288
+ /** The form. Its `name` titles the popover, submenu or dialog. */
1289
+ schema: PrefGroup;
1290
+ /** The selection's current values, from the deps `enabled` is given. Under
1291
+ * `enabled`'s contract: pure, fast, and it must not throw. */
1292
+ read(deps: ActionDeps): ActionSettingsState;
1293
+ /** The params one field's change triggers the action with, or `null` for
1294
+ * none. Default: `{ [path]: value }`. */
1295
+ params?(path: string, value: unknown): Record<string, unknown> | null;
1296
+ }
1297
+ /** `action`'s settings as they stand under `deps`, or `null` for an action
1298
+ * with no settings. A `read` that throws shows nothing rather than breaking
1299
+ * the surface. */
1300
+ declare function readActionSettings(action: Action, deps: ActionDeps | undefined): ActionSettingsState | null;
1301
+ /** The params a change of the field at `path` to `value` triggers the action with. */
1302
+ declare function actionSettingsParams(settings: ActionSettings, path: string, value: unknown): Record<string, unknown> | null;
1443
1303
 
1304
+ /** What content being routed brings to `Action.enabled`: a drop's or a
1305
+ * paste's items, or a carry's payload with no items. */
1306
+ interface Carried {
1307
+ items: readonly IngestItem[];
1308
+ carry?: {
1309
+ type: string;
1310
+ value: unknown;
1311
+ };
1312
+ }
1444
1313
  /**
1445
- * Result of evaluating an Action's `enabled` predicate.
1314
+ * The kit's symbolic reasons an action might report itself as disabled. The
1315
+ * consumer (palette, menu, etc.) maps these to display strings via its own
1316
+ * label map. An action may also answer with any other string, which is
1317
+ * display text and shown as is — see {@link ActionUnavailable}.
1446
1318
  */
1447
- interface ActionEnabledResult {
1448
- enabled: boolean;
1449
- reason?: ActionDisabledReason;
1319
+ declare const ActionDisabledReason: {
1320
+ readonly SelectionRequired: "selection-required";
1321
+ readonly SceneEmpty: "scene-empty";
1322
+ readonly NotApplicable: "not-applicable";
1323
+ /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
1324
+ readonly PredicateThrew: "predicate-threw";
1325
+ };
1326
+ /** Why an action is unavailable right now. */
1327
+ type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];
1328
+ /** What `enabled` answers with when the action cannot run: one of the
1329
+ * symbolic {@link ActionDisabledReason}s, or a sentence to show as it is. */
1330
+ type ActionUnavailable = ActionDisabledReason | (string & {});
1331
+ /**
1332
+ * What a palette, menu or toolbar renders for an action. No part of this is
1333
+ * read during dispatch.
1334
+ */
1335
+ interface ActionPresentation {
1336
+ label: string;
1337
+ /** The action's icon, by qualified name: `icon('group')` in the package that draws it. */
1338
+ icon?: IconRef;
1339
+ /** Grouping key for palette/menu surfaces. Free-form string; the kit
1340
+ * ships defaults for `'align'` (six edges/centers), `'distribute'`
1341
+ * (two axes), and recommends `'pathfinder'` for boolean ops. */
1342
+ group?: string;
1343
+ /** Words the action is also found by and gathered under. Where `group` is
1344
+ * the one place an action sits, an action carries any number of tags: a
1345
+ * palette search matches them as it matches the label, and
1346
+ * `actionsTagged` / `taggedActionItems` gather every action or entry
1347
+ * carrying one, for a bar or a menu built from a tag. */
1348
+ tags?: readonly string[];
1349
+ /** Display override for the keyboard shortcut. When omitted, palette
1350
+ * surfaces derive a label from `defaultBinding` via their own
1351
+ * formatter. */
1352
+ shortcut?: string;
1353
+ /** The distinct things a parametric action does, one per set of params —
1354
+ * `flip` flips horizontally or vertically, `reorder.forward` moves one step
1355
+ * or all the way. A bar, menu or palette shows one entry per variant and
1356
+ * triggers the action with that variant's `params`. Omitted = the action
1357
+ * is one entry. See `actionItems`. */
1358
+ variants?: readonly ActionVariant[];
1359
+ /** A form of the settings the action changes on the selection. A surface
1360
+ * draws the action as that form instead of a bare trigger, and each change
1361
+ * triggers it with the params `settings.params` makes. Not combined with
1362
+ * `variants`. */
1363
+ settings?: ActionSettings;
1450
1364
  }
1451
1365
  /**
1452
- * Safely evaluate an action's `enabled` predicate. Returns `{enabled: true}`
1453
- * when no predicate is supplied. Catches throws and treats them as disabled
1454
- * with reason `'(predicate threw)'`. In dev mode, warns once per action id
1455
- * when a single call exceeds 4ms (single frame at 240fps — generous; real
1456
- * predicates should be sub-millisecond).
1366
+ * One entry a parametric action contributes to a bar, menu or palette.
1457
1367
  */
1458
- declare function evaluateEnabled(action: Action, deps?: ActionDeps, at?: {
1459
- x: number;
1460
- y: number;
1461
- }): ActionEnabledResult;
1368
+ interface ActionVariant {
1369
+ /** Unique within the action; joined to its id to make the entry's key. */
1370
+ key: string;
1371
+ label: string;
1372
+ /** Falls back to the action's own `icon`. */
1373
+ icon?: IconRef;
1374
+ /** Tags this variant carries besides its action's, which it carries too. */
1375
+ tags?: readonly string[];
1376
+ /** Passed to `trigger`, and matched against each binding's
1377
+ * `opts.params` to find the variant's shortcuts. */
1378
+ params: Readonly<Record<string, unknown>>;
1379
+ }
1462
1380
  /**
1463
- * Safely evaluate an action's `active` predicate. `undefined` for an action
1464
- * with no pressed state; a predicate that throws reads as not active.
1381
+ * What routing consults: the bindings that reach the action, the deps its
1382
+ * invoker reads, the gates it passes, and the cursors it claims.
1465
1383
  */
1466
- declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | undefined;
1384
+ interface ActionDispatch extends BindingSource {
1385
+ /** Names of the deps this action's invoker reads (keys of `DepSchema`).
1386
+ * The dispatcher (and `trigger`, when `requires` is present) resolves
1387
+ * each name against the `DepRegistry` at invocation time and passes the
1388
+ * resulting bag to the invoker. Dev builds warn when the invoker reads a
1389
+ * dep it didn't declare here — see `buildDepsFromRequires`. */
1390
+ requires?: readonly DepName[];
1391
+ /** Pluggable invocation strategy. The gesture dispatcher routes matched
1392
+ * bindings through `invoker.start` / `invoker.run` depending on timing.
1393
+ * All kit-standard descriptors ship one; consumer-supplied actions
1394
+ * without an invoker can still register but won't be triggered. */
1395
+ invoker?: Invoker;
1396
+ /** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey
1397
+ * `BindingScope` instead of the ambient scope — meaning it beats any
1398
+ * active-tool binding on the same input shape. Use for tool-switch
1399
+ * shortcuts and global held-key triggers. Default: ambient. */
1400
+ scope?: 'hotkey';
1401
+ /**
1402
+ * Optional predicate saying whether the action can run right now. Return
1403
+ * `true` when it can, or an `ActionDisabledReason` or a sentence of display
1404
+ * text when it cannot. The
1405
+ * command palette greys out a disabled row, skips it in keyboard nav,
1406
+ * ignores clicks, and shows the reason next to the label. The dispatcher
1407
+ * skips a disabled action too, on a key as on a pointer, and falls through
1408
+ * to the next binding that matches. `ActionsRegistry.trigger` asks too, but
1409
+ * with no point, so a `run` whose answer depends on `at` should still
1410
+ * self-guard.
1411
+ *
1412
+ * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
1413
+ * must not throw. A throw is caught and treated as disabled with reason
1414
+ * `ActionDisabledReason.PredicateThrew`.
1415
+ *
1416
+ * The palette evaluates `enabled` once when opened and does NOT re-evaluate
1417
+ * on selection changes while open.
1418
+ *
1419
+ * `deps` is the same bag passed to `ImmediateInvoker.run`.
1420
+ *
1421
+ * `at` is the world point of the event being routed, when it has one — a
1422
+ * click, a press, a long-press. An action that only applies somewhere
1423
+ * (on a path segment, say) refuses other points here, so the dispatcher
1424
+ * falls through to the next binding and hover prediction shows the
1425
+ * action's `cursor` only where it would act. `trigger` passes the
1426
+ * `worldX`/`worldY` its params carry, so a menu row is run under the
1427
+ * question it was shown enabled for. Palettes and keystrokes pass no
1428
+ * point; treat absent as "anywhere".
1429
+ *
1430
+ * `carried` is what a drop, a paste or a carry being routed brings. For
1431
+ * an OS drag still in flight its items hold a MIME and nothing else, since
1432
+ * a browser withholds contents until the drop. A carry has no items and
1433
+ * hands over its payload whole. An action that takes only some content
1434
+ * refuses the rest here, so a drag it would not take is not accepted and a
1435
+ * paste it would not take stays the page's.
1436
+ */
1437
+ enabled?: (deps?: ActionDeps, at?: {
1438
+ x: number;
1439
+ y: number;
1440
+ }, carried?: Carried) => true | ActionUnavailable;
1441
+ /**
1442
+ * Whether the action reads as held down right now: bold on the selected
1443
+ * text, snapping switched on. A toolbar button draws it as pressed.
1444
+ * Omitted = the action has no pressed state, which is not the same as
1445
+ * one that is off. Never read during dispatch.
1446
+ *
1447
+ * Under the same contract as `enabled`: pure, fast, and it must not
1448
+ * throw. `deps` is the bag `enabled` is given.
1449
+ */
1450
+ active?: (deps?: ActionDeps) => boolean;
1451
+ /**
1452
+ * Show what running the action now would do, and change nothing: the
1453
+ * result an operation would make, drawn over the canvas. A toolbar asks
1454
+ * while its button is hovered or focused, through
1455
+ * `ActionsRegistry.preview`, and calls what this returns when that ends.
1456
+ * Asked only while `enabled` says yes, with the deps and params `run`
1457
+ * would get. Omitted = the action has nothing to show.
1458
+ */
1459
+ preview?: (deps: ActionDeps, params?: Record<string, unknown>) => (() => void) | void;
1460
+ /**
1461
+ * Declarative eligibility rule, evaluated against the current
1462
+ * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =
1463
+ * always eligible.
1464
+ *
1465
+ * Accepts either a fluent `Condition` (callable with `.rule`) or a
1466
+ * raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.
1467
+ *
1468
+ * Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)
1469
+ * over `mode:` rules — capability rules survive new modes being added
1470
+ * that allow the same capability.
1471
+ */
1472
+ eligible?: Rule | Condition;
1473
+ /**
1474
+ * CSS cursor shown while the pointer hovers a spot where this action
1475
+ * would win the drag — or, when the drag's winner declares no cursor,
1476
+ * the click. The hover-cursor pump runs the same match walk a real press
1477
+ * takes, so the hint and the actual target stay in sync. Omitted = no
1478
+ * override (the active tool's `Tool.cursor` shows). Affordance hits are
1479
+ * resolved earlier in the pump via `AffordanceRegion.cursor` and never
1480
+ * reach this field.
1481
+ *
1482
+ * A function is asked each time with the params the winning binding would
1483
+ * start the action with, tunings included, so a cursor can follow them — a
1484
+ * brush ring as wide as the brush. Read it through {@link actionCursor}.
1485
+ * Prediction runs `enabled()` but cannot run the invoker, so an action that
1486
+ * matches yet bails at `start()` (empty handle) may still show its cursor —
1487
+ * keep `enabled` accurate for actions that declare one.
1488
+ */
1489
+ cursor?: CursorSpec | ((params: Record<string, unknown> | undefined) => CursorSpec | undefined);
1490
+ /**
1491
+ * CSS cursor shown while THIS action's ongoing handle is in flight —
1492
+ * grabbing while panning, `move` while dragging a selection, `crosshair`
1493
+ * while pulling a marquee.
1494
+ *
1495
+ * Separate from `cursor` because the two answer different questions:
1496
+ * `cursor` is a prediction ("a drag from here would pan"), this is a state
1497
+ * ("you are panning"). An action can declare either, both, or neither;
1498
+ * with only `cursor` set, the hover hint holds for the duration of the
1499
+ * gesture.
1500
+ */
1501
+ activeCursor?: CursorSpec;
1502
+ /**
1503
+ * The modifiers an ongoing action reads while it runs, by role: Shift
1504
+ * holding a handle's angle, Alt breaking its mirror. The dispatcher resolves
1505
+ * each into `InvocationCtx.roles` on the press, every move, and the release,
1506
+ * so a binding's `opts.roles` or a tuning's `roles` can move one to another
1507
+ * key. Read them with {@link roleReader}. A binding's own `mods` are matched
1508
+ * once, at the press; these are read throughout.
1509
+ */
1510
+ modifierRoles?: ModifierRoles;
1511
+ }
1512
+ /**
1513
+ * Single registered action.
1514
+ */
1515
+ interface Action extends ActionDispatch, ActionPresentation {
1516
+ }
1517
+ /**
1518
+ * The whole of an actions registry the dispatcher reads. It walks the list
1519
+ * once per event to build its actionId lookup and never mutates it, so the
1520
+ * routing side depends on this rather than on `ActionsRegistry`, whose
1521
+ * register / mute / trigger surface is the React provider's.
1522
+ */
1523
+ interface ActionSource {
1524
+ list(): readonly Action[];
1525
+ }
1526
+ /** `action`'s hover cursor for an invocation given `params`: its `cursor`,
1527
+ * asked when it is a function. */
1528
+ declare function actionCursor(action: Pick<Action, 'cursor'> | undefined, params?: Record<string, unknown>): CursorSpec | undefined;
1529
+
1530
+ /** Which canvas hit a menu answers. Every field given must match. */
1531
+ interface MenuWhen {
1532
+ bodyTarget?: BodyTarget | readonly BodyTarget[];
1533
+ bodyKind?: string | readonly string[];
1534
+ }
1535
+ /** A menu as data: a prefs schema drawn as a menu (`resolveSchemaMenu`).
1536
+ * A def without `when` is opened only by id, from chrome. */
1537
+ interface MenuDef {
1538
+ id: string;
1539
+ when?: MenuWhen;
1540
+ menu: PrefGroup;
1541
+ }
1542
+ /** What a right-click landed on. */
1543
+ interface MenuHit {
1544
+ bodyTarget?: BodyTarget;
1545
+ bodyKind?: string;
1546
+ }
1547
+ /** A def with who contributed it: a tool id, or `null` for ambient. */
1548
+ interface OwnedMenuDef {
1549
+ def: MenuDef;
1550
+ owner: string | null;
1551
+ }
1467
1552
 
1553
+ /** What resolving reads: the registered actions, each one's deps, how to run
1554
+ * one, and the values behind the schema's value leaves. */
1555
+ interface MenuSources {
1556
+ actions(): readonly Action[];
1557
+ depsFor(action: Action): ActionDeps | undefined;
1558
+ trigger(id: string, params?: Record<string, unknown>): boolean;
1559
+ /** The value at a leaf's dotted path. Booleans, enums, and numbers are left
1560
+ * out of a menu without both this and `onChange`, colors and strings without
1561
+ * this, and `showIf`/`enableIf` are checked only with it. */
1562
+ values?(path: string): unknown;
1563
+ onChange?(path: string, value: unknown): void;
1564
+ /** The schema an `alias` leaf's `of` is read in. Default: the menu's own. */
1565
+ over?: PrefGroup;
1566
+ }
1468
1567
  /**
1469
- * The dep registry: named "live source" thunks. Actions declare
1470
- * `requires: ['selection']`; the dispatcher calls `registry.get('selection')`
1471
- * at invocation time to build a typed Deps bag.
1568
+ * Draw a prefs schema as menu entries. Action leaves become items from the
1569
+ * registry (one per variant for a parametric action), an `actions` leaf one
1570
+ * item per action in its group, a boolean a checked item, an enum radio
1571
+ * rows, and a number and a string a row edited in place (all through `src.values`/`src.onChange`);
1572
+ * a color shows as a swatch, read-only, and so does a string as a disabled row when it is a readout
1573
+ * or nothing writes it. A group drawn `as` a
1574
+ * `menu`, `page`, `tab`, or `panel` is a submenu, and any other group's rows
1575
+ * sit inline between separators. Paint, field, compound, and app-defined leaves are left out.
1576
+ * `at` is the world point the menu was opened at and reaches `enabled` and
1577
+ * every action's params; `params` go to every action, under its own.
1472
1578
  */
1579
+ declare function resolveSchemaMenu(schema: PrefGroup, src: MenuSources, at?: Point, params?: Record<string, unknown>): MenuEntry[];
1580
+ /** Turn a declared menu into model entries; see {@link resolveSchemaMenu}. */
1581
+ declare function resolveMenu(def: MenuDef, src: MenuSources, at?: Point, params?: Record<string, unknown>): MenuEntry[];
1473
1582
 
1474
- /** Holds the live sources an action's declared dependencies resolve to.
1475
- * Sources are thunks, read at invocation time, so an action never captures
1476
- * stale state. */
1477
- interface DepRegistry {
1478
- register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
1479
- get<K extends DepName>(name: K): DepSchema[K] | undefined;
1480
- /** Hear that what the registry answers may have changed: a source was
1481
- * registered or released anywhere in its tree, or an owner called
1482
- * {@link DepRegistry.changed}. Returns the unsubscribe. A hand-built
1483
- * registry whose answers never change leaves this and `changed` out. */
1484
- subscribe?(listener: () => void): () => void;
1485
- /** Say that a source's value changed. A source is a thunk read on demand,
1486
- * so its owner is the only one who knows; chrome showing an action's
1487
- * `enabled` or `active` state re-reads on this. */
1488
- changed?(): void;
1583
+ /** Ask to open a menu: by id (chrome) or by what was hit (canvas). */
1584
+ interface MenuOpenRequest {
1585
+ menu?: string;
1586
+ hit?: MenuHit;
1587
+ /** Viewport point the menu is placed at, unless `under` is set. */
1588
+ client: Point;
1589
+ /** Viewport rect of the button the menu hangs from: placed below it, or above when below does not fit. */
1590
+ under?: Rect;
1591
+ /** With `under`: opens above the button when it fits there, below when it does not. The canvas HUD menu host places below only and ignores it. */
1592
+ above?: boolean;
1593
+ /** World point, passed to `enabled` and to every item. */
1594
+ world?: Point;
1595
+ params?: Record<string, unknown>;
1596
+ source?: 'pointer' | 'keyboard';
1489
1597
  }
1490
- /** One node of the dep tree: the sources registered at it, over its parent's.
1491
- * Each name holds a stack of sources, newest live, so a displaced source is
1492
- * restored when the one above it leaves. */
1493
- declare class DepNode extends ScopeNode<DepNode> {
1494
- private readonly sources;
1495
- readonly registry: DepRegistry;
1496
- constructor(parent: DepNode | null, descends: boolean);
1598
+ /** The menu on screen. `seq` changes on every open, so a renderer remounts. */
1599
+ interface OpenMenu {
1600
+ seq: number;
1601
+ entries: readonly MenuEntry[];
1602
+ client: Point;
1603
+ under?: Rect;
1604
+ /** `MenuOpenRequest.above`, carried to the renderer. */
1605
+ above?: boolean;
1606
+ world?: Point;
1607
+ source: 'pointer' | 'keyboard';
1497
1608
  }
1498
- /**
1499
- * A stock dep registry with no parent: what `<DepRegistryProvider>` mounts,
1500
- * for a dispatcher driven without React. Sources registered under one name
1501
- * stack, newest live, and releasing one restores the one beneath it.
1502
- */
1503
- declare function createDepRegistry(): DepRegistry;
1609
+ /** Holds the declared menus and the one open menu. */
1610
+ interface MenuHost {
1611
+ /** Declare a menu; `owner` is a tool id, or `null` for always. Returns a release. */
1612
+ define(def: MenuDef, owner?: string | null): () => void;
1613
+ /** Pick, resolve and show. False when nothing matched or nothing resolved. */
1614
+ open(req: MenuOpenRequest): boolean;
1615
+ /** Whether `open(req)` would show anything. Changes no state and notifies no one. */
1616
+ has(req: MenuOpenRequest): boolean;
1617
+ close(): void;
1618
+ current(): OpenMenu | null;
1619
+ subscribe(fn: () => void): () => void;
1620
+ }
1621
+ /** Creates the host that decides which context menu is open, from the menus registered to it. */
1622
+ declare function createMenuHost(src: MenuSources & {
1623
+ activeTool(): string | null;
1624
+ }): MenuHost;
1504
1625
 
1505
1626
  /**
1506
- * Shared dep-bag builder — the single implementation behind both the gesture
1507
- * dispatcher's per-invocation `buildDeps` and `ActionsRegistry.trigger`'s
1508
- * `requires` branch. Reads the action's declared `requires`, resolves each
1509
- * name against the `DepRegistry`, and (dev builds only) wraps the bag in a
1510
- * Proxy that warns on undeclared reads.
1627
+ * The tree both input registries hang on. A provider is a root; every canvas
1628
+ * mounts a scope under the registry in scope; a yoke sits between the two. A
1629
+ * registration lives in the node it was made in, and a lookup starts at a leaf
1630
+ * and walks up, so one canvas never answers for another.
1511
1631
  */
1512
1632
 
1513
- /** Build the deps bag for an action from its declared `requires`. */
1514
- declare function buildDepsFromRequires(action: Action, depRegistry: DepRegistry): ActionDeps;
1633
+ /** What every node of one tree shares: a version and the listeners to it. */
1634
+ interface ScopeTree {
1635
+ version: number;
1636
+ readonly listeners: Emitter;
1637
+ }
1638
+ declare class ScopeNode<N extends ScopeNode<N>> {
1639
+ readonly parent: N | null;
1640
+ readonly descends: boolean;
1641
+ readonly tree: ScopeTree;
1642
+ private readonly children;
1643
+ private activeChild;
1644
+ /** `descends`: whether reads made here answer from the active child down —
1645
+ * true for a root or a yoke, which chrome reads through; false for a scope,
1646
+ * which answers for itself even with a scope nested inside it. */
1647
+ constructor(parent: N | null, descends: boolean);
1648
+ /** Bump the tree's version and tell every listener in it. */
1649
+ changed(): void;
1650
+ /** Join the parent's children, newest last; returns the leave. */
1651
+ mount(): () => void;
1652
+ /** Make this node the one its ancestors resolve through. */
1653
+ activate(): void;
1654
+ /** The node a lookup made here starts from: down the active children, or the
1655
+ * newest where nothing has been activated yet. A yoke with no members yet
1656
+ * is passed over — there is no canvas behind it to answer. */
1657
+ leaf(): N;
1658
+ private newestMember;
1659
+ /** Where a read made through this node starts. */
1660
+ start(): N;
1661
+ /** Whether this node is on the path a read from the root takes. */
1662
+ isActive(): boolean;
1663
+ /** This node, then each ancestor up to the root. */
1664
+ chain(): Generator<N>;
1665
+ }
1515
1666
 
1516
- /** Hotkey-slot trigger key. The slot is engaged while this key is held —
1517
- * hence "hotkey": active as long as the key is hot. Omitted means the tool
1518
- * is not eligible for the hotkey slot.
1519
- *
1520
- * The named keys map to their DOM names (`'space'` is `' '`, `'ctrl'` is
1521
- * `'Control'`). Anything else is a `KeyboardEvent.key` value, matched
1522
- * case-insensitively as every key spec is — so `'o'` holds on O. */
1523
- type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {});
1524
- /** Presentation metadata for tool palettes / menus. Optional on every
1525
- * tool — consumers that render a palette (`<ToolPalette>`) read these
1526
- * fields to display the tool; consumers that don't can ignore them.
1527
- *
1528
- * Note: cursor is NOT here. `Tool.cursor` (inherited from `Contribution`)
1529
- * is already plumbed through `<Canvas>` to `style.cursor` on the host. */
1530
- interface ToolPresentation {
1531
- /** Human-readable label, distinct from the `id`. Falls back to `id`. */
1532
- label?: string;
1533
- /** The tool's icon, by qualified name: `icon('rect')` in the package that draws it. */
1534
- icon?: _weasel_js_icons.IconRef;
1535
- /** Palette grouping key. Tools sharing a group render contiguously
1536
- * with separators between groups. Free-form string; the kit
1537
- * recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
1538
- group?: string;
1539
- /** Display override for the keyboard shortcut. When omitted the palette
1540
- * derives one from `Tool.keybinding` via its own formatter. */
1541
- shortcut?: string;
1542
- /** Keep the tool off palettes — for a tool that is only ever held, like a
1543
- * scrub engaged by its `hotkey`. It stays registered and selectable by
1544
- * id; only the palette leaves it out. */
1545
- hide?: boolean;
1667
+ /** Which tool is active, plus the stack of tools temporarily held active by a
1668
+ * hotkey (space-for-hand and the like). The dispatcher reads this to decide
1669
+ * whose bindings are in scope. */
1670
+ interface ActiveToolContextValue {
1671
+ /** The focused tool's id, or `null` when no tool holds the active slot —
1672
+ * a canvas can run on ambient bindings alone. */
1673
+ active: string | null;
1674
+ hotkeyStack: string[];
1675
+ setActive(id: string | null): void;
1676
+ pushHotkey(id: string): void;
1677
+ /**
1678
+ * Disengage a hotkey-held tool. With `id`, removes that tool's own entry
1679
+ * wherever it now sits — holds do not have to come up in the order they went
1680
+ * down, and popping the top would disengage a tool whose key is still held.
1681
+ * With no id, pops the top.
1682
+ */
1683
+ popHotkey(id?: string): void;
1546
1684
  }
1547
1685
  /**
1548
- * When an entry's bindings are live. A set, not one value: the hand tool is
1549
- * palette-selectable AND engaged by holding space, and both hold at once.
1686
+ * One node of the tool tree. A node that `owns` a tool holds its state; one
1687
+ * that does not — a scope inside a yoke — reads and writes its yoke's.
1550
1688
  */
1551
- interface Eligibility {
1552
- /** Selectable as the focused entry — exclusive, one at a time. */
1553
- focus?: boolean;
1554
- /** Also live while this key is held. */
1555
- offhand?: HotkeyTrigger;
1556
- /** Live regardless of what is focused. */
1557
- always?: boolean;
1558
- /** Live only for input this entry's own affordances produced. */
1559
- claimed?: boolean;
1560
- /** Modality filter, applied wherever it would otherwise be live. */
1561
- capabilities?: CapabilityTag[];
1689
+ declare class ToolNode extends ScopeNode<ToolNode> {
1690
+ private state;
1691
+ private value;
1692
+ constructor(parent: ToolNode | null, descends: boolean, owns: boolean, initialActive: string | null);
1693
+ /** The node whose tool a read here resolves to. */
1694
+ owner(): ToolNode;
1695
+ private update;
1696
+ /** A stable value per owner and state, for `useSyncExternalStore`. */
1697
+ snapshot(): ActiveToolContextValue;
1698
+ subscribe: (listener: () => void) => (() => void);
1562
1699
  }
1700
+ /** Props for `<ActiveToolContextProvider>`. */
1701
+ interface ActiveToolContextProviderProps {
1702
+ children: ReactNode;
1703
+ /** Seeds the active slot, and wins over the first `useTools` call's
1704
+ * `active`. Omitted, the slot starts empty and that call seeds it. */
1705
+ initialActive?: string | null;
1706
+ }
1707
+ /** Provides root active-tool state. Every canvas below has a tool of its own;
1708
+ * reads and writes here reach the one last used. */
1709
+ declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
1710
+ /** The active-tool state in scope. Throws outside a provider; use
1711
+ * `useOptionalActiveToolContext` where one is not guaranteed. */
1712
+ declare function useActiveToolContext(): ActiveToolContextValue;
1563
1713
  /**
1564
- * Where an entry's overlay sits in the layer stack, relative to the
1565
- * selection chrome. `'top'` is the default and renders above everything;
1566
- * the other two exist for chrome that belongs under the selection handles
1567
- * (a snap-target highlight, say). With no selection overlay in the stack,
1568
- * all three collapse to `'top'`.
1714
+ * Like `useActiveToolContext`, but returns `null` when no
1715
+ * `<ActiveToolContextProvider>` is in scope instead of throwing.
1569
1716
  */
1570
- type OverlayPosition = 'top' | 'before-selection' | 'after-selection';
1571
- /** Dep sources an entry provides while it is installed, by dep name. */
1572
- type ContributionDeps = {
1573
- [K in DepName]?: () => DepSchema[K];
1574
- };
1717
+ declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
1575
1718
  /**
1576
- * What routing reads off an entry: when its bindings are live, which they
1577
- * are, the actions they name, and the deps those actions read.
1578
- */
1579
- interface ContributionRouting {
1580
- id: string;
1581
- eligibility: Eligibility;
1582
- bindings?: GestureBinding[];
1583
- actions?: Action[];
1584
- /** Menus this entry declares: always, for an ambient entry; while active, for a tool. */
1585
- menus?: MenuDef[];
1586
- /** Dep sources this entry provides while it is installed. A host installs
1587
- * them for as long as the entry is registered, whatever its eligibility. */
1588
- deps?: ContributionDeps;
1589
- }
1719
+ * Mounts an `<ActiveToolContextProvider>` only when no parent provider is in
1720
+ * scope; otherwise renders children unwrapped.
1721
+ */
1722
+ declare function ActiveToolContextProviderIfRoot({ children, }: {
1723
+ children: ReactNode;
1724
+ }): react.JSX.Element;
1725
+
1590
1726
  /**
1591
- * What an entry draws and how a palette shows it. None of it reaches the
1592
- * dispatcher.
1727
+ * Tuning: what a host changes about an action it did not write — the params
1728
+ * every invocation starts from, and bindings added beside the action's own —
1729
+ * without restating the descriptor. `ActionsRegistry.tune` holds tunings and
1730
+ * applies them to whatever descriptor is live for the id.
1593
1731
  */
1594
- interface ContributionChrome<TOverlay = KernelOverlay> {
1595
- /** One layer, or several composed in the given order.
1596
- *
1597
- * Routing never reads a layer — it collects the live ones and hands them
1598
- * back in scope order. `TOverlay` is what the consuming kernel draws;
1599
- * `@weasel-js/core` binds it to `RenderLayer<unknown>`. */
1600
- overlay?: TOverlay | TOverlay[];
1601
- /** Defaults to `'top'`. Applies to every layer in `overlay`. */
1602
- overlayPosition?: OverlayPosition;
1603
- presentation?: ToolPresentation;
1732
+
1733
+ /** An action's params, or a thunk read each time the action asks for them. */
1734
+ type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
1735
+ /** A binding added for an action: a gesture alone, or a gesture with the
1736
+ * options it passes — the {@link BoundGesture} shape, its params typed. */
1737
+ type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
1738
+ spec: GestureSpec;
1739
+ opts: Omit<BindingOpts, 'params'> & {
1740
+ params?: ActionParams<P>;
1741
+ };
1742
+ };
1743
+ /** What a host changes about one action. */
1744
+ interface ActionTuning<P = Record<string, unknown>> {
1745
+ /** Params every invocation starts from. What a binding or a `trigger` call
1746
+ * passes is laid over them, so these fill in what it leaves out. A thunk is
1747
+ * read on every call, and an ongoing action resolving its `opts.params`
1748
+ * mid-gesture sees the value as it is then. */
1749
+ params?: ActionParams<P>;
1750
+ /** Bindings added after the action's own `defaultBinding`. */
1751
+ bindings?: readonly TunedBinding<P>[];
1752
+ /** Keys for some of the action's `modifierRoles`, by role id. A binding's
1753
+ * own `opts.roles` is laid over these. */
1754
+ roles?: RoleRebinds;
1604
1755
  }
1756
+ /** `action` with each tuning applied in turn, a later one's params over an
1757
+ * earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
1758
+ declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
1759
+
1605
1760
  /**
1606
- * A registry entry: what it contributes, and when it is eligible. Every role
1607
- * is optional and independent — an entry that only routes input declares only
1608
- * `bindings` and `actions`.
1761
+ * The entries an action contributes to a bar, menu or palette — one per
1762
+ * variant, or the action itself when it declares none.
1609
1763
  */
1610
- interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, ContributionChrome<TOverlay> {
1611
- /** Reflection escape hatch — the authored form, when there was one. */
1612
- def?: unknown;
1764
+
1765
+ /** One renderable entry: an action, optionally with params. */
1766
+ interface ActionItem {
1767
+ /** `action.id`, or `action.id:variant.key` — unique across a registry. */
1768
+ key: string;
1769
+ action: Action;
1770
+ label: string;
1771
+ icon?: IconRef;
1772
+ /** The action's tags, then the variant's own. */
1773
+ tags: readonly string[];
1774
+ /** What to pass to `trigger(action.id, params)`; absent for a plain action. */
1775
+ params?: Readonly<Record<string, unknown>>;
1613
1776
  }
1777
+ /** Expand `action` into the entries a surface renders. An
1778
+ * action without an immediate invoker has none: `trigger` cannot start a
1779
+ * drag, so a button for one would do nothing. */
1780
+ declare function actionItems(action: Action): ActionItem[];
1781
+ /** How a palette titles `item`: a variant reads as its action's
1782
+ * label and its own ("New: A4"), unless its label already holds the action's
1783
+ * as whole words ("Zoom In", "Reset Zoom"). */
1784
+ declare function actionItemTitle(item: ActionItem): string;
1614
1785
 
1615
1786
  /**
1616
- * Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
1617
- * the actions-layer binding-scope / matchBest logic on top.
1787
+ * Project an action's keyboard bindings into the shape a shortcut chip
1788
+ * renders from (`formatShortcut` / `formatShortcutParts` in `@weasel-js/ui`).
1618
1789
  */
1619
1790
 
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
- }
1791
+ /** One keyboard shortcut, flattened for display: the chip a menu row draws
1792
+ * (`MenuShortcut` in `@weasel-js/menu`, `ShortcutInput` in `@weasel-js/gestures`). */
1793
+ type ActionShortcut = MenuShortcut;
1641
1794
  /**
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.
1795
+ * The chip form of a single gesture spec, or `undefined` when the spec has
1796
+ * none.
1647
1797
  *
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.
1798
+ * - A spec's `key` may list spellings of one keycap (`['[', '{']` — the
1799
+ * shifted bracket reports as `'{'`). The first is the shortcut's name.
1800
+ * - A modifier declared `'optional'` matches held or unheld, so it isn't
1801
+ * part of what the user has to press.
1802
+ *
1803
+ * Non-keyboard specs (drag, wheel, click) have no chip form. Neither does a
1804
+ * *required* `ctrl` or `meta`: the display shape carries only `mod`.
1650
1805
  */
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;
1806
+ declare function keySpecShortcut(spec: GestureSpec): ActionShortcut | undefined;
1654
1807
  /**
1655
- * Find every binding that matches the event, sorted by precedence
1656
- * (best → worst). Ordering rules:
1808
+ * Every keyboard shortcut an action answers to, in declaration order.
1809
+ * Specs `keySpecShortcut` has no chip for are skipped, and bindings it leaves
1810
+ * identical are emitted once; an action bound only to those returns empty.
1657
1811
  *
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.
1812
+ * With `params` — an `ActionItem`'s — only the bindings that pass those same
1813
+ * values count, so each variant of `flip` gets its own key.
1662
1814
  */
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
- }
1815
+ declare function actionShortcuts(action: Action, params?: Readonly<Record<string, unknown>>): readonly ActionShortcut[];
1816
+
1673
1817
  /**
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.
1818
+ * Finding actions by tag: gathering the ones that carry it, and a person's
1819
+ * search matching it as it matches a label.
1677
1820
  */
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;
1821
+
1822
+ /** The actions carrying `tag`, themselves or on any variant, in
1823
+ * the order given. Drags included, which have no entries to show. */
1824
+ declare function actionsTagged(actions: readonly Action[], tag: string): Action[];
1825
+ /** The entries carrying `tag`: what a bar or a menu built from
1826
+ * a tag shows. A variant that does not carry it is left out. */
1827
+ declare function taggedActionItems(actions: readonly Action[], tag: string): ActionItem[];
1828
+ /** Whether `item` answers a search for `query`, by its palette title or
1829
+ * by a tag, and which. */
1830
+ declare function matchActionItem(item: ActionItem, query: string): QueryMatch | null;
1682
1831
 
1683
1832
  /**
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.
1833
+ * Palette-side evaluation of `Action.enabled`. The dispatcher calls the
1834
+ * predicate raw as a gate; this is the surface that also enforces the
1835
+ * contract the predicate's JSDoc states — no throwing, under 4ms in dev.
1691
1836
  */
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';
1837
+
1838
+ /**
1839
+ * Result of evaluating an Action's `enabled` predicate.
1840
+ */
1841
+ interface ActionEnabledResult {
1842
+ enabled: boolean;
1843
+ reason?: ActionUnavailable;
1765
1844
  }
1766
- /** The one above names the routed view; this one does not. */
1767
- | {
1768
- step: 'view';
1845
+ /**
1846
+ * Safely evaluate an action's `enabled` predicate. Returns `{enabled: true}`
1847
+ * when no predicate is supplied. Catches throws and treats them as disabled
1848
+ * with reason `'(predicate threw)'`. In dev mode, warns once per action id
1849
+ * when a single call exceeds 4ms (single frame at 240fps — generous; real
1850
+ * predicates should be sub-millisecond).
1851
+ */
1852
+ declare function evaluateEnabled(action: Action, deps?: ActionDeps, at?: {
1853
+ x: number;
1854
+ y: number;
1855
+ }): ActionEnabledResult;
1856
+ /**
1857
+ * Safely evaluate an action's `active` predicate. `undefined` for an action
1858
+ * with no pressed state; a predicate that throws reads as not active.
1859
+ */
1860
+ declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | undefined;
1861
+
1862
+ /**
1863
+ * The dep registry: named "live source" thunks. Actions declare
1864
+ * `requires: ['selection']`; the dispatcher calls `registry.get('selection')`
1865
+ * at invocation time to build a typed Deps bag.
1866
+ */
1867
+
1868
+ /** Holds the live sources an action's declared dependencies resolve to.
1869
+ * Sources are thunks, read at invocation time, so an action never captures
1870
+ * stale state. */
1871
+ interface DepRegistry {
1872
+ /** Adds a live source for `name` over any already there. `below` answers
1873
+ * what the registry would without this source, so one can handle part of
1874
+ * a dep and hand the rest to the default it displaced. */
1875
+ register<K extends DepName>(name: K, source: DepSource<K>): () => void;
1876
+ get<K extends DepName>(name: K): DepSchema[K] | undefined;
1877
+ /** Hear that what the registry answers may have changed: a source was
1878
+ * registered or released anywhere in its tree, or an owner called
1879
+ * {@link DepRegistry.changed}. Returns the unsubscribe. A hand-built
1880
+ * registry whose answers never change leaves this and `changed` out. */
1881
+ subscribe?(listener: () => void): () => void;
1882
+ /** Say that a source's value changed. A source is a thunk read on demand,
1883
+ * so its owner is the only one who knows; chrome showing an action's
1884
+ * `enabled` or `active` state re-reads on this. */
1885
+ changed?(): void;
1886
+ /** Every name a `get` here would answer, sorted, with where its live
1887
+ * source sits. For inspection; a hand-built registry may leave it out. */
1888
+ list?(): readonly DepListing[];
1889
+ }
1890
+ /** A live source for dep `K`: a thunk read at invocation time. `below` reads
1891
+ * the source this one displaced, under it in its scope or in a scope above. */
1892
+ type DepSource<K extends DepName> = (below: () => DepSchema[K] | undefined) => DepSchema[K];
1893
+ /** One name a dep registry answers, as {@link DepRegistry.list} reports it. */
1894
+ interface DepListing {
1895
+ name: string;
1896
+ /** How many scopes above the asking one the live source was registered: 0 for its own. */
1897
+ depth: number;
1898
+ /** How many sources that scope holds under the name, the live one included. */
1899
+ stacked: number;
1769
1900
  }
1770
- /** The one above sits in a higher scope tier. */
1771
- | {
1772
- step: 'tier';
1901
+ /** One node of the dep tree: the sources registered at it, over its parent's.
1902
+ * Each name holds a stack of sources, newest live, so a displaced source is
1903
+ * restored when the one above it leaves. */
1904
+ declare class DepNode extends ScopeNode<DepNode> {
1905
+ private readonly sources;
1906
+ readonly registry: DepRegistry;
1907
+ constructor(parent: DepNode | null, descends: boolean);
1773
1908
  }
1774
- /** Same tier; the one above is more specific in `part`. */
1775
- | {
1776
- step: 'specificity';
1777
- part: SpecificityPart;
1909
+ /**
1910
+ * A stock dep registry with no parent: what `<DepRegistryProvider>` mounts,
1911
+ * for a dispatcher driven without React. Sources registered under one name
1912
+ * stack, newest live, and releasing one restores the one beneath it.
1913
+ */
1914
+ declare function createDepRegistry(): DepRegistry;
1915
+
1916
+ /**
1917
+ * Shared dep-bag builder — the single implementation behind both the gesture
1918
+ * dispatcher's per-invocation `buildDeps` and `ActionsRegistry.trigger`'s
1919
+ * `requires` branch. Reads the action's declared `requires`, resolves each
1920
+ * name against the `DepRegistry`, and (dev builds only) wraps the bag in a
1921
+ * Proxy that warns on undeclared reads.
1922
+ */
1923
+
1924
+ /** Build the deps bag for an action from its declared `requires`. */
1925
+ declare function buildDepsFromRequires(action: Action, depRegistry: DepRegistry): ActionDeps;
1926
+
1927
+ /**
1928
+ * Binding overrides: a host's replacements for bindings it did not write,
1929
+ * keyed by binding id. An override replaces or removes one binding, on an
1930
+ * action or on a contribution alike; adding a key beside the defaults is
1931
+ * `tune`'s job.
1932
+ */
1933
+
1934
+ /** Overrides by binding id: a route string or spec to bind instead, or `null` to unbind. */
1935
+ type BindingOverrides = Readonly<Record<string, BindingOverrideInput>>;
1936
+ /** Overrides read into specs, one per binding id. */
1937
+ type ResolvedBindingOverrides = ReadonlyMap<string, GestureSpec | null>;
1938
+ /** A binding's id: its declared `opts.id`, else its owner and its position
1939
+ * there — `<entry>#<n>` for an entry's binding (`ownerId`), `<action>@<n>` for
1940
+ * an action's own (`ownerId` null), so an entry and an action of one id never
1941
+ * share a derived id. */
1942
+ declare function bindingId(b: GestureBinding, ownerId: string | null, index: number): string;
1943
+ /** Merge override maps into one, a later map winning per id. A route string
1944
+ * that does not parse is skipped with a warning, leaving the binding as it was. */
1945
+ declare function resolveBindingOverrides(maps: readonly BindingOverrides[]): ResolvedBindingOverrides;
1946
+ /** The bindings `ownerId` declares, each under its override, the unbound ones
1947
+ * left out: what dispatch routes by. A `null` owner is an action's own. */
1948
+ declare function overriddenBindings(bindings: readonly GestureBinding[], ownerId: string | null, overrides?: ResolvedBindingOverrides): GestureBinding[];
1949
+
1950
+ /** Hotkey-slot trigger key. The slot is engaged while this key is held —
1951
+ * hence "hotkey": active as long as the key is hot. Omitted means the tool
1952
+ * is not eligible for the hotkey slot.
1953
+ *
1954
+ * The named keys map to their DOM names (`'space'` is `' '`, `'ctrl'` is
1955
+ * `'Control'`). Anything else is a `KeyboardEvent.key` value, matched
1956
+ * case-insensitively as every key spec is — so `'o'` holds on O. */
1957
+ type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {});
1958
+ /** Presentation metadata for tool palettes / menus. Optional on every
1959
+ * tool — consumers that render a palette (`<ToolPalette>`) read these
1960
+ * fields to display the tool; consumers that don't can ignore them.
1961
+ *
1962
+ * Note: cursor is NOT here. `Tool.cursor` (inherited from `Contribution`)
1963
+ * is already plumbed through `<Canvas>` to `style.cursor` on the host. */
1964
+ interface ToolPresentation {
1965
+ /** Human-readable label, distinct from the `id`. Falls back to `id`. */
1966
+ label?: string;
1967
+ /** The tool's icon, by qualified name: `icon('rect')` in the package that draws it. */
1968
+ icon?: _weasel_js_icons.IconRef;
1969
+ /** Palette grouping key. Tools sharing a group render contiguously
1970
+ * with separators between groups. Free-form string; the kit
1971
+ * recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
1972
+ group?: string;
1973
+ /** Display override for the keyboard shortcut. When omitted the palette
1974
+ * derives one from `Tool.keybinding` via its own formatter. */
1975
+ shortcut?: string;
1976
+ /** Keep the tool off palettes — for a tool that is only ever held, like a
1977
+ * scrub engaged by its `hotkey`. It stays registered and selectable by
1978
+ * id; only the palette leaves it out. */
1979
+ hide?: boolean;
1778
1980
  }
1779
- /** Tied; the one above is gated by an `eligible` rule that holds. */
1780
- | {
1781
- step: 'context';
1981
+ /**
1982
+ * When an entry's bindings are live. A set, not one value: the hand tool is
1983
+ * palette-selectable AND engaged by holding space, and both hold at once.
1984
+ */
1985
+ interface Eligibility {
1986
+ /** Selectable as the focused entry — exclusive, one at a time. */
1987
+ focus?: boolean;
1988
+ /** Also live while this key is held. */
1989
+ offhand?: HotkeyTrigger;
1990
+ /** Live regardless of what is focused. */
1991
+ always?: boolean;
1992
+ /** Live only for input this entry's own affordances produced. */
1993
+ claimed?: boolean;
1994
+ /** Modality filter, applied wherever it would otherwise be live. */
1995
+ capabilities?: CapabilityTag[];
1996
+ /** A keymap: live only while it is the innermost one open, at the hotkey
1997
+ * tier. `unbound` overrides the `gestures.keymapUnbound` kit setting for
1998
+ * this keymap. Open one with a leader (`keymapContribution`). */
1999
+ keymap?: true | {
2000
+ unbound?: KeymapUnbound;
2001
+ };
1782
2002
  }
1783
- /** Tied on everything; registration order. */
1784
- | {
1785
- step: 'order';
2003
+ /**
2004
+ * Where an entry's overlay sits in the layer stack, relative to the
2005
+ * selection chrome. `'top'` is the default and renders above everything;
2006
+ * the other two exist for chrome that belongs under the selection handles
2007
+ * (a snap-target highlight, say). With no selection overlay in the stack,
2008
+ * all three collapse to `'top'`.
2009
+ */
2010
+ type OverlayPosition = 'top' | 'before-selection' | 'after-selection';
2011
+ /** Dep sources an entry provides while it is installed, by dep name. */
2012
+ type ContributionDeps = {
2013
+ [K in DepName]?: () => DepSchema[K];
1786
2014
  };
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';
2015
+ /**
2016
+ * What routing reads off an entry: when its bindings are live, which they
2017
+ * are, the actions they name, and the deps those actions read.
2018
+ */
2019
+ interface ContributionRouting {
2020
+ id: string;
2021
+ eligibility: Eligibility;
2022
+ bindings?: GestureBinding[];
2023
+ actions?: Action[];
2024
+ /** Menus this entry declares: always, for an ambient entry; while active, for a tool. */
2025
+ menus?: MenuDef[];
2026
+ /** Dep sources this entry provides while it is installed. A host installs
2027
+ * them for as long as the entry is registered, whatever its eligibility. */
2028
+ deps?: ContributionDeps;
1814
2029
  }
1815
- /** Below the winner; never asked. */
1816
- | {
1817
- kind: 'not-asked';
2030
+ /**
2031
+ * What an entry draws and how a palette shows it. None of it reaches the
2032
+ * dispatcher.
2033
+ */
2034
+ interface ContributionChrome<TOverlay = KernelOverlay> {
2035
+ /** One layer, or several composed in the given order.
2036
+ *
2037
+ * Routing never reads a layer — it collects the live ones and hands them
2038
+ * back in scope order. `TOverlay` is what the consuming kernel draws;
2039
+ * `@weasel-js/core` binds it to `RenderLayer<unknown>`. */
2040
+ overlay?: TOverlay | TOverlay[];
2041
+ /** Defaults to `'top'`. Applies to every layer in `overlay`. */
2042
+ overlayPosition?: OverlayPosition;
2043
+ presentation?: ToolPresentation;
1818
2044
  }
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;
2045
+ /**
2046
+ * A registry entry: what it contributes, and when it is eligible. Every role
2047
+ * is optional and independent — an entry that only routes input declares only
2048
+ * `bindings` and `actions`.
2049
+ */
2050
+ interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, ContributionChrome<TOverlay> {
2051
+ /** Reflection escape hatch — the authored form, when there was one. */
2052
+ def?: unknown;
1829
2053
  }
1830
2054
 
1831
2055
  /**
@@ -1866,6 +2090,30 @@ interface DispatcherContext {
1866
2090
  * reads as the root.
1867
2091
  */
1868
2092
  viewId?: string | null;
2093
+ /**
2094
+ * For a key from the page: whether this surface holds the keys, and whether
2095
+ * the pointer is over it. Bindings with `opts.over` are live only while it
2096
+ * is, the rest only while it holds them. Absent, every binding is live.
2097
+ */
2098
+ keyReach?: KeyReach;
2099
+ /** The keymaps open on this surface. Absent: none can open. */
2100
+ keymaps?: KeymapStack;
2101
+ /** Which bindings this pass of an input reaches while a keymap is open:
2102
+ * `'only'` the innermost keymap's, `'none'` everything but it. Absent: both. */
2103
+ keymapPass?: 'only' | 'none';
2104
+ /** What a key no open keymap binds does, unless the keymap says: the
2105
+ * `gestures.keymapUnbound` kit setting. Default `'modal'`. */
2106
+ leaderUnbound?: KeymapUnbound;
2107
+ /** The binding overrides in force, read per input, for contribution bindings;
2108
+ * the registry lays them over actions' own. Absent overrides nothing. */
2109
+ bindingOverrides?: () => ResolvedBindingOverrides;
2110
+ /** Where the dispatcher records how it resolved each input. Absent records nothing. */
2111
+ trace?: DispatchTrace;
2112
+ }
2113
+ /** Which of a surface's key bindings a key from the page reaches. */
2114
+ interface KeyReach {
2115
+ held: boolean;
2116
+ over: boolean;
1869
2117
  }
1870
2118
  /**
1871
2119
  * Handle returned by `Dispatcher.beginUiOngoing()` for driving an
@@ -2073,31 +2321,6 @@ interface Dispatcher {
2073
2321
  beginUiOngoing(actionId: string, deps: ActionDeps, params?: Record<string, unknown>): UiOngoingControl | null;
2074
2322
  }
2075
2323
 
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
2324
  /**
2102
2325
  * The slot an in-flight ongoing handle is keyed by:
2103
2326
  * - `key-held`: `key-held-<code>` (`key-held-Space`), so two keys that share a
@@ -2188,6 +2411,12 @@ interface ActionsRegistry {
2188
2411
  * passes `given`: every tuning's params, `given` laid over them. What a
2189
2412
  * tool's chrome reads to show what the action it binds will do. */
2190
2413
  paramsOf(id: string, given?: Record<string, unknown>): Record<string, unknown>;
2414
+ /** Replace or remove bindings by id, here and in scopes below. Holders
2415
+ * merge: an inner scope's beats an outer one's, and a later one beats an
2416
+ * earlier one in the same scope. Returns a release. See {@link BindingOverrides}. */
2417
+ overrideBindings(overrides: BindingOverrides): () => void;
2418
+ /** Every override in force from this scope, read into specs. */
2419
+ bindingOverrides(): ResolvedBindingOverrides;
2191
2420
  list(): readonly Action[];
2192
2421
  /** Fire an immediate-invoker action by id. The optional `params` arg is
2193
2422
  * forwarded to `ImmediateInvoker.run` as its second argument — use it for
@@ -2269,8 +2498,8 @@ interface ToolOffhandBindingSpec {
2269
2498
  declare function offhandKeyFor(trigger: HotkeyTrigger): string;
2270
2499
  /** Build the per-tool `BoundGesture` list for the consolidated action's
2271
2500
  * `defaultBinding`. Each entry pairs a `key-held` spec with
2272
- * `opts.params.toolId` so the invoker's `start` can pick out which tool
2273
- * to engage.
2501
+ * `opts.params.toolId`, so the invoker's `start` can pick out which tool
2502
+ * to engage, and is overridden as `tool.offhand.<toolId>`.
2274
2503
  *
2275
2504
  * An offhand tool engages only between gestures. Swapping the tool out
2276
2505
  * mid-drag would cancel the drag, and the key is free for the gesture's own
@@ -2430,6 +2659,7 @@ declare function createLongPressStore(): LongPressStore;
2430
2659
  * The `gestures` kit settings area. Declared here rather than in core because
2431
2660
  * the dispatcher reads it, and routing sits below core.
2432
2661
  */
2662
+
2433
2663
  /** Pointer gesture timings and travel; each falls back to its constant in `@weasel-js/gestures`. */
2434
2664
  interface GestureKitSettings {
2435
2665
  /** Longest gap between the clicks of a double click, in ms. */
@@ -2442,6 +2672,9 @@ interface GestureKitSettings {
2442
2672
  tapPx: number;
2443
2673
  /** Travel that makes a press a drag, in CSS px. */
2444
2674
  dragPx: number;
2675
+ /** What a key an open keymap does not bind does: `'modal'` swallows it,
2676
+ * `'cascade'` closes the keymap and lets it through. */
2677
+ keymapUnbound: KeymapUnbound;
2445
2678
  }
2446
2679
  declare module '@weasel-js/react' {
2447
2680
  interface KitSettings {
@@ -2569,6 +2802,8 @@ interface EligibilityState {
2569
2802
  engagedIds?: ReadonlySet<string>;
2570
2803
  /** Whether the active mode allows these capability tags. Omitted → allow. */
2571
2804
  allows?: (tags: readonly CapabilityTag[]) => boolean;
2805
+ /** The innermost open keymap's id: the one keymap entry that is live. */
2806
+ openKeymap?: string | null;
2572
2807
  }
2573
2808
  /**
2574
2809
  * The scope tier an entry's bindings are live at, or null when none are.
@@ -2584,7 +2819,7 @@ declare function liveScope(id: string, eligibility: Eligibility, state: Eligibil
2584
2819
  * breaks ties by declaration order, so the caller's entry order decides which
2585
2820
  * of two same-specificity bindings in one tier wins.
2586
2821
  */
2587
- declare function scopeBindings(entries: Iterable<Contribution<unknown>>, state: EligibilityState): ScopedBinding[];
2822
+ declare function scopeBindings(entries: Iterable<Contribution<unknown>>, state: EligibilityState, overrides?: ResolvedBindingOverrides): ScopedBinding[];
2588
2823
  /** What decides which entries are live, and in what order they are walked. */
2589
2824
  interface AssemblyInputs {
2590
2825
  focusedId: string | null;
@@ -2593,6 +2828,8 @@ interface AssemblyInputs {
2593
2828
  /** The capability tags the active mode allows. Omitted: no mode system, so
2594
2829
  * every entry is allowed. */
2595
2830
  allowedCapabilities?: ReadonlySet<CapabilityTag>;
2831
+ /** The innermost open keymap's id, if one is open. */
2832
+ openKeymap?: string | null;
2596
2833
  }
2597
2834
  /**
2598
2835
  * The entry set as routing walks it, with the state that tiers it. The
@@ -2661,6 +2898,126 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
2661
2898
  */
2662
2899
  declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
2663
2900
 
2901
+ /**
2902
+ * A keymap and the leader that opens it, from one definition. The keymap is an
2903
+ * entry whose bindings are live only while it is open; the leader is an
2904
+ * action on the hotkey tier, so it outranks what the active tool binds to the
2905
+ * same key, and it opens the keymap mid-gesture as well as between them.
2906
+ */
2907
+
2908
+ /** What {@link keymapContribution} builds a keymap and its leader from. */
2909
+ interface KeymapDef<TOverlay = never> {
2910
+ /** The keymap entry's id. Its leader action is `keymap.<id>`. */
2911
+ id: string;
2912
+ /** What the hint and a shortcut chip call it: `Loupe ▸ 2`. */
2913
+ label: string;
2914
+ /** The keystroke, or keystrokes, that open it. A `key-held` spec is read as
2915
+ * its `key` form: holding the key is what holds the keymap open. */
2916
+ leader: GestureSpec | readonly GestureSpec[];
2917
+ /** The keymap's own bindings. Escape is not one: it always closes the keymap. */
2918
+ bindings: readonly GestureBinding[];
2919
+ /** Actions the bindings name that are not registered elsewhere. */
2920
+ actions?: readonly Action[];
2921
+ /** Overrides the `gestures.keymapUnbound` kit setting for this keymap. */
2922
+ unbound?: KeymapUnbound;
2923
+ /** Drawn while the keymap is open. */
2924
+ overlay?: TOverlay | TOverlay[];
2925
+ overlayPosition?: OverlayPosition;
2926
+ }
2927
+ /** The leader action's id for keymap `id`. */
2928
+ declare const keymapLeaderId: (id: string) => string;
2929
+ /** The binding id of a keymap's leader — what a binding override rebinds it by.
2930
+ * A second leader key is `<id>#1`, and so on. */
2931
+ declare const keymapLeaderBindingId: (id: string, index?: number) => string;
2932
+ /** `[keymap entry, leader entry]`. Triggered without a key, the leader opens
2933
+ * its keymap tapped; `trigger('keymap.<id>', { hold: true })` holds it open
2934
+ * until Escape. Install both, with `<SceneCanvas ambient>`
2935
+ * or `useTools`'s `ambient`; the leader entry carries the actions. */
2936
+ declare function keymapContribution<TOverlay = never>(def: KeymapDef<TOverlay>): Contribution<TOverlay>[];
2937
+
2938
+ /** One row of a keymap's hint: a key chip, or a pointer gesture in words, and what it does. */
2939
+ interface KeymapRow {
2940
+ shortcut?: MenuShortcut;
2941
+ gesture?: string;
2942
+ label: string;
2943
+ }
2944
+ /** An open keymap as a hint shows it: {@link describeKeymap}'s result. */
2945
+ interface KeymapDescription {
2946
+ id: string;
2947
+ label: string;
2948
+ /** Whether a held leader is keeping it open. */
2949
+ held: boolean;
2950
+ rows: readonly KeymapRow[];
2951
+ }
2952
+ /** The rows a hint lists for an open keymap, under the overrides in force. */
2953
+ declare function describeKeymap(level: OpenKeymap, actions: readonly Action[], overrides?: ResolvedBindingOverrides): KeymapDescription;
2954
+ /**
2955
+ * Every key in `def` as a menu chip, by action id, each carrying the path
2956
+ * through its leader (`Loupe ▸ 2`), under `overrides`. A keymap with no key
2957
+ * leader gives its keys no path.
2958
+ */
2959
+ declare function keymapShortcuts(def: KeymapDef, overrides?: ResolvedBindingOverrides): ReadonlyMap<string, readonly MenuShortcut[]>;
2960
+
2961
+ /** Options for {@link captureKey}. */
2962
+ interface CaptureKeyOptions {
2963
+ /** Capture the held form, `key-held`, rather than the keystroke. */
2964
+ held?: boolean;
2965
+ /** Stop listening and resolve `null`. */
2966
+ signal?: AbortSignal;
2967
+ /** Whether Cmd is Mod. Defaults to the detected platform. */
2968
+ apple?: boolean;
2969
+ }
2970
+ /**
2971
+ * The next keystroke on `target` as a key spec: {@link captureGesture} taking
2972
+ * a keystroke alone, and leaving the pointer to the page. A bare modifier is
2973
+ * waited past. Write the result with `keySpecRoute` to persist it as a binding
2974
+ * override.
2975
+ */
2976
+ declare function captureKey(target: EventTarget, opts?: CaptureKeyOptions): Promise<KeySpec | KeyHeldSpec | null>;
2977
+
2978
+ /** A spec kind a binding editor can capture. */
2979
+ type CaptureKind = 'key' | 'key-held' | 'click' | 'doubleClick' | 'drag' | 'contextMenu';
2980
+ /** What `captureGesture` resolves to. */
2981
+ type CapturedSpec = KeySpec | KeyHeldSpec | ClickSpec | DoubleClickSpec | DragSpec | ContextMenuSpec;
2982
+
2983
+ /** The click-and-drag thresholds a capture reads, each defaulting to its constant. */
2984
+ interface CaptureThresholds {
2985
+ dragPx?: number;
2986
+ doubleClickMs?: number;
2987
+ doubleClickPx?: number;
2988
+ }
2989
+
2990
+ /** Options for {@link captureGesture}. */
2991
+ interface CaptureGestureOptions extends CaptureThresholds {
2992
+ /** The kinds to take; input of any other kind is waited past. A keystroke
2993
+ * is taken as `key` when both `key` and `key-held` are listed. Defaults to
2994
+ * `key`, `click`, `doubleClick`, `drag` and `contextMenu`. */
2995
+ kinds?: readonly CaptureKind[];
2996
+ /** Stop listening and resolve `null`. */
2997
+ signal?: AbortSignal;
2998
+ /** Whether Cmd is Mod. Defaults to the detected platform. */
2999
+ apple?: boolean;
3000
+ /** Travel that makes a press a drag, in CSS px: pass the `gestures.dragPx`
3001
+ * kit setting. Defaults to `DRAG_THRESHOLD_PX`. */
3002
+ dragPx?: number;
3003
+ /** The `gestures.doubleClickMs` kit setting. Defaults to `DOUBLE_CLICK_MAX_MS`. */
3004
+ doubleClickMs?: number;
3005
+ /** The `gestures.doubleClickPx` kit setting. Defaults to `DOUBLE_CLICK_MAX_PX`. */
3006
+ doubleClickPx?: number;
3007
+ }
3008
+ /**
3009
+ * The next input on `target` of one of `opts.kinds`, as a gesture spec, for a
3010
+ * binding editor: its modifiers with the platform's primary one as `mod`. A
3011
+ * press is told a click or a drag by the dispatcher's own recognizer, at its
3012
+ * drag threshold; a click is held back for a double click only when both are
3013
+ * asked for. A bare modifier key is waited past. Escape, or an abort, resolves
3014
+ * `null`. What it takes is `preventDefault`ed and stopped, the click a browser
3015
+ * follows a press with included, so nothing else acts on it. The pointer is
3016
+ * left alone when no pointer kind is asked for, and keys other than Escape
3017
+ * when no key kind is.
3018
+ */
3019
+ declare function captureGesture(target: EventTarget, opts?: CaptureGestureOptions): Promise<CapturedSpec | null>;
3020
+
2664
3021
  /**
2665
3022
  * A tool's activation key, as `ToolDef.keybinding` declares it. Captures
2666
3023
  * only the fields meaningful to a tool-select key —
@@ -2798,6 +3155,29 @@ interface ToolDef<TOverlay = KernelOverlay> {
2798
3155
  */
2799
3156
  declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
2800
3157
 
3158
+ /**
3159
+ * Tool sets: a named group of tool ids, the unit a consumer opts into whole.
3160
+ * A set names ids, never tools, because a tool is built by a hook that holds
3161
+ * its state; whatever mounts the set supplies the tool for each id.
3162
+ */
3163
+ /** A named group of tool ids. A member is an id or another set, whose ids it takes in order. */
3164
+ interface ToolSet<TId extends string = string> {
3165
+ readonly id: string;
3166
+ readonly members: readonly (TId | ToolSet<TId>)[];
3167
+ }
3168
+ /** One entry of a list of tools to mount: an id, or a set of them. */
3169
+ type ToolEntry<TId extends string = string> = TId | ToolSet<TId>;
3170
+ /** A set named `id` of `members`, in palette order. */
3171
+ declare function toolSet<TId extends string>(id: string, members: readonly ToolEntry<TId>[]): ToolSet<TId>;
3172
+ /** Whether `entry` is a set rather than a single id. */
3173
+ declare function isToolSet<TId extends string>(entry: ToolEntry<TId>): entry is ToolSet<TId>;
3174
+ /** Every id `entries` name, sets opened in place, each id once at its first place. */
3175
+ declare function toolIdsOf<TId extends string>(entries: readonly ToolEntry<TId>[]): TId[];
3176
+ /** `set` less every id `omit` names, sets opened. Keeps `set`'s id with a `-without` suffix. */
3177
+ declare function withoutTools<TId extends string>(set: ToolSet<TId>, ...omit: readonly ToolEntry<TId>[]): ToolSet<TId>;
3178
+ /** Whether `set` names `id`, directly or through a set inside it. */
3179
+ declare function toolSetHas<TId extends string>(set: ToolSet<TId>, id: string): boolean;
3180
+
2801
3181
  /**
2802
3182
  * One row in the route registry — a single `GestureBinding` on one tool,
2803
3183
  * flattened into the route grammar's vocabulary so the inspector, the
@@ -2944,6 +3324,9 @@ interface ToolScopes {
2944
3324
  * definition). Two gated actions conflict only when some mode lets both
2945
3325
  * rules hold. Default: the kit's `DEFAULT_MODES`. */
2946
3326
  modes?: readonly ActiveMode[];
3327
+ /** Binding overrides in force, applied to the entries' bindings. Actions are
3328
+ * read as given: an `ActionsRegistry`'s `list()` has applied them already. */
3329
+ overrides?: ResolvedBindingOverrides;
2947
3330
  }
2948
3331
  /**
2949
3332
  * Detect reachable route conflicts in an assembled tool set and report each one.
@@ -3092,12 +3475,21 @@ declare function deleteScratch<T>(store: ScratchStore, key: ScratchKey<T>): bool
3092
3475
  *
3093
3476
  * Output shape: **files first (source order), then strings (item order)**.
3094
3477
  * For paste (`itemsFromClipboardData`) string flavors are emitted in
3095
- * `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list.
3478
+ * `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list, then
3479
+ * the admitted ones in the order they were first admitted.
3096
3480
  */
3097
3481
 
3098
- /** The string flavors the kit normalizes. Everything else (browser-internal
3099
- * custom flavors, etc.) is dropped at materialization. */
3482
+ /** The string flavors the kit always keeps. Any other is dropped at
3483
+ * materialization unless {@link admitIngestMime} has admitted it. */
3100
3484
  declare const INGEST_STRING_MIMES: ReadonlySet<string>;
3485
+ /**
3486
+ * Keep string flavors of `mime` on drops, drags and pastes as well as the
3487
+ * {@link INGEST_STRING_MIMES}, so content from the app's own UI can travel
3488
+ * under a type of its own. Returns a release; the flavor is kept while any
3489
+ * admission of it is unreleased. Core admits each exact MIME a registered
3490
+ * content handler names.
3491
+ */
3492
+ declare function admitIngestMime(mime: string): () => void;
3101
3493
  /** Wrap raw `File`s (file picker, `CanvasExtensionApi.ingest`) as
3102
3494
  * `IngestItem`s, normalizing each MIME the same way the drop/paste paths
3103
3495
  * do. Synchronous — `File` objects are already fully owned. */
@@ -3202,6 +3594,10 @@ interface DepSchema {
3202
3594
  activeTool: ActiveToolContextValue;
3203
3595
  /** The menu host of the `ActionsProvider` in scope. */
3204
3596
  menuHost?: MenuHost;
3597
+ /** The canvas's dispatch trace, for a dep to record a mode switch into. */
3598
+ dispatchTrace?: DispatchTrace;
3599
+ /** The keymaps open on the canvas: what a leader opens and a hint reads. */
3600
+ keymaps?: KeymapStack;
3205
3601
  }
3206
3602
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
3207
3603
  type DepName = keyof DepSchema;
@@ -3249,4 +3645,4 @@ type KernelOverlay = OverlaySchema extends {
3249
3645
  overlay: infer O;
3250
3646
  } ? O : unknown;
3251
3647
 
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 };
3648
+ export { type BindingScope as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionParams as E, type ActionPresentation as F, type ActionSettings as G, type ActionSettingsState as H, type ActionShortcut as I, type ActionSource as J, type KeymapDescription as K, type LongPressOptions as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionTuning as P, type ActionTunings as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ActionUnavailable as U, type ActionsProp as V, type AnyTool as W, type AssemblyInputs as X, BROWSER_RESERVED_CHORDS as Y, type BindingOpts as Z, type BindingOverrides as _, type DepName as a, NEVER as a$, type BindingSource as a0, type BodyPredicate as a1, type Bounds as a2, type BuildRuleCtxArgs as a3, COARSE_TARGET_SCALE as a4, type CaptureGestureOptions as a5, type CaptureKeyOptions as a6, type CaptureKind as a7, type CapturedSpec as a8, type Carried as a9, type HitShape as aA, type HotkeyTrigger as aB, INGEST_STRING_MIMES as aC, type ImmediateInvoker as aD, type InvocationCtx as aE, type Invoker as aF, KEYMAP_HOLD as aG, type KeyBinding as aH, type KeyReach as aI, type KeymapDef as aJ, type KeymapRow as aK, type KeymapStack as aL, type KeymapUnbound as aM, type KeymapVerdict as aN, type LongPressState as aO, type LongPressStore as aP, type MatchOutcome as aQ, type MatchResult as aR, type MenuHit as aS, type MenuOpenRequest as aT, type MenuSources as aU, type MenuWhen as aV, type ModeShortcutHandlers as aW, type ModeSwitchLogEntry as aX, type ModifierRole as aY, type ModifierRoles as aZ, type ModifierState as a_, type Carry as aa, type CarryCallbacks as ab, type CarryPayload as ac, type CarryPointer as ad, type CarryStage as ae, type Condition as af, type Conflict as ag, type ContributionChrome as ah, type ContributionDeps as ai, type ContributionRouting as aj, DEFAULT_ALLOWED_CAPABILITIES as ak, DEFAULT_DEVICE_PROFILE as al, type DebugSink as am, type DepListing as an, type DetectedDeviceFacts as ao, type DeviceProfile as ap, type DispatchRecord as aq, type DispatchRecordInput as ar, type DispatcherContext as as, type DragSample as at, type DroppedCandidate as au, type Eligibility as av, type EligibilityState as aw, type GestureBinding as ax, type GestureKitSettings as ay, type HandleKind as az, type DepSource as b, buildRuleCtx as b$, type OngoingHandle as b0, type OngoingInvoker as b1, type OngoingOverlay as b2, type OpenKeymap as b3, type OpenMenu as b4, type OverlayRole as b5, type OverlaySchema as b6, type OwnedMenuDef as b7, PREDICATE_TARGET as b8, type PendingLongPress as b9, type ToolEntry as bA, type ToolKeybinding as bB, type ToolModifiers as bC, type ToolOffhandBindingSpec as bD, type ToolPresentation as bE, type ToolScopes as bF, type ToolSet as bG, type ToolSlot as bH, type TraceLogEntry as bI, type TraceRing as bJ, type TunedBinding as bK, type UiOngoingControl as bL, type ViewportGestureKind as bM, type WalkStep as bN, actionBindings as bO, actionCursor as bP, actionItemTitle as bQ, actionItems as bR, actionSettingsParams as bS, actionShortcuts as bT, actionsTagged as bU, admitIngestMime as bV, assembleEntries as bW, bindingId as bX, browserReservedChord as bY, buildDepsFromRequires as bZ, buildRouteRegistry as b_, type PlacedBy as ba, type Point2 as bb, type PointerSession as bc, type PointerSessionCallbacks as bd, type PointerSessionCancelReason as be, type PointerSessionOptions as bf, type RankedCandidate as bg, type RecordCandidate as bh, type RegistryEntry as bi, type ReservedChord as bj, type ResizeAnchor as bk, type ResolveAllOptions as bl, type ResolveOnlyResult as bm, type ResolvedBindingOverrides as bn, type ResolvedCandidate as bo, type RoleKeys as bp, type RoleRebinds as bq, type Rule as br, type ScopedBinding as bs, type ScratchKey as bt, type ScratchStore as bu, type SelectionApi as bv, type Selector as bw, type SpecificityPart as bx, TOOL_OFFHAND_ID as by, type ToolDef as bz, type ActionDeps as c, offhandKeyFor as c$, buildToolOffhandBindings as c0, captureGesture as c1, captureKey as c2, clientToCanvas as c3, clientToCanvasRect as c4, createDepRegistry as c5, createDispatchTrace as c6, createDispatcher as c7, createKeymapStack as c8, createLongPressStore as c9, isSegment as cA, isSelectedBody as cB, isTool as cC, isToolSet as cD, isUnselectedBody as cE, itemTypesFromDataTransfer as cF, itemsFromClipboardData as cG, itemsFromDataTransfer as cH, itemsFromFiles as cI, keySpecShortcut as cJ, keymapContribution as cK, keymapLeaderBindingId as cL, keymapLeaderId as cM, keymapShortcuts as cN, keysHeld as cO, liveScope as cP, makeToolOffhandAction as cQ, matchActionItem as cR, matchBest as cS, matchSorted as cT, matchSortedWithBarred as cU, matchesKeyBinding as cV, mergeContributions as cW, mergeMenuDefs as cX, modeShortcutSpec as cY, modeShortcuts as cZ, multiTouchGestureId as c_, createMenuHost as ca, createTraceRing as cb, defineTool as cc, deleteScratch as cd, describeKeymap as ce, describeRule as cf, dlog as cg, dwarn as ch, evaluate as ci, evaluateActive as cj, evaluateEnabled as ck, findConflicts as cl, getScratch as cm, heldKeySpec as cn, historyKey as co, isAnchor as cp, isAnchorOrControl as cq, isBalanceGrip as cr, isBody as cs, isControlHandle as ct, isCornerWidget as cu, isDebugEnabled as cv, isEmpty as cw, isPathEditPart as cx, isResizeHandle as cy, isRotateHandle as cz, type ActionsRegistry as d, openPointerSession as d0, overlaysOf as d1, overriddenBindings as d2, pickMenu as d3, pointerGestureId as d4, readActionSettings as d5, reportRouteConflicts as d6, resolveBindingOverrides as d7, resolveDeviceProfile as d8, resolveMenu as d9, resolveParams as da, resolveSchemaMenu as db, roleReader as dc, routesForSpec as dd, ruleCanHoldIn as de, rulesExclusive as df, scopeBindings as dg, scratchKey as dh, setScratch as di, startCarry as dj, taggedActionItems as dk, targetConsultsAffordance as dl, toolIdsOf as dm, toolSet as dn, toolSetHas as dp, tuneAction as dq, validateActionDefaultBinding as dr, validateActionId as ds, withoutTools as dt, ActionDisabledReason as e, type ActionVariant as f, type MenuDef as g, DepNode as h, type KernelOverlay as i, type Tool as j, ActiveToolContextProvider as k, ActiveToolContextProviderIfRoot as l, type ActiveToolContextProviderProps as m, type ActiveToolContextValue as n, useOptionalActiveToolContext as o, type Dispatcher as p, type AffordanceHit as q, type DispatchTrace as r, type DepSchema as s, ALWAYS as t, useActiveToolContext as u, type ActionDispatch as v, type ActionEnabledResult as w, type ActionEntry as x, type ActionItem as y, type ActionParamSchema as z };