@weasel-js/routing 1.6.1 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/{chunk-VEC3ACV5.js → chunk-ZGKJWZ4S.js} +774 -380
- package/dist/chunk-ZGKJWZ4S.js.map +1 -0
- package/dist/{index-CKmbNpwl.d.ts → index-BV53gfyB.d.ts} +649 -257
- package/dist/index.d.ts +2 -2
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/dist/internal--mYnGecm.d.ts +275 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.d.ts +213 -344
- package/dist/react.js +456 -406
- package/dist/react.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-VEC3ACV5.js.map +0 -1
|
@@ -1,8 +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';
|
|
5
|
+
import { ClaimableGesture, GestureSpec, InputEvent, TargetSpec, KeySpec, ParsedModifiers, GestureName, IngestItem } from '@weasel-js/gestures';
|
|
6
6
|
import { Op } from '@weasel-js/history';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -126,10 +126,21 @@ type HitShape = {
|
|
|
126
126
|
cx: number;
|
|
127
127
|
cy: number;
|
|
128
128
|
r: number;
|
|
129
|
+
}
|
|
130
|
+
/** A world-space polygon — for a region that is a turned square on screen,
|
|
131
|
+
* which under non-uniform zoom is no rotated rect in world. */
|
|
132
|
+
| {
|
|
133
|
+
kind: 'polygon';
|
|
134
|
+
points: readonly {
|
|
135
|
+
x: number;
|
|
136
|
+
y: number;
|
|
137
|
+
}[];
|
|
129
138
|
} | {
|
|
130
139
|
kind: 'path';
|
|
131
140
|
d: Path2D;
|
|
132
141
|
};
|
|
142
|
+
/** What a recorded viewport change was: a translation or a scale change. */
|
|
143
|
+
type ViewportGestureKind = 'pan' | 'zoom';
|
|
133
144
|
/**
|
|
134
145
|
* Where a tool reports its internal hit math so the debug overlay can draw it.
|
|
135
146
|
* Routing threads a sink through `ToolCtx` and never calls one.
|
|
@@ -155,6 +166,16 @@ interface DebugSink {
|
|
|
155
166
|
y: number;
|
|
156
167
|
}, accepted: boolean): void;
|
|
157
168
|
recordLayer(id: string, label: string, space: 'world' | 'screen', index: number): void;
|
|
169
|
+
/**
|
|
170
|
+
* A viewport change a gesture made: the view it started from, the view it
|
|
171
|
+
* produced, and the world point it held fixed — a pan's grab point, a zoom's
|
|
172
|
+
* focal point — when it had one. Survives `beginFrame`; the next record
|
|
173
|
+
* replaces it.
|
|
174
|
+
*/
|
|
175
|
+
recordViewport(kind: ViewportGestureKind, from: View, to: View, anchor?: {
|
|
176
|
+
x: number;
|
|
177
|
+
y: number;
|
|
178
|
+
}): void;
|
|
158
179
|
/** Clears every non-snap array. Called at the start of each Canvas render. */
|
|
159
180
|
beginFrame(): void;
|
|
160
181
|
/** Clears the snap array. Called at gesture end. */
|
|
@@ -382,6 +403,24 @@ interface Condition {
|
|
|
382
403
|
orNot(other: Condition | Rule): Condition;
|
|
383
404
|
}
|
|
384
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
408
|
+
*
|
|
409
|
+
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
410
|
+
* about the selector values themselves (two different `mode`s, an empty
|
|
411
|
+
* selection against a non-empty one, `focused: true` against `false`) and
|
|
412
|
+
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
413
|
+
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
414
|
+
* that share a tuple but are never eligible together.
|
|
415
|
+
*/
|
|
416
|
+
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
419
|
+
* `capability` tests are settled by the mode; every other test could go either
|
|
420
|
+
* way, so `false` is a proof and `true` means "may hold".
|
|
421
|
+
*/
|
|
422
|
+
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
423
|
+
|
|
385
424
|
/** The detected half of a profile — everything except the derived scale. */
|
|
386
425
|
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
387
426
|
/**
|
|
@@ -1079,7 +1118,7 @@ interface ActionDispatch extends BindingSource {
|
|
|
1079
1118
|
* gesture.
|
|
1080
1119
|
*
|
|
1081
1120
|
* This is where mid-gesture cursors live now. They used to come from the
|
|
1082
|
-
* tool side — `
|
|
1121
|
+
* tool side — an `engaged` phase entry's cursor,
|
|
1083
1122
|
* or a function-form `Tool.cursor` reading the gesture scratch out of the
|
|
1084
1123
|
* tool-routing dispatcher. Both belonged to a pipeline whose whole job was
|
|
1085
1124
|
* being taken over by bindings, and neither could describe a cursor for an
|
|
@@ -1150,6 +1189,65 @@ interface ActionEnabledResult {
|
|
|
1150
1189
|
*/
|
|
1151
1190
|
declare function evaluateEnabled(action: Action, deps?: ActionDeps): ActionEnabledResult;
|
|
1152
1191
|
|
|
1192
|
+
/**
|
|
1193
|
+
* The tree both input registries hang on. A provider is a root; every canvas
|
|
1194
|
+
* mounts a scope under the registry in scope; a yoke sits between the two. A
|
|
1195
|
+
* registration lives in the node it was made in, and a lookup starts at a leaf
|
|
1196
|
+
* and walks up, so one canvas never answers for another.
|
|
1197
|
+
*
|
|
1198
|
+
* Design: docs/proposals/2026-09-27-input-scopes.md.
|
|
1199
|
+
*/
|
|
1200
|
+
/** What every node of one tree shares: a version and the listeners to it. */
|
|
1201
|
+
interface ScopeTree {
|
|
1202
|
+
version: number;
|
|
1203
|
+
readonly listeners: Set<() => void>;
|
|
1204
|
+
}
|
|
1205
|
+
declare class ScopeNode<N extends ScopeNode<N>> {
|
|
1206
|
+
readonly parent: N | null;
|
|
1207
|
+
readonly descends: boolean;
|
|
1208
|
+
readonly tree: ScopeTree;
|
|
1209
|
+
private readonly children;
|
|
1210
|
+
private activeChild;
|
|
1211
|
+
/** `descends`: whether reads made here answer from the active child down —
|
|
1212
|
+
* true for a root or a yoke, which chrome reads through; false for a scope,
|
|
1213
|
+
* which answers for itself even with a scope nested inside it. */
|
|
1214
|
+
constructor(parent: N | null, descends: boolean);
|
|
1215
|
+
/** Bump the tree's version and tell every listener in it. */
|
|
1216
|
+
changed(): void;
|
|
1217
|
+
/** Join the parent's children, newest last; returns the leave. */
|
|
1218
|
+
mount(): () => void;
|
|
1219
|
+
/** Make this node the one its ancestors resolve through. */
|
|
1220
|
+
activate(): void;
|
|
1221
|
+
/** The node a lookup made here starts from: down the active children, or the
|
|
1222
|
+
* newest where nothing has been activated yet. A yoke with no members yet
|
|
1223
|
+
* is passed over — there is no canvas behind it to answer. */
|
|
1224
|
+
leaf(): N;
|
|
1225
|
+
private newestMember;
|
|
1226
|
+
/** Where a read made through this node starts. */
|
|
1227
|
+
start(): N;
|
|
1228
|
+
/** Whether this node is on the path a read from the root takes. */
|
|
1229
|
+
isActive(): boolean;
|
|
1230
|
+
/** This node, then each ancestor up to the root. */
|
|
1231
|
+
chain(): Generator<N>;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* Dep registry for registry unification.
|
|
1236
|
+
*
|
|
1237
|
+
* Holds named "live source" thunks. Actions declare `requires: ['selection']`;
|
|
1238
|
+
* the dispatcher calls `registry.get('selection')` at invocation time to
|
|
1239
|
+
* build a typed Deps bag.
|
|
1240
|
+
*
|
|
1241
|
+
* `DepSchema` — the map of dep name → value type — is the single source of
|
|
1242
|
+
* truth for what `requires`/`register`/`get` accept. Its concrete fields live
|
|
1243
|
+
* in `./depSchema` (co-located with the dep value types). It is declared there
|
|
1244
|
+
* as a plain `export interface` rather than via a cross-module
|
|
1245
|
+
* `declare module './depRegistry'` augmentation: that augmentation does NOT
|
|
1246
|
+
* merge once rollup-plugin-dts flattens both files into one `.d.ts` chunk,
|
|
1247
|
+
* which silently emptied `DepSchema` for consumers. Consumers still extend it
|
|
1248
|
+
* via `declare module '@weasel-js/core'` augmentation.
|
|
1249
|
+
*/
|
|
1250
|
+
|
|
1153
1251
|
/** Holds the live sources an action's declared dependencies resolve to.
|
|
1154
1252
|
* Sources are thunks, read at invocation time, so an action never captures
|
|
1155
1253
|
* stale state. */
|
|
@@ -1157,23 +1255,20 @@ interface DepRegistry {
|
|
|
1157
1255
|
register<K extends DepName>(name: K, source: () => DepSchema[K]): () => void;
|
|
1158
1256
|
get<K extends DepName>(name: K): DepSchema[K] | undefined;
|
|
1159
1257
|
}
|
|
1160
|
-
/**
|
|
1161
|
-
*
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
* component. The `source` thunk is called at dispatch time and should
|
|
1175
|
-
* return the latest value. */
|
|
1176
|
-
declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
|
|
1258
|
+
/** One node of the dep tree: the sources registered at it, over its parent's.
|
|
1259
|
+
* Each name holds a stack of sources, newest live, so a displaced source is
|
|
1260
|
+
* restored when the one above it leaves. */
|
|
1261
|
+
declare class DepNode extends ScopeNode<DepNode> {
|
|
1262
|
+
private readonly sources;
|
|
1263
|
+
readonly registry: DepRegistry;
|
|
1264
|
+
constructor(parent: DepNode | null, descends: boolean);
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* A stock dep registry with no parent: what `<DepRegistryProvider>` mounts,
|
|
1268
|
+
* for a dispatcher driven without React. Sources registered under one name
|
|
1269
|
+
* stack, newest live, and releasing one restores the one beneath it.
|
|
1270
|
+
*/
|
|
1271
|
+
declare function createDepRegistry(): DepRegistry;
|
|
1177
1272
|
|
|
1178
1273
|
/**
|
|
1179
1274
|
* Shared dep-bag builder — the single implementation behind both the gesture
|
|
@@ -1187,9 +1282,13 @@ declare function useDepSource<K extends DepName>(name: K, source: () => DepSchem
|
|
|
1187
1282
|
declare function buildDepsFromRequires(action: Action, depRegistry: DepRegistry): ActionDeps;
|
|
1188
1283
|
|
|
1189
1284
|
/** Hotkey-slot trigger key. The slot is engaged while this key is held —
|
|
1190
|
-
* hence "hotkey": active as long as the key is hot.
|
|
1191
|
-
*
|
|
1192
|
-
|
|
1285
|
+
* hence "hotkey": active as long as the key is hot. Omitted means the tool
|
|
1286
|
+
* is not eligible for the hotkey slot.
|
|
1287
|
+
*
|
|
1288
|
+
* The named keys map to their DOM names (`'space'` is `' '`, `'ctrl'` is
|
|
1289
|
+
* `'Control'`). Anything else is a `KeyboardEvent.key` value, matched
|
|
1290
|
+
* case-insensitively as every key spec is — so `'o'` holds on O. */
|
|
1291
|
+
type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift' | (string & {});
|
|
1193
1292
|
/** Presentation metadata for tool palettes / menus. Optional on every
|
|
1194
1293
|
* tool — consumers that render a palette (`<ToolPalette>`) read these
|
|
1195
1294
|
* fields to display the tool; consumers that don't can ignore them.
|
|
@@ -1209,6 +1308,10 @@ interface ToolPresentation<TScratch = unknown> {
|
|
|
1209
1308
|
/** Display override for the keyboard shortcut. When omitted the palette
|
|
1210
1309
|
* derives one from `Tool.keybinding` via its own formatter. */
|
|
1211
1310
|
shortcut?: string;
|
|
1311
|
+
/** Keep the tool off palettes — for a tool that is only ever held, like a
|
|
1312
|
+
* scrub engaged by its `hotkey`. It stays registered and selectable by
|
|
1313
|
+
* id; only the palette leaves it out. */
|
|
1314
|
+
hide?: boolean;
|
|
1212
1315
|
}
|
|
1213
1316
|
/**
|
|
1214
1317
|
* When an entry's bindings are live. A set, not one value: the hand tool is
|
|
@@ -1255,7 +1358,7 @@ interface ContributionRouting {
|
|
|
1255
1358
|
* What an entry draws and how a palette shows it. None of it reaches the
|
|
1256
1359
|
* dispatcher.
|
|
1257
1360
|
*/
|
|
1258
|
-
interface ContributionChrome<TOverlay =
|
|
1361
|
+
interface ContributionChrome<TOverlay = KernelOverlay> {
|
|
1259
1362
|
/** One layer, or several composed in the given order.
|
|
1260
1363
|
*
|
|
1261
1364
|
* Routing never reads a layer — it collects the live ones and hands them
|
|
@@ -1271,117 +1374,11 @@ interface ContributionChrome<TOverlay = unknown> {
|
|
|
1271
1374
|
* is optional and independent — an entry that only routes input declares only
|
|
1272
1375
|
* `bindings` and `actions`.
|
|
1273
1376
|
*/
|
|
1274
|
-
interface Contribution<TOverlay =
|
|
1377
|
+
interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, ContributionChrome<TOverlay> {
|
|
1275
1378
|
/** Reflection escape hatch — the authored form, when there was one. */
|
|
1276
1379
|
def?: unknown;
|
|
1277
1380
|
}
|
|
1278
1381
|
|
|
1279
|
-
/**
|
|
1280
|
-
* Configurable activation-key descriptor for tools that expose their
|
|
1281
|
-
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
1282
|
-
* only the fields meaningful to a caller-supplied tool-select key —
|
|
1283
|
-
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
1284
|
-
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
1285
|
-
* not part of the configurable surface.
|
|
1286
|
-
*/
|
|
1287
|
-
interface ToolKeybinding {
|
|
1288
|
-
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
1289
|
-
key: string | readonly string[];
|
|
1290
|
-
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
1291
|
-
mod?: boolean;
|
|
1292
|
-
/** Require Alt. Default `false`. */
|
|
1293
|
-
alt?: boolean;
|
|
1294
|
-
/**
|
|
1295
|
-
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
1296
|
-
* shift, `'optional'` allows either.
|
|
1297
|
-
*/
|
|
1298
|
-
shift?: boolean | 'optional';
|
|
1299
|
-
}
|
|
1300
|
-
/** Modifier-key snapshot at event dispatch time. */
|
|
1301
|
-
type ToolModifiers = ModifierState;
|
|
1302
|
-
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
1303
|
-
* via the tool's `TScratch` parameter; it survives across a single
|
|
1304
|
-
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
1305
|
-
* gesture start by `initScratch()`. */
|
|
1306
|
-
interface ToolCtx<TScratch = unknown> {
|
|
1307
|
-
worldX: number;
|
|
1308
|
-
worldY: number;
|
|
1309
|
-
modifiers: ToolModifiers;
|
|
1310
|
-
selection: SelectionApi;
|
|
1311
|
-
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
1312
|
-
* cast to a known shape. This layer doesn't constrain it. */
|
|
1313
|
-
adapter: unknown;
|
|
1314
|
-
applyOps: (ops: Op[], label: string) => void;
|
|
1315
|
-
/** Current viewport. Reflects camera-position semantics — see
|
|
1316
|
-
* `View` JSDoc. */
|
|
1317
|
-
view: View;
|
|
1318
|
-
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
1319
|
-
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
1320
|
-
* state. View changes are not undoable. */
|
|
1321
|
-
setView: (next: View) => void;
|
|
1322
|
-
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
1323
|
-
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
1324
|
-
* anchors. */
|
|
1325
|
-
canvasRect: DOMRect;
|
|
1326
|
-
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
1327
|
-
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
1328
|
-
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
1329
|
-
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
1330
|
-
debug?: DebugSink;
|
|
1331
|
-
scratch: TScratch;
|
|
1332
|
-
}
|
|
1333
|
-
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
1334
|
-
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
1335
|
-
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
1336
|
-
type ToolBounds = Bounds;
|
|
1337
|
-
/**
|
|
1338
|
-
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
1339
|
-
* into, plus the hooks that only make sense for one (`initScratch`,
|
|
1340
|
-
* activate/deactivate, live preview, `cursor`). Everything else — bindings,
|
|
1341
|
-
* actions, overlay, presentation — is inherited.
|
|
1342
|
-
*/
|
|
1343
|
-
interface Tool<TScratch = unknown, TOverlay = unknown> extends Contribution<TOverlay> {
|
|
1344
|
-
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
1345
|
-
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
1346
|
-
* is for tools that want their activation key to be configurable by the
|
|
1347
|
-
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
1348
|
-
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
1349
|
-
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
1350
|
-
* the invoker knows which tool to switch to). */
|
|
1351
|
-
keybinding?: ToolKeybinding;
|
|
1352
|
-
initScratch?: () => TScratch;
|
|
1353
|
-
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
1354
|
-
onActivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
1355
|
-
onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
1356
|
-
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
1357
|
-
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
1358
|
-
* reflect live gesture state without reaching into hook internals. The
|
|
1359
|
-
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
1360
|
-
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
1361
|
-
* the use site. */
|
|
1362
|
-
previewPose?: (id: string) => unknown;
|
|
1363
|
-
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
1364
|
-
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
1365
|
-
* can compute bounds without round-tripping through a geometry adapter. */
|
|
1366
|
-
previewBounds?: (id: string) => ToolBounds | null;
|
|
1367
|
-
/** Returns ids whose committed scene-render should be suppressed while this
|
|
1368
|
-
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
1369
|
-
* preview ghosts replace the committed pose). The standard scene slot
|
|
1370
|
-
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
1371
|
-
* `null` when no gesture is in flight. */
|
|
1372
|
-
previewIds?: () => Iterable<string> | null;
|
|
1373
|
-
}
|
|
1374
|
-
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
1375
|
-
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
1376
|
-
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
1377
|
-
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
1378
|
-
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
1379
|
-
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
1380
|
-
type AnyTool = Tool<any, any>;
|
|
1381
|
-
/** "A tool of any scratch type that draws `TOverlay`" — the container form for
|
|
1382
|
-
* registries that hold heterogeneous scratch shapes but one overlay type. */
|
|
1383
|
-
type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
1384
|
-
|
|
1385
1382
|
/**
|
|
1386
1383
|
* Pure matcher primitives live in `@weasel-js/gestures`. This file
|
|
1387
1384
|
* re-exports them for kit-internal consumers and layers the actions-layer
|
|
@@ -1396,18 +1393,18 @@ type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
|
1396
1393
|
interface ScopedBinding {
|
|
1397
1394
|
binding: GestureBinding;
|
|
1398
1395
|
scope: BindingScope;
|
|
1399
|
-
/**
|
|
1400
|
-
* to
|
|
1401
|
-
*
|
|
1402
|
-
|
|
1396
|
+
/** Id of the tool or contribution that declared this binding —
|
|
1397
|
+
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
1398
|
+
* action's own binding. */
|
|
1399
|
+
ownerId: string | null;
|
|
1403
1400
|
}
|
|
1404
1401
|
/** The binding that won a match. */
|
|
1405
1402
|
interface MatchResult {
|
|
1406
1403
|
binding: GestureBinding;
|
|
1407
1404
|
scope: BindingScope;
|
|
1408
|
-
/**
|
|
1409
|
-
* so the dispatcher can record it as the handle owner. */
|
|
1410
|
-
|
|
1405
|
+
/** Id of the entry that declared the binding — propagated from
|
|
1406
|
+
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
1407
|
+
ownerId: string | null;
|
|
1411
1408
|
}
|
|
1412
1409
|
/**
|
|
1413
1410
|
* True when a spec's target actually consults the affordance hit rather than
|
|
@@ -1422,19 +1419,6 @@ interface MatchResult {
|
|
|
1422
1419
|
* that declare nothing.
|
|
1423
1420
|
*/
|
|
1424
1421
|
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
1425
|
-
/** CSS-style specificity tuple for a GestureSpec. Higher tuple wins under
|
|
1426
|
-
* lexicographic compare. Dimensions, in order of precedence:
|
|
1427
|
-
*
|
|
1428
|
-
* [0] target — how much the spec's target narrows; see `targetRank`.
|
|
1429
|
-
* [1] mods — count of required modifier keys (shift/alt/ctrl/meta/mod).
|
|
1430
|
-
* `'optional'` does NOT count.
|
|
1431
|
-
* [2] phase — how much the spec's `phase` narrows; see `phaseRank`.
|
|
1432
|
-
* [3] exact — per-kind tiebreak: 2 for a drop/paste spec with a
|
|
1433
|
-
* non-empty `types` MIME filter, else 1.
|
|
1434
|
-
*
|
|
1435
|
-
* Identical tuples fall back to registration order in the matcher's
|
|
1436
|
-
* stable sort, preserving the pre-specificity tiebreaker. */
|
|
1437
|
-
declare function specificity(spec: GestureSpec): readonly [number, number, number, number];
|
|
1438
1422
|
/**
|
|
1439
1423
|
* Find the best-matching binding across all scopes.
|
|
1440
1424
|
*
|
|
@@ -1463,6 +1447,172 @@ declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], is
|
|
|
1463
1447
|
* action's `start()` returns an empty handle), the next match is tried.
|
|
1464
1448
|
*/
|
|
1465
1449
|
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
1450
|
+
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
1451
|
+
interface MatchOutcome {
|
|
1452
|
+
matches: MatchResult[];
|
|
1453
|
+
/** Bindings whose spec matched the event but which the claim barred, in
|
|
1454
|
+
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
1455
|
+
barred: MatchResult[];
|
|
1456
|
+
/** Owner of the exclusive claim, when one applied. */
|
|
1457
|
+
claimOwner?: string;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
1461
|
+
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
1462
|
+
* opt-in — the dispatcher asks only when it is recording.
|
|
1463
|
+
*/
|
|
1464
|
+
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
1465
|
+
collectBarred?: boolean;
|
|
1466
|
+
warn?: (message: string) => void;
|
|
1467
|
+
}): MatchOutcome;
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* How one input was resolved: every binding that matched it, what each filter
|
|
1471
|
+
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
1472
|
+
* that order did. The dispatcher fills one per input in dev builds, and
|
|
1473
|
+
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
1474
|
+
*
|
|
1475
|
+
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
1476
|
+
* buffer, serialized, or built by hand as a fixture.
|
|
1477
|
+
*/
|
|
1478
|
+
interface DispatchRecord {
|
|
1479
|
+
kind: 'dispatch';
|
|
1480
|
+
ts: number;
|
|
1481
|
+
input: DispatchRecordInput;
|
|
1482
|
+
/** Every binding whose spec matched the input, in matcher order, including
|
|
1483
|
+
* those a filter then dropped. */
|
|
1484
|
+
matched: readonly RecordCandidate[];
|
|
1485
|
+
dropped: readonly DroppedCandidate[];
|
|
1486
|
+
/** The candidates that survived both filters, best first. */
|
|
1487
|
+
ranked: readonly RankedCandidate[];
|
|
1488
|
+
/** True when nothing was invoked: the record answers "what would happen". */
|
|
1489
|
+
predicted: boolean;
|
|
1490
|
+
/** Action id of the candidate that fired (or would), else `null`. */
|
|
1491
|
+
fired: string | null;
|
|
1492
|
+
outcome: 'handled' | 'unhandled';
|
|
1493
|
+
}
|
|
1494
|
+
/** The input a {@link DispatchRecord} describes, reduced to plain data. */
|
|
1495
|
+
interface DispatchRecordInput {
|
|
1496
|
+
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
1497
|
+
eventKind: string;
|
|
1498
|
+
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
1499
|
+
key?: string;
|
|
1500
|
+
modifiers: {
|
|
1501
|
+
alt: boolean;
|
|
1502
|
+
ctrl: boolean;
|
|
1503
|
+
meta: boolean;
|
|
1504
|
+
shift: boolean;
|
|
1505
|
+
};
|
|
1506
|
+
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
1507
|
+
viewId: string | null;
|
|
1508
|
+
/** World point, for events that carry one. */
|
|
1509
|
+
world?: {
|
|
1510
|
+
x: number;
|
|
1511
|
+
y: number;
|
|
1512
|
+
};
|
|
1513
|
+
/** Active mode id, when the host supplies a rule context. */
|
|
1514
|
+
mode?: string;
|
|
1515
|
+
}
|
|
1516
|
+
/** One matched binding. */
|
|
1517
|
+
interface RecordCandidate {
|
|
1518
|
+
actionId: string;
|
|
1519
|
+
/** The binding's spec in route grammar — one per arg alternative. */
|
|
1520
|
+
routes: readonly string[];
|
|
1521
|
+
scope: BindingScope;
|
|
1522
|
+
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
1523
|
+
ownerId: string | null;
|
|
1524
|
+
/** Whether the binding's `views` names the routed view. */
|
|
1525
|
+
namesView: boolean;
|
|
1526
|
+
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
1527
|
+
specificity: readonly [number, number, number, number];
|
|
1528
|
+
/** The action's `eligible` rule as text, when it declares one. */
|
|
1529
|
+
eligible?: string;
|
|
1530
|
+
}
|
|
1531
|
+
/** A matched candidate a filter removed before ranking, and which filter. */
|
|
1532
|
+
type DroppedCandidate =
|
|
1533
|
+
/** An exclusive claim on the input barred bindings that do not read the
|
|
1534
|
+
* claim's affordance. */
|
|
1535
|
+
{
|
|
1536
|
+
candidate: RecordCandidate;
|
|
1537
|
+
filter: 'claim';
|
|
1538
|
+
owner?: string;
|
|
1539
|
+
}
|
|
1540
|
+
/** The action's `eligible` rule does not hold. */
|
|
1541
|
+
| {
|
|
1542
|
+
candidate: RecordCandidate;
|
|
1543
|
+
filter: 'ineligible';
|
|
1544
|
+
rule: string;
|
|
1545
|
+
};
|
|
1546
|
+
/** One field of the `specificity` tuple, in the order it is compared. */
|
|
1547
|
+
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
1548
|
+
/** The ranking step that put a candidate below the one before it. */
|
|
1549
|
+
type PlacedBy = {
|
|
1550
|
+
step: 'first';
|
|
1551
|
+
}
|
|
1552
|
+
/** The one above names the routed view; this one does not. */
|
|
1553
|
+
| {
|
|
1554
|
+
step: 'view';
|
|
1555
|
+
}
|
|
1556
|
+
/** The one above sits in a higher scope tier. */
|
|
1557
|
+
| {
|
|
1558
|
+
step: 'tier';
|
|
1559
|
+
}
|
|
1560
|
+
/** Same tier; the one above is more specific in `part`. */
|
|
1561
|
+
| {
|
|
1562
|
+
step: 'specificity';
|
|
1563
|
+
part: SpecificityPart;
|
|
1564
|
+
}
|
|
1565
|
+
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
1566
|
+
| {
|
|
1567
|
+
step: 'context';
|
|
1568
|
+
}
|
|
1569
|
+
/** Tied on everything; registration order. */
|
|
1570
|
+
| {
|
|
1571
|
+
step: 'order';
|
|
1572
|
+
};
|
|
1573
|
+
/** What the walk did with one ranked candidate. */
|
|
1574
|
+
type WalkStep = {
|
|
1575
|
+
kind: 'fired';
|
|
1576
|
+
}
|
|
1577
|
+
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
1578
|
+
* so an ongoing action here may still bail at `start()` for real. */
|
|
1579
|
+
| {
|
|
1580
|
+
kind: 'would-fire';
|
|
1581
|
+
}
|
|
1582
|
+
/** `enabled()` returned this reason. */
|
|
1583
|
+
| {
|
|
1584
|
+
kind: 'declined';
|
|
1585
|
+
reason: string;
|
|
1586
|
+
}
|
|
1587
|
+
/** An ongoing action's `start()` returned an empty handle. */
|
|
1588
|
+
| {
|
|
1589
|
+
kind: 'empty-handle';
|
|
1590
|
+
}
|
|
1591
|
+
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
1592
|
+
| {
|
|
1593
|
+
kind: 'misbound';
|
|
1594
|
+
}
|
|
1595
|
+
/** Its action was already tried higher up; each runs at most once. */
|
|
1596
|
+
| {
|
|
1597
|
+
kind: 'duplicate';
|
|
1598
|
+
} | {
|
|
1599
|
+
kind: 'no-such-action';
|
|
1600
|
+
}
|
|
1601
|
+
/** Below the winner; never asked. */
|
|
1602
|
+
| {
|
|
1603
|
+
kind: 'not-asked';
|
|
1604
|
+
}
|
|
1605
|
+
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
1606
|
+
| {
|
|
1607
|
+
kind: 'outranked';
|
|
1608
|
+
};
|
|
1609
|
+
/** A candidate that survived the filters: why it sits where it does, and
|
|
1610
|
+
* what the walk did with it. */
|
|
1611
|
+
interface RankedCandidate {
|
|
1612
|
+
candidate: RecordCandidate;
|
|
1613
|
+
placedBy: PlacedBy;
|
|
1614
|
+
walk: WalkStep;
|
|
1615
|
+
}
|
|
1466
1616
|
|
|
1467
1617
|
/**
|
|
1468
1618
|
* Dispatcher orchestrator — pure module, no React, no DOM.
|
|
@@ -1472,10 +1622,19 @@ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[],
|
|
|
1472
1622
|
* on `enabled()`; then invokes `immediate` or `ongoing` invokers and tracks
|
|
1473
1623
|
* in-flight handles.
|
|
1474
1624
|
*
|
|
1475
|
-
* ##
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1625
|
+
* ## Precedence and fall-through
|
|
1626
|
+
* An exclusive affordance claim first bars every binding that does not
|
|
1627
|
+
* consult the affordance. The rest are ranked, best first, by:
|
|
1628
|
+
* 1. bindings naming the routed view ahead of every other
|
|
1629
|
+
* (`preferViewScoped`);
|
|
1630
|
+
* 2. scope tier: hotkey > active > ambient;
|
|
1631
|
+
* 3. specificity within a tier (`specificity()` in `matcher.ts`);
|
|
1632
|
+
* 4. an action gated by an `eligible` rule that holds now ahead of one with
|
|
1633
|
+
* no rule (`preferContextual`) — Escape while editing a path exits the
|
|
1634
|
+
* edit rather than resetting the tool;
|
|
1635
|
+
* 5. registration order.
|
|
1636
|
+
* Candidates whose `eligible` rule is false are dropped first. The dispatcher
|
|
1637
|
+
* walks the rest and fires the first action whose `enabled()` returns
|
|
1479
1638
|
* `true`. If every candidate's `enabled()` returns a disabled reason, the
|
|
1480
1639
|
* event is unhandled. This mirrors CSS-style specificity matching with a
|
|
1481
1640
|
* `:not(:disabled)` filter, and lets a tool declare a high-specificity
|
|
@@ -1515,23 +1674,6 @@ declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[],
|
|
|
1515
1674
|
* synthesized event with no originating DOM pointer.
|
|
1516
1675
|
*/
|
|
1517
1676
|
declare function pointerGestureId(pointerId: number | undefined): string;
|
|
1518
|
-
/** One entry per `handleInput` call. */
|
|
1519
|
-
interface DispatchLogEntry {
|
|
1520
|
-
kind: 'dispatch';
|
|
1521
|
-
ts: number;
|
|
1522
|
-
eventKind: string;
|
|
1523
|
-
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`).
|
|
1524
|
-
* Lives on the event side of the entry — `fired` should describe
|
|
1525
|
-
* what dispatched, not which key triggered it. */
|
|
1526
|
-
key?: string;
|
|
1527
|
-
candidates: Array<{
|
|
1528
|
-
actionId: string;
|
|
1529
|
-
scope: BindingScope;
|
|
1530
|
-
enabledResult: boolean | string;
|
|
1531
|
-
}>;
|
|
1532
|
-
fired: string | null;
|
|
1533
|
-
outcome: 'handled' | 'unhandled';
|
|
1534
|
-
}
|
|
1535
1677
|
/** One entry per "mode change" — a kit state transition that isn't a
|
|
1536
1678
|
* dispatched input event but is load-bearing for understanding why an
|
|
1537
1679
|
* input was (or wasn't) handled later. Pushed via `recordModeSwitch()`;
|
|
@@ -1548,7 +1690,8 @@ interface ModeSwitchLogEntry {
|
|
|
1548
1690
|
* `'selection-dropped target'`, etc. */
|
|
1549
1691
|
detail?: string;
|
|
1550
1692
|
}
|
|
1551
|
-
|
|
1693
|
+
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
1694
|
+
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
1552
1695
|
/** Push a mode-switch record into the trace log. Safe to call from
|
|
1553
1696
|
* anywhere; no-ops outside DEV. */
|
|
1554
1697
|
declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
@@ -1562,12 +1705,12 @@ interface DispatcherContext {
|
|
|
1562
1705
|
actions: ActionSource;
|
|
1563
1706
|
/** Dep sources keyed by name. */
|
|
1564
1707
|
depRegistry: DepRegistry;
|
|
1565
|
-
/** Active tool's id (from ActiveToolContext). */
|
|
1566
|
-
activeToolId: string;
|
|
1708
|
+
/** Active tool's id (from ActiveToolContext); `null` when none is. */
|
|
1709
|
+
activeToolId: string | null;
|
|
1567
1710
|
/** Held-hotkey stack, top of stack last. */
|
|
1568
1711
|
hotkeyStack: readonly string[];
|
|
1569
|
-
/**
|
|
1570
|
-
|
|
1712
|
+
/** Every registered entry, tools and ambient contributions alike, by id. */
|
|
1713
|
+
entriesById: ReadonlyMap<string, Contribution<unknown>>;
|
|
1571
1714
|
/** Platform flag for `mod` shorthand resolution. */
|
|
1572
1715
|
isMac: boolean;
|
|
1573
1716
|
/**
|
|
@@ -1587,18 +1730,6 @@ interface DispatcherContext {
|
|
|
1587
1730
|
*/
|
|
1588
1731
|
viewId?: string | null;
|
|
1589
1732
|
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Filter `matches` to only those whose backing action has no `eligible`
|
|
1592
|
-
* rule, or whose rule evaluates true against `ruleCtx`. Exported for
|
|
1593
|
-
* unit testing.
|
|
1594
|
-
*
|
|
1595
|
-
* @internal
|
|
1596
|
-
*/
|
|
1597
|
-
declare function filterEligible<M extends {
|
|
1598
|
-
binding: {
|
|
1599
|
-
actionId: string;
|
|
1600
|
-
};
|
|
1601
|
-
}>(matches: readonly M[], actionLookup: (id: string) => Action | undefined, ruleCtx: RuleCtx): M[];
|
|
1602
1733
|
/**
|
|
1603
1734
|
* Handle returned by `Dispatcher.beginUiOngoing()` for driving an
|
|
1604
1735
|
* ongoing invoker from a UI control (color picker, slider).
|
|
@@ -1624,7 +1755,7 @@ interface ResolveOnlyResult {
|
|
|
1624
1755
|
action: Action;
|
|
1625
1756
|
scope: BindingScope;
|
|
1626
1757
|
/** Tool id owning the winning binding; `null` for ambient action bindings. */
|
|
1627
|
-
|
|
1758
|
+
ownerId: string | null;
|
|
1628
1759
|
}
|
|
1629
1760
|
/**
|
|
1630
1761
|
* One candidate from `Dispatcher.resolveAll` — a binding that matched the
|
|
@@ -1648,7 +1779,7 @@ interface ResolvedCandidate {
|
|
|
1648
1779
|
action: Action;
|
|
1649
1780
|
binding: GestureBinding;
|
|
1650
1781
|
scope: BindingScope;
|
|
1651
|
-
|
|
1782
|
+
ownerId: string | null;
|
|
1652
1783
|
/** The tuple from `specificity(binding.spec)`, surfaced so a reader can see
|
|
1653
1784
|
* why one candidate outranks another rather than inferring it. */
|
|
1654
1785
|
specificity: readonly [number, number, number, number];
|
|
@@ -1736,6 +1867,14 @@ interface Dispatcher {
|
|
|
1736
1867
|
* {@link ResolveAllOptions.evaluateShadowed}.
|
|
1737
1868
|
*/
|
|
1738
1869
|
resolveAll(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): ResolvedCandidate[];
|
|
1870
|
+
/**
|
|
1871
|
+
* The full {@link DispatchRecord} for `event` — what matched, what each
|
|
1872
|
+
* filter dropped, how the rest ranked and why, and the walk — without
|
|
1873
|
+
* invoking anything, so `predicted` is true and the winner is `would-fire`.
|
|
1874
|
+
* It is the same computation `handleInput` and `resolveAll` run. Costs more
|
|
1875
|
+
* than `resolveOnly`: it renders every candidate's routes and rule.
|
|
1876
|
+
*/
|
|
1877
|
+
explain(event: InputEvent, ctx: DispatcherContext, opts?: ResolveAllOptions): DispatchRecord;
|
|
1739
1878
|
/**
|
|
1740
1879
|
* Synthesize an end-of-gesture for every in-flight ongoing handle.
|
|
1741
1880
|
* Used by tool-switch cancellation (Q2 decision).
|
|
@@ -1856,10 +1995,9 @@ interface ActionsRegistry {
|
|
|
1856
1995
|
/** Drop every registrant of `id`. This is the "this action should not exist"
|
|
1857
1996
|
* door, not a release — for that, call what `register` returned. */
|
|
1858
1997
|
unregister(id: string): void;
|
|
1859
|
-
/** Declare `id` not for this scope: it stays registered
|
|
1860
|
-
*
|
|
1861
|
-
*
|
|
1862
|
-
* `<ActionsScope>` drops what it muted when it unmounts. This is the
|
|
1998
|
+
/** Declare `id` not for this scope: it stays registered wherever it was,
|
|
1999
|
+
* but here — and in scopes below — it lists as absent and neither
|
|
2000
|
+
* `trigger` nor `begin` will fire it. Returns a release. This is the
|
|
1863
2001
|
* "not for me" door — `unregister` is the "should not exist" one. */
|
|
1864
2002
|
mute(id: string): () => void;
|
|
1865
2003
|
list(): readonly Action[];
|
|
@@ -1888,18 +2026,16 @@ interface ActionsRegistry {
|
|
|
1888
2026
|
*/
|
|
1889
2027
|
begin(id: string, params?: Record<string, unknown>): UiOngoingControl | null;
|
|
1890
2028
|
/** Wire a dispatcher into the registry so `begin()` can delegate to it.
|
|
1891
|
-
* Returns a release that
|
|
1892
|
-
*
|
|
1893
|
-
* the canvas now on screen. Call with `null` to detach unconditionally. */
|
|
2029
|
+
* Returns a release that takes out only this dispatcher; a `null` wires
|
|
2030
|
+
* nothing. */
|
|
1894
2031
|
setDispatcher(d: Dispatcher | null): () => void;
|
|
1895
|
-
/**
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
*
|
|
1901
|
-
|
|
1902
|
-
setDepRegistry(r: DepRegistry | null): () => void;
|
|
2032
|
+
/** Make this registry's scope the one chrome above it reaches: `trigger`,
|
|
2033
|
+
* `begin` and `list` on any ancestor then answer from here. A dispatcher
|
|
2034
|
+
* calls it on the pointerdown and focus that land in its scope. */
|
|
2035
|
+
activate(): void;
|
|
2036
|
+
/** Whether a lookup made from the root of this registry's tree starts here —
|
|
2037
|
+
* the question a dispatcher asks before handling a keystroke. */
|
|
2038
|
+
isActive(): boolean;
|
|
1903
2039
|
}
|
|
1904
2040
|
/** Validate that `id` is usable as an action id in the route grammar.
|
|
1905
2041
|
* Rejects ids that start with a reserved sigil (would shadow future
|
|
@@ -1922,8 +2058,8 @@ interface ToolOffhandBindingSpec {
|
|
|
1922
2058
|
toolId: string;
|
|
1923
2059
|
key: string | string[];
|
|
1924
2060
|
}
|
|
1925
|
-
/** `KeyboardEvent.key` for a declared `Eligibility.offhand` trigger.
|
|
1926
|
-
*
|
|
2061
|
+
/** `KeyboardEvent.key` for a declared `Eligibility.offhand` trigger. The
|
|
2062
|
+
* named keys become their DOM names; any other trigger already is one. */
|
|
1927
2063
|
declare function offhandKeyFor(trigger: HotkeyTrigger): string;
|
|
1928
2064
|
/** Build the per-tool `BoundGesture` list for the consolidated action's
|
|
1929
2065
|
* `defaultBinding`. Each entry pairs a `key-held` spec with
|
|
@@ -1974,6 +2110,85 @@ declare const isControlHandle: (target: unknown) => boolean;
|
|
|
1974
2110
|
/** Any of the three anchor-editing affordances (anchor + both controls). */
|
|
1975
2111
|
declare const isAnchorOrControl: (target: unknown) => boolean;
|
|
1976
2112
|
|
|
2113
|
+
/**
|
|
2114
|
+
* The long-press the gesture dispatcher is timing, as observable state: where
|
|
2115
|
+
* the press landed, when the hold started, how far through it is, and whether
|
|
2116
|
+
* any binding would fire on it. Feedback — a filling ring, a haptic — reads
|
|
2117
|
+
* this; nothing here decides what the feedback looks like.
|
|
2118
|
+
*/
|
|
2119
|
+
/** A press being held toward a long-press. */
|
|
2120
|
+
interface PendingLongPress {
|
|
2121
|
+
pointerId: number;
|
|
2122
|
+
/** Long-press is synthesized for these two only; a mouse never arms one. */
|
|
2123
|
+
pointerType: 'touch' | 'pen';
|
|
2124
|
+
/** Where the press landed, in client coordinates. */
|
|
2125
|
+
client: {
|
|
2126
|
+
x: number;
|
|
2127
|
+
y: number;
|
|
2128
|
+
};
|
|
2129
|
+
/** The same point in CSS pixels from the canvas element's top-left — what a
|
|
2130
|
+
* `space: 'screen'` layer draws in. */
|
|
2131
|
+
local: {
|
|
2132
|
+
x: number;
|
|
2133
|
+
y: number;
|
|
2134
|
+
};
|
|
2135
|
+
/** The same point in the routed view's world coordinates. */
|
|
2136
|
+
world: {
|
|
2137
|
+
x: number;
|
|
2138
|
+
y: number;
|
|
2139
|
+
};
|
|
2140
|
+
/** The view the press routed to; `null` is the root view. */
|
|
2141
|
+
viewId: string | null;
|
|
2142
|
+
/** `performance.now()` when the hold started. */
|
|
2143
|
+
startedAt: number;
|
|
2144
|
+
/** How long the hold must last before the long-press fires, in ms. */
|
|
2145
|
+
duration: number;
|
|
2146
|
+
/**
|
|
2147
|
+
* Whether holding to the end would fire anything — a `longPress` binding,
|
|
2148
|
+
* or a `contextMenu` binding through the fallback. Resolved at press time.
|
|
2149
|
+
* Feedback shows only when this is true: a press that will do nothing must
|
|
2150
|
+
* not promise that it will.
|
|
2151
|
+
*/
|
|
2152
|
+
armed: boolean;
|
|
2153
|
+
}
|
|
2154
|
+
/** Read side of the pending long-press. Published as the `longPress` dep. */
|
|
2155
|
+
interface LongPressState {
|
|
2156
|
+
/** The press being held, or `null` when none is. Cleared when the
|
|
2157
|
+
* long-press fires, and when movement, release, cancel or a second
|
|
2158
|
+
* pointer abandons it. */
|
|
2159
|
+
get(): PendingLongPress | null;
|
|
2160
|
+
/** How far through the hold the pending press is, 0→1, at `now`
|
|
2161
|
+
* (`performance.now()` by default). 0 when nothing is pending. */
|
|
2162
|
+
progress(now?: number): number;
|
|
2163
|
+
/** Called whenever {@link get} changes. Progress is derived from the
|
|
2164
|
+
* clock, so it does not notify — a loop drawing it asks for frames. */
|
|
2165
|
+
subscribe(fn: () => void): () => void;
|
|
2166
|
+
}
|
|
2167
|
+
/** The dispatcher's writable half of {@link LongPressState}. */
|
|
2168
|
+
interface LongPressStore extends LongPressState {
|
|
2169
|
+
set(next: PendingLongPress | null): void;
|
|
2170
|
+
}
|
|
2171
|
+
/** How long a touch or pen press is held before it fires `longPress`, in ms. */
|
|
2172
|
+
declare const LONG_PRESS_MS = 500;
|
|
2173
|
+
/** How `useGestureDispatcher` times, publishes and signals a long-press. */
|
|
2174
|
+
interface LongPressOptions {
|
|
2175
|
+
/** Hold time in ms. Default {@link LONG_PRESS_MS}. */
|
|
2176
|
+
duration?: number;
|
|
2177
|
+
/**
|
|
2178
|
+
* Where the press being held is published: position, start time, progress,
|
|
2179
|
+
* and whether any binding would fire on it. `<SceneCanvas>` passes one and
|
|
2180
|
+
* republishes it as the `longPress` dep, which its default press ring
|
|
2181
|
+
* reads. Omit it and nothing is published.
|
|
2182
|
+
*/
|
|
2183
|
+
state?: LongPressStore;
|
|
2184
|
+
/**
|
|
2185
|
+
* Default true. A long-press some binding handled pulses `navigator.vibrate`
|
|
2186
|
+
* briefly, for touch and pen pointers, where the API exists.
|
|
2187
|
+
*/
|
|
2188
|
+
haptics?: boolean;
|
|
2189
|
+
}
|
|
2190
|
+
declare function createLongPressStore(): LongPressStore;
|
|
2191
|
+
|
|
1977
2192
|
/**
|
|
1978
2193
|
* The DOM half of a drag: one pointer, held down, from press to release.
|
|
1979
2194
|
*
|
|
@@ -1993,6 +2208,7 @@ type PointerSessionCancelReason =
|
|
|
1993
2208
|
| 'aborted'
|
|
1994
2209
|
/** A new press arrived on this pointer, so the tracked one had ended. */
|
|
1995
2210
|
| 'superseded';
|
|
2211
|
+
/** What {@link openPointerSession} reports. Exactly one of `onEnd` / `onCancel` fires when the session ends. */
|
|
1996
2212
|
interface PointerSessionCallbacks {
|
|
1997
2213
|
/** Every move belonging to this pointer, while it is still held. */
|
|
1998
2214
|
onMove?: (e: PointerEvent) => void;
|
|
@@ -2001,13 +2217,16 @@ interface PointerSessionCallbacks {
|
|
|
2001
2217
|
/** The session ended without a release. Fires once. */
|
|
2002
2218
|
onCancel?: (reason: PointerSessionCancelReason) => void;
|
|
2003
2219
|
}
|
|
2220
|
+
/** Options for {@link openPointerSession}. */
|
|
2004
2221
|
interface PointerSessionOptions {
|
|
2005
2222
|
/** Ask the origin element to capture the pointer. Default `true`. Capture
|
|
2006
2223
|
* keeps hover and click off everything the drag passes over; the session
|
|
2007
2224
|
* does not depend on it to keep receiving events. */
|
|
2008
2225
|
capture?: boolean;
|
|
2009
2226
|
}
|
|
2227
|
+
/** A live drag opened by {@link openPointerSession}. */
|
|
2010
2228
|
interface PointerSession {
|
|
2229
|
+
/** The pointer this session follows, taken from the press. */
|
|
2011
2230
|
readonly pointerId: number;
|
|
2012
2231
|
/** False once the session has ended, by any route. */
|
|
2013
2232
|
readonly active: boolean;
|
|
@@ -2051,7 +2270,7 @@ declare function liveScope(id: string, eligibility: Eligibility, state: Eligibil
|
|
|
2051
2270
|
* breaks ties by declaration order, so the caller's entry order decides which
|
|
2052
2271
|
* of two same-specificity bindings in one tier wins.
|
|
2053
2272
|
*/
|
|
2054
|
-
declare function scopeBindings(entries: Iterable<Contribution
|
|
2273
|
+
declare function scopeBindings(entries: Iterable<Contribution<unknown>>, state: EligibilityState): ScopedBinding[];
|
|
2055
2274
|
|
|
2056
2275
|
/**
|
|
2057
2276
|
* Concatenate contribution bundles into one registry, preserving order —
|
|
@@ -2064,6 +2283,165 @@ declare function scopeBindings(entries: Iterable<Contribution>, state: Eligibili
|
|
|
2064
2283
|
*/
|
|
2065
2284
|
declare function mergeContributions<T extends Contribution<unknown>>(...bundles: readonly (readonly T[])[]): T[];
|
|
2066
2285
|
|
|
2286
|
+
/**
|
|
2287
|
+
* What each mode shortcut does. A role left out binds nothing, so its key
|
|
2288
|
+
* falls through to whatever else the dispatcher has on it.
|
|
2289
|
+
*/
|
|
2290
|
+
interface ModeShortcutHandlers {
|
|
2291
|
+
/** Enter mode `id` — the `entry` shortcut. */
|
|
2292
|
+
enter?: (id: string) => void;
|
|
2293
|
+
/** Whether mode `id` can be entered now. A mode that needs a target it
|
|
2294
|
+
* doesn't have answers no, and its entry key falls through. Default yes. */
|
|
2295
|
+
canEnter?: (id: string) => boolean;
|
|
2296
|
+
/** Leave the active soft mode, keeping its session to resume. */
|
|
2297
|
+
exit?: () => void;
|
|
2298
|
+
/** Leave the active soft mode and throw its session away. */
|
|
2299
|
+
discard?: () => void;
|
|
2300
|
+
/** Commit the active strict mode's transaction. */
|
|
2301
|
+
commit?: () => void;
|
|
2302
|
+
/** Abandon the active strict mode's transaction. */
|
|
2303
|
+
cancel?: () => void;
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* A `ModeDefinition` shortcut chord — `'Escape'`, `'Meta+T'` — as a key spec.
|
|
2307
|
+
* Modifier names are case-insensitive; the last segment is the key.
|
|
2308
|
+
*/
|
|
2309
|
+
declare function modeShortcutSpec(shortcut: string): KeySpec;
|
|
2310
|
+
/**
|
|
2311
|
+
* The registry's declared mode shortcuts as an always-on contribution: one
|
|
2312
|
+
* action per shortcut, bound to its chord and gated on the mode it acts on —
|
|
2313
|
+
* `exit` / `discard` / `commit` / `cancel` while that mode is active, `entry`
|
|
2314
|
+
* while it is not. Install it with `<SceneCanvas ambient>`.
|
|
2315
|
+
*
|
|
2316
|
+
* The bindings ride the hotkey tier, so leaving a mode outranks what the same
|
|
2317
|
+
* key does otherwise (Escape clearing the selection), and they yield while a
|
|
2318
|
+
* gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
|
|
2319
|
+
* typed into an editable element never reach them.
|
|
2320
|
+
*/
|
|
2321
|
+
declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
|
|
2322
|
+
|
|
2323
|
+
/**
|
|
2324
|
+
* Configurable activation-key descriptor for tools that expose their
|
|
2325
|
+
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
2326
|
+
* only the fields meaningful to a caller-supplied tool-select key —
|
|
2327
|
+
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
2328
|
+
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
2329
|
+
* not part of the configurable surface.
|
|
2330
|
+
*/
|
|
2331
|
+
interface ToolKeybinding {
|
|
2332
|
+
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
2333
|
+
key: string | readonly string[];
|
|
2334
|
+
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
2335
|
+
mod?: boolean;
|
|
2336
|
+
/** Require Alt. Default `false`. */
|
|
2337
|
+
alt?: boolean;
|
|
2338
|
+
/**
|
|
2339
|
+
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
2340
|
+
* shift, `'optional'` allows either.
|
|
2341
|
+
*/
|
|
2342
|
+
shift?: boolean | 'optional';
|
|
2343
|
+
}
|
|
2344
|
+
/** Modifier-key snapshot at event dispatch time. */
|
|
2345
|
+
type ToolModifiers = ModifierState;
|
|
2346
|
+
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
2347
|
+
* via the tool's `TScratch` parameter; it survives across a single
|
|
2348
|
+
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
2349
|
+
* gesture start by `initScratch()`. */
|
|
2350
|
+
interface ToolCtx<TScratch = unknown> {
|
|
2351
|
+
worldX: number;
|
|
2352
|
+
worldY: number;
|
|
2353
|
+
modifiers: ToolModifiers;
|
|
2354
|
+
selection: SelectionApi;
|
|
2355
|
+
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
2356
|
+
* cast to a known shape. This layer doesn't constrain it. */
|
|
2357
|
+
adapter: unknown;
|
|
2358
|
+
applyOps: (ops: Op[], label: string) => void;
|
|
2359
|
+
/** Current viewport. Reflects camera-position semantics — see
|
|
2360
|
+
* `View` JSDoc. */
|
|
2361
|
+
view: View;
|
|
2362
|
+
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
2363
|
+
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
2364
|
+
* state. View changes are not undoable. */
|
|
2365
|
+
setView: (next: View) => void;
|
|
2366
|
+
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
2367
|
+
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
2368
|
+
* anchors. */
|
|
2369
|
+
canvasRect: DOMRect;
|
|
2370
|
+
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
2371
|
+
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
2372
|
+
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
2373
|
+
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
2374
|
+
debug?: DebugSink;
|
|
2375
|
+
scratch: TScratch;
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* What `onActivate` / `onDeactivate` receive: the tool's scratch, from
|
|
2379
|
+
* `initScratch()`. A lifecycle moment has no pointer, hit or modifier state to
|
|
2380
|
+
* report, so nothing else is populated; a callback needing the selection or
|
|
2381
|
+
* the scene closes over it, as the tool's actions do.
|
|
2382
|
+
*/
|
|
2383
|
+
interface ToolLifecycleCtx<TScratch = unknown> {
|
|
2384
|
+
scratch: TScratch;
|
|
2385
|
+
}
|
|
2386
|
+
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
2387
|
+
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
2388
|
+
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
2389
|
+
type ToolBounds = Bounds;
|
|
2390
|
+
/**
|
|
2391
|
+
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
2392
|
+
* into, plus the hooks that only make sense for one (`initScratch`,
|
|
2393
|
+
* activate/deactivate, live preview, `cursor`). Everything else — bindings,
|
|
2394
|
+
* actions, overlay, presentation — is inherited.
|
|
2395
|
+
*/
|
|
2396
|
+
interface Tool<TScratch = unknown, TOverlay = KernelOverlay> extends Contribution<TOverlay> {
|
|
2397
|
+
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
2398
|
+
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
2399
|
+
* is for tools that want their activation key to be configurable by the
|
|
2400
|
+
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
2401
|
+
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
2402
|
+
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
2403
|
+
* the invoker knows which tool to switch to). */
|
|
2404
|
+
keybinding?: ToolKeybinding;
|
|
2405
|
+
initScratch?: () => TScratch;
|
|
2406
|
+
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
2407
|
+
/** The tool became live: it took the active slot, or its `hotkey` went
|
|
2408
|
+
* down, while it held neither. */
|
|
2409
|
+
onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2410
|
+
/** The tool stopped being live in both slots, or its canvas unmounted. */
|
|
2411
|
+
onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2412
|
+
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
2413
|
+
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
2414
|
+
* reflect live gesture state without reaching into hook internals. The
|
|
2415
|
+
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
2416
|
+
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
2417
|
+
* the use site. */
|
|
2418
|
+
previewPose?: (id: string) => unknown;
|
|
2419
|
+
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
2420
|
+
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
2421
|
+
* can compute bounds without round-tripping through a geometry adapter. */
|
|
2422
|
+
previewBounds?: (id: string) => ToolBounds | null;
|
|
2423
|
+
/** Returns ids whose committed scene-render should be suppressed while this
|
|
2424
|
+
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
2425
|
+
* preview ghosts replace the committed pose). The standard scene slot
|
|
2426
|
+
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
2427
|
+
* `null` when no gesture is in flight. */
|
|
2428
|
+
previewIds?: () => Iterable<string> | null;
|
|
2429
|
+
}
|
|
2430
|
+
/** Whether `entry` is a tool: a contribution that can hold focus, chosen from
|
|
2431
|
+
* a palette or held on a key. Only a tool's focus hooks (`initScratch`,
|
|
2432
|
+
* `cursor`, `onActivate`, the previews) ever run. */
|
|
2433
|
+
declare function isTool<TOverlay>(entry: Contribution<TOverlay>): entry is Tool<unknown, TOverlay>;
|
|
2434
|
+
/** Internal — which slot a tool occupies in the dispatch order. */
|
|
2435
|
+
type ToolSlot = 'hotkey' | 'active' | 'ambient';
|
|
2436
|
+
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
2437
|
+
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
2438
|
+
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
2439
|
+
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
2440
|
+
type AnyTool = Tool<any, any>;
|
|
2441
|
+
/** "A tool of any scratch type that draws `TOverlay`" — the container form for
|
|
2442
|
+
* registries that hold heterogeneous scratch shapes but one overlay type. */
|
|
2443
|
+
type AnyToolOf<TOverlay> = Tool<any, TOverlay>;
|
|
2444
|
+
|
|
2067
2445
|
/**
|
|
2068
2446
|
* What a tool declares.
|
|
2069
2447
|
*
|
|
@@ -2073,7 +2451,7 @@ declare function mergeContributions<T extends Contribution<unknown>>(...bundles:
|
|
|
2073
2451
|
* definition is mostly `bindings`, plus presentation and any actions the tool
|
|
2074
2452
|
* itself introduces.
|
|
2075
2453
|
*/
|
|
2076
|
-
interface ToolDef<TScratch = void, TOverlay =
|
|
2454
|
+
interface ToolDef<TScratch = void, TOverlay = KernelOverlay> {
|
|
2077
2455
|
id: string;
|
|
2078
2456
|
/** Capability tags for modality eligibility. `defineTool` forwards these
|
|
2079
2457
|
* onto `Tool.eligibility.capabilities`, which is where every gate reads
|
|
@@ -2085,14 +2463,9 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2085
2463
|
* reference by id.
|
|
2086
2464
|
*
|
|
2087
2465
|
* Declared here rather than registered by the hook with `useAction`,
|
|
2088
|
-
* because tool hooks run wherever the consumer calls them
|
|
2089
|
-
*
|
|
2090
|
-
*
|
|
2091
|
-
* result was a binding pointing at an action id nothing had registered, so
|
|
2092
|
-
* the gesture fell through to whatever matched next (polygon's
|
|
2093
|
-
* wheel/arrow-key side adjustment did nothing and `nudge.*` moved the
|
|
2094
|
-
* selection instead). `<ToolActionsMounter>` registers these from inside
|
|
2095
|
-
* the provider.
|
|
2466
|
+
* because tool hooks run wherever the consumer calls them, which may be
|
|
2467
|
+
* outside any registry — and there `useAction` silently no-ops, leaving a
|
|
2468
|
+
* binding that points at an action id nothing registered.
|
|
2096
2469
|
*/
|
|
2097
2470
|
actions?: Action[];
|
|
2098
2471
|
/** Hook name as exported from the kit barrel (e.g. `'useHandTool'`).
|
|
@@ -2111,12 +2484,17 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2111
2484
|
* set so the invoker knows which tool to switch to). */
|
|
2112
2485
|
keybinding?: ToolKeybinding;
|
|
2113
2486
|
/** Held-key trigger: this tool engages while the key is down and
|
|
2114
|
-
* disengages on release
|
|
2487
|
+
* disengages on release — a modifier, `'space'`, or any other key
|
|
2488
|
+
* (`'o'`), never while typing in a text field. Carried onto
|
|
2489
|
+
* `Tool.eligibility.offhand`, which
|
|
2115
2490
|
* assembly reads to register the consolidated `tool.offhand` action — the
|
|
2116
2491
|
* declaration is the wiring, with nothing for the host to do. */
|
|
2117
2492
|
hotkey?: HotkeyTrigger;
|
|
2118
|
-
|
|
2119
|
-
|
|
2493
|
+
/** Fires as the tool becomes live in the active or hotkey slot. See
|
|
2494
|
+
* `Tool.onActivate`. */
|
|
2495
|
+
onActivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2496
|
+
/** Fires as the tool stops being live in either slot. */
|
|
2497
|
+
onDeactivate?: (ctx: ToolLifecycleCtx<TScratch>) => void;
|
|
2120
2498
|
cursor?: CursorSpec | ((ctx: ToolCtx<TScratch>) => CursorSpec);
|
|
2121
2499
|
/** Override the default scratch initializer. Default is `() => null`
|
|
2122
2500
|
* cast to `TScratch`, which works for tools whose scratch is fresh
|
|
@@ -2146,10 +2524,6 @@ interface ToolDef<TScratch = void, TOverlay = unknown> {
|
|
|
2146
2524
|
* `'top'`, above everything. */
|
|
2147
2525
|
overlayPosition?: OverlayPosition;
|
|
2148
2526
|
}
|
|
2149
|
-
/** Viewport-tool spec. Once phase tables went away this stopped differing
|
|
2150
|
-
* from `ToolDef` in any structural way; `defineViewportTool` survives as the
|
|
2151
|
-
* authoring signal that a tool pans/zooms the view rather than the scene. */
|
|
2152
|
-
type ViewportToolDef<TScratch = void, TOverlay = unknown> = ToolDef<TScratch, TOverlay>;
|
|
2153
2527
|
|
|
2154
2528
|
/**
|
|
2155
2529
|
* Build a `Tool<TScratch>` from a declarative `ToolDef<TScratch>`.
|
|
@@ -2165,22 +2539,7 @@ type ViewportToolDef<TScratch = void, TOverlay = unknown> = ToolDef<TScratch, TO
|
|
|
2165
2539
|
* and the `initScratch` / `cursor` defaults are worth applying uniformly, and
|
|
2166
2540
|
* because `Tool.def` gives reflection a handle on the authored form.
|
|
2167
2541
|
*/
|
|
2168
|
-
declare function defineTool<TScratch = void, TOverlay =
|
|
2169
|
-
|
|
2170
|
-
/**
|
|
2171
|
-
* Define a tool that acts on the viewport rather than the scene.
|
|
2172
|
-
*
|
|
2173
|
-
* This used to do real work: `ViewportPhaseDef` was a narrowed `PhaseDef`
|
|
2174
|
-
* (no click routes, drag restricted to the function form), and the factory
|
|
2175
|
-
* lifted it back to the permissive shape before handing it to `defineTool`.
|
|
2176
|
-
* With phase tables gone there is no shape left to narrow — a viewport tool
|
|
2177
|
-
* declares `bindings` like any other, pointing at `viewport.*` actions.
|
|
2178
|
-
*
|
|
2179
|
-
* It survives as an authoring signal. `defineViewportTool` at the top of a
|
|
2180
|
-
* hook says "this tool moves the camera, not the drawing", which is worth
|
|
2181
|
-
* more than the type gymnastics it replaced.
|
|
2182
|
-
*/
|
|
2183
|
-
declare function defineViewportTool<TScratch = void, TOverlay = unknown>(def: ViewportToolDef<TScratch, TOverlay>): Tool<TScratch, TOverlay>;
|
|
2542
|
+
declare function defineTool<TScratch = void, TOverlay = KernelOverlay>(def: ToolDef<TScratch, TOverlay>): Tool<TScratch, TOverlay>;
|
|
2184
2543
|
|
|
2185
2544
|
/**
|
|
2186
2545
|
* One row in the route registry — a single `GestureBinding` on one tool,
|
|
@@ -2191,7 +2550,8 @@ declare function defineViewportTool<TScratch = void, TOverlay = unknown>(def: Vi
|
|
|
2191
2550
|
* declare them; consumers walk the list and group client-side.
|
|
2192
2551
|
*/
|
|
2193
2552
|
interface RegistryEntry {
|
|
2194
|
-
|
|
2553
|
+
/** The id of the entry that declared the binding: a tool, or a contribution. */
|
|
2554
|
+
ownerId: string;
|
|
2195
2555
|
/** Which phase the binding's `phase` spec restricts it to. `'any'` when it
|
|
2196
2556
|
* declares none, declares `'*'`, or declares an atom list whose atoms
|
|
2197
2557
|
* don't agree on one phase — such a binding fires in either phase, and
|
|
@@ -2224,6 +2584,9 @@ interface RegistryEntry {
|
|
|
2224
2584
|
* specificity tuple, a `kindOf` predicate identity — read it here rather
|
|
2225
2585
|
* than re-walking `Tool.bindings`. */
|
|
2226
2586
|
spec: GestureSpec;
|
|
2587
|
+
/** The binding's `opts.views`: the views it is live in. Absent when it is
|
|
2588
|
+
* live in every view. */
|
|
2589
|
+
views?: readonly (string | null)[];
|
|
2227
2590
|
}
|
|
2228
2591
|
/**
|
|
2229
2592
|
* Stand-in target for a `{ kindOf }` predicate spec.
|
|
@@ -2246,10 +2609,7 @@ declare const PREDICATE_TARGET = "predicate";
|
|
|
2246
2609
|
* Registry, and reading `buildActionRegistry` next to `ActionsRegistry`
|
|
2247
2610
|
* suggested otherwise.
|
|
2248
2611
|
*/
|
|
2249
|
-
declare function buildRouteRegistry(
|
|
2250
|
-
/** The route-grammar gesture a `GestureSpec.kind` routes as, or `undefined`
|
|
2251
|
-
* for kinds the grammar has no name for. */
|
|
2252
|
-
declare function routeGestureForSpecKind(kind: GestureSpec['kind']): GestureName | undefined;
|
|
2612
|
+
declare function buildRouteRegistry(entries: readonly Contribution<unknown>[]): RegistryEntry[];
|
|
2253
2613
|
/**
|
|
2254
2614
|
* Every route string one `GestureSpec` declares, in route-grammar notation.
|
|
2255
2615
|
*
|
|
@@ -2273,9 +2633,9 @@ interface Conflict {
|
|
|
2273
2633
|
arg: string | undefined;
|
|
2274
2634
|
target: string | undefined;
|
|
2275
2635
|
modifiers: ParsedModifiers;
|
|
2276
|
-
/**
|
|
2277
|
-
* construction
|
|
2278
|
-
|
|
2636
|
+
/** The ids of every entry that declared the same tuple. At least 2 by
|
|
2637
|
+
* construction, in input order. */
|
|
2638
|
+
ownerIds: string[];
|
|
2279
2639
|
}
|
|
2280
2640
|
/** Detect exact-tuple overlaps across a tool registration set.
|
|
2281
2641
|
*
|
|
@@ -2299,6 +2659,12 @@ interface Conflict {
|
|
|
2299
2659
|
* right way to be wrong here: this check has to be silent when nothing is
|
|
2300
2660
|
* wrong or nobody will keep it on.
|
|
2301
2661
|
*
|
|
2662
|
+
* - Two actions whose `eligible` rules can never hold together — each
|
|
2663
|
+
* mode's own Escape exit, say. See {@link rulesExclusive}.
|
|
2664
|
+
*
|
|
2665
|
+
* - Two bindings whose `opts.views` keep them from being live and equally
|
|
2666
|
+
* ranked in any one view. See {@link viewsTie}.
|
|
2667
|
+
*
|
|
2302
2668
|
* Note that two bindings sharing a tuple on the SAME tool are now possible
|
|
2303
2669
|
* (bindings are an array, where phase tables were objects with unique
|
|
2304
2670
|
* keys) — so a conflict may name one tool twice.
|
|
@@ -2307,19 +2673,23 @@ interface Conflict {
|
|
|
2307
2673
|
* over-reports, because registry tools take turns in the active slot and
|
|
2308
2674
|
* can't collide with each other — see {@link findScopedConflicts}.
|
|
2309
2675
|
*/
|
|
2310
|
-
declare function findConflicts(
|
|
2676
|
+
declare function findConflicts(entries: readonly Contribution<unknown>[]): Conflict[];
|
|
2311
2677
|
/**
|
|
2312
2678
|
* The tool scopes as the dispatcher sees them — which is what decides whether
|
|
2313
2679
|
* two same-tuple bindings can actually collide.
|
|
2314
2680
|
*/
|
|
2315
2681
|
interface ToolScopes {
|
|
2316
2682
|
/** Tools eligible for the active slot, keyed by id or as a flat list. */
|
|
2317
|
-
registry: readonly
|
|
2318
|
-
/** Always-on
|
|
2319
|
-
ambient?: readonly
|
|
2683
|
+
registry: readonly Contribution<unknown>[] | Readonly<Record<string, Contribution<unknown>>>;
|
|
2684
|
+
/** Always-on entries. Every one of these is live at once. */
|
|
2685
|
+
ambient?: readonly Contribution<unknown>[];
|
|
2320
2686
|
/** Registered actions. Their `defaultBinding`s assemble at ambient scope
|
|
2321
2687
|
* (hotkey scope when `Action.scope` says so), alongside the tools above. */
|
|
2322
2688
|
actions?: readonly Action[];
|
|
2689
|
+
/** Every mode the canvas can be in (`activeModeOf` builds one from a
|
|
2690
|
+
* definition). Two gated actions conflict only when some mode lets both
|
|
2691
|
+
* rules hold. Default: the kit's `DEFAULT_MODES`. */
|
|
2692
|
+
modes?: readonly ActiveMode[];
|
|
2323
2693
|
}
|
|
2324
2694
|
/**
|
|
2325
2695
|
* Detect reachable route conflicts in an assembled tool set and report each one.
|
|
@@ -2349,9 +2719,11 @@ declare function reportRouteConflicts(scopes: ToolScopes, warn?: (message: strin
|
|
|
2349
2719
|
* consumers don't depend on its disappearance.
|
|
2350
2720
|
*/
|
|
2351
2721
|
/**
|
|
2352
|
-
*
|
|
2353
|
-
*
|
|
2354
|
-
*
|
|
2722
|
+
* A key plus a modifier policy, as {@link matchesKeyBinding} tests it against a
|
|
2723
|
+
* `KeyboardEvent`. Every modifier left unset is forbidden, so `{ key: 'a' }`
|
|
2724
|
+
* does not fire on Cmd+A. The matcher ignores `skipInEditable`, `enabled` and
|
|
2725
|
+
* `preventDefault`; those are for whatever attaches the listener. Tools
|
|
2726
|
+
* declaring an activation key use `ToolKeybinding` instead.
|
|
2355
2727
|
*/
|
|
2356
2728
|
interface KeyBinding {
|
|
2357
2729
|
/**
|
|
@@ -2536,5 +2908,25 @@ interface DepSchema {
|
|
|
2536
2908
|
}
|
|
2537
2909
|
/** Every dep name declared by anything that has merged into {@link DepSchema}. */
|
|
2538
2910
|
type DepName = keyof DepSchema;
|
|
2911
|
+
/**
|
|
2912
|
+
* What the kernel drawing these tools takes as an overlay. Routing never reads
|
|
2913
|
+
* an overlay, so it has no type of its own for one; the kernel merges its type
|
|
2914
|
+
* in, the same way and for the same reason as {@link DepSchema}:
|
|
2915
|
+
*
|
|
2916
|
+
* ```ts
|
|
2917
|
+
* declare module '@weasel-js/routing' {
|
|
2918
|
+
* interface OverlaySchema { overlay: RenderLayer<unknown> }
|
|
2919
|
+
* }
|
|
2920
|
+
* ```
|
|
2921
|
+
*
|
|
2922
|
+
* `defineTool` defaults its overlay type to it, so a
|
|
2923
|
+
* tool it builds fits the kernel's `tools` without naming the parameter.
|
|
2924
|
+
*/
|
|
2925
|
+
interface OverlaySchema {
|
|
2926
|
+
}
|
|
2927
|
+
/** The overlay type merged into {@link OverlaySchema}; `unknown` when none is. */
|
|
2928
|
+
type KernelOverlay = OverlaySchema extends {
|
|
2929
|
+
overlay: infer O;
|
|
2930
|
+
} ? O : unknown;
|
|
2539
2931
|
|
|
2540
|
-
export { type
|
|
2932
|
+
export { type GestureBinding as $, type ActionsRegistry as A, type BindingOpts as B, type Contribution as C, type DepRegistry as D, COARSE_TARGET_SCALE as E, type Condition as F, type Conflict as G, type ContributionChrome as H, type ContributionDeps as I, type ContributionRouting as J, type KernelOverlay as K, type LongPressOptions as L, DEFAULT_ALLOWED_CAPABILITIES as M, DEFAULT_DEVICE_PROFILE as N, type OverlayPosition as O, type DebugSink as P, type DetectedDeviceFacts as Q, type RuleCtx as R, ScopeNode as S, type DeviceProfile as T, type DispatchRecord as U, type DispatchRecordInput as V, type DispatcherContext as W, type DragSample as X, type DroppedCandidate as Y, type Eligibility as Z, type EligibilityState as _, type DepName as a, actionItems as a$, type HandleKind as a0, type HitShape as a1, type HotkeyTrigger as a2, INGEST_STRING_MIMES as a3, type ImmediateInvoker as a4, type InvocationCtx as a5, type Invoker as a6, type KeyBinding as a7, LONG_PRESS_MS as a8, type LongPressState as a9, type ResolveOnlyResult as aA, type ResolvedCandidate as aB, type Rule as aC, type ScratchKey as aD, type ScratchStore as aE, type SelectionApi as aF, type SelectionExtendKey as aG, type SelectionMode as aH, type Selector as aI, type SpecificityPart as aJ, TOOL_OFFHAND_ID as aK, type Tool as aL, type ToolBounds as aM, type ToolCtx as aN, type ToolDef as aO, type ToolKeybinding as aP, type ToolLifecycleCtx as aQ, type ToolModifiers as aR, type ToolOffhandBindingSpec as aS, type ToolPresentation as aT, type ToolSlot as aU, type TraceLogEntry as aV, type UiOngoingControl as aW, type View as aX, type ViewportGestureKind as aY, type WalkStep as aZ, actionBindings as a_, type LongPressStore as aa, type MatchOutcome as ab, type MatchResult as ac, type ModeShortcutHandlers as ad, type ModeSwitchLogEntry as ae, type ModifierState as af, NEVER as ag, type NodeId as ah, type OngoingHandle as ai, type OngoingInvoker as aj, type OngoingOverlay as ak, type OverlayRole as al, type OverlaySchema as am, PREDICATE_TARGET as an, type PendingLongPress as ao, type PlacedBy as ap, type Point2 as aq, type PointerSession as ar, type PointerSessionCallbacks as as, type PointerSessionCancelReason as at, type PointerSessionOptions as au, type RankedCandidate as av, type RecordCandidate as aw, type RegistryEntry as ax, type ResizeAnchor as ay, type ResolveAllOptions as az, type DepSchema as b, buildDepsFromRequires as b0, buildRouteRegistry as b1, buildRuleCtx as b2, buildToolOffhandBindings as b3, clientToCanvas as b4, clientToCanvasRect as b5, createDepRegistry as b6, createDispatcher as b7, createLongPressStore as b8, defineTool as b9, matchesKeyBinding as bA, mergeContributions as bB, modeShortcutSpec as bC, modeShortcuts as bD, offhandKeyFor as bE, openPointerSession as bF, pointerGestureId as bG, recordModeSwitch as bH, reportRouteConflicts as bI, resolveDeviceProfile as bJ, resolveParams as bK, routesForSpec as bL, ruleCanHoldIn as bM, rulesExclusive as bN, scopeBindings as bO, scratchKey as bP, setScratch as bQ, targetConsultsAffordance as bR, validateActionDefaultBinding as bS, validateActionId as bT, deleteScratch as ba, describeRule as bb, dlog as bc, evaluate as bd, evaluateEnabled as be, findConflicts as bf, getScratch as bg, isAnchor as bh, isAnchorOrControl as bi, isBody as bj, isControlHandle as bk, isEditableTarget as bl, isEmpty as bm, isResizeHandle as bn, isRotateHandle as bo, isSelectedBody as bp, isTool as bq, isUnselectedBody as br, itemsFromClipboardData as bs, itemsFromDataTransfer as bt, itemsFromFiles as bu, liveScope as bv, makeToolOffhandAction as bw, matchBest as bx, matchSorted as by, matchSortedWithBarred as bz, type Action as c, DepNode as d, type AnyToolOf as e, type ScopedBinding as f, type Dispatcher as g, type AffordanceHit as h, ALWAYS as i, type ActionDeps as j, ActionDisabledReason as k, type ActionDispatch as l, type ActionEnabledResult as m, type ActionEntry as n, type ActionItem as o, type ActionPresentation as p, type ActionSource as q, type ActionVariant as r, type ActionsProp as s, type AnyTool as t, type BindingScope as u, type BindingSource as v, type BodyPredicate as w, type BoundGesture as x, type Bounds as y, type BuildRuleCtxArgs as z };
|