@weasel-js/routing 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-D2TZLDP5.js → chunk-Q3Y3DPKH.js} +770 -370
- package/dist/chunk-Q3Y3DPKH.js.map +1 -0
- package/dist/{index-B_KvoXww.d.ts → index-Bmq4lSHY.d.ts} +601 -247
- package/dist/index.d.ts +2 -3
- package/dist/index.js +94 -8
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +220 -87
- package/dist/react.js +362 -302
- package/dist/react.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-D2TZLDP5.js.map +0 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { CapabilityTag } from '@weasel-js/modes';
|
|
1
|
+
import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
import { CursorSpec } from '@weasel-js/cursor';
|
|
5
|
-
import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
|
|
6
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
|
|
7
6
|
import { Op } from '@weasel-js/history';
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -235,6 +234,10 @@ interface RuleCtx {
|
|
|
235
234
|
* density 1. That is what the kit assumed before this field existed, so
|
|
236
235
|
* an absent profile is behavior-preserving by construction. */
|
|
237
236
|
readonly device?: DeviceProfile;
|
|
237
|
+
/** The view the input being dispatched landed in — a view id, or `null`
|
|
238
|
+
* for the surface's own camera. The dispatcher fills it; absent anywhere
|
|
239
|
+
* else a rule is evaluated. */
|
|
240
|
+
readonly viewId?: string | null;
|
|
238
241
|
}
|
|
239
242
|
/** The live state a `RuleCtx` is assembled from — the chrome context plus
|
|
240
243
|
* what the active mode allows. */
|
|
@@ -379,6 +382,24 @@ interface Condition {
|
|
|
379
382
|
orNot(other: Condition | Rule): Condition;
|
|
380
383
|
}
|
|
381
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
387
|
+
*
|
|
388
|
+
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
389
|
+
* about the selector values themselves (two different `mode`s, an empty
|
|
390
|
+
* selection against a non-empty one, `focused: true` against `false`) and
|
|
391
|
+
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
392
|
+
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
393
|
+
* that share a tuple but are never eligible together.
|
|
394
|
+
*/
|
|
395
|
+
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
396
|
+
/**
|
|
397
|
+
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
398
|
+
* `capability` tests are settled by the mode; every other test could go either
|
|
399
|
+
* way, so `false` is a proof and `true` means "may hold".
|
|
400
|
+
*/
|
|
401
|
+
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
402
|
+
|
|
382
403
|
/** The detected half of a profile — everything except the derived scale. */
|
|
383
404
|
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
384
405
|
/**
|
|
@@ -587,6 +608,12 @@ interface InvocationCtx {
|
|
|
587
608
|
key: string;
|
|
588
609
|
repeat: boolean;
|
|
589
610
|
};
|
|
611
|
+
/**
|
|
612
|
+
* The view the input landed in, as the surface's view resolver assigned it:
|
|
613
|
+
* a view id, or `null` for the surface's own camera. Absent where no
|
|
614
|
+
* surface routed the input — a UI-driven trigger, a bare dispatcher.
|
|
615
|
+
*/
|
|
616
|
+
viewId?: string | null;
|
|
590
617
|
/**
|
|
591
618
|
* Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
|
|
592
619
|
* UI-driven ongoing actions (color picker, opacity slider) so handles can
|
|
@@ -617,6 +644,16 @@ interface BindingOpts {
|
|
|
617
644
|
* values at commit, the invoker can re-call the thunk inside `onEnd`
|
|
618
645
|
* via `resolveParams(opts?.params)`. */
|
|
619
646
|
params?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
647
|
+
/**
|
|
648
|
+
* The views this binding is live in, by id, where `null` is the surface's
|
|
649
|
+
* own camera. Omitted means every view.
|
|
650
|
+
*
|
|
651
|
+
* A binding that names the view an input landed in outranks every binding
|
|
652
|
+
* that does not, whatever their scope tiers: naming a view is the most
|
|
653
|
+
* specific thing a binding can say about where it applies. Without that, a
|
|
654
|
+
* drag bound inside a minimap view loses to the active tool's marquee.
|
|
655
|
+
*/
|
|
656
|
+
views?: readonly (string | null)[];
|
|
620
657
|
}
|
|
621
658
|
/** Resolve `BindingOpts.params` to a concrete record (calling the thunk if
|
|
622
659
|
* needed). Returns `undefined` for absent params. */
|
|
@@ -1060,7 +1097,7 @@ interface ActionDispatch extends BindingSource {
|
|
|
1060
1097
|
* gesture.
|
|
1061
1098
|
*
|
|
1062
1099
|
* This is where mid-gesture cursors live now. They used to come from the
|
|
1063
|
-
* tool side — `
|
|
1100
|
+
* tool side — an `engaged` phase entry's cursor,
|
|
1064
1101
|
* or a function-form `Tool.cursor` reading the gesture scratch out of the
|
|
1065
1102
|
* tool-routing dispatcher. Both belonged to a pipeline whose whole job was
|
|
1066
1103
|
* being taken over by bindings, and neither could describe a cursor for an
|
|
@@ -1131,6 +1168,65 @@ interface ActionEnabledResult {
|
|
|
1131
1168
|
*/
|
|
1132
1169
|
declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabledResult;
|
|
1133
1170
|
|
|
1171
|
+
/**
|
|
1172
|
+
* The tree both input registries hang on. A provider is a root; every canvas
|
|
1173
|
+
* mounts a scope under the registry in scope; a yoke sits between the two. A
|
|
1174
|
+
* registration lives in the node it was made in, and a lookup starts at a leaf
|
|
1175
|
+
* and walks up, so one canvas never answers for another.
|
|
1176
|
+
*
|
|
1177
|
+
* Design: docs/proposals/2026-09-27-input-scopes.md.
|
|
1178
|
+
*/
|
|
1179
|
+
/** What every node of one tree shares: a version and the listeners to it. */
|
|
1180
|
+
interface ScopeTree {
|
|
1181
|
+
version: number;
|
|
1182
|
+
readonly listeners: Set<() => void>;
|
|
1183
|
+
}
|
|
1184
|
+
declare class ScopeNode<N extends ScopeNode<N>> {
|
|
1185
|
+
readonly parent: N | null;
|
|
1186
|
+
readonly descends: boolean;
|
|
1187
|
+
readonly tree: ScopeTree;
|
|
1188
|
+
private readonly children;
|
|
1189
|
+
private activeChild;
|
|
1190
|
+
/** `descends`: whether reads made here answer from the active child down —
|
|
1191
|
+
* true for a root or a yoke, which chrome reads through; false for a scope,
|
|
1192
|
+
* which answers for itself even with a scope nested inside it. */
|
|
1193
|
+
constructor(parent: N | null, descends: boolean);
|
|
1194
|
+
/** Bump the tree's version and tell every listener in it. */
|
|
1195
|
+
changed(): void;
|
|
1196
|
+
/** Join the parent's children, newest last; returns the leave. */
|
|
1197
|
+
mount(): () => void;
|
|
1198
|
+
/** Make this node the one its ancestors resolve through. */
|
|
1199
|
+
activate(): void;
|
|
1200
|
+
/** The node a lookup made here starts from: down the active children, or the
|
|
1201
|
+
* newest where nothing has been activated yet. A yoke with no members yet
|
|
1202
|
+
* is passed over — there is no canvas behind it to answer. */
|
|
1203
|
+
leaf(): N;
|
|
1204
|
+
private newestMember;
|
|
1205
|
+
/** Where a read made through this node starts. */
|
|
1206
|
+
start(): N;
|
|
1207
|
+
/** Whether this node is on the path a read from the root takes. */
|
|
1208
|
+
isActive(): boolean;
|
|
1209
|
+
/** This node, then each ancestor up to the root. */
|
|
1210
|
+
chain(): Generator<N>;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Dep registry for registry unification.
|
|
1215
|
+
*
|
|
1216
|
+
* Holds named "live source" thunks. Actions declare `requires: ['selection']`;
|
|
1217
|
+
* the dispatcher calls `registry.get('selection')` at invocation time to
|
|
1218
|
+
* build a typed Deps bag.
|
|
1219
|
+
*
|
|
1220
|
+
* `DepSchema` — the map of dep name → value type — is the single source of
|
|
1221
|
+
* truth for what `requires`/`register`/`get` accept. Its concrete fields live
|
|
1222
|
+
* in `./depSchema` (co-located with the dep value types). It is declared there
|
|
1223
|
+
* as a plain `export interface` rather than via a cross-module
|
|
1224
|
+
* `declare module './depRegistry'` augmentation: that augmentation does NOT
|
|
1225
|
+
* merge once rollup-plugin-dts flattens both files into one `.d.ts` chunk,
|
|
1226
|
+
* which silently emptied `DepSchema` for consumers. Consumers still extend it
|
|
1227
|
+
* via `declare module '@weasel-js/core'` augmentation.
|
|
1228
|
+
*/
|
|
1229
|
+
|
|
1134
1230
|
/** Holds the live sources an action's declared dependencies resolve to.
|
|
1135
1231
|
* Sources are thunks, read at invocation time, so an action never captures
|
|
1136
1232
|
* stale state. */
|
|
@@ -1138,23 +1234,20 @@ interface DepRegistry {
|
|
|
1138
1234
|
register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
|
|
1139
1235
|
get<K extends DepName>(name: K): DepSchema[K] | undefined;
|
|
1140
1236
|
}
|
|
1141
|
-
/**
|
|
1142
|
-
*
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
* component. The `source` thunk is called at dispatch time and should
|
|
1156
|
-
* return the latest value. */
|
|
1157
|
-
declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
|
|
1237
|
+
/** One node of the dep tree: the sources registered at it, over its parent's.
|
|
1238
|
+
* Each name holds a stack of sources, newest live, so a displaced source is
|
|
1239
|
+
* restored when the one above it leaves. */
|
|
1240
|
+
declare class DepNode extends ScopeNode<DepNode> {
|
|
1241
|
+
private readonly sources;
|
|
1242
|
+
readonly registry: DepRegistry;
|
|
1243
|
+
constructor(parent: DepNode | null, descends: boolean);
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* A stock dep registry with no parent: what `<DepRegistryProvider>` mounts,
|
|
1247
|
+
* for a dispatcher driven without React. Sources registered under one name
|
|
1248
|
+
* stack, newest live, and releasing one restores the one beneath it.
|
|
1249
|
+
*/
|
|
1250
|
+
declare function createDepRegistry(): DepRegistry;
|
|
1158
1251
|
|
|
1159
1252
|
/**
|
|
1160
1253
|
* Shared dep-bag builder — the single implementation behind both the gesture
|
|
@@ -1168,9 +1261,13 @@ declare function useDepSource<K extends DepName>(name: K, source: () => DepSchem
|
|
|
1168
1261
|
declare function buildDepsFromRequires(action: Action, depRegistry: DepRegistry): ActionDeps;
|
|
1169
1262
|
|
|
1170
1263
|
/** Hotkey-slot trigger key. The slot is engaged while this key is held —
|
|
1171
|
-
* hence "hotkey": active as long as the key is hot.
|
|
1172
|
-
*
|
|
1173
|
-
|
|
1264
|
+
* hence "hotkey": active as long as the key is hot. Omitted means the tool
|
|
1265
|
+
* is not eligible for the hotkey slot.
|
|
1266
|
+
*
|
|
1267
|
+
* The named keys map to their DOM names (`'space'` is `' '`, `'ctrl'` is
|
|
1268
|
+
* `'Control'`). Anything else is a `KeyboardEvent.key` value, matched
|
|
1269
|
+
* case-insensitively as every key spec is — so `'o'` holds on O. */
|
|
1270
|
+
type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {});
|
|
1174
1271
|
/** Presentation metadata for tool palettes / menus. Optional on every
|
|
1175
1272
|
* tool — consumers that render a palette (`<ToolPalette>`) read these
|
|
1176
1273
|
* fields to display the tool; consumers that don't can ignore them.
|
|
@@ -1190,6 +1287,10 @@ interface ToolPresentation<TScratch = unknown> {
|
|
|
1190
1287
|
/** Display override for the keyboard shortcut. When omitted the palette
|
|
1191
1288
|
* derives one from `Tool.keybinding` via its own formatter. */
|
|
1192
1289
|
shortcut?: string;
|
|
1290
|
+
/** Keep the tool off palettes — for a tool that is only ever held, like a
|
|
1291
|
+
* scrub engaged by its `hotkey`. It stays registered and selectable by
|
|
1292
|
+
* id; only the palette leaves it out. */
|
|
1293
|
+
hide?: boolean;
|
|
1193
1294
|
}
|
|
1194
1295
|
/**
|
|
1195
1296
|
* When an entry's bindings are live. A set, not one value: the hand tool is
|
|
@@ -1215,21 +1316,28 @@ interface Eligibility {
|
|
|
1215
1316
|
* all three collapse to `'top'`.
|
|
1216
1317
|
*/
|
|
1217
1318
|
type OverlayPosition = 'top' | 'before-selection' | 'after-selection';
|
|
1319
|
+
/** Dep sources an entry provides while it is installed, by dep name. */
|
|
1320
|
+
type ContributionDeps = {
|
|
1321
|
+
[K in DepName]?: () => DepSchema[K];
|
|
1322
|
+
};
|
|
1218
1323
|
/**
|
|
1219
1324
|
* What routing reads off an entry: when its bindings are live, which they
|
|
1220
|
-
* are,
|
|
1325
|
+
* are, the actions they name, and the deps those actions read.
|
|
1221
1326
|
*/
|
|
1222
1327
|
interface ContributionRouting {
|
|
1223
1328
|
id: string;
|
|
1224
1329
|
eligibility: Eligibility;
|
|
1225
1330
|
bindings?: GestureBinding[];
|
|
1226
1331
|
actions?: Action[];
|
|
1332
|
+
/** Dep sources this entry provides while it is installed. A host installs
|
|
1333
|
+
* them for as long as the entry is registered, whatever its eligibility. */
|
|
1334
|
+
deps?: ContributionDeps;
|
|
1227
1335
|
}
|
|
1228
1336
|
/**
|
|
1229
1337
|
* What an entry draws and how a palette shows it. None of it reaches the
|
|
1230
1338
|
* dispatcher.
|
|
1231
1339
|
*/
|
|
1232
|
-
interface ContributionChrome<TOverlay =
|
|
1340
|
+
interface ContributionChrome<TOverlay = KernelOverlay> {
|
|
1233
1341
|
/** One layer, or several composed in the given order.
|
|
1234
1342
|
*
|
|
1235
1343
|
* Routing never reads a layer — it collects the live ones and hands them
|
|
@@ -1245,117 +1353,11 @@ interface ContributionChrome<TOverlay = unknown> {
|
|
|
1245
1353
|
* is optional and independent — an entry that only routes input declares only
|
|
1246
1354
|
* `bindings` and `actions`.
|
|
1247
1355
|
*/
|
|
1248
|
-
interface Contribution<TOverlay =
|
|
1356
|
+
interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, ContributionChrome<TOverlay> {
|
|
1249
1357
|
/** Reflection escape hatch — the authored form, when there was one. */
|
|
1250
1358
|
def?: unknown;
|
|
1251
1359
|
}
|
|
1252
1360
|
|
|
1253
|
-
/**
|
|
1254
|
-
* Configurable activation-key descriptor for tools that expose their
|
|
1255
|
-
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
1256
|
-
* only the fields meaningful to a caller-supplied tool-select key —
|
|
1257
|
-
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
1258
|
-
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
1259
|
-
* not part of the configurable surface.
|
|
1260
|
-
*/
|
|
1261
|
-
interface ToolKeybinding {
|
|
1262
|
-
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
1263
|
-
key: string | readonly string[];
|
|
1264
|
-
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
1265
|
-
mod?: boolean;
|
|
1266
|
-
/** Require Alt. Default `false`. */
|
|
1267
|
-
alt?: boolean;
|
|
1268
|
-
/**
|
|
1269
|
-
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
1270
|
-
* shift, `'optional'` allows either.
|
|
1271
|
-
*/
|
|
1272
|
-
shift?: boolean | 'optional';
|
|
1273
|
-
}
|
|
1274
|
-
/** Modifier-key snapshot at event dispatch time. */
|
|
1275
|
-
type ToolModifiers = ModifierState;
|
|
1276
|
-
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
1277
|
-
* via the tool's `TScratch` parameter; it survives across a single
|
|
1278
|
-
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
1279
|
-
* gesture start by `initScratch()`. */
|
|
1280
|
-
interface ToolCtx<TScratch = unknown> {
|
|
1281
|
-
worldX: number;
|
|
1282
|
-
worldY: number;
|
|
1283
|
-
modifiers: ToolModifiers;
|
|
1284
|
-
selection: SelectionApi;
|
|
1285
|
-
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
1286
|
-
* cast to a known shape. This layer doesn't constrain it. */
|
|
1287
|
-
adapter: unknown;
|
|
1288
|
-
applyOps: (ops: Op[], label: string) => void;
|
|
1289
|
-
/** Current viewport. Reflects camera-position semantics — see
|
|
1290
|
-
* `View` JSDoc. */
|
|
1291
|
-
view: View;
|
|
1292
|
-
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
1293
|
-
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
1294
|
-
* state. View changes are not undoable. */
|
|
1295
|
-
setView: (next: View) => void;
|
|
1296
|
-
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
1297
|
-
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
1298
|
-
* anchors. */
|
|
1299
|
-
canvasRect: DOMRect;
|
|
1300
|
-
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
1301
|
-
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
1302
|
-
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
1303
|
-
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
1304
|
-
debug?: DebugSink;
|
|
1305
|
-
scratch: TScratch;
|
|
1306
|
-
}
|
|
1307
|
-
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
1308
|
-
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
1309
|
-
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
1310
|
-
type ToolBounds = Bounds;
|
|
1311
|
-
/**
|
|
1312
|
-
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
1313
|
-
* into, plus the hooks that only make sense for one (`initScratch`,
|
|
1314
|
-
* activate/deactivate, live preview, `cursor`). Everything else — bindings,
|
|
1315
|
-
* actions, overlay, presentation — is inherited.
|
|
1316
|
-
*/
|
|
1317
|
-
interface Tool<TScratch = unknown, TOverlay = unknown> extends Contribution<TOverlay> {
|
|
1318
|
-
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
1319
|
-
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
1320
|
-
* is for tools that want their activation key to be configurable by the
|
|
1321
|
-
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
1322
|
-
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
1323
|
-
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
1324
|
-
* the invoker knows which tool to switch to). */
|
|
1325
|
-
keybinding?: ToolKeybinding;
|
|
1326
|
-
initScratch?: () => TScratch;
|
|
1327
|
-
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
1328
|
-
onActivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
1329
|
-
onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
1330
|
-
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
1331
|
-
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
1332
|
-
* reflect live gesture state without reaching into hook internals. The
|
|
1333
|
-
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
1334
|
-
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
1335
|
-
* the use site. */
|
|
1336
|
-
previewPose?: (id: string) => unknown;
|
|
1337
|
-
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
1338
|
-
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
1339
|
-
* can compute bounds without round-tripping through a geometry adapter. */
|
|
1340
|
-
previewBounds?: (id: string) => ToolBounds | null;
|
|
1341
|
-
/** Returns ids whose committed scene-render should be suppressed while this
|
|
1342
|
-
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
1343
|
-
* preview ghosts replace the committed pose). The standard scene slot
|
|
1344
|
-
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
1345
|
-
* `null` when no gesture is in flight. */
|
|
1346
|
-
previewIds?: () => Iterable<string> | null;
|
|
1347
|
-
}
|
|
1348
|
-
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
1349
|
-
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
1350
|
-
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
1351
|
-
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
1352
|
-
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
1353
|
-
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
1354
|
-
type AnyTool = Tool<any, any>;
|
|
1355
|
-
/** "A tool of any scratch type that draws `TOverlay`" — the container form for
|
|
1356
|
-
* registries that hold heterogeneous scratch shapes but one overlay type. */
|
|
1357
|
-
type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
1358
|
-
|
|
1359
1361
|
/**
|
|
1360
1362
|
* Pure matcher primitives live in `@weasel-js/gestures`. This file
|
|
1361
1363
|
* re-exports them for kit-internal consumers and layers the actions-layer
|
|
@@ -1370,18 +1372,18 @@ type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
|
1370
1372
|
interface ScopedBinding {
|
|
1371
1373
|
binding: GestureBinding;
|
|
1372
1374
|
scope: BindingScope;
|
|
1373
|
-
/**
|
|
1374
|
-
* to
|
|
1375
|
-
*
|
|
1376
|
-
|
|
1375
|
+
/** Id of the tool or contribution that declared this binding —
|
|
1376
|
+
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
1377
|
+
* action's own binding. */
|
|
1378
|
+
ownerId: string | null;
|
|
1377
1379
|
}
|
|
1378
1380
|
/** The binding that won a match. */
|
|
1379
1381
|
interface MatchResult {
|
|
1380
1382
|
binding: GestureBinding;
|
|
1381
1383
|
scope: BindingScope;
|
|
1382
|
-
/**
|
|
1383
|
-
* so the dispatcher can record it as the handle owner. */
|
|
1384
|
-
|
|
1384
|
+
/** Id of the entry that declared the binding — propagated from
|
|
1385
|
+
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
1386
|
+
ownerId: string | null;
|
|
1385
1387
|
}
|
|
1386
1388
|
/**
|
|
1387
1389
|
* True when a spec's target actually consults the affordance hit rather than
|
|
@@ -1396,19 +1398,6 @@ interface MatchResult {
|
|
|
1396
1398
|
* that declare nothing.
|
|
1397
1399
|
*/
|
|
1398
1400
|
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
1399
|
-
/** CSS-style specificity tuple for a GestureSpec. Higher tuple wins under
|
|
1400
|
-
* lexicographic compare. Dimensions, in order of precedence:
|
|
1401
|
-
*
|
|
1402
|
-
* [0] target — how much the spec's target narrows; see `targetRank`.
|
|
1403
|
-
* [1] mods — count of required modifier keys (shift/alt/ctrl/meta/mod).
|
|
1404
|
-
* `'optional'` does NOT count.
|
|
1405
|
-
* [2] phase — how much the spec's `phase` narrows; see `phaseRank`.
|
|
1406
|
-
* [3] exact — per-kind tiebreak: 2 for a drop/paste spec with a
|
|
1407
|
-
* non-empty `types` MIME filter, else 1.
|
|
1408
|
-
*
|
|
1409
|
-
* Identical tuples fall back to registration order in the matcher's
|
|
1410
|
-
* stable sort, preserving the pre-specificity tiebreaker. */
|
|
1411
|
-
declare function specificity(spec: GestureSpec): readonly [number, number, number, number];
|
|
1412
1401
|
/**
|
|
1413
1402
|
* Find the best-matching binding across all scopes.
|
|
1414
1403
|
*
|
|
@@ -1437,6 +1426,167 @@ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], is
|
|
|
1437
1426
|
* action's `start()` returns an empty handle), the next match is tried.
|
|
1438
1427
|
*/
|
|
1439
1428
|
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
1429
|
+
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
1430
|
+
interface MatchOutcome {
|
|
1431
|
+
matches: MatchResult[];
|
|
1432
|
+
/** Bindings whose spec matched the event but which the claim barred, in
|
|
1433
|
+
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
1434
|
+
barred: MatchResult[];
|
|
1435
|
+
/** Owner of the exclusive claim, when one applied. */
|
|
1436
|
+
claimOwner?: string;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
1440
|
+
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
1441
|
+
* opt-in — the dispatcher asks only when it is recording.
|
|
1442
|
+
*/
|
|
1443
|
+
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
1444
|
+
collectBarred?: boolean;
|
|
1445
|
+
warn?: (message: string) => void;
|
|
1446
|
+
}): MatchOutcome;
|
|
1447
|
+
|
|
1448
|
+
/**
|
|
1449
|
+
* How one input was resolved: every binding that matched it, what each filter
|
|
1450
|
+
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
1451
|
+
* that order did. The dispatcher fills one per input in dev builds, and
|
|
1452
|
+
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
1453
|
+
*
|
|
1454
|
+
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
1455
|
+
* buffer, serialized, or built by hand as a fixture.
|
|
1456
|
+
*/
|
|
1457
|
+
interface DispatchRecord {
|
|
1458
|
+
kind: 'dispatch';
|
|
1459
|
+
ts: number;
|
|
1460
|
+
input: DispatchRecordInput;
|
|
1461
|
+
/** Every binding whose spec matched the input, in matcher order, including
|
|
1462
|
+
* those a filter then dropped. */
|
|
1463
|
+
matched: readonly RecordCandidate[];
|
|
1464
|
+
dropped: readonly DroppedCandidate[];
|
|
1465
|
+
/** The candidates that survived both filters, best first. */
|
|
1466
|
+
ranked: readonly RankedCandidate[];
|
|
1467
|
+
/** True when nothing was invoked: the record answers "what would happen". */
|
|
1468
|
+
predicted: boolean;
|
|
1469
|
+
/** Action id of the candidate that fired (or would), else `null`. */
|
|
1470
|
+
fired: string | null;
|
|
1471
|
+
outcome: 'handled' | 'unhandled';
|
|
1472
|
+
}
|
|
1473
|
+
interface DispatchRecordInput {
|
|
1474
|
+
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
1475
|
+
eventKind: string;
|
|
1476
|
+
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
1477
|
+
key?: string;
|
|
1478
|
+
modifiers: {
|
|
1479
|
+
alt: boolean;
|
|
1480
|
+
ctrl: boolean;
|
|
1481
|
+
meta: boolean;
|
|
1482
|
+
shift: boolean;
|
|
1483
|
+
};
|
|
1484
|
+
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
1485
|
+
viewId: string | null;
|
|
1486
|
+
/** World point, for events that carry one. */
|
|
1487
|
+
world?: {
|
|
1488
|
+
x: number;
|
|
1489
|
+
y: number;
|
|
1490
|
+
};
|
|
1491
|
+
/** Active mode id, when the host supplies a rule context. */
|
|
1492
|
+
mode?: string;
|
|
1493
|
+
}
|
|
1494
|
+
/** One matched binding. */
|
|
1495
|
+
interface RecordCandidate {
|
|
1496
|
+
actionId: string;
|
|
1497
|
+
/** The binding's spec in route grammar — one per arg alternative. */
|
|
1498
|
+
routes: readonly string[];
|
|
1499
|
+
scope: BindingScope;
|
|
1500
|
+
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
1501
|
+
ownerId: string | null;
|
|
1502
|
+
/** Whether the binding's `views` names the routed view. */
|
|
1503
|
+
namesView: boolean;
|
|
1504
|
+
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
1505
|
+
specificity: readonly [number, number, number, number];
|
|
1506
|
+
/** The action's `eligible` rule as text, when it declares one. */
|
|
1507
|
+
eligible?: string;
|
|
1508
|
+
}
|
|
1509
|
+
type DroppedCandidate =
|
|
1510
|
+
/** An exclusive claim on the input barred bindings that do not read the
|
|
1511
|
+
* claim's affordance. */
|
|
1512
|
+
{
|
|
1513
|
+
candidate: RecordCandidate;
|
|
1514
|
+
filter: 'claim';
|
|
1515
|
+
owner?: string;
|
|
1516
|
+
}
|
|
1517
|
+
/** The action's `eligible` rule does not hold. */
|
|
1518
|
+
| {
|
|
1519
|
+
candidate: RecordCandidate;
|
|
1520
|
+
filter: 'ineligible';
|
|
1521
|
+
rule: string;
|
|
1522
|
+
};
|
|
1523
|
+
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
1524
|
+
/** The ranking step that put a candidate below the one before it. */
|
|
1525
|
+
type PlacedBy = {
|
|
1526
|
+
step: 'first';
|
|
1527
|
+
}
|
|
1528
|
+
/** The one above names the routed view; this one does not. */
|
|
1529
|
+
| {
|
|
1530
|
+
step: 'view';
|
|
1531
|
+
}
|
|
1532
|
+
/** The one above sits in a higher scope tier. */
|
|
1533
|
+
| {
|
|
1534
|
+
step: 'tier';
|
|
1535
|
+
}
|
|
1536
|
+
/** Same tier; the one above is more specific in `part`. */
|
|
1537
|
+
| {
|
|
1538
|
+
step: 'specificity';
|
|
1539
|
+
part: SpecificityPart;
|
|
1540
|
+
}
|
|
1541
|
+
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
1542
|
+
| {
|
|
1543
|
+
step: 'context';
|
|
1544
|
+
}
|
|
1545
|
+
/** Tied on everything; registration order. */
|
|
1546
|
+
| {
|
|
1547
|
+
step: 'order';
|
|
1548
|
+
};
|
|
1549
|
+
/** What the walk did with one ranked candidate. */
|
|
1550
|
+
type WalkStep = {
|
|
1551
|
+
kind: 'fired';
|
|
1552
|
+
}
|
|
1553
|
+
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
1554
|
+
* so an ongoing action here may still bail at `start()` for real. */
|
|
1555
|
+
| {
|
|
1556
|
+
kind: 'would-fire';
|
|
1557
|
+
}
|
|
1558
|
+
/** `enabled()` returned this reason. */
|
|
1559
|
+
| {
|
|
1560
|
+
kind: 'declined';
|
|
1561
|
+
reason: string;
|
|
1562
|
+
}
|
|
1563
|
+
/** An ongoing action's `start()` returned an empty handle. */
|
|
1564
|
+
| {
|
|
1565
|
+
kind: 'empty-handle';
|
|
1566
|
+
}
|
|
1567
|
+
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
1568
|
+
| {
|
|
1569
|
+
kind: 'misbound';
|
|
1570
|
+
}
|
|
1571
|
+
/** Its action was already tried higher up; each runs at most once. */
|
|
1572
|
+
| {
|
|
1573
|
+
kind: 'duplicate';
|
|
1574
|
+
} | {
|
|
1575
|
+
kind: 'no-such-action';
|
|
1576
|
+
}
|
|
1577
|
+
/** Below the winner; never asked. */
|
|
1578
|
+
| {
|
|
1579
|
+
kind: 'not-asked';
|
|
1580
|
+
}
|
|
1581
|
+
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
1582
|
+
| {
|
|
1583
|
+
kind: 'outranked';
|
|
1584
|
+
};
|
|
1585
|
+
interface RankedCandidate {
|
|
1586
|
+
candidate: RecordCandidate;
|
|
1587
|
+
placedBy: PlacedBy;
|
|
1588
|
+
walk: WalkStep;
|
|
1589
|
+
}
|
|
1440
1590
|
|
|
1441
1591
|
/**
|
|
1442
1592
|
* Dispatcher orchestrator — pure module, no React, no DOM.
|
|
@@ -1446,10 +1596,19 @@ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[],
|
|
|
1446
1596
|
* on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
|
|
1447
1597
|
* in-flight handles.
|
|
1448
1598
|
*
|
|
1449
|
-
* ##
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1599
|
+
* ## Precedence and fall-through
|
|
1600
|
+
* An exclusive affordance claim first bars every binding that does not
|
|
1601
|
+
* consult the affordance. The rest are ranked, best first, by:
|
|
1602
|
+
* 1. bindings naming the routed view ahead of every other
|
|
1603
|
+
* (`preferViewScoped`);
|
|
1604
|
+
* 2. scope tier: hotkey > active > ambient;
|
|
1605
|
+
* 3. specificity within a tier (`specificity()` in `matcher.ts`);
|
|
1606
|
+
* 4. an action gated by an `eligible` rule that holds now ahead of one with
|
|
1607
|
+
* no rule (`preferContextual`) — Escape while editing a path exits the
|
|
1608
|
+
* edit rather than resetting the tool;
|
|
1609
|
+
* 5. registration order.
|
|
1610
|
+
* Candidates whose `eligible` rule is false are dropped first. The dispatcher
|
|
1611
|
+
* walks the rest and fires the first action whose `enabled()` returns
|
|
1453
1612
|
* `true`. If every candidate's `enabled()` returns a disabled reason, the
|
|
1454
1613
|
* event is unhandled. This mirrors CSS-style specificity matching with a
|
|
1455
1614
|
* `:not(:disabled)` filter, and lets a tool declare a high-specificity
|
|
@@ -1489,23 +1648,6 @@ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[],
|
|
|
1489
1648
|
* synthesized event with no originating DOM pointer.
|
|
1490
1649
|
*/
|
|
1491
1650
|
declare function pointerGestureId(pointerId: number | undefined): string;
|
|
1492
|
-
/** One entry per `handleInput` call. */
|
|
1493
|
-
interface DispatchLogEntry {
|
|
1494
|
-
kind: 'dispatch';
|
|
1495
|
-
ts: number;
|
|
1496
|
-
eventKind: string;
|
|
1497
|
-
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`).
|
|
1498
|
-
* Lives on the event side of the entry — `fired` should describe
|
|
1499
|
-
* what dispatched, not which key triggered it. */
|
|
1500
|
-
key?: string;
|
|
1501
|
-
candidates: Array<{
|
|
1502
|
-
actionId: string;
|
|
1503
|
-
scope: BindingScope;
|
|
1504
|
-
enabledResult: boolean | string;
|
|
1505
|
-
}>;
|
|
1506
|
-
fired: string | null;
|
|
1507
|
-
outcome: 'handled' | 'unhandled';
|
|
1508
|
-
}
|
|
1509
1651
|
/** One entry per "mode change" — a kit state transition that isn't a
|
|
1510
1652
|
* dispatched input event but is load-bearing for understanding why an
|
|
1511
1653
|
* input was (or wasn't) handled later. Pushed via `recordModeSwitch()`;
|
|
@@ -1522,7 +1664,17 @@ interface ModeSwitchLogEntry {
|
|
|
1522
1664
|
* `'selection-dropped target'`, etc. */
|
|
1523
1665
|
detail?: string;
|
|
1524
1666
|
}
|
|
1525
|
-
|
|
1667
|
+
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
1668
|
+
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
1669
|
+
/**
|
|
1670
|
+
* Dev only: offer the record for what a press at the pointer would do, as a
|
|
1671
|
+
* thunk on `window.__weaselDispatchLive__`. The hover pump offers one per move
|
|
1672
|
+
* and a reader builds the record only when it looks, so no record is built
|
|
1673
|
+
* while nothing is watching. `null` withdraws it when the pointer leaves.
|
|
1674
|
+
*
|
|
1675
|
+
* @internal
|
|
1676
|
+
*/
|
|
1677
|
+
declare function publishLiveDispatch(explain: (() => DispatchRecord) | null): void;
|
|
1526
1678
|
/** Push a mode-switch record into the trace log. Safe to call from
|
|
1527
1679
|
* anywhere; no-ops outside DEV. */
|
|
1528
1680
|
declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
@@ -1536,12 +1688,12 @@ interface DispatcherContext {
|
|
|
1536
1688
|
actions: ActionSource;
|
|
1537
1689
|
/** Dep sources keyed by name. */
|
|
1538
1690
|
depRegistry: DepRegistry;
|
|
1539
|
-
/** Active tool's id (from ActiveToolContext). */
|
|
1540
|
-
activeToolId: string;
|
|
1691
|
+
/** Active tool's id (from ActiveToolContext); `null` when none is. */
|
|
1692
|
+
activeToolId: string | null;
|
|
1541
1693
|
/** Held-hotkey stack, top of stack last. */
|
|
1542
1694
|
hotkeyStack: readonly string[];
|
|
1543
|
-
/**
|
|
1544
|
-
|
|
1695
|
+
/** Every registered entry, tools and ambient contributions alike, by id. */
|
|
1696
|
+
entriesById: ReadonlyMap<string, Contribution<unknown>>;
|
|
1545
1697
|
/** Platform flag for `mod` shorthand resolution. */
|
|
1546
1698
|
isMac: boolean;
|
|
1547
1699
|
/**
|
|
@@ -1552,6 +1704,14 @@ interface DispatcherContext {
|
|
|
1552
1704
|
* no eligibility filtering.
|
|
1553
1705
|
*/
|
|
1554
1706
|
getRuleCtx?: () => RuleCtx | undefined;
|
|
1707
|
+
/**
|
|
1708
|
+
* The view this input landed in — a view id, or `null` for the surface's
|
|
1709
|
+
* own camera. Bindings whose `opts.views` omit it are not live, those naming
|
|
1710
|
+
* it outrank the rest, and it reaches the invoker as `InvocationCtx.viewId`
|
|
1711
|
+
* and rules as `RuleCtx.viewId`. Absent means the host routes no views, which
|
|
1712
|
+
* reads as the root.
|
|
1713
|
+
*/
|
|
1714
|
+
viewId?: string | null;
|
|
1555
1715
|
}
|
|
1556
1716
|
/**
|
|
1557
1717
|
* Filter `matches` to only those whose backing action has no `eligible`
|
|
@@ -1565,6 +1725,23 @@ declare function filterEligible<M extends {
|
|
|
1565
1725
|
actionId: string;
|
|
1566
1726
|
};
|
|
1567
1727
|
}>(matches: readonly M[], actionLookup: (id: string) => Action | undefined, ruleCtx: RuleCtx): M[];
|
|
1728
|
+
/**
|
|
1729
|
+
* Among matches tied on scope and specificity, put those whose action is
|
|
1730
|
+
* gated by an `eligible` rule that holds now ahead of those with no rule: a
|
|
1731
|
+
* binding that applies only in the current context — Escape while editing a
|
|
1732
|
+
* path — outranks one that applies everywhere. Otherwise stable.
|
|
1733
|
+
*
|
|
1734
|
+
* @internal
|
|
1735
|
+
*/
|
|
1736
|
+
declare function preferContextual<M extends {
|
|
1737
|
+
binding: GestureBinding;
|
|
1738
|
+
scope: string;
|
|
1739
|
+
}>(matches: readonly M[], actionLookup: (id: string) => Action | undefined,
|
|
1740
|
+
/** Whether a gated action's rule holds; skip re-asking where the matches
|
|
1741
|
+
* were already filtered by it. */
|
|
1742
|
+
holds: (action: Action) => boolean,
|
|
1743
|
+
/** The routed view: a binding naming it is never tied with one that does not. */
|
|
1744
|
+
view: string | null): M[];
|
|
1568
1745
|
/**
|
|
1569
1746
|
* Handle returned by `Dispatcher.beginUiOngoing()` for driving an
|
|
1570
1747
|
* ongoing invoker from a UI control (color picker, slider).
|
|
@@ -1590,7 +1767,7 @@ interface ResolveOnlyResult {
|
|
|
1590
1767
|
action: Action;
|
|
1591
1768
|
scope: BindingScope;
|
|
1592
1769
|
/** Tool id owning the winning binding; `null` for ambient action bindings. */
|
|
1593
|
-
|
|
1770
|
+
ownerId: string | null;
|
|
1594
1771
|
}
|
|
1595
1772
|
/**
|
|
1596
1773
|
* One candidate from `Dispatcher.resolveAll` — a binding that matched the
|
|
@@ -1614,7 +1791,7 @@ interface ResolvedCandidate {
|
|
|
1614
1791
|
action: Action;
|
|
1615
1792
|
binding: GestureBinding;
|
|
1616
1793
|
scope: BindingScope;
|
|
1617
|
-
|
|
1794
|
+
ownerId: string | null;
|
|
1618
1795
|
/** The tuple from `specificity(binding.spec)`, surfaced so a reader can see
|
|
1619
1796
|
* why one candidate outranks another rather than inferring it. */
|
|
1620
1797
|
specificity: readonly [number, number, number, number];
|
|
@@ -1702,6 +1879,14 @@ interface Dispatcher {
|
|
|
1702
1879
|
* {@link ResolveAllOptions.evaluateShadowed}.
|
|
1703
1880
|
*/
|
|
1704
1881
|
resolveAll(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): ResolvedCandidate[];
|
|
1882
|
+
/**
|
|
1883
|
+
* The full {@link DispatchRecord} for `event` — what matched, what each
|
|
1884
|
+
* filter dropped, how the rest ranked and why, and the walk — without
|
|
1885
|
+
* invoking anything, so `predicted` is true and the winner is `would-fire`.
|
|
1886
|
+
* It is the same computation `handleInput` and `resolveAll` run. Costs more
|
|
1887
|
+
* than `resolveOnly`: it renders every candidate's routes and rule.
|
|
1888
|
+
*/
|
|
1889
|
+
explain(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): DispatchRecord;
|
|
1705
1890
|
/**
|
|
1706
1891
|
* Synthesize an end-of-gesture for every in-flight ongoing handle.
|
|
1707
1892
|
* Used by tool-switch cancellation (Q2 decision).
|
|
@@ -1822,10 +2007,9 @@ interface ActionsRegistry {
|
|
|
1822
2007
|
/** Drop every registrant of `id`. This is the "this action should not exist"
|
|
1823
2008
|
* door, not a release — for that, call what `register` returned. */
|
|
1824
2009
|
unregister(id: string): void;
|
|
1825
|
-
/** Declare `id` not for this scope: it stays registered
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
1828
|
-
* `<ActionsScope>` drops what it muted when it unmounts. This is the
|
|
2010
|
+
/** Declare `id` not for this scope: it stays registered wherever it was,
|
|
2011
|
+
* but here — and in scopes below — it lists as absent and neither
|
|
2012
|
+
* `trigger` nor `begin` will fire it. Returns a release. This is the
|
|
1829
2013
|
* "not for me" door — `unregister` is the "should not exist" one. */
|
|
1830
2014
|
mute(id: string): () => void;
|
|
1831
2015
|
list(): readonly Action[];
|
|
@@ -1854,18 +2038,16 @@ interface ActionsRegistry {
|
|
|
1854
2038
|
*/
|
|
1855
2039
|
begin(id: string, params?: Record<string, unknown>): UiOngoingControl | null;
|
|
1856
2040
|
/** Wire a dispatcher into the registry so `begin()` can delegate to it.
|
|
1857
|
-
* Returns a release that
|
|
1858
|
-
*
|
|
1859
|
-
* the canvas now on screen. Call with `null` to detach unconditionally. */
|
|
2041
|
+
* Returns a release that takes out only this dispatcher; a `null` wires
|
|
2042
|
+
* nothing. */
|
|
1860
2043
|
setDispatcher(d: Dispatcher | null): () => void;
|
|
1861
|
-
/**
|
|
1862
|
-
*
|
|
1863
|
-
*
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
*
|
|
1867
|
-
|
|
1868
|
-
setDepRegistry(r: DepRegistry | null): () => void;
|
|
2044
|
+
/** Make this registry's scope the one chrome above it reaches: `trigger`,
|
|
2045
|
+
* `begin` and `list` on any ancestor then answer from here. A dispatcher
|
|
2046
|
+
* calls it on the pointerdown and focus that land in its scope. */
|
|
2047
|
+
activate(): void;
|
|
2048
|
+
/** Whether a lookup made from the root of this registry's tree starts here —
|
|
2049
|
+
* the question a dispatcher asks before handling a keystroke. */
|
|
2050
|
+
isActive(): boolean;
|
|
1869
2051
|
}
|
|
1870
2052
|
/** Validate that `id` is usable as an action id in the route grammar.
|
|
1871
2053
|
* Rejects ids that start with a reserved sigil (would shadow future
|
|
@@ -1888,8 +2070,8 @@ interface ToolOffhandBindingSpec {
|
|
|
1888
2070
|
toolId: string;
|
|
1889
2071
|
key: string | string[];
|
|
1890
2072
|
}
|
|
1891
|
-
/** `KeyboardEvent.key` for a declared `Eligibility.offhand` trigger.
|
|
1892
|
-
*
|
|
2073
|
+
/** `KeyboardEvent.key` for a declared `Eligibility.offhand` trigger. The
|
|
2074
|
+
* named keys become their DOM names; any other trigger already is one. */
|
|
1893
2075
|
declare function offhandKeyFor(trigger: HotkeyTrigger): string;
|
|
1894
2076
|
/** Build the per-tool `BoundGesture` list for the consolidated action's
|
|
1895
2077
|
* `defaultBinding`. Each entry pairs a `key-held` spec with
|
|
@@ -2017,17 +2199,177 @@ declare function liveScope(id: string, eligibility: Eligibility, state: Eligibil
|
|
|
2017
2199
|
* breaks ties by declaration order, so the caller's entry order decides which
|
|
2018
2200
|
* of two same-specificity bindings in one tier wins.
|
|
2019
2201
|
*/
|
|
2020
|
-
declare function scopeBindings(entries: Iterable<Contribution
|
|
2202
|
+
declare function scopeBindings(entries: Iterable<Contribution<unknown>>, state: EligibilityState): ScopedBinding[];
|
|
2021
2203
|
|
|
2022
2204
|
/**
|
|
2023
2205
|
* Concatenate contribution bundles into one registry, preserving order —
|
|
2024
2206
|
* entry order decides which of two same-specificity bindings in one scope
|
|
2025
2207
|
* tier wins, so it is part of the result, not an accident of it.
|
|
2026
2208
|
*
|
|
2027
|
-
* Throws on a duplicate id rather than dropping one: a feature
|
|
2028
|
-
* losing its bindings
|
|
2209
|
+
* Throws on a duplicate id or dep name rather than dropping one: a feature
|
|
2210
|
+
* silently losing its bindings, or reading another feature's dep, is the
|
|
2211
|
+
* failure this registry exists to make loud.
|
|
2212
|
+
*/
|
|
2213
|
+
declare function mergeContributions<T extends Contribution<unknown>>(...bundles: readonly (readonly T[])[]): T[];
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* What each mode shortcut does. A role left out binds nothing, so its key
|
|
2217
|
+
* falls through to whatever else the dispatcher has on it.
|
|
2218
|
+
*/
|
|
2219
|
+
interface ModeShortcutHandlers {
|
|
2220
|
+
/** Enter mode `id` — the `entry` shortcut. */
|
|
2221
|
+
enter?: (id: string) => void;
|
|
2222
|
+
/** Whether mode `id` can be entered now. A mode that needs a target it
|
|
2223
|
+
* doesn't have answers no, and its entry key falls through. Default yes. */
|
|
2224
|
+
canEnter?: (id: string) => boolean;
|
|
2225
|
+
/** Leave the active soft mode, keeping its session to resume. */
|
|
2226
|
+
exit?: () => void;
|
|
2227
|
+
/** Leave the active soft mode and throw its session away. */
|
|
2228
|
+
discard?: () => void;
|
|
2229
|
+
/** Commit the active strict mode's transaction. */
|
|
2230
|
+
commit?: () => void;
|
|
2231
|
+
/** Abandon the active strict mode's transaction. */
|
|
2232
|
+
cancel?: () => void;
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* A `ModeDefinition` shortcut chord — `'Escape'`, `'Meta+T'` — as a key spec.
|
|
2236
|
+
* Modifier names are case-insensitive; the last segment is the key.
|
|
2237
|
+
*/
|
|
2238
|
+
declare function modeShortcutSpec(shortcut: string): KeySpec;
|
|
2239
|
+
/**
|
|
2240
|
+
* The registry's declared mode shortcuts as an always-on contribution: one
|
|
2241
|
+
* action per shortcut, bound to its chord and gated on the mode it acts on —
|
|
2242
|
+
* `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`
|
|
2243
|
+
* while it is not. Install it with `<SceneCanvas ambient>`.
|
|
2244
|
+
*
|
|
2245
|
+
* The bindings ride the hotkey tier, so leaving a mode outranks what the same
|
|
2246
|
+
* key does otherwise (Escape clearing the selection), and they yield while a
|
|
2247
|
+
* gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
|
|
2248
|
+
* typed into an editable element never reach them.
|
|
2029
2249
|
*/
|
|
2030
|
-
declare function
|
|
2250
|
+
declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Configurable activation-key descriptor for tools that expose their
|
|
2254
|
+
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
2255
|
+
* only the fields meaningful to a caller-supplied tool-select key —
|
|
2256
|
+
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
2257
|
+
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
2258
|
+
* not part of the configurable surface.
|
|
2259
|
+
*/
|
|
2260
|
+
interface ToolKeybinding {
|
|
2261
|
+
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
2262
|
+
key: string | readonly string[];
|
|
2263
|
+
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
2264
|
+
mod?: boolean;
|
|
2265
|
+
/** Require Alt. Default `false`. */
|
|
2266
|
+
alt?: boolean;
|
|
2267
|
+
/**
|
|
2268
|
+
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
2269
|
+
* shift, `'optional'` allows either.
|
|
2270
|
+
*/
|
|
2271
|
+
shift?: boolean | 'optional';
|
|
2272
|
+
}
|
|
2273
|
+
/** Modifier-key snapshot at event dispatch time. */
|
|
2274
|
+
type ToolModifiers = ModifierState;
|
|
2275
|
+
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
2276
|
+
* via the tool's `TScratch` parameter; it survives across a single
|
|
2277
|
+
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
2278
|
+
* gesture start by `initScratch()`. */
|
|
2279
|
+
interface ToolCtx<TScratch = unknown> {
|
|
2280
|
+
worldX: number;
|
|
2281
|
+
worldY: number;
|
|
2282
|
+
modifiers: ToolModifiers;
|
|
2283
|
+
selection: SelectionApi;
|
|
2284
|
+
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
2285
|
+
* cast to a known shape. This layer doesn't constrain it. */
|
|
2286
|
+
adapter: unknown;
|
|
2287
|
+
applyOps: (ops: Op[], label: string) => void;
|
|
2288
|
+
/** Current viewport. Reflects camera-position semantics — see
|
|
2289
|
+
* `View` JSDoc. */
|
|
2290
|
+
view: View;
|
|
2291
|
+
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
2292
|
+
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
2293
|
+
* state. View changes are not undoable. */
|
|
2294
|
+
setView: (next: View) => void;
|
|
2295
|
+
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
2296
|
+
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
2297
|
+
* anchors. */
|
|
2298
|
+
canvasRect: DOMRect;
|
|
2299
|
+
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
2300
|
+
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
2301
|
+
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
2302
|
+
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
2303
|
+
debug?: DebugSink;
|
|
2304
|
+
scratch: TScratch;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* What `onActivate` / `onDeactivate` receive: the tool's scratch, from
|
|
2308
|
+
* `initScratch()`. A lifecycle moment has no pointer, hit or modifier state to
|
|
2309
|
+
* report, so nothing else is populated; a callback needing the selection or
|
|
2310
|
+
* the scene closes over it, as the tool's actions do.
|
|
2311
|
+
*/
|
|
2312
|
+
interface ToolLifecycleCtx<TScratch = unknown> {
|
|
2313
|
+
scratch: TScratch;
|
|
2314
|
+
}
|
|
2315
|
+
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
2316
|
+
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
2317
|
+
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
2318
|
+
type ToolBounds = Bounds;
|
|
2319
|
+
/**
|
|
2320
|
+
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
2321
|
+
* into, plus the hooks that only make sense for one (`initScratch`,
|
|
2322
|
+
* activate/deactivate, live preview, `cursor`). Everything else — bindings,
|
|
2323
|
+
* actions, overlay, presentation — is inherited.
|
|
2324
|
+
*/
|
|
2325
|
+
interface Tool<TScratch = unknown, TOverlay = KernelOverlay> extends Contribution<TOverlay> {
|
|
2326
|
+
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
2327
|
+
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
2328
|
+
* is for tools that want their activation key to be configurable by the
|
|
2329
|
+
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
2330
|
+
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
2331
|
+
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
2332
|
+
* the invoker knows which tool to switch to). */
|
|
2333
|
+
keybinding?: ToolKeybinding;
|
|
2334
|
+
initScratch?: () => TScratch;
|
|
2335
|
+
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
2336
|
+
/** The tool became live: it took the active slot, or its `hotkey` went
|
|
2337
|
+
* down, while it held neither. */
|
|
2338
|
+
onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2339
|
+
/** The tool stopped being live in both slots, or its canvas unmounted. */
|
|
2340
|
+
onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2341
|
+
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
2342
|
+
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
2343
|
+
* reflect live gesture state without reaching into hook internals. The
|
|
2344
|
+
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
2345
|
+
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
2346
|
+
* the use site. */
|
|
2347
|
+
previewPose?: (id: string) => unknown;
|
|
2348
|
+
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
2349
|
+
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
2350
|
+
* can compute bounds without round-tripping through a geometry adapter. */
|
|
2351
|
+
previewBounds?: (id: string) => ToolBounds | null;
|
|
2352
|
+
/** Returns ids whose committed scene-render should be suppressed while this
|
|
2353
|
+
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
2354
|
+
* preview ghosts replace the committed pose). The standard scene slot
|
|
2355
|
+
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
2356
|
+
* `null` when no gesture is in flight. */
|
|
2357
|
+
previewIds?: () => Iterable<string> | null;
|
|
2358
|
+
}
|
|
2359
|
+
/** Whether `entry` is a tool: a contribution that can hold focus, chosen from
|
|
2360
|
+
* a palette or held on a key. Only a tool's focus hooks (`initScratch`,
|
|
2361
|
+
* `cursor`, `onActivate`, the previews) ever run. */
|
|
2362
|
+
declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<unknown, TOverlay>;
|
|
2363
|
+
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
2364
|
+
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
2365
|
+
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
2366
|
+
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
2367
|
+
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
2368
|
+
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
2369
|
+
type AnyTool = Tool<any, any>;
|
|
2370
|
+
/** "A tool of any scratch type that draws `TOverlay`" — the container form for
|
|
2371
|
+
* registries that hold heterogeneous scratch shapes but one overlay type. */
|
|
2372
|
+
type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
2031
2373
|
|
|
2032
2374
|
/**
|
|
2033
2375
|
* What a tool declares.
|
|
@@ -2038,7 +2380,7 @@ declare function mergeContributions(...bundles: readonly Contribution[][]): Cont
|
|
|
2038
2380
|
* definition is mostly `bindings`, plus presentation and any actions the tool
|
|
2039
2381
|
* itself introduces.
|
|
2040
2382
|
*/
|
|
2041
|
-
interface ToolDef<TScratch = void, TOverlay =
|
|
2383
|
+
interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
2042
2384
|
id: string;
|
|
2043
2385
|
/** Capability tags for modality eligibility. `defineTool` forwards these
|
|
2044
2386
|
* onto `Tool.eligibility.capabilities`, which is where every gate reads
|
|
@@ -2050,14 +2392,9 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2050
2392
|
* reference by id.
|
|
2051
2393
|
*
|
|
2052
2394
|
* Declared here rather than registered by the hook with `useAction`,
|
|
2053
|
-
* because tool hooks run wherever the consumer calls them
|
|
2054
|
-
*
|
|
2055
|
-
*
|
|
2056
|
-
* result was a binding pointing at an action id nothing had registered, so
|
|
2057
|
-
* the gesture fell through to whatever matched next (polygon's
|
|
2058
|
-
* wheel/arrow-key side adjustment did nothing and `nudge.*` moved the
|
|
2059
|
-
* selection instead). `<ToolActionsMounter>` registers these from inside
|
|
2060
|
-
* the provider.
|
|
2395
|
+
* because tool hooks run wherever the consumer calls them, which may be
|
|
2396
|
+
* outside any registry — and there `useAction` silently no-ops, leaving a
|
|
2397
|
+
* binding that points at an action id nothing registered.
|
|
2061
2398
|
*/
|
|
2062
2399
|
actions?: Action[];
|
|
2063
2400
|
/** Hook name as exported from the kit barrel (e.g. `'useHandTool'`).
|
|
@@ -2076,12 +2413,17 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2076
2413
|
* set so the invoker knows which tool to switch to). */
|
|
2077
2414
|
keybinding?: ToolKeybinding;
|
|
2078
2415
|
/** Held-key trigger: this tool engages while the key is down and
|
|
2079
|
-
* disengages on release
|
|
2416
|
+
* disengages on release — a modifier, `'space'`, or any other key
|
|
2417
|
+
* (`'o'`), never while typing in a text field. Carried onto
|
|
2418
|
+
* `Tool.eligibility.offhand`, which
|
|
2080
2419
|
* assembly reads to register the consolidated `tool.offhand` action — the
|
|
2081
2420
|
* declaration is the wiring, with nothing for the host to do. */
|
|
2082
2421
|
hotkey?: HotkeyTrigger;
|
|
2083
|
-
|
|
2084
|
-
|
|
2422
|
+
/** Fires as the tool becomes live in the active or hotkey slot. See
|
|
2423
|
+
* `Tool.onActivate`. */
|
|
2424
|
+
onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2425
|
+
/** Fires as the tool stops being live in either slot. */
|
|
2426
|
+
onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2085
2427
|
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
2086
2428
|
/** Override the default scratch initializer. Default is `() => null`
|
|
2087
2429
|
* cast to `TScratch`, which works for tools whose scratch is fresh
|
|
@@ -2111,10 +2453,6 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2111
2453
|
* `'top'`, above everything. */
|
|
2112
2454
|
overlayPosition?: OverlayPosition;
|
|
2113
2455
|
}
|
|
2114
|
-
/** Viewport-tool spec. Once phase tables went away this stopped differing
|
|
2115
|
-
* from `ToolDef` in any structural way; `defineViewportTool` survives as the
|
|
2116
|
-
* authoring signal that a tool pans/zooms the view rather than the scene. */
|
|
2117
|
-
type ViewportToolDef<TScratch = void, TOverlay = unknown> = ToolDef<TScratch, TOverlay>;
|
|
2118
2456
|
|
|
2119
2457
|
/**
|
|
2120
2458
|
* Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.
|
|
@@ -2130,22 +2468,7 @@ type ViewportToolDef<TScratch = void, TOverlay = unknown> = ToolDef<TScratch, TO
|
|
|
2130
2468
|
* and the `initScratch` / `cursor` defaults are worth applying uniformly, and
|
|
2131
2469
|
* because `Tool.def` gives reflection a handle on the authored form.
|
|
2132
2470
|
*/
|
|
2133
|
-
declare function defineTool<TScratch = void, TOverlay =
|
|
2134
|
-
|
|
2135
|
-
/**
|
|
2136
|
-
* Define a tool that acts on the viewport rather than the scene.
|
|
2137
|
-
*
|
|
2138
|
-
* This used to do real work: `ViewportPhaseDef` was a narrowed `PhaseDef`
|
|
2139
|
-
* (no click routes, drag restricted to the function form), and the factory
|
|
2140
|
-
* lifted it back to the permissive shape before handing it to `defineTool`.
|
|
2141
|
-
* With phase tables gone there is no shape left to narrow — a viewport tool
|
|
2142
|
-
* declares `bindings` like any other, pointing at `viewport.*` actions.
|
|
2143
|
-
*
|
|
2144
|
-
* It survives as an authoring signal. `defineViewportTool` at the top of a
|
|
2145
|
-
* hook says "this tool moves the camera, not the drawing", which is worth
|
|
2146
|
-
* more than the type gymnastics it replaced.
|
|
2147
|
-
*/
|
|
2148
|
-
declare function defineViewportTool<TScratch = void, TOverlay = unknown>(def: ViewportToolDef<TScratch, TOverlay>): Tool<TScratch, TOverlay>;
|
|
2471
|
+
declare function defineTool<TScratch = void, TOverlay = KernelOverlay>(def: ToolDef<TScratch, TOverlay>): Tool<TScratch, TOverlay>;
|
|
2149
2472
|
|
|
2150
2473
|
/**
|
|
2151
2474
|
* One row in the route registry — a single `GestureBinding` on one tool,
|
|
@@ -2156,7 +2479,8 @@ declare function defineViewportTool<TScratch = void, TOverlay = unknown>(def: Vi
|
|
|
2156
2479
|
* declare them; consumers walk the list and group client-side.
|
|
2157
2480
|
*/
|
|
2158
2481
|
interface RegistryEntry {
|
|
2159
|
-
|
|
2482
|
+
/** The id of the entry that declared the binding: a tool, or a contribution. */
|
|
2483
|
+
ownerId: string;
|
|
2160
2484
|
/** Which phase the binding's `phase` spec restricts it to. `'any'` when it
|
|
2161
2485
|
* declares none, declares `'*'`, or declares an atom list whose atoms
|
|
2162
2486
|
* don't agree on one phase — such a binding fires in either phase, and
|
|
@@ -2189,6 +2513,9 @@ interface RegistryEntry {
|
|
|
2189
2513
|
* specificity tuple, a `kindOf` predicate identity — read it here rather
|
|
2190
2514
|
* than re-walking `Tool.bindings`. */
|
|
2191
2515
|
spec: GestureSpec;
|
|
2516
|
+
/** The binding's `opts.views`: the views it is live in. Absent when it is
|
|
2517
|
+
* live in every view. */
|
|
2518
|
+
views?: readonly (string | null)[];
|
|
2192
2519
|
}
|
|
2193
2520
|
/**
|
|
2194
2521
|
* Stand-in target for a `{ kindOf }` predicate spec.
|
|
@@ -2211,10 +2538,7 @@ declare const PREDICATE_TARGET = "predicate";
|
|
|
2211
2538
|
* Registry, and reading `buildActionRegistry` next to `ActionsRegistry`
|
|
2212
2539
|
* suggested otherwise.
|
|
2213
2540
|
*/
|
|
2214
|
-
declare function buildRouteRegistry(
|
|
2215
|
-
/** The route-grammar gesture a `GestureSpec.kind` routes as, or `undefined`
|
|
2216
|
-
* for kinds the grammar has no name for. */
|
|
2217
|
-
declare function routeGestureForSpecKind(kind: GestureSpec['kind']): GestureName | undefined;
|
|
2541
|
+
declare function buildRouteRegistry(entries: readonly Contribution<unknown>[]): RegistryEntry[];
|
|
2218
2542
|
/**
|
|
2219
2543
|
* Every route string one `GestureSpec` declares, in route-grammar notation.
|
|
2220
2544
|
*
|
|
@@ -2238,9 +2562,9 @@ interface Conflict {
|
|
|
2238
2562
|
arg: string | undefined;
|
|
2239
2563
|
target: string | undefined;
|
|
2240
2564
|
modifiers: ParsedModifiers;
|
|
2241
|
-
/**
|
|
2242
|
-
* construction
|
|
2243
|
-
|
|
2565
|
+
/** The ids of every entry that declared the same tuple. At least 2 by
|
|
2566
|
+
* construction, in input order. */
|
|
2567
|
+
ownerIds: string[];
|
|
2244
2568
|
}
|
|
2245
2569
|
/** Detect exact-tuple overlaps across a tool registration set.
|
|
2246
2570
|
*
|
|
@@ -2264,6 +2588,12 @@ interface Conflict {
|
|
|
2264
2588
|
* right way to be wrong here: this check has to be silent when nothing is
|
|
2265
2589
|
* wrong or nobody will keep it on.
|
|
2266
2590
|
*
|
|
2591
|
+
* - Two actions whose `eligible` rules can never hold together — each
|
|
2592
|
+
* mode's own Escape exit, say. See {@link rulesExclusive}.
|
|
2593
|
+
*
|
|
2594
|
+
* - Two bindings whose `opts.views` keep them from being live and equally
|
|
2595
|
+
* ranked in any one view. See {@link viewsTie}.
|
|
2596
|
+
*
|
|
2267
2597
|
* Note that two bindings sharing a tuple on the SAME tool are now possible
|
|
2268
2598
|
* (bindings are an array, where phase tables were objects with unique
|
|
2269
2599
|
* keys) — so a conflict may name one tool twice.
|
|
@@ -2272,19 +2602,23 @@ interface Conflict {
|
|
|
2272
2602
|
* over-reports, because registry tools take turns in the active slot and
|
|
2273
2603
|
* can't collide with each other — see {@link findScopedConflicts}.
|
|
2274
2604
|
*/
|
|
2275
|
-
declare function findConflicts(
|
|
2605
|
+
declare function findConflicts(entries: readonly Contribution<unknown>[]): Conflict[];
|
|
2276
2606
|
/**
|
|
2277
2607
|
* The tool scopes as the dispatcher sees them — which is what decides whether
|
|
2278
2608
|
* two same-tuple bindings can actually collide.
|
|
2279
2609
|
*/
|
|
2280
2610
|
interface ToolScopes {
|
|
2281
2611
|
/** Tools eligible for the active slot, keyed by id or as a flat list. */
|
|
2282
|
-
registry: readonly
|
|
2283
|
-
/** Always-on
|
|
2284
|
-
ambient?: readonly
|
|
2612
|
+
registry: readonly Contribution<unknown>[] | Readonly<Record<string, Contribution<unknown>>>;
|
|
2613
|
+
/** Always-on entries. Every one of these is live at once. */
|
|
2614
|
+
ambient?: readonly Contribution<unknown>[];
|
|
2285
2615
|
/** Registered actions. Their `defaultBinding`s assemble at ambient scope
|
|
2286
2616
|
* (hotkey scope when `Action.scope` says so), alongside the tools above. */
|
|
2287
2617
|
actions?: readonly Action[];
|
|
2618
|
+
/** Every mode the canvas can be in (`activeModeOf` builds one from a
|
|
2619
|
+
* definition). Two gated actions conflict only when some mode lets both
|
|
2620
|
+
* rules hold. Default: the kit's `DEFAULT_MODES`. */
|
|
2621
|
+
modes?: readonly ActiveMode[];
|
|
2288
2622
|
}
|
|
2289
2623
|
/**
|
|
2290
2624
|
* Detect reachable route conflicts in an assembled tool set and report each one.
|
|
@@ -2501,5 +2835,25 @@ interface DepSchema {
|
|
|
2501
2835
|
}
|
|
2502
2836
|
/** Every dep name declared by anything that has merged into {@link DepSchema}. */
|
|
2503
2837
|
type DepName = keyof DepSchema;
|
|
2838
|
+
/**
|
|
2839
|
+
* What the kernel drawing these tools takes as an overlay. Routing never reads
|
|
2840
|
+
* an overlay, so it has no type of its own for one; the kernel merges its type
|
|
2841
|
+
* in, the same way and for the same reason as {@link DepSchema}:
|
|
2842
|
+
*
|
|
2843
|
+
* ```ts
|
|
2844
|
+
* declare module '@weasel-js/routing' {
|
|
2845
|
+
* interface OverlaySchema { overlay: RenderLayer<unknown> }
|
|
2846
|
+
* }
|
|
2847
|
+
* ```
|
|
2848
|
+
*
|
|
2849
|
+
* `defineTool` defaults its overlay type to it, so a
|
|
2850
|
+
* tool it builds fits the kernel's `tools` without naming the parameter.
|
|
2851
|
+
*/
|
|
2852
|
+
interface OverlaySchema {
|
|
2853
|
+
}
|
|
2854
|
+
/** The overlay type merged into {@link OverlaySchema}; `unknown` when none is. */
|
|
2855
|
+
type KernelOverlay = OverlaySchema extends {
|
|
2856
|
+
overlay: infer O;
|
|
2857
|
+
} ? O : unknown;
|
|
2504
2858
|
|
|
2505
|
-
export { type
|
|
2859
|
+
export { type HandleKind as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type Dispatcher 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, DEFAULT_ALLOWED_CAPABILITIES as L, DEFAULT_DEVICE_PROFILE as M, type DebugSink as N, type OverlayPosition as O, type DetectedDeviceFacts as P, type DeviceProfile as Q, type RuleCtx as R, ScopeNode as S, type DispatchRecord as T, type DispatchRecordInput as U, type DispatcherContext as V, type DragSample as W, type DroppedCandidate as X, type Eligibility as Y, type EligibilityState as Z, type GestureBinding as _, type AffordanceHit as a, clientToCanvasRect as a$, type HitShape as a0, type HotkeyTrigger as a1, INGEST_STRING_MIMES as a2, type ImmediateInvoker as a3, type InvocationCtx as a4, type Invoker as a5, type KeyBinding as a6, type MatchOutcome as a7, type MatchResult as a8, type ModeShortcutHandlers as a9, type SelectionApi as aA, type SelectionExtendKey as aB, type SelectionMode as aC, type Selector as aD, type SpecificityPart as aE, TOOL_OFFHAND_ID as aF, type Tool as aG, type ToolBounds as aH, type ToolCtx as aI, type ToolDef as aJ, type ToolKeybinding as aK, type ToolLifecycleCtx as aL, type ToolModifiers as aM, type ToolOffhandBindingSpec as aN, type ToolPresentation as aO, type ToolSlot as aP, type TraceLogEntry as aQ, type UiOngoingControl as aR, type View as aS, type WalkStep as aT, actionBindings as aU, actionItems as aV, buildDepsFromRequires as aW, buildRouteRegistry as aX, buildRuleCtx as aY, buildToolOffhandBindings as aZ, clientToCanvas as a_, type ModeSwitchLogEntry as aa, type ModifierState as ab, NEVER as ac, type NodeId as ad, type OngoingHandle as ae, type OngoingInvoker as af, type OngoingOverlay as ag, type OverlayRole as ah, type OverlaySchema as ai, PREDICATE_TARGET as aj, type PlacedBy as ak, type Point2 as al, type PointerSession as am, type PointerSessionCallbacks as an, type PointerSessionCancelReason as ao, type PointerSessionOptions as ap, type RankedCandidate as aq, type RecordCandidate as ar, type RegistryEntry as as, type ResizeAnchor as at, type ResolveAllOptions as au, type ResolveOnlyResult as av, type ResolvedCandidate as aw, type Rule as ax, type ScratchKey as ay, type ScratchStore as az, type DepSchema as b, createDepRegistry as b0, createDispatcher as b1, defineTool as b2, deleteScratch as b3, describeRule as b4, dlog as b5, evaluate as b6, evaluateEnabled as b7, filterEligible as b8, findConflicts as b9, pointerGestureId as bA, preferContextual as bB, publishLiveDispatch as bC, recordModeSwitch as bD, reportRouteConflicts as bE, resolveDeviceProfile as bF, resolveParams as bG, routesForSpec as bH, ruleCanHoldIn as bI, rulesExclusive as bJ, scopeBindings as bK, scratchKey as bL, setScratch as bM, targetConsultsAffordance as bN, validateActionDefaultBinding as bO, validateActionId as bP, getScratch as ba, isAnchor as bb, isAnchorOrControl as bc, isBody as bd, isControlHandle as be, isEditableTarget as bf, isEmpty as bg, isResizeHandle as bh, isRotateHandle as bi, isSelectedBody as bj, isTool as bk, isUnselectedBody as bl, itemsFromClipboardData as bm, itemsFromDataTransfer as bn, itemsFromFiles as bo, liveScope as bp, makeToolOffhandAction as bq, matchBest as br, matchSorted as bs, matchSortedWithBarred as bt, matchesKeyBinding as bu, mergeContributions as bv, modeShortcutSpec as bw, modeShortcuts as bx, offhandKeyFor as by, openPointerSession as bz, type DepRegistry as c, type DepName as d, type Action as e, DepNode as f, type AnyToolOf as g, type ScopedBinding 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 };
|