@jsenv/navi 0.29.387 → 0.29.389

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 (43) hide show
  1. package/README.md +4 -4
  2. package/dist/dev/jsenv_navi.js +556 -392
  3. package/dist/dev/jsenv_navi.js.map +81 -81
  4. package/dist/dev/jsenv_navi_side_effects.js +87 -78
  5. package/dist/dev/jsenv_navi_side_effects.js.map +4 -4
  6. package/dist/jsenv_navi.js +551 -387
  7. package/dist/jsenv_navi.js.map +81 -81
  8. package/dist/jsenv_navi_side_effects.js +87 -78
  9. package/dist/jsenv_navi_side_effects.js.map +4 -4
  10. package/docs/AI_INSTRUCTIONS.md +39 -23
  11. package/docs/actions.md +6 -4
  12. package/docs/autofocus.md +2 -1
  13. package/docs/badge_list.md +5 -3
  14. package/docs/control_group.md +4 -3
  15. package/docs/control_object.md +1 -1
  16. package/docs/control_value.md +8 -9
  17. package/docs/css_architecture.md +16 -14
  18. package/docs/dialog_shape.md +15 -10
  19. package/docs/drag_interactions.md +49 -41
  20. package/docs/drag_to_travel.md +32 -24
  21. package/docs/dynamic_import.md +7 -5
  22. package/docs/error_handling.md +31 -30
  23. package/docs/field_validation.md +35 -6
  24. package/docs/i18n.md +16 -7
  25. package/docs/interactions.md +28 -23
  26. package/docs/list_action.md +8 -5
  27. package/docs/list_refresh.md +8 -1
  28. package/docs/mobile_layout_pitfalls.md +6 -5
  29. package/docs/navigation.md +7 -5
  30. package/docs/network_policy.md +22 -7
  31. package/docs/pan_zoom.md +16 -9
  32. package/docs/popup_backdrop.md +11 -5
  33. package/docs/popup_lift.md +5 -3
  34. package/docs/popup_open.md +39 -28
  35. package/docs/resource.md +31 -20
  36. package/docs/route_transitions.md +13 -10
  37. package/docs/safe_area.md +59 -39
  38. package/docs/scroll.md +11 -9
  39. package/docs/testid.md +10 -1
  40. package/docs/typography.md +2 -3
  41. package/docs/view_transitions.md +9 -9
  42. package/docs/z_index.md +6 -5
  43. package/package.json +2 -2
package/docs/autofocus.md CHANGED
@@ -43,7 +43,8 @@ in order, and stops at the first that leads somewhere focusable:
43
43
  so a field naming itself wins over the surface around it;
44
44
  3. the first focusable element — what one came to do;
45
45
  4. the deepest `autoFocus="last-resort"`, the container itself included;
