@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/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # @weasel-js/gestures
2
2
 
3
- Pure gesture taxonomy, route grammars, and matcher primitives. No React, no DOM, no scene.
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/api/).
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
- ## Usage
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 { /* … */ } from '@weasel-js/gestures';
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