@weasel-js/routing 1.9.3 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
- import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
1
+ import { View } from '@weasel-js/geom';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
+ import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
4
5
  import { CursorSpec } from '@weasel-js/cursor';
5
- import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
6
+ import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
6
7
  import { Op } from '@weasel-js/history';
7
8
  import '@weasel-js/select';
8
9
 
@@ -29,22 +30,7 @@ import '@weasel-js/select';
29
30
  type NodeId = string & {
30
31
  readonly __brand: 'NodeId';
31
32
  };
32
- /**
33
- * Viewport state. `(view.x, view.y)` is the world point currently rendered at
34
- * the canvas top-left; `view.scale.x` / `view.scale.y` is pixels per world
35
- * unit on each axis.
36
- *
37
- * The one outright 2D dead end in the routing surface: a `View` names no
38
- * orientation, so a kernel with a camera cannot express its viewport as one.
39
- */
40
- interface View {
41
- x: number;
42
- y: number;
43
- scale: {
44
- x: number;
45
- y: number;
46
- };
47
- }
33
+
48
34
  /**
49
35
  * Axis-aligned rectangle in world space. The optional `rotation` (radians,
50
36
  * around the AABB center) lets chrome attach an orientation to an otherwise
@@ -196,6 +182,108 @@ type ResizeAnchor = {
196
182
  y: 'min' | 'max' | 'free';
197
183
  };
198
184
 
185
+ /**
186
+ * The tree both input registries hang on. A provider is a root; every canvas
187
+ * mounts a scope under the registry in scope; a yoke sits between the two. A
188
+ * registration lives in the node it was made in, and a lookup starts at a leaf
189
+ * and walks up, so one canvas never answers for another.
190
+ */
191
+ /** What every node of one tree shares: a version and the listeners to it. */
192
+ interface ScopeTree {
193
+ version: number;
194
+ readonly listeners: Set<() => void>;
195
+ }
196
+ declare class ScopeNode<N extends ScopeNode<N>> {
197
+ readonly parent: N | null;
198
+ readonly descends: boolean;
199
+ readonly tree: ScopeTree;
200
+ private readonly children;
201
+ private activeChild;
202
+ /** `descends`: whether reads made here answer from the active child down —
203
+ * true for a root or a yoke, which chrome reads through; false for a scope,
204
+ * which answers for itself even with a scope nested inside it. */
205
+ constructor(parent: N | null, descends: boolean);
206
+ /** Bump the tree's version and tell every listener in it. */
207
+ changed(): void;
208
+ /** Join the parent's children, newest last; returns the leave. */
209
+ mount(): () => void;
210
+ /** Make this node the one its ancestors resolve through. */
211
+ activate(): void;
212
+ /** The node a lookup made here starts from: down the active children, or the
213
+ * newest where nothing has been activated yet. A yoke with no members yet
214
+ * is passed over — there is no canvas behind it to answer. */
215
+ leaf(): N;
216
+ private newestMember;
217
+ /** Where a read made through this node starts. */
218
+ start(): N;
219
+ /** Whether this node is on the path a read from the root takes. */
220
+ isActive(): boolean;
221
+ /** This node, then each ancestor up to the root. */
222
+ chain(): Generator<N>;
223
+ }
224
+
225
+ /** Which tool is active, plus the stack of tools temporarily held active by a
226
+ * hotkey (space-for-hand and the like). The dispatcher reads this to decide
227
+ * whose bindings are in scope. */
228
+ interface ActiveToolContextValue {
229
+ /** The focused tool's id, or `null` when no tool holds the active slot —
230
+ * a canvas can run on ambient bindings alone. */
231
+ active: string | null;
232
+ hotkeyStack: string[];
233
+ setActive(id: string | null): void;
234
+ pushHotkey(id: string): void;
235
+ /**
236
+ * Disengage a hotkey-held tool. With `id`, removes that tool's own entry
237
+ * wherever it now sits — holds do not have to come up in the order they went
238
+ * down, and popping the top would disengage a tool whose key is still held.
239
+ * With no id, pops the top.
240
+ */
241
+ popHotkey(id?: string): void;
242
+ }
243
+ /**
244
+ * One node of the tool tree. A node that `owns` a tool holds its state; one
245
+ * that does not — a scope inside a yoke — reads and writes its yoke's.
246
+ */
247
+ declare class ToolNode extends ScopeNode<ToolNode> {
248
+ private state;
249
+ private value;
250
+ constructor(parent: ToolNode | null, descends: boolean, owns: boolean, initialActive: string | null);
251
+ /** The node whose tool a read here resolves to. */
252
+ owner(): ToolNode;
253
+ private update;
254
+ /** A stable value per owner and state, for `useSyncExternalStore`. */
255
+ snapshot(): ActiveToolContextValue;
256
+ subscribe: (listener: () => void) => (() => void);
257
+ }
258
+ /** Props for `<ActiveToolContextProvider>`. */
259
+ interface ActiveToolContextProviderProps {
260
+ children: ReactNode;
261
+ /** Seeds the active slot, and wins over the first `useTools` call's
262
+ * `active`. Omitted, the slot starts empty and that call seeds it. */
263
+ initialActive?: string | null;
264
+ }
265
+ /** Provides root active-tool state. Every canvas below has a tool of its own;
266
+ * reads and writes here reach the one last used. */
267
+ declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
268
+ /** The active-tool state in scope. Throws outside a provider; use
269
+ * `useOptionalActiveToolContext` where one is not guaranteed. */
270
+ declare function useActiveToolContext(): ActiveToolContextValue;
271
+ /**
272
+ * Like `useActiveToolContext`, but returns `null` when no
273
+ * `<ActiveToolContextProvider>` is in scope instead of throwing. Used by
274
+ * `useStandardActions` to preserve its silent-no-op contract when no provider
275
+ * is present.
276
+ */
277
+ declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
278
+ /**
279
+ * Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
280
+ * when no parent provider is in scope — otherwise renders children unwrapped
281
+ * so `<WeaselProvider>` defers to the host's existing scope.
282
+ */
283
+ declare function ActiveToolContextProviderIfRoot({ children, }: {
284
+ children: ReactNode;
285
+ }): react.JSX.Element;
286
+
199
287
  /**
200
288
  * Live state read by rule evaluation. Built once per frame on the consuming
201
289
  * surface — chrome-caps, the affordance pipeline, the dispatcher's
@@ -1035,12 +1123,13 @@ interface ActionDispatch extends BindingSource {
1035
1123
  scope?: 'hotkey';
1036
1124
  /**
1037
1125
  * @experimental
1038
- * Optional predicate the command palette consults when rendering. Return
1039
- * `true` when the action is currently triggerable. Return a reason string
1040
- * (e.g. `'Selection required'`) when disabled — the palette greys out
1041
- * the row, skips it in keyboard nav, ignores clicks, and shows the
1042
- * reason next to the label. Keystroke dispatch (the registered binding)
1043
- * is unaffected; the action's own `run` should self-guard.
1126
+ * Optional predicate saying whether the action can run right now. Return
1127
+ * `true` when it can, or a reason string (e.g. `'Selection required'`)
1128
+ * when it cannot. The command palette greys out a disabled row, skips it
1129
+ * in keyboard nav, ignores clicks, and shows the reason next to the label.
1130
+ * The dispatcher skips a disabled action too, on a key as on a pointer, and
1131
+ * falls through to the next binding that matches. `ActionsRegistry.trigger`
1132
+ * does not ask, so the action's own `run` should still self-guard.
1044
1133
  *
1045
1134
  * **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
1046
1135
  * must not throw. If a call throws or exceeds the budget in dev mode,
@@ -1071,6 +1160,17 @@ interface ActionDispatch extends BindingSource {
1071
1160
  x: number;
1072
1161
  y: number;
1073
1162
  }) => true | ActionDisabledReason;
1163
+ /**
1164
+ * @experimental
1165
+ * Whether the action reads as held down right now: bold on the selected
1166
+ * text, snapping switched on. A toolbar button draws it as pressed.
1167
+ * Omitted = the action has no pressed state, which is not the same as
1168
+ * one that is off. Never read during dispatch.
1169
+ *
1170
+ * Under the same contract as `enabled`: pure, fast, and it must not
1171
+ * throw. `deps` is the bag `enabled` is given.
1172
+ */
1173
+ active?: (deps?: ActionDeps) => boolean;
1074
1174
  /**
1075
1175
  * Declarative eligibility rule, evaluated against the current
1076
1176
  * `RuleCtx` by the dispatcher before invoking `start()`. Omitted =
@@ -1182,48 +1282,12 @@ interface ActionEnabledResult {
1182
1282
  * predicates should be sub-millisecond).
1183
1283
  */
