@weasel-js/routing 1.11.0 → 1.11.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-QBQQIFPI.js → chunk-KLBH7VFF.js} +172 -48
- package/dist/chunk-KLBH7VFF.js.map +1 -0
- package/dist/{index-CmfVvwWD.d.ts → index-Cb7ehEks.d.ts} +912 -766
- package/dist/index.d.ts +2 -2
- package/dist/index.js +34 -3
- package/dist/index.js.map +1 -1
- package/dist/{internal-21M1rTVo.d.ts → internal-Dcyz_cJv.d.ts} +9 -1
- package/dist/internal.d.ts +5 -5
- package/dist/react.d.ts +7 -7
- package/dist/react.js +84 -42
- package/dist/react.js.map +1 -1
- package/package.json +13 -13
- package/dist/chunk-QBQQIFPI.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { ModifierKey, ClaimableGesture, GestureSpec, InputEvent, TargetSpec, IngestItem, BodyTarget, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
|
|
2
|
+
import { FramedBox, View, Point } from '@weasel-js/geom';
|
|
3
|
+
import { CursorSpec } from '@weasel-js/cursor';
|
|
1
4
|
import { MenuEntry, Rect, MenuShortcut } from '@weasel-js/menu';
|
|
2
5
|
import { PrefGroup } from '@weasel-js/prefs';
|
|
3
|
-
import { FramedBox, View, Point } from '@weasel-js/geom';
|
|
4
6
|
import { CapabilityTag, ActiveMode, ModeRegistry } from '@weasel-js/modes';
|
|
5
|
-
import { ModifierKey, ClaimableGesture, GestureSpec, IngestItem, BodyTarget, InputEvent, TargetSpec, KeyHeldSpec, TargetPredicate, KeySpec, ParsedModifiers, GestureName } from '@weasel-js/gestures';
|
|
6
7
|
import * as _weasel_js_icons from '@weasel-js/icons';
|
|
7
8
|
import { IconRef } from '@weasel-js/icons';
|
|
8
|
-
import { CursorSpec } from '@weasel-js/cursor';
|
|
9
9
|
import * as react from 'react';
|
|
10
10
|
import { ReactNode } from 'react';
|
|
11
11
|
import { Emitter, QueryMatch } from '@weasel-js/registry';
|
|
@@ -169,489 +169,246 @@ type ResizeAnchor = {
|
|
|
169
169
|
y: 'min' | 'max' | 'free';
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
/** The chrome an in-flight gesture paints beside the scene. */
|
|
173
|
+
|
|
172
174
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* eligibility filter — and discarded.
|
|
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.
|
|
176
177
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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.
|
|
179
181
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
/** Where a dragged item would land: a reorder's insertion bar. */
|
|
194
|
+
| 'insert'
|
|
195
|
+
/** Where a dragged item is over and cannot land. */
|
|
196
|
+
| 'refused' | (string & {});
|
|
197
|
+
/**
|
|
198
|
+
* What an in-flight gesture proposes to paint *beside* the scene, returned by
|
|
199
|
+
* `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
|
|
200
|
+
* shape being dragged out, the line a slice would cut along.
|
|
201
|
+
*
|
|
202
|
+
* Every variant is world geometry and nothing else — no `DrawCommand`, no
|
|
203
|
+
* color, no coordinate space to choose — so a surface rendering the same
|
|
204
|
+
* gesture some other way can still read it. The painter owns every style
|
|
205
|
+
* decision.
|
|
206
|
+
*/
|
|
207
|
+
type OngoingOverlay = {
|
|
208
|
+
kind: 'marquee';
|
|
209
|
+
start: {
|
|
210
|
+
x: number;
|
|
211
|
+
y: number;
|
|
188
212
|
};
|
|
189
|
-
|
|
213
|
+
current: {
|
|
214
|
+
x: number;
|
|
215
|
+
y: number;
|
|
216
|
+
};
|
|
217
|
+
shiftHeld: boolean;
|
|
218
|
+
} | {
|
|
219
|
+
kind: 'lasso';
|
|
220
|
+
vertices: ReadonlyArray<{
|
|
221
|
+
x: number;
|
|
222
|
+
y: number;
|
|
223
|
+
}>;
|
|
224
|
+
current: {
|
|
225
|
+
x: number;
|
|
226
|
+
y: number;
|
|
227
|
+
};
|
|
228
|
+
shiftHeld: boolean;
|
|
229
|
+
} | {
|
|
190
230
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* is the only selector that ever read one and a number is all it needs. A
|
|
195
|
-
* host whose viewport is a camera has no `View` to hand over and would
|
|
196
|
-
* otherwise have to invent one to use eligibility at all; it omits this
|
|
197
|
-
* instead, and `zoomAtLeast` declines rather than matching on a fiction.
|
|
231
|
+
* An open run of world points the gesture is proposing — a cut line, a
|
|
232
|
+
* routed connector. `role` is the whole of what an action says about
|
|
233
|
+
* appearance.
|
|
198
234
|
*/
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
kind: 'polyline';
|
|
236
|
+
/** At least two points. Anything shorter is dropped in resolution. */
|
|
237
|
+
points: ReadonlyArray<{
|
|
238
|
+
x: number;
|
|
239
|
+
y: number;
|
|
240
|
+
}>;
|
|
241
|
+
/** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
|
|
242
|
+
role?: OverlayRole;
|
|
243
|
+
} | {
|
|
244
|
+
/**
|
|
245
|
+
* A round brush dragged through world points: the area it has swept so
|
|
246
|
+
* far, and its footprint at the last point. The radius is in screen
|
|
247
|
+
* pixels, so the footprint keeps its size at every zoom.
|
|
248
|
+
*/
|
|
249
|
+
kind: 'brush';
|
|
250
|
+
/** The brush's path so far, at least one point; the last is where it is now. */
|
|
251
|
+
points: ReadonlyArray<{
|
|
252
|
+
x: number;
|
|
253
|
+
y: number;
|
|
254
|
+
}>;
|
|
255
|
+
radiusPx: number;
|
|
256
|
+
} | {
|
|
257
|
+
/**
|
|
258
|
+
* Live insert-drag preview. Pre-commit there is no scene node to ghost
|
|
259
|
+
* via `previewIds()`/`previewPose()`, so insert paints its preview
|
|
260
|
+
* through the overlay instead.
|
|
223
261
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
*
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
multiActive: boolean;
|
|
240
|
-
modifiers: ModifierState;
|
|
241
|
-
action: {
|
|
242
|
-
kind: string | null;
|
|
243
|
-
id: string | null;
|
|
262
|
+
* `bounds` is the AABB of the current drag (start/current normalized).
|
|
263
|
+
* `extras` is the per-kind extras the action already collected; the
|
|
264
|
+
* overlay renderer narrows on `shape` and casts the field shape it
|
|
265
|
+
* expects.
|
|
266
|
+
*/
|
|
267
|
+
kind: 'insertPreview';
|
|
268
|
+
/** Which preview the dispatcher's overlay draws. The kit's own shape
|
|
269
|
+
* kinds are the ones it knows how to render; a consumer-defined kind
|
|
270
|
+
* is legal and simply gets no live preview. */
|
|
271
|
+
shape: string;
|
|
272
|
+
bounds: {
|
|
273
|
+
x: number;
|
|
274
|
+
y: number;
|
|
275
|
+
width: number;
|
|
276
|
+
height: number;
|
|
244
277
|
};
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
278
|
+
extras: unknown;
|
|
279
|
+
/** World-space point to paint a small "anchor" dot at, marking the
|
|
280
|
+
* click point — for radial shapes (polygon/star), where no vertex
|
|
281
|
+
* sits on it, and for any shape in center mode. */
|
|
282
|
+
anchorPoint?: {
|
|
283
|
+
x: number;
|
|
284
|
+
y: number;
|
|
285
|
+
};
|
|
286
|
+
/** World affines the shape will also be drawn under once it lands, by
|
|
287
|
+
* the container it is drawn into: the preview is painted under each. */
|
|
288
|
+
copies?: readonly (readonly [number, number, number, number, number, number])[];
|
|
289
|
+
};
|
|
290
|
+
/** A handle's overlays as a list: empty when it has none. */
|
|
291
|
+
declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Modifier roles: the modifiers an ongoing action reads while it runs, named
|
|
295
|
+
* by what they do rather than which key they are, so a binding or a tuning can
|
|
296
|
+
* move one to another key without forking the action.
|
|
297
|
+
*/
|
|
298
|
+
|
|
299
|
+
/** The keys that hold a role: any one of them held holds it. `'mod'` is Cmd
|
|
300
|
+
* on a Mac and Ctrl elsewhere. An empty list never holds it. */
|
|
301
|
+
type RoleKeys = readonly ModifierKey[];
|
|
302
|
+
/** One modifier an action reads mid-gesture. */
|
|
303
|
+
interface ModifierRole {
|
|
304
|
+
label: string;
|
|
305
|
+
/** The keys that hold it unless a tuning or a binding says otherwise. */
|
|
306
|
+
keys: RoleKeys;
|
|
256
307
|
}
|
|
308
|
+
/** An action's roles by id, e.g. `'handle.holdAngle'`. */
|
|
309
|
+
type ModifierRoles = Readonly<Record<string, ModifierRole>>;
|
|
310
|
+
/** Keys a tuning or a binding gives some of an action's roles. */
|
|
311
|
+
type RoleRebinds = Readonly<Record<string, RoleKeys>>;
|
|
312
|
+
/** Whether any of `keys` is held. */
|
|
313
|
+
declare function keysHeld(keys: RoleKeys, modifiers: ModifierState, isMac: boolean): boolean;
|
|
257
314
|
/**
|
|
258
|
-
*
|
|
315
|
+
* A reader for an action's own roles. It answers from `ctx.roles`, which the
|
|
316
|
+
* dispatcher resolves on every event, and falls back to the role's default
|
|
317
|
+
* keys where nothing resolved them: an invoker called directly, or a context
|
|
318
|
+
* a cancel-all built with no event.
|
|
319
|
+
*/
|
|
320
|
+
declare function roleReader<R extends ModifierRoles>(roles: R): (ctx: {
|
|
321
|
+
roles?: Readonly<Record<string, boolean>>;
|
|
322
|
+
modifiers: ModifierState;
|
|
323
|
+
}, id: keyof R & string) => boolean;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Invoker — pluggable invocation strategy for an Action.
|
|
259
327
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* recommended way (capability rules outlive new modes; mode rules have to
|
|
264
|
-
* be edited) would silently hide its chrome for every consumer that hasn't
|
|
265
|
-
* opted into modality. `NORMAL.allows` plus the implicit tags is what a
|
|
266
|
-
* mode-less canvas actually behaves like.
|
|
328
|
+
* An Action's `invoker` field describes how it's run. `immediate` invokers
|
|
329
|
+
* fire once (delete, align, undo). `ongoing` invokers open a phase machine
|
|
330
|
+
* driven by the dispatcher (move, resize, pinch-zoom).
|
|
267
331
|
*/
|
|
268
|
-
declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
|
|
269
|
-
/** Gather the current canvas and mode state into the context that action
|
|
270
|
-
* eligibility and chrome-visibility rules are evaluated against. */
|
|
271
|
-
declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
|
|
272
332
|
|
|
333
|
+
/** A 2D point in either world or screen coordinates; geom's `Point`. */
|
|
334
|
+
type Point2 = Point;
|
|
273
335
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
336
|
+
* Which UI affordance was hit at pointerdown. Populated by the dispatcher when
|
|
337
|
+
* the `affordanceAt` thunk is provided to `useGestureDispatcher`.
|
|
338
|
+
*
|
|
339
|
+
* `kind` is a discriminator string:
|
|
340
|
+
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
341
|
+
* `'handle:bottom-right'` — corner resize handles.
|
|
342
|
+
* - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
|
|
343
|
+
* edge resize handles.
|
|
344
|
+
* - `'rotate-handle'` — the rotation affordance.
|
|
345
|
+
* - `'anchor:N'` — a path anchor at index N.
|
|
276
346
|
*/
|
|
277
|
-
interface
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
347
|
+
interface AffordanceHit {
|
|
348
|
+
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
349
|
+
kind: string;
|
|
350
|
+
/** Id of whatever produced this hit — a kit affordance's `id`, or the
|
|
351
|
+
* registered layer's id. */
|
|
352
|
+
owner?: string;
|
|
353
|
+
/** `'exclusive'` means no binding may act on this point unless its target
|
|
354
|
+
* consults the affordance. `'shared'` (the default) competes on scope and
|
|
355
|
+
* specificity as bindings always have. */
|
|
356
|
+
strength?: 'exclusive' | 'shared';
|
|
357
|
+
/** Which gestures an exclusive claim bars. Omitted bars all of them. */
|
|
358
|
+
claimedKinds?: readonly ClaimableGesture[];
|
|
359
|
+
/** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
|
|
360
|
+
fixedPoint?: {
|
|
361
|
+
x: number;
|
|
362
|
+
y: number;
|
|
292
363
|
};
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
coarsePointer?: boolean;
|
|
310
|
-
/** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
|
|
311
|
-
* device is treated as `true`. */
|
|
312
|
-
canHover?: boolean;
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
|
|
316
|
-
* over `Selector` leaves. `when` is the escape hatch — its closure is
|
|
317
|
-
* opaque to introspection and should be avoided when a declarative form
|
|
318
|
-
* exists. Empty `all` is true; empty `any` is false.
|
|
319
|
-
*/
|
|
320
|
-
type Rule = Selector | {
|
|
321
|
-
all: readonly Rule[];
|
|
322
|
-
} | {
|
|
323
|
-
any: readonly Rule[];
|
|
324
|
-
} | {
|
|
325
|
-
not: Rule;
|
|
326
|
-
} | {
|
|
327
|
-
when: (ctx: RuleCtx) => boolean;
|
|
328
|
-
};
|
|
329
|
-
/** Constant rules. Kept here so they have a single source. */
|
|
330
|
-
declare const ALWAYS: Rule;
|
|
331
|
-
/** A rule that never passes. `any` of nothing is false. */
|
|
332
|
-
declare const NEVER: Rule;
|
|
333
|
-
/**
|
|
334
|
-
* Render a {@link Rule} as a short human-readable string, for inspectors and
|
|
335
|
-
* diagnostics that need to say WHICH rule decided something.
|
|
336
|
-
*
|
|
337
|
-
* ```
|
|
338
|
-
* { capability: 'edits-page' } → capability:edits-page
|
|
339
|
-
* { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
|
|
340
|
-
* { not: { selection: { empty: true } } } → not(selection:empty=true)
|
|
341
|
-
* { when: function hasPath() { … } } → when(hasPath)
|
|
342
|
-
* ```
|
|
343
|
-
*
|
|
344
|
-
* Never throws and always terminates.
|
|
345
|
-
*/
|
|
346
|
-
declare function describeRule(rule: Rule): string;
|
|
347
|
-
/** Evaluate a rule against a context. Pure, and cheap enough to run per
|
|
348
|
-
* frame for every piece of chrome. */
|
|
349
|
-
declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
|
|
350
|
-
/**
|
|
351
|
-
* Composable eligibility predicate with a fluent surface. `cond(ctx)`
|
|
352
|
-
* evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
|
|
353
|
-
* Conditions wrapping new trees.
|
|
354
|
-
*
|
|
355
|
-
* **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
|
|
356
|
-
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
|
|
357
|
-
* disjunction, name the subexpression or use the top-level `or(...)`.
|
|
358
|
-
*/
|
|
359
|
-
interface Condition {
|
|
360
|
-
(ctx: RuleCtx): boolean;
|
|
361
|
-
readonly rule: Rule;
|
|
362
|
-
/** `this && other` */
|
|
363
|
-
and(other: Condition | Rule): Condition;
|
|
364
|
-
/** `this || other` */
|
|
365
|
-
or(other: Condition | Rule): Condition;
|
|
366
|
-
/** `this && !other` */
|
|
367
|
-
andNot(other: Condition | Rule): Condition;
|
|
368
|
-
/** `this || !other` */
|
|
369
|
-
orNot(other: Condition | Rule): Condition;
|
|
364
|
+
/** Which nodes this affordance belongs to. */
|
|
365
|
+
targetIds?: string[];
|
|
366
|
+
/** Set only when `kind` matches `'handle:*'`: which corner or edge stays
|
|
367
|
+
* fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
|
|
368
|
+
anchor?: ResizeAnchor;
|
|
369
|
+
/** Cursor to show while the pointer hovers this affordance (no gesture in
|
|
370
|
+
* flight). Unset falls through to action-cursor prediction, then to the
|
|
371
|
+
* active tool's cursor. */
|
|
372
|
+
cursor?: CursorSpec;
|
|
373
|
+
/**
|
|
374
|
+
* Free-form payload from whatever produced the hit, carried through to the
|
|
375
|
+
* matching action untouched. Kit affordances leave it unset; it exists for
|
|
376
|
+
* a registered layer's own chrome, where the hit-test already resolved
|
|
377
|
+
* *which* of its pieces was hit.
|
|
378
|
+
*/
|
|
379
|
+
payload?: unknown;
|
|
370
380
|
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
374
|
-
*
|
|
375
|
-
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
376
|
-
* about the selector values themselves (two different `mode`s, an empty
|
|
377
|
-
* selection against a non-empty one, `focused: true` against `false`) and
|
|
378
|
-
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
379
|
-
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
380
|
-
* that share a tuple but are never eligible together.
|
|
381
|
-
*/
|
|
382
|
-
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
383
|
-
/**
|
|
384
|
-
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
385
|
-
* `capability` tests are settled by the mode; every other test could go either
|
|
386
|
-
* way, so `false` is a proof and `true` means "may hold".
|
|
387
|
-
*/
|
|
388
|
-
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
389
|
-
|
|
390
|
-
/** The detected half of a profile — everything except the derived scale. */
|
|
391
|
-
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
392
|
-
/**
|
|
393
|
-
* Handle/hit multiplier applied on a coarse pointer.
|
|
394
|
-
*
|
|
395
|
-
* 8px handle → 14px; 24px rotation distance → 42px. Counting the
|
|
396
|
-
* surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
|
|
397
|
-
* minimum-touch-target band. One constant so it is tunable in one place.
|
|
398
|
-
*/
|
|
399
|
-
declare const COARSE_TARGET_SCALE = 1.75;
|
|
400
|
-
/**
|
|
401
|
-
* Fold consumer overrides over detected facts and derive `targetScale`.
|
|
402
|
-
*
|
|
403
|
-
* `targetScale` is re-derived AFTER the merge, so an override of
|
|
404
|
-
* `coarsePointer` alone scales the chrome as expected. An explicit
|
|
405
|
-
* `targetScale` override wins over the derivation.
|
|
406
|
-
*/
|
|
407
|
-
declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
|
|
408
381
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
382
|
+
* One accumulated point on a drag trail: world-space position plus whatever
|
|
383
|
+
* stylus state the originating `PointerEvent` carried.
|
|
411
384
|
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* mouse-like device is the safe default: it is what the kit has always
|
|
415
|
-
* assumed, so an absent profile changes nothing for existing consumers.
|
|
416
|
-
*/
|
|
417
|
-
declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Modifier roles: the modifiers an ongoing action reads while it runs, named
|
|
421
|
-
* by what they do rather than which key they are, so a binding or a tuning can
|
|
422
|
-
* move one to another key without forking the action.
|
|
385
|
+
* The stylus fields are absent for mouse/touch on browsers that don't report
|
|
386
|
+
* them, and for synthetic events.
|
|
423
387
|
*/
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
/** The keys that hold it unless a tuning or a binding says otherwise. */
|
|
432
|
-
keys: RoleKeys;
|
|
388
|
+
interface DragSample extends Point2 {
|
|
389
|
+
/** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
|
|
390
|
+
pressure?: number;
|
|
391
|
+
/** Degrees, ±90. Zero for mouse/touch. */
|
|
392
|
+
tiltX?: number;
|
|
393
|
+
/** Degrees, ±90. Zero for mouse/touch. */
|
|
394
|
+
tiltY?: number;
|
|
433
395
|
}
|
|
434
|
-
/**
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
/** The chrome an in-flight gesture paints beside the scene. */
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* What a `polyline` overlay is, in a word — the narrowest thing an action can
|
|
455
|
-
* say about how its run should look without naming a paint.
|
|
456
|
-
*
|
|
457
|
-
* The named roles are the kit's; a painter maps each to a style and falls back
|
|
458
|
-
* to plain chrome for one it does not know, so a package outside core can coin
|
|
459
|
-
* its own and still be drawn.
|
|
460
|
-
*/
|
|
461
|
-
type OverlayRole =
|
|
462
|
-
/** Generic in-flight chrome — the marquee/lasso paint. */
|
|
463
|
-
'chrome'
|
|
464
|
-
/** A cut the gesture would make. */
|
|
465
|
-
| 'cut'
|
|
466
|
-
/** An edge the gesture would author between two points. */
|
|
467
|
-
| 'connector'
|
|
468
|
-
/** The stretch of a path a soft-selection drag reaches. */
|
|
469
|
-
| 'falloff'
|
|
470
|
-
/** What a lock holds a dragged point to — a line, a circle. */
|
|
471
|
-
| 'lock' | (string & {});
|
|
472
|
-
/**
|
|
473
|
-
* What an in-flight gesture proposes to paint *beside* the scene, returned by
|
|
474
|
-
* `OngoingHandle.overlay()` — a marquee rect, a lasso trail, the outline of a
|
|
475
|
-
* shape being dragged out, the line a slice would cut along.
|
|
476
|
-
*
|
|
477
|
-
* Every variant is world geometry and nothing else — no `DrawCommand`, no
|
|
478
|
-
* color, no coordinate space to choose — so a surface rendering the same
|
|
479
|
-
* gesture some other way can still read it. The painter owns every style
|
|
480
|
-
* decision.
|
|
481
|
-
*/
|
|
482
|
-
type OngoingOverlay = {
|
|
483
|
-
kind: 'marquee';
|
|
484
|
-
start: {
|
|
485
|
-
x: number;
|
|
486
|
-
y: number;
|
|
487
|
-
};
|
|
488
|
-
current: {
|
|
489
|
-
x: number;
|
|
490
|
-
y: number;
|
|
491
|
-
};
|
|
492
|
-
shiftHeld: boolean;
|
|
493
|
-
} | {
|
|
494
|
-
kind: 'lasso';
|
|
495
|
-
vertices: ReadonlyArray<{
|
|
496
|
-
x: number;
|
|
497
|
-
y: number;
|
|
498
|
-
}>;
|
|
499
|
-
current: {
|
|
500
|
-
x: number;
|
|
501
|
-
y: number;
|
|
502
|
-
};
|
|
503
|
-
shiftHeld: boolean;
|
|
504
|
-
} | {
|
|
505
|
-
/**
|
|
506
|
-
* An open run of world points the gesture is proposing — a cut line, a
|
|
507
|
-
* routed connector. `role` is the whole of what an action says about
|
|
508
|
-
* appearance.
|
|
509
|
-
*/
|
|
510
|
-
kind: 'polyline';
|
|
511
|
-
/** At least two points. Anything shorter is dropped in resolution. */
|
|
512
|
-
points: ReadonlyArray<{
|
|
513
|
-
x: number;
|
|
514
|
-
y: number;
|
|
515
|
-
}>;
|
|
516
|
-
/** What the run means. Omitted reads as {@link OverlayRole} `'chrome'`. */
|
|
517
|
-
role?: OverlayRole;
|
|
518
|
-
} | {
|
|
519
|
-
/**
|
|
520
|
-
* A round brush dragged through world points: the area it has swept so
|
|
521
|
-
* far, and its footprint at the last point. The radius is in screen
|
|
522
|
-
* pixels, so the footprint keeps its size at every zoom.
|
|
523
|
-
*/
|
|
524
|
-
kind: 'brush';
|
|
525
|
-
/** The brush's path so far, at least one point; the last is where it is now. */
|
|
526
|
-
points: ReadonlyArray<{
|
|
527
|
-
x: number;
|
|
528
|
-
y: number;
|
|
529
|
-
}>;
|
|
530
|
-
radiusPx: number;
|
|
531
|
-
} | {
|
|
532
|
-
/**
|
|
533
|
-
* Live insert-drag preview. Pre-commit there is no scene node to ghost
|
|
534
|
-
* via `previewIds()`/`previewPose()`, so insert paints its preview
|
|
535
|
-
* through the overlay instead.
|
|
536
|
-
*
|
|
537
|
-
* `bounds` is the AABB of the current drag (start/current normalized).
|
|
538
|
-
* `extras` is the per-kind extras the action already collected; the
|
|
539
|
-
* overlay renderer narrows on `shape` and casts the field shape it
|
|
540
|
-
* expects.
|
|
541
|
-
*/
|
|
542
|
-
kind: 'insertPreview';
|
|
543
|
-
/** Which preview the dispatcher's overlay draws. The kit's own shape
|
|
544
|
-
* kinds are the ones it knows how to render; a consumer-defined kind
|
|
545
|
-
* is legal and simply gets no live preview. */
|
|
546
|
-
shape: string;
|
|
547
|
-
bounds: {
|
|
548
|
-
x: number;
|
|
549
|
-
y: number;
|
|
550
|
-
width: number;
|
|
551
|
-
height: number;
|
|
552
|
-
};
|
|
553
|
-
extras: unknown;
|
|
554
|
-
/** World-space point to paint a small "anchor" dot at, marking the
|
|
555
|
-
* click point — for radial shapes (polygon/star), where no vertex
|
|
556
|
-
* sits on it, and for any shape in center mode. */
|
|
557
|
-
anchorPoint?: {
|
|
558
|
-
x: number;
|
|
559
|
-
y: number;
|
|
560
|
-
};
|
|
561
|
-
/** World affines the shape will also be drawn under once it lands, by
|
|
562
|
-
* the container it is drawn into: the preview is painted under each. */
|
|
563
|
-
copies?: readonly (readonly [number, number, number, number, number, number])[];
|
|
564
|
-
};
|
|
565
|
-
/** A handle's overlays as a list: empty when it has none. */
|
|
566
|
-
declare function overlaysOf(handle: OngoingHandle): readonly OngoingOverlay[];
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Invoker — pluggable invocation strategy for an Action.
|
|
570
|
-
*
|
|
571
|
-
* An Action's `invoker` field describes how it's run. `immediate` invokers
|
|
572
|
-
* fire once (delete, align, undo). `ongoing` invokers open a phase machine
|
|
573
|
-
* driven by the dispatcher (move, resize, pinch-zoom).
|
|
574
|
-
*/
|
|
575
|
-
|
|
576
|
-
/** A 2D point in either world or screen coordinates; geom's `Point`. */
|
|
577
|
-
type Point2 = Point;
|
|
578
|
-
/**
|
|
579
|
-
* Which UI affordance was hit at pointerdown. Populated by the dispatcher when
|
|
580
|
-
* the `affordanceAt` thunk is provided to `useGestureDispatcher`.
|
|
581
|
-
*
|
|
582
|
-
* `kind` is a discriminator string:
|
|
583
|
-
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
584
|
-
* `'handle:bottom-right'` — corner resize handles.
|
|
585
|
-
* - `'handle:top'` / `'handle:right'` / `'handle:bottom'` / `'handle:left'` —
|
|
586
|
-
* edge resize handles.
|
|
587
|
-
* - `'rotate-handle'` — the rotation affordance.
|
|
588
|
-
* - `'anchor:N'` — a path anchor at index N.
|
|
589
|
-
*/
|
|
590
|
-
interface AffordanceHit {
|
|
591
|
-
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
592
|
-
kind: string;
|
|
593
|
-
/** Id of whatever produced this hit — a kit affordance's `id`, or the
|
|
594
|
-
* registered layer's id. */
|
|
595
|
-
owner?: string;
|
|
596
|
-
/** `'exclusive'` means no binding may act on this point unless its target
|
|
597
|
-
* consults the affordance. `'shared'` (the default) competes on scope and
|
|
598
|
-
* specificity as bindings always have. */
|
|
599
|
-
strength?: 'exclusive' | 'shared';
|
|
600
|
-
/** Which gestures an exclusive claim bars. Omitted bars all of them. */
|
|
601
|
-
claimedKinds?: readonly ClaimableGesture[];
|
|
602
|
-
/** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
|
|
603
|
-
fixedPoint?: {
|
|
604
|
-
x: number;
|
|
605
|
-
y: number;
|
|
606
|
-
};
|
|
607
|
-
/** Which nodes this affordance belongs to. */
|
|
608
|
-
targetIds?: string[];
|
|
609
|
-
/** Set only when `kind` matches `'handle:*'`: which corner or edge stays
|
|
610
|
-
* fixed during a resize; `'free'` on the axis an edge handle leaves alone. */
|
|
611
|
-
anchor?: ResizeAnchor;
|
|
612
|
-
/** Cursor to show while the pointer hovers this affordance (no gesture in
|
|
613
|
-
* flight). Unset falls through to action-cursor prediction, then to the
|
|
614
|
-
* active tool's cursor. */
|
|
615
|
-
cursor?: CursorSpec;
|
|
616
|
-
/**
|
|
617
|
-
* Free-form payload from whatever produced the hit, carried through to the
|
|
618
|
-
* matching action untouched. Kit affordances leave it unset; it exists for
|
|
619
|
-
* a registered layer's own chrome, where the hit-test already resolved
|
|
620
|
-
* *which* of its pieces was hit.
|
|
621
|
-
*/
|
|
622
|
-
payload?: unknown;
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* One accumulated point on a drag trail: world-space position plus whatever
|
|
626
|
-
* stylus state the originating `PointerEvent` carried.
|
|
627
|
-
*
|
|
628
|
-
* The stylus fields are absent for mouse/touch on browsers that don't report
|
|
629
|
-
* them, and for synthetic events.
|
|
630
|
-
*/
|
|
631
|
-
interface DragSample extends Point2 {
|
|
632
|
-
/** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
|
|
633
|
-
pressure?: number;
|
|
634
|
-
/** Degrees, ±90. Zero for mouse/touch. */
|
|
635
|
-
tiltX?: number;
|
|
636
|
-
/** Degrees, ±90. Zero for mouse/touch. */
|
|
637
|
-
tiltY?: number;
|
|
638
|
-
}
|
|
639
|
-
/** Per-invocation runtime context the dispatcher hands to an Invoker.
|
|
640
|
-
* Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
|
|
641
|
-
* populated only for matching gesture kinds. */
|
|
642
|
-
interface InvocationCtx {
|
|
643
|
-
world: Point2;
|
|
644
|
-
/**
|
|
645
|
-
* The pointer in client/screen pixels, for an action that has to place DOM
|
|
646
|
-
* at the pointer. Absent on every invocation whose event carries no client
|
|
647
|
-
* coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
|
|
648
|
-
* read it with a fallback rather than assuming a point.
|
|
649
|
-
*
|
|
650
|
-
* A view-mutating drag wants `drag.screenDelta`, not the difference of two
|
|
651
|
-
* of these: world deltas are self-referential mid-pan, and so is any delta
|
|
652
|
-
* taken across a view change.
|
|
653
|
-
*/
|
|
654
|
-
screen?: Point2;
|
|
396
|
+
/** Per-invocation runtime context the dispatcher hands to an Invoker.
|
|
397
|
+
* Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
|
|
398
|
+
* populated only for matching gesture kinds. */
|
|
399
|
+
interface InvocationCtx {
|
|
400
|
+
world: Point2;
|
|
401
|
+
/**
|
|
402
|
+
* The pointer in client/screen pixels, for an action that has to place DOM
|
|
403
|
+
* at the pointer. Absent on every invocation whose event carries no client
|
|
404
|
+
* coordinates — a keystroke, a UI-driven trigger, a synthetic probe — so
|
|
405
|
+
* read it with a fallback rather than assuming a point.
|
|
406
|
+
*
|
|
407
|
+
* A view-mutating drag wants `drag.screenDelta`, not the difference of two
|
|
408
|
+
* of these: world deltas are self-referential mid-pan, and so is any delta
|
|
409
|
+
* taken across a view change.
|
|
410
|
+
*/
|
|
411
|
+
screen?: Point2;
|
|
655
412
|
modifiers: ModifierState;
|
|
656
413
|
deps: ActionDeps;
|
|
657
414
|
drag?: {
|
|
@@ -777,6 +534,12 @@ interface BindingOpts {
|
|
|
777
534
|
* binding; this tells the route-conflict check the order is the design.
|
|
778
535
|
*/
|
|
779
536
|
yields?: boolean;
|
|
537
|
+
/**
|
|
538
|
+
* For a key binding: live while the pointer is over the surface, whichever
|
|
539
|
+
* canvas holds the keys, and only then. For a key that means "here" — a
|
|
540
|
+
* magnifier's peek — on a page with more than one surface that hears it.
|
|
541
|
+
*/
|
|
542
|
+
over?: boolean;
|
|
780
543
|
/**
|
|
781
544
|
* Keys for some of the action's `modifierRoles` on this binding, by role
|
|
782
545
|
* id, over the action's defaults and any tuning's.
|
|
@@ -822,8 +585,11 @@ interface OngoingHandle {
|
|
|
822
585
|
*
|
|
823
586
|
* `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
824
587
|
* returns the interim pose for one of them (shape opaque — the canvas
|
|
825
|
-
* casts to its `TPose` parameter).
|
|
826
|
-
*
|
|
588
|
+
* casts to its `TPose` parameter). `previewIds` returning `null`, or
|
|
589
|
+
* omitted, means "no preview this gesture". An id with no `previewPose`
|
|
590
|
+
* is shown at its effective pose: an action that publishes its poses to
|
|
591
|
+
* the scene's pose overrides, as move, resize and rotate do, leaves
|
|
592
|
+
* `previewPose` out rather than answering the same pose twice.
|
|
827
593
|
*/
|
|
828
594
|
previewIds?(): Iterable<string> | null;
|
|
829
595
|
previewPose?(id: string): unknown | null;
|
|
@@ -873,63 +639,615 @@ interface ImmediateInvoker {
|
|
|
873
639
|
* is undefined; descriptors should default to a sensible variant. */
|
|
874
640
|
run(deps: ActionDeps, params?: Record<string, unknown>): void;
|
|
875
641
|
}
|
|
876
|
-
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
877
|
-
* the dispatcher pumps. */
|
|
878
|
-
interface OngoingInvoker {
|
|
879
|
-
timing: 'ongoing';
|
|
880
|
-
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
642
|
+
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
643
|
+
* the dispatcher pumps. */
|
|
644
|
+
interface OngoingInvoker {
|
|
645
|
+
timing: 'ongoing';
|
|
646
|
+
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
647
|
+
}
|
|
648
|
+
/** Pluggable invocation strategy for an Action. */
|
|
649
|
+
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
653
|
+
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
654
|
+
* gesture-bindings are registered globally.
|
|
655
|
+
*/
|
|
656
|
+
|
|
657
|
+
/** An interaction: a gesture spec composed with the id of the action it
|
|
658
|
+
* invokes. Tools declare arrays of these; the dispatcher matches an incoming
|
|
659
|
+
* input event against them and runs the winner's action. */
|
|
660
|
+
interface GestureBinding {
|
|
661
|
+
spec: GestureSpec;
|
|
662
|
+
actionId: string;
|
|
663
|
+
opts?: BindingOpts;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
|
|
667
|
+
* (no per-binding opts) or an object form that pairs a spec with
|
|
668
|
+
* `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
|
|
669
|
+
* Use the object form when two bindings for the same action differ only in
|
|
670
|
+
* a runtime parameter — the dispatcher extracts `opts.params` and passes
|
|
671
|
+
* them to `ImmediateInvoker.run` as its second argument.
|
|
672
|
+
*/
|
|
673
|
+
type BoundGesture = GestureSpec | {
|
|
674
|
+
spec: GestureSpec;
|
|
675
|
+
opts: BindingOpts;
|
|
676
|
+
};
|
|
677
|
+
/**
|
|
678
|
+
* The whole of an action `actionBindings` reads. Routing needs this much and
|
|
679
|
+
* none of `Action`'s palette fields, so it is the narrowest type the
|
|
680
|
+
* dispatcher's binding walk can be written against.
|
|
681
|
+
*/
|
|
682
|
+
interface BindingSource {
|
|
683
|
+
id: string;
|
|
684
|
+
/** The gesture-spec form of the binding, read by the gesture dispatcher.
|
|
685
|
+
* May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
|
|
686
|
+
* or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
|
|
687
|
+
* `{ spec, opts }` — use the object form for parametric actions where two
|
|
688
|
+
* bindings for the same action differ only by `opts.params` (e.g. `flip`
|
|
689
|
+
* with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
|
|
690
|
+
* passes them to `ImmediateInvoker.run` as its second argument. */
|
|
691
|
+
defaultBinding?: GestureSpec | BoundGesture[];
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Flatten an action's `defaultBinding` into `GestureBinding`s. A bare
|
|
695
|
+
* `GestureSpec` has `kind` at top level; the object form has `spec`.
|
|
696
|
+
*/
|
|
697
|
+
declare function actionBindings(action: BindingSource): GestureBinding[];
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
|
|
701
|
+
* the actions-layer binding-scope / matchBest logic on top.
|
|
702
|
+
*/
|
|
703
|
+
|
|
704
|
+
/** Where a binding came from, which is also its priority: a held hotkey beats
|
|
705
|
+
* the active tool, which beats bindings that are always in scope. */
|
|
706
|
+
type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
707
|
+
/** A binding paired with where it came from, ready to be matched against an
|
|
708
|
+
* event. */
|
|
709
|
+
interface ScopedBinding {
|
|
710
|
+
binding: GestureBinding;
|
|
711
|
+
scope: BindingScope;
|
|
712
|
+
/** Id of the tool or contribution that declared this binding —
|
|
713
|
+
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
714
|
+
* action's own binding. */
|
|
715
|
+
ownerId: string | null;
|
|
716
|
+
}
|
|
717
|
+
/** The binding that won a match. */
|
|
718
|
+
interface MatchResult {
|
|
719
|
+
binding: GestureBinding;
|
|
720
|
+
scope: BindingScope;
|
|
721
|
+
/** Id of the entry that declared the binding — propagated from
|
|
722
|
+
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
723
|
+
ownerId: string | null;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* True when a spec's target actually consults the affordance hit rather than
|
|
727
|
+
* only the body classification. `kindOf` predicates are handed the hit;
|
|
728
|
+
* `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
|
|
729
|
+
* `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
|
|
730
|
+
* `bodyTarget` / `bodyKind` and never see it.
|
|
731
|
+
*
|
|
732
|
+
* A predicate declares which it is with `readsAffordance`; one that declares
|
|
733
|
+
* nothing, which only untyped code can write, counts as not reading the hit.
|
|
734
|
+
*/
|
|
735
|
+
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
736
|
+
/** The first of {@link matchSorted}'s matches, or null when nothing matches. */
|
|
737
|
+
declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
|
|
738
|
+
/**
|
|
739
|
+
* Find every binding that matches the event, sorted by precedence
|
|
740
|
+
* (best → worst). Ordering rules:
|
|
741
|
+
*
|
|
742
|
+
* - Scopes by priority: hotkey > active > ambient.
|
|
743
|
+
* - Within a scope, more-specific bindings beat less-specific (see
|
|
744
|
+
* `specificity()`).
|
|
745
|
+
* - Among same-specificity bindings, first-declared wins.
|
|
746
|
+
*/
|
|
747
|
+
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
748
|
+
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
749
|
+
interface MatchOutcome {
|
|
750
|
+
matches: MatchResult[];
|
|
751
|
+
/** Bindings whose spec matched the event but which the claim barred, in
|
|
752
|
+
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
753
|
+
barred: MatchResult[];
|
|
754
|
+
/** Owner of the exclusive claim, when one applied. */
|
|
755
|
+
claimOwner?: string;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
759
|
+
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
760
|
+
* opt-in — the dispatcher asks only when it is recording.
|
|
761
|
+
*/
|
|
762
|
+
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
763
|
+
collectBarred?: boolean;
|
|
764
|
+
warn?: (message: string) => void;
|
|
765
|
+
}): MatchOutcome;
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* How one input was resolved: every binding that matched it, what each filter
|
|
769
|
+
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
770
|
+
* that order did. The dispatcher fills one per input in dev builds, and
|
|
771
|
+
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
772
|
+
*
|
|
773
|
+
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
774
|
+
* buffer, serialized, or built by hand as a fixture.
|
|
775
|
+
*/
|
|
776
|
+
interface DispatchRecord {
|
|
777
|
+
kind: 'dispatch';
|
|
778
|
+
ts: number;
|
|
779
|
+
input: DispatchRecordInput;
|
|
780
|
+
/** Every binding whose spec matched the input, in matcher order, including
|
|
781
|
+
* those a filter then dropped. */
|
|
782
|
+
matched: readonly RecordCandidate[];
|
|
783
|
+
dropped: readonly DroppedCandidate[];
|
|
784
|
+
/** The candidates that survived both filters, best first. */
|
|
785
|
+
ranked: readonly RankedCandidate[];
|
|
786
|
+
/** True when nothing was invoked: the record answers "what would happen". */
|
|
787
|
+
predicted: boolean;
|
|
788
|
+
/** Action id of the candidate that fired (or would), else `null`. */
|
|
789
|
+
fired: string | null;
|
|
790
|
+
outcome: 'handled' | 'unhandled';
|
|
791
|
+
}
|
|
792
|
+
/** The input a {@link DispatchRecord} describes, reduced to plain data. */
|
|
793
|
+
interface DispatchRecordInput {
|
|
794
|
+
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
795
|
+
eventKind: string;
|
|
796
|
+
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
797
|
+
key?: string;
|
|
798
|
+
modifiers: {
|
|
799
|
+
alt: boolean;
|
|
800
|
+
ctrl: boolean;
|
|
801
|
+
meta: boolean;
|
|
802
|
+
shift: boolean;
|
|
803
|
+
};
|
|
804
|
+
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
805
|
+
viewId: string | null;
|
|
806
|
+
/** World point, for events that carry one. */
|
|
807
|
+
world?: {
|
|
808
|
+
x: number;
|
|
809
|
+
y: number;
|
|
810
|
+
};
|
|
811
|
+
/** Active mode id, when the host supplies a rule context. */
|
|
812
|
+
mode?: string;
|
|
813
|
+
}
|
|
814
|
+
/** One matched binding. */
|
|
815
|
+
interface RecordCandidate {
|
|
816
|
+
actionId: string;
|
|
817
|
+
/** The binding's spec in route grammar — one per arg alternative. */
|
|
818
|
+
routes: readonly string[];
|
|
819
|
+
scope: BindingScope;
|
|
820
|
+
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
821
|
+
ownerId: string | null;
|
|
822
|
+
/** Whether the binding's `views` names the routed view. */
|
|
823
|
+
namesView: boolean;
|
|
824
|
+
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
825
|
+
specificity: readonly [number, number, number, number];
|
|
826
|
+
/** The action's `eligible` rule as text, when it declares one. */
|
|
827
|
+
eligible?: string;
|
|
828
|
+
}
|
|
829
|
+
/** A matched candidate a filter removed before ranking, and which filter. */
|
|
830
|
+
type DroppedCandidate =
|
|
831
|
+
/** An exclusive claim on the input barred bindings that do not read the
|
|
832
|
+
* claim's affordance. */
|
|
833
|
+
{
|
|
834
|
+
candidate: RecordCandidate;
|
|
835
|
+
filter: 'claim';
|
|
836
|
+
owner?: string;
|
|
837
|
+
}
|
|
838
|
+
/** The action's `eligible` rule does not hold. */
|
|
839
|
+
| {
|
|
840
|
+
candidate: RecordCandidate;
|
|
841
|
+
filter: 'ineligible';
|
|
842
|
+
rule: string;
|
|
843
|
+
};
|
|
844
|
+
/** One field of the `specificity` tuple, in the order it is compared. */
|
|
845
|
+
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
846
|
+
/** The ranking step that put a candidate below the one before it. */
|
|
847
|
+
type PlacedBy = {
|
|
848
|
+
step: 'first';
|
|
849
|
+
}
|
|
850
|
+
/** The one above names the routed view; this one does not. */
|
|
851
|
+
| {
|
|
852
|
+
step: 'view';
|
|
853
|
+
}
|
|
854
|
+
/** The one above sits in a higher scope tier. */
|
|
855
|
+
| {
|
|
856
|
+
step: 'tier';
|
|
857
|
+
}
|
|
858
|
+
/** Same tier; the one above is more specific in `part`. */
|
|
859
|
+
| {
|
|
860
|
+
step: 'specificity';
|
|
861
|
+
part: SpecificityPart;
|
|
862
|
+
}
|
|
863
|
+
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
864
|
+
| {
|
|
865
|
+
step: 'context';
|
|
866
|
+
}
|
|
867
|
+
/** Tied on everything; registration order. */
|
|
868
|
+
| {
|
|
869
|
+
step: 'order';
|
|
870
|
+
};
|
|
871
|
+
/** What the walk did with one ranked candidate. */
|
|
872
|
+
type WalkStep = {
|
|
873
|
+
kind: 'fired';
|
|
874
|
+
}
|
|
875
|
+
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
876
|
+
* so an ongoing action here may still bail at `start()` for real. */
|
|
877
|
+
| {
|
|
878
|
+
kind: 'would-fire';
|
|
879
|
+
}
|
|
880
|
+
/** `enabled()` returned this reason. */
|
|
881
|
+
| {
|
|
882
|
+
kind: 'declined';
|
|
883
|
+
reason: string;
|
|
884
|
+
}
|
|
885
|
+
/** An ongoing action's `start()` returned an empty handle. */
|
|
886
|
+
| {
|
|
887
|
+
kind: 'empty-handle';
|
|
888
|
+
}
|
|
889
|
+
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
890
|
+
| {
|
|
891
|
+
kind: 'misbound';
|
|
892
|
+
}
|
|
893
|
+
/** Its action was already tried higher up; each runs at most once. */
|
|
894
|
+
| {
|
|
895
|
+
kind: 'duplicate';
|
|
896
|
+
} | {
|
|
897
|
+
kind: 'no-such-action';
|
|
898
|
+
}
|
|
899
|
+
/** Below the winner; never asked. */
|
|
900
|
+
| {
|
|
901
|
+
kind: 'not-asked';
|
|
902
|
+
}
|
|
903
|
+
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
904
|
+
| {
|
|
905
|
+
kind: 'outranked';
|
|
906
|
+
};
|
|
907
|
+
/** A candidate that survived the filters: why it sits where it does, and
|
|
908
|
+
* what the walk did with it. */
|
|
909
|
+
interface RankedCandidate {
|
|
910
|
+
candidate: RecordCandidate;
|
|
911
|
+
placedBy: PlacedBy;
|
|
912
|
+
walk: WalkStep;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/** One entry per "mode change" — a kit state transition that isn't a
|
|
916
|
+
* dispatched input event but explains why an input was (or wasn't) handled
|
|
917
|
+
* later. Pushed via {@link DispatchTrace.recordModeSwitch}. */
|
|
918
|
+
interface ModeSwitchLogEntry {
|
|
919
|
+
kind: 'mode';
|
|
920
|
+
ts: number;
|
|
921
|
+
/** Logical mode name, e.g. `'editAnchors.editingId'`. */
|
|
922
|
+
mode: string;
|
|
923
|
+
from: string | null;
|
|
924
|
+
to: string | null;
|
|
925
|
+
/** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
|
|
926
|
+
* `'selection-dropped target'`, etc. */
|
|
927
|
+
detail?: string;
|
|
928
|
+
}
|
|
929
|
+
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
930
|
+
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
931
|
+
/** A rolling buffer that records in a dev build, and in any build while something watches it. */
|
|
932
|
+
interface TraceRing<T> {
|
|
933
|
+
/** Whether a push now keeps its entry. A caller building an entry checks first. */
|
|
934
|
+
recording(): boolean;
|
|
935
|
+
push(entry: T): void;
|
|
936
|
+
/** The entries pushed after `cursor`, oldest first, and the cursor to pass
|
|
937
|
+
* next time. Start from 0. Entries the buffer has dropped are skipped. */
|
|
938
|
+
since(cursor: number): {
|
|
939
|
+
entries: T[];
|
|
940
|
+
cursor: number;
|
|
941
|
+
};
|
|
942
|
+
/** Turn recording on until the returned release is called. */
|
|
943
|
+
watch(): () => void;
|
|
944
|
+
/** Hear each kept push. Returns the unsubscribe. */
|
|
945
|
+
subscribe(listener: () => void): () => void;
|
|
946
|
+
}
|
|
947
|
+
/** A {@link TraceRing} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
948
|
+
declare function createTraceRing<T>(limit?: number, dev?: boolean): TraceRing<T>;
|
|
949
|
+
/** One canvas's dispatch trace. `useGestureDispatcher` makes one, puts it on
|
|
950
|
+
* every `DispatcherContext` it builds, and publishes it as the `dispatchTrace` dep. */
|
|
951
|
+
interface DispatchTrace extends TraceRing<TraceLogEntry> {
|
|
952
|
+
recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
953
|
+
/** Offer the record for what a press at the pointer would do, built only
|
|
954
|
+
* when {@link DispatchTrace.live} reads it. `null` withdraws it. */
|
|
955
|
+
setLive(explain: (() => DispatchRecord) | null): void;
|
|
956
|
+
live(): DispatchRecord | null;
|
|
957
|
+
}
|
|
958
|
+
/** A {@link DispatchTrace} keeping the last `limit` entries. `dev` defaults to the build's. */
|
|
959
|
+
declare function createDispatchTrace(limit?: number, dev?: boolean): DispatchTrace;
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Live state read by rule evaluation. Built once per frame on the consuming
|
|
963
|
+
* surface — chrome-caps, the affordance pipeline, the dispatcher's
|
|
964
|
+
* eligibility filter — and discarded.
|
|
965
|
+
*
|
|
966
|
+
* Adding a new field is additive: existing rules don't change, new
|
|
967
|
+
* selector atoms can read it.
|
|
968
|
+
*/
|
|
969
|
+
interface RuleCtx {
|
|
970
|
+
readonly focused: boolean;
|
|
971
|
+
readonly selection: readonly NodeId[];
|
|
972
|
+
readonly multiActive: boolean;
|
|
973
|
+
readonly modifiers: ModifierState;
|
|
974
|
+
readonly action: {
|
|
975
|
+
readonly kind: string | null;
|
|
976
|
+
readonly id: string | null;
|
|
977
|
+
};
|
|
978
|
+
readonly hover: NodeId | null;
|
|
979
|
+
/**
|
|
980
|
+
* How far the viewport is zoomed in, as one number — `1` at native scale.
|
|
981
|
+
*
|
|
982
|
+
* A scalar rather than the `View` this used to carry, because `zoomAtLeast`
|
|
983
|
+
* is the only selector that ever read one and a number is all it needs. A
|
|
984
|
+
* host whose viewport is a camera has no `View` to hand over and would
|
|
985
|
+
* otherwise have to invent one to use eligibility at all; it omits this
|
|
986
|
+
* instead, and `zoomAtLeast` declines rather than matching on a fiction.
|
|
987
|
+
*/
|
|
988
|
+
readonly zoom?: number;
|
|
989
|
+
/** Active mode id. `'normal'` when no non-default mode is engaged. */
|
|
990
|
+
readonly mode: string;
|
|
991
|
+
/** Capability tags allowed by the active mode (the union of
|
|
992
|
+
* `ModeDefinition.allows` plus implicit tags). The `capability:`
|
|
993
|
+
* selector reads this to determine whether a tag is permitted. */
|
|
994
|
+
readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
995
|
+
/** Whether the current selection may be resized. `<SceneCanvas>` folds
|
|
996
|
+
* `selectTool.resize.resizable` over the selection (true only when every
|
|
997
|
+
* selected node is resizable). Read by the `resizable:` selector to gate
|
|
998
|
+
* `selection.resize-handles`. Absent (legacy ctx builders) is treated as
|
|
999
|
+
* resizable — back-compat: handles show unless a consumer opts a node out. */
|
|
1000
|
+
readonly selectionResizable?: boolean;
|
|
1001
|
+
/** Whether a path is currently in anchor-edit mode. Read by the
|
|
1002
|
+
* `editingAnchors:` selector, which gates the path-edit chrome.
|
|
1003
|
+
*
|
|
1004
|
+
* This is deliberately a fact about state, not about permission: the
|
|
1005
|
+
* anchor overlay and the anchor hit-test must agree, and the thing they
|
|
1006
|
+
* must agree on is "is there an edited path right now", which no
|
|
1007
|
+
* capability or mode id answers. A mode that allows `edits-anchors`
|
|
1008
|
+
* with nothing being edited should draw no anchors. Absent is treated
|
|
1009
|
+
* as false. */
|
|
1010
|
+
readonly editingAnchors?: boolean;
|
|
1011
|
+
/** Device facts — pointer coarseness, hover capability, density.
|
|
1012
|
+
*
|
|
1013
|
+
* Absent (legacy ctx builders) is treated as
|
|
1014
|
+
* {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
|
|
1015
|
+
* density 1. That is what the kit assumed before this field existed, so
|
|
1016
|
+
* an absent profile is behavior-preserving by construction. */
|
|
1017
|
+
readonly device?: DeviceProfile;
|
|
1018
|
+
/** The view the input being dispatched landed in — a view id, or `null`
|
|
1019
|
+
* for the surface's own camera. The dispatcher fills it; absent anywhere
|
|
1020
|
+
* else a rule is evaluated. */
|
|
1021
|
+
readonly viewId?: string | null;
|
|
1022
|
+
}
|
|
1023
|
+
/** The live state a `RuleCtx` is assembled from — the chrome context plus
|
|
1024
|
+
* what the active mode allows. */
|
|
1025
|
+
interface BuildRuleCtxArgs {
|
|
1026
|
+
focused: boolean;
|
|
1027
|
+
selection: readonly NodeId[];
|
|
1028
|
+
multiActive: boolean;
|
|
1029
|
+
modifiers: ModifierState;
|
|
1030
|
+
action: {
|
|
1031
|
+
kind: string | null;
|
|
1032
|
+
id: string | null;
|
|
1033
|
+
};
|
|
1034
|
+
hover: NodeId | null;
|
|
1035
|
+
/** See {@link RuleCtx.zoom}. */
|
|
1036
|
+
zoom?: number;
|
|
1037
|
+
mode: string;
|
|
1038
|
+
allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
1039
|
+
/** Optional — omitted means "resizable" (handles show). See {@link RuleCtx}. */
|
|
1040
|
+
selectionResizable?: boolean;
|
|
1041
|
+
/** Optional — omitted means "no path is being anchor-edited". */
|
|
1042
|
+
editingAnchors?: boolean;
|
|
1043
|
+
/** Optional — omitted means {@link DEFAULT_DEVICE_PROFILE}. */
|
|
1044
|
+
device?: DeviceProfile;
|
|
881
1045
|
}
|
|
882
|
-
/** Pluggable invocation strategy for an Action. */
|
|
883
|
-
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
884
|
-
|
|
885
1046
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
1047
|
+
* Capability set to assume when no mode registry is wired.
|
|
1048
|
+
*
|
|
1049
|
+
* "No modes configured" means *everything the default mode permits*, not
|
|
1050
|
+
* *nothing permitted*. The empty set is the wrong default: it makes every
|
|
1051
|
+
* `capability:` rule evaluate false, so a kit default written the
|
|
1052
|
+
* recommended way (capability rules outlive new modes; mode rules have to
|
|
1053
|
+
* be edited) would silently hide its chrome for every consumer that hasn't
|
|
1054
|
+
* opted into modality. `NORMAL.allows` plus the implicit tags is what a
|
|
1055
|
+
* mode-less canvas actually behaves like.
|
|
889
1056
|
*/
|
|
1057
|
+
declare const DEFAULT_ALLOWED_CAPABILITIES: ReadonlySet<CapabilityTag>;
|
|
1058
|
+
/** Gather the current canvas and mode state into the context that action
|
|
1059
|
+
* eligibility and chrome-visibility rules are evaluated against. */
|
|
1060
|
+
declare function buildRuleCtx(args: BuildRuleCtxArgs): RuleCtx;
|
|
890
1061
|
|
|
891
|
-
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1062
|
+
/**
|
|
1063
|
+
* A selector is a conjunction of key/value tests. Multiple keys at the same
|
|
1064
|
+
* level AND together. Each key maps to a selector primitive in the evaluator.
|
|
1065
|
+
*/
|
|
1066
|
+
interface Selector {
|
|
1067
|
+
selection?: {
|
|
1068
|
+
is?: number;
|
|
1069
|
+
atLeast?: number;
|
|
1070
|
+
empty?: boolean;
|
|
1071
|
+
};
|
|
1072
|
+
mode?: string | {
|
|
1073
|
+
not: string;
|
|
1074
|
+
} | {
|
|
1075
|
+
in: readonly string[];
|
|
1076
|
+
};
|
|
1077
|
+
capability?: CapabilityTag | readonly CapabilityTag[] | {
|
|
1078
|
+
in: readonly CapabilityTag[];
|
|
1079
|
+
} | {
|
|
1080
|
+
not: CapabilityTag;
|
|
1081
|
+
};
|
|
1082
|
+
gesturing?: boolean;
|
|
1083
|
+
actionIs?: string;
|
|
1084
|
+
modifierHeld?: keyof ModifierState;
|
|
1085
|
+
focused?: boolean;
|
|
1086
|
+
hovering?: boolean;
|
|
1087
|
+
hoveringSelected?: boolean;
|
|
1088
|
+
zoomAtLeast?: number;
|
|
1089
|
+
/** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
|
|
1090
|
+
* mode. Absent flag is treated as `false`. */
|
|
1091
|
+
editingAnchors?: boolean;
|
|
1092
|
+
/** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
|
|
1093
|
+
* (resizable), so `{ resizable: true }` passes for legacy ctx builders
|
|
1094
|
+
* that don't compute it. */
|
|
1095
|
+
resizable?: boolean;
|
|
1096
|
+
/** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
|
|
1097
|
+
* (touch, most styluses). Absent device is treated as `false`. */
|
|
1098
|
+
coarsePointer?: boolean;
|
|
1099
|
+
/** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
|
|
1100
|
+
* device is treated as `true`. */
|
|
1101
|
+
canHover?: boolean;
|
|
898
1102
|
}
|
|
899
1103
|
/**
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
* a runtime parameter — the dispatcher extracts `opts.params` and passes
|
|
905
|
-
* them to `ImmediateInvoker.run` as its second argument.
|
|
1104
|
+
* Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
|
|
1105
|
+
* over `Selector` leaves. `when` is the escape hatch — its closure is
|
|
1106
|
+
* opaque to introspection and should be avoided when a declarative form
|
|
1107
|
+
* exists. Empty `all` is true; empty `any` is false.
|
|
906
1108
|
*/
|
|
907
|
-
type
|
|
908
|
-
|
|
909
|
-
|
|
1109
|
+
type Rule = Selector | {
|
|
1110
|
+
all: readonly Rule[];
|
|
1111
|
+
} | {
|
|
1112
|
+
any: readonly Rule[];
|
|
1113
|
+
} | {
|
|
1114
|
+
not: Rule;
|
|
1115
|
+
} | {
|
|
1116
|
+
when: (ctx: RuleCtx) => boolean;
|
|
910
1117
|
};
|
|
1118
|
+
/** Constant rules. Kept here so they have a single source. */
|
|
1119
|
+
declare const ALWAYS: Rule;
|
|
1120
|
+
/** A rule that never passes. `any` of nothing is false. */
|
|
1121
|
+
declare const NEVER: Rule;
|
|
911
1122
|
/**
|
|
912
|
-
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
1123
|
+
* Render a {@link Rule} as a short human-readable string, for inspectors and
|
|
1124
|
+
* diagnostics that need to say WHICH rule decided something.
|
|
1125
|
+
*
|
|
1126
|
+
* ```
|
|
1127
|
+
* { capability: 'edits-page' } → capability:edits-page
|
|
1128
|
+
* { all: [{ mode: 'normal' }, { focused: true }] } → all(mode:normal, focused:true)
|
|
1129
|
+
* { not: { selection: { empty: true } } } → not(selection:empty=true)
|
|
1130
|
+
* { when: function hasPath() { … } } → when(hasPath)
|
|
1131
|
+
* ```
|
|
1132
|
+
*
|
|
1133
|
+
* Never throws and always terminates.
|
|
915
1134
|
*/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1135
|
+
declare function describeRule(rule: Rule): string;
|
|
1136
|
+
/** Evaluate a rule against a context. Pure, and cheap enough to run per
|
|
1137
|
+
* frame for every piece of chrome. */
|
|
1138
|
+
declare function evaluate(rule: Rule, ctx: RuleCtx): boolean;
|
|
1139
|
+
/**
|
|
1140
|
+
* Composable eligibility predicate with a fluent surface. `cond(ctx)`
|
|
1141
|
+
* evaluates its `Rule` tree, carried at `.rule`; the fluent methods return new
|
|
1142
|
+
* Conditions wrapping new trees.
|
|
1143
|
+
*
|
|
1144
|
+
* **Chain semantics: strict left-to-right, no precedence.** `a.and(b).or(c)` is
|
|
1145
|
+
* `(a && b) || c`; `a.or(b).and(c)` is `(a || b) && c`. For grouped
|
|
1146
|
+
* disjunction, name the subexpression or use the top-level `or(...)`.
|
|
1147
|
+
*/
|
|
1148
|
+
interface Condition {
|
|
1149
|
+
(ctx: RuleCtx): boolean;
|
|
1150
|
+
readonly rule: Rule;
|
|
1151
|
+
/** `this && other` */
|
|
1152
|
+
and(other: Condition | Rule): Condition;
|
|
1153
|
+
/** `this || other` */
|
|
1154
|
+
or(other: Condition | Rule): Condition;
|
|
1155
|
+
/** `this && !other` */
|
|
1156
|
+
andNot(other: Condition | Rule): Condition;
|
|
1157
|
+
/** `this || !other` */
|
|
1158
|
+
orNot(other: Condition | Rule): Condition;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Whether two rules can never hold at once — no `RuleCtx` passes both.
|
|
1163
|
+
*
|
|
1164
|
+
* Conservative: `true` is a proof, `false` means "may overlap". It reasons
|
|
1165
|
+
* about the selector values themselves (two different `mode`s, an empty
|
|
1166
|
+
* selection against a non-empty one, `focused: true` against `false`) and
|
|
1167
|
+
* through `all` / `any` / `not`; a `when` closure is opaque, so it overlaps
|
|
1168
|
+
* everything. The route-conflict check uses it to stay quiet about bindings
|
|
1169
|
+
* that share a tuple but are never eligible together.
|
|
1170
|
+
*/
|
|
1171
|
+
declare function rulesExclusive(a: Rule, b: Rule): boolean;
|
|
1172
|
+
/**
|
|
1173
|
+
* Whether `rule` can hold while `mode` is active. Only its `mode` and
|
|
1174
|
+
* `capability` tests are settled by the mode; every other test could go either
|
|
1175
|
+
* way, so `false` is a proof and `true` means "may hold".
|
|
1176
|
+
*/
|
|
1177
|
+
declare function ruleCanHoldIn(rule: Rule, mode: ActiveMode): boolean;
|
|
1178
|
+
|
|
1179
|
+
/** The detected half of a profile — everything except the derived scale. */
|
|
1180
|
+
type DetectedDeviceFacts = Omit<DeviceProfile, 'targetScale'>;
|
|
1181
|
+
/**
|
|
1182
|
+
* Handle/hit multiplier applied on a coarse pointer.
|
|
1183
|
+
*
|
|
1184
|
+
* 8px handle → 14px; 24px rotation distance → 42px. Counting the
|
|
1185
|
+
* surrounding grab zone, that lands in the Apple HIG 44pt / Material 48dp
|
|
1186
|
+
* minimum-touch-target band. One constant so it is tunable in one place.
|
|
1187
|
+
*/
|
|
1188
|
+
declare const COARSE_TARGET_SCALE = 1.75;
|
|
1189
|
+
/**
|
|
1190
|
+
* Fold consumer overrides over detected facts and derive `targetScale`.
|
|
1191
|
+
*
|
|
1192
|
+
* `targetScale` is re-derived AFTER the merge, so an override of
|
|
1193
|
+
* `coarsePointer` alone scales the chrome as expected. An explicit
|
|
1194
|
+
* `targetScale` override wins over the derivation.
|
|
1195
|
+
*/
|
|
1196
|
+
declare function resolveDeviceProfile(detected: DetectedDeviceFacts, overrides?: Partial<DeviceProfile>): DeviceProfile;
|
|
1197
|
+
/**
|
|
1198
|
+
* Assumed when `matchMedia` is unavailable (SSR, jsdom) and used as the
|
|
1199
|
+
* absent-means value for `RuleCtx.device`.
|
|
1200
|
+
*
|
|
1201
|
+
* Derived from `resolveDeviceProfile` rather than hand-written, so it can
|
|
1202
|
+
* never drift from what the function would compute for the same facts. A
|
|
1203
|
+
* mouse-like device is the safe default: it is what the kit has always
|
|
1204
|
+
* assumed, so an absent profile changes nothing for existing consumers.
|
|
1205
|
+
*/
|
|
1206
|
+
declare const DEFAULT_DEVICE_PROFILE: DeviceProfile;
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* A settings form an action carries: what a surface draws in place of a bare
|
|
1210
|
+
* trigger for an action whose job is to change settings on the selection.
|
|
1211
|
+
*/
|
|
1212
|
+
|
|
1213
|
+
/** What a settings form shows for the selection right now. */
|
|
1214
|
+
interface ActionSettingsState {
|
|
1215
|
+
/** The values, nested as the schema's groups nest. */
|
|
1216
|
+
values: Record<string, unknown>;
|
|
1217
|
+
/** The leaf paths where the selected items disagree. */
|
|
1218
|
+
mixed?: readonly string[];
|
|
926
1219
|
}
|
|
927
1220
|
/**
|
|
928
|
-
*
|
|
929
|
-
*
|
|
1221
|
+
* The settings form an action carries. A bar or a panel draws the action as a
|
|
1222
|
+
* button opening it, a menu as a submenu of its fields, and a palette as a
|
|
1223
|
+
* dialog; each change triggers the action with the params it makes.
|
|
930
1224
|
*/
|
|
931
|
-
|
|
1225
|
+
interface ActionSettings {
|
|
1226
|
+
/** The form. Its `name` titles the popover, submenu or dialog. */
|
|
1227
|
+
schema: PrefGroup;
|
|
1228
|
+
/** The selection's current values, from the deps `enabled` is given. Under
|
|
1229
|
+
* `enabled`'s contract: pure, fast, and it must not throw. */
|
|
1230
|
+
read(deps: ActionDeps): ActionSettingsState;
|
|
1231
|
+
/** The params one field's change triggers the action with, or `null` for
|
|
1232
|
+
* none. Default: `{ [path]: value }`. */
|
|
1233
|
+
params?(path: string, value: unknown): Record<string, unknown> | null;
|
|
1234
|
+
}
|
|
1235
|
+
/** `action`'s settings as they stand under `deps`, or `null` for an action
|
|
1236
|
+
* with no settings. A `read` that throws shows nothing rather than breaking
|
|
1237
|
+
* the surface. */
|
|
1238
|
+
declare function readActionSettings(action: Action, deps: ActionDeps | undefined): ActionSettingsState | null;
|
|
1239
|
+
/** The params a change of the field at `path` to `value` triggers the action with. */
|
|
1240
|
+
declare function actionSettingsParams(settings: ActionSettings, path: string, value: unknown): Record<string, unknown> | null;
|
|
932
1241
|
|
|
1242
|
+
/** What content being routed brings to `Action.enabled`: a drop's or a
|
|
1243
|
+
* paste's items, or a carry's payload with no items. */
|
|
1244
|
+
interface Carried {
|
|
1245
|
+
items: readonly IngestItem[];
|
|
1246
|
+
carry?: {
|
|
1247
|
+
type: string;
|
|
1248
|
+
value: unknown;
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
933
1251
|
/**
|
|
934
1252
|
* Closed enum of reasons an action might report itself as disabled. The
|
|
935
1253
|
* consumer (palette, menu, etc.) maps these symbolic values to display
|
|
@@ -972,6 +1290,11 @@ interface ActionPresentation {
|
|
|
972
1290
|
* triggers the action with that variant's `params`. Omitted = the action
|
|
973
1291
|
* is one entry. See `actionItems`. */
|
|
974
1292
|
variants?: readonly ActionVariant[];
|
|
1293
|
+
/** A form of the settings the action changes on the selection. A surface
|
|
1294
|
+
* draws the action as that form instead of a bare trigger, and each change
|
|
1295
|
+
* triggers it with the params `settings.params` makes. Not combined with
|
|
1296
|
+
* `variants`. */
|
|
1297
|
+
settings?: ActionSettings;
|
|
975
1298
|
}
|
|
976
1299
|
/**
|
|
977
1300
|
* One entry a parametric action contributes to a bar, menu or palette.
|
|
@@ -1037,18 +1360,17 @@ interface ActionDispatch extends BindingSource {
|
|
|
1037
1360
|
* question it was shown enabled for. Palettes and keystrokes pass no
|
|
1038
1361
|
* point; treat absent as "anywhere".
|
|
1039
1362
|
*
|
|
1040
|
-
* `carried` is what a drop or a
|
|
1041
|
-
*
|
|
1042
|
-
* browser withholds contents until the drop.
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1363
|
+
* `carried` is what a drop, a paste or a carry being routed brings. For
|
|
1364
|
+
* an OS drag still in flight its items hold a MIME and nothing else, since
|
|
1365
|
+
* a browser withholds contents until the drop. A carry has no items and
|
|
1366
|
+
* hands over its payload whole. An action that takes only some content
|
|
1367
|
+
* refuses the rest here, so a drag it would not take is not accepted and a
|
|
1368
|
+
* paste it would not take stays the page's.
|
|
1045
1369
|
*/
|
|
1046
1370
|
enabled?: (deps?: ActionDeps, at?: {
|
|
1047
1371
|
x: number;
|
|
1048
1372
|
y: number;
|
|
1049
|
-
}, carried?:
|
|
1050
|
-
items: readonly IngestItem[];
|
|
1051
|
-
}) => true | ActionDisabledReason;
|
|
1373
|
+
}, carried?: Carried) => true | ActionDisabledReason;
|
|
1052
1374
|
/**
|
|
1053
1375
|
* Whether the action reads as held down right now: bold on the selected
|
|
1054
1376
|
* text, snapping switched on. A toolbar button draws it as pressed.
|
|
@@ -1179,8 +1501,9 @@ interface MenuSources {
|
|
|
1179
1501
|
* Draw a prefs schema as menu entries. Action leaves become items from the
|
|
1180
1502
|
* registry (one per variant for a parametric action), an `actions` leaf one
|
|
1181
1503
|
* item per action in its group, a boolean a checked item, an enum radio
|
|
1182
|
-
* rows, and a number a row edited in place (all through `src.values`/`src.onChange`);
|
|
1183
|
-
* a color shows as a swatch and a string as a disabled row
|
|
1504
|
+
* rows, and a number and a string a row edited in place (all through `src.values`/`src.onChange`);
|
|
1505
|
+
* a color shows as a swatch, read-only, and so does a string as a disabled row when it is a readout
|
|
1506
|
+
* or nothing writes it. A group drawn `as` a
|
|
1184
1507
|
* `menu`, `page`, `tab`, or `panel` is a submenu, and any other group's rows
|
|
1185
1508
|
* sit inline between separators. Paint, field, compound, and app-defined leaves are left out.
|
|
1186
1509
|
* `at` is the world point the menu was opened at and reaches `enabled` and
|
|
@@ -1475,7 +1798,10 @@ declare function evaluateActive(action: Action, deps?: ActionDeps): boolean | un
|
|
|
1475
1798
|
* Sources are thunks, read at invocation time, so an action never captures
|
|
1476
1799
|
* stale state. */
|
|
1477
1800
|
interface DepRegistry {
|
|
1478
|
-
|
|
1801
|
+
/** Adds a live source for `name` over any already there. `below` answers
|
|
1802
|
+
* what the registry would without this source, so one can handle part of
|
|
1803
|
+
* a dep and hand the rest to the default it displaced. */
|
|
1804
|
+
register<K extends DepName>(name: K, source: DepSource<K>): () => void;
|
|
1479
1805
|
get<K extends DepName>(name: K): DepSchema[K] | undefined;
|
|
1480
1806
|
/** Hear that what the registry answers may have changed: a source was
|
|
1481
1807
|
* registered or released anywhere in its tree, or an owner called
|
|
@@ -1486,6 +1812,20 @@ interface DepRegistry {
|
|
|
1486
1812
|
* so its owner is the only one who knows; chrome showing an action's
|
|
1487
1813
|
* `enabled` or `active` state re-reads on this. */
|
|
1488
1814
|
changed?(): void;
|
|
1815
|
+
/** Every name a `get` here would answer, sorted, with where its live
|
|
1816
|
+
* source sits. For inspection; a hand-built registry may leave it out. */
|
|
1817
|
+
list?(): readonly DepListing[];
|
|
1818
|
+
}
|
|
1819
|
+
/** A live source for dep `K`: a thunk read at invocation time. `below` reads
|
|
1820
|
+
* the source this one displaced, under it in its scope or in a scope above. */
|
|
1821
|
+
type DepSource<K extends DepName> = (below: () => DepSchema[K] | undefined) => DepSchema[K];
|
|
1822
|
+
/** One name a dep registry answers, as {@link DepRegistry.list} reports it. */
|
|
1823
|
+
interface DepListing {
|
|
1824
|
+
name: string;
|
|
1825
|
+
/** How many scopes above the asking one the live source was registered: 0 for its own. */
|
|
1826
|
+
depth: number;
|
|
1827
|
+
/** How many sources that scope holds under the name, the live one included. */
|
|
1828
|
+
stacked: number;
|
|
1489
1829
|
}
|
|
1490
1830
|
/** One node of the dep tree: the sources registered at it, over its parent's.
|
|
1491
1831
|
* Each name holds a stack of sources, newest live, so a displaced source is
|
|
@@ -1612,222 +1952,6 @@ interface Contribution<TOverlay = KernelOverlay> extends ContributionRouting, Co
|
|
|
1612
1952
|
def?: unknown;
|
|
1613
1953
|
}
|
|
1614
1954
|
|
|
1615
|
-
/**
|
|
1616
|
-
* Re-exports the pure matcher primitives from `@weasel-js/gestures` and layers
|
|
1617
|
-
* the actions-layer binding-scope / matchBest logic on top.
|
|
1618
|
-
*/
|
|
1619
|
-
|
|
1620
|
-
/** Where a binding came from, which is also its priority: a held hotkey beats
|
|
1621
|
-
* the active tool, which beats bindings that are always in scope. */
|
|
1622
|
-
type BindingScope = 'ambient' | 'active' | 'hotkey';
|
|
1623
|
-
/** A binding paired with where it came from, ready to be matched against an
|
|
1624
|
-
* event. */
|
|
1625
|
-
interface ScopedBinding {
|
|
1626
|
-
binding: GestureBinding;
|
|
1627
|
-
scope: BindingScope;
|
|
1628
|
-
/** Id of the tool or contribution that declared this binding —
|
|
1629
|
-
* `'&'`-channel phase atoms resolve to it. `null` for a registered
|
|
1630
|
-
* action's own binding. */
|
|
1631
|
-
ownerId: string | null;
|
|
1632
|
-
}
|
|
1633
|
-
/** The binding that won a match. */
|
|
1634
|
-
interface MatchResult {
|
|
1635
|
-
binding: GestureBinding;
|
|
1636
|
-
scope: BindingScope;
|
|
1637
|
-
/** Id of the entry that declared the binding — propagated from
|
|
1638
|
-
* `ScopedBinding` so the dispatcher can record it as the handle owner. */
|
|
1639
|
-
ownerId: string | null;
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* True when a spec's target actually consults the affordance hit rather than
|
|
1643
|
-
* only the body classification. `kindOf` predicates are handed the hit;
|
|
1644
|
-
* `affordance:<k>` matches on its `kind`. The body-class strings (`'empty'`,
|
|
1645
|
-
* `'selected-body'`, `'unselected-body'`) and the `kind:` forms resolve from
|
|
1646
|
-
* `bodyTarget` / `bodyKind` and never see it.
|
|
1647
|
-
*
|
|
1648
|
-
* A predicate declares which it is with `readsAffordance`; one that declares
|
|
1649
|
-
* nothing, which only untyped code can write, counts as not reading the hit.
|
|
1650
|
-
*/
|
|
1651
|
-
declare function targetConsultsAffordance(specTarget: TargetSpec | undefined): boolean;
|
|
1652
|
-
/** The first of {@link matchSorted}'s matches, or null when nothing matches. */
|
|
1653
|
-
declare function matchBest(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>): MatchResult | null;
|
|
1654
|
-
/**
|
|
1655
|
-
* Find every binding that matches the event, sorted by precedence
|
|
1656
|
-
* (best → worst). Ordering rules:
|
|
1657
|
-
*
|
|
1658
|
-
* - Scopes by priority: hotkey > active > ambient.
|
|
1659
|
-
* - Within a scope, more-specific bindings beat less-specific (see
|
|
1660
|
-
* `specificity()`).
|
|
1661
|
-
* - Among same-specificity bindings, first-declared wins.
|
|
1662
|
-
*/
|
|
1663
|
-
declare function matchSorted(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, warn?: (message: string) => void): MatchResult[];
|
|
1664
|
-
/** {@link matchSorted}, plus what an exclusive claim on the event barred. */
|
|
1665
|
-
interface MatchOutcome {
|
|
1666
|
-
matches: MatchResult[];
|
|
1667
|
-
/** Bindings whose spec matched the event but which the claim barred, in
|
|
1668
|
-
* the same order `matches` uses. Filled only when `collectBarred` is set. */
|
|
1669
|
-
barred: MatchResult[];
|
|
1670
|
-
/** Owner of the exclusive claim, when one applied. */
|
|
1671
|
-
claimOwner?: string;
|
|
1672
|
-
}
|
|
1673
|
-
/**
|
|
1674
|
-
* {@link matchSorted} that can also report the bindings an exclusive claim
|
|
1675
|
-
* kept out. Collecting them costs a spec match per barred binding, so it is
|
|
1676
|
-
* opt-in — the dispatcher asks only when it is recording.
|
|
1677
|
-
*/
|
|
1678
|
-
declare function matchSortedWithBarred(e: InputEvent, bindings: readonly ScopedBinding[], isMac: boolean, engagedChannels?: ReadonlySet<string>, opts?: {
|
|
1679
|
-
collectBarred?: boolean;
|
|
1680
|
-
warn?: (message: string) => void;
|
|
1681
|
-
}): MatchOutcome;
|
|
1682
|
-
|
|
1683
|
-
/**
|
|
1684
|
-
* How one input was resolved: every binding that matched it, what each filter
|
|
1685
|
-
* dropped, the order the rest were ranked in and why, and what the walk down
|
|
1686
|
-
* that order did. The dispatcher fills one per input in dev builds, and
|
|
1687
|
-
* `Dispatcher.explain` fills one on request without invoking anything.
|
|
1688
|
-
*
|
|
1689
|
-
* Plain data — no functions, no live objects — so it can be kept in a trace
|
|
1690
|
-
* buffer, serialized, or built by hand as a fixture.
|
|
1691
|
-
*/
|
|
1692
|
-
interface DispatchRecord {
|
|
1693
|
-
kind: 'dispatch';
|
|
1694
|
-
ts: number;
|
|
1695
|
-
input: DispatchRecordInput;
|
|
1696
|
-
/** Every binding whose spec matched the input, in matcher order, including
|
|
1697
|
-
* those a filter then dropped. */
|
|
1698
|
-
matched: readonly RecordCandidate[];
|
|
1699
|
-
dropped: readonly DroppedCandidate[];
|
|
1700
|
-
/** The candidates that survived both filters, best first. */
|
|
1701
|
-
ranked: readonly RankedCandidate[];
|
|
1702
|
-
/** True when nothing was invoked: the record answers "what would happen". */
|
|
1703
|
-
predicted: boolean;
|
|
1704
|
-
/** Action id of the candidate that fired (or would), else `null`. */
|
|
1705
|
-
fired: string | null;
|
|
1706
|
-
outcome: 'handled' | 'unhandled';
|
|
1707
|
-
}
|
|
1708
|
-
/** The input a {@link DispatchRecord} describes, reduced to plain data. */
|
|
1709
|
-
interface DispatchRecordInput {
|
|
1710
|
-
/** `InputEvent.kind`: `'key'`, `'pointerdown'`, `'wheel'`, … */
|
|
1711
|
-
eventKind: string;
|
|
1712
|
-
/** For `key` / `key-held` events, the key id (`'Escape'`, `' '`, `'a'`). */
|
|
1713
|
-
key?: string;
|
|
1714
|
-
modifiers: {
|
|
1715
|
-
alt: boolean;
|
|
1716
|
-
ctrl: boolean;
|
|
1717
|
-
meta: boolean;
|
|
1718
|
-
shift: boolean;
|
|
1719
|
-
};
|
|
1720
|
-
/** The view the input was routed to; `null` for the surface's own camera. */
|
|
1721
|
-
viewId: string | null;
|
|
1722
|
-
/** World point, for events that carry one. */
|
|
1723
|
-
world?: {
|
|
1724
|
-
x: number;
|
|
1725
|
-
y: number;
|
|
1726
|
-
};
|
|
1727
|
-
/** Active mode id, when the host supplies a rule context. */
|
|
1728
|
-
mode?: string;
|
|
1729
|
-
}
|
|
1730
|
-
/** One matched binding. */
|
|
1731
|
-
interface RecordCandidate {
|
|
1732
|
-
actionId: string;
|
|
1733
|
-
/** The binding's spec in route grammar — one per arg alternative. */
|
|
1734
|
-
routes: readonly string[];
|
|
1735
|
-
scope: BindingScope;
|
|
1736
|
-
/** The tool or contribution that declared the binding; `null` for an action's own binding. */
|
|
1737
|
-
ownerId: string | null;
|
|
1738
|
-
/** Whether the binding's `views` names the routed view. */
|
|
1739
|
-
namesView: boolean;
|
|
1740
|
-
/** `specificity(spec)`: target, modifiers, phase, exact. */
|
|
1741
|
-
specificity: readonly [number, number, number, number];
|
|
1742
|
-
/** The action's `eligible` rule as text, when it declares one. */
|
|
1743
|
-
eligible?: string;
|
|
1744
|
-
}
|
|
1745
|
-
/** A matched candidate a filter removed before ranking, and which filter. */
|
|
1746
|
-
type DroppedCandidate =
|
|
1747
|
-
/** An exclusive claim on the input barred bindings that do not read the
|
|
1748
|
-
* claim's affordance. */
|
|
1749
|
-
{
|
|
1750
|
-
candidate: RecordCandidate;
|
|
1751
|
-
filter: 'claim';
|
|
1752
|
-
owner?: string;
|
|
1753
|
-
}
|
|
1754
|
-
/** The action's `eligible` rule does not hold. */
|
|
1755
|
-
| {
|
|
1756
|
-
candidate: RecordCandidate;
|
|
1757
|
-
filter: 'ineligible';
|
|
1758
|
-
rule: string;
|
|
1759
|
-
};
|
|
1760
|
-
/** One field of the `specificity` tuple, in the order it is compared. */
|
|
1761
|
-
type SpecificityPart = 'target' | 'mods' | 'phase' | 'exact';
|
|
1762
|
-
/** The ranking step that put a candidate below the one before it. */
|
|
1763
|
-
type PlacedBy = {
|
|
1764
|
-
step: 'first';
|
|
1765
|
-
}
|
|
1766
|
-
/** The one above names the routed view; this one does not. */
|
|
1767
|
-
| {
|
|
1768
|
-
step: 'view';
|
|
1769
|
-
}
|
|
1770
|
-
/** The one above sits in a higher scope tier. */
|
|
1771
|
-
| {
|
|
1772
|
-
step: 'tier';
|
|
1773
|
-
}
|
|
1774
|
-
/** Same tier; the one above is more specific in `part`. */
|
|
1775
|
-
| {
|
|
1776
|
-
step: 'specificity';
|
|
1777
|
-
part: SpecificityPart;
|
|
1778
|
-
}
|
|
1779
|
-
/** Tied; the one above is gated by an `eligible` rule that holds. */
|
|
1780
|
-
| {
|
|
1781
|
-
step: 'context';
|
|
1782
|
-
}
|
|
1783
|
-
/** Tied on everything; registration order. */
|
|
1784
|
-
| {
|
|
1785
|
-
step: 'order';
|
|
1786
|
-
};
|
|
1787
|
-
/** What the walk did with one ranked candidate. */
|
|
1788
|
-
type WalkStep = {
|
|
1789
|
-
kind: 'fired';
|
|
1790
|
-
}
|
|
1791
|
-
/** Predicted records only: this candidate would fire. Nothing was started,
|
|
1792
|
-
* so an ongoing action here may still bail at `start()` for real. */
|
|
1793
|
-
| {
|
|
1794
|
-
kind: 'would-fire';
|
|
1795
|
-
}
|
|
1796
|
-
/** `enabled()` returned this reason. */
|
|
1797
|
-
| {
|
|
1798
|
-
kind: 'declined';
|
|
1799
|
-
reason: string;
|
|
1800
|
-
}
|
|
1801
|
-
/** An ongoing action's `start()` returned an empty handle. */
|
|
1802
|
-
| {
|
|
1803
|
-
kind: 'empty-handle';
|
|
1804
|
-
}
|
|
1805
|
-
/** An ongoing action bound to a press-time `pointerDown` spec; refused. */
|
|
1806
|
-
| {
|
|
1807
|
-
kind: 'misbound';
|
|
1808
|
-
}
|
|
1809
|
-
/** Its action was already tried higher up; each runs at most once. */
|
|
1810
|
-
| {
|
|
1811
|
-
kind: 'duplicate';
|
|
1812
|
-
} | {
|
|
1813
|
-
kind: 'no-such-action';
|
|
1814
|
-
}
|
|
1815
|
-
/** Below the winner; never asked. */
|
|
1816
|
-
| {
|
|
1817
|
-
kind: 'not-asked';
|
|
1818
|
-
}
|
|
1819
|
-
/** Below the winner, asked anyway (`evaluateShadowed`), and it passed. */
|
|
1820
|
-
| {
|
|
1821
|
-
kind: 'outranked';
|
|
1822
|
-
};
|
|
1823
|
-
/** A candidate that survived the filters: why it sits where it does, and
|
|
1824
|
-
* what the walk did with it. */
|
|
1825
|
-
interface RankedCandidate {
|
|
1826
|
-
candidate: RecordCandidate;
|
|
1827
|
-
placedBy: PlacedBy;
|
|
1828
|
-
walk: WalkStep;
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
1955
|
/**
|
|
1832
1956
|
* The dispatcher's contract: what a host hands it per input
|
|
1833
1957
|
* (`DispatcherContext`) and what it answers with (`Dispatcher`).
|
|
@@ -1866,6 +1990,19 @@ interface DispatcherContext {
|
|
|
1866
1990
|
* reads as the root.
|
|
1867
1991
|
*/
|
|
1868
1992
|
viewId?: string | null;
|
|
1993
|
+
/**
|
|
1994
|
+
* For a key from the page: whether this surface holds the keys, and whether
|
|
1995
|
+
* the pointer is over it. Bindings with `opts.over` are live only while it
|
|
1996
|
+
* is, the rest only while it holds them. Absent, every binding is live.
|
|
1997
|
+
*/
|
|
1998
|
+
keyReach?: KeyReach;
|
|
1999
|
+
/** Where the dispatcher records how it resolved each input. Absent records nothing. */
|
|
2000
|
+
trace?: DispatchTrace;
|
|
2001
|
+
}
|
|
2002
|
+
/** Which of a surface's key bindings a key from the page reaches. */
|
|
2003
|
+
interface KeyReach {
|
|
2004
|
+
held: boolean;
|
|
2005
|
+
over: boolean;
|
|
1869
2006
|
}
|
|
1870
2007
|
/**
|
|
1871
2008
|
* Handle returned by `Dispatcher.beginUiOngoing()` for driving an
|
|
@@ -2073,31 +2210,6 @@ interface Dispatcher {
|
|
|
2073
2210
|
beginUiOngoing(actionId: string, deps: ActionDeps, params?: Record<string, unknown>): UiOngoingControl | null;
|
|
2074
2211
|
}
|
|
2075
2212
|
|
|
2076
|
-
/**
|
|
2077
|
-
* Dev-only trace of what the dispatcher did: one record per resolved input and
|
|
2078
|
-
* one per mode switch, in a rolling buffer the dev tools read.
|
|
2079
|
-
*/
|
|
2080
|
-
|
|
2081
|
-
/** One entry per "mode change" — a kit state transition that isn't a
|
|
2082
|
-
* dispatched input event but explains why an input was (or wasn't) handled
|
|
2083
|
-
* later. Pushed via `recordModeSwitch()`. */
|
|
2084
|
-
interface ModeSwitchLogEntry {
|
|
2085
|
-
kind: 'mode';
|
|
2086
|
-
ts: number;
|
|
2087
|
-
/** Logical mode name, e.g. `'editAnchors.editingId'`. */
|
|
2088
|
-
mode: string;
|
|
2089
|
-
from: string | null;
|
|
2090
|
-
to: string | null;
|
|
2091
|
-
/** Optional free-form context — `'enterPathEdit'`, `'exitPathEdit'`,
|
|
2092
|
-
* `'selection-dropped target'`, etc. */
|
|
2093
|
-
detail?: string;
|
|
2094
|
-
}
|
|
2095
|
-
/** A trace entry: one {@link DispatchRecord} per resolved input, or a mode switch. */
|
|
2096
|
-
type TraceLogEntry = DispatchRecord | ModeSwitchLogEntry;
|
|
2097
|
-
/** Push a mode-switch record into the trace log. Safe to call from
|
|
2098
|
-
* anywhere; no-ops outside DEV. */
|
|
2099
|
-
declare function recordModeSwitch(mode: string, from: string | null, to: string | null, detail?: string): void;
|
|
2100
|
-
|
|
2101
2213
|
/**
|
|
2102
2214
|
* The slot an in-flight ongoing handle is keyed by:
|
|
2103
2215
|
* - `key-held`: `key-held-<code>` (`key-held-Space`), so two keys that share a
|
|
@@ -2798,6 +2910,29 @@ interface ToolDef<TOverlay = KernelOverlay> {
|
|
|
2798
2910
|
*/
|
|
2799
2911
|
declare function defineTool<TOverlay = KernelOverlay>(def: ToolDef<TOverlay>): Tool<TOverlay>;
|
|
2800
2912
|
|
|
2913
|
+
/**
|
|
2914
|
+
* Tool sets: a named group of tool ids, the unit a consumer opts into whole.
|
|
2915
|
+
* A set names ids, never tools, because a tool is built by a hook that holds
|
|
2916
|
+
* its state; whatever mounts the set supplies the tool for each id.
|
|
2917
|
+
*/
|
|
2918
|
+
/** A named group of tool ids. A member is an id or another set, whose ids it takes in order. */
|
|
2919
|
+
interface ToolSet<TId extends string = string> {
|
|
2920
|
+
readonly id: string;
|
|
2921
|
+
readonly members: readonly (TId | ToolSet<TId>)[];
|
|
2922
|
+
}
|
|
2923
|
+
/** One entry of a list of tools to mount: an id, or a set of them. */
|
|
2924
|
+
type ToolEntry<TId extends string = string> = TId | ToolSet<TId>;
|
|
2925
|
+
/** A set named `id` of `members`, in palette order. */
|
|
2926
|
+
declare function toolSet<TId extends string>(id: string, members: readonly ToolEntry<TId>[]): ToolSet<TId>;
|
|
2927
|
+
/** Whether `entry` is a set rather than a single id. */
|
|
2928
|
+
declare function isToolSet<TId extends string>(entry: ToolEntry<TId>): entry is ToolSet<TId>;
|
|
2929
|
+
/** Every id `entries` name, sets opened in place, each id once at its first place. */
|
|
2930
|
+
declare function toolIdsOf<TId extends string>(entries: readonly ToolEntry<TId>[]): TId[];
|
|
2931
|
+
/** `set` less every id `omit` names, sets opened. Keeps `set`'s id with a `-without` suffix. */
|
|
2932
|
+
declare function withoutTools<TId extends string>(set: ToolSet<TId>, ...omit: readonly ToolEntry<TId>[]): ToolSet<TId>;
|
|
2933
|
+
/** Whether `set` names `id`, directly or through a set inside it. */
|
|
2934
|
+
declare function toolSetHas<TId extends string>(set: ToolSet<TId>, id: string): boolean;
|
|
2935
|
+
|
|
2801
2936
|
/**
|
|
2802
2937
|
* One row in the route registry — a single `GestureBinding` on one tool,
|
|
2803
2938
|
* flattened into the route grammar's vocabulary so the inspector, the
|
|
@@ -3092,12 +3227,21 @@ declare function deleteScratch<T>(store: ScratchStore, key: ScratchKey<T>): bool
|
|
|
3092
3227
|
*
|
|
3093
3228
|
* Output shape: **files first (source order), then strings (item order)**.
|
|
3094
3229
|
* For paste (`itemsFromClipboardData`) string flavors are emitted in
|
|
3095
|
-
* `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list
|
|
3230
|
+
* `INGEST_STRING_MIMES` order: text/plain, text/html, text/uri-list, then
|
|
3231
|
+
* the admitted ones in the order they were first admitted.
|
|
3096
3232
|
*/
|
|
3097
3233
|
|
|
3098
|
-
/** The string flavors the kit
|
|
3099
|
-
*
|
|
3234
|
+
/** The string flavors the kit always keeps. Any other is dropped at
|
|
3235
|
+
* materialization unless {@link admitIngestMime} has admitted it. */
|
|
3100
3236
|
declare const INGEST_STRING_MIMES: ReadonlySet<string>;
|
|
3237
|
+
/**
|
|
3238
|
+
* Keep string flavors of `mime` on drops, drags and pastes as well as the
|
|
3239
|
+
* {@link INGEST_STRING_MIMES}, so content from the app's own UI can travel
|
|
3240
|
+
* under a type of its own. Returns a release; the flavor is kept while any
|
|
3241
|
+
* admission of it is unreleased. Core admits each exact MIME a registered
|
|
3242
|
+
* content handler names.
|
|
3243
|
+
*/
|
|
3244
|
+
declare function admitIngestMime(mime: string): () => void;
|
|
3101
3245
|
/** Wrap raw `File`s (file picker, `CanvasExtensionApi.ingest`) as
|
|
3102
3246
|
* `IngestItem`s, normalizing each MIME the same way the drop/paste paths
|
|
3103
3247
|
* do. Synchronous — `File` objects are already fully owned. */
|
|
@@ -3202,6 +3346,8 @@ interface DepSchema {
|
|
|
3202
3346
|
activeTool: ActiveToolContextValue;
|
|
3203
3347
|
/** The menu host of the `ActionsProvider` in scope. */
|
|
3204
3348
|
menuHost?: MenuHost;
|
|
3349
|
+
/** The canvas's dispatch trace, for a dep to record a mode switch into. */
|
|
3350
|
+
dispatchTrace?: DispatchTrace;
|
|
3205
3351
|
}
|
|
3206
3352
|
/** Every dep name declared by anything that has merged into {@link DepSchema}. */
|
|
3207
3353
|
type DepName = keyof DepSchema;
|
|
@@ -3249,4 +3395,4 @@ type KernelOverlay = OverlaySchema extends {
|
|
|
3249
3395
|
overlay: infer O;
|
|
3250
3396
|
} ? O : unknown;
|
|
3251
3397
|
|
|
3252
|
-
export { type
|
|
3398
|
+
export { type Bounds as $, type Action as A, type BoundGesture as B, type Contribution as C, type DepRegistry as D, type ActionPresentation as E, type ActionSettings as F, type ActionSettingsState as G, type ActionShortcut as H, type ActionSource as I, type ActionTuning as J, type KernelOverlay as K, type LongPressOptions as L, type MenuHost as M, type NodeId as N, type OverlayPosition as O, type ActionTunings as P, type ActionsProp as Q, type RuleCtx as R, ScopeNode as S, ToolNode as T, type AnyTool as U, type AssemblyInputs as V, BROWSER_RESERVED_CHORDS as W, type BindingOpts as X, type BindingScope as Y, type BindingSource as Z, type BodyPredicate as _, type DepName as a, type PointerSessionCallbacks as a$, type BuildRuleCtxArgs as a0, COARSE_TARGET_SCALE as a1, type Carried as a2, type Carry as a3, type CarryCallbacks as a4, type CarryPayload as a5, type CarryPointer as a6, type CarryStage as a7, type Condition as a8, type Conflict as a9, type KeyReach as aA, type LongPressState as aB, type LongPressStore as aC, type MatchOutcome as aD, type MatchResult as aE, type MenuHit as aF, type MenuOpenRequest as aG, type MenuSources as aH, type MenuWhen as aI, type ModeShortcutHandlers as aJ, type ModeSwitchLogEntry as aK, type ModifierRole as aL, type ModifierRoles as aM, type ModifierState as aN, NEVER as aO, type OngoingHandle as aP, type OngoingInvoker as aQ, type OngoingOverlay as aR, type OpenMenu as aS, type OverlayRole as aT, type OverlaySchema as aU, type OwnedMenuDef as aV, PREDICATE_TARGET as aW, type PendingLongPress as aX, type PlacedBy as aY, type Point2 as aZ, type PointerSession as a_, type ContributionChrome as aa, type ContributionDeps as ab, type ContributionRouting as ac, DEFAULT_ALLOWED_CAPABILITIES as ad, DEFAULT_DEVICE_PROFILE as ae, type DebugSink as af, type DepListing as ag, type DetectedDeviceFacts as ah, type DeviceProfile as ai, type DispatchRecord as aj, type DispatchRecordInput as ak, type DispatcherContext as al, type DragSample as am, type DroppedCandidate as an, type Eligibility as ao, type EligibilityState as ap, type GestureBinding as aq, type GestureKitSettings as ar, type HandleKind as as, type HitShape as at, type HotkeyTrigger as au, INGEST_STRING_MIMES as av, type ImmediateInvoker as aw, type InvocationCtx as ax, type Invoker as ay, type KeyBinding as az, type DepSource as b, evaluateActive as b$, type PointerSessionCancelReason as b0, type PointerSessionOptions as b1, type RankedCandidate as b2, type RecordCandidate as b3, type RegistryEntry as b4, type ReservedChord as b5, type ResizeAnchor as b6, type ResolveAllOptions as b7, type ResolveOnlyResult as b8, type ResolvedCandidate as b9, actionCursor as bA, actionItemTitle as bB, actionItems as bC, actionSettingsParams as bD, actionShortcuts as bE, actionsTagged as bF, admitIngestMime as bG, assembleEntries as bH, browserReservedChord as bI, buildDepsFromRequires as bJ, buildRouteRegistry as bK, buildRuleCtx as bL, buildToolOffhandBindings as bM, clientToCanvas as bN, clientToCanvasRect as bO, createDepRegistry as bP, createDispatchTrace as bQ, createDispatcher as bR, createLongPressStore as bS, createMenuHost as bT, createTraceRing as bU, defineTool as bV, deleteScratch as bW, describeRule as bX, dlog as bY, dwarn as bZ, evaluate as b_, type RoleKeys as ba, type RoleRebinds as bb, type Rule as bc, type ScopedBinding as bd, type ScratchKey as be, type ScratchStore as bf, type SelectionApi as bg, type Selector as bh, type SpecificityPart as bi, TOOL_OFFHAND_ID as bj, type ToolDef as bk, type ToolEntry as bl, type ToolKeybinding as bm, type ToolModifiers as bn, type ToolOffhandBindingSpec as bo, type ToolPresentation as bp, type ToolScopes as bq, type ToolSet as br, type ToolSlot as bs, type TraceLogEntry as bt, type TraceRing as bu, type TunedBinding as bv, type UiOngoingControl as bw, type ViewportGestureKind as bx, type WalkStep as by, actionBindings as bz, type ActionDeps as c, tuneAction as c$, evaluateEnabled as c0, findConflicts as c1, getScratch as c2, heldKeySpec as c3, historyKey as c4, isAnchor as c5, isAnchorOrControl as c6, isBalanceGrip as c7, isBody as c8, isControlHandle as c9, modeShortcutSpec as cA, modeShortcuts as cB, multiTouchGestureId as cC, offhandKeyFor as cD, openPointerSession as cE, overlaysOf as cF, pickMenu as cG, pointerGestureId as cH, readActionSettings as cI, reportRouteConflicts as cJ, resolveDeviceProfile as cK, resolveMenu as cL, resolveParams as cM, resolveSchemaMenu as cN, roleReader as cO, routesForSpec as cP, ruleCanHoldIn as cQ, rulesExclusive as cR, scopeBindings as cS, scratchKey as cT, setScratch as cU, startCarry as cV, taggedActionItems as cW, targetConsultsAffordance as cX, toolIdsOf as cY, toolSet as cZ, toolSetHas as c_, isCornerWidget as ca, isDebugEnabled as cb, isEmpty as cc, isPathEditPart as cd, isResizeHandle as ce, isRotateHandle as cf, isSegment as cg, isSelectedBody as ch, isTool as ci, isToolSet as cj, isUnselectedBody as ck, itemTypesFromDataTransfer as cl, itemsFromClipboardData as cm, itemsFromDataTransfer as cn, itemsFromFiles as co, keySpecShortcut as cp, keysHeld as cq, liveScope as cr, makeToolOffhandAction as cs, matchActionItem as ct, matchBest as cu, matchSorted as cv, matchSortedWithBarred as cw, matchesKeyBinding as cx, mergeContributions as cy, mergeMenuDefs as cz, type ActionsRegistry as d, validateActionDefaultBinding as d0, validateActionId as d1, withoutTools as d2, ActionDisabledReason as e, type ActionVariant as f, type MenuDef as g, DepNode as h, type Tool as i, ActiveToolContextProvider as j, ActiveToolContextProviderIfRoot as k, type ActiveToolContextProviderProps as l, type ActiveToolContextValue as m, useOptionalActiveToolContext as n, type Dispatcher as o, type AffordanceHit as p, type DispatchTrace as q, type DepSchema as r, ALWAYS as s, type ActionDispatch as t, useActiveToolContext as u, type ActionEnabledResult as v, type ActionEntry as w, type ActionItem as x, type ActionParamSchema as y, type ActionParams as z };
|