@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.
- package/README.md +6 -5
- package/dist/{chunk-LOP2JTTA.js → chunk-Y5RURZBH.js} +235 -183
- package/dist/chunk-Y5RURZBH.js.map +1 -0
- package/dist/{index-CrbDNXT_.d.ts → index-gArM2tbt.d.ts} +363 -187
- package/dist/index.d.ts +3 -2
- package/dist/index.js +23 -20
- package/dist/index.js.map +1 -1
- package/dist/{internal-B1B6gfOS.d.ts → internal-B0SsznMG.d.ts} +9 -12
- package/dist/internal.d.ts +4 -3
- package/dist/react.d.ts +30 -73
- package/dist/react.js +410 -299
- package/dist/react.js.map +1 -1
- package/package.json +8 -7
- package/dist/chunk-LOP2JTTA.js.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
1039
|
-
* `true` when
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
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
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
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
|
-
|
|
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
|
|
1412
|
-
*
|
|
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
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
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
|
-
*
|
|
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():
|
|
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
|
|
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
|
|
1915
|
-
* changed anything observable (
|
|
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
|
-
*
|
|
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
|
|
2027
|
-
* `begin` and `list` on any ancestor then answer from
|
|
2028
|
-
* calls it on the pointerdown and
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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:
|
|
2206
|
+
declare const isResizeHandle: TargetPredicate & {
|
|
2207
|
+
readsAffordance: true;
|
|
2208
|
+
};
|
|
2098
2209
|
/** The rotate-around-center handle above the bounding box. */
|
|
2099
|
-
declare const isRotateHandle:
|
|
2210
|
+
declare const isRotateHandle: TargetPredicate & {
|
|
2211
|
+
readsAffordance: true;
|
|
2212
|
+
};
|
|
2100
2213
|
/** Any path anchor (the on-curve dot). */
|
|
2101
|
-
declare const isAnchor:
|
|
2214
|
+
declare const isAnchor: TargetPredicate & {
|
|
2215
|
+
readsAffordance: true;
|
|
2216
|
+
};
|
|
2102
2217
|
/** Either control handle (controlIn or controlOut). */
|
|
2103
|
-
declare const isControlHandle:
|
|
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:
|
|
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
|
-
*
|
|
2319
|
-
*
|
|
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
|
-
/**
|
|
2392
|
-
*
|
|
2393
|
-
*
|
|
2394
|
-
*
|
|
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
|
-
* -
|
|
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
|
|
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
|
|
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
|
|
2893
|
-
*
|
|
2894
|
-
*
|
|
2895
|
-
*
|
|
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
|
|
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
|
|
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 };
|