1184
1284
  declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabledResult;
1185
-
1186
1285
  /**
1187
- * The tree both input registries hang on. A provider is a root; every canvas
1188
- * mounts a scope under the registry in scope; a yoke sits between the two. A
1189
- * registration lives in the node it was made in, and a lookup starts at a leaf
1190
- * and walks up, so one canvas never answers for another.
1191
- *
1192
- * Design: docs/proposals/2026-09-27-input-scopes.md.
1286
+ * @experimental
1287
+ * Safely evaluate an action's `active` predicate. `undefined` for an action
1288
+ * with no pressed state; a predicate that throws reads as not active.
1193
1289
  */
1194
- /** What every node of one tree shares: a version and the listeners to it. */
1195
- interface ScopeTree {
1196
- version: number;
1197
- readonly listeners: Set<() => void>;
1198
- }
1199
- declare class ScopeNode<N extends ScopeNode<N>> {
1200
- readonly parent: N | null;
1201
- readonly descends: boolean;
1202
- readonly tree: ScopeTree;
1203
- private readonly children;
1204
- private activeChild;
1205
- /** `descends`: whether reads made here answer from the active child down —
1206
- * true for a root or a yoke, which chrome reads through; false for a scope,
1207
- * which answers for itself even with a scope nested inside it. */
1208
- constructor(parent: N | null, descends: boolean);
1209
- /** Bump the tree's version and tell every listener in it. */
1210
- changed(): void;
1211
- /** Join the parent's children, newest last; returns the leave. */
1212
- mount(): () => void;
1213
- /** Make this node the one its ancestors resolve through. */
1214
- activate(): void;
1215
- /** The node a lookup made here starts from: down the active children, or the
1216
- * newest where nothing has been activated yet. A yoke with no members yet
1217
- * is passed over — there is no canvas behind it to answer. */
1218
- leaf(): N;
1219
- private newestMember;
1220
- /** Where a read made through this node starts. */
1221
- start(): N;
1222
- /** Whether this node is on the path a read from the root takes. */
1223
- isActive(): boolean;
1224
- /** This node, then each ancestor up to the root. */
1225
- chain(): Generator<N>;
1226
- }
1290
+ declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | undefined;
1227
1291
 
1228
1292
  /**
1229
1293
  * Dep registry for registry unification.
@@ -1248,6 +1312,15 @@ declare class ScopeNode<N extends ScopeNode<N>> {
1248
1312
  interface DepRegistry {
1249
1313
  register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
1250
1314
  get<K extends DepName>(name: K): DepSchema[K] | undefined;
1315
+ /** Hear that what the registry answers may have changed: a source was
1316
+ * registered or released anywhere in its tree, or an owner called
1317
+ * {@link DepRegistry.changed}. Returns the unsubscribe. A hand-built
1318
+ * registry whose answers never change leaves this and `changed` out. */
1319
+ subscribe?(listener: () => void): () => void;
1320
+ /** Say that a source's value changed. A source is a thunk read on demand,
1321
+ * so its owner is the only one who knows; chrome showing an action's
1322
+ * `enabled` or `active` state re-reads on this. */
1323
+ changed?(): void;
1251
1324
  }
1252
1325
  /** One node of the dep tree: the sources registered at it, over its parent's.
1253
1326
  * Each name holds a stack of sources, newest live, so a displaced source is
@@ -1408,9 +1481,8 @@ interface MatchResult {
1408
1481
  * `bodyTarget` / `bodyKind` and never see it — which is why chrome floating
1409
1482
  * over empty canvas used to read as empty canvas.
1410
1483
  *
1411
- * A predicate carrying `readsAffordance: false` says the same about itself;
1412
- * the kit's own body predicates do. Shape stays the fallback for predicates
1413
- * that declare nothing.
1484
+ * A predicate declares which it is with `readsAffordance`; one that declares
1485
+ * nothing, which only untyped code can write, counts as not reading the hit.
1414
1486
  */
1415
1487
  declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
