@weasel-js/gestures 1.10.0 → 1.11.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/README.md +106 -4
- package/dist/index.d.ts +253 -36
- package/dist/index.js +290 -82
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# @weasel-js/gestures
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The vocabulary weasel uses to say which user input fires what, and the pure
|
|
4
|
+
functions that decide whether an input matches. It has no React, no scene, and
|
|
5
|
+
no dependencies, so it can run in a test, a worker, or a host that is not
|
|
6
|
+
weasel at all.
|
|
4
7
|
|
|
5
8
|
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
6
9
|
scene-graph canvas kit for React. See the
|
|
7
|
-
[API reference](https://orochi235.github.io/weasel/
|
|
10
|
+
[API reference](https://orochi235.github.io/weasel/#pkg/gestures).
|
|
8
11
|
|
|
9
12
|
## Install
|
|
10
13
|
|
|
@@ -12,12 +15,111 @@ scene-graph canvas kit for React. See the
|
|
|
12
15
|
npm install @weasel-js/gestures
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
##
|
|
18
|
+
## Terms
|
|
19
|
+
|
|
20
|
+
These words have fixed meanings across weasel:
|
|
21
|
+
|
|
22
|
+
- A **gesture** is a form of input: a click, a drag, a wheel turn, a keystroke,
|
|
23
|
+
a pinch, a file dropped on the canvas.
|
|
24
|
+
- An **action** is a user-intent operation: move, delete, zoom in.
|
|
25
|
+
- An **interaction** is a gesture bound to an action: "shift-click on empty
|
|
26
|
+
canvas adds to the selection".
|
|
27
|
+
|
|
28
|
+
This package covers the gesture side only. It describes gestures, recognizes
|
|
29
|
+
them from raw pointer input, and matches them against descriptions. Actions,
|
|
30
|
+
and the dispatcher that runs the action a matched gesture is bound to, live in
|
|
31
|
+
other weasel packages.
|
|
32
|
+
|
|
33
|
+
## What is in it
|
|
34
|
+
|
|
35
|
+
### Describing a gesture
|
|
36
|
+
|
|
37
|
+
A `GestureSpec` is an object describing one gesture a binding listens for:
|
|
38
|
+
its `kind` (`'click'`, `'drag'`, `'key'`, `'wheel'`, and so on), and optionally
|
|
39
|
+
the modifier keys that must be held, what the pointer must be over, and the
|
|
40
|
+
phase the owning tool must be in.
|
|
41
|
+
|
|
42
|
+
A **route** is the same description as a string, short enough to write inline
|
|
43
|
+
and to show in a shortcut list: `[initial] click => empty +shift`.
|
|
44
|
+
|
|
45
|
+
`parseRoute` turns a route into a `ParsedRoute`, `formatRoute` writes the
|
|
46
|
+
canonical string back, `routeToSpec` converts a parsed route into the
|
|
47
|
+
`GestureSpec` the matcher takes, and `describeRoute` renders it as an English
|
|
48
|
+
sentence. `GESTURE_DESCRIPTORS` is the table of gesture names the grammar
|
|
49
|
+
accepts, with whether each takes a target and what argument it carries.
|
|
50
|
+
|
|
51
|
+
### Matching input against a description
|
|
52
|
+
|
|
53
|
+
An `InputEvent` is one normalized input: a union keyed by `kind`, each arm
|
|
54
|
+
carrying the modifier state and whatever that kind of input reports.
|
|
55
|
+
|
|
56
|
+
`matchSpec(event, spec, isMac)` returns whether an event satisfies a spec.
|
|
57
|
+
Its parts are exported too: `matchModifiers`, `matchKey`, `matchTarget`, and
|
|
58
|
+
`matchPhase`. When several specs match one event, `specificity(spec)` gives a
|
|
59
|
+
tuple to rank them by, so the narrower description wins.
|
|
60
|
+
|
|
61
|
+
### Recognizing gestures from pointer input
|
|
62
|
+
|
|
63
|
+
`createPointerRecognizer` turns a stream of pointer presses, moves, and
|
|
64
|
+
releases into `InputEvent`s. It decides when a press has moved far enough to be
|
|
65
|
+
a drag, when a release in place is a click, when two clicks make a double
|
|
66
|
+
click, when a held touch is a long-press, and when several pointers form a
|
|
67
|
+
multitouch gesture. It touches no DOM: the host feeds it samples and supplies
|
|
68
|
+
the clock, and it hands each recognized event to a sink the host provides.
|
|
69
|
+
|
|
70
|
+
The thresholds it uses are exported as constants: `DRAG_THRESHOLD_PX`,
|
|
71
|
+
`LONG_PRESS_MS`, `DOUBLE_CLICK_MAX_MS`, `DOUBLE_CLICK_MAX_PX`, and
|
|
72
|
+
`TAP_THRESHOLD_PX`.
|
|
73
|
+
|
|
74
|
+
### How they fit together
|
|
75
|
+
|
|
76
|
+
| From | Through | To |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Pointer samples | `createPointerRecognizer` | `InputEvent` |
|
|
79
|
+
| Route string | `parseRoute`, then `routeToSpec` | `GestureSpec` |
|
|
80
|
+
| `InputEvent` and `GestureSpec` | `matchSpec` | `true` or `false` |
|
|
81
|
+
|
|
82
|
+
A host listens to the DOM, feeds pointer samples to a recognizer, and builds
|
|
83
|
+
key, wheel, and drop events itself. Each binding carries a `GestureSpec`,
|
|
84
|
+
written directly or parsed from a route. For every event, the host asks
|
|
85
|
+
`matchSpec` which bindings apply, ranks them with `specificity`, and runs the
|
|
86
|
+
winner's action.
|
|
87
|
+
|
|
88
|
+
The package also exports a few helpers a host needs while doing this:
|
|
89
|
+
`isApplePlatform` to decide what the `mod` modifier means, and
|
|
90
|
+
`isEditableTarget`, `focusedControlOwnsKey`, and `isInControlWithin` to leave
|
|
91
|
+
keys and presses alone when they belong to a text field or a button.
|
|
92
|
+
|
|
93
|
+
## Example
|
|
94
|
+
|
|
95
|
+
Parse a route, turn it into a spec, and test an event against it:
|
|
16
96
|
|
|
17
97
|
```ts
|
|
18
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
describeRoute, matchSpec, parseRoute, routeToSpec, type InputEvent,
|
|
100
|
+
} from '@weasel-js/gestures';
|
|
101
|
+
|
|
102
|
+
const route = parseRoute('[*:*] click => empty +shift');
|
|
103
|
+
const spec = routeToSpec(route);
|
|
104
|
+
// { kind: 'click', target: 'empty', mods: { shift: true } }
|
|
105
|
+
|
|
106
|
+
const shiftClick: InputEvent = {
|
|
107
|
+
kind: 'click',
|
|
108
|
+
bodyTarget: 'empty',
|
|
109
|
+
altKey: false, ctrlKey: false, metaKey: false, shiftKey: true,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
matchSpec(shiftClick, spec, false); // true
|
|
113
|
+
matchSpec({ ...shiftClick, shiftKey: false }, spec, false); // false: shift is required
|
|
114
|
+
|
|
115
|
+
describeRoute(parseRoute('[initial] drag +mod ?shift'));
|
|
116
|
+
// 'Fires when the user Mod-drags anywhere, while the tool is idle (Shift optional).'
|
|
19
117
|
```
|
|
20
118
|
|
|
119
|
+
Modifier matching is strict. A modifier the spec does not mention must not be
|
|
120
|
+
held, so a spec for plain `click` does not match a shift-click. Mark a modifier
|
|
121
|
+
`?shift` in a route, or `shift: 'optional'` in a spec, to accept it either way.
|
|
122
|
+
|
|
21
123
|
## License
|
|
22
124
|
|
|
23
125
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { MenuShortcut } from '@weasel-js/menu';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Declarative gesture taxonomy. Single source of truth for:
|
|
3
5
|
* - which gestures hit-test (have a `.target` slot in the route string)
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
* Reflection, matcher, and inspector UI all read this table. Adding a new
|
|
8
10
|
* gesture name in one place updates every consumer.
|
|
9
11
|
*/
|
|
10
|
-
type GestureName = 'click' | 'pointerDown' | 'hover' | 'dblTap' | 'drag' | 'wheel' | 'pinch' | 'keyDown' | 'keyUp' | 'keyHeld' | 'contextMenu' | 'longPress' | 'multiTouch' | 'multiTouchTap' | 'drop' | 'paste';
|
|
12
|
+
type GestureName = 'click' | 'pointerDown' | 'hover' | 'dblTap' | 'drag' | 'wheel' | 'pinch' | 'keyDown' | 'keyUp' | 'keyHeld' | 'contextMenu' | 'longPress' | 'multiTouch' | 'multiTouchTap' | 'drop' | 'carry' | 'paste';
|
|
11
13
|
/**
|
|
12
14
|
* Gesture kinds an affordance claim can bar, in the spec vocabulary bindings
|
|
13
15
|
* are written in. `'pointer'` is one token because `pointerDown` / `click` /
|
|
@@ -338,6 +340,11 @@ interface PointerDownSpec {
|
|
|
338
340
|
* The pointer moving over the surface with no button pressed. Fires on every
|
|
339
341
|
* such move; a held pointer's moves belong to its drag and fire nothing here.
|
|
340
342
|
*
|
|
343
|
+
* With `motion: 'leave'` it fires instead when the pointer passes out of what
|
|
344
|
+
* the binding targets: onto something its `target` does not match, or off the
|
|
345
|
+
* surface. With no `target` that is the whole surface. Its params are the
|
|
346
|
+
* move's that took the pointer out, or the last move's before it left the surface.
|
|
347
|
+
*
|
|
341
348
|
* A hover binding claims nothing. Every live binding that matches the move
|
|
342
349
|
* runs, not only the best one, and the move still reaches the hover-cursor
|
|
343
350
|
* pump. There is no press to own, so bind an immediate action, not an ongoing
|
|
@@ -346,6 +353,7 @@ interface PointerDownSpec {
|
|
|
346
353
|
*/
|
|
347
354
|
interface HoverSpec {
|
|
348
355
|
kind: 'hover';
|
|
356
|
+
motion?: 'move' | 'leave';
|
|
349
357
|
target?: TargetSpec;
|
|
350
358
|
mods?: ModSpec;
|
|
351
359
|
phase?: PhaseSpec;
|
|
@@ -391,6 +399,24 @@ interface DropSpec {
|
|
|
391
399
|
mods?: ModSpec;
|
|
392
400
|
phase?: PhaseSpec;
|
|
393
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* A press that began elsewhere on the page, carried over the canvas: a palette
|
|
404
|
+
* item dragged in, a row dragged out of a list. The in-app sibling of
|
|
405
|
+
* {@link DropSpec}, which is the OS's drag. A carry is begun with `startCarry`.
|
|
406
|
+
* `types` filters by the payload's `type`, as globs; omitted or empty matches
|
|
407
|
+
* any carry.
|
|
408
|
+
*
|
|
409
|
+
* An ongoing action bound here starts when the carry comes over the surface:
|
|
410
|
+
* its moves update it, the release commits it, and leaving the surface or
|
|
411
|
+
* canceling the press cancels it. An immediate action fires once, on the
|
|
412
|
+
* release.
|
|
413
|
+
*/
|
|
414
|
+
interface CarrySpec {
|
|
415
|
+
kind: 'carry';
|
|
416
|
+
types?: string[];
|
|
417
|
+
mods?: ModSpec;
|
|
418
|
+
phase?: PhaseSpec;
|
|
419
|
+
}
|
|
394
420
|
/** System-clipboard paste of external content. Same `types` semantics as
|
|
395
421
|
* {@link DropSpec} — omitted or empty = matches any paste. */
|
|
396
422
|
interface PasteSpec {
|
|
@@ -400,7 +426,7 @@ interface PasteSpec {
|
|
|
400
426
|
phase?: PhaseSpec;
|
|
401
427
|
}
|
|
402
428
|
/** 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;
|
|
429
|
+
type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | PinchSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | HoverSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | CarrySpec | PasteSpec;
|
|
404
430
|
|
|
405
431
|
/**
|
|
406
432
|
* Mini-grammar for the `arg` slot of `keyDown` / `keyUp` routes.
|
|
@@ -441,7 +467,7 @@ declare function keyRouteToSpec(r: ParsedKeyRoute): KeySpec;
|
|
|
441
467
|
* Wildcards become omitted fields: a `*` target, direction or MIME list, and a
|
|
442
468
|
* `[*:*]` phase slot. `[*]` is `[&:*]`, which an ambient binding never
|
|
443
469
|
* matches, so it stays a phase atom. A `keyDown` / `keyHeld` arg is read as a
|
|
444
|
-
* key route, so `keyDown(z?shift)` yields `mods.shift: 'optional'`; a drop or
|
|
470
|
+
* key route, so `keyDown(z?shift)` yields `mods.shift: 'optional'`; a drop, carry, or
|
|
445
471
|
* paste arg is a `|`-separated MIME list.
|
|
446
472
|
*
|
|
447
473
|
* Throws on a route no spec can express: `keyUp`, a key or finger-count
|
|
@@ -513,13 +539,6 @@ declare function modifierComboToParsed(key: ModifierCombo): ParsedModifiers;
|
|
|
513
539
|
* `name=req` pairs joined by `&`. */
|
|
514
540
|
declare function canonicalModifiers(mods: ParsedModifiers): string;
|
|
515
541
|
|
|
516
|
-
/** Keyboard/pointer modifier flags carried by every {@link InputEvent} arm. */
|
|
517
|
-
interface EventModifiers {
|
|
518
|
-
altKey: boolean;
|
|
519
|
-
ctrlKey: boolean;
|
|
520
|
-
metaKey: boolean;
|
|
521
|
-
shiftKey: boolean;
|
|
522
|
-
}
|
|
523
542
|
/**
|
|
524
543
|
* Body-target classification from the scene hit-test. Populated by
|
|
525
544
|
* `useGestureDispatcher` when a `classifyTarget` thunk is supplied to its
|
|
@@ -546,6 +565,28 @@ interface BodyClassification {
|
|
|
546
565
|
/** Semantic kind of the hit body. Undefined on empty canvas, and undefined
|
|
547
566
|
* on a body whose kind the scene can't name — never guessed. */
|
|
548
567
|
kind?: BodyKind;
|
|
568
|
+
/** The node classified. Matching ignores it. */
|
|
569
|
+
id?: string;
|
|
570
|
+
/** What a double-click at this point names, where that is not the body hit: the group a hit
|
|
571
|
+
* member belongs to. A double click's targets match against it. Absent, the hit is named. */
|
|
572
|
+
doubleClick?: {
|
|
573
|
+
body: BodyTarget;
|
|
574
|
+
kind?: BodyKind;
|
|
575
|
+
id?: string;
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/** The body a double-click names, as a press carries it to the double click it composes. */
|
|
579
|
+
interface DoubleClickBody {
|
|
580
|
+
bodyTarget: BodyTarget;
|
|
581
|
+
bodyKind?: BodyKind;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Keyboard/pointer modifier flags carried by every {@link InputEvent} arm. */
|
|
585
|
+
interface EventModifiers {
|
|
586
|
+
altKey: boolean;
|
|
587
|
+
ctrlKey: boolean;
|
|
588
|
+
metaKey: boolean;
|
|
589
|
+
shiftKey: boolean;
|
|
549
590
|
}
|
|
550
591
|
/** A keystroke. */
|
|
551
592
|
interface KeyEvent extends EventModifiers {
|
|
@@ -741,9 +782,19 @@ interface ContextMenuEvent extends EventModifiers {
|
|
|
741
782
|
bodyTarget?: BodyTarget;
|
|
742
783
|
bodyKind?: BodyKind;
|
|
743
784
|
}
|
|
744
|
-
/**
|
|
785
|
+
/** What a hover found under the pointer. */
|
|
786
|
+
interface HoverWhere {
|
|
787
|
+
affordance?: unknown;
|
|
788
|
+
bodyTarget?: BodyTarget;
|
|
789
|
+
bodyKind?: BodyKind;
|
|
790
|
+
}
|
|
791
|
+
/** A move of a pointer with no button pressed, or its leaving the surface. Matched by `hover` specs. */
|
|
745
792
|
interface HoverEvent extends EventModifiers {
|
|
746
793
|
kind: 'hover';
|
|
794
|
+
/** What the pointer was over at its last move, when it has made one since coming onto the surface. */
|
|
795
|
+
from?: HoverWhere;
|
|
796
|
+
/** The pointer has left the surface. Where it is and what it is over are its last move's. */
|
|
797
|
+
off?: boolean;
|
|
747
798
|
/** World-space position of the pointer. */
|
|
748
799
|
x: number;
|
|
749
800
|
y: number;
|
|
@@ -834,6 +885,21 @@ interface DropEvent extends EventModifiers {
|
|
|
834
885
|
clientX?: number;
|
|
835
886
|
clientY?: number;
|
|
836
887
|
}
|
|
888
|
+
/** A carry over the canvas (`startCarry`). `stage` is what it did here: came
|
|
889
|
+
* over, moved, was released, or left — off the surface, or canceled on it. */
|
|
890
|
+
interface CarryEvent extends EventModifiers {
|
|
891
|
+
kind: 'carry';
|
|
892
|
+
stage: 'enter' | 'move' | 'drop' | 'leave';
|
|
893
|
+
pointerId?: number;
|
|
894
|
+
/** The payload's type, which a spec's `types` globs filter, and the payload. */
|
|
895
|
+
type: string;
|
|
896
|
+
value: unknown;
|
|
897
|
+
/** World-space position (post view transform). */
|
|
898
|
+
x: number;
|
|
899
|
+
y: number;
|
|
900
|
+
clientX: number;
|
|
901
|
+
clientY: number;
|
|
902
|
+
}
|
|
837
903
|
/** External content pasted from the system clipboard. Carries no point. */
|
|
838
904
|
interface PasteEvent extends EventModifiers {
|
|
839
905
|
kind: 'paste';
|
|
@@ -848,7 +914,7 @@ interface PasteEvent extends EventModifiers {
|
|
|
848
914
|
* The `affordance?` field on {@link PointerDownEvent} is widened to `unknown`
|
|
849
915
|
* in this package — the kit narrows it back to `AffordanceHit` at consumption.
|
|
850
916
|
*/
|
|
851
|
-
type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | HoverEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | PasteEvent;
|
|
917
|
+
type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDownEvent | PointerMoveEvent | HoverEvent | PointerUpEvent | PointerCancelEvent | ClickEvent | DoubleClickEvent | ContextMenuEvent | LongPressEvent | MultiTouchEvent | MultiTouchTapEvent | DropEvent | CarryEvent | PasteEvent;
|
|
852
918
|
|
|
853
919
|
/**
|
|
854
920
|
* The stylus fields `src` carries, as a spread-able partial: a sample that
|
|
@@ -856,29 +922,8 @@ type InputEvent = KeyEvent | KeyHeldEvent | WheelEvent | PinchEvent | PointerDow
|
|
|
856
922
|
*/
|
|
857
923
|
declare function stylusOf(src: PointerSampleStylus): PointerSampleStylus;
|
|
858
924
|
|
|
859
|
-
/**
|
|
925
|
+
/** Target matching: a `TargetSpec` against what an event classified under its point. */
|
|
860
926
|
|
|
861
|
-
/** The modifier-state fields `matchModifiers` reads. Structural, so a DOM
|
|
862
|
-
* `KeyboardEvent` or `PointerEvent` satisfies it directly. */
|
|
863
|
-
type ModifiersEvent = {
|
|
864
|
-
altKey: boolean;
|
|
865
|
-
ctrlKey: boolean;
|
|
866
|
-
metaKey: boolean;
|
|
867
|
-
shiftKey: boolean;
|
|
868
|
-
};
|
|
869
|
-
/**
|
|
870
|
-
* Strict modifier match.
|
|
871
|
-
*
|
|
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`.
|
|
878
|
-
*/
|
|
879
|
-
declare function matchModifiers(e: ModifiersEvent, mods: ModSpec | undefined, isMac: boolean): boolean;
|
|
880
|
-
/** Case-insensitive key match; supports string or string[] (any-of). */
|
|
881
|
-
declare function matchKey(eventKey: string, specKey: string | string[]): boolean;
|
|
882
927
|
/**
|
|
883
928
|
* A {@link TargetSpec} resolved to its form, with the parts each form encodes
|
|
884
929
|
* in its string pulled out. This is the one enumeration of the target forms:
|
|
@@ -932,6 +977,30 @@ declare function parseTargetSpec(spec: TargetSpec): TargetSpecForm | null;
|
|
|
932
977
|
* silent wildcard.
|
|
933
978
|
*/
|
|
934
979
|
declare function matchTarget(target: unknown, specTarget: TargetSpec | undefined, bodyTarget?: string, bodyKind?: string): boolean;
|
|
980
|
+
|
|
981
|
+
/** Pure gesture matcher — no React, no state, no DOM. */
|
|
982
|
+
|
|
983
|
+
/** The modifier-state fields `matchModifiers` reads. Structural, so a DOM
|
|
984
|
+
* `KeyboardEvent` or `PointerEvent` satisfies it directly. */
|
|
985
|
+
type ModifiersEvent = {
|
|
986
|
+
altKey: boolean;
|
|
987
|
+
ctrlKey: boolean;
|
|
988
|
+
metaKey: boolean;
|
|
989
|
+
shiftKey: boolean;
|
|
990
|
+
};
|
|
991
|
+
/**
|
|
992
|
+
* Strict modifier match.
|
|
993
|
+
*
|
|
994
|
+
* - Omitted or `false`: MUST NOT be held.
|
|
995
|
+
* - `true`: MUST be held.
|
|
996
|
+
* - `'optional'`: accepts either held or unheld.
|
|
997
|
+
* - `mod` is platform-aware: matches `metaKey` on mac, `ctrlKey` elsewhere,
|
|
998
|
+
* and the *other* platform key is forbidden. Callers should not combine
|
|
999
|
+
* `mod` with `meta`/`ctrl`.
|
|
1000
|
+
*/
|
|
1001
|
+
declare function matchModifiers(e: ModifiersEvent, mods: ModSpec | undefined, isMac: boolean): boolean;
|
|
1002
|
+
/** Case-insensitive key match; supports string or string[] (any-of). */
|
|
1003
|
+
declare function matchKey(eventKey: string, specKey: string | string[]): boolean;
|
|
935
1004
|
/** Per-tool gesture-lifecycle state at match time. `selfChannel` is the tool
|
|
936
1005
|
* id `'&'` resolves to — the tool that owns the binding being evaluated — or
|
|
937
1006
|
* `null` for a binding without an owning tool, in which case no `'&'` atom
|
|
@@ -979,6 +1048,126 @@ declare function matchIngestTypes(items: readonly {
|
|
|
979
1048
|
*/
|
|
980
1049
|
declare function isApplePlatform(): boolean;
|
|
981
1050
|
|
|
1051
|
+
/** One key of a shortcut as a keyboard prints it. */
|
|
1052
|
+
interface KeyLegend {
|
|
1053
|
+
/** Glyph drawn for the key (modifier or key). */
|
|
1054
|
+
label: string;
|
|
1055
|
+
/** When true, the key may be held but is not required to trigger the action
|
|
1056
|
+
* (e.g. an optional modifier). Defaults to false. */
|
|
1057
|
+
optional?: boolean;
|
|
1058
|
+
}
|
|
1059
|
+
/** Logical modifier name. Matches the kit's `ModifierKey` in shape — kept as a
|
|
1060
|
+
* local type so `weasel-ui` stays independent of `@weasel-js/core`. */
|
|
1061
|
+
type LogicalMod = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta';
|
|
1062
|
+
/** Detected (or overridden) platform. `'mod'` resolves to ⌘ on macOS,
|
|
1063
|
+
* Ctrl on Windows / Linux. */
|
|
1064
|
+
type Platform = 'macos' | 'windows' | 'linux';
|
|
1065
|
+
/** Visual form of a modifier or named-key label.
|
|
1066
|
+
*
|
|
1067
|
+
* - `'auto'` (default): the per-entry, per-platform default — whatever
|
|
1068
|
+
* label is actually printed on the physical key. macOS keys show the
|
|
1069
|
+
* Apple glyphs (⌘ ⌥ ⌃ ⇧ ⎋ ↵ ⇥). Windows keys mostly show text
|
|
1070
|
+
* (Ctrl, Alt, Shift) BUT the Win key shows its logo (⊞) and the
|
|
1071
|
+
* context-menu key shows ▤. Linux mirrors Windows with Super (⊞).
|
|
1072
|
+
* - `'symbol'` forces a glyph wherever the key has one: Apple's on macOS,
|
|
1073
|
+
* ISO/IEC 9995-7's elsewhere (⎈ Ctrl, ⎇ Alt, ⎋ Esc). A key with no
|
|
1074
|
+
* symbol still falls back to text.
|
|
1075
|
+
* - `'text'` always spells the label out (Cmd / Option / Esc / Enter). */
|
|
1076
|
+
type LegendStyle = 'auto' | 'symbol' | 'text';
|
|
1077
|
+
/** A modifier to render, named platform-independently. */
|
|
1078
|
+
interface LogicalModSpec {
|
|
1079
|
+
name: LogicalMod;
|
|
1080
|
+
/** Marks this modifier as optional (may be held but isn't required).
|
|
1081
|
+
* Forwarded as the resulting `KeyLegend.optional`. */
|
|
1082
|
+
optional?: boolean;
|
|
1083
|
+
}
|
|
1084
|
+
/** Options for {@link keySpecsFromMods}. */
|
|
1085
|
+
interface KeySpecsFromModsOptions {
|
|
1086
|
+
/** Override OS detection. Defaults to {@link detectPlatform}. */
|
|
1087
|
+
platform?: Platform;
|
|
1088
|
+
/** Defaults to `'auto'`. Pass `'symbol'` or `'text'` to force a specific
|
|
1089
|
+
* style regardless of platform. */
|
|
1090
|
+
legend?: LegendStyle;
|
|
1091
|
+
/** Labels that replace the platform and legend tables', by a modifier's
|
|
1092
|
+
* logical name (`mod`, `shift`, `alt`, `ctrl`, `meta`) or a key's
|
|
1093
|
+
* `KeyboardEvent.key` (`Escape`, `Home`, `z`): a whole keyboard's worth
|
|
1094
|
+
* for a layout, a locale, or a keyboard of one's own. */
|
|
1095
|
+
labels?: KeyLabels;
|
|
1096
|
+
}
|
|
1097
|
+
/** Key labels by modifier name or `KeyboardEvent.key`. See {@link KeySpecsFromModsOptions.labels}. */
|
|
1098
|
+
type KeyLabels = Readonly<Record<string, string>>;
|
|
1099
|
+
/** Every label {@link keySpecsFromMods} can produce, on any platform and legend. */
|
|
1100
|
+
declare const MODIFIER_LABELS: ReadonlySet<string>;
|
|
1101
|
+
/** The platform whose key legends to draw. Whether `mod` is ⌘ comes from
|
|
1102
|
+
* `isApplePlatform`, the same read the dispatcher
|
|
1103
|
+
* matches `mod` with, so a keycap names the key that fires. With no
|
|
1104
|
+
* `navigator` (a server render) it answers `'macos'`, the kit's docs
|
|
1105
|
+
* convention; an unrecognized browser reads as `'linux'`. */
|
|
1106
|
+
declare function detectPlatform(): Platform;
|
|
1107
|
+
/**
|
|
1108
|
+
* Map a list of **logical** modifiers (`'mod'`, `'shift'`, …) to **visual**
|
|
1109
|
+
* `KeyLegend`s appropriate for the current platform — `'mod'` means "Cmd on
|
|
1110
|
+
* Mac, Ctrl on Win/Linux".
|
|
1111
|
+
*
|
|
1112
|
+
* ```ts
|
|
1113
|
+
* <KeySequence
|
|
1114
|
+
* keys={keySpecsFromMods([{ name: 'mod' }, { name: 'shift', optional: true }])}
|
|
1115
|
+
* />
|
|
1116
|
+
* ```
|
|
1117
|
+
*/
|
|
1118
|
+
declare function keySpecsFromMods(mods: readonly LogicalModSpec[], opts?: KeySpecsFromModsOptions): readonly KeyLegend[];
|
|
1119
|
+
/**
|
|
1120
|
+
* Render a raw `KeyboardEvent.key` value as a platform-appropriate
|
|
1121
|
+
* `KeyLegend`. Named non-modifier keys (Esc, Enter, Tab, Space, Backspace,
|
|
1122
|
+
* Delete, arrows) get the platform's conventional form. Letter / digit /
|
|
1123
|
+
* other keys pass through upper-cased.
|
|
1124
|
+
*
|
|
1125
|
+
* ```ts
|
|
1126
|
+
* keySpecFromKey('Escape', { platform: 'macos' }) // { label: '⎋' }
|
|
1127
|
+
* keySpecFromKey('Escape', { platform: 'windows' }) // { label: 'Esc' }
|
|
1128
|
+
* keySpecFromKey('p') // { label: 'P' }
|
|
1129
|
+
* ```
|
|
1130
|
+
*
|
|
1131
|
+
* Symbol mode off macOS draws ISO/IEC 9995-7's symbols where the key
|
|
1132
|
+
* has one, though Windows and Linux keyboards print the text form.
|
|
1133
|
+
*/
|
|
1134
|
+
declare function keySpecFromKey(raw: string, opts?: KeySpecsFromModsOptions & {
|
|
1135
|
+
optional?: boolean;
|
|
1136
|
+
}): KeyLegend;
|
|
1137
|
+
|
|
1138
|
+
/** A keyboard shortcut as the formatters read it: `@weasel-js/menu`'s `MenuShortcut`. */
|
|
1139
|
+
type ShortcutInput = MenuShortcut;
|
|
1140
|
+
/** Options for {@link formatShortcut} and {@link formatShortcutParts}. */
|
|
1141
|
+
interface FormatShortcutOptions {
|
|
1142
|
+
/** Defaults to the detected platform. */
|
|
1143
|
+
platform?: Platform;
|
|
1144
|
+
}
|
|
1145
|
+
/** Format a shortcut as an array of display chips, one per key: the
|
|
1146
|
+
* modifiers, then the key, each as the platform's keyboard prints it, so
|
|
1147
|
+
* `mod` is `⌘` on macOS and `Ctrl` elsewhere. UIs that want a boxed per-key
|
|
1148
|
+
* look render each chip in its own element. `shift: 'optional'` is treated
|
|
1149
|
+
* as falsy. */
|
|
1150
|
+
declare function formatShortcutParts(b: ShortcutInput | undefined, opts?: FormatShortcutOptions): readonly string[] | undefined;
|
|
1151
|
+
/** Format a shortcut as a single display string. Order: mod, shift, alt,
|
|
1152
|
+
* key. On macOS the glyphs run together (`⌘⇧Z`); elsewhere the keys are
|
|
1153
|
+
* spelled out and joined with `+` (`Ctrl+Shift+Z`). Returns `undefined` for
|
|
1154
|
+
* `undefined` input so callers can `??` a fallback. When `key` is an array,
|
|
1155
|
+
* the first element is used. `shift: 'optional'` is treated as falsy. */
|
|
1156
|
+
declare function formatShortcut(b: ShortcutInput | undefined, opts?: FormatShortcutOptions): string | undefined;
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* A shortcut as key legends, one per key, as `@weasel-js/ui`'s `KeySequence` draws them: mod, shift, alt, then the
|
|
1160
|
+
* key, each in the platform's own legend (`⌘` on macOS, `Ctrl` elsewhere).
|
|
1161
|
+
* `formatShortcutParts` is these labels as strings. `shift: 'optional'`
|
|
1162
|
+
* becomes an optional key; a key list
|
|
1163
|
+
* renders its first key. Returns `undefined` for no shortcut.
|
|
1164
|
+
*
|
|
1165
|
+
* ```tsx
|
|
1166
|
+
* <KeySequence keys={keySpecsFromShortcut({ key: 'z', mod: true, shift: true })} />
|
|
1167
|
+
* ```
|
|
1168
|
+
*/
|
|
1169
|
+
declare function keySpecsFromShortcut(shortcut: ShortcutInput | undefined, opts?: KeySpecsFromModsOptions): readonly KeyLegend[] | undefined;
|
|
1170
|
+
|
|
982
1171
|
/**
|
|
983
1172
|
* The nearest element `from` sits inside that can host a portalled overlay,
|
|
984
1173
|
* or `null` when there is none and the body is the only answer.
|
|
@@ -1008,7 +1197,7 @@ declare function fixedSeat(from: Element | null | undefined): FixedSeat;
|
|
|
1008
1197
|
* [1] mods — count of required modifier keys (shift/alt/ctrl/meta/mod).
|
|
1009
1198
|
* `'optional'` does NOT count.
|
|
1010
1199
|
* [2] phase — how much the spec's `phase` narrows; see `phaseRank`.
|
|
1011
|
-
* [3] exact — per-kind tiebreak: 2 for a drop/paste spec with a
|
|
1200
|
+
* [3] exact — per-kind tiebreak: 2 for a drop/carry/paste spec with a
|
|
1012
1201
|
* non-empty `types` MIME filter, else 1.
|
|
1013
1202
|
*
|
|
1014
1203
|
* Identical tuples fall back to registration order in the matcher's
|
|
@@ -1083,6 +1272,8 @@ interface PressFacts extends EventModifiers {
|
|
|
1083
1272
|
affordance?: unknown;
|
|
1084
1273
|
bodyTarget?: BodyTarget;
|
|
1085
1274
|
bodyKind?: BodyKind;
|
|
1275
|
+
/** Replayed onto the double click in place of `bodyTarget` and `bodyKind`. */
|
|
1276
|
+
doubleClickBody?: DoubleClickBody;
|
|
1086
1277
|
}
|
|
1087
1278
|
/** Where and how a press was released, for the click it turned out to be. */
|
|
1088
1279
|
interface ReleaseFacts extends EventModifiers {
|
|
@@ -1176,6 +1367,8 @@ interface PressSample extends PointerSample {
|
|
|
1176
1367
|
affordance?: unknown;
|
|
1177
1368
|
bodyTarget?: BodyTarget;
|
|
1178
1369
|
bodyKind?: BodyKind;
|
|
1370
|
+
/** What a double click this press completes lands on, where not the body hit. */
|
|
1371
|
+
doubleClickBody?: DoubleClickBody;
|
|
1179
1372
|
}
|
|
1180
1373
|
|
|
1181
1374
|
/**
|
|
@@ -1214,6 +1407,14 @@ interface PointerRecognizerOptions {
|
|
|
1214
1407
|
toSurface?: (client: Point) => Point;
|
|
1215
1408
|
/** Long-press hold time in ms, read at each press. Default `LONG_PRESS_MS`. */
|
|
1216
1409
|
longPressMs?: () => number | undefined;
|
|
1410
|
+
/** Longest gap in ms between two clicks of a double click, read at each click. Default `DOUBLE_CLICK_MAX_MS`. */
|
|
1411
|
+
doubleClickMs?: () => number | undefined;
|
|
1412
|
+
/** Farthest two clicks of a double click may land apart, in CSS px. Default `DOUBLE_CLICK_MAX_PX`. */
|
|
1413
|
+
doubleClickPx?: () => number | undefined;
|
|
1414
|
+
/** Farthest a multitouch tap's fingers may travel, in CSS px. Default `TAP_THRESHOLD_PX`. */
|
|
1415
|
+
tapPx?: () => number | undefined;
|
|
1416
|
+
/** Travel in CSS px that makes a press a drag, read at each move. Default `DRAG_THRESHOLD_PX`. */
|
|
1417
|
+
dragPx?: () => number | undefined;
|
|
1217
1418
|
clicks?: ClickMemory;
|
|
1218
1419
|
observer?: RecognizerObserver;
|
|
1219
1420
|
}
|
|
@@ -1250,6 +1451,22 @@ declare const CONTROL_SELECTOR: string;
|
|
|
1250
1451
|
*/
|
|
1251
1452
|
declare function isInControlWithin(target: EventTarget | null, host: Element): boolean;
|
|
1252
1453
|
|
|
1454
|
+
/**
|
|
1455
|
+
* Which pointers are down on an element: pressed on it or inside it, with any
|
|
1456
|
+
* button, and not yet released. The one answer to "is this move a hover" for
|
|
1457
|
+
* everything listening on that element.
|
|
1458
|
+
*/
|
|
1459
|
+
interface HeldPointers {
|
|
1460
|
+
count(): number;
|
|
1461
|
+
/** Stop reading. The element's listeners go with its last reader. */
|
|
1462
|
+
release(): void;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Read the pointers held on `element`. Every reader of one element shares one
|
|
1466
|
+
* set of listeners, so they cannot disagree.
|
|
1467
|
+
*/
|
|
1468
|
+
declare function heldPointers(element: Element): HeldPointers;
|
|
1469
|
+
|
|
1253
1470
|
/** Returns true when the target takes typed text: an input, a textarea, or a contenteditable element. */
|
|
1254
1471
|
declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
1255
1472
|
/**
|
|
@@ -1268,4 +1485,4 @@ declare function activatesFocusedControl(e: KeyboardEvent): boolean;
|
|
|
1268
1485
|
*/
|
|
1269
1486
|
declare function focusedControlOwnsKey(e: KeyboardEvent): boolean;
|
|
1270
1487
|
|
|
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 };
|
|
1488
|
+
export { ACTIVE_SIGILS, type BodyClassification, type BodyKind, type BodyTarget, CONTROL_SELECTOR, type CarryEvent, type CarrySpec, 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 DoubleClickBody, type DoubleClickEvent, type DoubleClickSpec, type DragSpec, type DropEvent, type DropSpec, type EventModifiers, type FixedSeat, type FormatShortcutOptions, 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 KeyLabels, type KeyLegend, type KeySpec, type KeySpecsFromModsOptions, LONG_PRESS_MS, type LegendStyle, type LogicalMod, type LogicalModSpec, type LongPressEvent, type LongPressSpec, MODIFIER_LABELS, 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 Platform, 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, type ShortcutInput, 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, detectPlatform, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, formatShortcut, formatShortcutParts, getGestureDescriptor, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, keyRouteToSpec, keySpecFromKey, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
|