@jsenv/navi 0.29.417 → 0.29.419

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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/dev/jsenv_navi.js +347 -83
  3. package/dist/dev/jsenv_navi.js.map +12 -12
  4. package/dist/jsenv_navi.js +345 -81
  5. package/dist/jsenv_navi.js.map +12 -12
  6. package/docs/AI_INSTRUCTIONS.md +177 -258
  7. package/docs/actions.md +164 -251
  8. package/docs/autofocus.md +66 -83
  9. package/docs/control_object.md +117 -214
  10. package/docs/control_value.md +121 -185
  11. package/docs/create_and_edit.md +159 -254
  12. package/docs/css_architecture.md +196 -260
  13. package/docs/data_states.md +41 -44
  14. package/docs/dialog_shape.md +91 -134
  15. package/docs/drag_interactions.md +291 -454
  16. package/docs/drag_to_travel.md +152 -748
  17. package/docs/dynamic_import.md +119 -136
  18. package/docs/error_handling.md +75 -127
  19. package/docs/field_validation.md +49 -89
  20. package/docs/form_changed.md +61 -78
  21. package/docs/group.md +141 -0
  22. package/docs/i18n.md +31 -76
  23. package/docs/interactions.md +170 -326
  24. package/docs/list_action.md +36 -51
  25. package/docs/list_refresh.md +73 -115
  26. package/docs/mobile_touch.md +56 -62
  27. package/docs/navigation.md +377 -723
  28. package/docs/network_policy.md +61 -75
  29. package/docs/pan_zoom.md +62 -103
  30. package/docs/popup_backdrop.md +128 -168
  31. package/docs/popup_lift.md +179 -268
  32. package/docs/popup_open.md +380 -651
  33. package/docs/resource.md +228 -408
  34. package/docs/route_transitions.md +333 -473
  35. package/docs/safe_area.md +102 -89
  36. package/docs/scroll.md +175 -317
  37. package/docs/state_binding.md +40 -84
  38. package/docs/testid.md +18 -23
  39. package/docs/typography.md +111 -141
  40. package/docs/view_transitions.md +106 -104
  41. package/docs/z_index.md +61 -109
  42. package/package.json +2 -2
  43. package/docs/badge_list.md +0 -83
  44. package/docs/control_group.md +0 -168
  45. package/docs/mobile_layout_pitfalls.md +0 -145
@@ -6,8 +6,6 @@ An element picked up and carried, and what letting go means. It is one gesture
6
6
  the gate they go through and how a control learns which of them asked are in
7
7
  [interactions.md](./interactions.md); who owns a press when the carried thing
8
8
  stands in a box that travels is in [drag_to_travel.md](./drag_to_travel.md).
9
- Whatever the moment, `event.detail.event` is the `pointerdown` the carry began
10
- with — for the release as well, unlike a swipe's.
11
9
 