1416
1488
  /**
@@ -1609,86 +1681,11 @@ interface RankedCandidate {
1609
1681
  }
1610
1682
 
1611
1683
  /**
1612
- * Dispatcher orchestrator — pure module, no React, no DOM.
1613
- *
1614
- * Assembles `ScopedBinding[]` from the actions registry, active tool, and
1615
- * hotkey stack; matches input events via `matchSorted`; gates each candidate
1616
- * on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
1617
- * in-flight handles.
1618
- *
1619
- * ## Precedence and fall-through
1620
- * An exclusive affordance claim first bars every binding that does not
1621
- * consult the affordance. The rest are ranked, best first, by:
1622
- * 1. bindings naming the routed view ahead of every other
1623
- * (`preferViewScoped`);
1624
- * 2. scope tier: hotkey > active > ambient;
1625
- * 3. specificity within a tier (`specificity()` in `matcher.ts`);
1626
- * 4. an action gated by an `eligible` rule that holds now ahead of one with
1627
- * no rule (`preferContextual`) — Escape while editing a path exits the
1628
- * edit rather than resetting the tool;
1629
- * 5. registration order.
1630
- * Candidates whose `eligible` rule is false are dropped first. The dispatcher
1631
- * walks the rest and fires the first action whose `enabled()` returns
1632
- * `true`. If every candidate's `enabled()` returns a disabled reason, the
1633
- * event is unhandled. This mirrors CSS-style specificity matching with a
1634
- * `:not(:disabled)` filter, and lets a tool declare a high-specificity
1635
- * binding (e.g. drag-on-empty → areaSelect) that gracefully falls through
1636
- * to a lower-specificity ambient binding (e.g. drag → viewport.dragPan)
1637
- * when its required deps aren't wired.
1638
- *
1639
- * ## gestureId scheme
1640
- * - `key-held` ongoing actions: `key-held-<key>` (e.g. `key-held- ` for Space).
1641
- * Chosen because key-held gestures are identified by the held key alone.
1642
- * - `pointerdown` / drag ongoing actions: `pointer-<pointerId>`, taken from
1643
- * the originating DOM `PointerEvent`. Each physical pointer — mouse, each
1644
- * touch, the stylus — gets its own handle slot. Events with no
1645
- * `pointerId` (synthesized probes, programmatic drags, most tests) key to
1646
- * `pointer-mouse`, so a single synthetic pointer behaves as it always has.
1647
- * - `multitouch` ongoing actions: `multitouch-<fingers>`.
1648
- * - Fallback for any other kind that triggers an ongoing invoker: `ongoing-<kind>`.
1649
- *
1650
- * ## Action-lookup miss behavior
1651
- * When `matchBest` resolves a binding whose `actionId` has no entry in
1652
- * `ctx.actions.list()`, the dispatcher emits `console.warn` and returns
1653
- * `'unhandled'`. The user's input gesture falls through as if unmatched.
1654
- * This preserves input flow (nothing is swallowed silently) while flagging
1655
- * the misconfiguration at dev time.
1684
+ * The dispatcher's contract: what a host hands it per input
1685
+ * (`DispatcherContext`) and what it answers with (`Dispatcher`).
1686
+ * `createDispatcher` in `dispatcher.ts` implements it.
1656
1687
  */
1657
1688
 
1658
- /**
1659
- * The in-flight handle slot a pointer's gestures key into.
1660
- *
1661
- * Exported because the React seam (`useGestureDispatcher`) has to name the
1662
- * same slot when it asks `dispatcher.inFlight()` whether a given pointer is
1663
- * mid-drag. That used to be the string literal `'pointer-mouse'` on both
1664
- * sides, which was correct only because `gestureIdFor` ignored the pointer id
1665
- * entirely — the two lies cancelled out.
1666
- *
1667
- * @param pointerId - `PointerEvent.pointerId`, or `undefined` for a
1668
- * synthesized event with no originating DOM pointer.
1669
- */
1670
- declare function pointerGestureId(pointerId: number | undefined): string;
1671
- /** One entry per "mode change" — a kit state transition that isn't a
1672
- * dispatched input event but is load-bearing for understanding why an
1673
- * input was (or wasn't) handled later. Pushed via `recordModeSwitch()`;
1674
- * consumers include the editAnchors dep (editingId changes), and
1675
- * could expand to active-tool / hotkey-stack / focus changes. */
1676
- interface ModeSwitchLogEntry {
1677
- kind: 'mode';
1678
- ts: number;
1679
- /** Logical mode name, e.g. `'editAnchors.editingId'`. */
1680
- mode: string;
1681
- from: string | null;
1682
- to: string | null;
1683
- /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
1684
- * `'selection-dropped target'`, etc. */
1685
- detail?: string;
1686
- }
1687
- /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
1688
- type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
1689
- /** Push a mode-switch record into the trace log. Safe to call from
1690
- * anywhere; no-ops outside DEV. */
1691
- declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1692
1689
  /** Everything the dispatcher must consult to route one input event: the
1693
1690
  * registered actions and their deps, which tool is active, which hotkeys are
1694
1691
  * held, and — optionally — the chrome state that action eligibility rules are
@@ -1732,9 +1729,13 @@ interface DispatcherContext {
1732
1729
  * calls the handle's `onMove`. Safe to call many times.
1733
1730
  * - `end(reason)` calls `onEnd(ctx, reason)` once and removes the handle
1734
1731
  * from the in-flight map. Idempotent — further calls are no-ops.
1732
+ * - `active` is false once the handle has ended, whether through `end` or
1733
+ * by the dispatcher (`cancelAll`, or a later `beginUiOngoing` for the same
1734
+ * action). `update` and `end` do nothing from then on.
1735
1735
  */
1736
1736
  interface UiOngoingControl {
1737
1737
  readonly gestureId: string;
1738
+ readonly active: boolean;
1738
1739
  update(params?: Record<string, unknown>): void;
1739
1740
  end(reason: 'commit' | 'cancel'): void;
1740
1741
  }
