@weasel-js/routing 1.9.4 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Y5RURZBH.js → chunk-VCEIQ64H.js} +1017 -851
- package/dist/chunk-VCEIQ64H.js.map +1 -0
- package/dist/{index-gArM2tbt.d.ts → index-DkOqUxow.d.ts} +370 -596
- package/dist/index.d.ts +3 -3
- package/dist/index.js +16 -64
- package/dist/index.js.map +1 -1
- package/dist/{internal-B0SsznMG.d.ts → internal-Boc2I3zn.d.ts} +38 -67
- package/dist/internal.d.ts +4 -4
- package/dist/react.d.ts +123 -34
- package/dist/react.js +688 -751
- package/dist/react.js.map +1 -1
- package/package.json +9 -8
- package/dist/chunk-Y5RURZBH.js.map +0 -1
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import { View } from '@weasel-js/geom';
|
|
1
|
+
import { PoseLinear, View } from '@weasel-js/geom';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
+
import { Emitter } from '@weasel-js/registry';
|
|
5
|
+
import { ClaimableGesture, GestureSpec, IngestItem, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
|
|
4
6
|
import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
|
|
5
7
|
import { CursorSpec } from '@weasel-js/cursor';
|
|
6
|
-
import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
|
|
7
|
-
import { Op } from '@weasel-js/history';
|
|
8
8
|
import '@weasel-js/select';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The primitives the routing surface is typed in.
|
|
12
12
|
*
|
|
13
|
-
* Routing
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* which is declared in `@weasel-js/history` and re-exported by
|
|
19
|
-
* `core/ops/types`. The selection click policy's own vocabulary comes from
|
|
20
|
-
* `@weasel-js/select`, where the rules that read it live.
|
|
21
|
-
*
|
|
22
|
-
* Authority for the *behavior* behind each of these stays in core: `View` is
|
|
23
|
-
* what `core/viewport` maintains, `SelectionApi` is what `useSelection`
|
|
24
|
-
* returns, `DeviceProfile` is what `resolveDeviceProfile` detects. Changing
|
|
25
|
-
* one of these shapes is a change to core's contract that happens to be
|
|
26
|
-
* spelled here.
|
|
13
|
+
* Routing has to name a viewport, a rect, a node id, and a selection, but it
|
|
14
|
+
* never constructs, projects, or renders any of them. So they are declared
|
|
15
|
+
* here as structural interfaces with no dependencies, and `@weasel-js/core`
|
|
16
|
+
* re-exports each. The behavior behind them stays in core: changing one of
|
|
17
|
+
* these shapes is a change to core's contract that happens to be spelled here.
|
|
27
18
|
*/
|
|
28
19
|
|
|
29
20
|
/** An opaque scene-node identifier. Branded so a bare string cannot stand in. */
|
|
@@ -33,8 +24,9 @@ type NodeId = string & {
|
|
|
33
24
|
|
|
34
25
|
/**
|
|
35
26
|
* Axis-aligned rectangle in world space. The optional `rotation` (radians,
|
|
36
|
-
* around the AABB center)
|
|
37
|
-
*
|
|
27
|
+
* around the AABB center) and `transform` (inside it, about the same center)
|
|
28
|
+
* let chrome carry the frame a box is drawn in without a parallel type;
|
|
29
|
+
* `poseFrameOf` composes the two.
|
|
38
30
|
*/
|
|
39
31
|
interface Bounds {
|
|
40
32
|
x: number;
|
|
@@ -42,6 +34,7 @@ interface Bounds {
|
|
|
42
34
|
width: number;
|
|
43
35
|
height: number;
|
|
44
36
|
rotation?: number;
|
|
37
|
+
transform?: PoseLinear;
|
|
45
38
|
}
|
|
46
39
|
/** The selection as routing reads and writes it. */
|
|
47
40
|
interface SelectionApi {
|
|
@@ -65,6 +58,7 @@ interface SelectionApi {
|
|
|
65
58
|
* Apply a click to the selection per the configured mode/extend key.
|
|
66
59
|
* - `single`: replaces selection with `[id]`, regardless of modifiers.
|
|
67
60
|
* - `multi`: with the extend key held, toggles `id` in/out of the selection;
|
|
61
|
+
* with the range key held, selects from the last pressed id through `id`;
|
|
68
62
|
* otherwise replaces with `[id]`.
|
|
69
63
|
*/
|
|
70
64
|
applyClick(id: NodeId, modifiers: {
|
|
@@ -126,8 +120,8 @@ type HitShape = {
|
|
|
126
120
|
/** What a recorded viewport change was: a translation or a scale change. */
|
|
127
121
|
type ViewportGestureKind = 'pan' | 'zoom';
|
|
128
122
|
/**
|
|
129
|
-
* Where
|
|
130
|
-
*
|
|
123
|
+
* Where an action reports its hit math so the debug overlay can draw it. An
|
|
124
|
+
* action reaches the canvas's sink as its `debug` dep; routing never calls one.
|
|
131
125
|
*/
|
|
132
126
|
interface DebugSink {
|
|
133
127
|
recordHitbox(id: string, kind: 'body' | 'handle' | 'rotation' | 'anchor', shape: HitShape): void;
|
|
@@ -188,10 +182,11 @@ type ResizeAnchor = {
|
|
|
188
182
|
* registration lives in the node it was made in, and a lookup starts at a leaf
|
|
189
183
|
* and walks up, so one canvas never answers for another.
|
|
190
184
|
*/
|
|
185
|
+
|
|
191
186
|
/** What every node of one tree shares: a version and the listeners to it. */
|
|
192
187
|
interface ScopeTree {
|
|
193
188
|
version: number;
|
|
194
|
-
readonly listeners:
|
|
189
|
+
readonly listeners: Emitter;
|
|
195
190
|
}
|
|
196
191
|
declare class ScopeNode<N extends ScopeNode<N>> {
|
|
197
192
|
readonly parent: N | null;
|
|
@@ -270,15 +265,12 @@ declare function ActiveToolContextProvider({ children, initialActive, }: ActiveT
|
|
|
270
265
|
declare function useActiveToolContext(): ActiveToolContextValue;
|
|
271
266
|
/**
|
|
272
267
|
* Like `useActiveToolContext`, but returns `null` when no
|
|
273
|
-
* `<ActiveToolContextProvider>` is in scope instead of throwing.
|
|
274
|
-
* `useStandardActions` to preserve its silent-no-op contract when no provider
|
|
275
|
-
* is present.
|
|
268
|
+
* `<ActiveToolContextProvider>` is in scope instead of throwing.
|
|
276
269
|
*/
|
|
277
270
|
declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
|
|
278
271
|
/**
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* so `<WeaselProvider>` defers to the host's existing scope.
|
|
272
|
+
* Mounts an `<ActiveToolContextProvider>` only when no parent provider is in
|
|
273
|
+
* scope; otherwise renders children unwrapped.
|
|
282
274
|
*/
|
|
283
275
|
declare function ActiveToolContextProviderIfRoot({ children, }: {
|
|
284
276
|
children: ReactNode;
|
|
@@ -456,25 +448,20 @@ declare const NEVER: Rule;
|
|
|
456
448
|
* { when: function hasPath() { … } } → when(hasPath)
|
|
457
449
|
* ```
|
|
458
450
|
*
|
|
459
|
-
* Never throws and always terminates
|
|
460
|
-
* on a cyclic value and renders the `when` arm as a useless `{}`.
|
|
451
|
+
* Never throws and always terminates.
|
|
461
452
|
*/
|
|
462
453
|
declare function describeRule(rule: Rule): string;
|
|
463
454
|
/** Evaluate a rule against a context. Pure, and cheap enough to run per
|
|
464
455
|
* frame for every piece of chrome. */
|
|
465
456
|
declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
|
|
466
457
|
/**
|
|
467
|
-
* Composable eligibility predicate with a fluent surface.
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
* Callable form `cond(ctx)` evaluates the tree against ctx. The fluent methods
|
|
472
|
-
* return new Conditions wrapping new trees.
|
|
458
|
+
* Composable eligibility predicate with a fluent surface. `cond(ctx)`
|
|
459
|
+
* evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
|
|
460
|
+
* Conditions wrapping new trees.
|
|
473
461
|
*
|
|
474
462
|
* **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
|
|
475
|
-
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`.
|
|
476
|
-
*
|
|
477
|
-
* the subexpression or use the top-level `or(...)`.
|
|
463
|
+
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
|
|
464
|
+
* disjunction, name the subexpression or use the top-level `or(...)`.
|
|
478
465
|
*/
|
|
479
466
|
interface Condition {
|
|
480
467
|
(ctx: RuleCtx): boolean;
|
|
@@ -542,9 +529,6 @@ declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
|
|
|
542
529
|
* An Action's `invoker` field describes how it's run. `immediate` invokers
|
|
543
530
|
* fire once (delete, align, undo). `ongoing` invokers open a phase machine
|
|
544
531
|
* driven by the dispatcher (move, resize, pinch-zoom).
|
|
545
|
-
*
|
|
546
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`
|
|
547
|
-
* § "Types" for the full design.
|
|
548
532
|
*/
|
|
549
533
|
|
|
550
534
|
/** A 2D point in either world or screen coordinates. */
|
|
@@ -553,31 +537,20 @@ interface Point2 {
|
|
|
553
537
|
y: number;
|
|
554
538
|
}
|
|
555
539
|
/**
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
* Populated by the dispatcher when the `affordanceAt` thunk is provided to
|
|
559
|
-
* `useGestureDispatcher`. Tools / action invokers that only fire on a specific
|
|
560
|
-
* affordance (e.g. a resize handle) use this field as a guard — if the
|
|
561
|
-
* affordance is absent or is the wrong kind, they return `{}` and let other
|
|
562
|
-
* bindings handle the drag.
|
|
540
|
+
* Which UI affordance was hit at pointerdown. Populated by the dispatcher when
|
|
541
|
+
* the `affordanceAt` thunk is provided to `useGestureDispatcher`.
|
|
563
542
|
*
|
|
564
543
|
* `kind` is a discriminator string:
|
|
565
544
|
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
566
545
|
* `'handle:bottom-right'` — corner resize handles.
|
|
567
546
|
* - `'rotate-handle'` — the rotation affordance.
|
|
568
547
|
* - `'anchor:N'` — a path anchor at index N.
|
|
569
|
-
*
|
|
570
|
-
* `fixedPoint` is the world-space point that should remain stationary during
|
|
571
|
-
* the gesture. For resize handles this is the opposite (diagonally fixed)
|
|
572
|
-
* corner; for rotate it is the pivot.
|
|
573
|
-
*
|
|
574
|
-
* `targetIds` are the node ids this affordance belongs to.
|
|
575
548
|
*/
|
|
576
549
|
interface AffordanceHit {
|
|
577
550
|
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
578
551
|
kind: string;
|
|
579
552
|
/** Id of whatever produced this hit — a kit affordance's `id`, or the
|
|
580
|
-
* registered layer's id.
|
|
553
|
+
* registered layer's id. */
|
|
581
554
|
owner?: string;
|
|
582
555
|
/** `'exclusive'` means no binding may act on this point unless its target
|
|
583
556
|
* consults the affordance. `'shared'` (the default) competes on scope and
|
|
@@ -592,25 +565,18 @@ interface AffordanceHit {
|
|
|
592
565
|
};
|
|
593
566
|
/** Which nodes this affordance belongs to. */
|
|
594
567
|
targetIds?: string[];
|
|
595
|
-
/** Set when `kind` matches `'handle:*'
|
|
596
|
-
*
|
|
597
|
-
* Other affordance kinds (rotate-handle, anchor:N, controlIn:N, controlOut:N)
|
|
598
|
-
* leave this undefined. */
|
|
568
|
+
/** Set only when `kind` matches `'handle:*'`: which corner stays fixed
|
|
569
|
+
* during a resize. */
|
|
599
570
|
anchor?: ResizeAnchor;
|
|
600
|
-
/** Cursor to show while the pointer hovers this affordance (no
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
* action-cursor prediction, then to the active tool's cursor. */
|
|
571
|
+
/** Cursor to show while the pointer hovers this affordance (no gesture in
|
|
572
|
+
* flight). Unset falls through to action-cursor prediction, then to the
|
|
573
|
+
* active tool's cursor. */
|
|
604
574
|
cursor?: CursorSpec;
|
|
605
575
|
/**
|
|
606
576
|
* Free-form payload from whatever produced the hit, carried through to the
|
|
607
|
-
* matching action untouched.
|
|
608
|
-
*
|
|
609
|
-
* Kit affordances describe themselves fully in the fields above and leave
|
|
610
|
-
* this unset. It exists for affordances the kit doesn't know the shape of —
|
|
577
|
+
* matching action untouched. Kit affordances leave it unset; it exists for
|
|
611
578
|
* a registered layer's own chrome, where the hit-test already resolved
|
|
612
|
-
* *which* of its pieces was hit
|
|
613
|
-
* redo that work. `@weasel-js/hud` passes the hit widget here.
|
|
579
|
+
* *which* of its pieces was hit.
|
|
614
580
|
*/
|
|
615
581
|
payload?: unknown;
|
|
616
582
|
}
|
|
@@ -619,9 +585,7 @@ interface AffordanceHit {
|
|
|
619
585
|
* stylus state the originating `PointerEvent` carried.
|
|
620
586
|
*
|
|
621
587
|
* The stylus fields are absent for mouse/touch on browsers that don't report
|
|
622
|
-
* them, and for synthetic events.
|
|
623
|
-
* (e.g. `Stroke.vertexWidths` from a pencil stroke) read them off the samples
|
|
624
|
-
* their `insert` dep receives — see `apps/site/demos/StrokeAndFillDemo.tsx`.
|
|
588
|
+
* them, and for synthetic events.
|
|
625
589
|
*/
|
|
626
590
|
interface DragSample extends Point2 {
|
|
627
591
|
/** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
|
|
@@ -659,23 +623,19 @@ interface InvocationCtx {
|
|
|
659
623
|
* mutates the viewport itself (pan, view-zoom), because world-space
|
|
660
624
|
* deltas become self-referential as the view shifts mid-drag.
|
|
661
625
|
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
* provide them.
|
|
626
|
+
* Absent when the underlying pointer events carried no
|
|
627
|
+
* `clientX`/`clientY`.
|
|
665
628
|
*/
|
|
666
629
|
screenDelta?: Point2;
|
|
667
630
|
affordance?: AffordanceHit;
|
|
668
631
|
/**
|
|
669
632
|
* Full pointermove history for the current drag, in world space, with
|
|
670
633
|
* per-sample stylus state when the browser reported it.
|
|
671
|
-
* Accumulated by the dispatcher on every `pointermove` pump event.
|
|
672
634
|
* Available only during `onMove` and `onEnd` calls (not on `start`).
|
|
673
635
|
*
|
|
674
636
|
* During `onMove` this is the dispatcher's live accumulator, and it grows
|
|
675
637
|
* under you — read it, don't retain it. On `onEnd` it is a snapshot, so
|
|
676
638
|
* that is where an action that keeps the trail should take it.
|
|
677
|
-
* Used by `lassoSelectAction` to build its polygon vertex list and by
|
|
678
|
-
* `insertAction`'s pencil kind to carry the freehand stroke.
|
|
679
639
|
*/
|
|
680
640
|
points?: DragSample[];
|
|
681
641
|
};
|
|
@@ -697,12 +657,13 @@ interface InvocationCtx {
|
|
|
697
657
|
centroid: Point2;
|
|
698
658
|
/** Distance between the two primary pointers, in CSS pixels. */
|
|
699
659
|
spread: number;
|
|
660
|
+
/** Degrees the line between the two primary pointers has turned since
|
|
661
|
+
* the gesture began, clockwise positive. It keeps counting past a half
|
|
662
|
+
* turn. Zero where the events carry no `MultiTouchEvent.angle`. */
|
|
700
663
|
rotation: number;
|
|
701
664
|
/**
|
|
702
|
-
* Pinch-zoom geometry
|
|
703
|
-
* handle is in flight and a pointermove-pump fires.
|
|
665
|
+
* Pinch-zoom geometry, populated while a multitouch handle is in flight.
|
|
704
666
|
* `startSpread` is the spread at the moment the gesture began.
|
|
705
|
-
* `currentSpread` is the spread at the current frame.
|
|
706
667
|
*/
|
|
707
668
|
pinch?: {
|
|
708
669
|
startSpread: number;
|
|
@@ -723,33 +684,27 @@ interface InvocationCtx {
|
|
|
723
684
|
viewId?: string | null;
|
|
724
685
|
/**
|
|
725
686
|
* Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
|
|
726
|
-
* UI-driven ongoing actions (color picker, opacity slider)
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* receive params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
687
|
+
* UI-driven ongoing actions (color picker, opacity slider). The gesture
|
|
688
|
+
* dispatcher does not populate this field; gesture-driven actions receive
|
|
689
|
+
* params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
730
690
|
*/
|
|
731
691
|
params?: Record<string, unknown>;
|
|
732
692
|
}
|
|
733
693
|
/** Per-invocation options the dispatcher reads from a `GestureBinding`'s
|
|
734
|
-
* `opts` field and passes to `OngoingInvoker.start`.
|
|
735
|
-
* behaviors; extensible. */
|
|
694
|
+
* `opts` field and passes to `OngoingInvoker.start`. */
|
|
736
695
|
interface BindingOpts {
|
|
737
696
|
/** Gesture behaviors the action's own invoker runs. Routing carries the
|
|
738
697
|
* array to the action and never reads an element, so the element type is
|
|
739
|
-
* open here; `@weasel-js/core` narrows it
|
|
740
|
-
* unknown, unknown>[]`, which is what a behavior is authored against. */
|
|
698
|
+
* open here; `@weasel-js/core` narrows it. */
|
|
741
699
|
behaviors?: unknown[];
|
|
742
|
-
/** Per-binding action parameters
|
|
743
|
-
*
|
|
744
|
-
* invokers, when needed). Loose typing (Record<string, unknown>) for
|
|
745
|
-
* now; consider per-action typing later via BindingOpts<A>.
|
|
700
|
+
/** Per-binding action parameters, which an immediate invoker reads via the
|
|
701
|
+
* second arg to `run`.
|
|
746
702
|
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* via `resolveParams(opts?.params)`. */
|
|
703
|
+
* May also be a thunk evaluated each time the value is needed, so a tool
|
|
704
|
+
* can close over refs that mutate during a gesture (e.g. polygon `sides`
|
|
705
|
+
* adjusted mid-drag via ArrowUp). An ongoing invoker that wants the latest
|
|
706
|
+
* values at commit re-calls it inside `onEnd` via
|
|
707
|
+
* `resolveParams(opts?.params)`. */
|
|
753
708
|
params?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
754
709
|
/**
|
|
755
710
|
* The views this binding is live in, by id, where `null` is the surface's
|
|
@@ -761,6 +716,13 @@ interface BindingOpts {
|
|
|
761
716
|
* drag bound inside a minimap view loses to the active tool's marquee.
|
|
762
717
|
*/
|
|
763
718
|
views?: readonly (string | null)[];
|
|
719
|
+
/**
|
|
720
|
+
* Says this binding is declared ahead of another on the same input on
|
|
721
|
+
* purpose: its action declines through `enabled()` when the input is not its
|
|
722
|
+
* to take, and the next binding fires. The dispatcher does that for any
|
|
723
|
+
* binding; this tells the route-conflict check the order is the design.
|
|
724
|
+
*/
|
|
725
|
+
yields?: boolean;
|
|
764
726
|
}
|
|
765
727
|
/** Resolve `BindingOpts.params` to a concrete record (calling the thunk if
|
|
766
728
|
* needed). Returns `undefined` for absent params. */
|
|
@@ -792,12 +754,10 @@ type OverlayRole =
|
|
|
792
754
|
* `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
|
|
793
755
|
* shape being dragged out, the line a slice would cut along.
|
|
794
756
|
*
|
|
795
|
-
* Every variant is world geometry and nothing else
|
|
796
|
-
* color, no coordinate space to choose
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
* normalizes this into `ResolvedOverlay`, which is what a painter consumes —
|
|
800
|
-
* `useDispatcherOverlayLayer` is core's, and it owns every style decision.
|
|
757
|
+
* Every variant is world geometry and nothing else — no `DrawCommand`, no
|
|
758
|
+
* color, no coordinate space to choose — so a surface rendering the same
|
|
759
|
+
* gesture some other way can still read it. The painter owns every style
|
|
760
|
+
* decision.
|
|
801
761
|
*/
|
|
802
762
|
type OngoingOverlay = {
|
|
803
763
|
kind: 'marquee';
|
|
@@ -823,13 +783,9 @@ type OngoingOverlay = {
|
|
|
823
783
|
shiftHeld: boolean;
|
|
824
784
|
} | {
|
|
825
785
|
/**
|
|
826
|
-
* An open run of world points the gesture is proposing —
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* Geometry only, like every other variant: `role` is the whole of what
|
|
830
|
-
* an action says about appearance, and the painting surface is what
|
|
831
|
-
* turns it into a stroke. An action that picked its own color would be
|
|
832
|
-
* unpaintable by any renderer but the one it had in mind.
|
|
786
|
+
* An open run of world points the gesture is proposing — a cut line, a
|
|
787
|
+
* routed connector. `role` is the whole of what an action says about
|
|
788
|
+
* appearance.
|
|
833
789
|
*/
|
|
834
790
|
kind: 'polyline';
|
|
835
791
|
/** At least two points. Anything shorter is dropped in resolution. */
|
|
@@ -841,19 +797,14 @@ type OngoingOverlay = {
|
|
|
841
797
|
role?: OverlayRole;
|
|
842
798
|
} | {
|
|
843
799
|
/**
|
|
844
|
-
* Live insert-drag preview
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
* preview through the dispatcher overlay layer instead.
|
|
800
|
+
* Live insert-drag preview. Pre-commit there is no scene node to ghost
|
|
801
|
+
* via `previewIds()`/`previewPose()`, so insert paints its preview
|
|
802
|
+
* through the overlay instead.
|
|
848
803
|
*
|
|
849
|
-
* `
|
|
850
|
-
* the
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
* commit factory uses, so the preview matches the eventual node.
|
|
854
|
-
*
|
|
855
|
-
* `extras` is opaque (`unknown`) at the union level; the overlay
|
|
856
|
-
* renderer narrows on `shape` and casts the field shape it expects.
|
|
804
|
+
* `bounds` is the AABB of the current drag (start/current normalized).
|
|
805
|
+
* `extras` is the per-kind extras the action already collected; the
|
|
806
|
+
* overlay renderer narrows on `shape` and casts the field shape it
|
|
807
|
+
* expects.
|
|
857
808
|
*/
|
|
858
809
|
kind: 'insertPreview';
|
|
859
810
|
/** Which preview the dispatcher's overlay draws. The kit's own shape
|
|
@@ -867,15 +818,16 @@ type OngoingOverlay = {
|
|
|
867
818
|
height: number;
|
|
868
819
|
};
|
|
869
820
|
extras: unknown;
|
|
870
|
-
/** World-space point to paint a small "anchor" dot at
|
|
871
|
-
* click point
|
|
872
|
-
*
|
|
873
|
-
* click point, and for any shape in center mode where the dot
|
|
874
|
-
* marks the center the shape grows around. */
|
|
821
|
+
/** World-space point to paint a small "anchor" dot at, marking the
|
|
822
|
+
* click point — for radial shapes (polygon/star), where no vertex
|
|
823
|
+
* sits on it, and for any shape in center mode. */
|
|
875
824
|
anchorPoint?: {
|
|
876
825
|
x: number;
|
|
877
826
|
y: number;
|
|
878
827
|
};
|
|
828
|
+
/** World affines the shape will also be drawn under once it lands, by
|
|
829
|
+
* the container it is drawn into: the preview is painted under each. */
|
|
830
|
+
copies?: readonly (readonly [number, number, number, number, number, number])[];
|
|
879
831
|
};
|
|
880
832
|
/** Handle returned from an `OngoingInvoker.start`. The dispatcher pumps
|
|
881
833
|
* `onMove` on subsequent input events of the same gesture and calls
|
|
@@ -884,42 +836,29 @@ type OngoingOverlay = {
|
|
|
884
836
|
interface OngoingHandle {
|
|
885
837
|
/**
|
|
886
838
|
* Optional logical action kind — a stable, human-readable tag the
|
|
887
|
-
* dispatcher exposes via `getActiveAction()`
|
|
888
|
-
*
|
|
889
|
-
* is currently in flight" without inspecting handles directly.
|
|
890
|
-
*
|
|
891
|
-
* Examples: `'marquee'`, `'lasso'`, `'move'`, `'resize'`, `'rotate'`,
|
|
892
|
-
* `'pan'`, `'pinch'`.
|
|
893
|
-
*
|
|
894
|
-
* Distinct from the dispatcher's internal `gestureId` (`pointer-mouse`,
|
|
895
|
-
* `key-held-Space`, etc.) which keys per-pointer state and is not
|
|
896
|
-
* meaningful to consumers.
|
|
839
|
+
* dispatcher exposes via `getActiveAction()`, e.g. `'marquee'`, `'move'`,
|
|
840
|
+
* `'resize'`, `'pan'`.
|
|
897
841
|
*
|
|
898
|
-
* When omitted,
|
|
899
|
-
*
|
|
900
|
-
* actions that don't have visible chrome of their own.
|
|
842
|
+
* When omitted, `getActiveAction().kind` reports `null` even though a
|
|
843
|
+
* handle is in flight.
|
|
901
844
|
*/
|
|
902
845
|
kind?: string;
|
|
846
|
+
/**
|
|
847
|
+
* The cursor this gesture shows now, read after every move. Wins over the
|
|
848
|
+
* action's `activeCursor`, for a cursor that follows the gesture — the
|
|
849
|
+
* rotate glyph turning about its pivot. `undefined` defers to the action.
|
|
850
|
+
*/
|
|
851
|
+
cursor?(): CursorSpec | undefined;
|
|
903
852
|
onMove?(ctx: InvocationCtx): void;
|
|
904
853
|
onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
|
|
905
854
|
/**
|
|
906
|
-
* Optional preview surface
|
|
907
|
-
*
|
|
908
|
-
* An ongoing-action implementation may populate `previewIds()` +
|
|
909
|
-
* `previewPose(id)` to expose its in-flight preview state for the
|
|
910
|
-
* canvas's preview-ghost layer (`usePreviewGhostLayer`) to render on
|
|
911
|
-
* top of the committed scene during the gesture.
|
|
855
|
+
* Optional preview surface: the in-flight state the canvas's preview-ghost
|
|
856
|
+
* layer renders on top of the committed scene during the gesture.
|
|
912
857
|
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
* pair: `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
918
|
-
* returns the interim pose for one of those ids (shape opaque — the
|
|
919
|
-
* canvas casts to its `TPose` parameter). The preview-ghost layer
|
|
920
|
-
* merges all sources via first-non-null semantics, with tool-side
|
|
921
|
-
* previews taking precedence over dispatcher-side (preserves
|
|
922
|
-
* backwards-compat during the registry-unification migration).
|
|
858
|
+
* `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
859
|
+
* returns the interim pose for one of them (shape opaque — the canvas
|
|
860
|
+
* casts to its `TPose` parameter). Returning `null`, or omitting the
|
|
861
|
+
* method, means "no preview this gesture".
|
|
923
862
|
*/
|
|
924
863
|
previewIds?(): Iterable<string> | null;
|
|
925
864
|
previewPose?(id: string): unknown | null;
|
|
@@ -932,40 +871,22 @@ interface OngoingHandle {
|
|
|
932
871
|
*/
|
|
933
872
|
previewOpaqueIds?(): Iterable<string> | null;
|
|
934
873
|
/**
|
|
935
|
-
* When `false`, the preview-ghost layer paints the ghost AND the
|
|
936
|
-
*
|
|
937
|
-
* `true
|
|
938
|
-
* during the gesture). Clone overrides to `false` so the original
|
|
939
|
-
* stays put and the ghost appears at the drag target.
|
|
874
|
+
* When `false`, the preview-ghost layer paints the ghost AND the source
|
|
875
|
+
* node stays visible at its committed pose, as a clone wants. Defaults to
|
|
876
|
+
* `true`: the ghost replaces the source during the gesture.
|
|
940
877
|
*/
|
|
941
878
|
previewHidesSource?: boolean;
|
|
942
879
|
/**
|
|
943
|
-
* Optional per-id preview *data
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* `data
|
|
947
|
-
* ghost layer assembles a synthetic node from `{ ...node, pose:
|
|
948
|
-
* previewPose ?? node.pose, data: previewData ?? node.data }` before
|
|
949
|
-
* calling the scene slot's `drawOne`.
|
|
950
|
-
*
|
|
951
|
-
* Sources compose first-non-null per axis: an action can emit only
|
|
952
|
-
* `previewPose` (translation), only `previewData` (data-only edit),
|
|
953
|
-
* or both (pose + data both change, e.g. anchor drag on a data.path
|
|
954
|
-
* node where the bounds shift).
|
|
880
|
+
* Optional per-id preview *data*, for a gesture that mutates `node.data`
|
|
881
|
+
* (e.g. anchor-edit on nodes that store the polygon on `data.path`) rather
|
|
882
|
+
* than, or in addition to, the pose. Falls back to the committed
|
|
883
|
+
* `node.data` when null/absent.
|
|
955
884
|
*/
|
|
956
885
|
previewData?(id: string): unknown | null;
|
|
957
886
|
/**
|
|
958
|
-
* Optional chrome surface
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
* non-ghost visual (marquee rectangle, lasso polyline) for the canvas's
|
|
962
|
-
* `useDispatcherOverlayLayer` to paint while the gesture is in flight.
|
|
963
|
-
* Returning `null` (or omitting the method) means "no overlay this
|
|
964
|
-
* gesture" — the canvas will skip this handle as a chrome source.
|
|
965
|
-
*
|
|
966
|
-
* Distinct from the `previewIds()`/`previewPose(id)` ghost surface,
|
|
967
|
-
* which paints displaced scene-node silhouettes. Marquee and lasso
|
|
968
|
-
* gestures don't displace any node, but still need on-screen feedback.
|
|
887
|
+
* Optional chrome surface: a non-ghost visual (marquee rectangle, lasso
|
|
888
|
+
* polyline) painted while the gesture is in flight. Returning `null`, or
|
|
889
|
+
* omitting the method, means "no overlay this gesture".
|
|
969
890
|
*/
|
|
970
891
|
overlay?(): OngoingOverlay | null;
|
|
971
892
|
}
|
|
@@ -984,17 +905,13 @@ interface OngoingInvoker {
|
|
|
984
905
|
timing: 'ongoing';
|
|
985
906
|
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
986
907
|
}
|
|
987
|
-
/** Pluggable invocation strategy for an Action.
|
|
988
|
-
* (`longPress`, `twoStage`, `modal`) extend this union without touching
|
|
989
|
-
* the `Action` type. */
|
|
908
|
+
/** Pluggable invocation strategy for an Action. */
|
|
990
909
|
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
991
910
|
|
|
992
911
|
/**
|
|
993
912
|
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
994
913
|
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
995
914
|
* gesture-bindings are registered globally.
|
|
996
|
-
*
|
|
997
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`.
|
|
998
915
|
*/
|
|
999
916
|
|
|
1000
917
|
/** An interaction: a gesture spec composed with the id of the action it
|
|
@@ -1006,7 +923,6 @@ interface GestureBinding {
|
|
|
1006
923
|
opts?: BindingOpts;
|
|
1007
924
|
}
|
|
1008
925
|
/**
|
|
1009
|
-
* @experimental
|
|
1010
926
|
* A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
|
|
1011
927
|
* (no per-binding opts) or an object form that pairs a spec with
|
|
1012
928
|
* `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
|
|
@@ -1064,11 +980,9 @@ type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDis
|
|
|
1064
980
|
*/
|
|
1065
981
|
interface ActionPresentation {
|
|
1066
982
|
label: string;
|
|
1067
|
-
/** Inline-SVG icon for palette / toolbar surfaces.
|
|
1068
|
-
* `
|
|
1069
|
-
*
|
|
1070
|
-
* metadata. May be a static `ReactNode` or a function (rare; useful
|
|
1071
|
-
* for state-aware icons like a "lock" toggle). */
|
|
983
|
+
/** Inline-SVG icon for palette / toolbar surfaces. May be a static
|
|
984
|
+
* `ReactNode` or a function (rare; useful for state-aware icons like a
|
|
985
|
+
* "lock" toggle). */
|
|
1072
986
|
icon?: ReactNode | (() => ReactNode);
|
|
1073
987
|
/** Grouping key for palette/menu surfaces. Free-form string; the kit
|
|
1074
988
|
* ships defaults for `'align'` (six edges/centers), `'distribute'`
|
|
@@ -1124,30 +1038,21 @@ interface ActionDispatch extends BindingSource {
|
|
|
1124
1038
|
/**
|
|
1125
1039
|
* @experimental
|
|
1126
1040
|
* Optional predicate saying whether the action can run right now. Return
|
|
1127
|
-
* `true` when it can, or
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1041
|
+
* `true` when it can, or an `ActionDisabledReason` when it cannot. The
|
|
1042
|
+
* command palette greys out a disabled row, skips it in keyboard nav,
|
|
1043
|
+
* ignores clicks, and shows the reason next to the label. The dispatcher
|
|
1044
|
+
* skips a disabled action too, on a key as on a pointer, and falls through
|
|
1045
|
+
* to the next binding that matches. `ActionsRegistry.trigger` does not ask,
|
|
1046
|
+
* so the action's own `run` should still self-guard.
|
|
1133
1047
|
*
|
|
1134
1048
|
* **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
|
|
1135
|
-
* must not throw.
|
|
1136
|
-
* `
|
|
1137
|
-
* caught and treated as disabled with reason `'(predicate threw)'`.
|
|
1049
|
+
* must not throw. A throw is caught and treated as disabled with reason
|
|
1050
|
+
* `ActionDisabledReason.PredicateThrew`.
|
|
1138
1051
|
*
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
* reactive updates are deferred — palette is short-lived.
|
|
1052
|
+
* The palette evaluates `enabled` once when opened and does NOT re-evaluate
|
|
1053
|
+
* on selection changes while open.
|
|
1142
1054
|
*
|
|
1143
|
-
*
|
|
1144
|
-
* `ActionDisabledReason` and the consumer's display map.
|
|
1145
|
-
*
|
|
1146
|
-
* The optional `deps` argument is the same bag passed to
|
|
1147
|
-
* `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may
|
|
1148
|
-
* synthesize it from the surrounding `DepRegistry` so predicates can
|
|
1149
|
-
* inspect selection / scene / etc. Predicates that don't need deps just
|
|
1150
|
-
* ignore the arg.
|
|
1055
|
+
* `deps` is the same bag passed to `ImmediateInvoker.run`.
|
|
1151
1056
|
*
|
|
1152
1057
|
* `at` is the world point of the event being routed, when it has one — a
|
|
1153
1058
|
* click, a press, a long-press. An action that only applies somewhere
|
|
@@ -1155,10 +1060,18 @@ interface ActionDispatch extends BindingSource {
|
|
|
1155
1060
|
* falls through to the next binding and hover prediction shows the
|
|
1156
1061
|
* action's `cursor` only where it would act. Palettes and keystrokes
|
|
1157
1062
|
* pass no point; treat absent as "anywhere".
|
|
1063
|
+
*
|
|
1064
|
+
* `carried` is what a drop or a paste being routed brings: its items. For
|
|
1065
|
+
* a drag still in flight they hold a MIME and nothing else, since a
|
|
1066
|
+
* browser withholds contents until the drop. An action that takes only
|
|
1067
|
+
* some content refuses the rest here, so a drag it would not take is not
|
|
1068
|
+
* accepted and a paste it would not take stays the page's.
|
|
1158
1069
|
*/
|
|
1159
1070
|
enabled?: (deps?: ActionDeps, at?: {
|
|
1160
1071
|
x: number;
|
|
1161
1072
|
y: number;
|
|
1073
|
+
}, carried?: {
|
|
1074
|
+
items: readonly IngestItem[];
|
|
1162
1075
|
}) => true | ActionDisabledReason;
|
|
1163
1076
|
/**
|
|
1164
1077
|
* @experimental
|
|
@@ -1187,12 +1100,11 @@ interface ActionDispatch extends BindingSource {
|
|
|
1187
1100
|
/**
|
|
1188
1101
|
* CSS cursor shown while the pointer hovers a spot where this action
|
|
1189
1102
|
* would win the drag — or, when the drag's winner declares no cursor,
|
|
1190
|
-
* the click. The hover-cursor pump
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
* pump via `AffordanceRegion.cursor` and never reach this field.
|
|
1103
|
+
* the click. The hover-cursor pump runs the same match walk a real press
|
|
1104
|
+
* takes, so the hint and the actual target stay in sync. Omitted = no
|
|
1105
|
+
* override (the active tool's `Tool.cursor` shows). Affordance hits are
|
|
1106
|
+
* resolved earlier in the pump via `AffordanceRegion.cursor` and never
|
|
1107
|
+
* reach this field.
|
|
1196
1108
|
*
|
|
1197
1109
|
* Static value only. Prediction runs `enabled()` but cannot run the
|
|
1198
1110
|
* invoker, so an action that matches yet bails at `start()` (empty
|
|
@@ -1210,19 +1122,12 @@ interface ActionDispatch extends BindingSource {
|
|
|
1210
1122
|
* ("you are panning"). An action can declare either, both, or neither;
|
|
1211
1123
|
* with only `cursor` set, the hover hint holds for the duration of the
|
|
1212
1124
|
* gesture.
|
|
1213
|
-
*
|
|
1214
|
-
* This is where mid-gesture cursors live now. They used to come from the
|
|
1215
|
-
* tool side — an `engaged` phase entry's cursor,
|
|
1216
|
-
* or a function-form `Tool.cursor` reading the gesture scratch out of the
|
|
1217
|
-
* tool-routing dispatcher. Both belonged to a pipeline whose whole job was
|
|
1218
|
-
* being taken over by bindings, and neither could describe a cursor for an
|
|
1219
|
-
* action a tool doesn't own.
|
|
1220
1125
|
*/
|
|
1221
1126
|
activeCursor?: CursorSpec;
|
|
1222
1127
|
}
|
|
1223
1128
|
/**
|
|
1224
1129
|
* @experimental
|
|
1225
|
-
* Single registered action.
|
|
1130
|
+
* Single registered action.
|
|
1226
1131
|
*/
|
|
1227
1132
|
interface Action extends ActionDispatch, ActionPresentation {
|
|
1228
1133
|
}
|
|
@@ -1237,6 +1142,37 @@ interface ActionSource {
|
|
|
1237
1142
|
list(): readonly Action[];
|
|
1238
1143
|
}
|
|
1239
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Tuning: what a host changes about an action it did not write — the params
|
|
1147
|
+
* every invocation starts from, and bindings added beside the action's own —
|
|
1148
|
+
* without restating the descriptor. `ActionsRegistry.tune` holds tunings and
|
|
1149
|
+
* applies them to whatever descriptor is live for the id.
|
|
1150
|
+
*/
|
|
1151
|
+
|
|
1152
|
+
/** An action's params, or a thunk read each time the action asks for them. */
|
|
1153
|
+
type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
|
|
1154
|
+
/** A binding added for an action: a gesture alone, or a gesture with the
|
|
1155
|
+
* options it passes — the {@link BoundGesture} shape, its params typed. */
|
|
1156
|
+
type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
|
|
1157
|
+
spec: GestureSpec;
|
|
1158
|
+
opts: Omit<BindingOpts, 'params'> & {
|
|
1159
|
+
params?: ActionParams<P>;
|
|
1160
|
+
};
|
|
1161
|
+
};
|
|
1162
|
+
/** What a host changes about one action. */
|
|
1163
|
+
interface ActionTuning<P = Record<string, unknown>> {
|
|
1164
|
+
/** Params every invocation starts from. What a binding or a `trigger` call
|
|
1165
|
+
* passes is laid over them, so these fill in what it leaves out. A thunk is
|
|
1166
|
+
* read on every call, and an ongoing action resolving its `opts.params`
|
|
1167
|
+
* mid-gesture sees the value as it is then. */
|
|
1168
|
+
params?: ActionParams<P>;
|
|
1169
|
+
/** Bindings added after the action's own `defaultBinding`. */
|
|
1170
|
+
bindings?: readonly TunedBinding<P>[];
|
|
1171
|
+
}
|
|
1172
|
+
/** `action` with each tuning applied in turn, a later one's params over an
|
|
1173
|
+
* earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
|
|
1174
|
+
declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
|
|
1175
|
+
|
|
1240
1176
|
/**
|
|
1241
1177
|
* @experimental
|
|
1242
1178
|
* The entries an action contributes to a bar, menu or palette — one per
|
|
@@ -1290,20 +1226,9 @@ declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabl
|
|
|
1290
1226
|
declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | undefined;
|
|
1291
1227
|
|
|
1292
1228
|
/**
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1296
|
-
* the dispatcher calls `registry.get('selection')` at invocation time to
|
|
1297
|
-
* build a typed Deps bag.
|
|
1298
|
-
*
|
|
1299
|
-
* `DepSchema` — the map of dep name → value type — is the single source of
|
|
1300
|
-
* truth for what `requires`/`register`/`get` accept. Its concrete fields live
|
|
1301
|
-
* in `./depSchema` (co-located with the dep value types). It is declared there
|
|
1302
|
-
* as a plain `export interface` rather than via a cross-module
|
|
1303
|
-
* `declare module './depRegistry'` augmentation: that augmentation does NOT
|
|
1304
|
-
* merge once rollup-plugin-dts flattens both files into one `.d.ts` chunk,
|
|
1305
|
-
* which silently emptied `DepSchema` for consumers. Consumers still extend it
|
|
1306
|
-
* via `declare module '@weasel-js/core'` augmentation.
|
|
1229
|
+
* The dep registry: named "live source" thunks. Actions declare
|
|
1230
|
+
* `requires: ['selection']`; the dispatcher calls `registry.get('selection')`
|
|
1231
|
+
* at invocation time to build a typed Deps bag.
|
|
1307
1232
|
*/
|
|
1308
1233
|
|
|
1309
1234
|
/** Holds the live sources an action's declared dependencies resolve to.
|
|
@@ -1362,12 +1287,12 @@ type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {})
|
|
|
1362
1287
|
*
|
|
1363
1288
|
* Note: cursor is NOT here. `Tool.cursor` (inherited from `Contribution`)
|
|
1364
1289
|
* is already plumbed through `<Canvas>` to `style.cursor` on the host. */
|
|
1365
|
-
interface ToolPresentation
|
|
1290
|
+
interface ToolPresentation {
|
|
1366
1291
|
/** Human-readable label, distinct from the `id`. Falls back to `id`. */
|
|
1367
1292
|
label?: string;
|
|
1368
|
-
/** Inline-SVG icon component output
|
|
1369
|
-
*
|
|
1370
|
-
icon?: react.ReactNode | ((
|
|
1293
|
+
/** Inline-SVG icon component output: a static `ReactNode`, or a function
|
|
1294
|
+
* returning one. */
|
|
1295
|
+
icon?: react.ReactNode | (() => react.ReactNode);
|
|
1371
1296
|
/** Palette grouping key. Tools sharing a group render contiguously
|
|
1372
1297
|
* with separators between groups. Free-form string; the kit
|
|
1373
1298
|
* recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
|
|
@@ -1447,9 +1372,8 @@ interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, Co
|
|
|
1447
1372
|
}
|
|
1448
1373
|
|
|
1449
1374
|
/**
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
* binding-scope / matchBest logic on top.
|
|
1375
|
+
* Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
|
|
1376
|
+
* the actions-layer binding-scope / matchBest logic on top.
|
|
1453
1377
|
*/
|
|
1454
1378
|
|
|
1455
1379
|
/** Where a binding came from, which is also its priority: a held hotkey beats
|
|
@@ -1478,25 +1402,13 @@ interface MatchResult {
|
|
|
1478
1402
|
* only the body classification. `kindOf` predicates are handed the hit;
|
|
1479
1403
|
* `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
|
|
1480
1404
|
* `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
|
|
1481
|
-
* `bodyTarget` / `bodyKind` and never see it
|
|
1482
|
-
* over empty canvas used to read as empty canvas.
|
|
1405
|
+
* `bodyTarget` / `bodyKind` and never see it.
|
|
1483
1406
|
*
|
|
1484
1407
|
* A predicate declares which it is with `readsAffordance`; one that declares
|
|
1485
1408
|
* nothing, which only untyped code can write, counts as not reading the hit.
|
|
1486
1409
|
*/
|
|
1487
1410
|
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
1488
|
-
/**
|
|
1489
|
-
* Find the best-matching binding across all scopes.
|
|
1490
|
-
*
|
|
1491
|
-
* Precedence: hotkey > active > ambient.
|
|
1492
|
-
* Within a scope, more-specific bindings win; same-specificity bindings keep
|
|
1493
|
-
* registration order (see `matchSorted`).
|
|
1494
|
-
* Returns null when nothing matches.
|
|
1495
|
-
*
|
|
1496
|
-
* Note: this returns only the single best match. The dispatcher uses
|
|
1497
|
-
* `matchSorted` so it can fall through to lower-specificity matches when a
|
|
1498
|
-
* higher-specificity match's `enabled()` reports disabled.
|
|
1499
|
-
*/
|
|
1411
|
+
/** The first of {@link matchSorted}'s matches, or null when nothing matches. */
|
|
1500
1412
|
declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
|
|
1501
1413
|
/**
|
|
1502
1414
|
* Find every binding that matches the event, sorted by precedence
|
|
@@ -1505,12 +1417,7 @@ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], is
|
|
|
1505
1417
|
* - Scopes by priority: hotkey > active > ambient.
|
|
1506
1418
|
* - Within a scope, more-specific bindings beat less-specific (see
|
|
1507
1419
|
* `specificity()`).
|
|
1508
|
-
* - Among same-specificity bindings, first-declared wins
|
|
1509
|
-
* preserves registration order — same tiebreaker as pre-specificity).
|
|
1510
|
-
*
|
|
1511
|
-
* Used by the dispatcher to implement specificity-ordered fall-through:
|
|
1512
|
-
* when the top match's `enabled()` returns a disabled reason (or the
|
|
1513
|
-
* action's `start()` returns an empty handle), the next match is tried.
|
|
1420
|
+
* - Among same-specificity bindings, first-declared wins.
|
|
1514
1421
|
*/
|
|
1515
1422
|
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
1516
1423
|
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
@@ -1686,11 +1593,9 @@ interface RankedCandidate {
|
|
|
1686
1593
|
* `createDispatcher` in `dispatcher.ts` implements it.
|
|
1687
1594
|
*/
|
|
1688
1595
|
|
|
1689
|
-
/** Everything the dispatcher must consult to route one input event
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1692
|
-
* evaluated against. Rebuilt per event rather than held, so the dispatcher
|
|
1693
|
-
* itself stays stateless apart from in-flight gestures. */
|
|
1596
|
+
/** Everything the dispatcher must consult to route one input event. Rebuilt
|
|
1597
|
+
* per event rather than held, so the dispatcher itself stays stateless apart
|
|
1598
|
+
* from in-flight gestures. */
|
|
1694
1599
|
interface DispatcherContext {
|
|
1695
1600
|
/** All registered actions; the dispatcher walks `.defaultBinding` for ambient bindings. */
|
|
1696
1601
|
actions: ActionSource;
|
|
@@ -1741,9 +1646,7 @@ interface UiOngoingControl {
|
|
|
1741
1646
|
}
|
|
1742
1647
|
/**
|
|
1743
1648
|
* Successful `Dispatcher.resolveOnly` prediction: the binding + action that
|
|
1744
|
-
* would fire if `event` were dispatched for real.
|
|
1745
|
-
* descriptor so callers (the hover-cursor pump) can read metadata like
|
|
1746
|
-
* `Action.cursor` without a second registry lookup.
|
|
1649
|
+
* would fire if `event` were dispatched for real.
|
|
1747
1650
|
*/
|
|
1748
1651
|
interface ResolveOnlyResult {
|
|
1749
1652
|
actionId: string;
|
|
@@ -1796,14 +1699,12 @@ interface ResolveAllOptions {
|
|
|
1796
1699
|
* Evaluate eligibility and `enabled()` for candidates below the winner
|
|
1797
1700
|
* instead of short-circuiting them to `shadowed`.
|
|
1798
1701
|
*
|
|
1799
|
-
* Off by default
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1702
|
+
* Off by default: the default walk's early exit is what keeps
|
|
1703
|
+
* `resolveOnly`'s `enabled()` call count identical to a real dispatch, and
|
|
1704
|
+
* `enabled()` predicates are only contractually pure — not free.
|
|
1802
1705
|
*
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1805
|
-
* anyway". With it on, `shadowed` narrows to its precise meaning: this
|
|
1806
|
-
* candidate would have fired, but something above it did.
|
|
1706
|
+
* With it on, `shadowed` narrows to its precise meaning: this candidate
|
|
1707
|
+
* would have fired, but something above it did.
|
|
1807
1708
|
*/
|
|
1808
1709
|
evaluateShadowed?: boolean;
|
|
1809
1710
|
}
|
|
@@ -1825,12 +1726,10 @@ interface Dispatcher {
|
|
|
1825
1726
|
*/
|
|
1826
1727
|
handleInput(event: InputEvent, ctx: DispatcherContext): 'handled' | 'unhandled';
|
|
1827
1728
|
/**
|
|
1828
|
-
* Predict which action `event` would route to WITHOUT invoking it
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
* unhandled. Pure query: no invoker runs, no in-flight state changes, no
|
|
1833
|
-
* trace-log entry.
|
|
1729
|
+
* Predict which action `event` would route to WITHOUT invoking it: the
|
|
1730
|
+
* first candidate `handleInput`'s walk would fire, or `null` when the event
|
|
1731
|
+
* would go unhandled. Pure query: no invoker runs, no in-flight state
|
|
1732
|
+
* changes, no trace-log entry.
|
|
1834
1733
|
*
|
|
1835
1734
|
* Known divergence from a real dispatch: an ongoing invoker that matches
|
|
1836
1735
|
* but returns an empty handle at `start()` (runtime bail) makes the real
|
|
@@ -1841,25 +1740,15 @@ interface Dispatcher {
|
|
|
1841
1740
|
resolveOnly(event: InputEvent, ctx: DispatcherContext): ResolveOnlyResult | null;
|
|
1842
1741
|
/**
|
|
1843
1742
|
* Every binding that matches `event`, in dispatch precedence order, each
|
|
1844
|
-
* with a verdict explaining whether it would fire.
|
|
1845
|
-
* `resolveOnly`
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
* `resolveOnly`'s walk would filter out are kept here and labelled
|
|
1849
|
-
* `ineligible` instead. Pure query: no invoker runs, no in-flight state
|
|
1850
|
-
* changes, no trace-log entry.
|
|
1743
|
+
* with a verdict explaining whether it would fire. Nothing is dropped:
|
|
1744
|
+
* candidates that `resolveOnly`'s walk would filter out are kept here and
|
|
1745
|
+
* labeled `ineligible` instead. Pure query, and it shares `resolveOnly`'s
|
|
1746
|
+
* known divergence from a real dispatch.
|
|
1851
1747
|
*
|
|
1852
1748
|
* `resolveOnly` is the first `would-fire` entry of this list.
|
|
1853
1749
|
*
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1856
|
-
* real dispatch fall through, and this cannot see that.
|
|
1857
|
-
*
|
|
1858
|
-
* By default everything below the winner is `shadowed` without being asked,
|
|
1859
|
-
* which is what keeps this walk as cheap as the dispatch it replays. Pass
|
|
1860
|
-
* `{ evaluateShadowed: true }` to keep evaluating past the winner, so a
|
|
1861
|
-
* lower candidate that is ALSO ineligible or disabled says so — see
|
|
1862
|
-
* {@link ResolveAllOptions.evaluateShadowed}.
|
|
1750
|
+
* By default everything below the winner is `shadowed` without being asked
|
|
1751
|
+
* — see {@link ResolveAllOptions.evaluateShadowed}.
|
|
1863
1752
|
*/
|
|
1864
1753
|
resolveAll(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): ResolvedCandidate[];
|
|
1865
1754
|
/**
|
|
@@ -1872,7 +1761,6 @@ interface Dispatcher {
|
|
|
1872
1761
|
explain(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): DispatchRecord;
|
|
1873
1762
|
/**
|
|
1874
1763
|
* Synthesize an end-of-gesture for every in-flight ongoing handle.
|
|
1875
|
-
* Used by tool-switch cancellation (Q2 decision).
|
|
1876
1764
|
*/
|
|
1877
1765
|
cancelAll(reason: 'commit' | 'cancel'): void;
|
|
1878
1766
|
/**
|
|
@@ -1888,41 +1776,31 @@ interface Dispatcher {
|
|
|
1888
1776
|
*/
|
|
1889
1777
|
inFlightCursor(): CursorSpec | null;
|
|
1890
1778
|
/**
|
|
1891
|
-
* Read-only iterator over currently in-flight `OngoingHandle` instances
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
* to walk each handle's `previewIds()` / `previewPose(id)` and render
|
|
1895
|
-
* dispatcher-driven gesture previews. Read-only by design:
|
|
1896
|
-
* external consumers must not mutate the in-flight map.
|
|
1779
|
+
* Read-only iterator over currently in-flight `OngoingHandle` instances,
|
|
1780
|
+
* for a preview layer to walk each handle's `previewIds()` /
|
|
1781
|
+
* `previewPose(id)`.
|
|
1897
1782
|
*/
|
|
1898
1783
|
getInFlightHandles(): Iterable<OngoingHandle>;
|
|
1899
1784
|
/**
|
|
1900
|
-
* Subscribe to in-flight state changes. The callback fires after
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
1904
|
-
* flight. Input that arrives with nothing in flight and starts nothing
|
|
1905
|
-
* does not fire it. Consumers re-read `getInFlightHandles()` and re-render.
|
|
1785
|
+
* Subscribe to in-flight state changes. The callback fires after handle
|
|
1786
|
+
* start, every `onMove` pump, end, cancel, and cancel-all, and after any
|
|
1787
|
+
* other input handled while a gesture is in flight. Input that arrives
|
|
1788
|
+
* with nothing in flight and starts nothing does not fire it.
|
|
1906
1789
|
*
|
|
1907
1790
|
* Returns an unsubscribe function.
|
|
1908
1791
|
*/
|
|
1909
1792
|
subscribe(fn: () => void): () => void;
|
|
1910
1793
|
/**
|
|
1911
1794
|
* Monotonic counter bumped on exactly the events {@link subscribe} fires
|
|
1912
|
-
* on
|
|
1913
|
-
* with `subscribe` to drive a render off in-flight gesture state without
|
|
1914
|
-
* a `useReducer` force-rerender.
|
|
1795
|
+
* on: the snapshot half of the `useSyncExternalStore` contract.
|
|
1915
1796
|
*
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
1919
|
-
* counts while a gesture is in flight — see `subscribe`).
|
|
1797
|
+
* A bump does **not** guarantee that anything observable changed (input
|
|
1798
|
+
* that matched no binding still counts while a gesture is in flight — see
|
|
1799
|
+
* `subscribe`).
|
|
1920
1800
|
*/
|
|
1921
1801
|
getVersion(): number;
|
|
1922
1802
|
/**
|
|
1923
|
-
* Snapshot of the currently active action
|
|
1924
|
-
* visibility rules, debug HUDs) that need to react to "what action
|
|
1925
|
-
* is in flight right now."
|
|
1803
|
+
* Snapshot of the currently active action.
|
|
1926
1804
|
*
|
|
1927
1805
|
* - `kind` — the `OngoingHandle.kind` reported by the in-flight
|
|
1928
1806
|
* handle (e.g. `'marquee'`, `'move'`). `null` when no action is
|
|
@@ -1933,26 +1811,15 @@ interface Dispatcher {
|
|
|
1933
1811
|
*
|
|
1934
1812
|
* When multiple handles are in flight simultaneously (e.g. a key-held
|
|
1935
1813
|
* action overlapping a pointer action), the most-recently-started
|
|
1936
|
-
* handle wins.
|
|
1937
|
-
* the one consumers care about.
|
|
1938
|
-
*
|
|
1939
|
-
* That rule used to be near-vacuous on the pointer side, because every
|
|
1940
|
-
* pointer shared one handle slot and two pointer drags could not coexist.
|
|
1941
|
-
* With per-pointer keying they can — but only via paths that bypass the
|
|
1942
|
-
* multi-pointer policy in `useGestureDispatcher` (which stops a second
|
|
1943
|
-
* finger from opening a drag while a pinch is live), such as a mouse and
|
|
1944
|
-
* a pen used together. Latest-start remains the right answer there.
|
|
1814
|
+
* handle wins.
|
|
1945
1815
|
*/
|
|
1946
1816
|
getActiveAction(): {
|
|
1947
1817
|
kind: string | null;
|
|
1948
1818
|
id: string | null;
|
|
1949
1819
|
};
|
|
1950
1820
|
/**
|
|
1951
|
-
* Start an ongoing action driven by UI (not a gesture)
|
|
1952
|
-
*
|
|
1953
|
-
* `action.invoker.start(ctx, { params })`, and registers the returned
|
|
1954
|
-
* handle in the in-flight map so `getInFlightHandles()` reports it —
|
|
1955
|
-
* enabling preview rendering via `SceneCanvas`.
|
|
1821
|
+
* Start an ongoing action driven by UI (not a gesture), and register its
|
|
1822
|
+
* handle in the in-flight map so `getInFlightHandles()` reports it.
|
|
1956
1823
|
*
|
|
1957
1824
|
* Returns `null` if `actionId` is unknown, the action's invoker is not
|
|
1958
1825
|
* ongoing, or `start` returned an empty handle.
|
|
@@ -1969,10 +1836,8 @@ interface Dispatcher {
|
|
|
1969
1836
|
*/
|
|
1970
1837
|
|
|
1971
1838
|
/** One entry per "mode change" — a kit state transition that isn't a
|
|
1972
|
-
* dispatched input event but
|
|
1973
|
-
*
|
|
1974
|
-
* consumers include the editAnchors dep (editingId changes), and
|
|
1975
|
-
* could expand to active-tool / hotkey-stack / focus changes. */
|
|
1839
|
+
* dispatched input event but explains why an input was (or wasn't) handled
|
|
1840
|
+
* later. Pushed via `recordModeSwitch()`. */
|
|
1976
1841
|
interface ModeSwitchLogEntry {
|
|
1977
1842
|
kind: 'mode';
|
|
1978
1843
|
ts: number;
|
|
@@ -1991,12 +1856,31 @@ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
|
1991
1856
|
declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
1992
1857
|
|
|
1993
1858
|
/**
|
|
1994
|
-
*
|
|
1859
|
+
* The slot an in-flight ongoing handle is keyed by:
|
|
1860
|
+
* - `key-held`: `key-held-<code>` (`key-held-Space`), so two keys that share a
|
|
1861
|
+
* `key`, such as the two Shifts, hold separately. An event with no code keys
|
|
1862
|
+
* by `key`.
|
|
1863
|
+
* - `pointerdown` and the drag that follows: `pointer-<pointerId>`. An event
|
|
1864
|
+
* with no `pointerId` (a synthesized probe, a programmatic drag, most tests)
|
|
1865
|
+
* keys to `pointer-mouse`.
|
|
1866
|
+
* - `multitouch`: `multitouch-<fingers>`.
|
|
1867
|
+
* - any other kind that starts an ongoing invoker: `ongoing-<kind>`.
|
|
1868
|
+
*/
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* The in-flight handle slot a pointer's gestures key into. Exported because
|
|
1872
|
+
* the React seam (`useGestureDispatcher`) has to name the same slot when it
|
|
1873
|
+
* asks `dispatcher.inFlight()` whether a given pointer is mid-drag.
|
|
1995
1874
|
*
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
1998
|
-
|
|
1999
|
-
|
|
1875
|
+
* @param pointerId - `PointerEvent.pointerId`, or `undefined` for a
|
|
1876
|
+
* synthesized event with no originating DOM pointer.
|
|
1877
|
+
*/
|
|
1878
|
+
declare function pointerGestureId(pointerId: number | undefined): string;
|
|
1879
|
+
/** The in-flight handle slot a multitouch gesture of `fingers` fingers keys into. */
|
|
1880
|
+
declare function multiTouchGestureId(fingers: number): string;
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* Dispatcher orchestrator — pure module, no React, no DOM.
|
|
2000
1884
|
*
|
|
2001
1885
|
* ## Precedence and fall-through
|
|
2002
1886
|
* An exclusive affordance claim first bars every binding that does not
|
|
@@ -2012,46 +1896,14 @@ declare function recordModeSwitch(mode: string, from: string | null, to: string
|
|
|
2012
1896
|
* Candidates whose `eligible` rule is false are dropped first. The dispatcher
|
|
2013
1897
|
* walks the rest and fires the first action whose `enabled()` returns
|
|
2014
1898
|
* `true`. If every candidate's `enabled()` returns a disabled reason, the
|
|
2015
|
-
* event is unhandled.
|
|
2016
|
-
* `:not(:disabled)` filter, and lets a tool declare a high-specificity
|
|
2017
|
-
* binding (e.g. drag-on-empty → areaSelect) that gracefully falls through
|
|
2018
|
-
* to a lower-specificity ambient binding (e.g. drag → viewport.dragPan)
|
|
2019
|
-
* when its required deps aren't wired.
|
|
2020
|
-
*
|
|
2021
|
-
* ## gestureId scheme
|
|
2022
|
-
* - `key-held` ongoing actions: `key-held-<code>` (`key-held-Space`), so two
|
|
2023
|
-
* keys that share a `key`, such as the two Shifts, hold separately. An event
|
|
2024
|
-
* with no code keys by `key`.
|
|
2025
|
-
* Chosen because key-held gestures are identified by the held key alone.
|
|
2026
|
-
* - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`, taken from
|
|
2027
|
-
* the originating DOM `PointerEvent`. Each physical pointer — mouse, each
|
|
2028
|
-
* touch, the stylus — gets its own handle slot. Events with no
|
|
2029
|
-
* `pointerId` (synthesized probes, programmatic drags, most tests) key to
|
|
2030
|
-
* `pointer-mouse`, so a single synthetic pointer behaves as it always has.
|
|
2031
|
-
* - `multitouch` ongoing actions: `multitouch-<fingers>`.
|
|
2032
|
-
* - Fallback for any other kind that triggers an ongoing invoker: `ongoing-<kind>`.
|
|
1899
|
+
* event is unhandled.
|
|
2033
1900
|
*
|
|
2034
1901
|
* ## Action-lookup miss behavior
|
|
2035
1902
|
* When the walk reaches a binding whose `actionId` has no entry in
|
|
2036
1903
|
* `ctx.actions.list()`, the dispatcher emits `console.warn` and returns
|
|
2037
|
-
* `'unhandled'
|
|
2038
|
-
* This preserves input flow (nothing is swallowed silently) while flagging
|
|
2039
|
-
* the misconfiguration at dev time.
|
|
1904
|
+
* `'unhandled'`, so the gesture falls through as if unmatched.
|
|
2040
1905
|
*/
|
|
2041
1906
|
|
|
2042
|
-
/**
|
|
2043
|
-
* The in-flight handle slot a pointer's gestures key into.
|
|
2044
|
-
*
|
|
2045
|
-
* Exported because the React seam (`useGestureDispatcher`) has to name the
|
|
2046
|
-
* same slot when it asks `dispatcher.inFlight()` whether a given pointer is
|
|
2047
|
-
* mid-drag. That used to be the string literal `'pointer-mouse'` on both
|
|
2048
|
-
* sides, which was correct only because `gestureIdFor` ignored the pointer id
|
|
2049
|
-
* entirely — the two lies cancelled out.
|
|
2050
|
-
*
|
|
2051
|
-
* @param pointerId - `PointerEvent.pointerId`, or `undefined` for a
|
|
2052
|
-
* synthesized event with no originating DOM pointer.
|
|
2053
|
-
*/
|
|
2054
|
-
declare function pointerGestureId(pointerId: number | undefined): string;
|
|
2055
1907
|
/** Build a dispatcher. `getAction` overrides how action ids are resolved;
|
|
2056
1908
|
* by default the `DispatcherContext`'s registry is used. */
|
|
2057
1909
|
declare function createDispatcher(opts?: {
|
|
@@ -2087,13 +1939,17 @@ interface ActionsRegistry {
|
|
|
2087
1939
|
* `trigger` nor `begin` will fire it. Returns a release. This is the
|
|
2088
1940
|
* "not for me" door — `unregister` is the "should not exist" one. */
|
|
2089
1941
|
mute(id: string): () => void;
|
|
1942
|
+
/** Tune `id` here and in scopes below: its params and added bindings
|
|
1943
|
+
* apply to whichever descriptor is live for it, registered before the
|
|
1944
|
+
* tuning or after. Returns a release. See {@link ActionTuning}. */
|
|
1945
|
+
tune(id: string, tuning: ActionTuning): () => void;
|
|
2090
1946
|
list(): readonly Action[];
|
|
2091
1947
|
/** Fire an immediate-invoker action by id. The optional `params` arg is
|
|
2092
1948
|
* forwarded to `ImmediateInvoker.run` as its second argument — use it for
|
|
2093
1949
|
* parametric actions (e.g. `trigger('tool.activate', { toolId: 'rect' })`).
|
|
2094
|
-
* Returns whether it ran one: `false` for an unknown id,
|
|
2095
|
-
* ongoing-invoker action, which
|
|
2096
|
-
*
|
|
1950
|
+
* Returns whether it ran one: `false` for an unknown id, for an action
|
|
1951
|
+
* whose `enabled` refuses, and for an ongoing-invoker action, which
|
|
1952
|
+
* `trigger` cannot reach (see `begin`). `enabled` is asked with no point. */
|
|
2097
1953
|
trigger(id: string, params?: Record<string, unknown>): boolean;
|
|
2098
1954
|
/**
|
|
2099
1955
|
* Subscribe to registry mutations. The callback fires after any
|
|
@@ -2162,7 +2018,11 @@ declare function offhandKeyFor(trigger: HotkeyTrigger): string;
|
|
|
2162
2018
|
/** Build the per-tool `BoundGesture` list for the consolidated action's
|
|
2163
2019
|
* `defaultBinding`. Each entry pairs a `key-held` spec with
|
|
2164
2020
|
* `opts.params.toolId` so the invoker's `start` can pick out which tool
|
|
2165
|
-
* to engage.
|
|
2021
|
+
* to engage.
|
|
2022
|
+
*
|
|
2023
|
+
* An offhand tool engages only between gestures. Swapping the tool out
|
|
2024
|
+
* mid-drag would cancel the drag, and the key is free for the gesture's own
|
|
2025
|
+
* tool to use — Space repositions a shape being drawn. */
|
|
2166
2026
|
declare function buildToolOffhandBindings(specs: readonly ToolOffhandBindingSpec[]): BoundGesture[];
|
|
2167
2027
|
/** The `key-held` spec for holding `key` (a `KeyboardEvent.key`, or several).
|
|
2168
2028
|
* A modifier key gets every modifier optional, since its own keydown reports
|
|
@@ -2222,13 +2082,15 @@ declare const isControlHandle: TargetPredicate & {
|
|
|
2222
2082
|
declare const isAnchorOrControl: TargetPredicate & {
|
|
2223
2083
|
readsAffordance: true;
|
|
2224
2084
|
};
|
|
2085
|
+
/** The outline of the path being edited, between its anchors. */
|
|
2086
|
+
declare const isSegment: TargetPredicate & {
|
|
2087
|
+
readsAffordance: true;
|
|
2088
|
+
};
|
|
2089
|
+
/** Anything path edit puts on the path: an anchor, a control, or a segment. */
|
|
2090
|
+
declare const isPathEditPart: TargetPredicate & {
|
|
2091
|
+
readsAffordance: true;
|
|
2092
|
+
};
|
|
2225
2093
|
|
|
2226
|
-
/**
|
|
2227
|
-
* The long-press the gesture dispatcher is timing, as observable state: where
|
|
2228
|
-
* the press landed, when the hold started, how far through it is, and whether
|
|
2229
|
-
* any binding would fire on it. Feedback — a filling ring, a haptic — reads
|
|
2230
|
-
* this; nothing here decides what the feedback looks like.
|
|
2231
|
-
*/
|
|
2232
2094
|
/** A press being held toward a long-press. */
|
|
2233
2095
|
interface PendingLongPress {
|
|
2234
2096
|
pointerId: number;
|
|
@@ -2281,8 +2143,7 @@ interface LongPressState {
|
|
|
2281
2143
|
interface LongPressStore extends LongPressState {
|
|
2282
2144
|
set(next: PendingLongPress | null): void;
|
|
2283
2145
|
}
|
|
2284
|
-
|
|
2285
|
-
declare const LONG_PRESS_MS = 500;
|
|
2146
|
+
|
|
2286
2147
|
/** How `useGestureDispatcher` times, publishes and signals a long-press. */
|
|
2287
2148
|
interface LongPressOptions {
|
|
2288
2149
|
/** Hold time in ms. Default {@link LONG_PRESS_MS}. */
|
|
@@ -2450,6 +2311,10 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
|
|
|
2450
2311
|
* `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`
|
|
2451
2312
|
* while it is not. Install it with `<SceneCanvas ambient>`.
|
|
2452
2313
|
*
|
|
2314
|
+
* An entry key one mode shares with another mode's leaving key is reported as
|
|
2315
|
+
* a route conflict unless `canEnter` is given: that is what keeps the entry
|
|
2316
|
+
* key from firing inside the other mode.
|
|
2317
|
+
*
|
|
2453
2318
|
* The bindings ride the hotkey tier, so leaving a mode outranks what the same
|
|
2454
2319
|
* key does otherwise (Escape clearing the selection), and they yield while a
|
|
2455
2320
|
* gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
|
|
@@ -2479,101 +2344,36 @@ interface ToolKeybinding {
|
|
|
2479
2344
|
}
|
|
2480
2345
|
/** Modifier-key snapshot at event dispatch time. */
|
|
2481
2346
|
type ToolModifiers = ModifierState;
|
|
2482
|
-
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
2483
|
-
* via the tool's `TScratch` parameter; it survives across a single
|
|
2484
|
-
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
2485
|
-
* gesture start by `initScratch()`. */
|
|
2486
|
-
interface ToolCtx<TScratch = unknown> {
|
|
2487
|
-
worldX: number;
|
|
2488
|
-
worldY: number;
|
|
2489
|
-
modifiers: ToolModifiers;
|
|
2490
|
-
selection: SelectionApi;
|
|
2491
|
-
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
2492
|
-
* cast to a known shape. This layer doesn't constrain it. */
|
|
2493
|
-
adapter: unknown;
|
|
2494
|
-
applyOps: (ops: Op[], label: string) => void;
|
|
2495
|
-
/** Current viewport. Reflects camera-position semantics — see
|
|
2496
|
-
* `View` JSDoc. */
|
|
2497
|
-
view: View;
|
|
2498
|
-
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
2499
|
-
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
2500
|
-
* state. View changes are not undoable. */
|
|
2501
|
-
setView: (next: View) => void;
|
|
2502
|
-
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
2503
|
-
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
2504
|
-
* anchors. */
|
|
2505
|
-
canvasRect: DOMRect;
|
|
2506
|
-
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
2507
|
-
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
2508
|
-
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
2509
|
-
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
2510
|
-
debug?: DebugSink;
|
|
2511
|
-
scratch: TScratch;
|
|
2512
|
-
}
|
|
2513
|
-
/**
|
|
2514
|
-
* What `onActivate` / `onDeactivate` receive: the tool's scratch, from
|
|
2515
|
-
* `initScratch()`. A lifecycle moment has no pointer, hit or modifier state to
|
|
2516
|
-
* report, so nothing else is populated; a callback needing the selection or
|
|
2517
|
-
* the scene closes over it, as the tool's actions do.
|
|
2518
|
-
*/
|
|
2519
|
-
interface ToolLifecycleCtx<TScratch = unknown> {
|
|
2520
|
-
scratch: TScratch;
|
|
2521
|
-
}
|
|
2522
|
-
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
2523
|
-
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
2524
|
-
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
2525
|
-
type ToolBounds = Bounds;
|
|
2526
2347
|
/**
|
|
2527
2348
|
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
2528
|
-
* into, plus the hooks that only make sense for one (
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2349
|
+
* into, plus the hooks that only make sense for one (activate/deactivate,
|
|
2350
|
+
* `cursor`). Everything else — bindings, actions, overlay, presentation — is
|
|
2351
|
+
* inherited.
|
|
2531
2352
|
*/
|
|
2532
|
-
interface Tool<
|
|
2353
|
+
interface Tool<TOverlay = KernelOverlay> extends Contribution<TOverlay> {
|
|
2533
2354
|
/** The key that activates this tool. `useKeybindings` appends a binding
|
|
2534
2355
|
* entry for it to the consolidated `tool.activate` action (with
|
|
2535
2356
|
* `opts.params.toolId` set so the invoker knows which tool to switch to).
|
|
2536
2357
|
* A tool with none has no activation key. */
|
|
2537
2358
|
keybinding?: ToolKeybinding;
|
|
2538
|
-
|
|
2539
|
-
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
2359
|
+
cursor?: CursorSpec | (() => CursorSpec);
|
|
2540
2360
|
/** The tool became live: it took the active slot, or its `hotkey` went
|
|
2541
|
-
* down, while it held neither.
|
|
2542
|
-
|
|
2361
|
+
* down, while it held neither. A lifecycle moment has no pointer, hit or
|
|
2362
|
+
* modifier state to report; a callback needing the selection or the scene
|
|
2363
|
+
* closes over it, as the tool's actions do. */
|
|
2364
|
+
onActivate?: () => void;
|
|
2543
2365
|
/** The tool stopped being live in both slots, or its canvas unmounted. */
|
|
2544
|
-
onDeactivate?: (
|
|
2545
|
-
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
2546
|
-
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
2547
|
-
* reflect live gesture state without reaching into hook internals. The
|
|
2548
|
-
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
2549
|
-
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
2550
|
-
* the use site. */
|
|
2551
|
-
previewPose?: (id: string) => unknown;
|
|
2552
|
-
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
2553
|
-
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
2554
|
-
* can compute bounds without round-tripping through a geometry adapter. */
|
|
2555
|
-
previewBounds?: (id: string) => ToolBounds | null;
|
|
2556
|
-
/** Returns ids whose committed scene-render should be suppressed while this
|
|
2557
|
-
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
2558
|
-
* preview ghosts replace the committed pose). The standard scene slot
|
|
2559
|
-
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
2560
|
-
* `null` when no gesture is in flight. */
|
|
2561
|
-
previewIds?: () => Iterable<string> | null;
|
|
2366
|
+
onDeactivate?: () => void;
|
|
2562
2367
|
}
|
|
2563
2368
|
/** Whether `entry` is a tool: a contribution that can hold focus, chosen from
|
|
2564
|
-
* a palette or held on a key. Only a tool's focus hooks (`
|
|
2565
|
-
* `
|
|
2566
|
-
declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<
|
|
2369
|
+
* a palette or held on a key. Only a tool's focus hooks (`cursor`,
|
|
2370
|
+
* `onActivate`, `onDeactivate`) ever run. */
|
|
2371
|
+
declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<TOverlay>;
|
|
2567
2372
|
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
2568
2373
|
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
2569
|
-
/**
|
|
2570
|
-
* dispatchers that
|
|
2571
|
-
|
|
2572
|
-
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
2573
|
-
type AnyTool = Tool<any, any>;
|
|
2574
|
-
/** "A tool of any scratch type that draws `TOverlay`" — the container form for
|
|
2575
|
-
* registries that hold heterogeneous scratch shapes but one overlay type. */
|
|
2576
|
-
type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
2374
|
+
/** A tool drawing any overlay type — the element type for registries and
|
|
2375
|
+
* dispatchers that never read the overlay. */
|
|
2376
|
+
type AnyTool = Tool<any>;
|
|
2577
2377
|
|
|
2578
2378
|
/**
|
|
2579
2379
|
* What a tool declares.
|
|
@@ -2584,7 +2384,7 @@ type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
|
2584
2384
|
* definition is mostly `bindings`, plus presentation and any actions the tool
|
|
2585
2385
|
* itself introduces.
|
|
2586
2386
|
*/
|
|
2587
|
-
interface ToolDef<
|
|
2387
|
+
interface ToolDef<TOverlay = KernelOverlay> {
|
|
2588
2388
|
id: string;
|
|
2589
2389
|
/** Capability tags for modality eligibility. `defineTool` forwards these
|
|
2590
2390
|
* onto `Tool.eligibility.capabilities`, which is where every gate reads
|
|
@@ -2607,7 +2407,7 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
|
2607
2407
|
* Introspection-only — do not make this load-bearing in production.
|
|
2608
2408
|
* Read off the def via `Tool.def` (the reflection escape hatch). */
|
|
2609
2409
|
hookName?: string;
|
|
2610
|
-
presentation?: ToolPresentation
|
|
2410
|
+
presentation?: ToolPresentation;
|
|
2611
2411
|
/** Optional caller-supplied activation key. Most built-in tools have their
|
|
2612
2412
|
* activation key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`;
|
|
2613
2413
|
* this field is for tools that want their activation key to be
|
|
@@ -2625,17 +2425,10 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
|
2625
2425
|
hotkey?: HotkeyTrigger;
|
|
2626
2426
|
/** Fires as the tool becomes live in the active or hotkey slot. See
|
|
2627
2427
|
* `Tool.onActivate`. */
|
|
2628
|
-
onActivate?: (
|
|
2428
|
+
onActivate?: () => void;
|
|
2629
2429
|
/** Fires as the tool stops being live in either slot. */
|
|
2630
|
-
onDeactivate?: (
|
|
2631
|
-
cursor?: CursorSpec | ((
|
|
2632
|
-
/** Override the default scratch initializer. Default is `() => null`
|
|
2633
|
-
* cast to `TScratch`, which works for tools whose scratch is fresh
|
|
2634
|
-
* every gesture. Tools that need scratch identity to survive across
|
|
2635
|
-
* gesture boundaries (e.g. the pen tool's multi-click subpath state)
|
|
2636
|
-
* pass a stable-ref-returning thunk here. The factory forwards this
|
|
2637
|
-
* onto the returned `Tool.initScratch`. */
|
|
2638
|
-
initScratch?: () => TScratch;
|
|
2430
|
+
onDeactivate?: () => void;
|
|
2431
|
+
cursor?: CursorSpec | (() => CursorSpec);
|
|
2639
2432
|
/** Declarative gesture bindings, forwarded onto `Tool.bindings`. The
|
|
2640
2433
|
* gesture dispatcher consults these at active scope while this tool is the
|
|
2641
2434
|
* active one, and at hotkey scope while it is held. This is the tool's
|
|
@@ -2648,7 +2441,7 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
|
2648
2441
|
*
|
|
2649
2442
|
* The layer's `draw` closure should read dynamic state through refs or
|
|
2650
2443
|
* closures captured in the enclosing render scope, and gate on it there —
|
|
2651
|
-
* `if (!
|
|
2444
|
+
* `if (!state.something) return []` — rather than expecting the kit to
|
|
2652
2445
|
* swap layers as the gesture progresses.
|
|
2653
2446
|
*
|
|
2654
2447
|
* Pass an array to contribute several layers; they render in order. */
|
|
@@ -2659,20 +2452,10 @@ interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
|
2659
2452
|
}
|
|
2660
2453
|
|
|
2661
2454
|
/**
|
|
2662
|
-
* Build a `Tool
|
|
2663
|
-
*
|
|
2664
|
-
* This used to be a translator: `ToolDef` carried `initial` / `engaged` phase
|
|
2665
|
-
* tables of hit-keyed route handlers, and `defineTool` compiled them into the
|
|
2666
|
-
* imperative `pointer` / `drag` / `keyboard` / `wheel` handlers the
|
|
2667
|
-
* tool-routing dispatcher called. Both ends of that are gone — tools declare
|
|
2668
|
-
* `bindings` and the gesture dispatcher routes them — so what remains is
|
|
2669
|
-
* identity plumbing plus the id check and the cursor resolver.
|
|
2670
|
-
*
|
|
2671
|
-
* It stays a function rather than becoming a spread because the id validation
|
|
2672
|
-
* and the `initScratch` / `cursor` defaults are worth applying uniformly, and
|
|
2673
|
-
* because `Tool.def` gives reflection a handle on the authored form.
|
|
2455
|
+
* Build a `Tool` from a declarative `ToolDef`: validates the id, applies the
|
|
2456
|
+
* `cursor` default, and keeps the authored form on `Tool.def` for reflection.
|
|
2674
2457
|
*/
|
|
2675
|
-
declare function defineTool<
|
|
2458
|
+
declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
|
|
2676
2459
|
|
|
2677
2460
|
/**
|
|
2678
2461
|
* One row in the route registry — a single `GestureBinding` on one tool,
|
|
@@ -2720,6 +2503,10 @@ interface RegistryEntry {
|
|
|
2720
2503
|
/** The binding's `opts.views`: the views it is live in. Absent when it is
|
|
2721
2504
|
* live in every view. */
|
|
2722
2505
|
views?: readonly (string | null)[];
|
|
2506
|
+
/** The binding's `opts.params`, as declared: a thunk is not called. */
|
|
2507
|
+
params?: unknown;
|
|
2508
|
+
/** The binding's `opts.yields`, when it is set. */
|
|
2509
|
+
yields?: true;
|
|
2723
2510
|
}
|
|
2724
2511
|
/**
|
|
2725
2512
|
* Stand-in target for a `{ kindOf }` predicate spec.
|
|
@@ -2756,10 +2543,8 @@ declare function buildRouteRegistry(entries: readonly Contribution<unknown>[]):
|
|
|
2756
2543
|
*/
|
|
2757
2544
|
declare function routesForSpec(spec: GestureSpec): readonly string[];
|
|
2758
2545
|
|
|
2759
|
-
/** Two or more
|
|
2760
|
-
* modifiers) tuple
|
|
2761
|
-
* arbitrarily (well, deterministically by slot order, but the author
|
|
2762
|
-
* probably didn't intend the duplication). */
|
|
2546
|
+
/** Two or more bindings declare the same exact (phase, gesture, arg, target,
|
|
2547
|
+
* modifiers) tuple, so slot order alone picks one. */
|
|
2763
2548
|
interface Conflict {
|
|
2764
2549
|
phase: 'initial' | 'engaged' | 'any';
|
|
2765
2550
|
gesture: GestureName;
|
|
@@ -2780,39 +2565,32 @@ interface Conflict {
|
|
|
2780
2565
|
* required against Alt required fire on different inputs, and Shift
|
|
2781
2566
|
* required outranks Shift optional. An optional modifier against a
|
|
2782
2567
|
* forbidden one is flagged, since both take the bare press.
|
|
2783
|
-
* -
|
|
2784
|
-
*
|
|
2785
|
-
*
|
|
2786
|
-
* their UI layer.
|
|
2787
|
-
*
|
|
2788
|
-
* - Two `{ kindOf }` predicate targets. The route grammar renders every
|
|
2789
|
-
* predicate as the single token {@link PREDICATE_TARGET}, so bucketing on
|
|
2790
|
-
* the rendered target alone reported select's `resize` / `rotate` / `move`
|
|
2791
|
-
* drags — three genuinely different predicates — as a three-way conflict.
|
|
2792
|
-
* Predicate entries bucket by function identity instead, which means two
|
|
2793
|
-
* *separately written but equivalent* predicates go unflagged. That's the
|
|
2794
|
-
* right way to be wrong here: this check has to be silent when nothing is
|
|
2795
|
-
* wrong or nobody will keep it on.
|
|
2796
|
-
*
|
|
2568
|
+
* - Two `{ kindOf }` predicate targets, unless they are the same function.
|
|
2569
|
+
* Predicates bucket by identity, so two separately written but
|
|
2570
|
+
* equivalent ones go unflagged.
|
|
2797
2571
|
* - Two actions whose `eligible` rules can never hold together — each
|
|
2798
2572
|
* mode's own Escape exit, say. See {@link rulesExclusive}.
|
|
2799
|
-
*
|
|
2800
2573
|
* - Two bindings whose `opts.views` keep them from being live and equally
|
|
2801
|
-
* ranked in any one view.
|
|
2574
|
+
* ranked in any one view.
|
|
2575
|
+
* - Two bindings of one action with equal params that tie on one platform
|
|
2576
|
+
* only: `mod` against `ctrl`, which are one key off a Mac and two on it.
|
|
2577
|
+
* The same pair tying on both platforms is a plain duplicate and is flagged.
|
|
2578
|
+
*
|
|
2579
|
+
* A binding whose action declines via `enabled()` so a lower-priority one
|
|
2580
|
+
* takes the gesture is flagged all the same, since seeing that intent would
|
|
2581
|
+
* mean evaluating the action, unless it says so with `opts.yields`. A pair
|
|
2582
|
+
* whose earlier binding yields is not a conflict; one whose later binding
|
|
2583
|
+
* yields still is, as the earlier one decides alone.
|
|
2802
2584
|
*
|
|
2803
|
-
*
|
|
2804
|
-
*
|
|
2805
|
-
* keys) — so a conflict may name one tool twice.
|
|
2585
|
+
* Two bindings on the same tool can share a tuple, so a conflict may name
|
|
2586
|
+
* one tool twice.
|
|
2806
2587
|
*
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
*
|
|
2588
|
+
* Feeding this a whole tool *registry* over-reports, because registry tools
|
|
2589
|
+
* take turns in the active slot and can't collide with each other — see
|
|
2590
|
+
* `findScopedConflicts`.
|
|
2810
2591
|
*/
|
|
2811
2592
|
declare function findConflicts(entries: readonly Contribution<unknown>[]): Conflict[];
|
|
2812
|
-
/**
|
|
2813
|
-
* The tool scopes as the dispatcher sees them — which is what decides whether
|
|
2814
|
-
* two same-tuple bindings can actually collide.
|
|
2815
|
-
*/
|
|
2593
|
+
/** The tool scopes as the dispatcher sees them. */
|
|
2816
2594
|
interface ToolScopes {
|
|
2817
2595
|
/** Tools eligible for the active slot, keyed by id or as a flat list. */
|
|
2818
2596
|
registry: readonly Contribution<unknown>[] | Readonly<Record<string, Contribution<unknown>>>;
|
|
@@ -2829,17 +2607,13 @@ interface ToolScopes {
|
|
|
2829
2607
|
/**
|
|
2830
2608
|
* Detect reachable route conflicts in an assembled tool set and report each one.
|
|
2831
2609
|
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
2834
|
-
* looked. Call it once wherever a tool set is assembled (`useTools` does),
|
|
2835
|
-
* behind a `process.env.NODE_ENV !== 'production'` guard.
|
|
2610
|
+
* Call it once wherever a tool set is assembled, behind a
|
|
2611
|
+
* `process.env.NODE_ENV !== 'production'` guard.
|
|
2836
2612
|
*
|
|
2837
2613
|
* **Warn, never throw.** A conflict between a consumer's tool and a kit tool
|
|
2838
|
-
* is
|
|
2839
|
-
* the
|
|
2840
|
-
*
|
|
2841
|
-
* always a bug, but the place to fail on that is the kit's own test suite
|
|
2842
|
-
* (`canvas/SceneCanvas.routeConflicts.test.tsx`), not a consumer's console.
|
|
2614
|
+
* is sometimes deliberate, since the loser can still take the gesture when
|
|
2615
|
+
* the winner declines through `enabled()`. A binding ordered that way on
|
|
2616
|
+
* purpose says so with `opts.yields`, and is not reported.
|
|
2843
2617
|
*
|
|
2844
2618
|
* @returns The conflicts found, so callers can dedupe repeat reports.
|
|
2845
2619
|
*/
|
|
@@ -2852,7 +2626,10 @@ declare function reportRouteConflicts(scopes: ToolScopes, warn?: (message: strin
|
|
|
2852
2626
|
*
|
|
2853
2627
|
* Extracted from the deleted singular `useKeybinding` hook so the remaining
|
|
2854
2628
|
* consumers don't depend on its disappearance.
|
|
2629
|
+
*
|
|
2630
|
+
* @packageDocumentation
|
|
2855
2631
|
*/
|
|
2632
|
+
|
|
2856
2633
|
/**
|
|
2857
2634
|
* A key plus a modifier policy, as {@link matchesKeyBinding} tests it against a
|
|
2858
2635
|
* `KeyboardEvent`. Every modifier left unset is forbidden, so `{ key: 'a' }`
|
|
@@ -2885,14 +2662,6 @@ interface KeyBinding {
|
|
|
2885
2662
|
/** Call `preventDefault` before the handler. Default `true`. */
|
|
2886
2663
|
preventDefault?: boolean;
|
|
2887
2664
|
}
|
|
2888
|
-
/** Returns true when the target is an input, textarea, or contenteditable element. */
|
|
2889
|
-
declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
2890
|
-
/**
|
|
2891
|
-
* True when `e` is Space or Enter on a focused control those keys activate — a
|
|
2892
|
-
* button, a link, a checkbox. The key is that control's, so a page-level
|
|
2893
|
-
* listener that claims it cancels the control's own activation.
|
|
2894
|
-
*/
|
|
2895
|
-
declare function activatesFocusedControl(e: KeyboardEvent): boolean;
|
|
2896
2665
|
/**
|
|
2897
2666
|
* Pure matcher for `KeyBinding` against a `KeyboardEvent`. Only checks key +
|
|
2898
2667
|
* modifier policy — does NOT apply the editable-target skip or
|
|
@@ -2900,38 +2669,21 @@ declare function activatesFocusedControl(e: KeyboardEvent): boolean;
|
|
|
2900
2669
|
* gesture dispatcher so the rules can't drift.
|
|
2901
2670
|
*/
|
|
2902
2671
|
declare function matchesKeyBinding(e: KeyboardEvent, b: KeyBinding): boolean;
|
|
2903
|
-
type ModifierKeys = Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>;
|
|
2904
2672
|
/** Which history step `e` asks for: Mod+Z undoes, Shift+Mod+Z and Mod+Y redo. `null` for anything else. Takes a
|
|
2905
2673
|
* React keyboard event as readily as a DOM one. */
|
|
2906
|
-
declare function historyKey(e:
|
|
2674
|
+
declare function historyKey(e: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>): 'undo' | 'redo' | null;
|
|
2907
2675
|
|
|
2908
2676
|
/**
|
|
2909
|
-
* Typed keys for the gesture/behavior scratch store.
|
|
2910
|
-
*
|
|
2911
|
-
*
|
|
2912
|
-
* mutable bag that behaviors use to stash state across `onDown` / `onMove`
|
|
2913
|
-
* / `onEnd` calls. The store is `unknown`-valued because behaviors are
|
|
2914
|
-
* tool-agnostic — a single behavior may run under different tools with
|
|
2915
|
-
* different scratch shapes. Without a typing convention every read site
|
|
2916
|
-
* has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the
|
|
2917
|
-
* shape can silently break readers with no compile-time signal.
|
|
2918
|
-
*
|
|
2919
|
-
* `ScratchKey<T>` is a phantom-typed string that carries its payload type
|
|
2920
|
-
* via a branded interface. `getScratch` and `setScratch` use the brand to
|
|
2921
|
-
* narrow / constrain at the typing layer; at runtime the key is just its
|
|
2922
|
-
* underlying string and the store remains a plain `Record`.
|
|
2677
|
+
* Typed keys for the gesture/behavior scratch store, `ctx.scratch`.
|
|
2678
|
+
* `ScratchKey<T>` carries its payload type at the typing layer; at runtime
|
|
2679
|
+
* the key is just its underlying string and the store remains a plain `Record`.
|
|
2923
2680
|
*
|
|
2924
2681
|
* @example
|
|
2925
2682
|
* ```ts
|
|
2926
|
-
* // Module-level
|
|
2683
|
+
* // Module-level constant, shared by writer + reader:
|
|
2927
2684
|
* const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');
|
|
2928
|
-
*
|
|
2929
|
-
* // Writer (a tool's onMove):
|
|
2930
2685
|
* setScratch(ctx.scratch, LASSO_VERTICES, vertices);
|
|
2931
|
-
*
|
|
2932
|
-
* // Reader (a behavior's onEnd):
|
|
2933
2686
|
* const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];
|
|
2934
|
-
* // ^? readonly { x: number; y: number }[] | undefined
|
|
2935
2687
|
* ```
|
|
2936
2688
|
*/
|
|
2937
2689
|
/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom
|
|
@@ -2946,13 +2698,9 @@ interface ScratchKey<T> {
|
|
|
2946
2698
|
* field already satisfies this; consumers don't need to construct one. */
|
|
2947
2699
|
type ScratchStore = Record<string, unknown>;
|
|
2948
2700
|
/**
|
|
2949
|
-
* Make a typed scratch key.
|
|
2950
|
-
* is
|
|
2951
|
-
*
|
|
2952
|
-
* Namespace the name by behavior or feature to avoid collisions
|
|
2953
|
-
* (`'behavior.field'` is a good convention). Two keys with the same
|
|
2954
|
-
* `name` refer to the same slot at runtime — the type parameter is a
|
|
2955
|
-
* compile-time contract, not a uniqueness guarantee.
|
|
2701
|
+
* Make a typed scratch key. Two keys with the same `name` refer to the same
|
|
2702
|
+
* slot at runtime — the type parameter is a compile-time contract, not a
|
|
2703
|
+
* uniqueness guarantee — so namespace the name (`'behavior.field'`).
|
|
2956
2704
|
*/
|
|
2957
2705
|
declare function scratchKey<T>(name: string): ScratchKey<T>;
|
|
2958
2706
|
/** Read a typed slot from the scratch store. Returns `undefined` if
|
|
@@ -2993,6 +2741,14 @@ declare function itemsFromFiles(files: File[]): IngestItem[];
|
|
|
2993
2741
|
/** Materialize a drop's `DataTransfer`. MUST be called during the `drop`
|
|
2994
2742
|
* event dispatch (see module doc). */
|
|
2995
2743
|
declare function itemsFromDataTransfer(dt: DataTransfer): Promise<IngestItem[]>;
|
|
2744
|
+
/**
|
|
2745
|
+
* What a drag still in flight says it carries: one item per flavor, MIME
|
|
2746
|
+
* only. A browser withholds contents until the drop, so nothing may read
|
|
2747
|
+
* `file` or `text` off these; they exist to ask which bindings would match.
|
|
2748
|
+
* A file the browser does not name yet (Firefox reports
|
|
2749
|
+
* `application/x-moz-file`, others only a `Files` type) is `UNKNOWN_MIME`.
|
|
2750
|
+
*/
|
|
2751
|
+
declare function itemTypesFromDataTransfer(dt: DataTransfer): readonly IngestItem[];
|
|
2996
2752
|
/** Materialize a paste's `clipboardData`. Fully synchronous. */
|
|
2997
2753
|
declare function itemsFromClipboardData(cd: DataTransfer): IngestItem[];
|
|
2998
2754
|
|
|
@@ -3055,6 +2811,8 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
|
|
|
3055
2811
|
* The pure half: no React, no DOM. The React seam that pumps browser events
|
|
3056
2812
|
* into the dispatcher, and the providers that feed it, live behind
|
|
3057
2813
|
* `@weasel-js/routing/react`.
|
|
2814
|
+
*
|
|
2815
|
+
* @packageDocumentation
|
|
3058
2816
|
*/
|
|
3059
2817
|
|
|
3060
2818
|
/**
|
|
@@ -3063,10 +2821,8 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
|
|
|
3063
2821
|
* keyed on.
|
|
3064
2822
|
*
|
|
3065
2823
|
* It declares one entry, `activeTool`, because the value and the action that
|
|
3066
|
-
* requires it (`tool.offhand`) are both routing's.
|
|
3067
|
-
*
|
|
3068
|
-
* invocation time; it does not know what a selection, a scene, or a viewport
|
|
3069
|
-
* is. Whoever supplies those merges them in:
|
|
2824
|
+
* requires it (`tool.offhand`) are both routing's. Whoever supplies the rest
|
|
2825
|
+
* merges them in:
|
|
3070
2826
|
*
|
|
3071
2827
|
* ```ts
|
|
3072
2828
|
* declare module '@weasel-js/routing' {
|
|
@@ -3075,12 +2831,7 @@ declare function dwarn(namespace: string, ...args: unknown[]): void;
|
|
|
3075
2831
|
* ```
|
|
3076
2832
|
*
|
|
3077
2833
|
* `@weasel-js/core` merges the kit's own deps this way and re-exports
|
|
3078
|
-
* `DepSchema`, so a consumer adds its own against `'@weasel-js/core'
|
|
3079
|
-
* never has to know this module exists. The merge carries through the
|
|
3080
|
-
* re-export in both directions — `scripts/smoke-consumer-bundle.mjs` asserts
|
|
3081
|
-
* it against the published `.d.ts`, which is where it once broke: a
|
|
3082
|
-
* cross-module augmentation stops merging when `rollup-plugin-dts` flattens
|
|
3083
|
-
* both files into one chunk.
|
|
2834
|
+
* `DepSchema`, so a consumer adds its own against `'@weasel-js/core'`.
|
|
3084
2835
|
*/
|
|
3085
2836
|
interface DepSchema {
|
|
3086
2837
|
/** Currently active tool id + hotkey-hold stack. */
|
|
@@ -3088,6 +2839,29 @@ interface DepSchema {
|
|
|
3088
2839
|
}
|
|
3089
2840
|
/** Every dep name declared by anything that has merged into {@link DepSchema}. */
|
|
3090
2841
|
type DepName = keyof DepSchema;
|
|
2842
|
+
/**
|
|
2843
|
+
* The params each action reads, by action id — what types a tuning's
|
|
2844
|
+
* `params` and the params on the bindings it adds. Whoever ships an action
|
|
2845
|
+
* merges its entry in, as with {@link DepSchema}:
|
|
2846
|
+
*
|
|
2847
|
+
* ```ts
|
|
2848
|
+
* declare module '@weasel-js/core' {
|
|
2849
|
+
* interface ActionParamSchema { brushDeleteAnchors: { radiusPx: number } }
|
|
2850
|
+
* }
|
|
2851
|
+
* ```
|
|
2852
|
+
*
|
|
2853
|
+
* Declare an entry as a type literal, not an interface: an interface has no
|
|
2854
|
+
* index signature, so it is not a `Record<string, unknown>`.
|
|
2855
|
+
*/
|
|
2856
|
+
interface ActionParamSchema {
|
|
2857
|
+
}
|
|
2858
|
+
/** Tunings keyed by action id: typed by {@link ActionParamSchema} for an id it
|
|
2859
|
+
* declares, `Record<string, unknown>` params for any other. */
|
|
2860
|
+
type ActionTunings = {
|
|
2861
|
+
readonly [K in keyof ActionParamSchema]?: ActionTuning<ActionParamSchema[K]>;
|
|
2862
|
+
} & {
|
|
2863
|
+
readonly [id: string]: ActionTuning | undefined;
|
|
2864
|
+
};
|
|
3091
2865
|
/**
|
|
3092
2866
|
* What the kernel drawing these tools takes as an overlay. Routing never reads
|
|
3093
2867
|
* an overlay, so it has no type of its own for one; the kernel merges its type
|
|
@@ -3109,4 +2883,4 @@ type KernelOverlay = OverlaySchema extends {
|
|
|
3109
2883
|
overlay: infer O;
|
|
3110
2884
|
} ? O : unknown;
|
|
3111
2885
|
|
|
3112
|
-
export {
|
|
2886
|
+
export { DEFAULT_ALLOWED_CAPABILITIES as $, type ActionsRegistry as A, type ActionVariant as B, type Contribution as C, type DepRegistry as D, type ActionsProp as E, type AnyTool as F, type AssemblyInputs as G, type BindingOpts as H, type BindingScope as I, type BindingSource as J, type KernelOverlay as K, type LongPressOptions as L, type BodyPredicate as M, type NodeId as N, type OverlayPosition as O, type BoundGesture as P, type Bounds as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type BuildRuleCtxArgs as U, COARSE_TARGET_SCALE as V, type Condition as W, type Conflict as X, type ContributionChrome as Y, type ContributionDeps as Z, type ContributionRouting as _, type DepName as a, type TraceLogEntry as a$, DEFAULT_DEVICE_PROFILE as a0, type DebugSink as a1, type DetectedDeviceFacts as a2, type DeviceProfile as a3, type DispatchRecord as a4, type DispatchRecordInput as a5, type DispatcherContext as a6, type DragSample as a7, type DroppedCandidate as a8, type Eligibility as a9, type Point2 as aA, type PointerSession as aB, type PointerSessionCallbacks as aC, type PointerSessionCancelReason as aD, type PointerSessionOptions as aE, type RankedCandidate as aF, type RecordCandidate as aG, type RegistryEntry as aH, type ResizeAnchor as aI, type ResolveAllOptions as aJ, type ResolveOnlyResult as aK, type ResolvedCandidate as aL, type Rule as aM, type ScopedBinding as aN, type ScratchKey as aO, type ScratchStore as aP, type SelectionApi as aQ, type Selector as aR, type SpecificityPart as aS, TOOL_OFFHAND_ID as aT, type ToolDef as aU, type ToolKeybinding as aV, type ToolModifiers as aW, type ToolOffhandBindingSpec as aX, type ToolPresentation as aY, type ToolScopes as aZ, type ToolSlot as a_, type EligibilityState as aa, type GestureBinding as ab, type HandleKind as ac, type HitShape as ad, type HotkeyTrigger as ae, INGEST_STRING_MIMES as af, type ImmediateInvoker as ag, type InvocationCtx as ah, type Invoker as ai, type KeyBinding as aj, type LongPressState as ak, type LongPressStore as al, type MatchOutcome as am, type MatchResult as an, type ModeShortcutHandlers as ao, type ModeSwitchLogEntry as ap, type ModifierState as aq, NEVER as ar, type OngoingHandle as as, type OngoingInvoker as at, type OngoingOverlay as au, type OverlayRole as av, type OverlaySchema as aw, PREDICATE_TARGET as ax, type PendingLongPress as ay, type PlacedBy as az, type DepSchema as b, ruleCanHoldIn as b$, type TunedBinding as b0, type UiOngoingControl as b1, type ViewportGestureKind as b2, type WalkStep as b3, actionBindings as b4, actionItems as b5, assembleEntries as b6, buildDepsFromRequires as b7, buildRouteRegistry as b8, buildRuleCtx as b9, isRotateHandle as bA, isSegment as bB, isSelectedBody as bC, isTool as bD, isUnselectedBody as bE, itemTypesFromDataTransfer as bF, itemsFromClipboardData as bG, itemsFromDataTransfer as bH, itemsFromFiles as bI, liveScope as bJ, makeToolOffhandAction as bK, matchBest as bL, matchSorted as bM, matchSortedWithBarred as bN, matchesKeyBinding as bO, mergeContributions as bP, modeShortcutSpec as bQ, modeShortcuts as bR, multiTouchGestureId as bS, offhandKeyFor as bT, openPointerSession as bU, pointerGestureId as bV, recordModeSwitch as bW, reportRouteConflicts as bX, resolveDeviceProfile as bY, resolveParams as bZ, routesForSpec as b_, buildToolOffhandBindings as ba, clientToCanvas as bb, clientToCanvasRect as bc, createDepRegistry as bd, createDispatcher as be, createLongPressStore as bf, defineTool as bg, deleteScratch as bh, describeRule as bi, dlog as bj, dwarn as bk, evaluate as bl, evaluateActive as bm, evaluateEnabled as bn, findConflicts as bo, getScratch as bp, heldKeySpec as bq, historyKey as br, isAnchor as bs, isAnchorOrControl as bt, isBody as bu, isControlHandle as bv, isDebugEnabled as bw, isEmpty as bx, isPathEditPart as by, isResizeHandle as bz, type Action as c, rulesExclusive as c0, scopeBindings as c1, scratchKey as c2, setScratch as c3, targetConsultsAffordance as c4, tuneAction as c5, validateActionDefaultBinding as c6, validateActionId as c7, DepNode as d, type Tool as e, ActiveToolContextProvider as f, ActiveToolContextProviderIfRoot as g, type ActiveToolContextProviderProps as h, type ActiveToolContextValue as i, useOptionalActiveToolContext as j, type Dispatcher as k, type AffordanceHit as l, ALWAYS as m, type ActionDeps as n, ActionDisabledReason as o, type ActionDispatch as p, type ActionEnabledResult as q, type ActionEntry as r, type ActionItem as s, type ActionParamSchema as t, useActiveToolContext as u, type ActionParams as v, type ActionPresentation as w, type ActionSource as x, type ActionTuning as y, type ActionTunings as z };
|