@weasel-js/routing 1.14.2 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-INZ63QGX.js → chunk-VAUJJODS.js} +201 -4
- package/dist/chunk-VAUJJODS.js.map +1 -0
- package/dist/{chunk-EUCNVGJV.js → chunk-Z2BZBJ7W.js} +6 -3
- package/dist/chunk-Z2BZBJ7W.js.map +1 -0
- package/dist/{chunk-HR5F4LBX.js → chunk-ZUAFANMN.js} +31 -10
- package/dist/chunk-ZUAFANMN.js.map +1 -0
- package/dist/{index-DJ7SxEKS.d.ts → index-D-XFwx8D.d.ts} +1281 -1074
- package/dist/index.d.ts +4 -3
- package/dist/index.js +115 -8
- package/dist/index.js.map +1 -1
- package/dist/{internal-CaJ4viHc.d.ts → internal-nqfC0td8.d.ts} +8 -1
- package/dist/internal.d.ts +7 -6
- package/dist/react.d.ts +27 -9
- package/dist/react.js +120 -19
- package/dist/react.js.map +1 -1
- package/dist/testing/registerAction.d.ts +6 -5
- package/dist/testing/registerAction.js +2 -2
- package/package.json +13 -13
- package/dist/chunk-EUCNVGJV.js.map +0 -1
- package/dist/chunk-HR5F4LBX.js.map +0 -1
- package/dist/chunk-INZ63QGX.js.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { History } from '@weasel-js/history';
|
|
2
|
+
import { PrefGroup, PrefLeaf, PrefNumber } from '@weasel-js/prefs';
|
|
2
3
|
import { FramedBox, View, Point } from '@weasel-js/geom';
|
|
3
|
-
import { CursorSpec } from '@weasel-js/cursor';
|
|
4
|
-
import { MenuEntry, Rect, MenuSide, MenuShortcut } from '@weasel-js/menu';
|
|
5
|
-
import { PrefGroup, PrefLeaf } from '@weasel-js/prefs';
|
|
6
4
|
import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
|
|
5
|
+
import { ModifierKey, ClaimableGesture, GestureSpec, IngestItem, InputEvent, TargetSpec, BodyTarget, BindingOverrideInput, BodyKind, KeyHeldSpec, TargetPredicate, KeySpec, ClickSpec, DoubleClickSpec, DragSpec, ContextMenuSpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
|
|
7
6
|
import * as _weasel_js_icons from '@weasel-js/icons';
|
|
8
7
|
import { IconRef } from '@weasel-js/icons';
|
|
8
|
+
import { CursorSpec } from '@weasel-js/cursor';
|
|
9
|
+
import { MenuEntry, Rect, MenuSide, MenuShortcut } from '@weasel-js/menu';
|
|
9
10
|
import * as react from 'react';
|
|
10
11
|
import { ReactNode } from 'react';
|
|
11
12
|
import { Emitter, QueryMatch } from '@weasel-js/registry';
|
|
@@ -169,363 +170,429 @@ type ResizeAnchor = {
|
|
|
169
170
|
y: 'min' | 'max' | 'free';
|
|
170
171
|
};
|
|
171
172
|
|
|
172
|
-
/** The chrome an in-flight gesture paints beside the scene. */
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* What a `polyline` overlay is, in a word — the narrowest thing an action can
|
|
176
|
-
* say about how its run should look without naming a paint.
|
|
177
|
-
*
|
|
178
|
-
* The named roles are the kit's; a painter maps each to a style and falls back
|
|
179
|
-
* to plain chrome for one it does not know, so a package outside core can coin
|
|
180
|
-
* its own and still be drawn.
|
|
181
|
-
*/
|
|
182
|
-
type OverlayRole =
|
|
183
|
-
/** Generic in-flight chrome — the marquee/lasso paint. */
|
|
184
|
-
'chrome'
|
|
185
|
-
/** A cut the gesture would make. */
|
|
186
|
-
| 'cut'
|
|
187
|
-
/** An edge the gesture would author between two points. */
|
|
188
|
-
| 'connector'
|
|
189
|
-
/** The stretch of a path a soft-selection drag reaches. */
|
|
190
|
-
| 'falloff'
|
|
191
|
-
/** What a lock holds a dragged point to — a line, a circle. */
|
|
192
|
-
| 'lock'
|
|
193
|
-
/** What a dragged item would be dropped on: the box it would go into. */
|
|
194
|
-
| 'target'
|
|
195
|
-
/** Where a dragged item would land: a reorder's insertion bar. */
|
|
196
|
-
| 'insert'
|
|
197
|
-
/** Where a dragged item is over and cannot land. */
|
|
198
|
-
| 'refused' | (string & {});
|
|
199
173
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
174
|
+
* Live state read by rule evaluation. Built once per frame on the consuming
|
|
175
|
+
* surface — chrome-caps, the affordance pipeline, the dispatcher's
|
|
176
|
+
* eligibility filter — and discarded.
|
|
203
177
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* gesture some other way can still read it. The painter owns every style
|
|
207
|
-
* decision.
|
|
178
|
+
* Adding a new field is additive: existing rules don't change, new
|
|
179
|
+
* selector atoms can read it.
|
|
208
180
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
y: number;
|
|
218
|
-
};
|
|
219
|
-
shiftHeld: boolean;
|
|
220
|
-
} | {
|
|
221
|
-
kind: 'lasso';
|
|
222
|
-
vertices: ReadonlyArray<{
|
|
223
|
-
x: number;
|
|
224
|
-
y: number;
|
|
225
|
-
}>;
|
|
226
|
-
current: {
|
|
227
|
-
x: number;
|
|
228
|
-
y: number;
|
|
181
|
+
interface RuleCtx {
|
|
182
|
+
readonly focused: boolean;
|
|
183
|
+
readonly selection: readonly NodeId[];
|
|
184
|
+
readonly multiActive: boolean;
|
|
185
|
+
readonly modifiers: ModifierState;
|
|
186
|
+
readonly action: {
|
|
187
|
+
readonly kind: string | null;
|
|
188
|
+
readonly id: string | null;
|
|
229
189
|
};
|
|
230
|
-
|
|
231
|
-
} | {
|
|
232
|
-
/**
|
|
233
|
-
* An open run of world points the gesture is proposing — a cut line, a
|
|
234
|
-
* routed connector. `role` is the whole of what an action says about
|
|
235
|
-
* appearance.
|
|
236
|
-
*/
|
|
237
|
-
kind: 'polyline';
|
|
238
|
-
/** At least two points. Anything shorter is dropped in resolution. */
|
|
239
|
-
points: ReadonlyArray<{
|
|
240
|
-
x: number;
|
|
241
|
-
y: number;
|
|
242
|
-
}>;
|
|
243
|
-
/** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
|
|
244
|
-
role?: OverlayRole;
|
|
245
|
-
} | {
|
|
246
|
-
/**
|
|
247
|
-
* A round brush dragged through world points: the area it has swept so
|
|
248
|
-
* far, and its footprint at the last point. The radius is in screen
|
|
249
|
-
* pixels, so the footprint keeps its size at every zoom.
|
|
250
|
-
*/
|
|
251
|
-
kind: 'brush';
|
|
252
|
-
/** The brush's path so far, at least one point; the last is where it is now. */
|
|
253
|
-
points: ReadonlyArray<{
|
|
254
|
-
x: number;
|
|
255
|
-
y: number;
|
|
256
|
-
}>;
|
|
257
|
-
radiusPx: number;
|
|
258
|
-
} | {
|
|
190
|
+
readonly hover: NodeId | null;
|
|
259
191
|
/**
|
|
260
|
-
*
|
|
261
|
-
* via `previewIds()`/`previewPose()`, so insert paints its preview
|
|
262
|
-
* through the overlay instead.
|
|
192
|
+
* How far the viewport is zoomed in, as one number — `1` at native scale.
|
|
263
193
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
kind: 'insertPreview';
|
|
270
|
-
/** Which preview the dispatcher's overlay draws. The kit's own shape
|
|
271
|
-
* kinds are the ones it knows how to render; a consumer-defined kind
|
|
272
|
-
* is legal and simply gets no live preview. */
|
|
273
|
-
shape: string;
|
|
274
|
-
bounds: {
|
|
275
|
-
x: number;
|
|
276
|
-
y: number;
|
|
277
|
-
width: number;
|
|
278
|
-
height: number;
|
|
279
|
-
};
|
|
280
|
-
extras: unknown;
|
|
281
|
-
/** World-space point to paint a small "anchor" dot at, marking the
|
|
282
|
-
* click point — for radial shapes (polygon/star), where no vertex
|
|
283
|
-
* sits on it, and for any shape in center mode. */
|
|
284
|
-
anchorPoint?: {
|
|
285
|
-
x: number;
|
|
286
|
-
y: number;
|
|
287
|
-
};
|
|
288
|
-
/** World affines the shape will also be drawn under once it lands, by
|
|
289
|
-
* the container it is drawn into: the preview is painted under each. */
|
|
290
|
-
copies?: readonly (readonly [number, number, number, number, number, number])[];
|
|
291
|
-
} | {
|
|
292
|
-
/**
|
|
293
|
-
* Why the gesture cannot land where it is, in words for the user: a
|
|
294
|
-
* refusal's reason. A surface shows `text` beside the pointer, which
|
|
295
|
-
* `at` is, in world coordinates.
|
|
194
|
+
* A scalar rather than the `View` this used to carry, because `zoomAtLeast`
|
|
195
|
+
* is the only selector that ever read one and a number is all it needs. A
|
|
196
|
+
* host whose viewport is a camera has no `View` to hand over and would
|
|
197
|
+
* otherwise have to invent one to use eligibility at all; it omits this
|
|
198
|
+
* instead, and `zoomAtLeast` declines rather than matching on a fiction.
|
|
296
199
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
200
|
+
readonly zoom?: number;
|
|
201
|
+
/** Active mode id. `'normal'` when no non-default mode is engaged. */
|
|
202
|
+
readonly mode: string;
|
|
203
|
+
/** Capability tags allowed by the active mode (the union of
|
|
204
|
+
* `ModeDefinition.allows` plus implicit tags). The `capability:`
|
|
205
|
+
* selector reads this to determine whether a tag is permitted. */
|
|
206
|
+
readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
207
|
+
/** Whether the current selection may be resized. `<SceneCanvas>` folds
|
|
208
|
+
* `selectTool.resize.resizable` over the selection (true only when every
|
|
209
|
+
* selected node is resizable). Read by the `resizable:` selector to gate
|
|
210
|
+
* `selection.resize-handles`. Absent (legacy ctx builders) is treated as
|
|
211
|
+
* resizable — back-compat: handles show unless a consumer opts a node out. */
|
|
212
|
+
readonly selectionResizable?: boolean;
|
|
213
|
+
/** Whether a path is currently in anchor-edit mode. Read by the
|
|
214
|
+
* `editingAnchors:` selector, which gates the path-edit chrome.
|
|
215
|
+
*
|
|
216
|
+
* This is deliberately a fact about state, not about permission: the
|
|
217
|
+
* anchor overlay and the anchor hit-test must agree, and the thing they
|
|
218
|
+
* must agree on is "is there an edited path right now", which no
|
|
219
|
+
* capability or mode id answers. A mode that allows `edits-anchors`
|
|
220
|
+
* with nothing being edited should draw no anchors. Absent is treated
|
|
221
|
+
* as false. */
|
|
222
|
+
readonly editingAnchors?: boolean;
|
|
223
|
+
/** Device facts — pointer coarseness, hover capability, density.
|
|
224
|
+
*
|
|
225
|
+
* Absent (legacy ctx builders) is treated as
|
|
226
|
+
* {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
|
|
227
|
+
* density 1. That is what the kit assumed before this field existed, so
|
|
228
|
+
* an absent profile is behavior-preserving by construction. */
|
|
229
|
+
readonly device?: DeviceProfile;
|
|
230
|
+
/** The view the input being dispatched landed in — a view id, or `null`
|
|
231
|
+
* for the surface's own camera. The dispatcher fills it; absent anywhere
|
|
232
|
+
* else a rule is evaluated. */
|
|
233
|
+
readonly viewId?: string | null;
|
|
234
|
+
/** Chrome ids the active tool hides (`ToolDef.hides`): never visible,
|
|
235
|
+
* whatever their rules say. Absent hides nothing. */
|
|
236
|
+
readonly hiddenChrome?: ReadonlySet<string>;
|
|
321
237
|
}
|
|
322
|
-
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* A reader for an action's own roles. It answers from `ctx.roles`, which the
|
|
330
|
-
* dispatcher resolves on every event, and falls back to the role's default
|
|
331
|
-
* keys where nothing resolved them: an invoker called directly, or a context
|
|
332
|
-
* a cancel-all built with no event.
|
|
333
|
-
*/
|
|
334
|
-
declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
|
|
335
|
-
roles?: Readonly<Record<string, boolean>>;
|
|
238
|
+
/** The live state a `RuleCtx` is assembled from — the chrome context plus
|
|
239
|
+
* what the active mode allows. */
|
|
240
|
+
interface BuildRuleCtxArgs {
|
|
241
|
+
focused: boolean;
|
|
242
|
+
selection: readonly NodeId[];
|
|
243
|
+
multiActive: boolean;
|
|
336
244
|
modifiers: ModifierState;
|
|
337
|
-
|
|
338
|
-
|
|
245
|
+
action: {
|
|
246
|
+
kind: string | null;
|
|
247
|
+
id: string | null;
|
|
248
|
+
};
|
|
249
|
+
hover: NodeId | null;
|
|
250
|
+
/** See {@link RuleCtx.zoom}. */
|
|
251
|
+
zoom?: number;
|
|
252
|
+
mode: string;
|
|
253
|
+
allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
254
|
+
/** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
|
|
255
|
+
selectionResizable?: boolean;
|
|
256
|
+
/** Optional — omitted means "no path is being anchor-edited". */
|
|
257
|
+
editingAnchors?: boolean;
|
|
258
|
+
/** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
|
|
259
|
+
device?: DeviceProfile;
|
|
260
|
+
/** Optional — omitted hides nothing. See {@link RuleCtx.hiddenChrome}. */
|
|
261
|
+
hiddenChrome?: ReadonlySet<string>;
|
|
262
|
+
}
|
|
339
263
|
/**
|
|
340
|
-
*
|
|
264
|
+
* Capability set to assume when no mode registry is wired.
|
|
341
265
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
266
|
+
* "No modes configured" means *everything the default mode permits*, not
|
|
267
|
+
* *nothing permitted*. The empty set is the wrong default: it makes every
|
|
268
|
+
* `capability:` rule evaluate false, so a kit default written the
|
|
269
|
+
* recommended way (capability rules outlive new modes; mode rules have to
|
|
270
|
+
* be edited) would silently hide its chrome for every consumer that hasn't
|
|
271
|
+
* opted into modality. `NORMAL.allows` plus the implicit tags is what a
|
|
272
|
+
* mode-less canvas actually behaves like.
|
|
345
273
|
*/
|
|
274
|
+
declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
|
|
275
|
+
/** Gather the current canvas and mode state into the context that action
|
|
276
|
+
* eligibility and chrome-visibility rules are evaluated against. */
|
|
277
|
+
declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
|
|
346
278
|
|
|
347
|
-
/** A 2D point in either world or screen coordinates; geom's `Point`. */
|
|
348
|
-
type Point2 = Point;
|
|
349
279
|
/**
|
|
350
|
-
* A
|
|
351
|
-
*
|
|
352
|
-
* `@weasel-js/kernel3d` scene, passes identity, so there it is the pane's
|
|
353
|
-
* screen point and the deps reading it cast a ray through the camera they
|
|
354
|
-
* close over. Every world point the dispatcher hands out, and every dep
|
|
355
|
-
* payload it reaches, is one of these.
|
|
280
|
+
* A selector is a conjunction of key/value tests. Multiple keys at the same
|
|
281
|
+
* level AND together. Each key maps to a selector primitive in the evaluator.
|
|
356
282
|
*/
|
|
357
|
-
|
|
283
|
+
interface Selector {
|
|
284
|
+
selection?: {
|
|
285
|
+
is?: number;
|
|
286
|
+
atLeast?: number;
|
|
287
|
+
empty?: boolean;
|
|
288
|
+
};
|
|
289
|
+
mode?: string | {
|
|
290
|
+
not: string;
|
|
291
|
+
} | {
|
|
292
|
+
in: readonly string[];
|
|
293
|
+
};
|
|
294
|
+
capability?: CapabilityTag | readonly CapabilityTag[] | {
|
|
295
|
+
in: readonly CapabilityTag[];
|
|
296
|
+
} | {
|
|
297
|
+
not: CapabilityTag;
|
|
298
|
+
};
|
|
299
|
+
gesturing?: boolean;
|
|
300
|
+
actionIs?: string;
|
|
301
|
+
modifierHeld?: keyof ModifierState;
|
|
302
|
+
focused?: boolean;
|
|
303
|
+
hovering?: boolean;
|
|
304
|
+
hoveringSelected?: boolean;
|
|
305
|
+
zoomAtLeast?: number;
|
|
306
|
+
/** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
|
|
307
|
+
* mode. Absent flag is treated as `false`. */
|
|
308
|
+
editingAnchors?: boolean;
|
|
309
|
+
/** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
|
|
310
|
+
* (resizable), so `{ resizable: true }` passes for legacy ctx builders
|
|
311
|
+
* that don't compute it. */
|
|
312
|
+
resizable?: boolean;
|
|
313
|
+
/** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
|
|
314
|
+
* (touch, most styluses). Absent device is treated as `false`. */
|
|
315
|
+
coarsePointer?: boolean;
|
|
316
|
+
/** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
|
|
317
|
+
* device is treated as `true`. */
|
|
318
|
+
canHover?: boolean;
|
|
319
|
+
}
|
|
358
320
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
321
|
+
* Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
|
|
322
|
+
* over `Selector` leaves. `when` is the escape hatch — its closure is
|
|
323
|
+
* opaque to introspection and should be avoided when a declarative form
|
|
324
|
+
* exists. Empty `all` is true; empty `any` is false.
|
|
325
|
+
*/
|
|
326
|
+
type Rule = Selector | {
|
|
327
|
+
all: readonly Rule[];
|
|
328
|
+
} | {
|
|
329
|
+
any: readonly Rule[];
|
|
330
|
+
} | {
|
|
331
|
+
not: Rule;
|
|
332
|
+
} | {
|
|
333
|
+
when: (ctx: RuleCtx) => boolean;
|
|
334
|
+
};
|
|
335
|
+
/** Constant rules. Kept here so they have a single source. */
|
|
336
|
+
declare const ALWAYS: Rule;
|
|
337
|
+
/** A rule that never passes. `any` of nothing is false. */
|
|
338
|
+
declare const NEVER: Rule;
|
|
339
|
+
/**
|
|
340
|
+
* Render a {@link Rule} as a short human-readable string, for inspectors and
|
|
341
|
+
* diagnostics that need to say WHICH rule decided something.
|
|
361
342
|
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
343
|
+
* ```
|
|
344
|
+
* { capability: 'edits-page' } → capability:edits-page
|
|
345
|
+
* { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
|
|
346
|
+
* { not: { selection: { empty: true } } } → not(selection:empty=true)
|
|
347
|
+
* { when: function hasPath() { … } } → when(hasPath)
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* Never throws and always terminates.
|
|
369
351
|
*/
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
|
|
394
|
-
* active tool's cursor. */
|
|
395
|
-
cursor?: CursorSpec;
|
|
396
|
-
/**
|
|
397
|
-
* Free-form payload from whatever produced the hit, carried through to the
|
|
398
|
-
* matching action untouched. Kit affordances leave it unset; it exists for
|
|
399
|
-
* a registered layer's own chrome, where the hit-test already resolved
|
|
400
|
-
* *which* of its pieces was hit.
|
|
401
|
-
*/
|
|
402
|
-
payload?: unknown;
|
|
352
|
+
declare function describeRule(rule: Rule): string;
|
|
353
|
+
/** Evaluate a rule against a context. Pure, and cheap enough to run per
|
|
354
|
+
* frame for every piece of chrome. */
|
|
355
|
+
declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
|
|
356
|
+
/**
|
|
357
|
+
* Composable eligibility predicate with a fluent surface. `cond(ctx)`
|
|
358
|
+
* evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
|
|
359
|
+
* Conditions wrapping new trees.
|
|
360
|
+
*
|
|
361
|
+
* **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
|
|
362
|
+
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
|
|
363
|
+
* disjunction, name the subexpression or use the top-level `or(...)`.
|
|
364
|
+
*/
|
|
365
|
+
interface Condition {
|
|
366
|
+
(ctx: RuleCtx): boolean;
|
|
367
|
+
readonly rule: Rule;
|
|
368
|
+
/** `this && other` */
|
|
369
|
+
and(other: Condition | Rule): Condition;
|
|
370
|
+
/** `this || other` */
|
|
371
|
+
or(other: Condition | Rule): Condition;
|
|
372
|
+
/** `this && !other` */
|
|
373
|
+
andNot(other: Condition | Rule): Condition;
|
|
374
|
+
/** `this || !other` */
|
|
375
|
+
orNot(other: Condition | Rule): Condition;
|
|
403
376
|
}
|
|
377
|
+
|
|
404
378
|
/**
|
|
405
|
-
*
|
|
406
|
-
* stylus state the originating `PointerEvent` carried.
|
|
379
|
+
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
407
380
|
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
381
|
+
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
382
|
+
* about the selector values themselves (two different `mode`s, an empty
|
|
383
|
+
* selection against a non-empty one, `focused: true` against `false`) and
|
|
384
|
+
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
385
|
+
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
386
|
+
* that share a tuple but are never eligible together.
|
|
410
387
|
*/
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
388
|
+
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
389
|
+
/**
|
|
390
|
+
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
391
|
+
* `capability` tests are settled by the mode; every other test could go either
|
|
392
|
+
* way, so `false` is a proof and `true` means "may hold".
|
|
393
|
+
*/
|
|
394
|
+
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
395
|
+
|
|
396
|
+
/** The detected half of a profile — everything except the derived scale. */
|
|
397
|
+
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
398
|
+
/**
|
|
399
|
+
* Handle/hit multiplier applied on a coarse pointer.
|
|
400
|
+
*
|
|
401
|
+
* 8px handle → 14px; 24px rotation distance → 42px. Counting the
|
|
402
|
+
* surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
|
|
403
|
+
* minimum-touch-target band. One constant so it is tunable in one place.
|
|
404
|
+
*/
|
|
405
|
+
declare const COARSE_TARGET_SCALE = 1.75;
|
|
406
|
+
/**
|
|
407
|
+
* Fold consumer overrides over detected facts and derive `targetScale`.
|
|
408
|
+
*
|
|
409
|
+
* `targetScale` is re-derived AFTER the merge, so an override of
|
|
410
|
+
* `coarsePointer` alone scales the chrome as expected. An explicit
|
|
411
|
+
* `targetScale` override wins over the derivation.
|
|
412
|
+
*/
|
|
413
|
+
declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
|
|
414
|
+
/**
|
|
415
|
+
* Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
|
|
416
|
+
* absent-means value for `RuleCtx.device`.
|
|
417
|
+
*
|
|
418
|
+
* Derived from `resolveDeviceProfile` rather than hand-written, so it can
|
|
419
|
+
* never drift from what the function would compute for the same facts. A
|
|
420
|
+
* mouse-like device is the safe default: it is what the kit has always
|
|
421
|
+
* assumed, so an absent profile changes nothing for existing consumers.
|
|
422
|
+
*/
|
|
423
|
+
declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Modifier roles: the modifiers an ongoing action reads while it runs, named
|
|
427
|
+
* by what they do rather than which key they are, so a binding or a tuning can
|
|
428
|
+
* move one to another key without forking the action.
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
|
|
432
|
+
* on a Mac and Ctrl elsewhere. An empty list never holds it. */
|
|
433
|
+
type RoleKeys = readonly ModifierKey[];
|
|
434
|
+
/** One modifier an action reads mid-gesture. */
|
|
435
|
+
interface ModifierRole {
|
|
436
|
+
label: string;
|
|
437
|
+
/** The keys that hold it unless a tuning or a binding says otherwise. */
|
|
438
|
+
keys: RoleKeys;
|
|
418
439
|
}
|
|
419
|
-
/**
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
*/
|
|
434
|
-
screen?: Point2;
|
|
440
|
+
/** An action's roles by id, e.g. `'handle.holdAngle'`. */
|
|
441
|
+
type ModifierRoles = Readonly<Record<string, ModifierRole>>;
|
|
442
|
+
/** Keys a tuning or a binding gives some of an action's roles. */
|
|
443
|
+
type RoleRebinds = Readonly<Record<string, RoleKeys>>;
|
|
444
|
+
/** Whether any of `keys` is held. */
|
|
445
|
+
declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
|
|
446
|
+
/**
|
|
447
|
+
* A reader for an action's own roles. It answers from `ctx.roles`, which the
|
|
448
|
+
* dispatcher resolves on every event, and falls back to the role's default
|
|
449
|
+
* keys where nothing resolved them: an invoker called directly, or a context
|
|
450
|
+
* a cancel-all built with no event.
|
|
451
|
+
*/
|
|
452
|
+
declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
|
|
453
|
+
roles?: Readonly<Record<string, boolean>>;
|
|
435
454
|
modifiers: ModifierState;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
centroid: Point2;
|
|
481
|
-
/** Distance between the two primary pointers, in CSS pixels. */
|
|
482
|
-
spread: number;
|
|
483
|
-
/** Degrees the line between the two primary pointers has turned since
|
|
484
|
-
* the gesture began, clockwise positive. It keeps counting past a half
|
|
485
|
-
* turn. Zero where the events carry no `MultiTouchEvent.angle`. */
|
|
486
|
-
rotation: number;
|
|
487
|
-
/**
|
|
488
|
-
* Pinch-zoom geometry, populated while a multitouch handle is in flight.
|
|
489
|
-
* `startSpread` is the spread at the moment the gesture began.
|
|
490
|
-
*/
|
|
491
|
-
pinch?: {
|
|
492
|
-
startSpread: number;
|
|
493
|
-
currentSpread: number;
|
|
494
|
-
/** Canvas-local, same as the enclosing `centroid`. */
|
|
495
|
-
centroid: Point2;
|
|
496
|
-
};
|
|
455
|
+
}, id: keyof R & string) => boolean;
|
|
456
|
+
|
|
457
|
+
/** The chrome an in-flight gesture paints beside the scene. */
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* What a `polyline` overlay is, in a word — the narrowest thing an action can
|
|
461
|
+
* say about how its run should look without naming a paint.
|
|
462
|
+
*
|
|
463
|
+
* The named roles are the kit's; a painter maps each to a style and falls back
|
|
464
|
+
* to plain chrome for one it does not know, so a package outside core can coin
|
|
465
|
+
* its own and still be drawn.
|
|
466
|
+
*/
|
|
467
|
+
type OverlayRole =
|
|
468
|
+
/** Generic in-flight chrome — the marquee/lasso paint. */
|
|
469
|
+
'chrome'
|
|
470
|
+
/** A cut the gesture would make. */
|
|
471
|
+
| 'cut'
|
|
472
|
+
/** An edge the gesture would author between two points. */
|
|
473
|
+
| 'connector'
|
|
474
|
+
/** The stretch of a path a soft-selection drag reaches. */
|
|
475
|
+
| 'falloff'
|
|
476
|
+
/** What a lock holds a dragged point to — a line, a circle. */
|
|
477
|
+
| 'lock'
|
|
478
|
+
/** What a dragged item would be dropped on: the box it would go into. */
|
|
479
|
+
| 'target'
|
|
480
|
+
/** Where a dragged item would land: a reorder's insertion bar. */
|
|
481
|
+
| 'insert'
|
|
482
|
+
/** Where a dragged item is over and cannot land. */
|
|
483
|
+
| 'refused' | (string & {});
|
|
484
|
+
/**
|
|
485
|
+
* What an in-flight gesture proposes to paint *beside* the scene, returned by
|
|
486
|
+
* `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
|
|
487
|
+
* shape being dragged out, the line a slice would cut along.
|
|
488
|
+
*
|
|
489
|
+
* Every variant is world geometry and nothing else — no `DrawCommand`, no
|
|
490
|
+
* color, no coordinate space to choose — so a surface rendering the same
|
|
491
|
+
* gesture some other way can still read it. The painter owns every style
|
|
492
|
+
* decision.
|
|
493
|
+
*/
|
|
494
|
+
type OngoingOverlay = {
|
|
495
|
+
kind: 'marquee';
|
|
496
|
+
start: {
|
|
497
|
+
x: number;
|
|
498
|
+
y: number;
|
|
497
499
|
};
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
current: {
|
|
501
|
+
x: number;
|
|
502
|
+
y: number;
|
|
501
503
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
504
|
+
shiftHeld: boolean;
|
|
505
|
+
} | {
|
|
506
|
+
kind: 'lasso';
|
|
507
|
+
vertices: ReadonlyArray<{
|
|
508
|
+
x: number;
|
|
509
|
+
y: number;
|
|
510
|
+
}>;
|
|
511
|
+
current: {
|
|
512
|
+
x: number;
|
|
513
|
+
y: number;
|
|
507
514
|
};
|
|
515
|
+
shiftHeld: boolean;
|
|
516
|
+
} | {
|
|
508
517
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
518
|
+
* An open run of world points the gesture is proposing — a cut line, a
|
|
519
|
+
* routed connector. `role` is the whole of what an action says about
|
|
520
|
+
* appearance.
|
|
512
521
|
*/
|
|
513
|
-
|
|
522
|
+
kind: 'polyline';
|
|
523
|
+
/** At least two points. Anything shorter is dropped in resolution. */
|
|
524
|
+
points: ReadonlyArray<{
|
|
525
|
+
x: number;
|
|
526
|
+
y: number;
|
|
527
|
+
}>;
|
|
528
|
+
/** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
|
|
529
|
+
role?: OverlayRole;
|
|
530
|
+
} | {
|
|
514
531
|
/**
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
* params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
532
|
+
* A round brush dragged through world points: the area it has swept so
|
|
533
|
+
* far, and its footprint at the last point. The radius is in screen
|
|
534
|
+
* pixels, so the footprint keeps its size at every zoom.
|
|
519
535
|
*/
|
|
520
|
-
|
|
536
|
+
kind: 'brush';
|
|
537
|
+
/** The brush's path so far, at least one point; the last is where it is now. */
|
|
538
|
+
points: ReadonlyArray<{
|
|
539
|
+
x: number;
|
|
540
|
+
y: number;
|
|
541
|
+
}>;
|
|
542
|
+
radiusPx: number;
|
|
543
|
+
} | {
|
|
521
544
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
545
|
+
* Live insert-drag preview. Pre-commit there is no scene node to ghost
|
|
546
|
+
* via `previewIds()`/`previewPose()`, so insert paints its preview
|
|
547
|
+
* through the overlay instead.
|
|
548
|
+
*
|
|
549
|
+
* `bounds` is the AABB of the current drag (start/current normalized).
|
|
550
|
+
* `extras` is the per-kind extras the action already collected; the
|
|
551
|
+
* overlay renderer narrows on `shape` and casts the field shape it
|
|
552
|
+
* expects.
|
|
526
553
|
*/
|
|
527
|
-
|
|
528
|
-
|
|
554
|
+
kind: 'insertPreview';
|
|
555
|
+
/** Which preview the dispatcher's overlay draws. The kit's own shape
|
|
556
|
+
* kinds are the ones it knows how to render; a consumer-defined kind
|
|
557
|
+
* is legal and simply gets no live preview. */
|
|
558
|
+
shape: string;
|
|
559
|
+
bounds: {
|
|
560
|
+
x: number;
|
|
561
|
+
y: number;
|
|
562
|
+
width: number;
|
|
563
|
+
height: number;
|
|
564
|
+
};
|
|
565
|
+
extras: unknown;
|
|
566
|
+
/** World-space point to paint a small "anchor" dot at, marking the
|
|
567
|
+
* click point — for radial shapes (polygon/star), where no vertex
|
|
568
|
+
* sits on it, and for any shape in center mode. */
|
|
569
|
+
anchorPoint?: {
|
|
570
|
+
x: number;
|
|
571
|
+
y: number;
|
|
572
|
+
};
|
|
573
|
+
/** World affines the shape will also be drawn under once it lands, by
|
|
574
|
+
* the container it is drawn into: the preview is painted under each. */
|
|
575
|
+
copies?: readonly (readonly [number, number, number, number, number, number])[];
|
|
576
|
+
} | {
|
|
577
|
+
/**
|
|
578
|
+
* Why the gesture cannot land where it is, in words for the user: a
|
|
579
|
+
* refusal's reason. A surface shows `text` beside the pointer, which
|
|
580
|
+
* `at` is, in world coordinates.
|
|
581
|
+
*/
|
|
582
|
+
kind: 'reason';
|
|
583
|
+
at: {
|
|
584
|
+
x: number;
|
|
585
|
+
y: number;
|
|
586
|
+
};
|
|
587
|
+
text: string;
|
|
588
|
+
};
|
|
589
|
+
/** A handle's overlays as a list: empty when it has none. */
|
|
590
|
+
declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* The options a gesture binding hands the action it fires, and reading their params.
|
|
594
|
+
*/
|
|
595
|
+
|
|
529
596
|
/** Per-invocation options the dispatcher reads from a `GestureBinding`'s
|
|
530
597
|
* `opts` field and passes to `OngoingInvoker.start`. */
|
|
531
598
|
interface BindingOpts {
|
|
@@ -568,6 +635,8 @@ interface BindingOpts {
|
|
|
568
635
|
* For a key binding: live while the pointer is over the surface, whichever
|
|
569
636
|
* canvas holds the keys, and only then. For a key that means "here" — a
|
|
570
637
|
* magnifier's peek — on a page with more than one surface that hears it.
|
|
638
|
+
* A held key follows the pointer: one already down when it arrives presses
|
|
639
|
+
* the binding, and leaving releases it.
|
|
571
640
|
*/
|
|
572
641
|
over?: boolean;
|
|
573
642
|
/**
|
|
@@ -576,69 +645,279 @@ interface BindingOpts {
|
|
|
576
645
|
*/
|
|
577
646
|
roles?: RoleRebinds;
|
|
578
647
|
/** Screen travel, in CSS pixels, a drag makes before this binding's ongoing action starts, over the
|
|
579
|
-
* action's own `dragThresholdPx`. See
|
|
648
|
+
* action's own `dragThresholdPx`. See `OngoingInvoker.dragThresholdPx`. */
|
|
580
649
|
dragThresholdPx?: number;
|
|
581
650
|
}
|
|
582
651
|
/** Resolve `BindingOpts.params` to a concrete record (calling the thunk if
|
|
583
652
|
* needed). Returns `undefined` for absent params. */
|
|
584
653
|
declare function resolveParams(params: BindingOpts['params']): Record<string, unknown> | undefined;
|
|
585
|
-
/** The deps an action receives, composed per call from what it `requires`.
|
|
586
|
-
* Each name resolves to its {@link DepSchema} entry, and is absent when no
|
|
587
|
-
* source registered it; a name nothing has declared passes through as
|
|
588
|
-
* `unknown`. */
|
|
589
|
-
interface ActionDeps extends Partial<DepSchema> {
|
|
590
|
-
[k: string]: unknown;
|
|
591
|
-
}
|
|
592
654
|
|
|
593
|
-
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
*
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
* the
|
|
632
|
-
*
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
655
|
+
/**
|
|
656
|
+
* Invoker — pluggable invocation strategy for an Action.
|
|
657
|
+
*
|
|
658
|
+
* An Action's `invoker` field describes how it's run. `immediate` invokers
|
|
659
|
+
* fire once (delete, align, undo). `ongoing` invokers open a phase machine
|
|
660
|
+
* driven by the dispatcher (move, resize, pinch-zoom).
|
|
661
|
+
*/
|
|
662
|
+
|
|
663
|
+
/** A 2D point in either world or screen coordinates; geom's `Point`. */
|
|
664
|
+
type Point2 = Point;
|
|
665
|
+
/**
|
|
666
|
+
* A point in the space the dispatcher's `clientToWorld` maps the pointer into:
|
|
667
|
+
* the world, on a 2D canvas. A host with a camera of its own, such as a
|
|
668
|
+
* `@weasel-js/kernel3d` scene, passes identity, so there it is the pane's
|
|
669
|
+
* screen point and the deps reading it cast a ray through the camera they
|
|
670
|
+
* close over. Every world point the dispatcher hands out, and every dep
|
|
671
|
+
* payload it reaches, is one of these.
|
|
672
|
+
*/
|
|
673
|
+
type WorldPoint = Point2;
|
|
674
|
+
/**
|
|
675
|
+
* Which UI affordance was hit at pointerdown. Populated by the dispatcher when
|
|
676
|
+
* the `affordanceAt` thunk is provided to `useGestureDispatcher`.
|
|
677
|
+
*
|
|
678
|
+
* `kind` is a discriminator string:
|
|
679
|
+
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
680
|
+
* `'handle:bottom-right'` — corner resize handles.
|
|
681
|
+
* - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
|
|
682
|
+
* edge resize handles.
|
|
683
|
+
* - `'rotate-handle'` — the rotation affordance.
|
|
684
|
+
* - `'anchor:N'` — a path anchor at index N.
|
|
685
|
+
*/
|
|
686
|
+
interface AffordanceHit {
|
|
687
|
+
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
688
|
+
kind: string;
|
|
689
|
+
/** Id of whatever produced this hit — a kit affordance's `id`, or the
|
|
690
|
+
* registered layer's id. */
|
|
691
|
+
owner?: string;
|
|
692
|
+
/** `'exclusive'` means no binding may act on this point unless its target
|
|
693
|
+
* consults the affordance. `'shared'` (the default) competes on scope and
|
|
694
|
+
* specificity as bindings always have. */
|
|
695
|
+
strength?: 'exclusive' | 'shared';
|
|
696
|
+
/** Which gestures an exclusive claim bars. Omitted bars all of them. */
|
|
697
|
+
claimedKinds?: readonly ClaimableGesture[];
|
|
698
|
+
/** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
|
|
699
|
+
fixedPoint?: {
|
|
700
|
+
x: number;
|
|
701
|
+
y: number;
|
|
702
|
+
};
|
|
703
|
+
/** Which nodes this affordance belongs to. */
|
|
704
|
+
targetIds?: string[];
|
|
705
|
+
/** Set only when `kind` matches `'handle:*'`: which corner or edge stays
|
|
706
|
+
* fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
|
|
707
|
+
anchor?: ResizeAnchor;
|
|
708
|
+
/** Cursor to show while the pointer hovers this affordance (no gesture in
|
|
709
|
+
* flight). Unset falls through to action-cursor prediction, then to the
|
|
710
|
+
* active tool's cursor. */
|
|
711
|
+
cursor?: CursorSpec;
|
|
712
|
+
/**
|
|
713
|
+
* Free-form payload from whatever produced the hit, carried through to the
|
|
714
|
+
* matching action untouched. Kit affordances leave it unset; it exists for
|
|
715
|
+
* a registered layer's own chrome, where the hit-test already resolved
|
|
716
|
+
* *which* of its pieces was hit.
|
|
717
|
+
*/
|
|
718
|
+
payload?: unknown;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* One accumulated point on a drag trail: world-space position plus whatever
|
|
722
|
+
* stylus state the originating `PointerEvent` carried.
|
|
723
|
+
*
|
|
724
|
+
* The stylus fields are absent for mouse/touch on browsers that don't report
|
|
725
|
+
* them, and for synthetic events.
|
|
726
|
+
*/
|
|
727
|
+
interface DragSample extends WorldPoint {
|
|
728
|
+
/** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
|
|
729
|
+
pressure?: number;
|
|
730
|
+
/** Degrees, ±90. Zero for mouse/touch. */
|
|
731
|
+
tiltX?: number;
|
|
732
|
+
/** Degrees, ±90. Zero for mouse/touch. */
|
|
733
|
+
tiltY?: number;
|
|
734
|
+
}
|
|
735
|
+
/** Per-invocation runtime context the dispatcher hands to an Invoker.
|
|
736
|
+
* Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
|
|
737
|
+
* populated only for matching gesture kinds. */
|
|
738
|
+
interface InvocationCtx {
|
|
739
|
+
world: WorldPoint;
|
|
740
|
+
/**
|
|
741
|
+
* The pointer in client/screen pixels, for an action that has to place DOM
|
|
742
|
+
* at the pointer. Absent on every invocation whose event carries no client
|
|
743
|
+
* coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
|
|
744
|
+
* read it with a fallback rather than assuming a point.
|
|
745
|
+
*
|
|
746
|
+
* A view-mutating drag wants `drag.screenDelta`, not the difference of two
|
|
747
|
+
* of these: world deltas are self-referential mid-pan, and so is any delta
|
|
748
|
+
* taken across a view change.
|
|
749
|
+
*/
|
|
750
|
+
screen?: Point2;
|
|
751
|
+
modifiers: ModifierState;
|
|
752
|
+
deps: ActionDeps;
|
|
753
|
+
drag?: {
|
|
754
|
+
start: WorldPoint;
|
|
755
|
+
current: WorldPoint;
|
|
756
|
+
delta: Point2;
|
|
757
|
+
/**
|
|
758
|
+
* Drag delta in the surface's own CSS pixels from the drag origin: client
|
|
759
|
+
* pixels, or through the host's `clientToLocal` where it supplies one, so
|
|
760
|
+
* a canvas under a scaled ancestor pans with the pointer. Use this — never
|
|
761
|
+
* `delta` — for any action whose effect
|
|
762
|
+
* mutates the viewport itself (pan, view-zoom), because world-space
|
|
763
|
+
* deltas become self-referential as the view shifts mid-drag.
|
|
764
|
+
*
|
|
765
|
+
* Absent when the underlying pointer events carried no
|
|
766
|
+
* `clientX`/`clientY`.
|
|
767
|
+
*/
|
|
768
|
+
screenDelta?: Point2;
|
|
769
|
+
affordance?: AffordanceHit;
|
|
770
|
+
/**
|
|
771
|
+
* Full pointermove history for the current drag, in world space, with
|
|
772
|
+
* per-sample stylus state when the browser reported it.
|
|
773
|
+
* Available only during `onMove` and `onEnd` calls (not on `start`).
|
|
774
|
+
*
|
|
775
|
+
* During `onMove` this is the dispatcher's live accumulator, and it grows
|
|
776
|
+
* under you — read it, don't retain it. On `onEnd` it is a snapshot, so
|
|
777
|
+
* that is where an action that keeps the trail should take it.
|
|
778
|
+
*/
|
|
779
|
+
points?: DragSample[];
|
|
780
|
+
};
|
|
781
|
+
wheel?: {
|
|
782
|
+
deltaX: number;
|
|
783
|
+
deltaY: number;
|
|
784
|
+
deltaZ: number;
|
|
785
|
+
};
|
|
786
|
+
/** One `pinch` sample: `scale` is a multiplier since the previous sample,
|
|
787
|
+
* `rotation` degrees since it. See `PinchEvent`. */
|
|
788
|
+
pinch?: {
|
|
789
|
+
scale: number;
|
|
790
|
+
rotation: number;
|
|
791
|
+
};
|
|
792
|
+
multiTouch?: {
|
|
793
|
+
/** Canvas-local CSS pixels — see `MultiTouchEvent.centroid`. Neither
|
|
794
|
+
* `world` nor `screen`: it is the space `zoomAt` anchors in, so passing
|
|
795
|
+
* it through `clientToWorld` removes the canvas origin twice. */
|
|
796
|
+
centroid: Point2;
|
|
797
|
+
/** Distance between the two primary pointers, in CSS pixels. */
|
|
798
|
+
spread: number;
|
|
799
|
+
/** Degrees the line between the two primary pointers has turned since
|
|
800
|
+
* the gesture began, clockwise positive. It keeps counting past a half
|
|
801
|
+
* turn. Zero where the events carry no `MultiTouchEvent.angle`. */
|
|
802
|
+
rotation: number;
|
|
803
|
+
/**
|
|
804
|
+
* Pinch-zoom geometry, populated while a multitouch handle is in flight.
|
|
805
|
+
* `startSpread` is the spread at the moment the gesture began.
|
|
806
|
+
*/
|
|
807
|
+
pinch?: {
|
|
808
|
+
startSpread: number;
|
|
809
|
+
currentSpread: number;
|
|
810
|
+
/** Canvas-local, same as the enclosing `centroid`. */
|
|
811
|
+
centroid: Point2;
|
|
812
|
+
};
|
|
813
|
+
};
|
|
814
|
+
key?: {
|
|
815
|
+
key: string;
|
|
816
|
+
repeat: boolean;
|
|
817
|
+
};
|
|
818
|
+
/** What a carry over the surface brings: its payload, as `startCarry` was
|
|
819
|
+
* handed it. Present for every event of a `carry` binding's gesture. */
|
|
820
|
+
carry?: {
|
|
821
|
+
type: string;
|
|
822
|
+
value: unknown;
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* The view the input landed in, as the surface's view resolver assigned it:
|
|
826
|
+
* a view id, or `null` for the surface's own camera. Absent where no
|
|
827
|
+
* surface routed the input — a UI-driven trigger, a bare dispatcher.
|
|
828
|
+
*/
|
|
829
|
+
viewId?: string | null;
|
|
830
|
+
/**
|
|
831
|
+
* Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
|
|
832
|
+
* UI-driven ongoing actions (color picker, opacity slider). The gesture
|
|
833
|
+
* dispatcher does not populate this field; gesture-driven actions receive
|
|
834
|
+
* params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
835
|
+
*/
|
|
836
|
+
params?: Record<string, unknown>;
|
|
837
|
+
/**
|
|
838
|
+
* Which of the action's `modifierRoles` are held for this event, by role
|
|
839
|
+
* id. The dispatcher fills it on every event of a gesture whose action
|
|
840
|
+
* declares roles; absent otherwise. Read it through `roleReader`, which
|
|
841
|
+
* falls back to the roles' default keys where it is absent.
|
|
842
|
+
*/
|
|
843
|
+
roles?: Readonly<Record<string, boolean>>;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** The deps an action receives, composed per call from what it `requires`.
|
|
847
|
+
* Each name resolves to its {@link DepSchema} entry, and is absent when no
|
|
848
|
+
* source registered it; a name nothing has declared passes through as
|
|
849
|
+
* `unknown`. */
|
|
850
|
+
interface ActionDeps extends Partial<DepSchema> {
|
|
851
|
+
[k: string]: unknown;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** Handle returned from an `OngoingInvoker.start`. The dispatcher pumps
|
|
855
|
+
* `onMove` on subsequent input events of the same gesture and calls
|
|
856
|
+
* `onEnd` exactly once (with `'commit'` on natural completion or `'cancel'`
|
|
857
|
+
* on pointercancel / blur / escape). */
|
|
858
|
+
interface OngoingHandle {
|
|
859
|
+
/**
|
|
860
|
+
* Optional logical action kind — a stable, human-readable tag the
|
|
861
|
+
* dispatcher exposes via `getActiveAction()`, e.g. `'marquee'`, `'move'`,
|
|
862
|
+
* `'resize'`, `'pan'`.
|
|
863
|
+
*
|
|
864
|
+
* When omitted, `getActiveAction().kind` reports `null` even though a
|
|
865
|
+
* handle is in flight.
|
|
866
|
+
*/
|
|
867
|
+
kind?: string;
|
|
868
|
+
/**
|
|
869
|
+
* The cursor this gesture shows now, read after every move. Wins over the
|
|
870
|
+
* action's `activeCursor`, for a cursor that follows the gesture — the
|
|
871
|
+
* rotate glyph turning about its pivot. `undefined` defers to the action.
|
|
872
|
+
*/
|
|
873
|
+
cursor?(): CursorSpec | undefined;
|
|
874
|
+
/**
|
|
875
|
+
* A move of the gesture. Return `'pass'` from a pointer drag's move to hand
|
|
876
|
+
* the drag off: the dispatcher ends this handle with `'cancel'`, offers the
|
|
877
|
+
* drag as it was pressed to every other candidate in rank order, and the one
|
|
878
|
+
* that takes it starts as if it had won the press, then sees this move. With
|
|
879
|
+
* no taker the drag is over. A drag never returns to an action that passed it.
|
|
880
|
+
*/
|
|
881
|
+
onMove?(ctx: InvocationCtx): void | 'pass';
|
|
882
|
+
onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
|
|
883
|
+
/**
|
|
884
|
+
* Commit this gesture again, as it ended, with `params` in place of the ones it read: what lets a drag of an
|
|
885
|
+
* action declaring `params` be the tree's last operation, kept when its `onEnd('commit')` leaves one history
|
|
886
|
+
* entry. The keeper calls it after undoing that entry and putting back the selection the gesture started on.
|
|
887
|
+
* Recompute the commit from what the gesture recorded (its start state, the pointer as it ended or every move
|
|
888
|
+
* when the path taken matters, the modifiers held), never the live pointer, and commit one entry as `onEnd`
|
|
889
|
+
* did. `params` is every param the gesture resolved at its end with the panel's values laid over them; read
|
|
890
|
+
* them instead of `opts.params`. It may run any number of times. A gesture that cannot be recomputed
|
|
891
|
+
* faithfully leaves it out, and its action gets no panel.
|
|
892
|
+
*/
|
|
893
|
+
replay?(params: Record<string, unknown>): void;
|
|
894
|
+
/**
|
|
895
|
+
* Params this gesture's commit took from what it edited rather than from its binding, read once it has ended:
|
|
896
|
+
* a held warp's own grid and tolerance, say, which the binding's settings do not reach. The last-operation
|
|
897
|
+
* panel shows them over the resolved params, and `replay` is handed them, so a change to one field leaves the
|
|
898
|
+
* rest as the gesture found them.
|
|
899
|
+
*/
|
|
900
|
+
params?(): Readonly<Record<string, unknown>>;
|
|
901
|
+
/**
|
|
902
|
+
* Optional preview surface: the in-flight state the canvas's preview-ghost
|
|
903
|
+
* layer renders on top of the committed scene during the gesture.
|
|
904
|
+
*
|
|
905
|
+
* `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
906
|
+
* returns the interim pose for one of them (shape opaque — the canvas
|
|
907
|
+
* casts to its `TPose` parameter). `previewIds` returning `null`, or
|
|
908
|
+
* omitted, means "no preview this gesture". An id with no `previewPose`
|
|
909
|
+
* is shown at its effective pose: an action that publishes its poses to
|
|
910
|
+
* the scene's pose overrides, as move, resize and rotate do, leaves
|
|
911
|
+
* `previewPose` out rather than answering the same pose twice. `<SceneCanvas>`
|
|
912
|
+
* also shows both answers to the scene as overrides (core's
|
|
913
|
+
* `publishPreviews`), so what derives from a previewed node follows it.
|
|
914
|
+
*/
|
|
915
|
+
previewIds?(): Iterable<string> | null;
|
|
916
|
+
previewPose?(id: string): unknown | null;
|
|
917
|
+
/**
|
|
918
|
+
* Subset of `previewIds()` the ghost layer paints at full opacity. The
|
|
919
|
+
* ghost alpha says "this is in flight under the pointer"; a node the
|
|
920
|
+
* gesture merely displaces — a layout sibling reflowing into its
|
|
642
921
|
* destination slot — is not, and reads better settled. Honored at
|
|
643
922
|
* subtree-root granularity.
|
|
644
923
|
*/
|
|
@@ -674,648 +953,33 @@ interface OngoingHandle {
|
|
|
674
953
|
}
|
|
675
954
|
/** Fire-once invocation. Runs to completion synchronously (or fires off an
|
|
676
955
|
* async side-effect; the registry doesn't wait). */
|
|
677
|
-
interface ImmediateInvoker {
|
|
678
|
-
timing: 'immediate';
|
|
679
|
-
/** `params` carries the matched binding's opts.params. Invoked from the
|
|
680
|
-
* command palette, or anywhere else with no per-binding context, `params`
|
|
681
|
-
* is undefined; descriptors should default to a sensible variant.
|
|
682
|
-
*
|
|
683
|
-
* Return `'pass'` when, having looked at the event, there is nothing to do
|
|
684
|
-
* with it — a scroll already at the end of its range — and the dispatcher
|
|
685
|
-
* walks on to the next matching binding, as for a declined `enabled()`.
|
|
686
|
-
* Prediction cannot run the invoker, so it still names this action. */
|
|
687
|
-
run(deps: ActionDeps, params?: Record<string, unknown>): void | 'pass';
|
|
688
|
-
}
|
|
689
|
-
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
690
|
-
* the dispatcher pumps. */
|
|
691
|
-
interface OngoingInvoker {
|
|
692
|
-
timing: 'ongoing';
|
|
693
|
-
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
694
|
-
/**
|
|
695
|
-
* Screen travel, in CSS pixels, a pointer drag makes before the dispatcher calls `start`; a
|
|
696
|
-
* binding's own `dragThresholdPx` wins over it. Until then the gesture is held for this action and
|
|
697
|
-
* nothing else takes it, and a release short of it ends the gesture with `start` never called.
|
|
698
|
-
* Unset, the action starts once the press is a drag at all (`gestures.dragPx`).
|
|
699
|
-
*/
|
|
700
|
-
dragThresholdPx?: number;
|
|
701
|
-
}
|
|
702
|
-
/** Pluggable invocation strategy for an Action. */
|
|
703
|
-
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
707
|
-
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
708
|
-
* gesture-bindings are registered globally.
|
|
709
|
-
*/
|
|
710
|
-
|
|
711
|
-
/** An interaction: a gesture spec composed with the id of the action it
|
|
712
|
-
* invokes. Tools declare arrays of these; the dispatcher matches an incoming
|
|
713
|
-
* input event against them and runs the winner's action. */
|
|
714
|
-
interface GestureBinding {
|
|
715
|
-
spec: GestureSpec;
|
|
716
|
-
actionId: string;
|
|
717
|
-
opts?: BindingOpts;
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
|
|
721
|
-
* (no per-binding opts) or an object form that pairs a spec with
|
|
722
|
-
* `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
|
|
723
|
-
* Use the object form when two bindings for the same action differ only in
|
|
724
|
-
* a runtime parameter — the dispatcher extracts `opts.params` and passes
|
|
725
|
-
* them to `ImmediateInvoker.run` as its second argument.
|
|
726
|
-
*/
|
|
727
|
-
type BoundGesture = GestureSpec | {
|
|
728
|
-
spec: GestureSpec;
|
|
729
|
-
opts: BindingOpts;
|
|
730
|
-
};
|
|
731
|
-
/**
|
|
732
|
-
* The whole of an action `actionBindings` reads. Routing needs this much and
|
|
733
|
-
* none of `Action`'s palette fields, so it is the narrowest type the
|
|
734
|
-
* dispatcher's binding walk can be written against.
|
|
735
|
-
*/
|
|
736
|
-
interface BindingSource {
|
|
737
|
-
id: string;
|
|
738
|
-
/** The gesture-spec form of the binding, read by the gesture dispatcher.
|
|
739
|
-
* May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
|
|
740
|
-
* or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
|
|
741
|
-
* `{ spec, opts }` — use the object form for parametric actions where two
|
|
742
|
-
* bindings for the same action differ only by `opts.params` (e.g. `flip`
|
|
743
|
-
* with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
|
|
744
|
-
* passes them to `ImmediateInvoker.run` as its second argument. */
|
|
745
|
-
defaultBinding?: GestureSpec | BoundGesture[];
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
|
|
749
|
-
* `GestureSpec` has `kind` at top level; the object form has `spec`.
|
|
750
|
-
*/
|
|
751
|
-
declare function actionBindings(action: BindingSource): GestureBinding[];
|
|
752
|
-
|
|
753
|
-
/** What a key no open keymap binds does: `'modal'` swallows it and leaves the
|
|
754
|
-
* keymap open; `'cascade'` closes the keymap and dispatches it as if none were. */
|
|
755
|
-
type KeymapUnbound = 'modal' | 'cascade';
|
|
756
|
-
/** What holds a level opened with `{ hold: true }` open: no key releases it, so it closes on Escape. */
|
|
757
|
-
declare const KEYMAP_HOLD = "hold";
|
|
758
|
-
/** One open level of a {@link KeymapStack}. */
|
|
759
|
-
interface OpenKeymap {
|
|
760
|
-
readonly id: string;
|
|
761
|
-
readonly label: string;
|
|
762
|
-
/** The keymap's bindings as declared, for a hint to list. */
|
|
763
|
-
readonly bindings: readonly GestureBinding[];
|
|
764
|
-
/** The gesture id of the leader holding this level open, while it is held,
|
|
765
|
-
* or {@link KEYMAP_HOLD}. */
|
|
766
|
-
readonly heldBy?: string;
|
|
767
|
-
/** Whether input arrived while the leader was held, so its release closes the level. */
|
|
768
|
-
readonly usedWhileHeld: boolean;
|
|
769
|
-
}
|
|
770
|
-
/** What the keymaps made of the raw input in progress: `'taken'` when one of
|
|
771
|
-
* them handled it, `'cascaded'` when it went on past them, `null` before either. */
|
|
772
|
-
type KeymapVerdict = 'taken' | 'cascaded' | null;
|
|
773
|
-
/** The keymaps open on one surface, innermost last; a canvas's `keymaps` dep. */
|
|
774
|
-
interface KeymapStack {
|
|
775
|
-
/** Every open level, outermost first. */
|
|
776
|
-
open(): readonly OpenKeymap[];
|
|
777
|
-
innermost(): OpenKeymap | null;
|
|
778
|
-
/** Open a level. `heldBy` names what holds it open; `bindings` are what a hint lists. */
|
|
779
|
-
push(id: string, label: string, opts?: {
|
|
780
|
-
heldBy?: string;
|
|
781
|
-
bindings?: readonly GestureBinding[];
|
|
782
|
-
}): void;
|
|
783
|
-
/** Mark the levels the last raw input opened as held by `heldBy`: the key
|
|
784
|
-
* that opened them is still down. False when it opened none. */
|
|
785
|
-
holdOpened(heldBy: string): boolean;
|
|
786
|
-
/** The leader `heldBy` came up: close its level, those above it and the
|
|
787
|
-
* tapped ones beneath it if input arrived meanwhile, else leave it open
|
|
788
|
-
* for one more input. */
|
|
789
|
-
release(heldBy: string): void;
|
|
790
|
-
/** Close the innermost level: Escape. */
|
|
791
|
-
closeInnermost(): void;
|
|
792
|
-
/** One of the keymaps took this input, or a pointer gesture went past them.
|
|
793
|
-
* Marks held levels used and closes once-levels down to the innermost held one. */
|
|
794
|
-
inputSeen(): void;
|
|
795
|
-
/** A key no keymap binds was swallowed: held levels count it as input, and nothing closes. */
|
|
796
|
-
swallowed(): void;
|
|
797
|
-
/** This input went on past the keymaps: as `inputSeen`, remembered as a cascade. */
|
|
798
|
-
cascaded(): void;
|
|
799
|
-
/** What the keymaps made of the input in progress. */
|
|
800
|
-
verdict(): KeymapVerdict;
|
|
801
|
-
closeAll(): void;
|
|
802
|
-
/** Run `fn` as one raw input: closes made inside apply when it returns. Nests. */
|
|
803
|
-
batch<T>(fn: () => T): T;
|
|
804
|
-
subscribe(listener: () => void): () => void;
|
|
805
|
-
getVersion(): number;
|
|
806
|
-
}
|
|
807
|
-
/** An empty {@link KeymapStack}. `useGestureDispatcher` makes one per surface. */
|
|
808
|
-
declare function createKeymapStack(): KeymapStack;
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
|
|
812
|
-
* the actions-layer binding-scope / matchBest logic on top.
|
|
813
|
-
*/
|
|
814
|
-
|
|
815
|
-
/** Where a binding came from, which is also its priority: a held hotkey beats
|
|
816
|
-
* the active tool, which beats bindings that are always in scope. */
|
|
817
|
-
type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
818
|
-
/** A binding paired with where it came from, ready to be matched against an
|
|
819
|
-
* event. */
|
|
820
|
-
interface ScopedBinding {
|
|
821
|
-
binding: GestureBinding;
|
|
822
|
-
scope: BindingScope;
|
|
823
|
-
/** Id of the tool or contribution that declared this binding —
|
|
824
|
-
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
825
|
-
* action's own binding. */
|
|
826
|
-
ownerId: string | null;
|
|
827
|
-
}
|
|
828
|
-
/** The binding that won a match. */
|
|
829
|
-
interface MatchResult {
|
|
830
|
-
binding: GestureBinding;
|
|
831
|
-
scope: BindingScope;
|
|
832
|
-
/** Id of the entry that declared the binding — propagated from
|
|
833
|
-
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
834
|
-
ownerId: string | null;
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* True when a spec's target actually consults the affordance hit rather than
|
|
838
|
-
* only the body classification. `kindOf` predicates are handed the hit;
|
|
839
|
-
* `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
|
|
840
|
-
* `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
|
|
841
|
-
* `bodyTarget` / `bodyKind` and never see it.
|
|
842
|
-
*
|
|
843
|
-
* A predicate declares which it is with `readsAffordance`; one that declares
|
|
844
|
-
* nothing, which only untyped code can write, counts as not reading the hit.
|
|
845
|
-
*/
|
|
846
|
-
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
847
|
-
/** The first of {@link matchSorted}'s matches, or null when nothing matches. */
|
|
848
|
-
declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
|
|
849
|
-
/**
|
|
850
|
-
* Find every binding that matches the event, sorted by precedence
|
|
851
|
-
* (best → worst). Ordering rules:
|
|
852
|
-
*
|
|
853
|
-
* - Scopes by priority: hotkey > active > ambient.
|
|
854
|
-
* - Within a scope, more-specific bindings beat less-specific (see
|
|
855
|
-
* `specificity()`).
|
|
856
|
-
* - Among same-specificity bindings, first-declared wins.
|
|
857
|
-
*/
|
|
858
|
-
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
859
|
-
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
860
|
-
interface MatchOutcome {
|
|
861
|
-
matches: MatchResult[];
|
|
862
|
-
/** Bindings whose spec matched the event but which the claim barred, in
|
|
863
|
-
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
864
|
-
barred: MatchResult[];
|
|
865
|
-
/** Owner of the exclusive claim, when one applied. */
|
|
866
|
-
claimOwner?: string;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
870
|
-
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
871
|
-
* opt-in — the dispatcher asks only when it is recording.
|
|
872
|
-
*/
|
|
873
|
-
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
874
|
-
collectBarred?: boolean;
|
|
875
|
-
warn?: (message: string) => void;
|
|
876
|
-
}): MatchOutcome;
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* How one input was resolved: every binding that matched it, what each filter
|
|
880
|
-
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
881
|
-
* that order did. The dispatcher fills one per input in dev builds, and
|
|
882
|
-
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
883
|
-
*
|
|
884
|
-
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
885
|
-
* buffer, serialized, or built by hand as a fixture.
|
|
886
|
-
*/
|
|
887
|
-
interface DispatchRecord {
|
|
888
|
-
kind: 'dispatch';
|
|
889
|
-
ts: number;
|
|
890
|
-
input: DispatchRecordInput;
|
|
891
|
-
/** Every binding whose spec matched the input, in matcher order, including
|
|
892
|
-
* those a filter then dropped. */
|
|
893
|
-
matched: readonly RecordCandidate[];
|
|
894
|
-
dropped: readonly DroppedCandidate[];
|
|
895
|
-
/** The candidates that survived both filters, best first. */
|
|
896
|
-
ranked: readonly RankedCandidate[];
|
|
897
|
-
/** True when nothing was invoked: the record answers "what would happen". */
|
|
898
|
-
predicted: boolean;
|
|
899
|
-
/** Action id of the candidate that fired (or would), else `null`. */
|
|
900
|
-
fired: string | null;
|
|
901
|
-
outcome: 'handled' | 'unhandled';
|
|
902
|
-
}
|
|
903
|
-
/** The input a {@link DispatchRecord} describes, reduced to plain data. */
|
|
904
|
-
interface DispatchRecordInput {
|
|
905
|
-
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
906
|
-
eventKind: string;
|
|
907
|
-
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
908
|
-
key?: string;
|
|
909
|
-
modifiers: {
|
|
910
|
-
alt: boolean;
|
|
911
|
-
ctrl: boolean;
|
|
912
|
-
meta: boolean;
|
|
913
|
-
shift: boolean;
|
|
914
|
-
};
|
|
915
|
-
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
916
|
-
viewId: string | null;
|
|
917
|
-
/** World point, for events that carry one. */
|
|
918
|
-
world?: {
|
|
919
|
-
x: number;
|
|
920
|
-
y: number;
|
|
921
|
-
};
|
|
922
|
-
/** Active mode id, when the host supplies a rule context. */
|
|
923
|
-
mode?: string;
|
|
924
|
-
}
|
|
925
|
-
/** One matched binding. */
|
|
926
|
-
interface RecordCandidate {
|
|
927
|
-
actionId: string;
|
|
928
|
-
/** The binding's spec in route grammar — one per arg alternative. */
|
|
929
|
-
routes: readonly string[];
|
|
930
|
-
scope: BindingScope;
|
|
931
|
-
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
932
|
-
ownerId: string | null;
|
|
933
|
-
/** Whether the binding's `views` names the routed view. */
|
|
934
|
-
namesView: boolean;
|
|
935
|
-
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
936
|
-
specificity: readonly [number, number, number, number];
|
|
937
|
-
/** The action's `eligible` rule as text, when it declares one. */
|
|
938
|
-
eligible?: string;
|
|
939
|
-
}
|
|
940
|
-
/** A matched candidate a filter removed before ranking, and which filter. */
|
|
941
|
-
type DroppedCandidate =
|
|
942
|
-
/** An exclusive claim on the input barred bindings that do not read the
|
|
943
|
-
* claim's affordance. */
|
|
944
|
-
{
|
|
945
|
-
candidate: RecordCandidate;
|
|
946
|
-
filter: 'claim';
|
|
947
|
-
owner?: string;
|
|
948
|
-
}
|
|
949
|
-
/** The action's `eligible` rule does not hold. */
|
|
950
|
-
| {
|
|
951
|
-
candidate: RecordCandidate;
|
|
952
|
-
filter: 'ineligible';
|
|
953
|
-
rule: string;
|
|
954
|
-
};
|
|
955
|
-
/** One field of the `specificity` tuple, in the order it is compared. */
|
|
956
|
-
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
957
|
-
/** The ranking step that put a candidate below the one before it. */
|
|
958
|
-
type PlacedBy = {
|
|
959
|
-
step: 'first';
|
|
960
|
-
}
|
|
961
|
-
/** The one above names the routed view; this one does not. */
|
|
962
|
-
| {
|
|
963
|
-
step: 'view';
|
|
964
|
-
}
|
|
965
|
-
/** The one above sits in a higher scope tier. */
|
|
966
|
-
| {
|
|
967
|
-
step: 'tier';
|
|
968
|
-
}
|
|
969
|
-
/** Same tier; the one above is more specific in `part`. */
|
|
970
|
-
| {
|
|
971
|
-
step: 'specificity';
|
|
972
|
-
part: SpecificityPart;
|
|
973
|
-
}
|
|
974
|
-
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
975
|
-
| {
|
|
976
|
-
step: 'context';
|
|
977
|
-
}
|
|
978
|
-
/** Tied on everything; registration order. */
|
|
979
|
-
| {
|
|
980
|
-
step: 'order';
|
|
981
|
-
};
|
|
982
|
-
/** What the walk did with one ranked candidate. */
|
|
983
|
-
type WalkStep = {
|
|
984
|
-
kind: 'fired';
|
|
985
|
-
}
|
|
986
|
-
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
987
|
-
* so an ongoing action here may still bail at `start()` for real. */
|
|
988
|
-
| {
|
|
989
|
-
kind: 'would-fire';
|
|
990
|
-
}
|
|
991
|
-
/** `enabled()` returned this reason. */
|
|
992
|
-
| {
|
|
993
|
-
kind: 'declined';
|
|
994
|
-
reason: string;
|
|
995
|
-
}
|
|
996
|
-
/** An ongoing action's `start()` returned an empty handle, or an
|
|
997
|
-
* immediate one's `run()` returned `'pass'`. */
|
|
998
|
-
| {
|
|
999
|
-
kind: 'empty-handle';
|
|
1000
|
-
}
|
|
1001
|
-
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
1002
|
-
| {
|
|
1003
|
-
kind: 'misbound';
|
|
1004
|
-
}
|
|
1005
|
-
/** Its action was already tried higher up; each runs at most once. */
|
|
1006
|
-
| {
|
|
1007
|
-
kind: 'duplicate';
|
|
1008
|
-
} | {
|
|
1009
|
-
kind: 'no-such-action';
|
|
1010
|
-
}
|
|
1011
|
-
/** Below the winner; never asked. */
|
|
1012
|
-
| {
|
|
1013
|
-
kind: 'not-asked';
|
|
1014
|
-
}
|
|
1015
|
-
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
1016
|
-
| {
|
|
1017
|
-
kind: 'outranked';
|
|
1018
|
-
};
|
|
1019
|
-
/** A candidate that survived the filters: why it sits where it does, and
|
|
1020
|
-
* what the walk did with it. */
|
|
1021
|
-
interface RankedCandidate {
|
|
1022
|
-
candidate: RecordCandidate;
|
|
1023
|
-
placedBy: PlacedBy;
|
|
1024
|
-
walk: WalkStep;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/** One entry per "mode change" — a kit state transition that isn't a
|
|
1028
|
-
* dispatched input event but explains why an input was (or wasn't) handled
|
|
1029
|
-
* later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
|
|
1030
|
-
interface ModeSwitchLogEntry {
|
|
1031
|
-
kind: 'mode';
|
|
1032
|
-
ts: number;
|
|
1033
|
-
/** Logical mode name, e.g. `'editAnchors.editingId'`. */
|
|
1034
|
-
mode: string;
|
|
1035
|
-
from: string | null;
|
|
1036
|
-
to: string | null;
|
|
1037
|
-
/** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
|
|
1038
|
-
* `'selection-dropped target'`, etc. */
|
|
1039
|
-
detail?: string;
|
|
1040
|
-
}
|
|
1041
|
-
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
1042
|
-
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
1043
|
-
/** A rolling buffer that records in a dev build, and in any build while something watches it. */
|
|
1044
|
-
interface TraceRing<T> {
|
|
1045
|
-
/** Whether a push now keeps its entry. A caller building an entry checks first. */
|
|
1046
|
-
recording(): boolean;
|
|
1047
|
-
push(entry: T): void;
|
|
1048
|
-
/** The entries pushed after `cursor`, oldest first, and the cursor to pass
|
|
1049
|
-
* next time. Start from 0. Entries the buffer has dropped are skipped. */
|
|
1050
|
-
since(cursor: number): {
|
|
1051
|
-
entries: T[];
|
|
1052
|
-
cursor: number;
|
|
1053
|
-
};
|
|
1054
|
-
/** Turn recording on until the returned release is called. */
|
|
1055
|
-
watch(): () => void;
|
|
1056
|
-
/** Hear each kept push. Returns the unsubscribe. */
|
|
1057
|
-
subscribe(listener: () => void): () => void;
|
|
1058
|
-
}
|
|
1059
|
-
/** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
1060
|
-
declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
|
|
1061
|
-
/** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
|
|
1062
|
-
* every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
|
|
1063
|
-
interface DispatchTrace extends TraceRing<TraceLogEntry> {
|
|
1064
|
-
recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
1065
|
-
/** Offer the record for what a press at the pointer would do, built only
|
|
1066
|
-
* when {@link DispatchTrace.live} reads it. `null` withdraws it. */
|
|
1067
|
-
setLive(explain: (() => DispatchRecord) | null): void;
|
|
1068
|
-
live(): DispatchRecord | null;
|
|
1069
|
-
}
|
|
1070
|
-
/** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
1071
|
-
declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
|
|
1072
|
-
|
|
1073
|
-
/**
|
|
1074
|
-
* Live state read by rule evaluation. Built once per frame on the consuming
|
|
1075
|
-
* surface — chrome-caps, the affordance pipeline, the dispatcher's
|
|
1076
|
-
* eligibility filter — and discarded.
|
|
1077
|
-
*
|
|
1078
|
-
* Adding a new field is additive: existing rules don't change, new
|
|
1079
|
-
* selector atoms can read it.
|
|
1080
|
-
*/
|
|
1081
|
-
interface RuleCtx {
|
|
1082
|
-
readonly focused: boolean;
|
|
1083
|
-
readonly selection: readonly NodeId[];
|
|
1084
|
-
readonly multiActive: boolean;
|
|
1085
|
-
readonly modifiers: ModifierState;
|
|
1086
|
-
readonly action: {
|
|
1087
|
-
readonly kind: string | null;
|
|
1088
|
-
readonly id: string | null;
|
|
1089
|
-
};
|
|
1090
|
-
readonly hover: NodeId | null;
|
|
1091
|
-
/**
|
|
1092
|
-
* How far the viewport is zoomed in, as one number — `1` at native scale.
|
|
1093
|
-
*
|
|
1094
|
-
* A scalar rather than the `View` this used to carry, because `zoomAtLeast`
|
|
1095
|
-
* is the only selector that ever read one and a number is all it needs. A
|
|
1096
|
-
* host whose viewport is a camera has no `View` to hand over and would
|
|
1097
|
-
* otherwise have to invent one to use eligibility at all; it omits this
|
|
1098
|
-
* instead, and `zoomAtLeast` declines rather than matching on a fiction.
|
|
1099
|
-
*/
|
|
1100
|
-
readonly zoom?: number;
|
|
1101
|
-
/** Active mode id. `'normal'` when no non-default mode is engaged. */
|
|
1102
|
-
readonly mode: string;
|
|
1103
|
-
/** Capability tags allowed by the active mode (the union of
|
|
1104
|
-
* `ModeDefinition.allows` plus implicit tags). The `capability:`
|
|
1105
|
-
* selector reads this to determine whether a tag is permitted. */
|
|
1106
|
-
readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
1107
|
-
/** Whether the current selection may be resized. `<SceneCanvas>` folds
|
|
1108
|
-
* `selectTool.resize.resizable` over the selection (true only when every
|
|
1109
|
-
* selected node is resizable). Read by the `resizable:` selector to gate
|
|
1110
|
-
* `selection.resize-handles`. Absent (legacy ctx builders) is treated as
|
|
1111
|
-
* resizable — back-compat: handles show unless a consumer opts a node out. */
|
|
1112
|
-
readonly selectionResizable?: boolean;
|
|
1113
|
-
/** Whether a path is currently in anchor-edit mode. Read by the
|
|
1114
|
-
* `editingAnchors:` selector, which gates the path-edit chrome.
|
|
1115
|
-
*
|
|
1116
|
-
* This is deliberately a fact about state, not about permission: the
|
|
1117
|
-
* anchor overlay and the anchor hit-test must agree, and the thing they
|
|
1118
|
-
* must agree on is "is there an edited path right now", which no
|
|
1119
|
-
* capability or mode id answers. A mode that allows `edits-anchors`
|
|
1120
|
-
* with nothing being edited should draw no anchors. Absent is treated
|
|
1121
|
-
* as false. */
|
|
1122
|
-
readonly editingAnchors?: boolean;
|
|
1123
|
-
/** Device facts — pointer coarseness, hover capability, density.
|
|
1124
|
-
*
|
|
1125
|
-
* Absent (legacy ctx builders) is treated as
|
|
1126
|
-
* {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
|
|
1127
|
-
* density 1. That is what the kit assumed before this field existed, so
|
|
1128
|
-
* an absent profile is behavior-preserving by construction. */
|
|
1129
|
-
readonly device?: DeviceProfile;
|
|
1130
|
-
/** The view the input being dispatched landed in — a view id, or `null`
|
|
1131
|
-
* for the surface's own camera. The dispatcher fills it; absent anywhere
|
|
1132
|
-
* else a rule is evaluated. */
|
|
1133
|
-
readonly viewId?: string | null;
|
|
1134
|
-
}
|
|
1135
|
-
/** The live state a `RuleCtx` is assembled from — the chrome context plus
|
|
1136
|
-
* what the active mode allows. */
|
|
1137
|
-
interface BuildRuleCtxArgs {
|
|
1138
|
-
focused: boolean;
|
|
1139
|
-
selection: readonly NodeId[];
|
|
1140
|
-
multiActive: boolean;
|
|
1141
|
-
modifiers: ModifierState;
|
|
1142
|
-
action: {
|
|
1143
|
-
kind: string | null;
|
|
1144
|
-
id: string | null;
|
|
1145
|
-
};
|
|
1146
|
-
hover: NodeId | null;
|
|
1147
|
-
/** See {@link RuleCtx.zoom}. */
|
|
1148
|
-
zoom?: number;
|
|
1149
|
-
mode: string;
|
|
1150
|
-
allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
1151
|
-
/** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
|
|
1152
|
-
selectionResizable?: boolean;
|
|
1153
|
-
/** Optional — omitted means "no path is being anchor-edited". */
|
|
1154
|
-
editingAnchors?: boolean;
|
|
1155
|
-
/** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
|
|
1156
|
-
device?: DeviceProfile;
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Capability set to assume when no mode registry is wired.
|
|
1160
|
-
*
|
|
1161
|
-
* "No modes configured" means *everything the default mode permits*, not
|
|
1162
|
-
* *nothing permitted*. The empty set is the wrong default: it makes every
|
|
1163
|
-
* `capability:` rule evaluate false, so a kit default written the
|
|
1164
|
-
* recommended way (capability rules outlive new modes; mode rules have to
|
|
1165
|
-
* be edited) would silently hide its chrome for every consumer that hasn't
|
|
1166
|
-
* opted into modality. `NORMAL.allows` plus the implicit tags is what a
|
|
1167
|
-
* mode-less canvas actually behaves like.
|
|
1168
|
-
*/
|
|
1169
|
-
declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
|
|
1170
|
-
/** Gather the current canvas and mode state into the context that action
|
|
1171
|
-
* eligibility and chrome-visibility rules are evaluated against. */
|
|
1172
|
-
declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
|
|
1173
|
-
|
|
1174
|
-
/**
|
|
1175
|
-
* A selector is a conjunction of key/value tests. Multiple keys at the same
|
|
1176
|
-
* level AND together. Each key maps to a selector primitive in the evaluator.
|
|
1177
|
-
*/
|
|
1178
|
-
interface Selector {
|
|
1179
|
-
selection?: {
|
|
1180
|
-
is?: number;
|
|
1181
|
-
atLeast?: number;
|
|
1182
|
-
empty?: boolean;
|
|
1183
|
-
};
|
|
1184
|
-
mode?: string | {
|
|
1185
|
-
not: string;
|
|
1186
|
-
} | {
|
|
1187
|
-
in: readonly string[];
|
|
1188
|
-
};
|
|
1189
|
-
capability?: CapabilityTag | readonly CapabilityTag[] | {
|
|
1190
|
-
in: readonly CapabilityTag[];
|
|
1191
|
-
} | {
|
|
1192
|
-
not: CapabilityTag;
|
|
1193
|
-
};
|
|
1194
|
-
gesturing?: boolean;
|
|
1195
|
-
actionIs?: string;
|
|
1196
|
-
modifierHeld?: keyof ModifierState;
|
|
1197
|
-
focused?: boolean;
|
|
1198
|
-
hovering?: boolean;
|
|
1199
|
-
hoveringSelected?: boolean;
|
|
1200
|
-
zoomAtLeast?: number;
|
|
1201
|
-
/** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
|
|
1202
|
-
* mode. Absent flag is treated as `false`. */
|
|
1203
|
-
editingAnchors?: boolean;
|
|
1204
|
-
/** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
|
|
1205
|
-
* (resizable), so `{ resizable: true }` passes for legacy ctx builders
|
|
1206
|
-
* that don't compute it. */
|
|
1207
|
-
resizable?: boolean;
|
|
1208
|
-
/** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
|
|
1209
|
-
* (touch, most styluses). Absent device is treated as `false`. */
|
|
1210
|
-
coarsePointer?: boolean;
|
|
1211
|
-
/** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
|
|
1212
|
-
* device is treated as `true`. */
|
|
1213
|
-
canHover?: boolean;
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
|
|
1217
|
-
* over `Selector` leaves. `when` is the escape hatch — its closure is
|
|
1218
|
-
* opaque to introspection and should be avoided when a declarative form
|
|
1219
|
-
* exists. Empty `all` is true; empty `any` is false.
|
|
1220
|
-
*/
|
|
1221
|
-
type Rule = Selector | {
|
|
1222
|
-
all: readonly Rule[];
|
|
1223
|
-
} | {
|
|
1224
|
-
any: readonly Rule[];
|
|
1225
|
-
} | {
|
|
1226
|
-
not: Rule;
|
|
1227
|
-
} | {
|
|
1228
|
-
when: (ctx: RuleCtx) => boolean;
|
|
1229
|
-
};
|
|
1230
|
-
/** Constant rules. Kept here so they have a single source. */
|
|
1231
|
-
declare const ALWAYS: Rule;
|
|
1232
|
-
/** A rule that never passes. `any` of nothing is false. */
|
|
1233
|
-
declare const NEVER: Rule;
|
|
1234
|
-
/**
|
|
1235
|
-
* Render a {@link Rule} as a short human-readable string, for inspectors and
|
|
1236
|
-
* diagnostics that need to say WHICH rule decided something.
|
|
1237
|
-
*
|
|
1238
|
-
* ```
|
|
1239
|
-
* { capability: 'edits-page' } → capability:edits-page
|
|
1240
|
-
* { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
|
|
1241
|
-
* { not: { selection: { empty: true } } } → not(selection:empty=true)
|
|
1242
|
-
* { when: function hasPath() { … } } → when(hasPath)
|
|
1243
|
-
* ```
|
|
1244
|
-
*
|
|
1245
|
-
* Never throws and always terminates.
|
|
1246
|
-
*/
|
|
1247
|
-
declare function describeRule(rule: Rule): string;
|
|
1248
|
-
/** Evaluate a rule against a context. Pure, and cheap enough to run per
|
|
1249
|
-
* frame for every piece of chrome. */
|
|
1250
|
-
declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
|
|
1251
|
-
/**
|
|
1252
|
-
* Composable eligibility predicate with a fluent surface. `cond(ctx)`
|
|
1253
|
-
* evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
|
|
1254
|
-
* Conditions wrapping new trees.
|
|
1255
|
-
*
|
|
1256
|
-
* **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
|
|
1257
|
-
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
|
|
1258
|
-
* disjunction, name the subexpression or use the top-level `or(...)`.
|
|
1259
|
-
*/
|
|
1260
|
-
interface Condition {
|
|
1261
|
-
(ctx: RuleCtx): boolean;
|
|
1262
|
-
readonly rule: Rule;
|
|
1263
|
-
/** `this && other` */
|
|
1264
|
-
and(other: Condition | Rule): Condition;
|
|
1265
|
-
/** `this || other` */
|
|
1266
|
-
or(other: Condition | Rule): Condition;
|
|
1267
|
-
/** `this && !other` */
|
|
1268
|
-
andNot(other: Condition | Rule): Condition;
|
|
1269
|
-
/** `this || !other` */
|
|
1270
|
-
orNot(other: Condition | Rule): Condition;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
1275
|
-
*
|
|
1276
|
-
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
1277
|
-
* about the selector values themselves (two different `mode`s, an empty
|
|
1278
|
-
* selection against a non-empty one, `focused: true` against `false`) and
|
|
1279
|
-
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
1280
|
-
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
1281
|
-
* that share a tuple but are never eligible together.
|
|
1282
|
-
*/
|
|
1283
|
-
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
1284
|
-
/**
|
|
1285
|
-
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
1286
|
-
* `capability` tests are settled by the mode; every other test could go either
|
|
1287
|
-
* way, so `false` is a proof and `true` means "may hold".
|
|
1288
|
-
*/
|
|
1289
|
-
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
1290
|
-
|
|
1291
|
-
/** The detected half of a profile — everything except the derived scale. */
|
|
1292
|
-
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
1293
|
-
/**
|
|
1294
|
-
* Handle/hit multiplier applied on a coarse pointer.
|
|
1295
|
-
*
|
|
1296
|
-
* 8px handle → 14px; 24px rotation distance → 42px. Counting the
|
|
1297
|
-
* surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
|
|
1298
|
-
* minimum-touch-target band. One constant so it is tunable in one place.
|
|
1299
|
-
*/
|
|
1300
|
-
declare const COARSE_TARGET_SCALE = 1.75;
|
|
1301
|
-
/**
|
|
1302
|
-
* Fold consumer overrides over detected facts and derive `targetScale`.
|
|
1303
|
-
*
|
|
1304
|
-
* `targetScale` is re-derived AFTER the merge, so an override of
|
|
1305
|
-
* `coarsePointer` alone scales the chrome as expected. An explicit
|
|
1306
|
-
* `targetScale` override wins over the derivation.
|
|
1307
|
-
*/
|
|
1308
|
-
declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
|
|
1309
|
-
/**
|
|
1310
|
-
* Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
|
|
1311
|
-
* absent-means value for `RuleCtx.device`.
|
|
1312
|
-
*
|
|
1313
|
-
* Derived from `resolveDeviceProfile` rather than hand-written, so it can
|
|
1314
|
-
* never drift from what the function would compute for the same facts. A
|
|
1315
|
-
* mouse-like device is the safe default: it is what the kit has always
|
|
1316
|
-
* assumed, so an absent profile changes nothing for existing consumers.
|
|
1317
|
-
*/
|
|
1318
|
-
declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
|
|
956
|
+
interface ImmediateInvoker {
|
|
957
|
+
timing: 'immediate';
|
|
958
|
+
/** `params` carries the matched binding's opts.params. Invoked from the
|
|
959
|
+
* command palette, or anywhere else with no per-binding context, `params`
|
|
960
|
+
* is undefined; descriptors should default to a sensible variant.
|
|
961
|
+
*
|
|
962
|
+
* Return `'pass'` when, having looked at the event, there is nothing to do
|
|
963
|
+
* with it — a scroll already at the end of its range — and the dispatcher
|
|
964
|
+
* walks on to the next matching binding, as for a declined `enabled()`.
|
|
965
|
+
* Prediction cannot run the invoker, so it still names this action. */
|
|
966
|
+
run(deps: ActionDeps, params?: Record<string, unknown>): void | 'pass';
|
|
967
|
+
}
|
|
968
|
+
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
969
|
+
* the dispatcher pumps. */
|
|
970
|
+
interface OngoingInvoker {
|
|
971
|
+
timing: 'ongoing';
|
|
972
|
+
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
973
|
+
/**
|
|
974
|
+
* Screen travel, in CSS pixels, a pointer drag makes before the dispatcher calls `start`; a
|
|
975
|
+
* binding's own `dragThresholdPx` wins over it. Until then the gesture is held for this action and
|
|
976
|
+
* nothing else takes it, and a release short of it ends the gesture with `start` never called.
|
|
977
|
+
* Unset, the action starts once the press is a drag at all (`gestures.dragPx`).
|
|
978
|
+
*/
|
|
979
|
+
dragThresholdPx?: number;
|
|
980
|
+
}
|
|
981
|
+
/** Pluggable invocation strategy for an Action. */
|
|
982
|
+
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
1319
983
|
|
|
1320
984
|
/**
|
|
1321
985
|
* A settings form an action carries: what a surface draws in place of a bare
|
|
@@ -1351,6 +1015,54 @@ declare function readActionSettings(action: Action, deps: ActionDeps | undefined
|
|
|
1351
1015
|
/** The params a change of the field at `path` to `value` triggers the action with. */
|
|
1352
1016
|
declare function actionSettingsParams(settings: ActionSettings, path: string, value: unknown): Record<string, unknown> | null;
|
|
1353
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
1020
|
+
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
1021
|
+
* gesture-bindings are registered globally.
|
|
1022
|
+
*/
|
|
1023
|
+
|
|
1024
|
+
/** An interaction: a gesture spec composed with the id of the action it
|
|
1025
|
+
* invokes. Tools declare arrays of these; the dispatcher matches an incoming
|
|
1026
|
+
* input event against them and runs the winner's action. */
|
|
1027
|
+
interface GestureBinding {
|
|
1028
|
+
spec: GestureSpec;
|
|
1029
|
+
actionId: string;
|
|
1030
|
+
opts?: BindingOpts;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
|
|
1034
|
+
* (no per-binding opts) or an object form that pairs a spec with
|
|
1035
|
+
* `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
|
|
1036
|
+
* Use the object form when two bindings for the same action differ only in
|
|
1037
|
+
* a runtime parameter — the dispatcher extracts `opts.params` and passes
|
|
1038
|
+
* them to `ImmediateInvoker.run` as its second argument.
|
|
1039
|
+
*/
|
|
1040
|
+
type BoundGesture = GestureSpec | {
|
|
1041
|
+
spec: GestureSpec;
|
|
1042
|
+
opts: BindingOpts;
|
|
1043
|
+
};
|
|
1044
|
+
/**
|
|
1045
|
+
* The whole of an action `actionBindings` reads. Routing needs this much and
|
|
1046
|
+
* none of `Action`'s palette fields, so it is the narrowest type the
|
|
1047
|
+
* dispatcher's binding walk can be written against.
|
|
1048
|
+
*/
|
|
1049
|
+
interface BindingSource {
|
|
1050
|
+
id: string;
|
|
1051
|
+
/** The gesture-spec form of the binding, read by the gesture dispatcher.
|
|
1052
|
+
* May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
|
|
1053
|
+
* or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
|
|
1054
|
+
* `{ spec, opts }` — use the object form for parametric actions where two
|
|
1055
|
+
* bindings for the same action differ only by `opts.params` (e.g. `flip`
|
|
1056
|
+
* with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
|
|
1057
|
+
* passes them to `ImmediateInvoker.run` as its second argument. */
|
|
1058
|
+
defaultBinding?: GestureSpec | BoundGesture[];
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
|
|
1062
|
+
* `GestureSpec` has `kind` at top level; the object form has `spec`.
|
|
1063
|
+
*/
|
|
1064
|
+
declare function actionBindings(action: BindingSource): GestureBinding[];
|
|
1065
|
+
|
|
1354
1066
|
/** What content being routed brings to `Action.enabled`: a drop's or a
|
|
1355
1067
|
* paste's items, or a carry's payload with no items. */
|
|
1356
1068
|
interface Carried {
|
|
@@ -1411,6 +1123,15 @@ interface ActionPresentation {
|
|
|
1411
1123
|
* triggers it with the params `settings.params` makes. Not combined with
|
|
1412
1124
|
* `variants`. */
|
|
1413
1125
|
settings?: ActionSettings;
|
|
1126
|
+
/** The params the invoker reads, as a form: one leaf per param, keyed by its name, with its label, range,
|
|
1127
|
+
* step, and default. A run that commits one history entry is kept as the tree's last operation, which a panel
|
|
1128
|
+
* draws as this form so the params can be changed after the fact: any immediate run, and a gesture whose
|
|
1129
|
+
* handle can `replay` (see `OngoingHandle.replay`). `ActionParamSchema` stays what types a tuning; this is
|
|
1130
|
+
* what a person sees.
|
|
1131
|
+
*
|
|
1132
|
+
* A function answers the form for one run from the params it resolved, for an action whose params depend
|
|
1133
|
+
* on what it was asked to do (`insert`'s on the kind of shape), or `null` for a run with none to show. */
|
|
1134
|
+
params?: PrefGroup | ((params: Readonly<Record<string, unknown>>) => PrefGroup | null);
|
|
1414
1135
|
}
|
|
1415
1136
|
/**
|
|
1416
1137
|
* One entry a parametric action contributes to a bar, menu or palette.
|
|
@@ -1562,20 +1283,452 @@ interface ActionDispatch extends BindingSource {
|
|
|
1562
1283
|
/**
|
|
1563
1284
|
* Single registered action.
|
|
1564
1285
|
*/
|
|
1565
|
-
interface Action extends ActionDispatch, ActionPresentation {
|
|
1566
|
-
}
|
|
1286
|
+
interface Action extends ActionDispatch, ActionPresentation {
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* The whole of an actions registry the dispatcher reads. It walks the list
|
|
1290
|
+
* once per event to build its actionId lookup and never mutates it, so the
|
|
1291
|
+
* routing side depends on this rather than on `ActionsRegistry`, whose
|
|
1292
|
+
* register / mute / trigger surface is the React provider's.
|
|
1293
|
+
*/
|
|
1294
|
+
interface ActionSource {
|
|
1295
|
+
list(): readonly Action[];
|
|
1296
|
+
}
|
|
1297
|
+
/** `action`'s hover cursor for an invocation given `params`: its `cursor`,
|
|
1298
|
+
* asked when it is a function. */
|
|
1299
|
+
declare function actionCursor(action: Pick<Action, 'cursor'> | undefined, params?: Record<string, unknown>): CursorSpec | undefined;
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Tuning: what a host changes about an action it did not write — the params
|
|
1303
|
+
* every invocation starts from, and bindings added beside the action's own —
|
|
1304
|
+
* without restating the descriptor. `ActionsRegistry.tune` holds tunings and
|
|
1305
|
+
* applies them to whatever descriptor is live for the id.
|
|
1306
|
+
*/
|
|
1307
|
+
|
|
1308
|
+
/** An action's params, or a thunk read each time the action asks for them. */
|
|
1309
|
+
type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
|
|
1310
|
+
/** A binding added for an action: a gesture alone, or a gesture with the
|
|
1311
|
+
* options it passes — the {@link BoundGesture} shape, its params typed. */
|
|
1312
|
+
type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
|
|
1313
|
+
spec: GestureSpec;
|
|
1314
|
+
opts: Omit<BindingOpts, 'params'> & {
|
|
1315
|
+
params?: ActionParams<P>;
|
|
1316
|
+
};
|
|
1317
|
+
};
|
|
1318
|
+
/** What a host changes about one action. */
|
|
1319
|
+
interface ActionTuning<P = Record<string, unknown>> {
|
|
1320
|
+
/** Params every invocation starts from. What a binding or a `trigger` call
|
|
1321
|
+
* passes is laid over them, so these fill in what it leaves out. A thunk is
|
|
1322
|
+
* read on every call, and an ongoing action resolving its `opts.params`
|
|
1323
|
+
* mid-gesture sees the value as it is then. */
|
|
1324
|
+
params?: ActionParams<P>;
|
|
1325
|
+
/** Bindings added after the action's own `defaultBinding`. */
|
|
1326
|
+
bindings?: readonly TunedBinding<P>[];
|
|
1327
|
+
/** Keys for some of the action's `modifierRoles`, by role id. A binding's
|
|
1328
|
+
* own `opts.roles` is laid over these. */
|
|
1329
|
+
roles?: RoleRebinds;
|
|
1330
|
+
/** Told the params the last-operation panel just set for the action, which hold for the rest of the session.
|
|
1331
|
+
* A tuning whose `params` thunk reads a host's store writes the ones it supplies back there, so the store, and
|
|
1332
|
+
* whatever draws it, says what the next run will use rather than overriding it. */
|
|
1333
|
+
keep?(params: Readonly<Record<string, unknown>>): void;
|
|
1334
|
+
}
|
|
1335
|
+
/** `action` with each tuning applied in turn, a later one's params over an
|
|
1336
|
+
* earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
|
|
1337
|
+
declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* The last operation: the latest run of an action that declares `params` and
|
|
1341
|
+
* committed one history entry, kept so its params can be changed after the
|
|
1342
|
+
* fact. A change undoes that entry and runs the action again in its place: an
|
|
1343
|
+
* immediate action is run again with the params it was handed, and a gesture
|
|
1344
|
+
* is replayed by its handle (`OngoingHandle.replay`).
|
|
1345
|
+
*/
|
|
1346
|
+
|
|
1347
|
+
/** One operation that can still be adjusted. */
|
|
1348
|
+
interface LastOperation {
|
|
1349
|
+
id: string;
|
|
1350
|
+
/** What a panel titles it: the form's name, else the action's label. */
|
|
1351
|
+
label: string;
|
|
1352
|
+
/** The action's `params` form. */
|
|
1353
|
+
schema: PrefGroup;
|
|
1354
|
+
/** What it ran with, one value per leaf of `schema`, by the leaf's path. */
|
|
1355
|
+
values: Readonly<Record<string, unknown>>;
|
|
1356
|
+
}
|
|
1357
|
+
/** The last operation of one actions tree, and the two things to do with it. */
|
|
1358
|
+
interface LastOperationStore {
|
|
1359
|
+
/** The operation, or `null` once anything else reached the history, or it was dismissed. */
|
|
1360
|
+
get(): LastOperation | null;
|
|
1361
|
+
subscribe(listener: () => void): () => void;
|
|
1362
|
+
/**
|
|
1363
|
+
* Undo the operation's entry, put back the selection it ran on, and run it again with `patch` laid over its
|
|
1364
|
+
* values (a gesture is replayed by its handle), keeping the entry that makes in place of the old one: the
|
|
1365
|
+
* history, or the journal open on it, still holds one entry for it. The merged values become the action's
|
|
1366
|
+
* params for the rest of the session, under any tuning, and each tuning that keeps them is handed them
|
|
1367
|
+
* (`ActionTuning.keep`). Returns whether it ran.
|
|
1368
|
+
*/
|
|
1369
|
+
adjust(patch: Readonly<Record<string, unknown>>): boolean;
|
|
1370
|
+
/** Forget the operation; the scene and the history stay as they are. */
|
|
1371
|
+
dismiss(): void;
|
|
1372
|
+
}
|
|
1373
|
+
/** Every selection a host holds, captured: calling what `take` returns puts all of it back. */
|
|
1374
|
+
interface SelectionSnapshotDep {
|
|
1375
|
+
take(): () => void;
|
|
1376
|
+
}
|
|
1377
|
+
/** What the store reads from the scope an operation ran in. */
|
|
1378
|
+
interface LastOperationHost {
|
|
1379
|
+
history(): History | null;
|
|
1380
|
+
takeSelection(): (() => void) | null;
|
|
1381
|
+
/** The params a run of `id` handed `given` starts with: every tuning's, `given` laid over them. */
|
|
1382
|
+
paramsOf(id: string, given: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
1383
|
+
/** Run the action live for `id` here with `params`, as `trigger` would. */
|
|
1384
|
+
rerun(id: string, params: Record<string, unknown>): void;
|
|
1385
|
+
/** Hand `params`, which a panel change made `id`'s for the session, to each tuning of `id` that keeps them. */
|
|
1386
|
+
keep(id: string, params: Readonly<Record<string, unknown>>): void;
|
|
1387
|
+
}
|
|
1388
|
+
/** The store one actions tree keeps at its root. `onSession` is told when an action's session params change. */
|
|
1389
|
+
declare class LastOperationKeeper implements LastOperationStore {
|
|
1390
|
+
private readonly onSession;
|
|
1391
|
+
private held;
|
|
1392
|
+
private busy;
|
|
1393
|
+
private readonly listeners;
|
|
1394
|
+
private readonly session;
|
|
1395
|
+
constructor(onSession: () => void);
|
|
1396
|
+
readonly get: () => LastOperation | null;
|
|
1397
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
1398
|
+
readonly dismiss: () => void;
|
|
1399
|
+
readonly adjust: (patch: Readonly<Record<string, unknown>>) => boolean;
|
|
1400
|
+
/** The tuning holding `id`'s session params, laid under every other; `null` before it has any. */
|
|
1401
|
+
sessionTuning(id: string): ActionTuning | null;
|
|
1402
|
+
/** `action` with its run kept as the last operation when it commits one entry: an immediate action declaring
|
|
1403
|
+
* `params`, or an ongoing one declaring them whose gesture's handle can `replay`, and in either case only a
|
|
1404
|
+
* run its `params` answers a form for. Any other action comes back as it is. */
|
|
1405
|
+
wrap(action: Action, host: LastOperationHost): Action;
|
|
1406
|
+
/** `invoker` with each gesture whose handle can `replay` kept as the last operation when its commit leaves
|
|
1407
|
+
* one entry. The selection is taken at the press and the params at the release, as the commit read them. */
|
|
1408
|
+
private ongoing;
|
|
1409
|
+
private hold;
|
|
1410
|
+
private remember;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/** What a key no open keymap binds does: `'modal'` swallows it and leaves the
|
|
1414
|
+
* keymap open; `'cascade'` closes the keymap and dispatches it as if none were. */
|
|
1415
|
+
type KeymapUnbound = 'modal' | 'cascade';
|
|
1416
|
+
/** What holds a level opened with `{ hold: true }` open: no key releases it, so it closes on Escape. */
|
|
1417
|
+
declare const KEYMAP_HOLD = "hold";
|
|
1418
|
+
/** One open level of a {@link KeymapStack}. */
|
|
1419
|
+
interface OpenKeymap {
|
|
1420
|
+
readonly id: string;
|
|
1421
|
+
readonly label: string;
|
|
1422
|
+
/** The keymap's bindings as declared, for a hint to list. */
|
|
1423
|
+
readonly bindings: readonly GestureBinding[];
|
|
1424
|
+
/** The gesture id of the leader holding this level open, while it is held,
|
|
1425
|
+
* or {@link KEYMAP_HOLD}. */
|
|
1426
|
+
readonly heldBy?: string;
|
|
1427
|
+
/** Whether input arrived while the leader was held, so its release closes the level. */
|
|
1428
|
+
readonly usedWhileHeld: boolean;
|
|
1429
|
+
}
|
|
1430
|
+
/** What the keymaps made of the raw input in progress: `'taken'` when one of
|
|
1431
|
+
* them handled it, `'cascaded'` when it went on past them, `null` before either. */
|
|
1432
|
+
type KeymapVerdict = 'taken' | 'cascaded' | null;
|
|
1433
|
+
/** The keymaps open on one surface, innermost last; a canvas's `keymaps` dep. */
|
|
1434
|
+
interface KeymapStack {
|
|
1435
|
+
/** Every open level, outermost first. */
|
|
1436
|
+
open(): readonly OpenKeymap[];
|
|
1437
|
+
innermost(): OpenKeymap | null;
|
|
1438
|
+
/** Open a level. `heldBy` names what holds it open; `bindings` are what a hint lists. */
|
|
1439
|
+
push(id: string, label: string, opts?: {
|
|
1440
|
+
heldBy?: string;
|
|
1441
|
+
bindings?: readonly GestureBinding[];
|
|
1442
|
+
}): void;
|
|
1443
|
+
/** Mark the levels the last raw input opened as held by `heldBy`: the key
|
|
1444
|
+
* that opened them is still down. False when it opened none. */
|
|
1445
|
+
holdOpened(heldBy: string): boolean;
|
|
1446
|
+
/** The leader `heldBy` came up: close its level, those above it and the
|
|
1447
|
+
* tapped ones beneath it if input arrived meanwhile, else leave it open
|
|
1448
|
+
* for one more input. */
|
|
1449
|
+
release(heldBy: string): void;
|
|
1450
|
+
/** Close the innermost level: Escape. */
|
|
1451
|
+
closeInnermost(): void;
|
|
1452
|
+
/** One of the keymaps took this input, or a pointer gesture went past them.
|
|
1453
|
+
* Marks held levels used and closes once-levels down to the innermost held one. */
|
|
1454
|
+
inputSeen(): void;
|
|
1455
|
+
/** A key no keymap binds was swallowed: held levels count it as input, and nothing closes. */
|
|
1456
|
+
swallowed(): void;
|
|
1457
|
+
/** This input went on past the keymaps: as `inputSeen`, remembered as a cascade. */
|
|
1458
|
+
cascaded(): void;
|
|
1459
|
+
/** What the keymaps made of the input in progress. */
|
|
1460
|
+
verdict(): KeymapVerdict;
|
|
1461
|
+
closeAll(): void;
|
|
1462
|
+
/** Run `fn` as one raw input: closes made inside apply when it returns. Nests. */
|
|
1463
|
+
batch<T>(fn: () => T): T;
|
|
1464
|
+
subscribe(listener: () => void): () => void;
|
|
1465
|
+
getVersion(): number;
|
|
1466
|
+
}
|
|
1467
|
+
/** An empty {@link KeymapStack}. `useGestureDispatcher` makes one per surface. */
|
|
1468
|
+
declare function createKeymapStack(): KeymapStack;
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
|
|
1472
|
+
* the actions-layer binding-scope / matchBest logic on top.
|
|
1473
|
+
*/
|
|
1474
|
+
|
|
1475
|
+
/** Where a binding came from, which is also its priority: a held hotkey beats
|
|
1476
|
+
* the active tool, which beats bindings that are always in scope. */
|
|
1477
|
+
type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
1478
|
+
/** A binding paired with where it came from, ready to be matched against an
|
|
1479
|
+
* event. */
|
|
1480
|
+
interface ScopedBinding {
|
|
1481
|
+
binding: GestureBinding;
|
|
1482
|
+
scope: BindingScope;
|
|
1483
|
+
/** Id of the tool or contribution that declared this binding —
|
|
1484
|
+
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
1485
|
+
* action's own binding. */
|
|
1486
|
+
ownerId: string | null;
|
|
1487
|
+
}
|
|
1488
|
+
/** The binding that won a match. */
|
|
1489
|
+
interface MatchResult {
|
|
1490
|
+
binding: GestureBinding;
|
|
1491
|
+
scope: BindingScope;
|
|
1492
|
+
/** Id of the entry that declared the binding — propagated from
|
|
1493
|
+
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
1494
|
+
ownerId: string | null;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* True when a spec's target actually consults the affordance hit rather than
|
|
1498
|
+
* only the body classification. `kindOf` predicates are handed the hit;
|
|
1499
|
+
* `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
|
|
1500
|
+
* `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
|
|
1501
|
+
* `bodyTarget` / `bodyKind` and never see it.
|
|
1502
|
+
*
|
|
1503
|
+
* A predicate declares which it is with `readsAffordance`; one that declares
|
|
1504
|
+
* nothing, which only untyped code can write, counts as not reading the hit.
|
|
1505
|
+
*/
|
|
1506
|
+
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
1507
|
+
/** The first of {@link matchSorted}'s matches, or null when nothing matches. */
|
|
1508
|
+
declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
|
|
1509
|
+
/**
|
|
1510
|
+
* Find every binding that matches the event, sorted by precedence
|
|
1511
|
+
* (best → worst). Ordering rules:
|
|
1512
|
+
*
|
|
1513
|
+
* - Scopes by priority: hotkey > active > ambient.
|
|
1514
|
+
* - Within a scope, more-specific bindings beat less-specific (see
|
|
1515
|
+
* `specificity()`).
|
|
1516
|
+
* - Among same-specificity bindings, first-declared wins.
|
|
1517
|
+
*/
|
|
1518
|
+
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
1519
|
+
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
1520
|
+
interface MatchOutcome {
|
|
1521
|
+
matches: MatchResult[];
|
|
1522
|
+
/** Bindings whose spec matched the event but which the claim barred, in
|
|
1523
|
+
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
1524
|
+
barred: MatchResult[];
|
|
1525
|
+
/** Owner of the exclusive claim, when one applied. */
|
|
1526
|
+
claimOwner?: string;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
1530
|
+
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
1531
|
+
* opt-in — the dispatcher asks only when it is recording.
|
|
1532
|
+
*/
|
|
1533
|
+
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
1534
|
+
collectBarred?: boolean;
|
|
1535
|
+
warn?: (message: string) => void;
|
|
1536
|
+
}): MatchOutcome;
|
|
1537
|
+
|
|
1567
1538
|
/**
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1539
|
+
* How one input was resolved: every binding that matched it, what each filter
|
|
1540
|
+
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
1541
|
+
* that order did. The dispatcher fills one per input in dev builds, and
|
|
1542
|
+
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
1543
|
+
*
|
|
1544
|
+
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
1545
|
+
* buffer, serialized, or built by hand as a fixture.
|
|
1572
1546
|
*/
|
|
1573
|
-
interface
|
|
1574
|
-
|
|
1547
|
+
interface DispatchRecord {
|
|
1548
|
+
kind: 'dispatch';
|
|
1549
|
+
ts: number;
|
|
1550
|
+
input: DispatchRecordInput;
|
|
1551
|
+
/** Every binding whose spec matched the input, in matcher order, including
|
|
1552
|
+
* those a filter then dropped. */
|
|
1553
|
+
matched: readonly RecordCandidate[];
|
|
1554
|
+
dropped: readonly DroppedCandidate[];
|
|
1555
|
+
/** The candidates that survived both filters, best first. */
|
|
1556
|
+
ranked: readonly RankedCandidate[];
|
|
1557
|
+
/** True when nothing was invoked: the record answers "what would happen". */
|
|
1558
|
+
predicted: boolean;
|
|
1559
|
+
/** Action id of the candidate that fired (or would), else `null`. */
|
|
1560
|
+
fired: string | null;
|
|
1561
|
+
outcome: 'handled' | 'unhandled';
|
|
1575
1562
|
}
|
|
1576
|
-
/**
|
|
1577
|
-
|
|
1578
|
-
|
|
1563
|
+
/** The input a {@link DispatchRecord} describes, reduced to plain data. */
|
|
1564
|
+
interface DispatchRecordInput {
|
|
1565
|
+
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
1566
|
+
eventKind: string;
|
|
1567
|
+
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
1568
|
+
key?: string;
|
|
1569
|
+
modifiers: {
|
|
1570
|
+
alt: boolean;
|
|
1571
|
+
ctrl: boolean;
|
|
1572
|
+
meta: boolean;
|
|
1573
|
+
shift: boolean;
|
|
1574
|
+
};
|
|
1575
|
+
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
1576
|
+
viewId: string | null;
|
|
1577
|
+
/** World point, for events that carry one. */
|
|
1578
|
+
world?: {
|
|
1579
|
+
x: number;
|
|
1580
|
+
y: number;
|
|
1581
|
+
};
|
|
1582
|
+
/** Active mode id, when the host supplies a rule context. */
|
|
1583
|
+
mode?: string;
|
|
1584
|
+
}
|
|
1585
|
+
/** One matched binding. */
|
|
1586
|
+
interface RecordCandidate {
|
|
1587
|
+
actionId: string;
|
|
1588
|
+
/** The binding's spec in route grammar — one per arg alternative. */
|
|
1589
|
+
routes: readonly string[];
|
|
1590
|
+
scope: BindingScope;
|
|
1591
|
+
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
1592
|
+
ownerId: string | null;
|
|
1593
|
+
/** Whether the binding's `views` names the routed view. */
|
|
1594
|
+
namesView: boolean;
|
|
1595
|
+
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
1596
|
+
specificity: readonly [number, number, number, number];
|
|
1597
|
+
/** The action's `eligible` rule as text, when it declares one. */
|
|
1598
|
+
eligible?: string;
|
|
1599
|
+
}
|
|
1600
|
+
/** A matched candidate a filter removed before ranking, and which filter. */
|
|
1601
|
+
type DroppedCandidate =
|
|
1602
|
+
/** An exclusive claim on the input barred bindings that do not read the
|
|
1603
|
+
* claim's affordance. */
|
|
1604
|
+
{
|
|
1605
|
+
candidate: RecordCandidate;
|
|
1606
|
+
filter: 'claim';
|
|
1607
|
+
owner?: string;
|
|
1608
|
+
}
|
|
1609
|
+
/** The action's `eligible` rule does not hold. */
|
|
1610
|
+
| {
|
|
1611
|
+
candidate: RecordCandidate;
|
|
1612
|
+
filter: 'ineligible';
|
|
1613
|
+
rule: string;
|
|
1614
|
+
};
|
|
1615
|
+
/** One field of the `specificity` tuple, in the order it is compared. */
|
|
1616
|
+
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
1617
|
+
/** The ranking step that put a candidate below the one before it. */
|
|
1618
|
+
type PlacedBy = {
|
|
1619
|
+
step: 'first';
|
|
1620
|
+
}
|
|
1621
|
+
/** The one above names the routed view; this one does not. */
|
|
1622
|
+
| {
|
|
1623
|
+
step: 'view';
|
|
1624
|
+
}
|
|
1625
|
+
/** The one above sits in a higher scope tier. */
|
|
1626
|
+
| {
|
|
1627
|
+
step: 'tier';
|
|
1628
|
+
}
|
|
1629
|
+
/** Same tier; the one above is more specific in `part`. */
|
|
1630
|
+
| {
|
|
1631
|
+
step: 'specificity';
|
|
1632
|
+
part: SpecificityPart;
|
|
1633
|
+
}
|
|
1634
|
+
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
1635
|
+
| {
|
|
1636
|
+
step: 'context';
|
|
1637
|
+
}
|
|
1638
|
+
/** Tied on everything; registration order. */
|
|
1639
|
+
| {
|
|
1640
|
+
step: 'order';
|
|
1641
|
+
};
|
|
1642
|
+
/** What the walk did with one ranked candidate. */
|
|
1643
|
+
type WalkStep = {
|
|
1644
|
+
kind: 'fired';
|
|
1645
|
+
}
|
|
1646
|
+
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
1647
|
+
* so an ongoing action here may still bail at `start()` for real. */
|
|
1648
|
+
| {
|
|
1649
|
+
kind: 'would-fire';
|
|
1650
|
+
}
|
|
1651
|
+
/** `enabled()` returned this reason. */
|
|
1652
|
+
| {
|
|
1653
|
+
kind: 'declined';
|
|
1654
|
+
reason: string;
|
|
1655
|
+
}
|
|
1656
|
+
/** An ongoing action's `start()` returned an empty handle, or an
|
|
1657
|
+
* immediate one's `run()` returned `'pass'`. */
|
|
1658
|
+
| {
|
|
1659
|
+
kind: 'empty-handle';
|
|
1660
|
+
}
|
|
1661
|
+
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
1662
|
+
| {
|
|
1663
|
+
kind: 'misbound';
|
|
1664
|
+
}
|
|
1665
|
+
/** Its action was already tried higher up; each runs at most once. */
|
|
1666
|
+
| {
|
|
1667
|
+
kind: 'duplicate';
|
|
1668
|
+
} | {
|
|
1669
|
+
kind: 'no-such-action';
|
|
1670
|
+
}
|
|
1671
|
+
/** Below the winner; never asked. */
|
|
1672
|
+
| {
|
|
1673
|
+
kind: 'not-asked';
|
|
1674
|
+
}
|
|
1675
|
+
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
1676
|
+
| {
|
|
1677
|
+
kind: 'outranked';
|
|
1678
|
+
};
|
|
1679
|
+
/** A candidate that survived the filters: why it sits where it does, and
|
|
1680
|
+
* what the walk did with it. */
|
|
1681
|
+
interface RankedCandidate {
|
|
1682
|
+
candidate: RecordCandidate;
|
|
1683
|
+
placedBy: PlacedBy;
|
|
1684
|
+
walk: WalkStep;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/** One entry per "mode change" — a kit state transition that isn't a
|
|
1688
|
+
* dispatched input event but explains why an input was (or wasn't) handled
|
|
1689
|
+
* later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
|
|
1690
|
+
interface ModeSwitchLogEntry {
|
|
1691
|
+
kind: 'mode';
|
|
1692
|
+
ts: number;
|
|
1693
|
+
/** Logical mode name, e.g. `'editAnchors.editingId'`. */
|
|
1694
|
+
mode: string;
|
|
1695
|
+
from: string | null;
|
|
1696
|
+
to: string | null;
|
|
1697
|
+
/** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
|
|
1698
|
+
* `'selection-dropped target'`, etc. */
|
|
1699
|
+
detail?: string;
|
|
1700
|
+
}
|
|
1701
|
+
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
1702
|
+
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
1703
|
+
/** A rolling buffer that records in a dev build, and in any build while something watches it. */
|
|
1704
|
+
interface TraceRing<T> {
|
|
1705
|
+
/** Whether a push now keeps its entry. A caller building an entry checks first. */
|
|
1706
|
+
recording(): boolean;
|
|
1707
|
+
push(entry: T): void;
|
|
1708
|
+
/** The entries pushed after `cursor`, oldest first, and the cursor to pass
|
|
1709
|
+
* next time. Start from 0. Entries the buffer has dropped are skipped. */
|
|
1710
|
+
since(cursor: number): {
|
|
1711
|
+
entries: T[];
|
|
1712
|
+
cursor: number;
|
|
1713
|
+
};
|
|
1714
|
+
/** Turn recording on until the returned release is called. */
|
|
1715
|
+
watch(): () => void;
|
|
1716
|
+
/** Hear each kept push. Returns the unsubscribe. */
|
|
1717
|
+
subscribe(listener: () => void): () => void;
|
|
1718
|
+
}
|
|
1719
|
+
/** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
1720
|
+
declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
|
|
1721
|
+
/** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
|
|
1722
|
+
* every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
|
|
1723
|
+
interface DispatchTrace extends TraceRing<TraceLogEntry> {
|
|
1724
|
+
recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
1725
|
+
/** Offer the record for what a press at the pointer would do, built only
|
|
1726
|
+
* when {@link DispatchTrace.live} reads it. `null` withdraws it. */
|
|
1727
|
+
setLive(explain: (() => DispatchRecord) | null): void;
|
|
1728
|
+
live(): DispatchRecord | null;
|
|
1729
|
+
}
|
|
1730
|
+
/** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
1731
|
+
declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
|
|
1579
1732
|
|
|
1580
1733
|
/** Which canvas hit a menu answers. Every field given must match. */
|
|
1581
1734
|
interface MenuWhen {
|
|
@@ -1691,6 +1844,60 @@ declare function createMenuHost(src: MenuSources & {
|
|
|
1691
1844
|
activeTool(): string | null;
|
|
1692
1845
|
}): MenuHost;
|
|
1693
1846
|
|
|
1847
|
+
/**
|
|
1848
|
+
* Tool options: the settings a tool's user adjusts while it is active, such as
|
|
1849
|
+
* a brush's size and strength. A tool declares them as a prefs group whose
|
|
1850
|
+
* number leaves are params of the action it binds; the host holds their values
|
|
1851
|
+
* (`toolOptionValues` dep) and `<SceneCanvas>` tunes the action from them. A tool
|
|
1852
|
+
* whose options name a size or a strength also gets step actions for them,
|
|
1853
|
+
* bound to `[` `]` and `Shift+[` `Shift+]` while it is the active tool.
|
|
1854
|
+
*/
|
|
1855
|
+
|
|
1856
|
+
/** How a step action moves one option: by a factor, or by an amount. */
|
|
1857
|
+
interface ToolOptionStep {
|
|
1858
|
+
/** The option's key in `prefs`. */
|
|
1859
|
+
option: string;
|
|
1860
|
+
/** Each step multiplies (or divides) the value by this. */
|
|
1861
|
+
times?: number;
|
|
1862
|
+
/** Each step adds (or takes away) this. Used when `times` is absent. */
|
|
1863
|
+
plus?: number;
|
|
1864
|
+
}
|
|
1865
|
+
/** What a tool declares about its options. See {@link ToolDef.options}. */
|
|
1866
|
+
interface ToolOptions {
|
|
1867
|
+
/** The action the options are params of: each leaf of `prefs` is the param
|
|
1868
|
+
* of the same key. */
|
|
1869
|
+
actionId: string;
|
|
1870
|
+
/** The options, as number leaves keyed by param, with their bounds and
|
|
1871
|
+
* defaults. A host draws them from this (a group drawn `as: 'bar'` makes an
|
|
1872
|
+
* options row) and keeps their values in its prefs. */
|
|
1873
|
+
prefs: PrefGroup;
|
|
1874
|
+
/** The option `[` and `]` step: how large the tool acts. */
|
|
1875
|
+
size?: ToolOptionStep;
|
|
1876
|
+
/** The option `Shift+[` and `Shift+]` step: how hard it acts. */
|
|
1877
|
+
strength?: ToolOptionStep;
|
|
1878
|
+
}
|
|
1879
|
+
/** Where a host keeps tool options' values: the `toolOptionValues` dep. */
|
|
1880
|
+
interface ToolOptionValues {
|
|
1881
|
+
/** `toolId`'s values, sparse: an option missing from it reads its default.
|
|
1882
|
+
* The same object until one of them changes. */
|
|
1883
|
+
get(toolId: string): Readonly<Record<string, number>>;
|
|
1884
|
+
set(toolId: string, option: string, value: number): void;
|
|
1885
|
+
subscribe(listener: () => void): () => void;
|
|
1886
|
+
}
|
|
1887
|
+
/** A {@link ToolOptionValues} held in memory, for a host that keeps no prefs. */
|
|
1888
|
+
declare function createToolOptionValues(): ToolOptionValues;
|
|
1889
|
+
/** Every option's value for `toolId`: what `values` holds, else its default. */
|
|
1890
|
+
declare function toolOptionParams(toolId: string, options: ToolOptions, values: ToolOptionValues | undefined): Record<string, number>;
|
|
1891
|
+
/** `value` moved one step up (`dir` 1) or down (-1), within the leaf's bounds
|
|
1892
|
+
* and rounded to its `step`. */
|
|
1893
|
+
declare function steppedOption(leaf: PrefNumber, value: number, step: ToolOptionStep, dir: 1 | -1): number;
|
|
1894
|
+
/** The step actions for `toolId`'s size and strength options, and the key
|
|
1895
|
+
* bindings that reach them. */
|
|
1896
|
+
declare function toolOptionSteps(toolId: string, toolLabel: string, options: ToolOptions): {
|
|
1897
|
+
actions: Action[];
|
|
1898
|
+
bindings: GestureBinding[];
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1694
1901
|
/**
|
|
1695
1902
|
* The tree both input registries hang on. A provider is a root; every canvas
|
|
1696
1903
|
* mounts a scope under the registry in scope; a yoke sits between the two. A
|
|
@@ -1791,40 +1998,6 @@ declare function ActiveToolContextProviderIfRoot({ children, }: {
|
|
|
1791
1998
|
children: ReactNode;
|
|
1792
1999
|
}): react.JSX.Element;
|
|
1793
2000
|
|
|
1794
|
-
/**
|
|
1795
|
-
* Tuning: what a host changes about an action it did not write — the params
|
|
1796
|
-
* every invocation starts from, and bindings added beside the action's own —
|
|
1797
|
-
* without restating the descriptor. `ActionsRegistry.tune` holds tunings and
|
|
1798
|
-
* applies them to whatever descriptor is live for the id.
|
|
1799
|
-
*/
|
|
1800
|
-
|
|
1801
|
-
/** An action's params, or a thunk read each time the action asks for them. */
|
|
1802
|
-
type ActionParams<P = Record<string, unknown>> = Partial<P> | (() => Partial<P>);
|
|
1803
|
-
/** A binding added for an action: a gesture alone, or a gesture with the
|
|
1804
|
-
* options it passes — the {@link BoundGesture} shape, its params typed. */
|
|
1805
|
-
type TunedBinding<P = Record<string, unknown>> = GestureSpec | {
|
|
1806
|
-
spec: GestureSpec;
|
|
1807
|
-
opts: Omit<BindingOpts, 'params'> & {
|
|
1808
|
-
params?: ActionParams<P>;
|
|
1809
|
-
};
|
|
1810
|
-
};
|
|
1811
|
-
/** What a host changes about one action. */
|
|
1812
|
-
interface ActionTuning<P = Record<string, unknown>> {
|
|
1813
|
-
/** Params every invocation starts from. What a binding or a `trigger` call
|
|
1814
|
-
* passes is laid over them, so these fill in what it leaves out. A thunk is
|
|
1815
|
-
* read on every call, and an ongoing action resolving its `opts.params`
|
|
1816
|
-
* mid-gesture sees the value as it is then. */
|
|
1817
|
-
params?: ActionParams<P>;
|
|
1818
|
-
/** Bindings added after the action's own `defaultBinding`. */
|
|
1819
|
-
bindings?: readonly TunedBinding<P>[];
|
|
1820
|
-
/** Keys for some of the action's `modifierRoles`, by role id. A binding's
|
|
1821
|
-
* own `opts.roles` is laid over these. */
|
|
1822
|
-
roles?: RoleRebinds;
|
|
1823
|
-
}
|
|
1824
|
-
/** `action` with each tuning applied in turn, a later one's params over an
|
|
1825
|
-
* earlier one's. Applying a tuning to a copy it already tuned changes nothing. */
|
|
1826
|
-
declare function tuneAction(action: Action, tunings: readonly ActionTuning[]): Action;
|
|
1827
|
-
|
|
1828
2001
|
/**
|
|
1829
2002
|
* The entries an action contributes to a bar, menu or palette — one per
|
|
1830
2003
|
* variant, or the action itself when it declares none.
|
|
@@ -3045,7 +3218,9 @@ declare function modeShortcutSpec(shortcut: string): KeySpec;
|
|
|
3045
3218
|
* The bindings ride the hotkey tier, so leaving a mode outranks what the same
|
|
3046
3219
|
* key does otherwise (Escape clearing the selection), and they yield while a
|
|
3047
3220
|
* gesture is in flight, so Escape mid-drag still cancels the drag first. Keys
|
|
3048
|
-
* typed into an editable element never reach them.
|
|
3221
|
+
* typed into an editable element never reach them. A mode's `exit` and
|
|
3222
|
+
* `cancel`, pressed while a tool other than its `defaultTool` is active,
|
|
3223
|
+
* return to that tool instead (`returnToModeDefault`), so the next press leaves.
|
|
3049
3224
|
*/
|
|
3050
3225
|
declare function modeShortcuts(registry: ModeRegistry, handlers: ModeShortcutHandlers): Contribution<never>;
|
|
3051
3226
|
|
|
@@ -3203,6 +3378,10 @@ interface Tool<TOverlay = KernelOverlay> extends Contribution<TOverlay> {
|
|
|
3203
3378
|
* `opts.params.toolId` set so the invoker knows which tool to switch to).
|
|
3204
3379
|
* A tool with none has no activation key. */
|
|
3205
3380
|
keybinding?: ToolKeybinding;
|
|
3381
|
+
/** The settings its user adjusts while it is active; see `ToolDef.options`. */
|
|
3382
|
+
options?: ToolOptions;
|
|
3383
|
+
/** Chrome hidden while this is the active tool; see `ToolDef.hides`. */
|
|
3384
|
+
hides?: readonly string[];
|
|
3206
3385
|
cursor?: CursorSpec | (() => CursorSpec);
|
|
3207
3386
|
/** The tool became live: it took the active slot, or its `hotkey` went
|
|
3208
3387
|
* down, while it held neither. A lifecycle moment has no pointer, hit or
|
|
@@ -3250,6 +3429,17 @@ interface ToolDef<TOverlay = KernelOverlay> {
|
|
|
3250
3429
|
actions?: Action[];
|
|
3251
3430
|
/** Menus this tool adds while it is active. */
|
|
3252
3431
|
menus?: MenuDef[];
|
|
3432
|
+
/**
|
|
3433
|
+
* The settings its user adjusts while it is active, as params of the action
|
|
3434
|
+
* it binds: a brush's size and strength. `<SceneCanvas>` tunes that action
|
|
3435
|
+
* from the `toolOptionValues` dep, and `defineTool` adds step actions for the
|
|
3436
|
+
* size and strength, keyed to `[` `]` and `Shift+[` `Shift+]` while this is
|
|
3437
|
+
* the active tool. See {@link import('./toolOptions').ToolOptions}.
|
|
3438
|
+
*/
|
|
3439
|
+
options?: ToolOptions;
|
|
3440
|
+
/** Chrome ids (`'selection.resize-handles'`) hidden, and not hit-tested,
|
|
3441
|
+
* while this is the active tool: chrome its own controls stand in for. */
|
|
3442
|
+
hides?: readonly string[];
|
|
3253
3443
|
/** Hook name as exported from the kit barrel (e.g. `'useHandTool'`).
|
|
3254
3444
|
* Set by built-in hooks for inspector / debugging. Consumer-authored
|
|
3255
3445
|
* tools may set this to surface their hook name; omitted is fine.
|
|
@@ -3306,6 +3496,15 @@ interface ToolDef<TOverlay = KernelOverlay> {
|
|
|
3306
3496
|
*/
|
|
3307
3497
|
declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
|
|
3308
3498
|
|
|
3499
|
+
/**
|
|
3500
|
+
* Make the active mode's default tool active when another tool holds the
|
|
3501
|
+
* slot, and say whether it did. A key that leaves a mode calls this first, so
|
|
3502
|
+
* one press returns to the mode's tool and the next leaves: the mode shortcuts'
|
|
3503
|
+
* `exit` and `cancel`, and core's `exitPathEdit`. Reads the `activeTool` and
|
|
3504
|
+
* `modeDefaultTool` deps, which the caller must require.
|
|
3505
|
+
*/
|
|
3506
|
+
declare function returnToModeDefault(deps: ActionDeps | undefined): boolean;
|
|
3507
|
+
|
|
3309
3508
|
/**
|
|
3310
3509
|
* Tool sets: a named group of tool ids, the unit a consumer opts into whole.
|
|
3311
3510
|
* A set names ids, never tools, because a tool is built by a hook that holds
|
|
@@ -3754,12 +3953,20 @@ declare function pickMenu(defs: readonly MenuDef[], hit: MenuHit): MenuDef | und
|
|
|
3754
3953
|
interface DepSchema {
|
|
3755
3954
|
/** Currently active tool id + hotkey-hold stack. */
|
|
3756
3955
|
activeTool: ActiveToolContextValue;
|
|
3956
|
+
/** Where the host keeps its tools' option values (`ToolDef.options`).
|
|
3957
|
+
* Absent: every option is its default, and the step actions do nothing. */
|
|
3958
|
+
toolOptionValues?: ToolOptionValues;
|
|
3757
3959
|
/** The menu host of the `ActionsProvider` in scope. */
|
|
3758
3960
|
menuHost?: MenuHost;
|
|
3759
3961
|
/** The canvas's dispatch trace, for a dep to record a mode switch into. */
|
|
3760
3962
|
dispatchTrace?: DispatchTrace;
|
|
3761
3963
|
/** The keymaps open on the canvas: what a leader opens and a hint reads. */
|
|
3762
3964
|
keymaps?: KeymapStack;
|
|
3965
|
+
/** Every selection the host holds, captured so the last operation can put it back before it reruns. */
|
|
3966
|
+
selectionSnapshot?: SelectionSnapshotDep;
|
|
3967
|
+
/** The tool the active mode starts on (`ModeDefinition.defaultTool`), when the canvas mounts it;
|
|
3968
|
+
* else null. `<SceneCanvas>` sources it. */
|
|
3969
|
+
modeDefaultTool?: () => string | null;
|
|
3763
3970
|
}
|
|
3764
3971
|
/** Every dep name declared by anything that has merged into {@link DepSchema}. */
|
|
3765
3972
|
type DepName = keyof DepSchema;
|
|
@@ -3807,4 +4014,4 @@ type KernelOverlay = OverlaySchema extends {
|
|
|
3807
4014
|
overlay: infer O;
|
|
3808
4015
|
} ? O : unknown;
|
|
3809
4016
|
|
|
3810
|
-
export { type
|
|
4017
|
+
export { type AssemblyInputs as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionEntry as E, type ActionItem as F, type ActionParamSchema as G, type ActionParams as H, type ActionPresentation as I, type ActionSettings as J, type KeymapDescription as K, LastOperationKeeper as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionSettingsState as P, type ActionShortcut as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type ActionSource as U, type ActionTuning as V, type WorldPoint as W, type ActionTunings as X, type ActionUnavailable as Y, type ActionsProp as Z, type AnyTool as _, type DepName as a, type MenuSources as a$, BROWSER_RESERVED_CHORDS as a0, type BindingOpts as a1, type BindingOverrides as a2, type BindingScope as a3, type BindingSource as a4, type BodyPredicate as a5, type Bounds as a6, type BuildRuleCtxArgs as a7, COARSE_TARGET_SCALE as a8, type CaptureGestureOptions as a9, type EligibilityState as aA, type GestureBinding as aB, type GestureKitSettings as aC, type HandleKind as aD, type HitShape as aE, type HotkeyTrigger as aF, INGEST_STRING_MIMES as aG, type ImmediateInvoker as aH, type ImmediateParams as aI, type InvocationCtx as aJ, type Invoker as aK, KEYMAP_HOLD as aL, type KeyBinding as aM, type KeyReach as aN, type KeymapDef as aO, type KeymapRow as aP, type KeymapStack as aQ, type KeymapUnbound as aR, type KeymapVerdict as aS, type LongPressState as aT, type LongPressStore as aU, type MatchOutcome as aV, type MatchResult as aW, type MenuAccess as aX, type MenuCustomAt as aY, type MenuHit as aZ, type MenuOpenRequest as a_, type CaptureKeyOptions as aa, type CaptureKind as ab, type CapturedSpec as ac, type Carried as ad, type Carry as ae, type CarryCallbacks as af, type CarryPayload as ag, type CarryPointer as ah, type CarryStage as ai, type Condition as aj, type Conflict as ak, type ContributionChrome as al, type ContributionDeps as am, type ContributionRouting as an, DEFAULT_ALLOWED_CAPABILITIES as ao, DEFAULT_DEVICE_PROFILE as ap, type DebugSink as aq, type DepListing as ar, type DetectedDeviceFacts as as, type DeviceProfile as at, type DispatchRecord as au, type DispatchRecordInput as av, type DispatcherContext as aw, type DragSample as ax, type DroppedCandidate as ay, type Eligibility as az, type DepSource as b, actionCursor as b$, type MenuWhen as b0, type ModeShortcutHandlers as b1, type ModeSwitchLogEntry as b2, type ModifierRole as b3, type ModifierRoles as b4, type ModifierState as b5, NEVER as b6, type OngoingHandle as b7, type OngoingInvoker as b8, type OngoingOverlay as b9, type ScopedBinding as bA, type ScratchKey as bB, type ScratchStore as bC, type SelectionApi as bD, type SelectionSnapshotDep as bE, type Selector as bF, type SpecificityPart as bG, TOOL_OFFHAND_ID as bH, type ToolDef as bI, type ToolEntry as bJ, type ToolKeybinding as bK, type ToolModifiers as bL, type ToolOffhandBindingSpec as bM, type ToolOptionStep as bN, type ToolOptionValues as bO, type ToolOptions as bP, type ToolPresentation as bQ, type ToolScopes as bR, type ToolSet as bS, type ToolSlot as bT, type TraceLogEntry as bU, type TraceRing as bV, type TunedBinding as bW, type UiOngoingControl as bX, type ViewportGestureKind as bY, type WalkStep as bZ, actionBindings as b_, type OpenKeymap as ba, type OpenMenu as bb, type OverlayRole as bc, type OverlaySchema as bd, type OwnedMenuDef as be, PREDICATE_TARGET as bf, type PendingIngestItem as bg, type PendingLongPress as bh, type PlacedBy as bi, type Point2 as bj, type PointerSession as bk, type PointerSessionCallbacks as bl, type PointerSessionCancelReason as bm, type PointerSessionOptions as bn, type RankedCandidate as bo, type RecordCandidate as bp, type RegistryEntry as bq, type ReservedChord as br, type ResizeAnchor as bs, type ResolveAllOptions as bt, type ResolveOnlyResult as bu, type ResolvedBindingOverrides as bv, type ResolvedCandidate as bw, type RoleKeys as bx, type RoleRebinds as by, type Rule as bz, type ActionDeps as c, keymapLeaderId as c$, actionItemTitle as c0, actionItems as c1, actionSettingsParams as c2, actionShortcuts as c3, actionsTagged as c4, admitIngestMime as c5, assembleEntries as c6, bindingId as c7, browserReservedChord as c8, buildDepsFromRequires as c9, heldKeySpec as cA, historyKey as cB, holdPreview as cC, holdPreviewId as cD, isAnchor as cE, isAnchorOrControl as cF, isBalanceGrip as cG, isBody as cH, isControlHandle as cI, isCornerWidget as cJ, isDebugEnabled as cK, isEmpty as cL, isPathEditPart as cM, isResizeHandle as cN, isRotateHandle as cO, isSegment as cP, isSelectedBody as cQ, isTool as cR, isToolSet as cS, isUnselectedBody as cT, itemTypesFromDataTransfer as cU, itemsFromClipboardData as cV, itemsFromDataTransfer as cW, itemsFromFiles as cX, keySpecShortcut as cY, keymapContribution as cZ, keymapLeaderBindingId as c_, buildRouteRegistry as ca, buildRuleCtx as cb, buildToolOffhandBindings as cc, captureGesture as cd, captureKey as ce, clientToCanvas as cf, clientToCanvasRect as cg, createDepRegistry as ch, createDispatchTrace as ci, createDispatcher as cj, createKeymapStack as ck, createLongPressStore as cl, createMenuHost as cm, createToolOptionValues as cn, createTraceRing as co, defineTool as cp, deleteScratch as cq, describeKeymap as cr, describeRule as cs, dlog as ct, dwarn as cu, evaluate as cv, evaluateActive as cw, evaluateEnabled as cx, findConflicts as cy, getScratch as cz, type ActionsRegistry as d, keymapShortcuts as d0, keysHeld as d1, liveScope as d2, makeToolOffhandAction as d3, matchActionItem as d4, matchBest as d5, matchSorted as d6, matchSortedWithBarred as d7, matchesKeyBinding as d8, mergeContributions as d9, scratchKey as dA, setScratch as dB, startCarry as dC, steppedOption as dD, taggedActionItems as dE, targetConsultsAffordance as dF, toolIdsOf as dG, toolOptionParams as dH, toolOptionSteps as dI, toolSet as dJ, toolSetHas as dK, tuneAction as dL, validateActionDefaultBinding as dM, validateActionId as dN, withoutTools as dO, mergeMenuDefs as da, modeShortcutSpec as db, modeShortcuts as dc, modifierStateOf as dd, multiTouchGestureId as de, offhandKeyFor as df, openPointerSession as dg, overlaysOf as dh, overriddenBindings as di, pendingKindOf as dj, pickMenu as dk, pointerGestureId as dl, readActionSettings as dm, reportRouteConflicts as dn, resolveBindingOverrides as dp, resolveDeviceProfile as dq, resolveMenu as dr, resolveParams as ds, resolveSchemaMenu as dt, returnToModeDefault as du, roleReader as dv, routesForSpec as dw, ruleCanHoldIn as dx, rulesExclusive as dy, scopeBindings as dz, ActionDisabledReason as e, type ActionVariant as f, type LastOperationStore as g, type LastOperation as h, type MenuDef as i, DepNode as j, type KernelOverlay as k, type Tool as l, ActiveToolContextProvider as m, ActiveToolContextProviderIfRoot as n, type ActiveToolContextProviderProps as o, type ActiveToolContextValue as p, useOptionalActiveToolContext as q, type Dispatcher as r, type AffordanceHit as s, type DispatchTrace as t, useActiveToolContext as u, type LongPressOptions as v, type DepSchema as w, ALWAYS as x, type ActionDispatch as y, type ActionEnabledResult as z };
|