@@ -1880,12 +1881,12 @@ interface Dispatcher {
1880
1881
  */
1881
1882
  inFlight(): ReadonlyMap<string, OngoingHandle>;
1882
1883
  /**
1883
- * CSS cursor for the gesture currently in flight, or `null` when nothing
1884
+ * Cursor for the gesture currently in flight, as declared, or `null` when nothing
1884
1885
  * is. Reads `Action.activeCursor` (falling back to `Action.cursor`) off the
1885
1886
  * action whose handle is open — the hover pump applies this instead of its
1886
1887
  * prediction once a gesture starts, which is how `grab` becomes `grabbing`.
1887
1888
  */
1888
- inFlightCursor(): string | null;
1889
+ inFlightCursor(): CursorSpec | null;
1889
1890
  /**
1890
1891
  * Read-only iterator over currently in-flight `OngoingHandle` instances.
1891
1892
  *
@@ -1899,7 +1900,9 @@ interface Dispatcher {
1899
1900
  * Subscribe to in-flight state changes. The callback fires after every
1900
1901
  * mutation that affects what the preview-ghost / dispatcher-overlay
1901
1902
  * layers read — handle start, every `onMove` pump, end, cancel,
1902
- * cancel-all. Consumers re-read `getInFlightHandles()` and re-render.
1903
+ * cancel-all — and after any other input handled while a gesture is in
1904
+ * flight. Input that arrives with nothing in flight and starts nothing
1905
+ * does not fire it. Consumers re-read `getInFlightHandles()` and re-render.
1903
1906
  *
1904
1907
  * Returns an unsubscribe function.
1905
1908
  */
@@ -1911,9 +1914,9 @@ interface Dispatcher {
1911
1914
  * a `useReducer` force-rerender.
1912
1915
  *
1913
1916
  * Starts at 0 and only ever increases. Two reads returning the same number
1914
- * mean nothing pumped in between; it does **not** guarantee that a bump
1915
- * changed anything observable (a pump that matched no binding still
1916
- * counts — see `subscribe`).
1917
+ * mean no gesture was in flight in between; it does **not** guarantee that
1918
+ * a bump changed anything observable (input that matched no binding still
1919
+ * counts while a gesture is in flight — see `subscribe`).
1917
1920
  */
1918
1921
  getVersion(): number;
1919
1922
  /**
@@ -1959,6 +1962,96 @@ interface Dispatcher {
1959
1962
  */
1960
1963
  beginUiOngoing(actionId: string, deps: ActionDeps, params?: Record<string, unknown>): UiOngoingControl | null;
1961
1964
  }
1965
+
1966
+ /**
1967
+ * Dev-only trace of what the dispatcher did: one record per resolved input and
1968
+ * one per mode switch, in a rolling buffer the dev tools read.
1969
+ */
1970
+
1971
+ /** One entry per "mode change" — a kit state transition that isn't a
1972
+ * dispatched input event but is load-bearing for understanding why an
1973
+ * input was (or wasn't) handled later. Pushed via `recordModeSwitch()`;
1974
+ * consumers include the editAnchors dep (editingId changes), and
1975
+ * could expand to active-tool / hotkey-stack / focus changes. */
1976
+ interface ModeSwitchLogEntry {
1977
+ kind: 'mode';
1978
+ ts: number;
1979
+ /** Logical mode name, e.g. `'editAnchors.editingId'`. */
1980
+ mode: string;
1981
+ from: string | null;
1982
+ to: string | null;
1983
+ /** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
1984
+ * `'selection-dropped target'`, etc. */
1985
+ detail?: string;
1986
+ }
1987
+ /** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
1988
+ type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
1989
+ /** Push a mode-switch record into the trace log. Safe to call from
1990
+ * anywhere; no-ops outside DEV. */
1991
+ declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
1992
+
1993
+ /**
1994
+ * Dispatcher orchestrator — pure module, no React, no DOM.
1995
+ *
1996
+ * Assembles `ScopedBinding[]` from the actions registry, active tool, and
1997
+ * hotkey stack; matches input events via `matchSorted`; gates each candidate
1998
+ * on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
1999
+ * in-flight handles.
2000
+ *
2001
+ * ## Precedence and fall-through
2002
+ * An exclusive affordance claim first bars every binding that does not
2003
+ * consult the affordance. The rest are ranked, best first, by:
2004
+ * 1. bindings naming the routed view ahead of every other
2005
+ * (`preferViewScoped`);
2006
+ * 2. scope tier: hotkey > active > ambient;
2007
+ * 3. specificity within a tier (`specificity()` in `matcher.ts`);
2008
+ * 4. an action gated by an `eligible` rule that holds now ahead of one with
2009
+ * no rule (`preferContextual`) — Escape while editing a path exits the
2010
+ * edit rather than resetting the tool;
2011
+ * 5. registration order.
2012
+ * Candidates whose `eligible` rule is false are dropped first. The dispatcher
2013
+ * walks the rest and fires the first action whose `enabled()` returns
2014
+ * `true`. If every candidate's `enabled()` returns a disabled reason, the
2015
+ * event is unhandled. This mirrors CSS-style specificity matching with a
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>`.
2033
+ *
2034
+ * ## Action-lookup miss behavior
2035
+ * When the walk reaches a binding whose `actionId` has no entry in
2036
+ * `ctx.actions.list()`, the dispatcher emits `console.warn` and returns
2037
+ * `'unhandled'`. The user's input gesture falls through as if unmatched.
2038
+ * This preserves input flow (nothing is swallowed silently) while flagging
2039
+ * the misconfiguration at dev time.
2040
+ */
2041
+
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;
1962
2055
  /** Build a dispatcher. `getAction` overrides how action ids are resolved;
1963
2056
  * by default the `DispatcherContext`'s registry is used. */
1964
2057
  declare function createDispatcher(opts?: {
@@ -1998,7 +2091,9 @@ interface ActionsRegistry {
1998
2091
  /** Fire an immediate-invoker action by id. The optional `params` arg is
1999
2092
  * forwarded to `ImmediateInvoker.run` as its second argument — use it for
2000
2093
  * parametric actions (e.g. `trigger('tool.activate', { toolId: 'rect' })`).
2001
- * Ongoing-invoker actions are not reachable from `trigger`. */
2094
+ * Returns whether it ran one: `false` for an unknown id, and for an
2095
+ * ongoing-invoker action, which `trigger` cannot reach (see `begin`). It
2096
+ * does not ask `enabled`. */
2002
2097
  trigger(id: string, params?: Record<string, unknown>): boolean;
2003
2098
  /**
2004
2099
  * Subscribe to registry mutations. The callback fires after any
@@ -2023,13 +2118,22 @@ interface ActionsRegistry {
2023
2118
  * Returns a release that takes out only this dispatcher; a `null` wires
2024
2119
  * nothing. */
2025
2120
  setDispatcher(d: Dispatcher | null): () => void;
2026
- /** Make this registry's scope the one chrome above it reaches: `trigger`,
2027
- * `begin` and `list` on any ancestor then answer from here. A dispatcher
2028
- * calls it on the pointerdown and focus that land in its scope. */
2121
+ /** Make this registry's scope the one chrome above it reaches, and the one
2122
+ * keys go to: `trigger`, `begin` and `list` on any ancestor then answer from
2123
+ * here. A dispatcher calls it on the pointerdown and wheel that land in its
2124
+ * scope; moving DOM focus alone does not. */
2029
2125
  activate(): void;
2030
- /** Whether a lookup made from the root of this registry's tree starts here —
2031
- * the question a dispatcher asks before handling a keystroke. */
2126
+ /** Whether a lookup made from the root of this registry's tree starts here.
2127
+ * A dispatcher with no canvas to press in asks this before handling a
2128
+ * keystroke. */
2032
2129
  isActive(): boolean;
2130
+ /** Whether this registry's scope is the one last activated on the page, in
2131
+ * any tree: the question a canvas's dispatcher asks before handling a key
2132
+ * or a paste that reached the page. No scope holds the keys before the
2133
+ * first activation. */
2134
+ holdsKeys(): boolean;
2135
+ /** Let go of the keys if this scope holds them, as its canvas unmounts. */
2136
+ dropKeys(): void;
2033
2137
  }
2034
2138
  /** Validate that `id` is usable as an action id in the route grammar.
2035
2139
  * Rejects ids that start with a reserved sigil (would shadow future
@@ -2060,6 +2164,12 @@ declare function offhandKeyFor(trigger: HotkeyTrigger): string;
2060
2164
  * `opts.params.toolId` so the invoker's `start` can pick out which tool
2061
2165
  * to engage. */
2062
2166
  declare function buildToolOffhandBindings(specs: readonly ToolOffhandBindingSpec[]): BoundGesture[];
2167
+ /** The `key-held` spec for holding `key` (a `KeyboardEvent.key`, or several).
2168
+ * A modifier key gets every modifier optional, since its own keydown reports
2169
+ * its flag set. Any other key engages with Shift or Alt already down and not
2170
+ * with Cmd or Ctrl, so a letter trigger leaves its browser chord alone.
2171
+ * Every hold-a-key binding builds its spec here, so that rule has one home. */
2172
+ declare function heldKeySpec(key: string | string[]): KeyHeldSpec;
2063
2173
  /** Build the consolidated `tool.offhand` action — "hold a key to engage a
2064
2174
  * secondary tool" (e.g. Space-for-hand). `bindings` enumerates one
2065
2175
  * `{ spec: keyHeld, opts: { params: { toolId } } }` per tool; the invoker
@@ -2070,7 +2180,7 @@ declare function makeToolOffhandAction(bindings: BoundGesture[]): Action;
2070
2180
 
2071
2181
  /**
2072
2182
  * Named predicates for `defaultBinding.target.kindOf`. Each matches the
2073
- * existing `kindOf` signature `(target: unknown, bodyTarget?: string) => boolean`
2183
+ * `TargetPredicate` shape, declaring whether it reads the hit,
2074
2184
  * so they drop into action descriptors without a wrapper:
2075
2185
  *
2076
2186
  * target: { kindOf: isAnchor }
@@ -2079,10 +2189,9 @@ declare function makeToolOffhandAction(bindings: BoundGesture[]): Action;
2079
2189
  * a new predicate here is what enables every action that needs the same
2080
2190
  * shape to read it. Drift prevention.
2081
2191
  */
2082
- /** A predicate that resolves from `bodyTarget` alone. The exclusive-claim
2083
- * filter reads `readsAffordance`: without it a body predicate looks like it
2084
- * consults the hit, and survives a claim that should have barred it. */
2085
- type BodyPredicate = ((target: unknown, bodyTarget?: string) => boolean) & {
2192
+
2193
+ /** A predicate that resolves from `bodyTarget` alone. */
2194
+ type BodyPredicate = TargetPredicate & {
2086
2195
  readsAffordance: false;
2087
2196
  };
2088
2197
  /** Matches any body hit — selected or unselected node body. */
@@ -2094,15 +2203,25 @@ declare const isUnselectedBody: BodyPredicate;
2094
2203
  /** Empty-canvas hit — no node beneath cursor. */
2095
2204
  declare const isEmpty: BodyPredicate;
2096
2205
  /** Any corner resize handle — top-left/top-right/bottom-left/bottom-right. */
2097
- declare const isResizeHandle: (target: unknown) => boolean;
2206
+ declare const isResizeHandle: TargetPredicate & {
2207
+ readsAffordance: true;
2208
+ };
2098
2209
  /** The rotate-around-center handle above the bounding box. */
2099
- declare const isRotateHandle: (target: unknown) => boolean;
2210
+ declare const isRotateHandle: TargetPredicate & {
2211
+ readsAffordance: true;
2212
+ };
2100
2213
  /** Any path anchor (the on-curve dot). */
2101
- declare const isAnchor: (target: unknown) => boolean;
2214
+ declare const isAnchor: TargetPredicate & {
2215
+ readsAffordance: true;
2216
+ };
2102
2217
  /** Either control handle (controlIn or controlOut). */
2103
- declare const isControlHandle: (target: unknown) => boolean;
2218
+ declare const isControlHandle: TargetPredicate & {
2219
+ readsAffordance: true;
2220
+ };
2104
2221
  /** Any of the three anchor-editing affordances (anchor + both controls). */
2105
- declare const isAnchorOrControl: (target: unknown) => boolean;
2222
+ declare const isAnchorOrControl: TargetPredicate & {
2223
+ readsAffordance: true;
2224
+ };
2106
2225
 
2107
2226
  /**
2108
2227
  * The long-press the gesture dispatcher is timing, as observable state: where
@@ -2181,6 +2300,7 @@ interface LongPressOptions {
2181
2300
  */
2182
2301
  haptics?: boolean;
2183
2302
  }
2303
+ /** A store holding the press currently being held, or none. Pass it as `LongPressOptions.state`. */
2184
2304
  declare function createLongPressStore(): LongPressStore;
2185
2305
 
2186
2306
  /**
@@ -2265,6 +2385,29 @@ declare function liveScope(id: string, eligibility: Eligibility, state: Eligibil
2265
2385
  * of two same-specificity bindings in one tier wins.
2266
2386
  */
2267
2387
  declare function scopeBindings(entries: Iterable<Contribution<unknown>>, state: EligibilityState): ScopedBinding[];
2388
+ /** What decides which entries are live, and in what order they are walked. */
2389
+ interface AssemblyInputs {
2390
+ focusedId: string | null;
2391
+ /** Ids held by a hotkey, oldest first, as the active-tool context keeps them. */
2392
+ hotkeyStack: readonly string[];
2393
+ /** The capability tags the active mode allows. Omitted: no mode system, so
2394
+ * every entry is allowed. */
2395
+ allowedCapabilities?: ReadonlySet<CapabilityTag>;
2396
+ }
2397
+ /**
2398
+ * The entry set as routing walks it, with the state that tiers it. The
2399
+ * dispatcher's bindings and a canvas's tool overlays both come from here, so a
2400
+ * tool the dispatcher would not route to is never drawn, and two held tools
2401
+ * stack in one order.
2402
+ *
2403
+ * Held entries lead, newest hold first: the top of the stack is the one every
2404
+ * reader treats as engaged, so it wins a tie within the hotkey tier. A tagged
2405
+ * entry is live only while the mode allows every one of its tags.
2406
+ */
2407
+ declare function assembleEntries<T extends Contribution<unknown>>(entries: Iterable<T>, inputs: AssemblyInputs): {
2408
+ ordered: T[];
2409
+ state: EligibilityState;
2410
+ };
2268
2411
 
2269
2412
  /**
2270
2413
  * Concatenate contribution bundles into one registry, preserving order —
@@ -2315,9 +2458,8 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
2315
2458
  declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
2316
2459
 
2317
2460
  /**
2318
- * Configurable activation-key descriptor for tools that expose their
2319
- * keybinding to the host (currently Lasso and Eyedropper). Captures
2320
- * only the fields meaningful to a caller-supplied tool-select key —
2461
+ * A tool's activation key, as `ToolDef.keybinding` declares it. Captures
2462
+ * only the fields meaningful to a tool-select key —
2321
2463
  * dispatcher-internal fields (`skipInEditable`, `enabled`,
2322
2464
  * `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
2323
2465
  * not part of the configurable surface.
@@ -2388,13 +2530,10 @@ type ToolBounds = Bounds;
2388
2530
  * actions, overlay, presentation — is inherited.
2389
2531
  */
2390
2532
  interface Tool<TScratch = unknown, TOverlay = KernelOverlay> extends Contribution<TOverlay> {
2391
- /** Optional caller-supplied key. Most built-in tools have their activation
2392
- * key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
2393
- * is for tools that want their activation key to be configurable by the
2394
- * host (currently Lasso and Eyedropper). The dynamic loop in
2395
- * `useKeybindings.ts` picks this up and appends a binding entry to the
2396
- * consolidated `tool.activate` action (with `opts.params.toolId` set so
2397
- * the invoker knows which tool to switch to). */
2533
+ /** The key that activates this tool. `useKeybindings` appends a binding
2534
+ * entry for it to the consolidated `tool.activate` action (with
2535
+ * `opts.params.toolId` set so the invoker knows which tool to switch to).
2536
+ * A tool with none has no activation key. */
2398
2537
  keybinding?: ToolKeybinding;
2399
2538
  initScratch?: () => TScratch;
2400
2539
  cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
@@ -2637,8 +2776,10 @@ interface Conflict {
2637
2776
  * - Broad vs. narrow targets (e.g. an untargeted `click` alongside
2638
2777
  * `click` on `empty`) — the dispatcher's specificity ordering resolves
2639
2778
  * those cleanly, and the broad one is usually the intended fallback.
2640
- * - Different modifier requirements on the same target — they fire on
2641
- * different inputs.
2779
+ * - Modifier requirements no one press satisfies at equal rank: Shift
2780
+ * required against Alt required fire on different inputs, and Shift
2781
+ * required outranks Shift optional. An optional modifier against a
2782
+ * forbidden one is flagged, since both take the bare press.
2642
2783
  * - A binding whose action declines via `enabled()` so a lower-priority
2643
2784
  * one can take the gesture. Detecting that intent would mean evaluating
2644
2785
  * the action; consumers can suppress known-intentional compositions in
@@ -2657,7 +2798,7 @@ interface Conflict {
2657
2798
  * mode's own Escape exit, say. See {@link rulesExclusive}.
2658
2799
  *
2659
2800
  * - Two bindings whose `opts.views` keep them from being live and equally
2660
- * ranked in any one view. See {@link viewsTie}.
2801
+ * ranked in any one view. See `viewsTie`.
2661
2802
  *
2662
2803
  * Note that two bindings sharing a tuple on the SAME tool are now possible
2663
2804
  * (bindings are an array, where phase tables were objects with unique
@@ -2665,7 +2806,7 @@ interface Conflict {
2665
2806
  *
2666
2807
  * This is the raw same-tuple detector. Feeding it a whole tool *registry*
2667
2808
  * over-reports, because registry tools take turns in the active slot and
2668
- * can't collide with each other — see {@link findScopedConflicts}.
2809
+ * can't collide with each other — see `findScopedConflicts`.
2669
2810
  */
2670
2811
  declare function findConflicts(entries: readonly Contribution<unknown>[]): Conflict[];
2671
2812
  /**
@@ -2872,9 +3013,41 @@ declare function clientToCanvasRect(rect: {
2872
3013
  top: number;
2873
3014
  }, clientX: number, clientY: number): [number, number];
2874
3015
 
3016
+ /**
3017
+ * Namespaced, opt-in console tracing for the kit.
3018
+ *
3019
+ * Enable in the browser:
3020
+ *
3021
+ * localStorage.setItem('weasel.debug', '*') // all namespaces
3022
+ * localStorage.setItem('weasel.debug', 'tools,dispatch') // specific ones
3023
+ * localStorage.setItem('weasel.debug', '1') // legacy "all"
3024
+ *
3025
+ * Each call site picks a stable namespace string. Off by default; the flag
3026
+ * is read once and cached per page session — reload to pick up a change.
3027
+ *
3028
+ * The one definition: `@weasel-js/core` and `@weasel-js/ui` pass these on, so
3029
+ * every package reads the flag from one cache.
3030
+ *
3031
+ * Known kit namespaces:
3032
+ * - tools — active-tool + hotkey transitions (useTools)
3033
+ * - dispatch — gesture start / end / cancel (dispatcher)
3034
+ * - drag-rect — useDragRect lifecycle
3035
+ * - drag-radial — useDragRadial lifecycle
3036
+ * - scene — restored-op replay gaps (history persistence)
3037
+ * - scene-canvas — SceneCanvas mount / unmount; adapter paste-translation warnings
3038
+ * - history — applyOps push, no-op suppression, serialize/restore
3039
+ * - clipboard — OS clipboard write/read failures, malformed payloads
3040
+ */
3041
+ /** True when `namespace` (or `*`) is enabled. Pass no argument to test
3042
+ * whether *any* namespace is on (legacy global-flag semantics). */
3043
+ declare function isDebugEnabled(namespace?: string): boolean;
2875
3044
  /** Conditional `console.debug` — no-op unless `namespace` (or `*`) is enabled.
2876
3045
  * Each call is prefixed with `[namespace]` so the source is searchable. */
2877
3046
  declare function dlog(namespace: string, ...args: unknown[]): void;
3047
+ /** Conditional `console.warn` — like `dlog` but at warn level. Use for
3048
+ * diagnostics that indicate something worth surfacing during debugging
3049
+ * (e.g. no-op op batches suppressed). */
3050
+ declare function dwarn(namespace: string, ...args: unknown[]): void;
2878
3051
 
2879
3052
  /**
2880
3053
  * @weasel-js/routing — binding-to-action routing.
@@ -2889,10 +3062,11 @@ declare function dlog(namespace: string, ...args: unknown[]): void;
2889
3062
  * `Action.requires`, `DepRegistry.register`/`get` and `useDepSource` are all
2890
3063
  * keyed on.
2891
3064
  *
2892
- * It is declared **empty** here on purpose. Routing knows that an action names
2893
- * its dependencies and that the registry resolves them at invocation time; it
2894
- * does not know what a selection, a scene or a viewport is. Whoever supplies
2895
- * those merges them in:
3065
+ * It declares one entry, `activeTool`, because the value and the action that
3066
+ * requires it (`tool.offhand`) are both routing's. Routing knows that an
3067
+ * action names its dependencies and that the registry resolves them at
3068
+ * invocation time; it does not know what a selection, a scene, or a viewport
3069
+ * is. Whoever supplies those merges them in:
2896
3070
  *
2897
3071
  * ```ts
2898
3072
  * declare module '@weasel-js/routing' {
@@ -2900,7 +3074,7 @@ declare function dlog(namespace: string, ...args: unknown[]): void;
2900
3074
  * }
2901
3075
  * ```
2902
3076
  *
2903
- * `@weasel-js/core` merges the kit's own 24 deps this way and re-exports
3077
+ * `@weasel-js/core` merges the kit's own deps this way and re-exports
2904
3078
  * `DepSchema`, so a consumer adds its own against `'@weasel-js/core'` and
2905
3079
  * never has to know this module exists. The merge carries through the
2906
3080
  * re-export in both directions — `scripts/smoke-consumer-bundle.mjs` asserts
@@ -2909,6 +3083,8 @@ declare function dlog(namespace: string, ...args: unknown[]): void;
2909
3083
  * both files into one chunk.
2910
3084
  */
2911
3085
  interface DepSchema {
3086
+ /** Currently active tool id + hotkey-hold stack. */
3087
+ activeTool: ActiveToolContextValue;
2912
3088
  }
2913
3089
  /** Every dep name declared by anything that has merged into {@link DepSchema}. */
2914
3090
  type DepName = keyof DepSchema;
@@ -2933,4 +3109,4 @@ type KernelOverlay = OverlaySchema extends {
2933
3109
  overlay: infer O;
2934
3110
  } ? O : unknown;
2935
3111
 
2936
- export { type GestureBinding as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type DepRegistry as D, COARSE_TARGET_SCALE as E, type Condition as F, type Conflict as G, type ContributionChrome as H, type ContributionDeps as I, type ContributionRouting as J, type KernelOverlay as K, type LongPressOptions as L, DEFAULT_ALLOWED_CAPABILITIES as M, DEFAULT_DEVICE_PROFILE as N, type OverlayPosition as O, type DebugSink as P, type DetectedDeviceFacts as Q, type RuleCtx as R, ScopeNode as S, type DeviceProfile as T, type DispatchRecord as U, type DispatchRecordInput as V, type DispatcherContext as W, type DragSample as X, type DroppedCandidate as Y, type Eligibility as Z, type EligibilityState as _, type DepName as a, buildDepsFromRequires as a$, type HandleKind as a0, type HitShape as a1, type HotkeyTrigger as a2, INGEST_STRING_MIMES as a3, type ImmediateInvoker as a4, type InvocationCtx as a5, type Invoker as a6, type KeyBinding as a7, LONG_PRESS_MS as a8, type LongPressState as a9, type ResolveOnlyResult as aA, type ResolvedCandidate as aB, type Rule as aC, type ScratchKey as aD, type ScratchStore as aE, type SelectionApi as aF, type Selector as aG, type SpecificityPart as aH, TOOL_OFFHAND_ID as aI, type Tool as aJ, type ToolBounds as aK, type ToolCtx as aL, type ToolDef as aM, type ToolKeybinding as aN, type ToolLifecycleCtx as aO, type ToolModifiers as aP, type ToolOffhandBindingSpec as aQ, type ToolPresentation as aR, type ToolSlot as aS, type TraceLogEntry as aT, type UiOngoingControl as aU, type View as aV, type ViewportGestureKind as aW, type WalkStep as aX, actionBindings as aY, actionItems as aZ, activatesFocusedControl as a_, type LongPressStore as aa, type MatchOutcome as ab, type MatchResult as ac, type ModeShortcutHandlers as ad, type ModeSwitchLogEntry as ae, type ModifierState as af, NEVER as ag, type NodeId as ah, type OngoingHandle as ai, type OngoingInvoker as aj, type OngoingOverlay as ak, type OverlayRole as al, type OverlaySchema as am, PREDICATE_TARGET as an, type PendingLongPress as ao, type PlacedBy as ap, type Point2 as aq, type PointerSession as ar, type PointerSessionCallbacks as as, type PointerSessionCancelReason as at, type PointerSessionOptions as au, type RankedCandidate as av, type RecordCandidate as aw, type RegistryEntry as ax, type ResizeAnchor as ay, type ResolveAllOptions as az, type DepSchema as b, buildRouteRegistry as b0, buildRuleCtx as b1, buildToolOffhandBindings as b2, clientToCanvas as b3, clientToCanvasRect as b4, createDepRegistry as b5, createDispatcher as b6, createLongPressStore as b7, defineTool as b8, deleteScratch as b9, matchesKeyBinding as bA, mergeContributions as bB, modeShortcutSpec as bC, modeShortcuts as bD, offhandKeyFor as bE, openPointerSession as bF, pointerGestureId as bG, recordModeSwitch as bH, reportRouteConflicts as bI, resolveDeviceProfile as bJ, resolveParams as bK, routesForSpec as bL, ruleCanHoldIn as bM, rulesExclusive as bN, scopeBindings as bO, scratchKey as bP, setScratch as bQ, targetConsultsAffordance as bR, validateActionDefaultBinding as bS, validateActionId as bT, describeRule as ba, dlog as bb, evaluate as bc, evaluateEnabled as bd, findConflicts as be, getScratch as bf, historyKey as bg, isAnchor as bh, isAnchorOrControl as bi, isBody as bj, isControlHandle as bk, isEditableTarget as bl, isEmpty as bm, isResizeHandle as bn, isRotateHandle as bo, isSelectedBody as bp, isTool as bq, isUnselectedBody as br, itemsFromClipboardData as bs, itemsFromDataTransfer as bt, itemsFromFiles as bu, liveScope as bv, makeToolOffhandAction as bw, matchBest as bx, matchSorted as by, matchSortedWithBarred as bz, type Action as c, DepNode as d, type AnyToolOf as e, type ScopedBinding as f, type Dispatcher as g, type AffordanceHit as h, ALWAYS as i, type ActionDeps as j, ActionDisabledReason as k, type ActionDispatch as l, type ActionEnabledResult as m, type ActionEntry as n, type ActionItem as o, type ActionPresentation as p, type ActionSource as q, type ActionVariant as r, type ActionsProp as s, type AnyTool as t, type BindingScope as u, type BindingSource as v, type BodyPredicate as w, type BoundGesture as x, type Bounds as y, type BuildRuleCtxArgs as z };
3112
+ export { type DispatchRecord as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type DepRegistry as D, type BindingScope as E, type BindingSource as F, type BodyPredicate as G, type BoundGesture as H, type Bounds as I, type BuildRuleCtxArgs as J, type KernelOverlay as K, type LongPressOptions as L, COARSE_TARGET_SCALE as M, type Condition as N, type OverlayPosition as O, type Conflict as P, type ContributionChrome as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ContributionDeps as U, type ContributionRouting as V, DEFAULT_ALLOWED_CAPABILITIES as W, DEFAULT_DEVICE_PROFILE as X, type DebugSink as Y, type DetectedDeviceFacts as Z, type DeviceProfile as _, type DepName as a, type TraceLogEntry as a$, type DispatchRecordInput as a0, type DispatcherContext as a1, type DragSample as a2, type DroppedCandidate as a3, type Eligibility as a4, type EligibilityState as a5, type GestureBinding as a6, type HandleKind as a7, type HitShape as a8, type HotkeyTrigger as a9, type PointerSessionCancelReason as aA, type PointerSessionOptions as aB, type RankedCandidate as aC, type RecordCandidate as aD, type RegistryEntry as aE, type ResizeAnchor as aF, type ResolveAllOptions as aG, type ResolveOnlyResult as aH, type ResolvedCandidate as aI, type Rule as aJ, type ScopedBinding as aK, type ScratchKey as aL, type ScratchStore as aM, type SelectionApi as aN, type Selector as aO, type SpecificityPart as aP, TOOL_OFFHAND_ID as aQ, type Tool as aR, type ToolBounds as aS, type ToolCtx as aT, type ToolDef as aU, type ToolKeybinding as aV, type ToolLifecycleCtx as aW, type ToolModifiers as aX, type ToolOffhandBindingSpec as aY, type ToolPresentation as aZ, type ToolSlot as a_, INGEST_STRING_MIMES as aa, type ImmediateInvoker as ab, type InvocationCtx as ac, type Invoker as ad, type KeyBinding as ae, LONG_PRESS_MS as af, type LongPressState as ag, type LongPressStore as ah, type MatchOutcome as ai, type MatchResult as aj, type ModeShortcutHandlers as ak, type ModeSwitchLogEntry as al, type ModifierState as am, NEVER as an, type NodeId as ao, type OngoingHandle as ap, type OngoingInvoker as aq, type OngoingOverlay as ar, type OverlayRole as as, type OverlaySchema as at, PREDICATE_TARGET as au, type PendingLongPress as av, type PlacedBy as aw, type Point2 as ax, type PointerSession as ay, type PointerSessionCallbacks as az, type DepSchema as b, scratchKey as b$, type UiOngoingControl as b0, type ViewportGestureKind as b1, type WalkStep as b2, actionBindings as b3, actionItems as b4, activatesFocusedControl as b5, assembleEntries as b6, buildDepsFromRequires as b7, buildRouteRegistry as b8, buildRuleCtx as b9, isRotateHandle as bA, isSelectedBody as bB, isTool as bC, isUnselectedBody as bD, itemsFromClipboardData as bE, itemsFromDataTransfer as bF, itemsFromFiles as bG, liveScope as bH, makeToolOffhandAction as bI, matchBest as bJ, matchSorted as bK, matchSortedWithBarred as bL, matchesKeyBinding as bM, mergeContributions as bN, modeShortcutSpec as bO, modeShortcuts as bP, offhandKeyFor as bQ, openPointerSession as bR, pointerGestureId as bS, recordModeSwitch as bT, reportRouteConflicts as bU, resolveDeviceProfile as bV, resolveParams as bW, routesForSpec as bX, ruleCanHoldIn as bY, rulesExclusive as bZ, scopeBindings 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, isEditableTarget as bx, isEmpty as by, isResizeHandle as bz, type Action as c, setScratch as c0, targetConsultsAffordance as c1, validateActionDefaultBinding as c2, validateActionId as c3, DepNode as d, type AnyToolOf 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 ActionPresentation as t, useActiveToolContext as u, type ActionSource as v, type ActionVariant as w, type ActionsProp as x, type AnyTool as y, type AssemblyInputs as z };