@weasel-js/gestures 1.9.4 → 1.10.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/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' | 'multiTouch' | '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` /
@@ -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
- /** Phase of a gesture lifecycle. `initial` means the tool is idle
66
- * (scratch null); `engaged` means a gesture is in progress (scratch
67
- * populated). The route-grammar's `[phase]` slot draws from this set. */
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
- * Shorthand: a bare phaseValue (no `:`) implies channel `&` ("this tool's
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`. Useful
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. Routes that don't have a `+shift`
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 (e.g., Cmd+Z vs Cmd+Shift+Z are usually two distinct
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,32 +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 semantics (strict): an omitted modifier field means the
202
- * modifier MUST NOT be held — i.e., a bare `{ kind: 'key', key: 'Escape' }`
203
- * matches only unmodified Escape, NOT Cmd+Escape. A `true` means the
204
- * modifier MUST be held; `false` is the same as omitted (must be absent).
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 (mirrors `KeyBinding.mod`).
188
+ * elsewhere.
210
189
  *
211
190
  * Every modifier additionally accepts `'optional'`, meaning "held or not,
212
- * both acceptable" — the explicit opt-in for loose matching, used by
213
- * actions like nudge whose step size depends on shift but whose firing
214
- * does not.
191
+ * both acceptable" — used by actions like nudge whose step size depends on
192
+ * shift but whose firing does not.
215
193
  */
216
194
  type ModSpec = Partial<{
217
195
  alt: boolean | 'optional';
@@ -242,10 +220,8 @@ declare function hitPredicate(fn: (hit: unknown, bodyTarget?: string) => boolean
242
220
  declare function bodyPredicate(fn: (bodyTarget: string | undefined) => boolean): TargetPredicate & {
243
221
  readsAffordance: false;
244
222
  };
245
- /** Target selector for click and drag gesture specs. String forms are sugar
246
- * for the kit-owned object-kind registry (TODO.md Tier 1 follow-up); until
247
- * that ships, consumers can pass `{ kindOf: predicate }` to classify hits
248
- * themselves.
223
+ /** Target selector for click and drag gesture specs. Consumers can pass
224
+ * `{ kindOf: predicate }` to classify hits themselves.
249
225
  *
250
226
  * Adding a form here is a compile error in `parseTargetSpec` until
251
227
  * `TargetSpecForm` grows a matching variant — which is in turn a compile
@@ -254,19 +230,16 @@ type TargetSpec = 'empty' | 'selected-body' | 'unselected-body' | `kind:${string
254
230
  kindOf: TargetPredicate;
255
231
  };
256
232
  /** Phase qualifier on a gesture spec. Restricts when the spec matches based
257
- * on per-tool gesture-lifecycle state.
233
+ * on per-tool gesture-lifecycle state. When omitted, matches in any phase.
258
234
  *
259
- * Shorthand forms (most common case — gate on the binding's own tool):
235
+ * Shorthand forms gate on the binding's own tool:
260
236
  * `'engaged'` → `[{ channel: '&', phase: 'engaged' }]` // self mid-gesture
261
237
  * `'initial'` → `[{ channel: '&', phase: 'initial' }]` // self idle
262
238
  * `'*'` → `[{ channel: '&', phase: '*' }]` // either self phase
263
239
  *
264
240
  * Array form for explicit channel:phase atoms — e.g. `[{ channel: 'rect',
265
241
  * phase: 'engaged' }]` for "when the rect tool is mid-gesture, regardless of
266
- * which scope I'm in." See the v3 route grammar in
267
- * `@weasel-js/gestures/grammar` for the full lattice.
268
- *
269
- * When omitted, matches in any phase (preserves pre-phase behavior). */
242
+ * which scope I'm in." */
270
243
  type PhaseSpec = 'initial' | 'engaged' | '*' | readonly PhaseAtom[];
271
244
  /** Single-keystroke gesture (keydown). */
272
245
  interface KeySpec {
@@ -292,10 +265,8 @@ interface KeyHeldSpec {
292
265
  mods?: ModSpec;
293
266
  phase?: PhaseSpec;
294
267
  }
295
- /** Wheel-event gesture. `direction` filters by deltaY sign; default `'*'`.
296
- * - `'up'` → matches only deltaY < 0
297
- * - `'down'` → matches only deltaY > 0
298
- * - `'*'` → 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. */
299
270
  interface WheelSpec {
300
271
  kind: 'wheel';
301
272
  direction?: 'up' | 'down' | '*';
@@ -304,10 +275,9 @@ interface WheelSpec {
304
275
  phase?: PhaseSpec;
305
276
  }
306
277
  /** Pinch reported as a scale factor about a focal point (WebKit
307
- * `gesturechange`). `direction` filters by the sample's scale; default `'*'`.
308
- * - `'out'` → fingers spreading, scale > 1
309
- * - `'in'` → fingers closing, scale < 1
310
- * 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. */
311
281
  interface PinchSpec {
312
282
  kind: 'pinch';
313
283
  direction?: 'in' | 'out' | '*';
@@ -324,9 +294,7 @@ interface ClickSpec {
324
294
  phase?: PhaseSpec;
325
295
  }
326
296
  /** Double-click: two `click` events within 600ms and 8px of each other.
327
- * Synthesized by `useGestureDispatcher`; emitted AFTER the second
328
- * `click`. Bindings that want to handle a double-click should declare
329
- * this kind rather than chasing two `click` events. */
297
+ * Synthesized by `useGestureDispatcher`; emitted AFTER the second `click`. */
330
298
  interface DoubleClickSpec {
331
299
  kind: 'doubleClick';
332
300
  target?: TargetSpec;
@@ -351,13 +319,10 @@ interface DragSpec {
351
319
  }
352
320
  /**
353
321
  * Bare pointer press, matched at down time — before the dispatcher knows
354
- * whether the gesture will become a click or a drag.
355
- *
356
- * Reach for this only when the effect must be visible while the button is
357
- * still held. Selection is the motivating case: pressing an unselected node
358
- * highlights it immediately, and the drag that may follow then starts from an
359
- * already-correct selection. Anything that can wait for the release belongs on
360
- * 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.
361
326
  *
362
327
  * A matching binding does NOT own the gesture: the same press goes on to open
363
328
  * a drag or synthesize a click as usual. Bind an immediate action here, not an
@@ -369,17 +334,36 @@ interface PointerDownSpec {
369
334
  mods?: ModSpec;
370
335
  phase?: PhaseSpec;
371
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
+ * With `motion: 'leave'` it fires instead when the pointer passes out of what
342
+ * the binding targets: onto something its `target` does not match, or off the
343
+ * surface. With no `target` that is the whole surface. Its params are the
344
+ * move's that took the pointer out, or the last move's before it left the surface.
345
+ *
346
+ * A hover binding claims nothing. Every live binding that matches the move
347
+ * runs, not only the best one, and the move still reaches the hover-cursor
348
+ * pump. There is no press to own, so bind an immediate action, not an ongoing
349
+ * one. Its params carry `worldX` / `worldY`, `clientX` / `clientY`,
350
+ * `affordance`, `bodyTarget`, and `mods`.
351
+ */
352
+ interface HoverSpec {
353
+ kind: 'hover';
354
+ motion?: 'move' | 'leave';
355
+ target?: TargetSpec;
356
+ mods?: ModSpec;
357
+ phase?: PhaseSpec;
358
+ }
372
359
  /**
373
360
  * Press held past the long-press threshold without crossing the drag
374
361
  * threshold. Synthesized by `useGestureDispatcher` from the pointer stream.
375
- *
376
- * Fires for `touch` and `pen` pointers only. A mouse held still for half a
377
- * second is an ordinary slow click, and firing on it would produce a context
378
- * menu nobody asked for.
362
+ * Fires for `touch` and `pen` pointers only: a mouse held still is an
363
+ * ordinary slow click.
379
364
  *
380
365
  * When a long-press matches no binding, the dispatcher re-dispatches it as a
381
- * `contextmenu` event — so `contextMenu` bindings work under a finger with no
382
- * consumer changes, while `longPress` stays independently bindable.
366
+ * `contextmenu` event, so `contextMenu` bindings work under a finger.
383
367
  */
384
368
  interface LongPressSpec {
385
369
  kind: 'longPress';
@@ -421,10 +405,8 @@ interface PasteSpec {
421
405
  mods?: ModSpec;
422
406
  phase?: PhaseSpec;
423
407
  }
424
- /** The full union of supported gesture spec kinds. New invocation forms
425
- * (two-stage, modal-dialog) extend this union without touching
426
- * the `Action` type. */
427
- type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | PinchSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
408
+ /** The full union of supported gesture spec kinds. */
409
+ type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | PinchSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | HoverSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
428
410
 
429
411
  /**
430
412
  * Mini-grammar for the `arg` slot of `keyDown` / `keyUp` routes.
@@ -537,11 +519,7 @@ declare function modifierComboToParsed(key: ModifierCombo): ParsedModifiers;
537
519
  * `name=req` pairs joined by `&`. */
538
520
  declare function canonicalModifiers(mods: ParsedModifiers): string;
539
521
 
540
- /**
541
- * Keyboard/pointer modifier flags carried by every {@link InputEvent} arm.
542
- * Factored out so the four booleans are documented once instead of repeated
543
- * on all twelve event shapes.
544
- */
522
+ /** Keyboard/pointer modifier flags carried by every {@link InputEvent} arm. */
545
523
  interface EventModifiers {
546
524
  altKey: boolean;
547
525
  ctrlKey: boolean;
@@ -558,24 +536,15 @@ type BodyTarget = 'empty' | 'selected-body' | 'unselected-body';
558
536
  /**
559
537
  * Semantic kind of the node body under the point — `'text'`, `'rect'`, or an
560
538
  * app's own `'app:note'`. Companion to {@link BodyTarget}, which says only how
561
- * the body relates to the selection; this says *what it is*. Read by
562
- * `matchTarget` to resolve the `kind:<k>` / `kind:<k>:selected` TargetSpec
563
- * forms.
564
- *
565
- * Supplied by the same `classifyTarget` thunk that supplies `bodyTarget`, and
566
- * absent for the same reasons plus one more: the thunk hit a body whose kind
567
- * the scene can't name. An absent `bodyKind` makes every `kind:` form
568
- * no-match, never a wildcard.
539
+ * the body relates to the selection. Supplied by the same `classifyTarget`
540
+ * thunk; an absent `bodyKind` makes every `kind:` form no-match, never a
541
+ * wildcard.
569
542
  */
570
543
  type BodyKind = string;
571
544
  /**
572
545
  * What a scene hit-test found under a point — the shape a `classifyTarget`
573
546
  * thunk returns, and the source of both {@link BodyTarget} and
574
547
  * {@link BodyKind} on the events the matcher sees.
575
- *
576
- * One thunk returns both halves rather than two thunks returning one each:
577
- * the two answers come from the same hit-test, and splitting them would cost
578
- * a second pick on every pointer event.
579
548
  */
580
549
  interface BodyClassification {
581
550
  /** How the hit body relates to the current selection. */
@@ -650,14 +619,10 @@ interface PointerSampleStylus {
650
619
  tiltY?: number;
651
620
  }
652
621
  /**
653
- * Which physical pointer a sample came from.
654
- *
655
- * Carried by every pointer-kind event so the dispatcher can key in-flight
656
- * gesture handles per pointer (`pointer-<pointerId>`) instead of funneling
657
- * mouse, each touch, and the stylus into one slot. Optional because
658
- * synthesized events (tests, programmatic drags, the hover pump's
659
- * `resolveOnly` probe) have no originating `PointerEvent`; those all key to
660
- * the same default slot, which is the pre-`pointerId` behavior.
622
+ * Which physical pointer a sample came from, so the dispatcher can key
623
+ * in-flight gesture handles per pointer. Optional because synthesized events
624
+ * (tests, programmatic drags, the hover pump's `resolveOnly` probe) have no
625
+ * originating `PointerEvent`; those all key to the same default slot.
661
626
  */
662
627
  interface PointerIdentity {
663
628
  /** `PointerEvent.pointerId` from the originating DOM event. */
@@ -667,21 +632,17 @@ interface PointerIdentity {
667
632
  interface PointerDownEvent extends EventModifiers, PointerSampleStylus, PointerIdentity {
668
633
  kind: 'pointerdown';
669
634
  /**
670
- * Which of the two dispatches this press produces.
671
- *
672
- * A physical pointerdown is dispatched twice, for two different audiences:
635
+ * Which of the two dispatches this press produces. A physical pointerdown
636
+ * is dispatched twice:
673
637
  *
674
638
  * - `'press'` — emitted immediately, at down time. Matches `pointerDown`
675
- * specs only. This is the dispatch that lets a binding act *before* the
676
- * gesture is classified (select applies the selection here, so
677
- * press-and-hold highlights without waiting for a release).
639
+ * specs only.
678
640
  * - omitted — the copy the dispatcher buffers and flushes once movement
679
641
  * crosses the drag threshold. Matches `drag` specs only, and is what
680
642
  * opens a drag handle.
681
643
  *
682
644
  * The split is what keeps one press from firing both a `pointerDown` and a
683
- * `drag` binding. Synthetic probes (the hover pump's `resolveOnly`) leave it
684
- * omitted, so drag prediction is unaffected.
645
+ * `drag` binding.
685
646
  */
686
647
  stage?: 'press';
687
648
  target?: unknown;
@@ -728,8 +689,6 @@ interface ClickEvent extends EventModifiers {
728
689
  /**
729
690
  * World-space coordinates of the click, derived via the `clientToWorld`
730
691
  * thunk supplied to the dispatcher. Absent when the thunk isn't wired.
731
- * Forwarded into action params so click invokers can act on the click's
732
- * position without their own pointer-listener plumbing.
733
692
  */
734
693
  x?: number;
735
694
  y?: number;
@@ -743,23 +702,18 @@ interface ClickEvent extends EventModifiers {
743
702
  /**
744
703
  * World-space coordinates of the *press* that opened this click, as opposed
745
704
  * to `x`/`y`, which are the release. The two differ by up to the
746
- * drag threshold (`DRAG_THRESHOLD_PX`, in screen pixels).
747
- *
748
- * Actions that place geometry at the click want this one — a click's
749
- * location reads as where the user put the pointer down, and pinning to the
750
- * press point keeps a snapped coordinate stable against the small drift
751
- * between press and release. Absent when the `clientToWorld` thunk isn't
752
- * wired, same as `x`/`y`.
705
+ * drag threshold (`DRAG_THRESHOLD_PX`, in screen pixels). Actions that place
706
+ * geometry at the click want this one: pinning to the press point keeps a
707
+ * snapped coordinate stable against the drift between press and release.
708
+ * Absent when the `clientToWorld` thunk isn't wired, same as `x`/`y`.
753
709
  */
754
710
  pressX?: number;
755
711
  pressY?: number;
756
712
  /**
757
713
  * Affordance the *press* landed on, carried forward from the pointerdown
758
- * exactly like {@link ClickEvent.bodyTarget} is. A click's target is
759
- * whatever the press hit — chrome doesn't move out from under a pointer
760
- * between down and up — and without this a `kindOf` target on a click spec
761
- * would receive the raw DOM element while the same predicate on a drag spec
762
- * receives the affordance.
714
+ * like {@link ClickEvent.bodyTarget} is. Without this a `kindOf` target on
715
+ * a click spec would receive the raw DOM element while the same predicate
716
+ * on a drag spec receives the affordance.
763
717
  */
764
718
  affordance?: unknown;
765
719
  bodyTarget?: BodyTarget;
@@ -793,6 +747,28 @@ interface ContextMenuEvent extends EventModifiers {
793
747
  bodyTarget?: BodyTarget;
794
748
  bodyKind?: BodyKind;
795
749
  }
750
+ /** What a hover found under the pointer. */
751
+ interface HoverWhere {
752
+ affordance?: unknown;
753
+ bodyTarget?: BodyTarget;
754
+ bodyKind?: BodyKind;
755
+ }
756
+ /** A move of a pointer with no button pressed, or its leaving the surface. Matched by `hover` specs. */
757
+ interface HoverEvent extends EventModifiers {
758
+ kind: 'hover';
759
+ /** What the pointer was over at its last move, when it has made one since coming onto the surface. */
760
+ from?: HoverWhere;
761
+ /** The pointer has left the surface. Where it is and what it is over are its last move's. */
762
+ off?: boolean;
763
+ /** World-space position of the pointer. */
764
+ x: number;
765
+ y: number;
766
+ clientX?: number;
767
+ clientY?: number;
768
+ affordance?: unknown;
769
+ bodyTarget?: BodyTarget;
770
+ bodyKind?: BodyKind;
771
+ }
796
772
  /** A press held past the long-press threshold without moving. */
797
773
  interface LongPressEvent extends EventModifiers {
798
774
  kind: 'longpress';
@@ -818,9 +794,8 @@ interface MultiTouchEvent extends EventModifiers {
818
794
  * which is the space `zoomAt` anchors in. Not client coords, and not world:
819
795
  * calling `clientToWorld` on it subtracts the canvas origin a second time.
820
796
  *
821
- * Populated by `useGestureDispatcher` on the pointermove-pump of a running
822
- * multitouch handle (updated each frame). Absent on the initial
823
- * pointerdown-triggered multitouch event.
797
+ * Present on the event a finger landing produces and on each move of a
798
+ * running gesture; absent on the one that ends it.
824
799
  */
825
800
  centroid?: {
826
801
  x: number;
@@ -829,10 +804,15 @@ interface MultiTouchEvent extends EventModifiers {
829
804
  /**
830
805
  * Distance between the two primary pointers, in CSS pixels. A distance is
831
806
  * origin-free, so it reads the same in client and canvas-local space.
832
- * Populated on move-pump events alongside `centroid`. Absent on the initial
833
- * event.
807
+ * Present and absent alongside `centroid`.
834
808
  */
835
809
  spread?: number;
810
+ /**
811
+ * Direction from the first of the two primary pointers to the second, in
812
+ * degrees in (-180, 180], growing clockwise on screen. Present and absent
813
+ * alongside `centroid`.
814
+ */
815
+ angle?: number;
836
816
  /**
837
817
  * Set on the event that ends the running multitouch gesture, whatever its
838
818
  * finger count: `'commit'` when a finger lifts, `'cancel'` when one of its
@@ -876,80 +856,29 @@ interface PasteEvent extends EventModifiers {
876
856
  items: readonly IngestItem[];
877
857
  }
878
858
  /**
879
- * Normalized input-event shape consumed by the pure matcher. Built by the
880
- * React seam (`useGestureDispatcher`) from DOM events. Pump-only events
859
+ * Normalized input-event shape consumed by the pure matcher. Pump-only events
881
860
  * ({@link PointerMoveEvent}, {@link PointerUpEvent}, {@link PointerCancelEvent})
882
861
  * ride in the same union so the dispatcher's `handleInput` signature stays
883
862
  * uniform; the matcher itself never matches them.
884
863
  *
885
864
  * The `affordance?` field on {@link PointerDownEvent} is widened to `unknown`
886
865
  * in this package — the kit narrows it back to `AffordanceHit` at consumption.
887
- * This keeps `@weasel-js/gestures` free of any kit-affordance type.
888
866
  */
889
- type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | PasteEvent;
867
+ type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | HoverEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | PasteEvent;
890
868
 
891
869
  /**
892
- * Pure gesture matcher — no React, no state, no DOM.
893
- *
894
- * The kit's dispatcher orchestrator wraps these primitives with its
895
- * actions-layer concerns (BindingScope, ScopedBinding, MatchResult, matchBest).
896
- *
897
- * ## key-held phase decision
898
- * `matchSpec` returns `true` only for `phase: 'down'`, not `phase: 'up'`.
899
- * The dispatcher tracks the held key independently and handles the `up` phase
900
- * itself; the matcher never sees an "un-match" it needs to distinguish.
901
- * This keeps the matcher's output a simple boolean and avoids a three-valued
902
- * return type.
903
- *
904
- * ## TargetSpec string forms
905
- * Every form `TargetSpec` declares is resolved here, each from a different
906
- * field the dispatcher packs onto the event:
907
- *
908
- * | Form | Read from | Supplied by |
909
- * | --- | --- | --- |
910
- * | `'empty'` / `'selected-body'` / `'unselected-body'` | `bodyTarget` | the `classifyTarget` thunk |
911
- * | `kind:<k>` / `kind:<k>:selected` | `bodyKind` + `bodyTarget` | the `classifyTarget` thunk's node-kind resolver |
912
- * | `affordance:<k>` | the raw target's `.kind` | the `affordanceAt` thunk |
913
- * | `{ kindOf }` | the raw target + `bodyTarget` | both |
914
- *
915
- * A form whose source field is absent does not match — an unwired
916
- * `classifyTarget` makes every `bodyTarget`/`bodyKind` form `false` rather
917
- * than a silent wildcard. That is deliberate: a binding that can't be
918
- * evaluated should stay out of the way of one that can.
870
+ * The stylus fields `src` carries, as a spread-able partial: a sample that
871
+ * carries none of them adds no `undefined`-valued keys to what it is spread into.
919
872
  */
873
+ declare function stylusOf(src: PointerSampleStylus): PointerSampleStylus;
874
+
875
+ /** Target matching: a `TargetSpec` against what an event classified under its point. */
920
876
 
921
- /** The modifier-state fields `matchModifiers` reads. Structural, so a DOM
922
- * `KeyboardEvent` or `PointerEvent` satisfies it directly. */
923
- type ModifiersEvent = {
924
- altKey: boolean;
925
- ctrlKey: boolean;
926
- metaKey: boolean;
927
- shiftKey: boolean;
928
- };
929
- /**
930
- * Strict modifier match.
931
- *
932
- * - Omitted modifier MUST NOT be held.
933
- * - `true` MUST be held.
934
- * - `false` MUST NOT be held (same as omitted; both forms accepted for clarity).
935
- * - `'optional'` accepts either held or unheld. Supported on every modifier
936
- * (`alt`, `ctrl`, `meta`, `mod`, `shift`).
937
- * - `mod` is platform-aware: matches `metaKey` on mac, `ctrlKey` elsewhere.
938
- * When `mod` is set, the corresponding `meta`/`ctrl` field is implied AND
939
- * the *other* platform key is forbidden. Callers should not combine `mod`
940
- * with `meta`/`ctrl`.
941
- */
942
- declare function matchModifiers(e: ModifiersEvent, mods: ModSpec | undefined, isMac: boolean): boolean;
943
- /** Case-insensitive key match; supports string or string[] (any-of). */
944
- declare function matchKey(eventKey: string, specKey: string | string[]): boolean;
945
877
  /**
946
878
  * A {@link TargetSpec} resolved to its form, with the parts each form encodes
947
- * in its string pulled out.
948
- *
949
- * This is the one enumeration of the target forms. Three sites act on it and
950
- * each does something different — `matchTarget` below, and `targetRank` /
951
- * `targetConsultsAffordance` in `@weasel-js/core`'s dispatcher matcher — so
952
- * they switch on `form` exhaustively rather than re-deriving the prefixes.
879
+ * in its string pulled out. This is the one enumeration of the target forms:
880
+ * sites that act on it switch on `form` exhaustively rather than re-deriving
881
+ * the prefixes.
953
882
  */
954
883
  type TargetSpecForm = {
955
884
  form: 'body';
@@ -976,16 +905,13 @@ declare function parseTargetSpec(spec: TargetSpec): TargetSpecForm | null;
976
905
  /**
977
906
  * Match a target value + event against a TargetSpec.
978
907
  *
979
- * - `{ kindOf: predicate }` — calls the predicate with the raw target value
980
- * (affordance hit on pointerdown; event.target otherwise) AND the
981
- * `bodyTarget` string. Predicates that only need the target can ignore
982
- * the second arg.
908
+ * - `{ kindOf: predicate }` — calls the predicate with the affordance hit and
909
+ * the `bodyTarget` string.
983
910
  * - `'empty'`, `'selected-body'`, `'unselected-body'` — compared against
984
- * `bodyTarget` on the event (populated by `useGestureDispatcher` when a
985
- * `classifyTarget` thunk is supplied).
911
+ * `bodyTarget` on the event.
986
912
  * - `kind:<k>` — compared against `bodyKind`, the *semantic* kind of the node
987
- * body under the point (`'text'`, `'rect'`, an app's own `'app:note'`), as
988
- * resolved by `classifyTarget`. Selection-agnostic.
913
+ * body under the point (`'text'`, `'rect'`, an app's own `'app:note'`).
914
+ * Selection-agnostic.
989
915
  * - `kind:<k>:selected` — the same, and additionally requires the body to be
990
916
  * in the current selection. Only the final `:selected` is a suffix, so a
991
917
  * kind may itself contain colons.
@@ -1001,16 +927,35 @@ declare function parseTargetSpec(spec: TargetSpec): TargetSpecForm | null;
1001
927
  * silent wildcard.
1002
928
  */
1003
929
  declare function matchTarget(target: unknown, specTarget: TargetSpec | undefined, bodyTarget?: string, bodyKind?: string): boolean;
1004
- /** Per-tool gesture-lifecycle state at match time.
930
+
931
+ /** Pure gesture matcher — no React, no state, no DOM. */
932
+
933
+ /** The modifier-state fields `matchModifiers` reads. Structural, so a DOM
934
+ * `KeyboardEvent` or `PointerEvent` satisfies it directly. */
935
+ type ModifiersEvent = {
936
+ altKey: boolean;
937
+ ctrlKey: boolean;
938
+ metaKey: boolean;
939
+ shiftKey: boolean;
940
+ };
941
+ /**
942
+ * Strict modifier match.
1005
943
  *
1006
- * - `selfChannel` is the tool id `'&'` resolves to — i.e., the tool that
1007
- * owns the binding being evaluated. `null` for bindings without an
1008
- * owning tool (e.g. ambient actions registered without scope ties); in
1009
- * that case any `phase: '&'` atom can't match.
1010
- * - `engagedChannels` is the set of tool ids that currently have an
1011
- * in-flight handle. Derived by the dispatcher from its `inFlight()`
1012
- * map keyed by tool id at match time.
944
+ * - Omitted or `false`: MUST NOT be held.
945
+ * - `true`: MUST be held.
946
+ * - `'optional'`: accepts either held or unheld.
947
+ * - `mod` is platform-aware: matches `metaKey` on mac, `ctrlKey` elsewhere,
948
+ * and the *other* platform key is forbidden. Callers should not combine
949
+ * `mod` with `meta`/`ctrl`.
1013
950
  */
951
+ declare function matchModifiers(e: ModifiersEvent, mods: ModSpec | undefined, isMac: boolean): boolean;
952
+ /** Case-insensitive key match; supports string or string[] (any-of). */
953
+ declare function matchKey(eventKey: string, specKey: string | string[]): boolean;
954
+ /** Per-tool gesture-lifecycle state at match time. `selfChannel` is the tool
955
+ * id `'&'` resolves to — the tool that owns the binding being evaluated — or
956
+ * `null` for a binding without an owning tool, in which case no `'&'` atom
957
+ * can match. `engagedChannels` is the set of tool ids that currently have an
958
+ * in-flight handle. */
1014
959
  interface PhaseContext {
1015
960
  selfChannel: string | null;
1016
961
  engagedChannels: ReadonlySet<string>;
@@ -1020,14 +965,6 @@ interface PhaseContext {
1020
965
  * constraint). Union semantics: a phase atom list matches when ANY
1021
966
  * atom matches. */
1022
967
  declare function matchPhase(spec: PhaseSpec | undefined, ctx: PhaseContext): boolean;
1023
- /** MIME-glob match: `'image/*'` prefix-matches the major type; anything
1024
- * else is an exact (case-insensitive) match; bare `'*'` or `'*\/*'` matches all. */
1025
- declare function mimeMatchesGlob(mime: string, glob: string): boolean;
1026
- /** True if ANY item's MIME matches ANY of the `types` globs.
1027
- * `types: []` (empty) ≡ omitted = match any. */
1028
- declare function matchIngestTypes(items: readonly {
1029
- mime: string;
1030
- }[], types: string[] | undefined): boolean;
1031
968
  /**
1032
969
  * Match a single GestureSpec against an InputEvent.
1033
970
  *
@@ -1036,6 +973,20 @@ declare function matchIngestTypes(items: readonly {
1036
973
  */
1037
974
  declare function matchSpec(e: InputEvent, spec: GestureSpec, isMac: boolean, phaseCtx?: PhaseContext): boolean;
1038
975
 
976
+ /** Which external content a `drop` or `paste` spec's MIME globs let through. */
977
+ /** MIME-glob match: `'image/*'` prefix-matches the major type; anything
978
+ * else is an exact (case-insensitive) match; bare `'*'` or `'*\/*'` matches all. */
979
+ declare function mimeMatchesGlob(mime: string, glob: string): boolean;
980
+ /** The MIME of an item whose type is not known yet: a file hovering in an OS
981
+ * drag, which some browsers do not name until it is dropped. */
982
+ declare const UNKNOWN_MIME = "*/*";
983
+ /** True if ANY item's MIME matches ANY of the `types` globs.
984
+ * `types: []` (empty) ≡ omitted = match any. An item of
985
+ * {@link UNKNOWN_MIME} passes every glob. */
986
+ declare function matchIngestTypes(items: readonly {
987
+ mime: string;
988
+ }[], types: string[] | undefined): boolean;
989
+
1039
990
  /**
1040
991
  * Whether this is an Apple platform, where `mod` means the Command key and
1041
992
  * not Ctrl. iPhone and iPad count: with a keyboard attached their shortcuts
@@ -1047,6 +998,28 @@ declare function matchSpec(e: InputEvent, spec: GestureSpec, isMac: boolean, pha
1047
998
  */
1048
999
  declare function isApplePlatform(): boolean;
1049
1000
 
1001
+ /**
1002
+ * The nearest element `from` sits inside that can host a portalled overlay,
1003
+ * or `null` when there is none and the body is the only answer.
1004
+ */
1005
+ declare function nearestPortalHost(from: Element | null | undefined): HTMLElement | null;
1006
+ /**
1007
+ * Where a `position: fixed` element following the pointer mounts: `host`, and
1008
+ * the client-space point its `left: 0; top: 0` lands on.
1009
+ */
1010
+ interface FixedSeat {
1011
+ host: HTMLElement;
1012
+ left: number;
1013
+ top: number;
1014
+ }
1015
+ /**
1016
+ * The seat for something drawn at a client-space point on behalf of `from`, a
1017
+ * drag ghost above all: the nearest portal host, so it resolves the theme
1018
+ * `from` is drawn in, or the body. Subtract `left` and `top` from the client
1019
+ * point to place it.
1020
+ */
1021
+ declare function fixedSeat(from: Element | null | undefined): FixedSeat;
1022
+
1050
1023
  /** CSS-style specificity tuple for a GestureSpec. Higher tuple wins under
1051
1024
  * lexicographic compare. Dimensions, in order of precedence:
1052
1025
  *
@@ -1058,14 +1031,14 @@ declare function isApplePlatform(): boolean;
1058
1031
  * non-empty `types` MIME filter, else 1.
1059
1032
  *
1060
1033
  * Identical tuples fall back to registration order in the matcher's
1061
- * stable sort, preserving the pre-specificity tiebreaker. */
1034
+ * stable sort. */
1062
1035
  declare function specificity(spec: GestureSpec): readonly [number, number, number, number];
1063
1036
 
1064
1037
  /**
1065
1038
  * How far, in CSS pixels, a press must travel before it is a drag rather than
1066
- * a click. `drag` specs match only past it: routing's `useGestureDispatcher`
1067
- * holds a pointerdown back until the pointer crosses it, so a press-and-release
1068
- * that stays under it never opens a drag.
1039
+ * a click. `drag` specs match only past it: the pointer recognizer holds a
1040
+ * pointerdown back until the pointer crosses it, so a press-and-release that
1041
+ * stays under it never opens a drag.
1069
1042
  */
1070
1043
  declare const DRAG_THRESHOLD_PX = 4;
1071
1044
  /** Whether a pointer that went down at `from` has travelled far enough, by
@@ -1078,6 +1051,214 @@ declare function pastDragThreshold(from: {
1078
1051
  clientY: number;
1079
1052
  }, threshold?: number): boolean;
1080
1053
 
1054
+ /**
1055
+ * The times and distances that tell one pointer gesture from another. The
1056
+ * drag threshold sits with its helper in `ui/dragThreshold.ts`.
1057
+ */
1058
+ /** How long a touch or pen press is held before it fires `longPress`, in ms. */
1059
+ declare const LONG_PRESS_MS = 500;
1060
+ /** The longest gap between two clicks that still makes a double click, in ms:
1061
+ * the upper end of OS double-click settings. */
1062
+ declare const DOUBLE_CLICK_MAX_MS = 600;
1063
+ /** How far apart, in CSS pixels, two clicks may land and still make a double
1064
+ * click: the drift of a real mouse or trackpad between them. */
1065
+ declare const DOUBLE_CLICK_MAX_PX = 8;
1066
+ /** How far, in CSS pixels, a finger or the centroid of a multitouch gesture
1067
+ * may travel before the gesture stops being a tap. */
1068
+ declare const TAP_THRESHOLD_PX = 8;
1069
+
1070
+ /** A position in whatever space the recognizer's `positionOf` reports. */
1071
+ interface Point {
1072
+ x: number;
1073
+ y: number;
1074
+ }
1075
+ /** Where two or more pointers sit: what `multiTouchGeometry` reads from their positions. */
1076
+ interface MultiTouchGeometry {
1077
+ centroid: Point;
1078
+ /** Distance between the first two pointers. */
1079
+ spread: number;
1080
+ /** Direction from the first pointer to the second, in degrees in (-180, 180],
1081
+ * growing clockwise on screen. */
1082
+ angle: number;
1083
+ }
1084
+ /** Every pointer's centroid, and the first two's spread and angle. All zero with fewer than 2 pointers. */
1085
+ declare function multiTouchGeometry(positions: Iterable<Point>): MultiTouchGeometry;
1086
+
1087
+ /**
1088
+ * What the recognizer remembers of a press, and the events a release or a
1089
+ * hold on it synthesizes.
1090
+ */
1091
+
1092
+ /** Press-time state replayed onto a synthesized click, long-press or
1093
+ * contextmenu, whose matchers resolve targets against it. */
1094
+ interface PressFacts extends EventModifiers {
1095
+ target: unknown;
1096
+ clientX: number;
1097
+ clientY: number;
1098
+ /** World-space press point, forwarded onto the synthesized click as
1099
+ * `pressX`/`pressY` — see `ClickEvent`. */
1100
+ worldX: number;
1101
+ worldY: number;
1102
+ affordance?: unknown;
1103
+ bodyTarget?: BodyTarget;
1104
+ bodyKind?: BodyKind;
1105
+ }
1106
+ /** Where and how a press was released, for the click it turned out to be. */
1107
+ interface ReleaseFacts extends EventModifiers {
1108
+ target: unknown;
1109
+ clientX: number;
1110
+ clientY: number;
1111
+ worldX: number;
1112
+ worldY: number;
1113
+ }
1114
+ /** A press released where it went down, as a click, and as the double click
1115
+ * it is when it follows another closely. Both happen at the release, with the
1116
+ * release's modifiers, on what the press landed on. */
1117
+ declare function clickEvents(down: PressFacts, up: ReleaseFacts): {
1118
+ click: InputEvent;
1119
+ doubleclick: InputEvent;
1120
+ };
1121
+ /** A held press as a long-press, and as the contextmenu it falls back to when
1122
+ * the long-press matches nothing. The fallback is what makes existing
1123
+ * `contextMenu` bindings reachable by touch with no consumer change. */
1124
+ declare function longPressEvents(down: PressFacts): {
1125
+ longpress: InputEvent;
1126
+ contextmenu: InputEvent;
1127
+ };
1128
+
1129
+ /** A press released in place: its click, and the double click a second one makes. */
1130
+
1131
+ /**
1132
+ * The click a double click would follow. Held by the host rather than the
1133
+ * recognizer when it has to outlive one: a host that rebuilds its recognizer
1134
+ * between two clicks would otherwise drop the double click.
1135
+ */
1136
+ interface ClickMemory {
1137
+ last: {
1138
+ t: number;
1139
+ clientX: number;
1140
+ clientY: number;
1141
+ } | null;
1142
+ }
1143
+ /** Told of clicks without competing for them, as a binding would. */
1144
+ interface ClickObserver {
1145
+ /** After the click was dispatched, so whatever its binding changed is readable. */
1146
+ click?(world: Point): void;
1147
+ /** Before the double click is dispatched, whichever binding then handles it. */
1148
+ doubleClick?(world: Point): void;
1149
+ }
1150
+ /** What a release composed. `doubleClick` is absent unless one was dispatched. */
1151
+ interface ReleaseOutcome {
1152
+ doubleClick?: 'handled' | 'unhandled';
1153
+ }
1154
+
1155
+ /** The one press being timed toward a long-press. */
1156
+
1157
+ /** A clock and a timer, so a host or a test supplies its own. */
1158
+ interface RecognizerClock {
1159
+ now(): number;
1160
+ setTimer(fn: () => void, ms: number): unknown;
1161
+ clearTimer(timer: unknown): void;
1162
+ }
1163
+ /** A press held toward a long-press. */
1164
+ interface HeldPress {
1165
+ pointerId: number;
1166
+ /** A mouse never arms one. */
1167
+ pointerType: 'touch' | 'pen';
1168
+ press: PressFacts;
1169
+ /** `RecognizerClock.now()` when the hold started. */
1170
+ startedAt: number;
1171
+ /** How long the hold must last before the long-press fires, in ms. */
1172
+ duration: number;
1173
+ }
1174
+
1175
+ /**
1176
+ * What a host feeds the pointer recognizer, one per pointer event, and the
1177
+ * pump events each becomes.
1178
+ */
1179
+
1180
+ /** One pointer event, in client space. */
1181
+ interface PointerSample extends EventModifiers, PointerSampleStylus {
1182
+ pointerId: number;
1183
+ clientX: number;
1184
+ clientY: number;
1185
+ /** What the originating event was aimed at. */
1186
+ target?: unknown;
1187
+ }
1188
+ /** A pointer going down, with what the host found under it. */
1189
+ interface PressSample extends PointerSample {
1190
+ /** The world point the host hit-tested at. */
1191
+ x: number;
1192
+ y: number;
1193
+ /** `PointerEvent.pointerType`. Only `'touch'` and `'pen'` arm a long-press. */
1194
+ pointerType: string;
1195
+ affordance?: unknown;
1196
+ bodyTarget?: BodyTarget;
1197
+ bodyKind?: BodyKind;
1198
+ }
1199
+
1200
+ /**
1201
+ * Turns pointer samples into the gestures they add up to: a press, the drag
1202
+ * it becomes past the threshold, the click it is when released in place, the
1203
+ * double click two of those make, a long-press, and multitouch.
1204
+ *
1205
+ * It decides with its sink's answers, so it is not a pure function of its
1206
+ * input: a long-press only spends its press when something handled it.
1207
+ */
1208
+
1209
+ /** Where recognized events go. */
1210
+ interface RecognizerSink {
1211
+ dispatch(ev: InputEvent): 'handled' | 'unhandled';
1212
+ /** Whether a multitouch gesture of `fingers` fingers is running, to be pumped. */
1213
+ multiTouchInFlight(fingers: number): boolean;
1214
+ }
1215
+ /** What a host hears about beyond the events themselves, to drive press feedback. */
1216
+ interface RecognizerObserver extends ClickObserver {
1217
+ /** The press being held toward a long-press, or `null` once it fires or is abandoned. */
1218
+ holdChanged?(pending: HeldPress | null): void;
1219
+ /** A long-press something handled, as itself or as its contextmenu fallback. */
1220
+ longPress?(pointerType: 'touch' | 'pen'): void;
1221
+ }
1222
+ /** What {@link createPointerRecognizer} needs from its host. */
1223
+ interface PointerRecognizerOptions {
1224
+ sink: RecognizerSink;
1225
+ clock: RecognizerClock;
1226
+ /**
1227
+ * Client point to world point; the identity by default. Asked as each event
1228
+ * is built rather than once per sample: an event dispatched earlier in the
1229
+ * same sample, a pinch step say, may have moved the view.
1230
+ */
1231
+ toWorld?: (clientX: number, clientY: number) => Point;
1232
+ /** Client point to the space a multitouch centroid is reported in. The identity by default. */
1233
+ toSurface?: (client: Point) => Point;
1234
+ /** Long-press hold time in ms, read at each press. Default `LONG_PRESS_MS`. */
1235
+ longPressMs?: () => number | undefined;
1236
+ clicks?: ClickMemory;
1237
+ observer?: RecognizerObserver;
1238
+ }
1239
+ /** A recognizer for one surface. Its host feeds it every pointer sample the surface receives. */
1240
+ interface PointerRecognizer {
1241
+ press(sample: PressSample): void;
1242
+ /** A move of any pointer, held or not. */
1243
+ move(sample: PointerSample): void;
1244
+ release(sample: PointerSample): ReleaseOutcome;
1245
+ /** A held pointer ended without a release. */
1246
+ cancel(pointerId: number): void;
1247
+ /**
1248
+ * Whether a touch or pen that armed a long-press is still held. One still
1249
+ * pending fires here, ahead of its timer: a platform that reports the hold
1250
+ * itself is reporting the same one.
1251
+ */
1252
+ takeHold(): boolean;
1253
+ heldCount(): number;
1254
+ /** A held pointer's latest client position. */
1255
+ positionOf(pointerId: number): Point | undefined;
1256
+ /** Forget every held pointer and stop timing, dispatching nothing. */
1257
+ reset(): void;
1258
+ }
1259
+ /** Build a recognizer holding no pointers. It touches no DOM: time and timers come from `opts.clock`. */
1260
+ declare function createPointerRecognizer(opts: PointerRecognizerOptions): PointerRecognizer;
1261
+
1081
1262
  /** Elements that handle their own press and keys. */
1082
1263
  declare const CONTROL_SELECTOR: string;
1083
1264
  /**
@@ -1088,4 +1269,38 @@ declare const CONTROL_SELECTOR: string;
1088
1269
  */
1089
1270
  declare function isInControlWithin(target: EventTarget | null, host: Element): boolean;
1090
1271
 
1091
- export { ACTIVE_SIGILS, type BodyClassification, type BodyKind, type BodyTarget, CONTROL_SELECTOR, type ChannelRef, type ClaimableGesture, type ClickEvent, type ClickSpec, type ContextMenuEvent, type ContextMenuSpec, DRAG_THRESHOLD_PX, type DescribeRouteOptions, type DoubleClickEvent, type DoubleClickSpec, type DragSpec, type DropEvent, type DropSpec, type EventModifiers, GESTURE_DESCRIPTORS, type GestureArgSpec, type GestureDescriptor, type GestureName, type GestureSpec, type IngestItem, type InputEvent, type KeyEvent, type KeyHeldEvent, type KeyHeldSpec, type KeySpec, type LongPressEvent, type LongPressSpec, MOD_NAME_SET, type ModRequirement, type ModSpec, type ModifierCombo, type ModifierKey, type ModifiersEvent, type MultiTouchEvent, 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 PointerCancelEvent, type PointerDownEvent, type PointerDownSpec, type PointerIdentity, type PointerMoveEvent, type PointerSampleStylus, type PointerUpEvent, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, RESERVED_SIGILS, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, type RouteDescriptionPart, type RouteFieldName, type RoutePhase, type RouteTermLabel, type TargetPredicate, type TargetSpec, type TargetSpecForm, VALID_MOD_NAMES, type WheelEvent, type WheelSpec, bodyPredicate, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatKeyRoute, formatPhaseAtom, formatRoute, getGestureDescriptor, hitPredicate, isApplePlatform, isInControlWithin, isKnownGestureName, keyRouteToSpec, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity };
1272
+ /**
1273
+ * Which pointers are down on an element: pressed on it or inside it, with any
1274
+ * button, and not yet released. The one answer to "is this move a hover" for
1275
+ * everything listening on that element.
1276
+ */
1277
+ interface HeldPointers {
1278
+ count(): number;
1279
+ /** Stop reading. The element's listeners go with its last reader. */
1280
+ release(): void;
1281
+ }
1282
+ /**
1283
+ * Read the pointers held on `element`. Every reader of one element shares one
1284
+ * set of listeners, so they cannot disagree.
1285
+ */
1286
+ declare function heldPointers(element: Element): HeldPointers;
1287
+
1288
+ /** Returns true when the target takes typed text: an input, a textarea, or a contenteditable element. */
1289
+ declare function isEditableTarget(target: EventTarget | null): boolean;
1290
+ /**
1291
+ * True when `e` is Space or Enter on a focused control those keys activate — a
1292
+ * button, a link, a checkbox. The key is that control's, so a page-level
1293
+ * listener that claims it cancels the control's own activation.
1294
+ */
1295
+ declare function activatesFocusedControl(e: KeyboardEvent): boolean;
1296
+ /**
1297
+ * True when the key `e` reports belongs to the control it was typed on: Space
1298
+ * or Enter on anything those activate, and a `<select>`'s type-ahead
1299
+ * characters and navigation keys. A select claims nothing else, so Delete,
1300
+ * Escape, and every modifier chord typed on one are still a page-level
1301
+ * listener's to take. A text field is {@link isEditableTarget}'s question,
1302
+ * since it owns every key.
1303
+ */
1304
+ declare function focusedControlOwnsKey(e: KeyboardEvent): boolean;
1305
+
1306
+ 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 HeldPointers, type HeldPress, type HoverEvent, type HoverSpec, type HoverWhere, 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, heldPointers, 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 };