@weasel-js/gestures 1.9.3 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +437 -221
- package/dist/index.js +438 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Reflection, matcher, and inspector UI all read this table. Adding a new
|
|
8
8
|
* gesture name in one place updates every consumer.
|
|
9
9
|
*/
|
|
10
|
-
type GestureName = 'click' | 'pointerDown' | 'dblTap' | 'drag' | 'wheel' | 'pinch' | 'keyDown' | 'keyUp' | 'keyHeld' | 'contextMenu' | 'longPress' | 'multiTouchTap' | 'drop' | 'paste';
|
|
10
|
+
type GestureName = 'click' | 'pointerDown' | 'hover' | 'dblTap' | 'drag' | 'wheel' | 'pinch' | 'keyDown' | 'keyUp' | 'keyHeld' | 'contextMenu' | 'longPress' | 'multiTouch' | 'multiTouchTap' | 'drop' | 'paste';
|
|
11
11
|
/**
|
|
12
12
|
* Gesture kinds an affordance claim can bar, in the spec vocabulary bindings
|
|
13
13
|
* are written in. `'pointer'` is one token because `pointerDown` / `click` /
|
|
@@ -32,7 +32,7 @@ interface GestureArgSpec {
|
|
|
32
32
|
interface GestureDescriptor {
|
|
33
33
|
name: GestureName;
|
|
34
34
|
/** Does the route's `.target` slot apply? Targetless gestures (`wheel`,
|
|
35
|
-
* `keyDown/Up`, `multiTouchTap`) elide it entirely in the v2 grammar. */
|
|
35
|
+
* `keyDown/Up`, `multiTouch`, `multiTouchTap`) elide it entirely in the v2 grammar. */
|
|
36
36
|
hasTarget: boolean;
|
|
37
37
|
/** Optional argument spec. Encoded as `gesture(value)` in the route string. */
|
|
38
38
|
arg?: GestureArgSpec;
|
|
@@ -62,9 +62,9 @@ type ModifierCombo = 'default' | 'mod' | 'shift' | 'alt' | 'mod+shift' | 'mod+al
|
|
|
62
62
|
*/
|
|
63
63
|
declare function mods(...keys: ReadonlyArray<'mod' | 'shift' | 'alt'>): ModifierCombo;
|
|
64
64
|
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
/** Where a tool stands when a binding is matched: `engaged` while an action it
|
|
66
|
+
* began is still in flight, `initial` otherwise. The route grammar's `[phase]`
|
|
67
|
+
* slot draws from this set. */
|
|
68
68
|
type RoutePhase = 'initial' | 'engaged';
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -83,9 +83,7 @@ type RoutePhase = 'initial' | 'engaged';
|
|
|
83
83
|
* sigil = '+' | '?' -- ! @ # $ % ^ & * reserved as id-prefix
|
|
84
84
|
* modName = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta'
|
|
85
85
|
*
|
|
86
|
-
*
|
|
87
|
-
* own phase"). `[engaged]` ≡ `[&:engaged]`; `[*]` ≡ `[&:*]`. The truly-loose
|
|
88
|
-
* form (any channel, any phase) is `[*:*]`.
|
|
86
|
+
* The truly-loose form (any channel, any phase) is `[*:*]`.
|
|
89
87
|
*
|
|
90
88
|
* Examples:
|
|
91
89
|
* [initial] click => empty +shift -- self idle
|
|
@@ -118,9 +116,7 @@ declare const RESERVED_ID_NAMES: ReadonlySet<string>;
|
|
|
118
116
|
/** v3 modifier requirement on a single modifier key. Absent from a
|
|
119
117
|
* `ParsedModifiers` map means "must not be held" (the strict default). */
|
|
120
118
|
type ModRequirement = 'required' | 'optional';
|
|
121
|
-
/** Canonical modifier names accepted in the modSlot, in canonical order.
|
|
122
|
-
* Single source of truth: the {@link ModifierKey} type and the runtime
|
|
123
|
-
* validator (`MOD_NAME_SET`) are both derived from this tuple. */
|
|
119
|
+
/** Canonical modifier names accepted in the modSlot, in canonical order. */
|
|
124
120
|
declare const VALID_MOD_NAMES: readonly ["mod", "shift", "alt", "ctrl", "meta"];
|
|
125
121
|
/** A single modifier name accepted in the modSlot. */
|
|
126
122
|
type ModifierKey = (typeof VALID_MOD_NAMES)[number];
|
|
@@ -167,18 +163,12 @@ declare function parseRoute(input: string): ParsedRoute;
|
|
|
167
163
|
declare function formatPhaseAtom(a: PhaseAtom): string;
|
|
168
164
|
/** Visual-collapse pass: given a list of route strings, fold pairs that
|
|
169
165
|
* differ ONLY in `shift` presence (one has `+shift`, the other doesn't,
|
|
170
|
-
* everything else identical) into a single route with `?shift`.
|
|
171
|
-
* for inspector / palette displays that want to show "Cmd+[" and
|
|
172
|
-
* "Cmd+Shift+[" as one Cmd+?Shift+[ chip rather than two rows.
|
|
166
|
+
* everything else identical) into a single route with `?shift`.
|
|
173
167
|
*
|
|
174
168
|
* Preserves input order — the first member of each fold-pair holds the
|
|
175
|
-
* position; the second is dropped.
|
|
176
|
-
* vs absent twin pass through unchanged. Does NOT fold other modifier
|
|
169
|
+
* position; the second is dropped. Does NOT fold other modifier
|
|
177
170
|
* pairs (alt/ctrl/meta/mod) — those tend to carry meaning across the
|
|
178
|
-
* same key
|
|
179
|
-
* actions, but Cmd+] vs Cmd+Shift+] is more often parametric variation
|
|
180
|
-
* of one action). Open the helper if a use case for the other
|
|
181
|
-
* modifiers shows up.
|
|
171
|
+
* same key.
|
|
182
172
|
*/
|
|
183
173
|
declare function collapseShiftPairs(routes: readonly string[]): string[];
|
|
184
174
|
/** Render a parsed route back to a canonical route string: shorthand where
|
|
@@ -186,33 +176,20 @@ declare function collapseShiftPairs(routes: readonly string[]): string[];
|
|
|
186
176
|
* `parseRoute(formatRoute(r))` yields `r` again. */
|
|
187
177
|
declare function formatRoute(r: ParsedRoute): string;
|
|
188
178
|
|
|
189
|
-
/**
|
|
190
|
-
* GestureSpec — describes the form of a user input event that can fire an action.
|
|
191
|
-
*
|
|
192
|
-
* Used by `Action.defaultBinding` (the action's preferred gesture) and by
|
|
193
|
-
* `GestureBinding.spec` (a tool's binding table entry). The dispatcher matches
|
|
194
|
-
* incoming input events against registered specs to determine which action to
|
|
195
|
-
* invoke.
|
|
196
|
-
*
|
|
197
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md` § "Types".
|
|
198
|
-
*/
|
|
179
|
+
/** GestureSpec — describes the form of a user input event that can fire an action. */
|
|
199
180
|
/** Optional modifier-key requirement for a gesture spec.
|
|
200
181
|
*
|
|
201
|
-
* Matching
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* This mirrors today's `KeyBinding` matcher and keeps conflict detection
|
|
206
|
-
* coherent.
|
|
182
|
+
* Matching is strict: an omitted modifier field means the modifier MUST NOT
|
|
183
|
+
* be held — a bare `{ kind: 'key', key: 'Escape' }` matches only unmodified
|
|
184
|
+
* Escape, NOT Cmd+Escape. A `true` means the modifier MUST be held; `false`
|
|
185
|
+
* is the same as omitted.
|
|
207
186
|
*
|
|
208
187
|
* `mod` is a platform-aware shorthand: matches `metaKey` on mac, `ctrlKey`
|
|
209
|
-
* elsewhere
|
|
188
|
+
* elsewhere.
|
|
210
189
|
*
|
|
211
|
-
*
|
|
212
|
-
* both acceptable" —
|
|
213
|
-
*
|
|
214
|
-
* does not. To widen other modifiers similarly, extend their type when
|
|
215
|
-
* a real consumer needs it.
|
|
190
|
+
* Every modifier additionally accepts `'optional'`, meaning "held or not,
|
|
191
|
+
* both acceptable" — used by actions like nudge whose step size depends on
|
|
192
|
+
* shift but whose firing does not.
|
|
216
193
|
*/
|
|
217
194
|
type ModSpec = Partial<{
|
|
218
195
|
alt: boolean | 'optional';
|
|
@@ -221,22 +198,30 @@ type ModSpec = Partial<{
|
|
|
221
198
|
mod: boolean | 'optional';
|
|
222
199
|
shift: boolean | 'optional';
|
|
223
200
|
}>;
|
|
224
|
-
/** The predicate form of {@link TargetSpec}. `hit` is the
|
|
225
|
-
*
|
|
201
|
+
/** The predicate form of {@link TargetSpec}. `hit` is the affordance hit
|
|
202
|
+
* under the pointer, for every gesture kind; `bodyTarget` is the optional
|
|
226
203
|
* body-class string ('empty' | 'selected-body' | 'unselected-body') when
|
|
227
|
-
* `classifyTarget` is wired.
|
|
228
|
-
*
|
|
204
|
+
* `classifyTarget` is wired. Build one with `hitPredicate` or
|
|
205
|
+
* `bodyPredicate`, which make the declaration below. */
|
|
229
206
|
interface TargetPredicate {
|
|
230
207
|
(hit: unknown, bodyTarget?: string): boolean;
|
|
231
|
-
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
readsAffordance
|
|
208
|
+
/** Whether the predicate reads `hit`. An exclusive affordance claim bars
|
|
209
|
+
* every binding whose target does not: `false` is a predicate that decides
|
|
210
|
+
* from `bodyTarget` alone, and chrome over a body must not read as that body. */
|
|
211
|
+
readsAffordance: boolean;
|
|
235
212
|
}
|
|
236
|
-
/**
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
213
|
+
/** A {@link TargetPredicate} that reads the affordance hit, alone or with the
|
|
214
|
+
* body class, so it stays in contention under an exclusive claim. */
|
|
215
|
+
declare function hitPredicate(fn: (hit: unknown, bodyTarget?: string) => boolean): TargetPredicate & {
|
|
216
|
+
readsAffordance: true;
|
|
217
|
+
};
|
|
218
|
+
/** A {@link TargetPredicate} that decides from the body class alone, so an
|
|
219
|
+
* exclusive affordance claim bars it. */
|
|
220
|
+
declare function bodyPredicate(fn: (bodyTarget: string | undefined) => boolean): TargetPredicate & {
|
|
221
|
+
readsAffordance: false;
|
|
222
|
+
};
|
|
223
|
+
/** Target selector for click and drag gesture specs. Consumers can pass
|
|
224
|
+
* `{ kindOf: predicate }` to classify hits themselves.
|
|
240
225
|
*
|
|
241
226
|
* Adding a form here is a compile error in `parseTargetSpec` until
|
|
242
227
|
* `TargetSpecForm` grows a matching variant — which is in turn a compile
|
|
@@ -245,25 +230,27 @@ type TargetSpec = 'empty' | 'selected-body' | 'unselected-body' | `kind:${string
|
|
|
245
230
|
kindOf: TargetPredicate;
|
|
246
231
|
};
|
|
247
232
|
/** Phase qualifier on a gesture spec. Restricts when the spec matches based
|
|
248
|
-
* on per-tool gesture-lifecycle state.
|
|
233
|
+
* on per-tool gesture-lifecycle state. When omitted, matches in any phase.
|
|
249
234
|
*
|
|
250
|
-
* Shorthand forms
|
|
235
|
+
* Shorthand forms gate on the binding's own tool:
|
|
251
236
|
* `'engaged'` → `[{ channel: '&', phase: 'engaged' }]` // self mid-gesture
|
|
252
237
|
* `'initial'` → `[{ channel: '&', phase: 'initial' }]` // self idle
|
|
253
238
|
* `'*'` → `[{ channel: '&', phase: '*' }]` // either self phase
|
|
254
239
|
*
|
|
255
240
|
* Array form for explicit channel:phase atoms — e.g. `[{ channel: 'rect',
|
|
256
241
|
* phase: 'engaged' }]` for "when the rect tool is mid-gesture, regardless of
|
|
257
|
-
* which scope I'm in."
|
|
258
|
-
* `@weasel-js/gestures/grammar` for the full lattice.
|
|
259
|
-
*
|
|
260
|
-
* When omitted, matches in any phase (preserves pre-phase behavior). */
|
|
242
|
+
* which scope I'm in." */
|
|
261
243
|
type PhaseSpec = 'initial' | 'engaged' | '*' | readonly PhaseAtom[];
|
|
262
244
|
/** Single-keystroke gesture (keydown). */
|
|
263
245
|
interface KeySpec {
|
|
264
246
|
kind: 'key';
|
|
265
247
|
/** A single key, or an array of acceptable keys (case-insensitive match). */
|
|
266
248
|
key: string | string[];
|
|
249
|
+
/** The physical key, as `KeyboardEvent.code` (`'BracketRight'`), or several.
|
|
250
|
+
* Tried before `key`, and either matching is enough: the binding then holds
|
|
251
|
+
* on that key whatever a layout or a held modifier makes it type, and still
|
|
252
|
+
* on any key that types `key`. `key` is what a route names the binding by. */
|
|
253
|
+
code?: string | string[];
|
|
267
254
|
mods?: ModSpec;
|
|
268
255
|
phase?: PhaseSpec;
|
|
269
256
|
}
|
|
@@ -273,13 +260,13 @@ interface KeyHeldSpec {
|
|
|
273
260
|
kind: 'key-held';
|
|
274
261
|
/** A single key, or an array of acceptable keys (case-insensitive match). */
|
|
275
262
|
key: string | string[];
|
|
263
|
+
/** The physical key or keys, as on {@link KeySpec.code}. */
|
|
264
|
+
code?: string | string[];
|
|
276
265
|
mods?: ModSpec;
|
|
277
266
|
phase?: PhaseSpec;
|
|
278
267
|
}
|
|
279
|
-
/** Wheel-event gesture. `direction` filters by deltaY sign
|
|
280
|
-
*
|
|
281
|
-
* - `'down'` → matches only deltaY > 0
|
|
282
|
-
* - `'*'` → matches either sign (default; universal-wildcard convention) */
|
|
268
|
+
/** Wheel-event gesture. `direction` filters by deltaY sign: `'up'` matches
|
|
269
|
+
* only deltaY < 0, `'down'` only deltaY > 0, and the default `'*'` either. */
|
|
283
270
|
interface WheelSpec {
|
|
284
271
|
kind: 'wheel';
|
|
285
272
|
direction?: 'up' | 'down' | '*';
|
|
@@ -288,10 +275,9 @@ interface WheelSpec {
|
|
|
288
275
|
phase?: PhaseSpec;
|
|
289
276
|
}
|
|
290
277
|
/** Pinch reported as a scale factor about a focal point (WebKit
|
|
291
|
-
* `gesturechange`). `direction` filters by the sample's scale
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* A touchscreen pinch is {@link MultiTouchSpec}, not this. */
|
|
278
|
+
* `gesturechange`). `direction` filters by the sample's scale: `'out'` is
|
|
279
|
+
* scale > 1, `'in'` is scale < 1; default `'*'`. A touchscreen pinch is
|
|
280
|
+
* {@link MultiTouchSpec}, not this. */
|
|
295
281
|
interface PinchSpec {
|
|
296
282
|
kind: 'pinch';
|
|
297
283
|
direction?: 'in' | 'out' | '*';
|
|
@@ -307,10 +293,8 @@ interface ClickSpec {
|
|
|
307
293
|
mods?: ModSpec;
|
|
308
294
|
phase?: PhaseSpec;
|
|
309
295
|
}
|
|
310
|
-
/** Double-click: two `click` events within
|
|
311
|
-
* Synthesized by `useGestureDispatcher`; emitted AFTER the second
|
|
312
|
-
* `click`. Bindings that want to handle a double-click should declare
|
|
313
|
-
* this kind rather than chasing two `click` events. */
|
|
296
|
+
/** Double-click: two `click` events within 600ms and 8px of each other.
|
|
297
|
+
* Synthesized by `useGestureDispatcher`; emitted AFTER the second `click`. */
|
|
314
298
|
interface DoubleClickSpec {
|
|
315
299
|
kind: 'doubleClick';
|
|
316
300
|
target?: TargetSpec;
|
|
@@ -335,13 +319,10 @@ interface DragSpec {
|
|
|
335
319
|
}
|
|
336
320
|
/**
|
|
337
321
|
* Bare pointer press, matched at down time — before the dispatcher knows
|
|
338
|
-
* whether the gesture will become a click or a drag.
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* highlights it immediately, and the drag that may follow then starts from an
|
|
343
|
-
* already-correct selection. Anything that can wait for the release belongs on
|
|
344
|
-
* a `click` spec, which does not fire on a press that turns into a drag.
|
|
322
|
+
* whether the gesture will become a click or a drag. Reach for this only when
|
|
323
|
+
* the effect must be visible while the button is still held; anything that
|
|
324
|
+
* can wait for the release belongs on a `click` spec, which does not fire on
|
|
325
|
+
* a press that turns into a drag.
|
|
345
326
|
*
|
|
346
327
|
* A matching binding does NOT own the gesture: the same press goes on to open
|
|
347
328
|
* a drag or synthesize a click as usual. Bind an immediate action here, not an
|
|
@@ -353,17 +334,30 @@ interface PointerDownSpec {
|
|
|
353
334
|
mods?: ModSpec;
|
|
354
335
|
phase?: PhaseSpec;
|
|
355
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* The pointer moving over the surface with no button pressed. Fires on every
|
|
339
|
+
* such move; a held pointer's moves belong to its drag and fire nothing here.
|
|
340
|
+
*
|
|
341
|
+
* A hover binding claims nothing. Every live binding that matches the move
|
|
342
|
+
* runs, not only the best one, and the move still reaches the hover-cursor
|
|
343
|
+
* pump. There is no press to own, so bind an immediate action, not an ongoing
|
|
344
|
+
* one. Its params carry `worldX` / `worldY`, `clientX` / `clientY`,
|
|
345
|
+
* `affordance`, `bodyTarget`, and `mods`.
|
|
346
|
+
*/
|
|
347
|
+
interface HoverSpec {
|
|
348
|
+
kind: 'hover';
|
|
349
|
+
target?: TargetSpec;
|
|
350
|
+
mods?: ModSpec;
|
|
351
|
+
phase?: PhaseSpec;
|
|
352
|
+
}
|
|
356
353
|
/**
|
|
357
354
|
* Press held past the long-press threshold without crossing the drag
|
|
358
355
|
* threshold. Synthesized by `useGestureDispatcher` from the pointer stream.
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* second is an ordinary slow click, and firing on it would produce a context
|
|
362
|
-
* menu nobody asked for.
|
|
356
|
+
* Fires for `touch` and `pen` pointers only: a mouse held still is an
|
|
357
|
+
* ordinary slow click.
|
|
363
358
|
*
|
|
364
359
|
* When a long-press matches no binding, the dispatcher re-dispatches it as a
|
|
365
|
-
* `contextmenu` event
|
|
366
|
-
* consumer changes, while `longPress` stays independently bindable.
|
|
360
|
+
* `contextmenu` event, so `contextMenu` bindings work under a finger.
|
|
367
361
|
*/
|
|
368
362
|
interface LongPressSpec {
|
|
369
363
|
kind: 'longPress';
|
|
@@ -379,8 +373,9 @@ interface MultiTouchSpec {
|
|
|
379
373
|
phase?: PhaseSpec;
|
|
380
374
|
}
|
|
381
375
|
/** Multi-touch tap gesture — fires when N fingers touch down then release
|
|
382
|
-
* together without movement past the tap threshold
|
|
383
|
-
* dispatcher from the underlying multitouch
|
|
376
|
+
* together without movement past the tap threshold, sooner than a long-press
|
|
377
|
+
* would fire. Synthesized by the dispatcher from the underlying multitouch
|
|
378
|
+
* tracking. */
|
|
384
379
|
interface MultiTouchTapSpec {
|
|
385
380
|
kind: 'multiTouchTap';
|
|
386
381
|
fingers: number;
|
|
@@ -404,10 +399,8 @@ interface PasteSpec {
|
|
|
404
399
|
mods?: ModSpec;
|
|
405
400
|
phase?: PhaseSpec;
|
|
406
401
|
}
|
|
407
|
-
/** The full union of supported gesture spec kinds.
|
|
408
|
-
|
|
409
|
-
* the `Action` type. */
|
|
410
|
-
type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | PinchSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
|
|
402
|
+
/** The full union of supported gesture spec kinds. */
|
|
403
|
+
type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | PinchSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | HoverSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
|
|
411
404
|
|
|
412
405
|
/**
|
|
413
406
|
* Mini-grammar for the `arg` slot of `keyDown` / `keyUp` routes.
|
|
@@ -475,8 +468,8 @@ declare const ROUTE_TERMS: {
|
|
|
475
468
|
* means without duplicating prose. */
|
|
476
469
|
declare const ROUTE_FIELD_DEFINITIONS: {
|
|
477
470
|
readonly phases: "Which lifecycle stage(s) a channel must be in for this route to fire. `initial` = the channel is idle; `engaged` = the channel is mid-gesture. The channel is the binding's own tool (`&`), any tool (`*`), or a named tool id.";
|
|
478
|
-
readonly gesture: "The class of input event that triggers this route — click, drag, double-tap, keyDown/keyUp, wheel, pinch, contextMenu, multiTouchTap, drop, or paste.";
|
|
479
|
-
readonly arg: "Sub-class of the gesture. Direction for wheel (up / down / *) and pinch (in / out / *), key name for keyDown / keyUp, finger count for multiTouchTap, MIME glob for drop / paste. Other gestures have no arg slot.";
|
|
471
|
+
readonly gesture: "The class of input event that triggers this route — click, drag, double-tap, keyDown/keyUp, wheel, pinch, contextMenu, multiTouch, multiTouchTap, drop, or paste.";
|
|
472
|
+
readonly arg: "Sub-class of the gesture. Direction for wheel (up / down / *) and pinch (in / out / *), key name for keyDown / keyUp, finger count for multiTouch / multiTouchTap, MIME glob for drop / paste. Other gestures have no arg slot.";
|
|
480
473
|
readonly target: "Which hit-test result the gesture must land on. `*` matches any target; `empty` matches the empty canvas; otherwise the value names a specific hit-target kind.";
|
|
481
474
|
readonly modifiers: "Modifier keys the user must hold (`+key`) or may optionally hold (`?key`) for this route to match. Unlisted modifiers must not be held.";
|
|
482
475
|
};
|
|
@@ -520,11 +513,7 @@ declare function modifierComboToParsed(key: ModifierCombo): ParsedModifiers;
|
|
|
520
513
|
* `name=req` pairs joined by `&`. */
|
|
521
514
|
declare function canonicalModifiers(mods: ParsedModifiers): string;
|
|
522
515
|
|
|
523
|
-
/**
|
|
524
|
-
* Keyboard/pointer modifier flags carried by every {@link InputEvent} arm.
|
|
525
|
-
* Factored out so the four booleans are documented once instead of repeated
|
|
526
|
-
* on all twelve event shapes.
|
|
527
|
-
*/
|
|
516
|
+
/** Keyboard/pointer modifier flags carried by every {@link InputEvent} arm. */
|
|
528
517
|
interface EventModifiers {
|
|
529
518
|
altKey: boolean;
|
|
530
519
|
ctrlKey: boolean;
|
|
@@ -541,24 +530,15 @@ type BodyTarget = 'empty' | 'selected-body' | 'unselected-body';
|
|
|
541
530
|
/**
|
|
542
531
|
* Semantic kind of the node body under the point — `'text'`, `'rect'`, or an
|
|
543
532
|
* app's own `'app:note'`. Companion to {@link BodyTarget}, which says only how
|
|
544
|
-
* the body relates to the selection
|
|
545
|
-
* `
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* Supplied by the same `classifyTarget` thunk that supplies `bodyTarget`, and
|
|
549
|
-
* absent for the same reasons plus one more: the thunk hit a body whose kind
|
|
550
|
-
* the scene can't name. An absent `bodyKind` makes every `kind:` form
|
|
551
|
-
* no-match, never a wildcard.
|
|
533
|
+
* the body relates to the selection. Supplied by the same `classifyTarget`
|
|
534
|
+
* thunk; an absent `bodyKind` makes every `kind:` form no-match, never a
|
|
535
|
+
* wildcard.
|
|
552
536
|
*/
|
|
553
537
|
type BodyKind = string;
|
|
554
538
|
/**
|
|
555
539
|
* What a scene hit-test found under a point — the shape a `classifyTarget`
|
|
556
540
|
* thunk returns, and the source of both {@link BodyTarget} and
|
|
557
541
|
* {@link BodyKind} on the events the matcher sees.
|
|
558
|
-
*
|
|
559
|
-
* One thunk returns both halves rather than two thunks returning one each:
|
|
560
|
-
* the two answers come from the same hit-test, and splitting them would cost
|
|
561
|
-
* a second pick on every pointer event.
|
|
562
542
|
*/
|
|
563
543
|
interface BodyClassification {
|
|
564
544
|
/** How the hit body relates to the current selection. */
|
|
@@ -571,12 +551,16 @@ interface BodyClassification {
|
|
|
571
551
|
interface KeyEvent extends EventModifiers {
|
|
572
552
|
kind: 'key';
|
|
573
553
|
key: string;
|
|
554
|
+
/** `KeyboardEvent.code`, when the source event had one. */
|
|
555
|
+
code?: string;
|
|
574
556
|
repeat?: boolean;
|
|
575
557
|
}
|
|
576
558
|
/** A held key transitioning down or up (e.g. space-to-pan). */
|
|
577
559
|
interface KeyHeldEvent extends EventModifiers {
|
|
578
560
|
kind: 'key-held';
|
|
579
561
|
key: string;
|
|
562
|
+
/** `KeyboardEvent.code`, when the source event had one. */
|
|
563
|
+
code?: string;
|
|
580
564
|
phase: 'down' | 'up';
|
|
581
565
|
}
|
|
582
566
|
/** A scroll-wheel / trackpad scroll, with raw deltas and client coords. */
|
|
@@ -629,14 +613,10 @@ interface PointerSampleStylus {
|
|
|
629
613
|
tiltY?: number;
|
|
630
614
|
}
|
|
631
615
|
/**
|
|
632
|
-
* Which physical pointer a sample came from
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
* mouse, each touch, and the stylus into one slot. Optional because
|
|
637
|
-
* synthesized events (tests, programmatic drags, the hover pump's
|
|
638
|
-
* `resolveOnly` probe) have no originating `PointerEvent`; those all key to
|
|
639
|
-
* the same default slot, which is the pre-`pointerId` behavior.
|
|
616
|
+
* Which physical pointer a sample came from, so the dispatcher can key
|
|
617
|
+
* in-flight gesture handles per pointer. Optional because synthesized events
|
|
618
|
+
* (tests, programmatic drags, the hover pump's `resolveOnly` probe) have no
|
|
619
|
+
* originating `PointerEvent`; those all key to the same default slot.
|
|
640
620
|
*/
|
|
641
621
|
interface PointerIdentity {
|
|
642
622
|
/** `PointerEvent.pointerId` from the originating DOM event. */
|
|
@@ -646,21 +626,17 @@ interface PointerIdentity {
|
|
|
646
626
|
interface PointerDownEvent extends EventModifiers, PointerSampleStylus, PointerIdentity {
|
|
647
627
|
kind: 'pointerdown';
|
|
648
628
|
/**
|
|
649
|
-
* Which of the two dispatches this press produces.
|
|
650
|
-
*
|
|
651
|
-
* A physical pointerdown is dispatched twice, for two different audiences:
|
|
629
|
+
* Which of the two dispatches this press produces. A physical pointerdown
|
|
630
|
+
* is dispatched twice:
|
|
652
631
|
*
|
|
653
632
|
* - `'press'` — emitted immediately, at down time. Matches `pointerDown`
|
|
654
|
-
* specs only.
|
|
655
|
-
* gesture is classified (select applies the selection here, so
|
|
656
|
-
* press-and-hold highlights without waiting for a release).
|
|
633
|
+
* specs only.
|
|
657
634
|
* - omitted — the copy the dispatcher buffers and flushes once movement
|
|
658
635
|
* crosses the drag threshold. Matches `drag` specs only, and is what
|
|
659
636
|
* opens a drag handle.
|
|
660
637
|
*
|
|
661
638
|
* The split is what keeps one press from firing both a `pointerDown` and a
|
|
662
|
-
* `drag` binding.
|
|
663
|
-
* omitted, so drag prediction is unaffected.
|
|
639
|
+
* `drag` binding.
|
|
664
640
|
*/
|
|
665
641
|
stage?: 'press';
|
|
666
642
|
target?: unknown;
|
|
@@ -707,8 +683,6 @@ interface ClickEvent extends EventModifiers {
|
|
|
707
683
|
/**
|
|
708
684
|
* World-space coordinates of the click, derived via the `clientToWorld`
|
|
709
685
|
* thunk supplied to the dispatcher. Absent when the thunk isn't wired.
|
|
710
|
-
* Forwarded into action params so click invokers can act on the click's
|
|
711
|
-
* position without their own pointer-listener plumbing.
|
|
712
686
|
*/
|
|
713
687
|
x?: number;
|
|
714
688
|
y?: number;
|
|
@@ -722,23 +696,18 @@ interface ClickEvent extends EventModifiers {
|
|
|
722
696
|
/**
|
|
723
697
|
* World-space coordinates of the *press* that opened this click, as opposed
|
|
724
698
|
* to `x`/`y`, which are the release. The two differ by up to the
|
|
725
|
-
* drag threshold (`DRAG_THRESHOLD_PX`, in screen pixels).
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* press point keeps a snapped coordinate stable against the small drift
|
|
730
|
-
* between press and release. Absent when the `clientToWorld` thunk isn't
|
|
731
|
-
* wired, same as `x`/`y`.
|
|
699
|
+
* drag threshold (`DRAG_THRESHOLD_PX`, in screen pixels). Actions that place
|
|
700
|
+
* geometry at the click want this one: pinning to the press point keeps a
|
|
701
|
+
* snapped coordinate stable against the drift between press and release.
|
|
702
|
+
* Absent when the `clientToWorld` thunk isn't wired, same as `x`/`y`.
|
|
732
703
|
*/
|
|
733
704
|
pressX?: number;
|
|
734
705
|
pressY?: number;
|
|
735
706
|
/**
|
|
736
707
|
* Affordance the *press* landed on, carried forward from the pointerdown
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
* would receive the raw DOM element while the same predicate on a drag spec
|
|
741
|
-
* receives the affordance.
|
|
708
|
+
* like {@link ClickEvent.bodyTarget} is. Without this a `kindOf` target on
|
|
709
|
+
* a click spec would receive the raw DOM element while the same predicate
|
|
710
|
+
* on a drag spec receives the affordance.
|
|
742
711
|
*/
|
|
743
712
|
affordance?: unknown;
|
|
744
713
|
bodyTarget?: BodyTarget;
|
|
@@ -772,6 +741,18 @@ interface ContextMenuEvent extends EventModifiers {
|
|
|
772
741
|
bodyTarget?: BodyTarget;
|
|
773
742
|
bodyKind?: BodyKind;
|
|
774
743
|
}
|
|
744
|
+
/** A move of a pointer with no button pressed. Matched by `hover` specs. */
|
|
745
|
+
interface HoverEvent extends EventModifiers {
|
|
746
|
+
kind: 'hover';
|
|
747
|
+
/** World-space position of the pointer. */
|
|
748
|
+
x: number;
|
|
749
|
+
y: number;
|
|
750
|
+
clientX?: number;
|
|
751
|
+
clientY?: number;
|
|
752
|
+
affordance?: unknown;
|
|
753
|
+
bodyTarget?: BodyTarget;
|
|
754
|
+
bodyKind?: BodyKind;
|
|
755
|
+
}
|
|
775
756
|
/** A press held past the long-press threshold without moving. */
|
|
776
757
|
interface LongPressEvent extends EventModifiers {
|
|
777
758
|
kind: 'longpress';
|
|
@@ -797,9 +778,8 @@ interface MultiTouchEvent extends EventModifiers {
|
|
|
797
778
|
* which is the space `zoomAt` anchors in. Not client coords, and not world:
|
|
798
779
|
* calling `clientToWorld` on it subtracts the canvas origin a second time.
|
|
799
780
|
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
* pointerdown-triggered multitouch event.
|
|
781
|
+
* Present on the event a finger landing produces and on each move of a
|
|
782
|
+
* running gesture; absent on the one that ends it.
|
|
803
783
|
*/
|
|
804
784
|
centroid?: {
|
|
805
785
|
x: number;
|
|
@@ -808,10 +788,21 @@ interface MultiTouchEvent extends EventModifiers {
|
|
|
808
788
|
/**
|
|
809
789
|
* Distance between the two primary pointers, in CSS pixels. A distance is
|
|
810
790
|
* origin-free, so it reads the same in client and canvas-local space.
|
|
811
|
-
*
|
|
812
|
-
* event.
|
|
791
|
+
* Present and absent alongside `centroid`.
|
|
813
792
|
*/
|
|
814
793
|
spread?: number;
|
|
794
|
+
/**
|
|
795
|
+
* Direction from the first of the two primary pointers to the second, in
|
|
796
|
+
* degrees in (-180, 180], growing clockwise on screen. Present and absent
|
|
797
|
+
* alongside `centroid`.
|
|
798
|
+
*/
|
|
799
|
+
angle?: number;
|
|
800
|
+
/**
|
|
801
|
+
* Set on the event that ends the running multitouch gesture, whatever its
|
|
802
|
+
* finger count: `'commit'` when a finger lifts, `'cancel'` when one of its
|
|
803
|
+
* pointers is canceled. Such an event starts nothing.
|
|
804
|
+
*/
|
|
805
|
+
end?: 'commit' | 'cancel';
|
|
815
806
|
}
|
|
816
807
|
/** A multi-finger tap (no drag). */
|
|
817
808
|
interface MultiTouchTapEvent extends EventModifiers {
|
|
@@ -849,47 +840,23 @@ interface PasteEvent extends EventModifiers {
|
|
|
849
840
|
items: readonly IngestItem[];
|
|
850
841
|
}
|
|
851
842
|
/**
|
|
852
|
-
* Normalized input-event shape consumed by the pure matcher.
|
|
853
|
-
* React seam (`useGestureDispatcher`) from DOM events. Pump-only events
|
|
843
|
+
* Normalized input-event shape consumed by the pure matcher. Pump-only events
|
|
854
844
|
* ({@link PointerMoveEvent}, {@link PointerUpEvent}, {@link PointerCancelEvent})
|
|
855
845
|
* ride in the same union so the dispatcher's `handleInput` signature stays
|
|
856
846
|
* uniform; the matcher itself never matches them.
|
|
857
847
|
*
|
|
858
848
|
* The `affordance?` field on {@link PointerDownEvent} is widened to `unknown`
|
|
859
849
|
* in this package — the kit narrows it back to `AffordanceHit` at consumption.
|
|
860
|
-
* This keeps `@weasel-js/gestures` free of any kit-affordance type.
|
|
861
850
|
*/
|
|
862
|
-
type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | PasteEvent;
|
|
851
|
+
type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | HoverEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | PasteEvent;
|
|
863
852
|
|
|
864
853
|
/**
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
* The kit's dispatcher orchestrator wraps these primitives with its
|
|
868
|
-
* actions-layer concerns (BindingScope, ScopedBinding, MatchResult, matchBest).
|
|
869
|
-
*
|
|
870
|
-
* ## key-held phase decision
|
|
871
|
-
* `matchSpec` returns `true` only for `phase: 'down'`, not `phase: 'up'`.
|
|
872
|
-
* The dispatcher tracks the held key independently and handles the `up` phase
|
|
873
|
-
* itself; the matcher never sees an "un-match" it needs to distinguish.
|
|
874
|
-
* This keeps the matcher's output a simple boolean and avoids a three-valued
|
|
875
|
-
* return type.
|
|
876
|
-
*
|
|
877
|
-
* ## TargetSpec string forms
|
|
878
|
-
* Every form `TargetSpec` declares is resolved here, each from a different
|
|
879
|
-
* field the dispatcher packs onto the event:
|
|
880
|
-
*
|
|
881
|
-
* | Form | Read from | Supplied by |
|
|
882
|
-
* | --- | --- | --- |
|
|
883
|
-
* | `'empty'` / `'selected-body'` / `'unselected-body'` | `bodyTarget` | the `classifyTarget` thunk |
|
|
884
|
-
* | `kind:<k>` / `kind:<k>:selected` | `bodyKind` + `bodyTarget` | the `classifyTarget` thunk's node-kind resolver |
|
|
885
|
-
* | `affordance:<k>` | the raw target's `.kind` | the `affordanceAt` thunk |
|
|
886
|
-
* | `{ kindOf }` | the raw target + `bodyTarget` | both |
|
|
887
|
-
*
|
|
888
|
-
* A form whose source field is absent does not match — an unwired
|
|
889
|
-
* `classifyTarget` makes every `bodyTarget`/`bodyKind` form `false` rather
|
|
890
|
-
* than a silent wildcard. That is deliberate: a binding that can't be
|
|
891
|
-
* evaluated should stay out of the way of one that can.
|
|
854
|
+
* The stylus fields `src` carries, as a spread-able partial: a sample that
|
|
855
|
+
* carries none of them adds no `undefined`-valued keys to what it is spread into.
|
|
892
856
|
*/
|
|
857
|
+
declare function stylusOf(src: PointerSampleStylus): PointerSampleStylus;
|
|
858
|
+
|
|
859
|
+
/** Pure gesture matcher — no React, no state, no DOM. */
|
|
893
860
|
|
|
894
861
|
/** The modifier-state fields `matchModifiers` reads. Structural, so a DOM
|
|
895
862
|
* `KeyboardEvent` or `PointerEvent` satisfies it directly. */
|
|
@@ -902,27 +869,21 @@ type ModifiersEvent = {
|
|
|
902
869
|
/**
|
|
903
870
|
* Strict modifier match.
|
|
904
871
|
*
|
|
905
|
-
* - Omitted
|
|
906
|
-
* - `true
|
|
907
|
-
* - `
|
|
908
|
-
* - `
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
* When `mod` is set, the corresponding `meta`/`ctrl` field is implied AND
|
|
912
|
-
* the *other* platform key is forbidden. Callers should not combine `mod`
|
|
913
|
-
* with `meta`/`ctrl`.
|
|
872
|
+
* - Omitted or `false`: MUST NOT be held.
|
|
873
|
+
* - `true`: MUST be held.
|
|
874
|
+
* - `'optional'`: accepts either held or unheld.
|
|
875
|
+
* - `mod` is platform-aware: matches `metaKey` on mac, `ctrlKey` elsewhere,
|
|
876
|
+
* and the *other* platform key is forbidden. Callers should not combine
|
|
877
|
+
* `mod` with `meta`/`ctrl`.
|
|
914
878
|
*/
|
|
915
879
|
declare function matchModifiers(e: ModifiersEvent, mods: ModSpec | undefined, isMac: boolean): boolean;
|
|
916
880
|
/** Case-insensitive key match; supports string or string[] (any-of). */
|
|
917
881
|
declare function matchKey(eventKey: string, specKey: string | string[]): boolean;
|
|
918
882
|
/**
|
|
919
883
|
* A {@link TargetSpec} resolved to its form, with the parts each form encodes
|
|
920
|
-
* in its string pulled out.
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
* each does something different — `matchTarget` below, and `targetRank` /
|
|
924
|
-
* `targetConsultsAffordance` in `@weasel-js/core`'s dispatcher matcher — so
|
|
925
|
-
* they switch on `form` exhaustively rather than re-deriving the prefixes.
|
|
884
|
+
* in its string pulled out. This is the one enumeration of the target forms:
|
|
885
|
+
* sites that act on it switch on `form` exhaustively rather than re-deriving
|
|
886
|
+
* the prefixes.
|
|
926
887
|
*/
|
|
927
888
|
type TargetSpecForm = {
|
|
928
889
|
form: 'body';
|
|
@@ -949,16 +910,13 @@ declare function parseTargetSpec(spec: TargetSpec): TargetSpecForm | null;
|
|
|
949
910
|
/**
|
|
950
911
|
* Match a target value + event against a TargetSpec.
|
|
951
912
|
*
|
|
952
|
-
* - `{ kindOf: predicate }` — calls the predicate with the
|
|
953
|
-
*
|
|
954
|
-
* `bodyTarget` string. Predicates that only need the target can ignore
|
|
955
|
-
* the second arg.
|
|
913
|
+
* - `{ kindOf: predicate }` — calls the predicate with the affordance hit and
|
|
914
|
+
* the `bodyTarget` string.
|
|
956
915
|
* - `'empty'`, `'selected-body'`, `'unselected-body'` — compared against
|
|
957
|
-
* `bodyTarget` on the event
|
|
958
|
-
* `classifyTarget` thunk is supplied).
|
|
916
|
+
* `bodyTarget` on the event.
|
|
959
917
|
* - `kind:<k>` — compared against `bodyKind`, the *semantic* kind of the node
|
|
960
|
-
* body under the point (`'text'`, `'rect'`, an app's own `'app:note'`)
|
|
961
|
-
*
|
|
918
|
+
* body under the point (`'text'`, `'rect'`, an app's own `'app:note'`).
|
|
919
|
+
* Selection-agnostic.
|
|
962
920
|
* - `kind:<k>:selected` — the same, and additionally requires the body to be
|
|
963
921
|
* in the current selection. Only the final `:selected` is a suffix, so a
|
|
964
922
|
* kind may itself contain colons.
|
|
@@ -974,16 +932,11 @@ declare function parseTargetSpec(spec: TargetSpec): TargetSpecForm | null;
|
|
|
974
932
|
* silent wildcard.
|
|
975
933
|
*/
|
|
976
934
|
declare function matchTarget(target: unknown, specTarget: TargetSpec | undefined, bodyTarget?: string, bodyKind?: string): boolean;
|
|
977
|
-
/** Per-tool gesture-lifecycle state at match time.
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
* that case any `phase: '&'` atom can't match.
|
|
983
|
-
* - `engagedChannels` is the set of tool ids that currently have an
|
|
984
|
-
* in-flight handle. Derived by the dispatcher from its `inFlight()`
|
|
985
|
-
* map keyed by tool id at match time.
|
|
986
|
-
*/
|
|
935
|
+
/** Per-tool gesture-lifecycle state at match time. `selfChannel` is the tool
|
|
936
|
+
* id `'&'` resolves to — the tool that owns the binding being evaluated — or
|
|
937
|
+
* `null` for a binding without an owning tool, in which case no `'&'` atom
|
|
938
|
+
* can match. `engagedChannels` is the set of tool ids that currently have an
|
|
939
|
+
* in-flight handle. */
|
|
987
940
|
interface PhaseContext {
|
|
988
941
|
selfChannel: string | null;
|
|
989
942
|
engagedChannels: ReadonlySet<string>;
|
|
@@ -993,23 +946,60 @@ interface PhaseContext {
|
|
|
993
946
|
* constraint). Union semantics: a phase atom list matches when ANY
|
|
994
947
|
* atom matches. */
|
|
995
948
|
declare function matchPhase(spec: PhaseSpec | undefined, ctx: PhaseContext): boolean;
|
|
949
|
+
/**
|
|
950
|
+
* Match a single GestureSpec against an InputEvent.
|
|
951
|
+
*
|
|
952
|
+
* For `key-held`, only `phase: 'down'` returns true. The dispatcher tracks the
|
|
953
|
+
* held state independently and handles `phase: 'up'` itself.
|
|
954
|
+
*/
|
|
955
|
+
declare function matchSpec(e: InputEvent, spec: GestureSpec, isMac: boolean, phaseCtx?: PhaseContext): boolean;
|
|
956
|
+
|
|
957
|
+
/** Which external content a `drop` or `paste` spec's MIME globs let through. */
|
|
996
958
|
/** MIME-glob match: `'image/*'` prefix-matches the major type; anything
|
|
997
959
|
* else is an exact (case-insensitive) match; bare `'*'` or `'*\/*'` matches all. */
|
|
998
960
|
declare function mimeMatchesGlob(mime: string, glob: string): boolean;
|
|
961
|
+
/** The MIME of an item whose type is not known yet: a file hovering in an OS
|
|
962
|
+
* drag, which some browsers do not name until it is dropped. */
|
|
963
|
+
declare const UNKNOWN_MIME = "*/*";
|
|
999
964
|
/** True if ANY item's MIME matches ANY of the `types` globs.
|
|
1000
|
-
* `types: []` (empty) ≡ omitted = match any.
|
|
965
|
+
* `types: []` (empty) ≡ omitted = match any. An item of
|
|
966
|
+
* {@link UNKNOWN_MIME} passes every glob. */
|
|
1001
967
|
declare function matchIngestTypes(items: readonly {
|
|
1002
968
|
mime: string;
|
|
1003
969
|
}[], types: string[] | undefined): boolean;
|
|
970
|
+
|
|
1004
971
|
/**
|
|
1005
|
-
*
|
|
972
|
+
* Whether this is an Apple platform, where `mod` means the Command key and
|
|
973
|
+
* not Ctrl. iPhone and iPad count: with a keyboard attached their shortcuts
|
|
974
|
+
* are Command shortcuts. False where there is no `navigator`.
|
|
1006
975
|
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
* For TargetSpec string forms, returns false (kind registry not yet available).
|
|
976
|
+
* Pass the answer to {@link matchModifiers} / {@link matchSpec} as `isMac`,
|
|
977
|
+
* and read it wherever a `mod` shortcut is drawn, so a label names the key
|
|
978
|
+
* the matcher listens for.
|
|
1011
979
|
*/
|
|
1012
|
-
declare function
|
|
980
|
+
declare function isApplePlatform(): boolean;
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* The nearest element `from` sits inside that can host a portalled overlay,
|
|
984
|
+
* or `null` when there is none and the body is the only answer.
|
|
985
|
+
*/
|
|
986
|
+
declare function nearestPortalHost(from: Element | null | undefined): HTMLElement | null;
|
|
987
|
+
/**
|
|
988
|
+
* Where a `position: fixed` element following the pointer mounts: `host`, and
|
|
989
|
+
* the client-space point its `left: 0; top: 0` lands on.
|
|
990
|
+
*/
|
|
991
|
+
interface FixedSeat {
|
|
992
|
+
host: HTMLElement;
|
|
993
|
+
left: number;
|
|
994
|
+
top: number;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* The seat for something drawn at a client-space point on behalf of `from`, a
|
|
998
|
+
* drag ghost above all: the nearest portal host, so it resolves the theme
|
|
999
|
+
* `from` is drawn in, or the body. Subtract `left` and `top` from the client
|
|
1000
|
+
* point to place it.
|
|
1001
|
+
*/
|
|
1002
|
+
declare function fixedSeat(from: Element | null | undefined): FixedSeat;
|
|
1013
1003
|
|
|
1014
1004
|
/** CSS-style specificity tuple for a GestureSpec. Higher tuple wins under
|
|
1015
1005
|
* lexicographic compare. Dimensions, in order of precedence:
|
|
@@ -1022,14 +1012,14 @@ declare function matchSpec(e: InputEvent, spec: GestureSpec, isMac: boolean, pha
|
|
|
1022
1012
|
* non-empty `types` MIME filter, else 1.
|
|
1023
1013
|
*
|
|
1024
1014
|
* Identical tuples fall back to registration order in the matcher's
|
|
1025
|
-
* stable sort
|
|
1015
|
+
* stable sort. */
|
|
1026
1016
|
declare function specificity(spec: GestureSpec): readonly [number, number, number, number];
|
|
1027
1017
|
|
|
1028
1018
|
/**
|
|
1029
1019
|
* How far, in CSS pixels, a press must travel before it is a drag rather than
|
|
1030
|
-
* a click. `drag` specs match only past it:
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1020
|
+
* a click. `drag` specs match only past it: the pointer recognizer holds a
|
|
1021
|
+
* pointerdown back until the pointer crosses it, so a press-and-release that
|
|
1022
|
+
* stays under it never opens a drag.
|
|
1033
1023
|
*/
|
|
1034
1024
|
declare const DRAG_THRESHOLD_PX = 4;
|
|
1035
1025
|
/** Whether a pointer that went down at `from` has travelled far enough, by
|
|
@@ -1042,6 +1032,214 @@ declare function pastDragThreshold(from: {
|
|
|
1042
1032
|
clientY: number;
|
|
1043
1033
|
}, threshold?: number): boolean;
|
|
1044
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* The times and distances that tell one pointer gesture from another. The
|
|
1037
|
+
* drag threshold sits with its helper in `ui/dragThreshold.ts`.
|
|
1038
|
+
*/
|
|
1039
|
+
/** How long a touch or pen press is held before it fires `longPress`, in ms. */
|
|
1040
|
+
declare const LONG_PRESS_MS = 500;
|
|
1041
|
+
/** The longest gap between two clicks that still makes a double click, in ms:
|
|
1042
|
+
* the upper end of OS double-click settings. */
|
|
1043
|
+
declare const DOUBLE_CLICK_MAX_MS = 600;
|
|
1044
|
+
/** How far apart, in CSS pixels, two clicks may land and still make a double
|
|
1045
|
+
* click: the drift of a real mouse or trackpad between them. */
|
|
1046
|
+
declare const DOUBLE_CLICK_MAX_PX = 8;
|
|
1047
|
+
/** How far, in CSS pixels, a finger or the centroid of a multitouch gesture
|
|
1048
|
+
* may travel before the gesture stops being a tap. */
|
|
1049
|
+
declare const TAP_THRESHOLD_PX = 8;
|
|
1050
|
+
|
|
1051
|
+
/** A position in whatever space the recognizer's `positionOf` reports. */
|
|
1052
|
+
interface Point {
|
|
1053
|
+
x: number;
|
|
1054
|
+
y: number;
|
|
1055
|
+
}
|
|
1056
|
+
/** Where two or more pointers sit: what `multiTouchGeometry` reads from their positions. */
|
|
1057
|
+
interface MultiTouchGeometry {
|
|
1058
|
+
centroid: Point;
|
|
1059
|
+
/** Distance between the first two pointers. */
|
|
1060
|
+
spread: number;
|
|
1061
|
+
/** Direction from the first pointer to the second, in degrees in (-180, 180],
|
|
1062
|
+
* growing clockwise on screen. */
|
|
1063
|
+
angle: number;
|
|
1064
|
+
}
|
|
1065
|
+
/** Every pointer's centroid, and the first two's spread and angle. All zero with fewer than 2 pointers. */
|
|
1066
|
+
declare function multiTouchGeometry(positions: Iterable<Point>): MultiTouchGeometry;
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* What the recognizer remembers of a press, and the events a release or a
|
|
1070
|
+
* hold on it synthesizes.
|
|
1071
|
+
*/
|
|
1072
|
+
|
|
1073
|
+
/** Press-time state replayed onto a synthesized click, long-press or
|
|
1074
|
+
* contextmenu, whose matchers resolve targets against it. */
|
|
1075
|
+
interface PressFacts extends EventModifiers {
|
|
1076
|
+
target: unknown;
|
|
1077
|
+
clientX: number;
|
|
1078
|
+
clientY: number;
|
|
1079
|
+
/** World-space press point, forwarded onto the synthesized click as
|
|
1080
|
+
* `pressX`/`pressY` — see `ClickEvent`. */
|
|
1081
|
+
worldX: number;
|
|
1082
|
+
worldY: number;
|
|
1083
|
+
affordance?: unknown;
|
|
1084
|
+
bodyTarget?: BodyTarget;
|
|
1085
|
+
bodyKind?: BodyKind;
|
|
1086
|
+
}
|
|
1087
|
+
/** Where and how a press was released, for the click it turned out to be. */
|
|
1088
|
+
interface ReleaseFacts extends EventModifiers {
|
|
1089
|
+
target: unknown;
|
|
1090
|
+
clientX: number;
|
|
1091
|
+
clientY: number;
|
|
1092
|
+
worldX: number;
|
|
1093
|
+
worldY: number;
|
|
1094
|
+
}
|
|
1095
|
+
/** A press released where it went down, as a click, and as the double click
|
|
1096
|
+
* it is when it follows another closely. Both happen at the release, with the
|
|
1097
|
+
* release's modifiers, on what the press landed on. */
|
|
1098
|
+
declare function clickEvents(down: PressFacts, up: ReleaseFacts): {
|
|
1099
|
+
click: InputEvent;
|
|
1100
|
+
doubleclick: InputEvent;
|
|
1101
|
+
};
|
|
1102
|
+
/** A held press as a long-press, and as the contextmenu it falls back to when
|
|
1103
|
+
* the long-press matches nothing. The fallback is what makes existing
|
|
1104
|
+
* `contextMenu` bindings reachable by touch with no consumer change. */
|
|
1105
|
+
declare function longPressEvents(down: PressFacts): {
|
|
1106
|
+
longpress: InputEvent;
|
|
1107
|
+
contextmenu: InputEvent;
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
/** A press released in place: its click, and the double click a second one makes. */
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* The click a double click would follow. Held by the host rather than the
|
|
1114
|
+
* recognizer when it has to outlive one: a host that rebuilds its recognizer
|
|
1115
|
+
* between two clicks would otherwise drop the double click.
|
|
1116
|
+
*/
|
|
1117
|
+
interface ClickMemory {
|
|
1118
|
+
last: {
|
|
1119
|
+
t: number;
|
|
1120
|
+
clientX: number;
|
|
1121
|
+
clientY: number;
|
|
1122
|
+
} | null;
|
|
1123
|
+
}
|
|
1124
|
+
/** Told of clicks without competing for them, as a binding would. */
|
|
1125
|
+
interface ClickObserver {
|
|
1126
|
+
/** After the click was dispatched, so whatever its binding changed is readable. */
|
|
1127
|
+
click?(world: Point): void;
|
|
1128
|
+
/** Before the double click is dispatched, whichever binding then handles it. */
|
|
1129
|
+
doubleClick?(world: Point): void;
|
|
1130
|
+
}
|
|
1131
|
+
/** What a release composed. `doubleClick` is absent unless one was dispatched. */
|
|
1132
|
+
interface ReleaseOutcome {
|
|
1133
|
+
doubleClick?: 'handled' | 'unhandled';
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** The one press being timed toward a long-press. */
|
|
1137
|
+
|
|
1138
|
+
/** A clock and a timer, so a host or a test supplies its own. */
|
|
1139
|
+
interface RecognizerClock {
|
|
1140
|
+
now(): number;
|
|
1141
|
+
setTimer(fn: () => void, ms: number): unknown;
|
|
1142
|
+
clearTimer(timer: unknown): void;
|
|
1143
|
+
}
|
|
1144
|
+
/** A press held toward a long-press. */
|
|
1145
|
+
interface HeldPress {
|
|
1146
|
+
pointerId: number;
|
|
1147
|
+
/** A mouse never arms one. */
|
|
1148
|
+
pointerType: 'touch' | 'pen';
|
|
1149
|
+
press: PressFacts;
|
|
1150
|
+
/** `RecognizerClock.now()` when the hold started. */
|
|
1151
|
+
startedAt: number;
|
|
1152
|
+
/** How long the hold must last before the long-press fires, in ms. */
|
|
1153
|
+
duration: number;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* What a host feeds the pointer recognizer, one per pointer event, and the
|
|
1158
|
+
* pump events each becomes.
|
|
1159
|
+
*/
|
|
1160
|
+
|
|
1161
|
+
/** One pointer event, in client space. */
|
|
1162
|
+
interface PointerSample extends EventModifiers, PointerSampleStylus {
|
|
1163
|
+
pointerId: number;
|
|
1164
|
+
clientX: number;
|
|
1165
|
+
clientY: number;
|
|
1166
|
+
/** What the originating event was aimed at. */
|
|
1167
|
+
target?: unknown;
|
|
1168
|
+
}
|
|
1169
|
+
/** A pointer going down, with what the host found under it. */
|
|
1170
|
+
interface PressSample extends PointerSample {
|
|
1171
|
+
/** The world point the host hit-tested at. */
|
|
1172
|
+
x: number;
|
|
1173
|
+
y: number;
|
|
1174
|
+
/** `PointerEvent.pointerType`. Only `'touch'` and `'pen'` arm a long-press. */
|
|
1175
|
+
pointerType: string;
|
|
1176
|
+
affordance?: unknown;
|
|
1177
|
+
bodyTarget?: BodyTarget;
|
|
1178
|
+
bodyKind?: BodyKind;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Turns pointer samples into the gestures they add up to: a press, the drag
|
|
1183
|
+
* it becomes past the threshold, the click it is when released in place, the
|
|
1184
|
+
* double click two of those make, a long-press, and multitouch.
|
|
1185
|
+
*
|
|
1186
|
+
* It decides with its sink's answers, so it is not a pure function of its
|
|
1187
|
+
* input: a long-press only spends its press when something handled it.
|
|
1188
|
+
*/
|
|
1189
|
+
|
|
1190
|
+
/** Where recognized events go. */
|
|
1191
|
+
interface RecognizerSink {
|
|
1192
|
+
dispatch(ev: InputEvent): 'handled' | 'unhandled';
|
|
1193
|
+
/** Whether a multitouch gesture of `fingers` fingers is running, to be pumped. */
|
|
1194
|
+
multiTouchInFlight(fingers: number): boolean;
|
|
1195
|
+
}
|
|
1196
|
+
/** What a host hears about beyond the events themselves, to drive press feedback. */
|
|
1197
|
+
interface RecognizerObserver extends ClickObserver {
|
|
1198
|
+
/** The press being held toward a long-press, or `null` once it fires or is abandoned. */
|
|
1199
|
+
holdChanged?(pending: HeldPress | null): void;
|
|
1200
|
+
/** A long-press something handled, as itself or as its contextmenu fallback. */
|
|
1201
|
+
longPress?(pointerType: 'touch' | 'pen'): void;
|
|
1202
|
+
}
|
|
1203
|
+
/** What {@link createPointerRecognizer} needs from its host. */
|
|
1204
|
+
interface PointerRecognizerOptions {
|
|
1205
|
+
sink: RecognizerSink;
|
|
1206
|
+
clock: RecognizerClock;
|
|
1207
|
+
/**
|
|
1208
|
+
* Client point to world point; the identity by default. Asked as each event
|
|
1209
|
+
* is built rather than once per sample: an event dispatched earlier in the
|
|
1210
|
+
* same sample, a pinch step say, may have moved the view.
|
|
1211
|
+
*/
|
|
1212
|
+
toWorld?: (clientX: number, clientY: number) => Point;
|
|
1213
|
+
/** Client point to the space a multitouch centroid is reported in. The identity by default. */
|
|
1214
|
+
toSurface?: (client: Point) => Point;
|
|
1215
|
+
/** Long-press hold time in ms, read at each press. Default `LONG_PRESS_MS`. */
|
|
1216
|
+
longPressMs?: () => number | undefined;
|
|
1217
|
+
clicks?: ClickMemory;
|
|
1218
|
+
observer?: RecognizerObserver;
|
|
1219
|
+
}
|
|
1220
|
+
/** A recognizer for one surface. Its host feeds it every pointer sample the surface receives. */
|
|
1221
|
+
interface PointerRecognizer {
|
|
1222
|
+
press(sample: PressSample): void;
|
|
1223
|
+
/** A move of any pointer, held or not. */
|
|
1224
|
+
move(sample: PointerSample): void;
|
|
1225
|
+
release(sample: PointerSample): ReleaseOutcome;
|
|
1226
|
+
/** A held pointer ended without a release. */
|
|
1227
|
+
cancel(pointerId: number): void;
|
|
1228
|
+
/**
|
|
1229
|
+
* Whether a touch or pen that armed a long-press is still held. One still
|
|
1230
|
+
* pending fires here, ahead of its timer: a platform that reports the hold
|
|
1231
|
+
* itself is reporting the same one.
|
|
1232
|
+
*/
|
|
1233
|
+
takeHold(): boolean;
|
|
1234
|
+
heldCount(): number;
|
|
1235
|
+
/** A held pointer's latest client position. */
|
|
1236
|
+
positionOf(pointerId: number): Point | undefined;
|
|
1237
|
+
/** Forget every held pointer and stop timing, dispatching nothing. */
|
|
1238
|
+
reset(): void;
|
|
1239
|
+
}
|
|
1240
|
+
/** Build a recognizer holding no pointers. It touches no DOM: time and timers come from `opts.clock`. */
|
|
1241
|
+
declare function createPointerRecognizer(opts: PointerRecognizerOptions): PointerRecognizer;
|
|
1242
|
+
|
|
1045
1243
|
/** Elements that handle their own press and keys. */
|
|
1046
1244
|
declare const CONTROL_SELECTOR: string;
|
|
1047
1245
|
/**
|
|
@@ -1052,4 +1250,22 @@ declare const CONTROL_SELECTOR: string;
|
|
|
1052
1250
|
*/
|
|
1053
1251
|
declare function isInControlWithin(target: EventTarget | null, host: Element): boolean;
|
|
1054
1252
|
|
|
1055
|
-
|
|
1253
|
+
/** Returns true when the target takes typed text: an input, a textarea, or a contenteditable element. */
|
|
1254
|
+
declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
1255
|
+
/**
|
|
1256
|
+
* True when `e` is Space or Enter on a focused control those keys activate — a
|
|
1257
|
+
* button, a link, a checkbox. The key is that control's, so a page-level
|
|
1258
|
+
* listener that claims it cancels the control's own activation.
|
|
1259
|
+
*/
|
|
1260
|
+
declare function activatesFocusedControl(e: KeyboardEvent): boolean;
|
|
1261
|
+
/**
|
|
1262
|
+
* True when the key `e` reports belongs to the control it was typed on: Space
|
|
1263
|
+
* or Enter on anything those activate, and a `<select>`'s type-ahead
|
|
1264
|
+
* characters and navigation keys. A select claims nothing else, so Delete,
|
|
1265
|
+
* Escape, and every modifier chord typed on one are still a page-level
|
|
1266
|
+
* listener's to take. A text field is {@link isEditableTarget}'s question,
|
|
1267
|
+
* since it owns every key.
|
|
1268
|
+
*/
|
|
1269
|
+
declare function focusedControlOwnsKey(e: KeyboardEvent): boolean;
|
|
1270
|
+
|
|
1271
|
+
export { ACTIVE_SIGILS, type BodyClassification, type BodyKind, type BodyTarget, CONTROL_SELECTOR, type ChannelRef, type ClaimableGesture, type ClickEvent, type ClickMemory, type ClickObserver, type ClickSpec, type ContextMenuEvent, type ContextMenuSpec, DOUBLE_CLICK_MAX_MS, DOUBLE_CLICK_MAX_PX, DRAG_THRESHOLD_PX, type DescribeRouteOptions, type DoubleClickEvent, type DoubleClickSpec, type DragSpec, type DropEvent, type DropSpec, type EventModifiers, type FixedSeat, GESTURE_DESCRIPTORS, type GestureArgSpec, type GestureDescriptor, type GestureName, type GestureSpec, type HeldPress, type HoverEvent, type HoverSpec, type IngestItem, type InputEvent, type KeyEvent, type KeyHeldEvent, type KeyHeldSpec, type KeySpec, LONG_PRESS_MS, type LongPressEvent, type LongPressSpec, MOD_NAME_SET, type ModRequirement, type ModSpec, type ModifierCombo, type ModifierKey, type ModifiersEvent, type MultiTouchEvent, type MultiTouchGeometry, type MultiTouchSpec, type MultiTouchTapEvent, type MultiTouchTapSpec, type OptionalMod, type ParsedKeyRoute, type ParsedModifiers, type ParsedRoute, type PasteEvent, type PasteSpec, type PhaseAtom, type PhaseContext, type PhaseSpec, type PinchEvent, type PinchSpec, type Point, type PointerCancelEvent, type PointerDownEvent, type PointerDownSpec, type PointerIdentity, type PointerMoveEvent, type PointerRecognizer, type PointerRecognizerOptions, type PointerSample, type PointerSampleStylus, type PointerUpEvent, type PressFacts, type PressSample, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, RESERVED_SIGILS, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, type RecognizerClock, type RecognizerObserver, type RecognizerSink, type ReleaseFacts, type ReleaseOutcome, type RouteDescriptionPart, type RouteFieldName, type RoutePhase, type RouteTermLabel, TAP_THRESHOLD_PX, type TargetPredicate, type TargetSpec, type TargetSpecForm, UNKNOWN_MIME, VALID_MOD_NAMES, type WheelEvent, type WheelSpec, activatesFocusedControl, bodyPredicate, canonicalModifiers, clickEvents, collapseShiftPairs, createPointerRecognizer, describeRoute, describeRouteParts, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, getGestureDescriptor, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, keyRouteToSpec, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
|