12
10
  - [Carrying something: `move`, `reorder`, `land`, `toss`, `leave`](#carrying-something-move-reorder-land-toss-leave)
13
11
  - [While it is being moved: `moving`](#while-it-is-being-moved-moving)
@@ -17,6 +15,7 @@ with — for the release as well, unlike a swipe's.
17
15
  - [Saying the grab is acquired: `grab`](#saying-the-grab-is-acquired-grab)
18
16
  - [Saying the hold is let go: `release`](#saying-the-hold-is-let-go-release)
19
17
  - [The hand pulls and nothing follows: `refuse`](#the-hand-pulls-and-nothing-follows-refuse)
18
+ - [A press made elsewhere: `--navi-grab`](#a-press-made-elsewhere---navi-grab)
20
19
  - [Dressing the clone](#dressing-the-clone)
21
20
  - [What says a thing can be picked up](#what-says-a-thing-can-be-picked-up)
22
21
  - [The text inside: `user-select: none`](#the-text-inside-user-select-none)
@@ -25,23 +24,19 @@ with — for the release as well, unlike a swipe's.
25
24
 
26
25
  ## Carrying something: `move`, `reorder`, `land`, `toss`, `leave`
27
26
 
28
- All five are the same gesture — the element is picked up and carried — and what
29
- differs is the release. One detector reads them all, because it is one press.
27
+ `move` carries the element ITSELF and leaves it where it was put; `reorder`,
28
+ `land` and `toss` carry a copy and put the original back; `leave` carries the
29
+ element beside `move` or `moving`, a copy otherwise. Something moved has a new
30
+ place of its own; something reordered had its place taken by the list.
30
31
 
31
32
  `toss` and `leave` each **combine** with `reorder` and with `land`: dropped on
32
33
  another item the element changes places, thrown far and fast it is gotten rid of,
33
34
  let go of away from every place it leaves. `leave` combines with `move` as well.
34
- `move` does **not** combine with any of the three that carry a copy (`reorder`,
35
- `land`, `toss`), and neither do `reorder` and `land` with each other — one
36
- release cannot mean two of those. Declared together, the copy-carrying one wins
37
- and `move` is never answered — the element itself never travels, and a release
38
- that is not the other outcome means nothing at all; of `reorder` and `land`,
39
- `land` wins. A dev warning says so in each case.
40
-
41
- `move` carries the element ITSELF and leaves it where it was put; the others
42
- carry a copy and put the original back. That is the same difference said in layout
43
- terms: something moved has a new place of its own, something reordered had its
44
- place taken by the list.
35
+ `move` does **not** combine with the three that carry a copy, and neither do
36
+ `reorder` and `land` with each other — one release cannot mean two of those.
37
+ Declared together, the copy-carrying one wins and the element itself never
38
+ travels; of `reorder` and `land`, `land` wins. `moving` goes with `move` and
39
+ `leave`, never with a copy. A dev warning says so in each case.
45
40
 
46
41
  ```jsx
47
42
  <Box
@@ -52,34 +47,47 @@ place taken by the list.
52
47
  />
53
48
  ```
54
49
 
55
- `data-drag-free` on the element or a container lets it leave; by default a `move`
56
- stays inside what one can SEE of its scroll container — the nearest ancestor whose
57
- `overflow` is `auto` or `scroll`, the page's viewport when there is none — so a
58
- board that only cuts its content (`hidden`, `clip`) does not hold what moves on
59
- it. A `move` whose answer rejects travels back, because a
60
- place the application would not accept must not stay on screen as if it had.
61
-
62
- **Where the position is kept is the answer's.** An element drawn from state —
63
- `left`/`top` computed from a position the application holds — is redrawn by the
64
- handler above, and from then on its layout owns the position: navi sees the
65
- element drawn elsewhere while the answer was given and lets go of its own
66
- translate, so the thing does not land twice as far as the hand went. A handler
67
- that draws nothing (a token on a free canvas) leaves the position to the element,
68
- where it is baked in. Nothing to declare either way — but a handler whose draw
69
- comes later than its answer has to return the promise of that draw.
70
-
71
- That is one answer, at the release, with the whole of the gesture in it: `move` is
72
- not told while the hand moves, and until the release navi carries the element with
73
- a translate of its own. A thing that has to change something WHILE it is dragged —
74
- or that must never be translated at all — declares `moving` instead (below).
75
-
76
- **A copy that can be thrown frees its own area.** What is dragged is otherwise kept
77
- inside its scroll area — right for a reorder, since a row belongs to its list, and
78
- fatal for a throw: the copy hits the edge of the list, no distance is ever covered,
79
- so no throw can happen and no sideways movement is even visible. So `toss` lifts
80
- that constraint for the copy it carries, and `leave` lifts it for whatever it is
81
- declared on — a thing that can be let go of outside has to be able to get there.
82
- Neither is a way to free a plain `move`, which says `data-drag-free`.
50
+ What each one is told, in `event.detail` beside `event` — the `pointerdown` the
51
+ carry began with, for the release as well (unlike a swipe's), except for
52
+ [a press handed over](#a-press-made-elsewhere---navi-grab):
53
+
54
+ | interaction | `event.detail` |
55
+ | ---------------- | ---------------------------------------------------------------- |
56
+ | `move`, `moving` | `{ x, y }`, how far from the grab: once, or on every frame |
57
+ | `reorder` | `{ fromId, toId, syncCloneWithDropTarget }` |
58
+ | `land` | `{ fromId, toId, x, y, width, height, syncCloneWithDropTarget }` |
59
+ | `toss` | `{ id, velocity, x, y }` |
60
+ | `leave` | `{ id, x, y }` |
61
+ | `grab` | `{ pointerType, gestureInfo }` |
62
+ | `release` | `{ id, x, y, outcome }` |
63
+ | `refuse` | `{ pointerType }` |
64
+
65
+ Items and places are named by their `id`. **What an outcome returns is waited
66
+ on**: the gesture holds what it carries until that promise settles, and a
67
+ rejection sends it home — a place the application would not accept must not stay
68
+ on screen as if it had. `moving`, `grab`, `release` and `refuse` **report, they
69
+ do not ask**: what they return is not waited on, and preventing their event
70
+ calls nothing off.
71
+
72
+ **Where the position is kept is the answer's.** Until the release navi carries
73
+ the element with a translate of its own. An element drawn from state
74
+ (`left`/`top` computed from a position the application holds) is redrawn by the
75
+ `move` handler, and its layout owns the position from then on: navi sees it drawn
76
+ elsewhere and drops its translate, or it would land twice as far as the hand
77
+ went. A handler that draws nothing (a token on a free canvas) leaves the position
78
+ baked into the element; one whose draw comes later than its answer returns the
79
+ promise of that draw. Something that changes WHILE it is dragged, or must never
80
+ be translated, is [`moving`](#while-it-is-being-moved-moving).
81
+
82
+ **Where it may go.** A `move` stays inside what one can SEE of its scroll
83
+ container — the nearest ancestor whose `overflow` is `auto` or `scroll`, the
84
+ viewport when there is none — so a board that only cuts its content (`hidden`,
85
+ `clip`) does not hold it; a copy stays inside its scroll area, as a row belongs
86
+ to its list. `data-drag-free` on the element or a container lets it out, and
87
+ three things do on their own, each needing to get somewhere: a `toss` for the
88
+ copy it throws (kept inside its list, it covers no distance), a `leave` for
89
+ whatever it is declared on, and a `data-drop-container` (a place outside is only
90
+ reached from outside).
83
91
 
84
92
  ```jsx
85
93
  <List.Item
@@ -98,42 +106,34 @@ Neither is a way to free a plain `move`, which says `data-drag-free`.
98
106
  />
99
107
  ```
100
108
 
101
- The gesture is `startDragTo`'s, whole: `move` carries the element itself, the
102
- others carry a copy above the page while the original keeps its place, with a
103
- drop hint, drop targets found by intersection, no-op drops filtered out, and the
104
- flight of a thrown copy plus its return when the answer refuses. Only what the
105
- declared outcomes need runs — no copy for a move, no hint for something
106
- that can only be thrown away.
107
-
108
109
  Every element declaring `reorder` marks itself, so the set of items IS the set of
109
110
  elements that declared it — no selector to pass, and an item that must not move
110
- simply does not declare it. An element declaring only `toss` marks nothing: it is
111
- not a place anything lands. Items are named by their `id`.
112
-
113
- `toId` is null for a drop at the end. `syncCloneWithDropTarget` must be called
114
- synchronously inside the transition callback, next to the state change, so the copy
115
- is captured where it lands rather than where it was let go of.
116
-
117
- **The promise matters in both cases**: the gesture holds its copy until the answer
118
- settles. Returning the transition is what makes a landing continuous; a `toss` that
119
- rejects brings the copy back, because the thing still exists and the screen has to
120
- say so.
111
+ simply does not declare it; one declaring only `toss` is not a place anything
112
+ lands. `toId` is null for a drop at the end. `syncCloneWithDropTarget` must be
113
+ called synchronously inside the transition callback, next to the state change,
114
+ so the copy is captured where it lands rather than where it was let go of;
115
+ returning the transition is what makes the landing continuous. Starting that
116
+ transition is the application's call, not navi's: a `view-transition-name` must
117
+ be unique per document (see
118
+ [view_transitions.md](./view_transitions.md#a-name-is-unique-per-document)), so
119
+ only the application can name what moves.
121
120
 
122
121
  A throw is asked about before a landing: a hand that sent something across the
123
122
  screen has not asked for it to swap places with whatever it flew over.
124
123
 
125
- Starting a document transition is the application's call, not navi's: a
126
- `view-transition-name` must be unique per document, so only the application can
127
- name what moves.
128
-
129
124
  | Attribute | Meaning |
130
125
  | -------------------------------------------------------- | -------------------------------------- |
131
126
  | `data-drag-axis="x"\|"y"\|"xy"` | which axes the drag walks |
132
127
  | `data-drag-delay` `data-drag-slop` `data-drag-threshold` | when the press becomes a grab |
133
128
  | `data-drag-on-contact` | a finger may drag by travelling too |
129
+ | `data-drag-free` | it may leave its scroll area |
134
130
  | `data-toss-distance` `data-toss-speed` | how far and how fast counts as a throw |
135
131
  | `data-drop-container` | where the places are looked for |
136
132
 
133
+ `data-drag-axis` is `y` for a `reorder` with no `land`, `toss` or `leave` beside
134
+ it — it walks the list; a list that runs sideways says `x` — and `xy` for
135
+ everything else, which goes wherever the hand takes it.
136
+
137
137
  ### While it is being moved: `moving`
138
138
 
139
139
  `move` says where the element ended up, once. That is the answer for something
@@ -152,42 +152,28 @@ all along, and needs to be the one drawing.
152
152
  />
153
153
  ```
154
154
 
155
- Declaring it says both things at once: the element is told where the hand has
156
- taken it on every frame, and **navi moves nothing** — no translate while the
157
- gesture runs, nothing to hand back at the release, no reading the element
158
- afterwards to find out who owns its position. The caller draws, from the numbers
159
- it is given.
155
+ Declaring it says both at once: the element is told where the hand has taken it
156
+ on every frame, and **navi moves nothing** — no translate, nothing to hand back
157
+ at the release. The numbers are counted from the grab, not steps since the last
158
+ frame (what `pan` gives, a surface having no grab to count from): a caller adding
159
+ up steps drifts, and has nothing to re-read after a frame it missed.
160
160
 
161
- Its detail is `{ x, y }`: where it is now, counted from the grab — the very
162
- numbers `move` ends with, said all along instead of once. Not steps since the last
163
- frame, which is what `pan` gives (a surface has no grab to count from): a caller
164
- adding up steps drifts, and has nothing to re-read after a frame it missed.
165
-
166
- Told, not asked, like `grab` and `release` — a draw that has to be awaited before
167
- the next frame is a draw one frame late. Where it differs from those two is that
168
- it is a gesture of its own: `moving` alone is a complete declaration, and it keeps
169
- everything the drag knows — the axes, the threshold, the hold a finger owes,
170
- `data-drag-free`, `grab`/`release`, `leave`, and `"refuse"` to lock it. Write
171
- `move` beside it only when the release settles something the frames did not.
172
-
173
- Nothing travels back, either: a `move` or a `leave` beside it whose answer rejects
174
- normally sends the element home, and there is no home to send it to when navi
175
- never moved it. The state the frames wrote is the caller's to put back.
176
-
177
- It carries the element itself, so it goes with `move` and `leave` and not with the
178
- three that carry a copy — for those, the original never goes anywhere, and there
179
- is nothing being moved to tell about (a dev warning says so).
161
+ Told, not asked — a draw awaited before the next frame is a draw one frame late —
162
+ yet a complete declaration on its own, keeping everything the drag knows: the
163
+ axes, the threshold, the hold a finger owes, `data-drag-free`,
164
+ `grab`/`release`, `leave`, `"refuse"` to lock it. Write `move` beside it only
165
+ when the release settles something the frames did not. Nothing travels back: a
166
+ refused `move` or `leave` beside it has no home to send the element to, so what
167
+ the frames wrote is the caller's to put back.
180
168
 
181
169
  ### Let go of away from every place: `leave`
182
170
 
183
- A throw is a **gesture**: far and fast, the flick that gets rid of a row, judged
184
- before any landing. A release outside is a **place**: the hand let go with nothing
185
- under the thing, judged after a landing was looked for. They share the outcome an
186
- application usually attaches to them and nothing else — so `toss` is the throw,
187
- `leave` is the release outside, and neither reads the other's rules. There is no
188
- speed to a release; a fast drag across a plan that ends ON it has not asked for the
189
- thing to go, and a row pulled sideways out of its list and let go is a row put
190
- back, not a row deleted — a list declares `toss`, a surface declares `leave`.
171
+ A throw is a **gesture** — far and fast, judged before any landing. A release
172
+ outside is a **place** — nothing under the thing, judged after a landing was
173
+ looked for. Neither reads the other's rules: there is no speed to a release, a
174
+ fast drag that ends ON a plan has not asked for the thing to go, and a row pulled
175
+ sideways out of its list and let go is a row put back, not a row deleted — a list
176
+ declares `toss`, a surface declares `leave`.
191
177
 
192
178
  ```jsx
193
179
  <div data-drop-container>
@@ -205,37 +191,21 @@ back, not a row deleted — a list declares `toss`, a surface declares `leave`.
205
191
  </div>
206
192
  ```
207
193
 
208
- `leave` combines with every other outcome. Beside `land` or `reorder`, "outside" is
209
- away from every place. Where nothing is a place — beside `move`, which carries the
210
- element itself, beside `toss`, or alone — it is outside the surface the element
211
- stands in: the nearest
194
+ `leave` combines with every other outcome. Beside `land` or `reorder`, "outside"
195
+ is away from every place. Where nothing is a place — beside `move`, beside
196
+ `toss`, or alone — it is outside the surface the element stands in: the nearest
212
197
  `data-droppable` ancestor, or, without one, what can be seen of the scroll
213
198
  container (a dev warning says which). Either way it is judged on the element's
214
199
  box no longer overlapping it, not on the pointer, which is still well inside the
215
- frame when a small marker has just left it.
200
+ frame when a small marker has just left it. Picked up and put straight back down
201
+ stays a cancel: it has to have gone somewhere to be away from anything. Its
202
+ `x`/`y` are what an exit is animated with.
216
203
 
217
- ```jsx
218
- <Plan data-droppable>
219
- <Marker
220
- interactions={{
221
- move: (event) => remember(event.detail.x, event.detail.y),
222
- leave: () => remove(marker.id),
223
- }}
224
- />
225
- </Plan>
226
- ```
227
-
228
- Its detail is `toss`'s without the speed — `{ id, x, y }`, the distance travelled
229
- being what an exit is animated with. Picked up and put straight back down stays a cancel: it has to
230
- have gone somewhere to be away from anything.
231
-
232
- What the thing does while the answer is asked follows what was carried. A copy
233
- fades where it was let go of; the element itself stays where the hand put it. A
234
- resolve takes the copy away and lets go of the element's position — the
235
- application has removed the thing, or drawn it where it goes back to (the address
236
- point returns to where the geocoder put it) — and a reject brings either back, as
237
- a refused `move` does. A surface that clips its overflow clips the element on its
238
- way out; the copy a `land` carries lives in the top layer and does not.
204
+ While the answer is asked, a copy fades where it was let go of and the element
205
+ itself stays where the hand put it; a resolve takes the copy away and lets go of
206
+ the element's position (the application removed the thing, or drew it where it
207
+ goes back to). A surface that clips its overflow clips the element on its way
208
+ out; the copy lives in the top layer and does not.
239
209
 
240
210
  ### A finger that does not have to wait: `data-drag-on-contact`
241
211
 
@@ -271,42 +241,25 @@ construction, so the answer is an insertion and putting a row back where it
271
241
  already was is a no-op. A place of a board is a place of its own, which may
272
242
  already be taken — so nothing is inserted, nothing is a no-op, and the answer is
273
243
  simply "this one came down on that one". What that means is yours: take the
274
- place, swap the two, refuse.
275
-
276
- ```jsx
277
- <Box
278
- id={playerId}
279
- interactions={{
280
- land: (event) => {
281
- const { fromId, toId, syncCloneWithDropTarget } = event.detail;
282
- return document.startViewTransition(() => {
283
- syncCloneWithDropTarget();
284
- setLineup((previous) => swapPlaces(previous, fromId, toId));
285
- }).finished;
286
- },
287
- }}
288
- />
289
- ```
290
-
291
- `toId` always names an element and is **never null**: a copy over nothing is a
292
- release that meant nothing, and the interaction does not happen at all.
244
+ place, swap the two, refuse. It is answered in the shape of a `reorder` (the
245
+ transition returned, `syncCloneWithDropTarget` inside it), and `toId` always names
246
+ an element — **never null**: a copy over nothing is a release that meant nothing,
247
+ and the interaction does not happen at all.
293
248
 
294
249
  **Which elements are places: those marked `data-droppable`, and only those.**
295
250
  Declaring `land` says an element can be CARRIED, which on a board is a different
296
251
  thing from being somewhere one can be put — a zone receives without ever being
297
252
  carried, a piece is carried without ever receiving, and both at once is a third
298
253
  case (dropped on a piece, the two swap, so it says `data-droppable` as well). A
299
- list has no such distinction, every row being both, which is why `reorder` needs
300
- no marker in the markup.
254
+ list has no such distinction, which is why `reorder` needs no marker.
301
255
 
302
256
  Places are looked for among the carried element's **siblings**, which is what a
303
- board is: pieces drawn beside the places, positioned over them. When what is
304
- carried does not stand among the places — a palette BESIDE the surface it fills, a
305
- marker drawn INSIDE the surface it can be put back on — say what holds both with
306
- `data-drop-container`. It holds the places rather than being one, so it goes on an
307
- ancestor of them: a surface that is itself the place is never found from inside
308
- itself. Missed, nothing lands and nothing says why — so the first press on a
309
- `land` with no place in reach warns, and names the surface it is standing on.
257
+ board is. When what is carried does not stand among the places — a palette
258
+ BESIDE the surface it fills, a marker drawn INSIDE the surface it can be put back
259
+ on — say what holds both with `data-drop-container`, on an ancestor of the
260
+ places: a surface that is itself the place is never found from inside itself.
261
+ Missed, nothing lands, so the first press on a `land` with no place in reach
262
+ warns, and names the surface it is standing on.
310
263
 
311
264
  ```jsx
312
265
  <div data-drop-container>
@@ -319,36 +272,20 @@ itself. Missed, nothing lands and nothing says why — so the first press on a
319
272
  </div>
320
273
  ```
321
274
 
322
- It also frees the copy's travel: the places being elsewhere by construction, a
323
- copy kept inside its own scroll area could never reach them.
324
-
325
275
  **When the place is bigger than what stands on it** — a zone holding a smaller
326
276
  card, a square holding a piece — the copy must not take the place's box, or it
327
- resizes on landing and resizes back when the real element appears.
328
- `syncCloneWithDropTarget` takes an element for that: the copy comes down on THAT
329
- box instead of the target's. Pass whatever occupies the destination — the piece
330
- already standing there, the empty slot waiting.
331
-
332
- ```jsx
333
- land: (event) => {
334
- const { fromId, toId, syncCloneWithDropTarget } = event.detail;
335
- const landingElement = pieceAt(toId) || slotOf(toId);
336
- return document.startViewTransition(() => {
337
- syncCloneWithDropTarget(landingElement);
338
- …
339
- }).finished;
340
- };
341
- ```
277
+ resizes on landing and resizes back when the real element appears. Pass
278
+ `syncCloneWithDropTarget` whatever occupies the destination — the piece already
279
+ standing there, the empty slot waiting — and the copy comes down on THAT box:
280
+ `syncCloneWithDropTarget(pieceAt(toId) || slotOf(toId))`.
342
281
 
343
282
  #### A place that is a surface
344
283
 
345
- A board's place is an element one can point at. A **surface** — a plan drawn over
346
- an aerial photo, a map, a floor — is one box where every point is a place, and
347
- "which element did it come down on" says nothing there. So the detail says
348
- **where**: `x`, `y`, `width`, `height`, the box the copy came down in, measured
349
- inside the place with its border and its scroll taken out. `toId` still names the
350
- surface it came down on, and nothing changes about the rest — the copy travels,
351
- the original stays where it was.
284
+ A **surface** — a plan drawn over an aerial photo, a map, a floor — is one box
285
+ where every point is a place, and "which element did it come down on" says
286
+ nothing there. So the detail says **where**: `x`, `y`, `width`, `height`, the box
287
+ the copy came down in, measured inside the place with its border and its scroll
288
+ taken out. `toId` names the surface.
352
289
 
353
290
  ```jsx
354
291
  <Shape
@@ -363,104 +300,43 @@ the original stays where it was.
363
300
 
364
301
  The size comes along because the anchor is yours: a chip dragged out of a palette
365
302
  is not the shape it becomes, so the middle of what the hand carried is usually the
366
- point that was aimed at.
367
-
368
- `syncCloneWithDropTarget` is the one thing to leave alone here — called with
369
- nothing it takes the whole surface's box, and there is rarely an element to pass
370
- it either, the thing being created by the answer itself. Not calling it leaves the
371
- copy where the hand put it, over the thing appearing there.
303
+ point that was aimed at. Leave `syncCloneWithDropTarget` alone here — called with
304
+ nothing it takes the whole surface's box, and the thing is created by the answer
305
+ itself — so the copy stays where the hand put it, over the thing appearing there.
372
306
 
373
307
  #### Naming what travels
374
308
 
375
- **The copy is already named, and it is the copy that does the visible travel.**
376
- `syncCloneWithDropTarget` moves it onto the destination inside the callback — so
377
- the piece the hand let go of slides to its place whether the application names
378
- anything or not. What is left to name is the OTHER one: the piece that was
379
- standing there and has to go the other way.
380
-
381
- The name is the copy's own, because two copies can be on screen at once — one
382
- still flying away while its `toss` is answered, another landing — and a name
383
- claimed twice aborts the transition. What every copy shares is the class
384
- `navi-drag-clone`, which is how a stylesheet reaches the landing:
385
- `::view-transition-group(.navi-drag-clone)`. A view-transition class is read
386
- from Chrome 125 on; before that the landing still plays, and only a rule written
387
- against the class goes unread. The wrapper carrying the copy paints nothing and
388
- takes neither.
389
-
390
- **And the original is hidden for the whole landing**, not only for the drag: it
391
- wears `navi-drag-clone-source` (`visibility: hidden`) until the promise returned
392
- by `land` settles, because the copy stands for it until then. An element that
393
- paints nothing is still captured — the group gets an empty image — so a name put
394
- on the source is a group fading in from nothing, or out into nothing, over the
395
- copy that is doing the real travel. That is what a swap looks like when it fades
396
- instead of sliding.
397
-
398
- Both follow from one rule: **a name rides the element that MOVES, and that
399
- element has to be visible at both ends of the transition.** Where the places are
400
- fixed and the pieces are drawn into them, nothing moves — two boxes change
401
- content, and hand-writing a name on each so that it "follows the player" names a
402
- journey whose start or end is the hidden source. Key the piece by WHO it is and
403
- let it be re-parented:
404
-
405
- ```jsx
406
- {places.map((place) => {
407
- const playerId = lineupAt(place.id);
408
- // Keyed by the player: the same DOM node walks from one place to the other,
409
- // so the browser has something to morph — and the displaced one was visible
410
- // before and is visible after.
411
- return <Piece key={playerId} id={playerId} style={boxOf(place)} … />;
412
- })}
413
- ```
414
-
415
- **The name is written twice, and neither write is redundant.** The old state is
416
- captured the moment `startViewTransition` is called, before any render, so the
417
- name must be on the DOM by then — written by hand, on the element. The render
418
- happening inside the callback would then put the plain name straight back, so
419
- the same name must also come from state. Clear that state when `finished`
420
- resolves: a name left behind is claimed twice by the next transition, and that
421
- one is dropped for it.
422
-
423
- ```jsx
424
- land: (event) => {
425
- const { fromId, toId, syncCloneWithDropTarget } = event.detail;
426
- const displacedId = playerAt(toId);
427
- const roles = { [fromId]: OVER, [displacedId]: UNDER };
428
- for (const id of Object.keys(roles)) {
429
- document.getElementById(id).style.viewTransitionName = roles[id];
430
- }
431
- const transition = document.startViewTransition(() => {
432
- syncCloneWithDropTarget(document.getElementById(displacedId));
433
- setSwapRoles(roles); // the render inside the callback keeps the names
434
- setLineup((previous) => swapPlaces(previous, fromId, toId));
435
- });
436
- transition.finished.then(() => setSwapRoles(null));
437
- return transition.finished;
438
- };
439
- ```
440
-
441
- The roles are names because a `::view-transition` pseudo can be selected by name
442
- and by nothing else — which is also how one of the two is told to pass over the
443
- other, and how the travel is given a length worth a card crossing a board rather
444
- than a menu opening. Both the group and the image pair are addressed: the morph
445
- lives in one, anything a style adds rides in the other.
446
-
447
- ```css
448
- ::view-transition-group(swap_over) {
449
- z-index: 20;
450
- }
451
- ::view-transition-group(swap_over),
452
- ::view-transition-image-pair(swap_over) {
453
- animation-duration: 420ms;
454
- animation-timing-function: ease-in-out;
455
- }
456
- ```
457
-
458
- The hint follows what a place is: a line drawn in the gap for `reorder`, the
459
- place itself lit up for `land`. Both are drawn among the places — the carried
460
- element's parent, or the `data-drop-container` when there is one — so the
461
- variables dressing them are read from the list, the board or the surface and reach
462
- them by plain inheritance. The copy goes the other way: it is drawn beside the
463
- thing it copies, and dressed by where that thing stands.
309
+ **The copy is already named, and it does the visible travel**:
310
+ `syncCloneWithDropTarget` moves it onto the destination inside the callback. Each
311
+ copy has a name of its own, two being on screen at once while a `toss` is
312
+ answered, and all share the class `navi-drag-clone`, so
313
+ `::view-transition-group(.navi-drag-clone)` styles the landing (read from Chrome
314
+ 125 on; before that only a rule written against the class goes unread).
315
+
316
+ What is left to name is the OTHER piece, the one standing there that goes the
317
+ other way. **A name rides the element that MOVES, and that element has to be
318
+ visible at both ends of the transition.** Never the source: it is
319
+ `visibility: hidden` until the promise `land` returned settles, which still gets
320
+ it captured, as an empty image — a name on it is a group fading in from nothing,
321
+ or out into nothing, over the copy doing the real travel. Where the places are
322
+ fixed and the pieces drawn into them, key each piece by WHO it is, so the same
323
+ node walks from one place to the other.
324
+
325
+ The name is written twice, and neither write is redundant: by hand on the DOM
326
+ before `startViewTransition` is called, because the old state is captured right
327
+ then, and from state, because the render inside the callback would put the plain
328
+ name straight back. Clear that state when `finished` resolves — a name left
329
+ behind is claimed twice by the next transition, and that one is dropped for it.
330
+ The names are roles (the piece passing over, the one passing under) because a
331
+ `::view-transition` pseudo is selected by name and by nothing else; address both
332
+ its group and its image pair — the morph lives in one, anything a style adds
333
+ rides in the other. The padel board in `38_interactions_demo.html` does all of
334
+ it.
335
+
336
+ The drop hint follows what a place is: a line drawn in the gap for `reorder`, the
337
+ place itself lit up for `land`. Both are drawn among the places — the parent, or
338
+ the `data-drop-container` — so the variables dressing them are set on the list,
339
+ the board or the surface and reach them by inheritance:
464
340
 
465
341
  | Variable | Dresses |
466
342
  | ------------------------------------------------------------------------------------------ | ----------------------- |
@@ -471,50 +347,29 @@ thing it copies, and dressed by where that thing stands.
471
347
 
472
348
  ### Saying the grab is acquired: `grab`
473
349
 
474
- The interactions above answer the **release**. Between the press and the release there is
475
- one instant that counts for the hand: the one where the object stops being pressed
476
- and starts being held. `grab` is that instant — the same one whichever way the drag
477
- was entered, a finger held still or a mouse travelled a few pixels.
478
-
479
- ```jsx
480
- <Box
481
- interactions={{
482
- toss: (event) => remove(event.detail.id),
483
- grab: () => navigator.vibrate?.(10),
484
- }}
485
- />
486
- ```
487
-
488
- It matters most where it is least visible. On a screen the held object is under the
489
- thumb that hides it, so the only feedback available is the one that is felt; without
490
- it the hand waits, doubts the press was heard, and lets go too early — the whole
491
- gesture fails, not its decoration. With a mouse the grab is acquired after a few
492
- pixels and the object has visibly moved, so the answer is already there.
493
-
494
- Nothing here is about vibration: a sound, a class, a measure are the same moment.
495
-
496
- If what the moment is for is **paint**, no listener is needed at all: navi puts
497
- `data-grabbed` on the element for as long as the gesture holds it, and a
498
- stylesheet draws from that. `grab` is for what a stylesheet cannot do — the
499
- vibration above, a state kept elsewhere, a counter.
500
-
501
- `grab` **reports, it does not ask**: what it returns is not waited on, and
502
- preventing its event does not call the gesture off. And it is not an interaction on
503
- its own — declared without one of the five above (or `moving`) there is no
504
- gesture for it to be the beginning of, and a dev warning says so, unless the
505
- element declares `pan` or `zoom`, whose moments they then are
506
- ([pan_zoom.md](./pan_zoom.md)). Its detail carries `pointerType` and the
507
- `gestureInfo`.
508
-
509
- A `longpress` needs none of this: it already happens at the moment the hold is
510
- acquired, not at the release.
350
+ The outcomes answer the **release**. Between the press and the release there is
351
+ one instant that counts for the hand — the object stops being pressed and starts
352
+ being held, whichever way the drag was entered — and `grab` is that instant:
353
+ `grab: () => navigator.vibrate?.(10)` beside the outcome. On a screen the held
354
+ object is under the thumb that hides it, so the only feedback available is the
355
+ one that is felt; without it the hand doubts the press was heard and lets go too
356
+ early — the whole gesture fails, not its decoration. For **paint** no listener
357
+ is needed: navi puts `data-grabbed` on the element for as long as the gesture
358
+ holds it. `grab` is for what a stylesheet cannot do — a vibration, a state kept
359
+ elsewhere, a counter.
360
+
361
+ It is not an interaction on its own: declared without an outcome or `moving`
362
+ there is no gesture for it to begin, and a dev warning says so — unless the
363
+ element declares `pan` or `zoom`, whose moment it then is
364
+ ([pan_zoom.md](./pan_zoom.md)). A `longpress` needs none of this: it already
365
+ happens at the moment the hold is acquired.
511
366
 
512
367
  ### Saying the hold is let go: `release`
513
368
 
514
- The mirror of `grab`, and the only interaction of the family always told. Each of
515
- the five above answers ONE meaning, so a release that means none of them — let go
516
- of over nothing, taken away by the system — reaches nobody, and whatever `grab` set
517
- up stays set up with nothing to take it down.
369
+ The mirror of `grab`, and the only interaction of the family always told. Each
370
+ outcome answers ONE meaning, so a release that means none of them — let go of
371
+ over nothing, taken away by the system — reaches nobody, and whatever `grab` set
372
+ up (a counter of what is in the hand) stays set up with nothing to take it down.
518
373
 
519
374
  ```jsx
520
375
  <Chip
@@ -526,28 +381,20 @@ up stays set up with nothing to take it down.
526
381
  />
527
382
  ```
528
383
 
529
- A bank of chips one drags onto a plan is the case: the counter must count what is
530
- in the hand from the moment the copy takes off — « Terrain 4/21 » before it lands —
531
- and something has to say when the hand is empty again, whether the copy landed,
532
- flew off or came home.
533
-
534
- Its detail is `{ id, x, y, outcome }`. `outcome` is which of the five is about to
535
- answer — `"move"`, `"reorder"`, `"land"`, `"toss"`, `"leave"` — or `null` when the
536
- release means none of them. It is told **before** that answer runs, so what the grab
537
- put up comes down at the moment the hand lets go, while still knowing whether
538
- something is on its way.
539
-
540
- Like `grab`, it **reports, it does not ask**, and it is not an interaction on its
541
- own: a moment of a drag needs a drag to happen in, and a dev warning says so.
384
+ `outcome` is which one is about to answer — `"move"`, `"reorder"`, `"land"`,
385
+ `"toss"`, `"leave"` — or `null` when the release means none of them. It is told
386
+ **before** that answer runs, so what the grab put up comes down the moment the
387
+ hand lets go, while still knowing whether something is on its way. Like `grab`,
388
+ it needs a drag to happen in, and a dev warning says so.
542
389
 
543
390
  ### The hand pulls and nothing follows: `refuse`
544
391
 
545
392
  Something that can be carried is not always free to be: a court whose place on the
546
393
  plan is settled, a marker pinned by whoever owns it. Taking the interaction away —
547
- `move: false` — makes the element **deaf** rather than locked. The press is then
548
- nobody's, so whatever it stands on answers it (the surface pans under an object the
549
- hand was aiming at), and the pull is told nothing at all — and a thing that does not
550
- move and says nothing reads as a screen that is broken, so the hand insists.
394
+ `move: false` — makes the element **deaf** rather than locked: the press is
395
+ nobody's, so whatever it stands on answers it (the surface pans under an object
396
+ the hand was aiming at), and the pull is told nothing — a thing that does not move
397
+ and says nothing reads as a broken screen, so the hand insists.
551
398
 
552
399
  ```jsx
553
400
  <Court
@@ -563,44 +410,49 @@ move and says nothing reads as a screen that is broken, so the hand insists.
563
410
  />
564
411
  ```
565
412
 
566
- So the interaction stays declared and says `"refuse"` in place of what it does. The
567
- threshold is the same one (a mouse travelling, a finger holding still, the first
568
- pixel inside a `data-drag-on-contact`), and at the instant the grab would have been
569
- acquired there is none: nothing translates, no copy is made, no release is
570
- answered. `refuse` is that instant, and the one where feedback is expected.
571
-
572
- **It walks no axis, so it takes none.** Everything that reads what a drag source
573
- walks to know what is left for itself — a box that travels, a surface that pans —
574
- steps over an element that is refusing: a swipe starting on a locked row is the
575
- row of slides' swipe, a drag starting on a pinned court pans the plan. A thing
576
- that cannot be taken hold of is exactly the one a hand rests on without thinking,
577
- and a surface with a dead zone the size of an object in the middle of it is wrong
578
- every time. So `move: "refuse"` really is `move: false` plus a word to the hand.
579
-
580
- **The press itself stays the element's**, and is settled there like any other
581
- gesture settles one: the pointer is taken, so a `longpress` declared beside the
582
- drag does not answer a hundred milliseconds later, and the click the release
583
- leaves behind is swallowed. What is locked behaves like what is not, up to the
584
- moment it says no. The exception is a box declaring `pan`/`zoom`, the one thing
585
- that takes a press whole and in every direction: there the surface keeps it, and
586
- the refusal is told without taking anything — no pointer, no click, nothing
587
- prevented.
588
-
589
- One outcome refusing refuses the whole gesture — the five answer one carry, and
590
- something that must not be carried has none of them. Like `grab` and `release`,
591
- `refuse` **reports, it does not ask**, and it is not an interaction on its own.
592
- Its detail is `{ pointerType }` beside the press (`event`), and no `gestureInfo`
593
- because there was no gesture, and what feedback wants to know is which hand asked
594
- — a vibration for a finger, nothing extra for a mouse whose cursor has already
595
- said it.
413
+ So the interaction stays declared and says `"refuse"` in place of what it does.
414
+ The threshold is the same — a mouse travelling, a finger holding still, a mouse's
415
+ few pixels inside a `data-drag-on-contact` — and at the instant the grab would
416
+ have been acquired there is none: nothing translates, no copy is made, no release
417
+ is answered. `refuse` is told then, where feedback is expected, with no
418
+ `gestureInfo` (there was no gesture) and the `pointerType` that says which hand
419
+ asked — a vibration for a finger, nothing extra for a mouse whose cursor has
420
+ already said it. One outcome refusing refuses the whole carry.
421
+
422
+ **It walks no axis, so it takes none.** Whatever reads what a drag source walks
423
+ to know what is left for itself — a box that travels, a surface that pans — steps
424
+ over an element that is refusing: a swipe starting on a locked row is the row of
425
+ slides' swipe, a drag starting on a pinned court pans the plan. A thing that
426
+ cannot be taken hold of is exactly the one a hand rests on without thinking, and
427
+ a surface with a dead zone the size of an object in the middle of it is wrong
428
+ every time. For the axes, `move: "refuse"` is `move: false`; for the press it is
429
+ not.
430
+
431
+ **The press itself stays the element's**, settled there like any gesture settles
432
+ one: the pointer is taken, so a `longpress` declared beside the drag does not
433
+ answer afterwards, and the click the release leaves behind is swallowed. The
434
+ exception is a box declaring `pan`/`zoom`, the one thing that takes a press whole
435
+ and in every direction: there the surface keeps it, and the refusal is told
436
+ without taking anything — no pointer, no click, nothing prevented.
437
+
438
+ ### A press made elsewhere: `--navi-grab`
439
+
440
+ A hold opens a popup, and something in it is what the hand meant to take:
441
+ `triggerNaviCommand(element, "--navi-grab", openEvent)` hands the press still
442
+ down to that element (or the drag source around it), so the finger carries it
443
+ without lifting and pressing again. It brings **a copy only**, to the finger,
444
+ once the popup has settled: `move` and `moving` carry nothing there (a dev
445
+ warning says so), and let go of before it has travelled — the hold only meant to
446
+ open the popup — the copy goes back and nothing is answered. Its interactions
447
+ are chained to that request rather than to a press on the element, so
448
+ `event.detail.event` is the request: reach the `pointerdown` with
449
+ `findEvent(event, "pointerdown")`.
596
450
 
597
451
  ### Dressing the clone
598
452
 
599
- What the pointer carries is a copy, and a copy of a transparent element is
600
- invisible — an element has no background unless something gave it one, and a row
601
- usually gets its own from the list around it, which the copy has left. So the
602
- clone's look is the page's to declare, through the attributes the gesture puts on
603
- it:
453
+ A copy of a transparent element is invisible — a row usually gets its background
454
+ from the list around it, which the copy has left. So the clone's look is the
455
+ page's to declare, through the attributes the gesture puts on it:
604
456
 
605
457
  | Attribute | On |
606
458
  | ------------------------- | ---------------------------------------------------- |
@@ -615,17 +467,18 @@ it:
615
467
  }
616
468
  ```
617
469
 
618
- Reusing the item's own class is the point: the copy is that item, so it is styled
619
- as that item plus whatever being carried changes.
470
+ Reusing the item's own class is the point: the copy is that item, styled as that
471
+ item plus whatever being carried changes.
620
472
 
621
473
  **Which is also the trap, for anything positioned on a board.** The copy is a
622
- deep clone of the element put in a carrier box, so a geometry written in the style
623
- attribute comes with it. navi takes back where it stands (`position`, `inset`,
624
- `margin` and `translate` are reset on the copy) but not its size —
625
- `width: calc(50% - 2 * var(--gap))` then means half of the carrier instead of
626
- half of the board, and the piece is carried at the wrong size. Put what a piece
627
- LOOKS like in a class and leave only which place it is inline (two custom
628
- properties will do), then let it fill its carrier:
474
+ deep clone put in a carrier box, so a geometry written in the style attribute
475
+ comes with it. navi resets where it stands (`position`, `inset`, `margin`,
476
+ `translate`) but not its size: `width: calc(50% - 2 * var(--gap))` then means
477
+ half of the carrier, and the piece is carried at the wrong size. Put what a piece
478
+ LOOKS like in a class, leave only which place it is inline (two custom properties
479
+ will do), and let it fill its carrier — said of what is inside the wrapper, not
480
+ of `[navi-drag-clone]`, which the copy loses as it lands (that is how it drops
481
+ its lift for the transition) while it has to keep its size all the way down:
629
482
 
630
483
  ```css
631
484
  [navi-drag-clone-wrapper] .piece {
@@ -634,24 +487,18 @@ properties will do), then let it fill its carrier:
634
487
  }
635
488
  ```
636
489
 
637
- Said of what is inside the wrapper rather than of `[navi-drag-clone]` itself,
638
- because the copy loses that mark as it lands — that is how it drops its lift for
639
- the transition — and it has to keep its size all the way down. The copy is a real element in the
640
- page, in the top layer, and everything about its look is reachable from CSS —
641
- including these, read off the dragged element so a whole list or a single item can
642
- answer:
490
+ Read off the dragged element, so a whole list or a single item can answer:
643
491
 
644
492
  | Variable | What it changes |
645
493
  | --------------------- | ------------------------------------------------------------- |
646
494
  | `--drag-clone-shadow` | what being lifted casts; `none` for something that flies flat |
647
495
  | `--drag-clone-scale` | how much bigger it gets once picked up; `1` to keep its size |
648
496
 
649
- **What stays behind is the source, not a hole.** The original is never taken out of
650
- the page — it keeps its place in the layout and wears `navi-drag-clone-source`,
651
- which only makes it `visibility: hidden`. So a mark left where the thing was — an
652
- imprint, a dashed outline, the shape a note was pinned on — is drawn ON that
653
- element and not next to it, and its parts have to say `visibility: visible` to come
654
- back from the hidden source:
497
+ **What stays behind is the source, not a hole.** The original keeps its place in
498
+ the layout, only `visibility: hidden`, until the answer settles — it says where
499
+ the thing left from for as long as the question is open. A mark left there (an
500
+ imprint, a dashed outline) is drawn ON that element, and its parts say
501
+ `visibility: visible` to come back from the hidden source:
655
502
 
656
503
  ```css
657
504
  .paper[navi-drag-clone-source]::after {
@@ -664,17 +511,6 @@ back from the hidden source:
664
511
  }
665
512
  ```
666
513
 
667
- It stays until the answer settles, which is what makes it say where the thing left
668
- from for as long as the question is open — and if the answer refuses, the copy comes
669
- back to it.
670
-
671
- `data-drag-axis` says which axes the drag walks, and its default is not the same
672
- for every outcome: `reorder` alone walks the list (`y`; a list that runs sideways
673
- says `data-drag-axis="x"`), while a `move` goes wherever it is put, a `land` wherever the board has
674
- places, a `toss` wherever it was thrown and a `leave` out by whichever edge (`xy`).
675
- `data-drag-delay`,
676
- `data-drag-slop`, `data-drag-threshold` tune when the press becomes a grab.
677
-
678
514
  ### What says a thing can be picked up
679
515
 
680
516
  Almost nothing, on purpose. A **handle** (`data-drag-handle`) exists only to drag,
@@ -686,8 +522,8 @@ text inside cannot be selected either — a finger is another matter, see below)
686
522
 
687
523
  So on a board where a piece is also clickable, the cursor is already spoken for and
688
524
  the affordance has to be said in the piece itself — a grip mark in a corner, a
689
- shadow appearing under the pointer, a handle. It is worth deciding, not defaulting:
690
- a board one may drag on is worth nothing if nobody tries.
525
+ shadow appearing under the pointer, a handle. Decide it rather than default it: a
526
+ board one may drag on is worth nothing if nobody tries.
691
527
 
692
528
  ### The text inside: `user-select: none`
693
529
 
@@ -711,9 +547,9 @@ a stylesheet, and it has to be true before the press:
711
547
 
712
548
  **Most of the time that is what you want.** What can be picked up carries a
713
549
  label rather than a text to read — a token, a row, a card one moves — and the
714
- press is there for the drag: nobody selects the word inside a thing they are
715
- carrying. Keep the selection where the text IS the point (a note one copies from)
716
- and give that one a `data-drag-handle`: the grip drags, the text stays text.
550
+ press is there for the drag. Keep the selection where the text IS the point (a
551
+ note one copies from) and give that one a `data-drag-handle`: the grip drags, the
552
+ text stays text.
717
553
 
718
554
  navi says it on its own in one place only, inside `[data-drag-on-contact]`, where
719
555
  there is no wait left to answer the finger with.
@@ -721,42 +557,49 @@ there is no wait left to answer the finger with.
721
557
  ## A gesture whose product is a value
722
558
 
723
559
  `move`, `reorder`, `land`, `toss` and `leave` answer the same question — where did
724
- the element end up — because all five carry it. A rotation does not: what comes out
725
- of it is an angle, and the handle that produced it stays exactly where it is. Same
726
- for a scale, or a sun dragged around a plan to set the hour.
560
+ the element end up. A rotation does not: what comes out of it is an angle, and
561
+ the handle that produced it stays exactly where it is. Same for a scale, or a sun
562
+ dragged around a plan to set the hour.
727
563
 
728
- What navi names is never the value: it does not know that these pixels are an
564
+ What navi names is never the value — it does not know that these pixels are an
729
565
  angle, that the angle snaps to the neighbouring court, or where the result is
730
- kept. What it names is the **arbitration** — a press that could have been a tap, a
731
- scroll, a page travelling, the surface underneath — and once that is settled it
732
- hands the numbers over. `moving` is that for something carried (the deltas from the
733
- grab, every frame, nothing translated), `pan` and `zoom` for a surface under the
734
- hand ([pan_zoom.md](./pan_zoom.md)). A value gesture written on either of those two shapes is `interactions`
735
- work, and the machinery below is not needed for it.
736
-
737
- What is left to it is a gesture whose SHAPE is none of navi's: it does not begin on
738
- an element that could be carried, or on a surface — it begins wherever the
739
- application says, on its own rules. Then navi hands over the machinery instead and
740
- leaves the paint alone:
566
+ kept — but the **arbitration**: a press that could have been a tap, a scroll, a
567
+ page travelling, the surface underneath. Once that is settled it hands the
568
+ numbers over. **A value computed from how far a handle was taken is `moving`**:
569
+ the deltas from the grab, every frame, nothing translated, and `move` beside it
570
+ when the release commits something. A value read off a surface is `pan` and
571
+ `zoom` ([pan_zoom.md](./pan_zoom.md)). Both are `interactions` work.
572
+
573
+ What is left is a gesture whose SHAPE is none of navi's: it does not begin on an
574
+ element that could be carried, or on a surface — it begins wherever the
575
+ application says, on its own rules. Then navi hands over the machinery and leaves
576
+ the paint alone; the options are in the JSDoc of `dragAfterIntent`,
577
+ `markDragSource` and `createDragGestureController`:
741
578
 
742
579
  ```js
743
- import { createDragGestureController, dragAfterIntent } from "@jsenv/navi";
580
+ import {
581
+ createDragGestureController,
582
+ dragAfterIntent,
583
+ markDragSource,
584
+ } from "@jsenv/navi";
585
+
586
+ // At mount, not in the pointerdown.
587
+ const unmarkDragSource = markDragSource(element, "xy");
744
588
 
745
589
  const onPointerDown = (pointerDownEvent) => {
746
- // Read now: the callback below runs once the intent shows, after dispatch,
747
- // when `currentTarget` is null.
748
- const element = pointerDownEvent.currentTarget;
590
+ // `element`, not `pointerDownEvent.currentTarget`: the callback runs once the
591
+ // intent shows, after dispatch, when `currentTarget` is null.
749
592
  dragAfterIntent(pointerDownEvent, () => {
750
593
  const controller = createDragGestureController({
751
594
  onDrag: (gestureInfo) => {
752
595
  const { xDelta, yDelta } = gestureInfo.layout;
753
- angleSignal.value = snapToNeighbours(angleFromDelta(xDelta, yDelta));
596
+ valueSignal.value = valueFrom(xDelta, yDelta);
754
597
  },
755
598
  onRelease: (gestureInfo) => {
756
599
  if (gestureInfo.cancelled) {
757
600
  return;
758
601
  }
759
- COURT.PUT.run({ angle: angleSignal.peek() });
602
+ commit(valueSignal.peek());
760
603
  },
761
604
  });
762
605
  return controller.grabViaPointer(pointerDownEvent, { element });
@@ -764,40 +607,34 @@ const onPointerDown = (pointerDownEvent) => {
764
607
  };
765
608
  ```
766
609
 
767
- An element that ends up somewhere is NOT this: that one is `move` — or `land` when
768
- what it ends up on is a surface and a copy is what travels — and doing it by hand
769
- gives up the constraint, the commit and the way back when the answer refuses.
770
-
771
- Three things come with the machinery, and they are the reason not to write it again:
772
-
773
- - **When the press becomes a drag** is `dragAfterIntent`, and the answer is not one
774
- policy but three: a `data-drag-handle` exists only to drag, so it takes the
775
- gesture on contact; a mouse resolves it by distance; a finger resolves it by
776
- TIME, because travel is exactly what a scroll looks like. One timer for every
777
- pointer makes the mouse wait for something it never had to prove.
778
- - **A touch has to be refusable before it is refused.** `touch-action` must be
779
- non-`auto` when the finger LANDS: after that the touch is on the compositor's
780
- fast path, every `preventDefault` is an intervention, and on Android the scroll
781
- runs away with the object. `markDragSource(element, axes)` says it from a
782
- stylesheet, on the source itself — which is how what surrounds it keeps
783
- scrolling. A blanket `touch-action: none` on the container buys the same drag by
784
- taking the pan away, and the pan then has to be written by hand too.
785
- - **A capture that goes was not necessarily given back.** `lostpointercapture`
786
- reads the same whether the gesture handed the pointer over or the browser dropped
787
- it mid-drag, which it does more often than the specification suggests. The loop
788
- tells the two apart, and `gestureInfo.cancelled` is where it comes out: a release
789
- nobody asked for must commit nothing.
790
-
791
- A gesture driven this way is outside the registry, so it says so itself to whatever
792
- travels above it: `data-no-drag-travel` (see `docs/drag_to_travel.md`).
610
+ An element that ends up somewhere is NOT this: that one is `move` — or `land`
611
+ when what it ends up on is a surface — and doing it by hand gives up the
612
+ constraint, the commit and the way back when the answer refuses.
613
+
614
+ **A touch has to be refusable before it is refused**, so `markDragSource` goes
615
+ down at mount, on the source itself, and what surrounds it keeps scrolling (the
616
+ two conditions are in
617
+ [mobile_touch.md](./mobile_touch.md#the-browser-decides-about-8px-in-and-does-not-wait)).
618
+ A blanket `touch-action: none` on the container buys the same drag by taking the
619
+ pan away, and the pan then has to be written by hand too. **A capture that goes
620
+ was not necessarily given back**: `lostpointercapture` reads the same whether the
621
+ gesture handed the pointer over or the browser dropped it mid-drag, which it does
622
+ more often than the specification suggests. The loop tells the two apart, and
623
+ `gestureInfo.cancelled` is where it comes out — a release nobody asked for must
624
+ commit nothing.
625
+
626
+ A gesture driven this way is outside the registry, so it says so itself to
627
+ whatever travels above it: `data-no-drag-travel` (see
628
+ [drag_to_travel.md](./drag_to_travel.md#a-navi-component-that-reads-the-pointer-marks-itself)).
793
629
 
794
630
  ## Reference
795
631
 
796
- - `src/control/interaction/interaction_drag.js` — `move`, `reorder`, `land`,
797
- `toss`, `leave` and the `grab`/`release`/`refuse` moments.
798
- - `@jsenv/dom` — `src/interaction/drag/drag_gesture.js` (the loop, its options
799
- and what `gestureInfo` holds) and `src/interaction/drag/drag_after_intent.js`
800
- (when a press becomes a drag, and what a source says in the stylesheet).
632
+ - `src/control/interaction/interaction_drag.js` — the outcomes, `moving`, the
633
+ `grab`/`release`/`refuse` moments and `--navi-grab`.
634
+ - `@jsenv/dom` — `src/interaction/drag/drag_to.js` (`startDragTo`,
635
+ `refuseDragTo`), `drag_gesture.js` (the loop and what `gestureInfo` holds),
636
+ `drag_after_intent.js` (when a press becomes a drag, and what a source says in
637
+ the stylesheet).
801
638
  - `src/control/demos/38_interactions_demo.html` — a board whose places are
802
639
  zones and the same board whose places are the pieces.
803
640
  - `src/control/demos/integration/5_plan_editor_demo.html` — a place that is a