46
- 5. nothing, and the caller decides what that means.
46
+ 5. nothing: the focus stays where it was, and [when the opening places
47
+ nothing](#when-the-opening-places-nothing) says what follows.
47
48
 
48
49
  Step 1 is why reopening a popup comes back to where the user was, rather than to
49
50
  what the content asks for on a fresh open.
@@ -21,9 +21,11 @@ for, and nothing is set up for a case that cannot happen:
21
21
 
22
22
  ## How badges are counted
23
23
 
24
- A `Badge` inside a `BadgeList` does not render itself: it hands its props to
25
- the list through `BadgeListContext` and renders nothing. Badges register in
26
- tree order, so by the time the list gets to its own content it holds them all
24
+ Once the list has something to decide — a `max`, a `fallback`, `maxLines` — a
25
+ `Badge` inside it does not render itself: it hands its props to the list
26
+ through `BadgeListContext` and renders nothing (a plain list leaves its badges
27
+ to render themselves). Badges register in tree order, so by the time the list
28
+ gets to its own content it holds them all
27
29
  and knows how many there are before deciding what to show — without walking
28
30
  children vnodes, and without rendering a badge it then has to take back.
29
31
 
@@ -31,9 +31,10 @@ Stacked, one setting per row:
31
31
  </Group>
32
32
  ```
33
33
 
34
- `row` (a `Box` prop — its children are laid out as rows, so they stack
35
- vertically) is the only prop that changes the arrangement; everything else is
36
- read off the members themselves.
34
+ `row` (or `vertical`, the same prop — each member is a row, so they stack
35
+ vertically and meet along horizontal seams) is the only prop that changes the
36
+ arrangement; everything else is read off the members themselves. Say it rather
37
+ than `flex="y"`: the seams follow `row`, not the layout.
37
38
 
38
39
  Live examples: `src/control/demos/15_group_demo.html`.
39
40
 
@@ -36,7 +36,7 @@ It is also the brick composite controls are built from:
36
36
  with a value of your own — two wheels that are one number of minutes, three
37
37
  fields that are one date. A group with them takes and hands back a single
38
38
  value, so it can be driven by one `value`/`signal` like any other control. It is
39
- what `SpinTime` is made of, and the same mechanism `InputDuration`, `SpinGroup`
39
+ what `TimeSpin` is made of, and the same mechanism `InputDuration`, `SpinGroup`
40
40
  and `WheelGroup` use for their own members.
41
41
 
42
42
  ## `<Form>`: the shape, plus a send
@@ -115,9 +115,10 @@ is a value being offered to that control. It is not an action, and it is not a
115
115
  signal to write by hand:
116
116
 
117
117
  ```jsx
118
- <ControlGroup id="duration">
119
- <TimeWheel name="duration" signal={durationSignal} />
120
- </ControlGroup>
118
+ <WheelGroup id="duration" signal={durationSignal}>
119
+ <Wheel name="hours">…</Wheel>
120
+ <Wheel name="minutes">…</Wheel>
121
+ </WheelGroup>
121
122
 
122
123
  <Button
123
124
  command="--navi-update"
@@ -217,9 +218,9 @@ They are not competing, they answer two different questions:
217
218
  - `defaultValue` is where the control starts, and where a reset goes back to.
218
219
 
219
220
  ```jsx
220
- // "how many players" is what my account usually answers, unless this game says
221
+ // "which levels" is what my account usually answers, unless this game says
221
222
  // otherwise — no `??` to write, and no first render showing the wrong one
222
- <List selectable signal={gameLevelsSignal} defaultValue={me.levels}>
223
+ <List selectable multiple signal={gameLevelsSignal} defaultValue={me.levels}>
223
224
  ```
224
225
 
225
226
  An emptied signal (`signal.value = undefined`) puts the control back on its
@@ -371,8 +372,8 @@ ignored** — on a leaf control as on a group (a selectable list, a checkbox
371
372
  group) — and navi says so in dev. One owner, whichever half of the binding you
372
373
  look at.
373
374
 
374
- Replacing `value` with `signal` also means dropping the `uiAction` that used to
375
- write the signal by hand — it is exactly what the binding now does. Keep
375
+ Replacing `value` with `signal` also means dropping the `uiAction` that wrote
376
+ the signal by hand — writing it is exactly what the binding does. Keep
376
377
  `uiAction` only for what is not "remember the value": logging, a side effect,
377
378
  something else moving with it.
378
379
 
@@ -418,10 +419,8 @@ Reference: `src/control/picker/preset/spin_time.jsx`,
418
419
  ## See also
419
420
 
420
421
  - [state_binding.md](./state_binding.md) — the same rule beyond controls:
421
-
422
422
  a `SlideContainer`'s area, a popup being open, the position in the app — all
423
423
  bound rather than copied back by a callback
424
-
425
424
  - [form_changed.md](./form_changed.md) — what a form makes of each of these:
426
425
  which fields it counts as already answered, and when it sends nothing
427
426
  - [control_object.md](./control_object.md) — several controls reading as one
@@ -22,7 +22,7 @@
22
22
  Navi components are styled through a combination of CSS custom properties (variables) and scoped CSS rules. The architecture is designed so that:
23
23
 
24
24
  1. **Navi wins by default** — the rules that paint and lay a component out are not placed inside `@layer`, so they beat any layered global style. The exception is deliberate and always says so in a comment: a rule navi is happy to hand back sits in `@layer navi` next to the defaults — see [The exception](#the-exception-a-rule-navi-offers-back).
25
- 2. **Defaults are easy to override** — default values for CSS variables are declared inside `@layer navi`, which has the lowest possible specificity, making them trivially overridable from outside.
25
+ 2. **Defaults are easy to override** — default values for CSS variables are declared inside `@layer navi`, which any unlayered rule on the same element beats whatever its specificity, making them trivially overridable from outside.
26
26
  3. **The preferred override surface is component props** — props translate to inline styles or data attributes, not class names.
27
27
 
28
28
  ---
@@ -131,13 +131,15 @@ element.setAttribute(SWIPE_AXES_ATTRIBUTE, "x");
131
131
 
132
132
  ### Beware when moving a shared sheet out
133
133
 
134
- Splitting a chunk out of a template changes **cascade order**: the extracted
135
- sheet is adopted by the install call, which runs before the component's own
136
- assignment, so it now comes _before_ that sheet instead of after it. That matters only when the two carry
134
+ Splitting a chunk out of a template changes **cascade order**: in the template
135
+ it sits where it was written, while an extracted sheet is adopted by the install
136
+ call, which runs before the component's own assignment — so its rules come
137
+ _before_ the component's sheet. That matters only when the two carry
137
138
  the same selector at the same specificity and in the same layer — check for that
138
139
  before splitting. In navi, `popup_css.js` and `surface_text_css.js` overlap
139
140
  `.navi_dialog` and `.navi_popover` with dialog's and popover's own css, which is
140
- why they are still concatenated.
141
+ why dialog.jsx and popover.jsx interpolate them, and mark those two templates
142
+ `jsenv-css-opaque`.
141
143
 
142
144
  ### A sheet on the far side of a split
143
145
 
@@ -182,15 +184,15 @@ lower it. That is the strongest reason not to write one.
182
184
  @layer navi {
183
185
  /* CSS variable defaults — and, when navi says so, a rule it offers back */
184
186
  .navi_button {
185
- --button-height: 32px;
186
- --button-padding-x: 12px;
187
+ --button-font-size: var(--navi-control-font-size);
188
+ --button-border-radius: var(--navi-control-border-radius);
187
189
  }
188
190
  }
189
191
 
190
192
  /* The rules that paint and lay out — outside any layer */
191
193
  .navi_button {
192
- height: var(--button-height);
193
- padding-inline: var(--button-padding-x);
194
+ font-size: var(--button-font-size);
195
+ border-radius: var(--button-border-radius);
194
196
  }
195
197
  ```
196
198
 
@@ -203,7 +205,7 @@ This means an app can override a Navi default from its own unlayered CSS, withou
203
205
  ```css
204
206
  /* App CSS — no layer needed, automatically wins over @layer navi */
205
207
  .navi_button {
206
- --button-height: 40px;
208
+ --button-font-size: 16px;
207
209
  }
208
210
  ```
209
211
 
@@ -257,10 +259,10 @@ Props are the primary way to customize appearance. They translate to inline `sty
257
259
 
258
260
  ```jsx
259
261
  // Size and color via props
260
- <Button size="l" primary />
262
+ <Button size="l" color="secondary" />
261
263
 
262
264
  // Custom CSS variable via style prop
263
- <Button style={{ "--button-height": "48px" }} />
265
+ <Button style={{ "--button-font-size": "18px" }} />
264
266
  ```
265
267
 
266
268
  #### Variants set defaults, never resolved values
@@ -470,8 +472,8 @@ and a paper could no longer say anything.
470
472
  A dialog lives in the browser's top layer and is calibrated on the viewport, so
471
473
  an app that is a column in a wide window states its own screen once, on `:root`,
472
474
  with `--navi-app-max-width` — and never caps popups through
473
- `--dialog-max-width`, a `--component-*` token the pickers already write
474
- themselves. The whole of it, placement included, is in
475
+ `--dialog-max-width`, a `--component-*` token every dialog resets on itself and
476
+ a `maxWidth` prop writes inline. The whole of it, placement included, is in
475
477
  [safe_area.md](./safe_area.md#an-app-that-is-narrower-than-the-window).
476
478
 
477
479
  ### 3. Direct rule override (avoid unless necessary)
@@ -16,7 +16,8 @@ Where a `Dialog` sits and how big it gets. What happens _inside_ it (scrolling,
16
16
 
17
17
  ## A dialog is sized by its content, never by a `width`
18
18
 
19
- There is no `width` prop, and that is the whole design: a dialog is a surface
19
+ `Dialog` has no `width` prop of its own (one handed to it is `Box`'s, still
20
+ under the ceiling below), and that is the whole design: a dialog is a surface
20
21
  laid over the app, and what it holds is what knows how wide it should be. What
21
22
  a caller states are **bounds** — a floor under a dialog too narrow for its
22
23
  content, a ceiling over one that would sprawl:
@@ -50,8 +51,9 @@ Two consequences worth knowing before writing CSS of your own:
50
51
  (`--navi-app-width`/`--navi-app-height`, which track the visual viewport), so
51
52
  the browser reflows the dialog as the keyboard opens. Nothing to wire.
52
53
  - "the container" is the **app's own screen** for `layer="top"` — the visual
53
- viewport, or the narrower one the app declared with `--navi-app-max-width`
54
- (see [`safe_area.md`](./safe_area.md)) — and the positioned ancestor for
54
+ viewport minus the app's bands, whether `--navi-app-max-width` centers them
55
+ or the app writes `--navi-app-inset-*` itself (see
56
+ [`safe_area.md`](./safe_area.md)) — and the positioned ancestor for
55
57
  `layer="local"`.
56
58
 
57
59
  ## One dialog, two shapes
@@ -185,9 +187,11 @@ its container is also what its size ceiling is computed from. Writing them
185
187
  separately is how a dialog ends up flush on one side and inset on the other.
186
188
 
187
189
  It defaults to a share of whatever holds the dialog (`3appw` for `layer="top"`,
188
- `3cqw` for `layer="local"`) and accepts a spacing token (`"s"`, `"m"`…), a
189
- number of pixels, or a viewport length — `appw`/`apph` being the app's own
190
- screen, `vvw`/`vvh` the visual viewport, which shrinks when the keyboard opens.
190
+ `3cqw` for `layer="local"`) and accepts a number of pixels, a viewport length —
191
+ `appw`/`apph` being the app's own screen, `vvw`/`vvh` the visual viewport, which
192
+ shrinks when the keyboard opens — or a container length (`cqw`/`cqh`). A
193
+ spacing token (`"s"`, `"m"`…) cannot be resolved to pixels here: navi warns
194
+ and places the dialog flush.
191
195
  Pass `0` for something meant to sit flush (a side panel), and note that docking
192
196
  already passes `0` for you.
193
197
 
@@ -241,11 +245,12 @@ exposes both sets of bounds under prefixed names, and they mean exactly what
241
245
  they mean on `Dialog` itself:
242
246
 
243
247
  `dialogMinWidth`, `dialogMinHeight`, `dialogMaxWidth`, `dialogMaxHeight`,
244
- `dialogExpand`, `dialogExpandX`, `dialogExpandY`, `dockedOnSmallTouchScreen`,
245
- `marginWithContainer` — plus `popoverMaxHeight` and `popupWidthFitContent` for
246
- the other shape.
248
+ `dialogExpand`, `dialogExpandX`, `dialogExpandY`, `dialogSizeFromAnchor`,
249
+ `dockedOnSmallTouchScreen`, `marginWithContainer` — plus `popoverMaxHeight` and
250
+ `popupWidthFitContent` for the other shape.
247
251
 
248
- `SplitButton` forwards the same set to the picker it wraps. Anything not in
252
+ `SplitButton` forwards the same set (minus `dialogSizeFromAnchor`) to the picker
253
+ it wraps. Anything not in
249
254
  that set lands on the split button's own box instead — so a prop that seems to
250
255
  do nothing to the menu is worth checking against that list first (see
251
256
  `POPUP_PROP_SET` in `src/control/input/split_button.jsx`).
@@ -6,6 +6,8 @@ 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.
9
11
 
10
12
  - [Carrying something: `move`, `reorder`, `land`, `toss`, `leave`](#carrying-something-move-reorder-land-toss-leave)
11
13
  - [While it is being moved: `moving`](#while-it-is-being-moved-moving)
@@ -29,11 +31,12 @@ differs is the release. One detector reads them all, because it is one press.
29
31
  `toss` and `leave` each **combine** with `reorder` and with `land`: dropped on
30
32
  another item the element changes places, thrown far and fast it is gotten rid of,
31
33
  let go of away from every place it leaves. `leave` combines with `move` as well.
32
- `toss` does **not** combine with `move`, and neither do `reorder` and `land` with
33
- each other — one release cannot mean two of those. Declared together, the
34
- copy-carrying one wins and `move` is never answered — the element itself never
35
- travels, and a release that is not the other outcome means nothing at all (a dev
36
- warning says so).
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.
37
40
 
38
41
  `move` carries the element ITSELF and leaves it where it was put; the others
39
42
  carry a copy and put the original back. That is the same difference said in layout
@@ -50,9 +53,10 @@ place taken by the list.
50
53
  ```
51
54
 
52
55
  `data-drag-free` on the element or a container lets it leave; by default a `move`
53
- stays inside what one can SEE of its container — which requires that container to
54
- be a scroll container at all (`overflow` anything but `visible`), since there is
55
- nothing else for "inside" to mean. A `move` whose answer rejects travels back, because a
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
56
60
  place the application would not accept must not stay on screen as if it had.
57
61
 
58
62
  **Where the position is kept is the answer's.** An element drawn from state —
@@ -202,8 +206,9 @@ back, not a row deleted — a list declares `toss`, a surface declares `leave`.
202
206
  ```
203
207
 
204
208
  `leave` combines with every other outcome. Beside `land` or `reorder`, "outside" is
205
- away from every place. Beside `move` — the element itself travels, and nothing is a
206
- place — it is outside the surface the element stands in: the nearest
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
207
212
  `data-droppable` ancestor, or, without one, what can be seen of the scroll
208
213
  container (a dev warning says which). Either way it is judged on the element's
209
214
  box no longer overlapping it, not on the pointer, which is still well inside the
@@ -220,8 +225,8 @@ frame when a small marker has just left it.
220
225
  </Plan>
221
226
  ```
222
227
 
223
- Its detail is `toss`'s — `{ id, x, y }`, the distance travelled being what an exit
224
- is animated with. Picked up and put straight back down stays a cancel: it has to
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
225
230
  have gone somewhere to be away from anything.
226
231
 
227
232
  What the thing does while the answer is asked follows what was carried. A copy
@@ -283,8 +288,8 @@ place, swap the two, refuse.
283
288
  />
284
289
  ```
285
290
 
286
- `toId` is an element and **never null**: a copy over nothing is a release that
287
- meant nothing, and the interaction does not happen at all.
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.
288
293
 
289
294
  **Which elements are places: those marked `data-droppable`, and only those.**
290
295
  Declaring `land` says an element can be CARRIED, which on a board is a different
@@ -368,11 +373,11 @@ copy where the hand put it, over the thing appearing there.
368
373
  #### Naming what travels
369
374
 
370
375
  **The copy is already named, and it is the copy that does the visible travel.**
371
- The wrapper carrying it answers to `navi-drag-clone-wrapper`, the copy inside it
372
- to `navi-drag-clone`, and `syncCloneWithDropTarget` moves that box onto the
373
- destination inside the callback — so the piece the hand let go of slides to its
374
- place whether the application names anything or not. What is left to name is the
375
- OTHER one: the piece that was standing there and has to go the other way.
376
+ It answers to `navi-drag-clone` (the wrapper carrying it paints nothing and takes
377
+ no name), and `syncCloneWithDropTarget` moves it onto the destination inside the
378
+ callback — so the piece the hand let go of slides to its place whether the
379
+ application names anything or not. What is left to name is the OTHER one: the
380
+ piece that was standing there and has to go the other way.
376
381
 
377
382
  **And the original is hidden for the whole landing**, not only for the drag: it
378
383
  wears `navi-drag-clone-source` (`visibility: hidden`) until the promise returned
@@ -487,9 +492,11 @@ vibration above, a state kept elsewhere, a counter.
487
492
 
488
493
  `grab` **reports, it does not ask**: what it returns is not waited on, and
489
494
  preventing its event does not call the gesture off. And it is not an interaction on
490
- its own — declared without one of the five above there is no gesture for it to be
491
- the beginning of, and a dev warning says so. Its detail carries `pointerType` and
492
- the `gestureInfo`.
495
+ its own — declared without one of the five above (or `moving`) there is no
496
+ gesture for it to be the beginning of, and a dev warning says so, unless the
497
+ element declares `pan` or `zoom`, whose moments they then are
498
+ ([pan_zoom.md](./pan_zoom.md)). Its detail carries `pointerType` and the
499
+ `gestureInfo`.
493
500
 
494
501
  A `longpress` needs none of this: it already happens at the moment the hold is
495
502
  acquired, not at the release.
@@ -574,7 +581,7 @@ prevented.
574
581
  One outcome refusing refuses the whole gesture — the five answer one carry, and
575
582
  something that must not be carried has none of them. Like `grab` and `release`,
576
583
  `refuse` **reports, it does not ask**, and it is not an interaction on its own.
577
- Its detail is `{ pointerType }` and nothing more: there is no `gestureInfo`
584
+ Its detail is `{ pointerType }` beside the press (`event`), and no `gestureInfo`
578
585
  because there was no gesture, and what feedback wants to know is which hand asked
579
586
  — a vibration for a finger, nothing extra for a mouse whose cursor has already
580
587
  said it.
@@ -587,11 +594,11 @@ usually gets its own from the list around it, which the copy has left. So the
587
594
  clone's look is the page's to declare, through the attributes the gesture puts on
588
595
  it:
589
596
 
590
- | Attribute | On |
591
- | ------------------------- | ------------------------------------------------------ |
592
- | `navi-drag-clone` | the copy being carried |
593
- | `navi-drag-clone-wrapper` | what positions it (already shadowed, in the top layer) |
594
- | `navi-drag-clone-source` | the original, still in place (already hidden) |
597
+ | Attribute | On |
598
+ | ------------------------- | ---------------------------------------------------- |
599
+ | `navi-drag-clone` | the copy being carried (already shadowed and scaled) |
600
+ | `navi-drag-clone-wrapper` | what positions it, in the top layer |
601
+ | `navi-drag-clone-source` | the original, still in place (already hidden) |
595
602
 
596
603
  ```css
597
604
  .task[navi-drag-clone] {
@@ -603,19 +610,19 @@ it:
603
610
  Reusing the item's own class is the point: the copy is that item, so it is styled
604
611
  as that item plus whatever being carried changes.
605
612
 
606
- **Which is also the trap, for anything positioned on a board.** The copy is the
607
- same element re-parented into a carrier box, so a geometry written in the style
608
- attribute follows it there — `width: calc(50% - 2 * var(--gap))` then means half
609
- of the copy instead of half of the board, and the piece is carried at the wrong
610
- size. Put what a piece LOOKS like in a class and leave only which place it is
611
- inline (two custom properties will do), then let it fill its carrier:
613
+ **Which is also the trap, for anything positioned on a board.** The copy is a
614
+ deep clone of the element put in a carrier box, so a geometry written in the style
615
+ attribute comes with it. navi takes back where it stands (`position`, `inset`,
616
+ `margin` and `translate` are reset on the copy) but not its size —
617
+ `width: calc(50% - 2 * var(--gap))` then means half of the carrier instead of
618
+ half of the board, and the piece is carried at the wrong size. Put what a piece
619
+ LOOKS like in a class and leave only which place it is inline (two custom
620
+ properties will do), then let it fill its carrier:
612
621
 
613
622
  ```css
614
623
  [navi-drag-clone-wrapper] .piece {
615
- position: static;
616
624
  width: 100%;
617
625
  height: 100%;
618
- translate: none;
619
626
  }
620
627
  ```
621
628
 
@@ -654,8 +661,8 @@ from for as long as the question is open — and if the answer refuses, the copy
654
661
  back to it.
655
662
 
656
663
  `data-drag-axis` says which axes the drag walks, and its default is not the same
657
- for every outcome: `reorder` alone walks the list (`y`, or `x` for a list that runs
658
- sideways), while a `move` goes wherever it is put, a `land` wherever the board has
664
+ for every outcome: `reorder` alone walks the list (`y`; a list that runs sideways
665
+ says `data-drag-axis="x"`), while a `move` goes wherever it is put, a `land` wherever the board has
659
666
  places, a `toss` wherever it was thrown and a `leave` out by whichever edge (`xy`).
660
667
  `data-drag-delay`,
661
668
  `data-drag-slop`, `data-drag-threshold` tune when the press becomes a grab.
@@ -728,6 +735,9 @@ leaves the paint alone:
728
735
  import { createDragGestureController, dragAfterIntent } from "@jsenv/navi";
729
736
 
730
737
  const onPointerDown = (pointerDownEvent) => {
738
+ // Read now: the callback below runs once the intent shows, after dispatch,
739
+ // when `currentTarget` is null.
740
+ const element = pointerDownEvent.currentTarget;
731
741
  dragAfterIntent(pointerDownEvent, () => {
732
742
  const controller = createDragGestureController({
733
743
  onDrag: (gestureInfo) => {
@@ -741,9 +751,7 @@ const onPointerDown = (pointerDownEvent) => {
741
751
  COURT.PUT.run({ angle: angleSignal.peek() });
742
752
  },
743
753
  });
744
- return controller.grabViaPointer(pointerDownEvent, {
745
- element: pointerDownEvent.currentTarget,
746
- });
754
+ return controller.grabViaPointer(pointerDownEvent, { element });
747
755
  });
748
756
  };
749
757
  ```
@@ -192,12 +192,13 @@ laptop a horizontal two-finger swipe IS the back-navigation gesture, and a
192
192
  region that neither takes it nor lets it go is the worst of the three — the page
193
193
  rocks and nothing happens.
194
194
 
195
- Not the same thing as the drag gesture it sits beside
196
- ([drag_gesture.js](../../dom/src/interaction/drag/drag_gesture.js)): that one is
197
- for **carrying an object** across the page — it lays a backdrop over the
198
- document, makes everything else `inert`, takes the focus and blocks the scroll
199
- keys. Here nothing is picked up and the page must keep its focus and its
200
- scrolling while a screen slides. Same word, other gesture.
195
+ Not the same thing as **carrying an object** across the page, although both read
196
+ the pointer through the same loop
197
+ ([drag_gesture.js](../../dom/src/interaction/drag/drag_gesture.js)): a carry has
198
+ it lay a backdrop over the document, make everything else `inert`, take the
199
+ focus and block the scroll keys. Here nothing is picked up and the page must keep
200
+ its focus and its scrolling while a screen slides, so the travel asks it for none
201
+ of that (`documentInteractions: "manual"`). Same loop, other gesture.
201
202
 
202
203
  ## Who owns a gesture
203
204
 
@@ -324,7 +325,9 @@ page moved a little, and it looked wrong":
324
325
  rather than the thing that does the work;
325
326
  - **the selection** a drag paints across the text it crosses: `user-select:
326
327
  none`, but only once the press has become a travel — a press on text IS how
327
- one selects it, and nothing has said otherwise yet.
328
+ one selects it, and nothing has said otherwise yet. What its first pixels
329
+ selected is collapsed at that same moment: it was the beginning of the
330
+ travel, not a selection.
328
331
 
329
332
  Both are written by the gesture itself (`data-drag-travel-gesture` and
330
333
  `data-drag-travel-walking` on `:root`), so a page that bounces the rest of the
@@ -333,8 +336,9 @@ the browser for what those two properties do not cover.
333
336
 
334
337
  ### What is contained, and what still leaks
335
338
 
336
- Containing is only ever read on a **scroll container** — an element that clips,
337
- in the browser's sense, whether or not it has anything to scroll. Three places
339
+ Containing is only ever read on a **scroll container** — an element whose
340
+ `overflow` is `hidden`, `auto` or `scroll`, whether or not it has anything to
341
+ scroll (`clip` cuts without making one). Three places
338
342
  could carry it: the travelling box, everything inside it, or the scrollers
339
343
  themselves. Which of the three works is an engine question, and the answer
340
344
  splits in two:
@@ -365,12 +369,14 @@ them is that an empty `textarea` is contained too — a browser cannot be asked
365
369
  "only if it scrolls" — so on Blink a wheel over one moves nothing rather than
366
370
  the list around it.
367
371
 
368
- On Blink this leaves the boxes that **do not clip**, since those are never
369
- asked. Of navi's own: `SlideContainer` clips (`overflow: hidden`) and a
370
- `SidePanel` is a `Dialog`, which scrolls (`overflow: auto`) — both are asked. A
371
- `RouteTravel` box does not clip and must not be made to: a scroll container
372
- there would become the nearest one for every `position: sticky` inside the pages
373
- it holds. A row marked swipeable by `interactions` does not clip either.
372
+ On Blink this leaves the boxes that are **not scroll containers**, since those
373
+ are never asked. Of navi's own: a `SidePanel` is a `Dialog`, which scrolls
374
+ (`overflow: auto`), and is asked. A `SlideContainer` cuts with `overflow: clip`,
375
+ which is not a scroll container (`hidden` would be one, and would let the slides
376
+ off stage be scrolled to), so it is not asked. A `RouteTravel` box does not clip
377
+ at all and must not be made a scroll container: one there would become the
378
+ nearest one for every `position: sticky` inside the pages it holds. A row marked
379
+ swipeable by `interactions` is not one either.
374
380
 
375
381
  Which is why navi contains what it KNOWS scrolls, in `box.jsx`:
376
382
 
@@ -386,7 +392,8 @@ does, and it puts the containment exactly where every engine reads it. A list, a
386
392
  dialog body, a scrolling panel inside a travelling page are covered on Blink
387
393
  again.
388
394
 
389
- What still leaks, and only on Blink, and only under a box that does not clip: a
395
+ What still leaks, and only on Blink, and only under a box that is not a scroll
396
+ container: a
390
397
  scroller **nobody declared and no tag names** — a bare
391
398
  `<div style="overflow: auto">`, a widget from elsewhere. Its leftovers reach the
392
399
  page, which is the old symptom in a much smaller corner. Two ways out, per case:
@@ -447,10 +454,10 @@ The two ends cost differently, and it is worth knowing which one is being felt:
447
454
  and there is no gap at all.
448
455
 
449
456
  What the browser will not turn around with it is everything ELSE the
450
- transition carries — see "One gesture that bar cannot follow" at the end of
451
- this file.
457
+ transition carries — see [One gesture that bar cannot
458
+ follow](#one-gesture-that-bar-cannot-follow).
452
459
 
453
- Two things a travel in hand must never lose:
460
+ What a travel in hand must never lose:
454
461
 
455
462
  - **a travel being undone is not up for grabs.** Its end is already decided;
456
463
  held again mid-revert, its animations never finish, the wait for them never
@@ -546,13 +553,13 @@ travel had started finishes without anyone. It is invisible with a mouse, which
546
553
  is why it survives a whole session of desktop testing.
547
554
 
548
555
  So a travel that has become ours refuses the `touchmove` (`preventDefault`), and
549
- only then — a finger that means to scroll must still scroll. Two details make it
550
- hold:
556
+ only then — a finger that means to scroll must still scroll. Three details make
557
+ it hold:
551
558
 
552
- - the listener sits on the element the touch LANDED on as well as on the box: a
559
+ - the listener sits on the element the touch LANDED on as well as on the window: a
553
560
  touch keeps being dispatched at the node it started on, and a travel may
554
561
  replace the DOM under the finger (a page that travels navigates), after which
555
- that node no longer passes through the box on its way up;
562
+ that node no longer passes through the window on its way anywhere;
556
563
  - the pointer is captured on the BOX rather than on what the finger landed on,
557
564
  for the same reason: what the caller does may take that target away, and a
558
565
  capture whose element leaves the document is a capture the browser drops;
@@ -659,7 +666,8 @@ aimed decides what that costs:
659
666
  at half of its time a travel has covered ~80% of its distance, and rewound
660
667
  at `-1` the visible way home collapses into the steep end of the curve — a
661
668
  snap, not a return. The pictures walk home over how far they visibly are
662
- from home, at the travel's own pace (`revertWalkTime`);
669
+ from home, at the travel's own pace (`walkPicturesHome` in
670
+ `src/transition/view_transition_revert.js`);
663
671
  - both of the above run straight into the compositor traps: the distance is
664
672
  computed from the clock through the easing curve (the pseudo-elements'
665
673
  animated position cannot be read), and the rate is handed over with
@@ -126,9 +126,9 @@ what asks, not to the render.**
126
126
 
127
127
  It starts one render late, and per instance: a component fetched this way
128
128
  in three places asks three times (the browser fetches the module once), and
129
- coming back to it suspends for the microtask the import takes to answer
130
- from the module map. Reach for it when there is genuinely no state to read
131
- the request off — not to save declaring one.
129
+ coming back to it draws its wait again for the microtask the import takes to
130
+ answer from the module map. Reach for it when there is genuinely no state to
131
+ read the request off — not to save declaring one.
132
132
 
133
133
  Delegating instead (the hook without `loading: true`, under a `<Loading>`
134
134
  around the frame) follows the rule for data: **the `<Loading>` goes where the
@@ -210,8 +210,10 @@ The page or the component reading its code says it where it stands, as
210
210
  above. A failure left to a boundary — a page reading its code without
211
211
  `error: true` — reaches it like a failed read does; a boundary that shows only
212
212
  what came from the network and rethrows the rest as a bug recognises an import
213
- by its action — `error.action` is the route action the app declared for the
214
- code — rather than by the browser's message.
213
+ by its action rather than by the browser's message. `error.action` is the
214
+ instance that ran, not the route action itself (a route action hands out the
215
+ instance for its params), and every instance bound from one action carries its
216
+ `callback`: `error.action.callback === GAME_PAGE_CODE.callback`.
215
217
 
216
218
  ## Preact's `lazy()` is not this
217
219