@jsenv/navi 0.29.523 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,7 +66,7 @@ start when unsure which export solves a problem.
66
66
  `SlideContainer` read from the URL, what a layer drawn over the screen may say
67
67
  in its address. Before any routing code.
68
68
  - `route_transitions.md` — a transition states a relation between two pages;
69
- `RouteTravel` or a transition, never both on one pair; fixed bars and
69
+ `RouteTravel` or a transition on one pair; fixed bars and
70
70
  `RouteTransitionArea`; the page being left stays mounted, hidden, until the
71
71
  movement is over; a test waits for the page, never for its address; what a
72
72
  transition costs. Before animating a navigation, and before believing a
@@ -77,8 +77,9 @@ start when unsure which export solves a problem.
77
77
  whole document, what cannot be pointed at while one plays. Before
78
78
  `document.startViewTransition`, and before putting a value on `:root` for a
79
79
  movement.
80
- - `drag_to_travel.md` — a pointer or a wheel pushing a screen aside, and who owns
81
- a press several boxes want. Before putting anything that reads the pointer
80
+ - `drag_to_travel.md` — a hand pushing a screen aside: what a row the browser
81
+ scrolls answers, what a box pushed aside reads, and who owns a press several
82
+ boxes want. Before putting anything that reads the pointer
82
83
  inside a box that travels.
83
84
 
84
85
  ### Data
@@ -410,30 +410,19 @@ right. **A page left out of `routes` does not travel**: one opens the create
410
410
  screen from the list, one does not slide there, so the list is absent and that
411
411
  move plays nothing — leaving a page out is how a movement is refused.
412
412
 
413
- A pair with a movement of its own gets a row of its own, on its own axis. The
414
- game and its edit screen are the same thing seen two ways, so they travel
415
- vertically inside the place the outer row holds for them — editing above
416
- (the column's order is the children's), coming down over the game, sent back up
417
- by saving:
413
+ A pair with a movement of its own takes a route transition rather than a row. The
414
+ game and its edit screen are the same thing seen two ways, so they turn over inside
415
+ the place the outer row holds for them, with `defineRouteTransition`:
418
416
 
419
417
  ```jsx
420
- const GameArea = () => (
421
- <RouteTravel axis="y">
422
- <Route>
423
- <Route route={EDIT_GAME_ROUTE} element={EditGamePage} />
424
- <Route route={GAME_ROUTE} element={GamePage} />
425
- </Route>
426
- </RouteTravel>
427
- );
418
+ defineRouteTransition(GAME_ROUTE, EDIT_GAME_ROUTE, "flip");
428
419
 
429
- // in the outer row: the same element on both branches
420
+ // in the row: the same element on both branches
430
421
  <Route route={EDIT_GAME_ROUTE} element={GameArea} />
431
422
  <Route route={GAME_ROUTE} element={GameArea} />
432
423
  ```
433
424
 
434
- The outer row does not move for it — the edit url is not one of its pages — and
435
- the same element on both branches keeps the inner row mounted, with something
436
- to travel between.
425
+ The row does not move for it: the edit url is not one of its pages.
437
426
 
438
427
  ## The same loop in a dialog
439
428
 
@@ -49,6 +49,13 @@ screen** for `layer="top"` — the visual viewport minus the app's bands (see
49
49
  [`safe_area.md`](./safe_area.md)) — and the positioned ancestor for
50
50
  `layer="local"`.
51
51
 
52
+ A modal dialog covered by another modal dialog is not in use, so it does not
53
+ follow the keyboard: it stays where it is, and the keyboard covers it as it
54
+ covers anything else behind that sheet. Once back on top it answers the screen
55
+ as it is: at once when the focus landed on a field, since the keyboard stays;
56
+ once the keyboard has left otherwise, so it never jumps up and back down. What
57
+ sits under a non-modal dialog is still usable, and keeps following the keyboard.
58
+
52
59
  ## One dialog, two shapes
53
60
 
54
61
  `dockedOnSmallTouchScreen` is the whole small-screen story in one prop: on a
@@ -1,78 +1,93 @@
1
- # Travelling by drag, and who owns the gesture
1
+ # Travelling: a hand, a screen, and who owns the gesture
2
2
 
3
- A travel is a pointer or a wheel pushing a whole screen aside to bring in the
4
- next one: slides inside a box (`SlideContainer`), pages that are URLs
5
- (`RouteTravel`), a docked `Dialog` or a `SidePanel` pushed back the way it came
6
- to close it, a row swiped aside with `interactions`. One module answers what
7
- such a gesture IS — `drag_to_travel.js` in @jsenv/dom — and all of them read
8
- it, so a hand never has to learn two sets of numbers.
3
+ A travel is a hand pushing a whole screen aside to bring in the next one. There
4
+ are two families, and they do not work the same way:
5
+
6
+ - **A row of screens** (`SlideContainer`, `RouteTravel`) is a real scroller with
7
+ one snap point per screen. The browser reads the hand. See
8
+ [The browser scrolls the row](#the-browser-scrolls-the-row).
9
+ - **A box pushed aside** (a docked `Dialog` or a `SidePanel` pushed back to close
10
+ it, a row swiped with `interactions`) has nothing to scroll to, so the press is
11
+ read by one module, `drag_to_travel.js` in @jsenv/dom. See
12
+ [A box pushed aside: the press](#a-box-pushed-aside-the-press).
9
13
 
10
14
  What an application has to know is who gets a press that lands inside such a
11
- box. It is decided at the press, from the DOM, before anything moves — so read
15
+ box. It is decided before anything moves, so read
12
16
  [Who owns a gesture](#who-owns-a-gesture) before putting anything that reads
13
17
  the pointer inside a box that travels.
14
18
 
15
- - [What the rules are](#what-the-rules-are)
19
+ - [The browser scrolls the row](#the-browser-scrolls-the-row)
20
+ - [A box pushed aside: the press](#a-box-pushed-aside-the-press)
16
21
  - [Who owns a gesture](#who-owns-a-gesture)
17
22
  - [Boxes inside boxes](#boxes-inside-boxes)
18
23
  - [Something being carried inside a box](#something-being-carried-inside-a-box)
19
24
  - [A surface in the top layer](#a-surface-in-the-top-layer)
20
25
  - [A navi component that reads the pointer marks ITSELF](#a-navi-component-that-reads-the-pointer-marks-itself)
21
26
  - [What is contained, and what still leaks](#what-is-contained-and-what-still-leaks)
22
- - [The two consumers that travel between screens](#the-two-consumers-that-travel-between-screens)
23
- - [One gesture that bar cannot follow](#one-gesture-that-bar-cannot-follow)
24
-
25
- ## What the rules are
27
+ - [The two rows](#the-two-rows)
28
+
29
+ ## The browser scrolls the row
30
+
31
+ A touch pans on the compositor, a trackpad follows the fingers, a wheel is read
32
+ for what it is. So a row answers a hand the way any scroll snap carousel on the
33
+ web does, and these are its rules, not ours:
34
+
35
+ - **A trackpad or a finger holds the screen.** Let go past half the box, or with
36
+ momentum, it goes; let go short of that with the hand at rest, it comes back.
37
+ - **One notch of a wheel is one screen**, however small the notch. Only the
38
+ browser can tell a notch from a trackpad: the page cannot.
39
+ - **A big swipe can cross more than one screen.** `scroll-snap-stop` stops the
40
+ momentum at the next screen, not a finger still pushing past it. With
41
+ `mount="near"`, a screen reached ahead of its neighbours is built on the way, so
42
+ it can show empty for a moment.
43
+ - **A stroke stays on what it started on.** A vertical scroll cannot change
44
+ screen, whatever it leans towards on the way. The price is the mirror case: a
45
+ stroke that opens on a sideways crumb is the row's, and the vertical scroll it
46
+ turns into does nothing until the hand lifts.
47
+ - **A mouse does not drag**, as it does not scroll anything else on the web.
48
+
49
+ What stays ours:
50
+
51
+ - **A travel asked for** (a tab, a key, a command, a state) plays in `duration`,
52
+ one box long whatever the distance, in `SlideContainer`. In `RouteTravel` it is
53
+ the browser's smooth scroll.
54
+ - **A direction that is refused is the end of the row.** Whatever holds the user
55
+ where they are (a `<Slide required>` waiting for its answer, a `preventNavNext`)
56
+ takes what lies beyond off the row, so nothing can be scrolled to. There is no
57
+ screen walked to and then taken back.
58
+ - **What a box cannot say to a scroller is said per axis.** `travelByDrag` is about
59
+ a finger, `travelByScroll` about a wheel or a trackpad: off on an axis, that hand
60
+ is handed on to what scrolls inside, then around.
61
+
62
+ **`position: sticky` does not stick inside a `RouteTravel` row of two pages or more
63
+ while the document scrolls.** A horizontal scroller cannot let the other axis
64
+ through, so the row is what sticky sticks to, and the row never scrolls vertically.
65
+ A page that scrolls by itself inside the row is not concerned.
66
+
67
+ ## A box pushed aside: the press
26
68
 
27
69
  - A press is not a gesture until it has wandered 10px with a mouse, 6px with a
28
- finger or a pen, and the axis it leans on then is the axis it walks, for good:
29
- a diagonal would ask for two travels at once when only one screen can arrive.
30
- The reading leans towards the box's own axis — a thumb swiping sideways moves
31
- along an arc, and its first pixels lean off-axis far more than the swipe does
32
- — so the cross axis takes the press only when it clearly dominates. Where
33
- something under the finger scrolls on that axis, the page included, the
34
- reading is even, the way the browser's is: a scroll that wobbles stays a
35
- scroll, and a swipe whose first pixels are steeper than 45° scrolls too. A
36
- finger's 6px is a deadline rather than a feel: the browser commits a touch to
37
- its own pan about 8px in, and whatever is read after that is read about a
38
- pointer it has already cancelled (see
70
+ finger or a pen, and the axis it leans on then is the axis it walks, for good.
71
+ The reading leans towards the box's own axis: a thumb swiping sideways moves
72
+ along an arc, and its first pixels lean off-axis far more than the swipe does,
73
+ so the cross axis takes the press only when it clearly dominates. Where
74
+ something under the finger scrolls on that axis, the page included, the reading
75
+ is even, the way the browser's is: a scroll that wobbles stays a scroll, and a
76
+ swipe whose first pixels are steeper than 45° scrolls too. A finger's 6px is a
77
+ deadline rather than a feel: the browser commits a touch to its own pan about
78
+ 8px in, and whatever is read after that is read about a pointer it has already
79
+ cancelled (see
39
80
  [mobile_touch.md](./mobile_touch.md#the-browser-decides-about-8px-in-and-does-not-wait)).
40
81
  Its price is the click of a tap that shook more than 6px.
41
- - **A page change is asked for a little later than the press is taken.** A
42
- `RouteTravel` holds the press at the same 6px, but nothing is asked of the
43
- router until the hand has pulled 10px further, 16px in all with a finger: a
44
- navigation, a render and the requests of the page arriving are not worth a
45
- hand that only shook. Turned around before that, it simply picks the other
46
- neighbour. The picture sets off from where it stands rather than jumping those
47
- pixels, and let go of before that, nothing has moved.
48
- - **Letting go asks where the hand is about to put things.** Where the picture
49
- stands, plus half a second at the speed the fingertip has as it lifts, against
50
- half the box. A flick lands far and goes. A hand thrown back lands short and
51
- everything goes back, otherwise a screen caught in flight and thrown back
52
- still arrives. A hand that slowed to a stop has no speed left, and the picture
53
- answers alone. Speed counts from 25px of finger, a tap that slipped not being
54
- a flick, and past 0.4 px/ms it answers on its own, or half a wide box would be
55
- out of reach of any flick.
82
+ - **Letting go asks where the hand is about to put things.** Where the box stands,
83
+ plus half a second at the speed the fingertip has as it lifts, against half the
84
+ box. A flick lands far and goes. A hand thrown back lands short and everything
85
+ goes back. A hand that slowed to a stop has no speed left, and the picture
86
+ answers alone. Speed counts from 25px of finger, a tap that slipped not being a
87
+ flick, and past 0.4 px/ms it answers on its own, or half a wide box would be out
88
+ of reach of any flick.
56
89
  - Pulling towards nothing follows the finger at a fraction of its distance and
57
90
  comes back: a wall one can lean on, never walk through.
58
- - **A direction that will be refused is one of those walls, and it must be a
59
- wall from the first pixel.** Whatever holds the user where they are — a
60
- `<Slide required>` waiting for its answer, a `preventNavNext` — is read where
61
- the gesture is ARMED, not only when it is let go of. Read at the release
62
- alone, the screen one may not reach is walked to, read on the way, and then
63
- taken back: a wall that shows what is behind it and pushes you back reads as a
64
- bug even when it is a rule. What a lock answers is therefore not "is there a
65
- screen that way" but "may I go there".
66
- - **A hand that walks a whole box and keeps going is asking for the next one**,
67
- and the gesture walks on into it without being let go of.
68
- - **A wheel push moves one screen**, the way an arrow key does — never a
69
- distance. What keeps arriving after the fingers are gone is momentum and is
70
- not counted: a second screen takes a second push, or one that insists.
71
- - **A press on a travel still moving is the next swipe first.** A step the way
72
- it is going asks for the screen after, and the travel goes on there without
73
- ever having stopped; only a step against it, or a finger saying nothing for
74
- about 100ms, catches it where it stands. Let go before either — a tap — and
75
- the travel was never touched.
76
91
 
77
92
  How to drive a gesture without a device, and why the obvious way hides the race
78
93
  that loses swipes on a phone, is
@@ -80,8 +95,10 @@ that loses swipes on a phone, is
80
95
 
81
96
  ## Who owns a gesture
82
97
 
83
- Five things can claim a pointer that landed on a travelling box, and all five
84
- are read at the press, before the box moves:
98
+ A row of screens leaves this to the browser, which hands a touch to the innermost
99
+ scroller that can take it. A box pushed aside reads it itself: five things can
100
+ claim a pointer that landed on it, and all five are read at the press, before the
101
+ box moves:
85
102
 
86
103
  1. **What says so itself.** What the browser already answers the pointer on — a
87
104
  field, a `contenteditable`, a `select`, a range; a dedicated drag handle
@@ -118,20 +135,17 @@ is not pushed back at all. A `SidePanel` is pushed from its whole surface.
118
135
  ### Boxes inside boxes
119
136
 
120
137
  A row of slides inside a page that walks between pages, a carousel inside a
121
- carousel, a `SlideContainer` inside a `RouteTravel`: they all get the same
122
- press, and the innermost is the one the hand is pointing at. So it takes the
123
- gesture on the axis it walks, and the boxes above it are left with whatever axis
124
- it does not — a row swiped sideways inside a column of screens keeps the
125
- sideways gesture, and the column still answers a finger going down. Nothing has
126
- to be declared for this: each box says which axes it travels in the DOM
127
- (`data-travel-by-drag`, `data-travel-by-wheel`), and that is what the boxes above
128
- read.
129
-
130
- Decided at the press, once and for all: from the first pixel the gesture belongs
131
- to whoever asked the browser for the pointer last, which is the outermost box.
132
- So an inner box sitting on its last slide does not hand the gesture over
133
- mid-drag: it leans on its wall, the way it does when it is alone. Travelling the
134
- box around it means starting the gesture outside it.
138
+ carousel, a `SlideContainer` inside a `RouteTravel`: the innermost is the one the
139
+ hand is pointing at. It takes the gesture on the axis it walks, and the boxes
140
+ around it keep whatever axis it does not, so a row swiped sideways inside a column
141
+ of screens keeps the sideways gesture, and the column still answers a finger going
142
+ down.
143
+
144
+ Decided when the stroke starts, once and for all. An inner row sitting on its last
145
+ slide does not hand the gesture over: it leans on its wall, the way it does when
146
+ it is alone. Travelling the box around it means starting the gesture outside it. A
147
+ box pushed aside reads the axes of what is inside it in the DOM
148
+ (`data-travel-by-drag`).
135
149
 
136
150
  When the boxes say ONE place between them — a row of titles in each slide of a
137
151
  column of levels, the address naming a title — they bind one state: see
@@ -199,68 +213,37 @@ than while a finger is down, because a browser decides what a gesture may do
199
213
  when the gesture begins; the bounce is refused only while a gesture runs, so a
200
214
  page that bounces the rest of the time goes on bouncing.
201
215
 
202
- What still leaks, only on Blink (Chrome, Edge), and only under a box that is not
203
- a scroll container — a `SlideContainer` (it cuts with `overflow: clip`), a
204
- `RouteTravel`, a swiped row: a scroller **nobody declared and no tag names** — a
205
- bare `<div style="overflow: auto">`, a widget from elsewhere. Its leftovers
216
+ What still leaks, only on Blink (Chrome, Edge), and only under a box pushed aside,
217
+ which is not a scroll container: a scroller **nobody declared and no tag names** —
218
+ a bare `<div style="overflow: auto">`, a widget from elsewhere. Its leftovers
206
219
  reach the page. Two ways out, per case: give the scroller a `Box` with an
207
220
  `overflow` (it is then declared), or contain it by hand
208
221
  (`overscroll-behavior-x: contain` under a box travelling sideways). The general
209
222
  fix belongs to Blink.
210
223
 
211
- ## The two consumers that travel between screens
212
-
213
- A popup pushed back towards its edge and a swiped row are travels too, the
214
- simple kind: one box, one direction, no neighbour to bring in. The two below
215
- carry screens.
216
-
217
- | | `SlideContainer` | `RouteTravel` |
218
- | ----------------------- | ---------------------------------- | ---------------------------------- |
219
- | what the screens are | `<Slide>`s in one box, all mounted | routes — one mounted, ever |
220
- | what says which is here | a `signal`, `current` / a command | the URL |
221
- | what the finger moves | a translated track | the pictures of a view transition |
222
- | when the picture moves | from the press becoming a gesture | from 10px after that |
223
- | letting go too early | the track comes back | the transition is played backwards |
224
- | what says the order | the layout map | the `<Route>` children, in order |
225
- | what one wheel push is | `move(±1)`, as an arrow key | one travel, as a tab pressed |
226
-
227
- Both expose how far the travel has come, and the way to read it differs because
228
- what draws an indicator differs. `SlideContainer` writes
229
- `--slide-travel-progress` — where the picture stands against the current slide,
230
- in boxes: 0 on it, +1 a whole box before it, -1 one after — on its box, for
224
+ A row of screens is a scroll container, and contains itself.
225
+
226
+ ## The two rows
227
+
228
+ | | `SlideContainer` | `RouteTravel` |
229
+ | ----------------------------- | --------------------------------- | ------------------------------------------- |
230
+ | what the screens are | `<Slide>`s in one box | routes |
231
+ | what says which is here | a `signal`, `current` / a command | the URL |
232
+ | what a screen not on it shows | itself, built or `mount="near"` | itself once visited; before that, see below |
233
+ | what says the order | the layout map | the `<Route>` children, or `routes` |
234
+ | a travel asked for | plays in `duration`, one box long | the browser's smooth scroll |
235
+
236
+ A `RouteTravel` page not visited yet can only be built by the address moving, since
237
+ a route's actions ask only while it matches. So the address moves once the hand has
238
+ really pulled, 16px, and the page fills in under the finger. See
239
+ [navigation.md](./navigation.md#tabs-that-travel-routetravel).
240
+
241
+ Both tell what follows them how far the row has come. `SlideContainer` writes
242
+ `--slide-travel-progress` (where the picture stands against the slide it counts
243
+ from, in boxes: 0 on it, +1 a whole box before it, -1 one after) on its box, for
231
244
  anything drawn inside it, and on every element marked
232
- `data-slide-container-follows={id}` outside it (a tab bar above the box). It is
233
- declared with `@property`, so it interpolates. `RouteTravel` leaves it to the
234
- browser: give the indicator a `view-transition-name` of its own and it is
235
- animated from where it was to where it is, even from outside the box (see
236
- [view_transitions.md](./view_transitions.md#a-name-is-unique-per-document)).
237
-
238
- A travel is not a queue: a tab pressed while a page is arriving does not wait
239
- its turn, and does not start a second travel on top of the first either. The
240
- travel in flight is aimed there instead. Back where it set off from, it is
241
- undone — the same pictures, run backwards; further the same way, it costs
242
- nothing; the other way, the pictures start again from the beginning, and a page
243
- nearly arrived snaps back first.
244
-
245
- A hand that walks a `RouteTravel` page whole and keeps going gets the next page,
246
- but out of the far end there is a short gap: the page it was leaving is gone,
247
- the next travel needs pictures of its own (a navigation, a render, a snapshot),
248
- and over those frames nothing follows the finger before catching up with it.
249
- Walked back out of the start there is none — the pair in hand is already the
250
- right one. A `SlideContainer` has every slide mounted and hands over without a
251
- gap either way.
252
-
253
- ### One gesture that bar cannot follow
254
-
255
- A `RouteTravel` travel that turns around mid-gesture, or is aimed further the
256
- same way while it plays — a tab pressed two pages along, the next swipe landing
257
- on a page still arriving — keeps its pages by pointing the router elsewhere
258
- under the same transition. But everything ELSE the transition carries was
259
- photographed when it began, on its way to a tab nobody is going to anymore, and
260
- the thing itself has already moved on in the live page: one would see two bars.
261
-
262
- So on those gestures the pictures of everything that is not the pages are
263
- dropped, and those things are left where they are, live: the bar jumps to the
264
- tab one is heading for instead of sliding to a tab one is not. A slide would be
265
- nicer, and it is not available — the browser measured both of its ends before
266
- the hand changed its mind, and neither can be asked for again.
245
+ `data-slide-container-follows={id}` outside it, a tab bar above the box. It counts
246
+ from the current slide (`data-slide-current`), or once a hand has carried the row
247
+ past it from `data-slide-travel-base`, towards `data-slide-travel-toward`.
248
+ `RouteTravel` needs nothing: a `<Nav currentIndicator>` draws its bar under the hand
249
+ for any row it holds tabs for.
@@ -179,7 +179,7 @@ nothing to preload — the navigation itself asks.
179
179
 
180
180
  A route element still waiting is what the container rendered — its pending
181
181
  screen, or the fallback of the boundary it suspended into — so a route transition
182
- or a `RouteTravel` takes its picture of **that**, code and data alike. navi does
182
+ takes its picture of **that**, code and data alike. navi does
183
183
  not hold a navigation for a chunk: the honest answer to "a chunk is a hundred
184
184
  milliseconds" is the prefetch above — the code is there before the press — and a
185
185
  skeleton in the page's own shape is what gets photographed when it is not.
@@ -560,6 +560,12 @@ teardown, so a travelling container above it does not take the gesture (see
560
560
  has decided. One gesture cannot mean both. What they leave alone keeps its
561
561
  drag: an element inside marked `data-no-drag-travel` reads the pointer itself.
562
562
  An element declaring a swipe also gets `draggable={false}`.
563
+ - **A popup is in front of the box it was written in, not part of it.** It stays
564
+ a DOM descendant of its trigger, so its events bubble through that box, but a
565
+ hold, a swipe, a counted tap or a `"keyboard:<shortcut>"` pressed in the popup
566
+ is not read by the box around it. A box inside the popup, or the popup itself,
567
+ answers as usual. A plain `onKeyDown` still hears the event: it is the
568
+ browser's, so it asks `event.target` itself if it should not.
563
569
  - **A popup can open while the finger is still down** — a menu under a waiting
564
570
  finger is the native gesture (see
565
571
  [popup_open.md](./popup_open.md#opening-while-the-finger-is-still-down), and
@@ -55,11 +55,11 @@ intent before about 8px**, and measure it as a distance (`Math.hypot`), not per
55
55
  axis: the browser measures its slop as a distance, and a per-axis threshold is
56
56
  up to 1.4 times later on a diagonal. Waiting for more evidence is not
57
57
  available, because whatever is read after the browser's moment is read about
58
- a pointer that has already been cancelled. It is why a travel reads a finger at
59
- 6px, before the browser reads the same pixels evenly: leaning towards its own
58
+ a pointer that has already been cancelled. It is why a box pushed aside reads a
59
+ finger at 6px, before the browser reads the same pixels evenly: leaning towards its own
60
60
  axis where nothing scrolls on the other one, evenly where something does, and
61
61
  waiting for the browser's own pixels when that reading says the pan is the
62
- browser's (see [drag_to_travel.md](./drag_to_travel.md#what-the-rules-are)).
62
+ browser's (see [drag_to_travel.md](./drag_to_travel.md#a-box-pushed-aside-the-press)).
63
63
 
64
64
  Whether a `touchmove` can be refused at all is settled even earlier, when the
65
65
  finger lands: the non-passive listener that will refuse must already be on the
@@ -461,10 +461,11 @@ says once, for the whole row, where the bar that marks the current tab goes:
461
461
  </Nav>
462
462
  ```
463
463
 
464
- The bar glides from one tab to the next when the change between them plays as a
465
- view transition — a `RouteTravel` swipe, a route transition — because `<Nav>`
466
- names it for that movement (`currentIndicatorSlides`, on by default), and only
467
- for a movement between two tabs of its own row: during any other route movement
464
+ A hand on a `RouteTravel` row carries the bar with it, for any row the `<Nav>` holds
465
+ tabs for. It also glides from one tab to the next when the change between them plays
466
+ as a route transition, because `<Nav>` names it for that movement
467
+ (`currentIndicatorSlides`, on by default), and only for a movement between two tabs
468
+ of its own row: during any other route movement
468
469
  the bar leaves or arrives with its row, a named element inside a page that moves
469
470
  standing still (see
470
471
  [view_transitions.md](./view_transitions.md#a-name-is-unique-per-document)).
@@ -527,9 +528,9 @@ by a button, it is a command carrying the fallback:
527
528
 
528
529
  ## Tabs that travel: `RouteTravel`
529
530
 
530
- `<RouteTravel>` wraps the `<Route>` tree of a row of tabs and makes every change
531
- between them a movement — a tab pressed, a key, the back button, and a thumb
532
- dragging the pages:
531
+ `<RouteTravel>` wraps the `<Route>` tree of a row of tabs and makes it a row the
532
+ browser scrolls, with one snap point per page: a tab pressed, the back button, and
533
+ a hand pushing the pages:
533
534
 
534
535
  ```jsx
535
536
  <SectionNav />
@@ -542,41 +543,63 @@ dragging the pages:
542
543
  </RouteTravel>
543
544
  ```
544
545
 
545
- The router still mounts only the branch that matches: the page being left is the
546
- picture the browser keeps of it, and the page arriving mounts during the gesture,
547
- filling in under the finger as its own loading state. The order of the tabs —
548
- "one step that way", which no URL says — is the order the children are written;
549
- the row is on the **first** of its pages that matches, like a `<Route>`; a page
550
- left out of the row does not travel. A swipe **replaces** the current entry, as
551
- `<Link replace>` does for a tab pressed: two gestures towards the same neighbour
552
- must not write two different histories — and a row of tabs is the one place a
553
- replace moves the scroll
554
- ([below](#a-row-of-tabs-where-a-replace-is-an-arrival)). Several `RouteTravel`
555
- boxes may live on one page, and only the one travelling is captured.
556
-
557
- **The URL says where the hand is heading, not where the reader is.** It changes
558
- once the hand has really pulled, and comes back if the page is let go of. So a
559
- counter, a title or a breadcrumb around the box reads
560
- `routeTravelSettledPageSignal` first, or it moves for a pull that goes nowhere:
546
+ A page is built when its route first matches, since a route's actions only ask
547
+ while it does, and stays once built: a hand brings back the page itself, with where
548
+ it was scrolled to. The order of the tabs — "one step that way", which no URL says —
549
+ is the order the children are written; the row is on the **first** of its pages
550
+ that matches, like a `<Route>`; a page left out of the row does not travel. A swipe
551
+ **replaces** the current entry, as `<Link replace>` does for a tab pressed: two
552
+ gestures towards the same neighbour must not write two different histories — and a
553
+ row of tabs is the one place a replace moves the scroll
554
+ ([below](#a-row-of-tabs-where-a-replace-is-an-arrival)).
555
+
556
+ **A window over something longer** says its pages with `routes`, and its children
557
+ are then one page, rendered once, whatever the address:
558
+
559
+ ```jsx
560
+ <RouteTravel routes={ids.map((id) => ({ route: GAME_ROUTE, params: { id } }))}>
561
+ <GamePage />
562
+ </RouteTravel>
563
+ ```
564
+
565
+ That is the form for pages that are one route with other params, the same component
566
+ and so only one of it, and for pages that are not children of the box. What a hand
567
+ leaves behind is a picture of it, a clone, in the pane it stood in. `routes` may be
568
+ given again around the page on screen as the reader moves through the list: it is
569
+ taken once the row has come to rest, and nothing is seen moving for it. With fewer
570
+ than two pages, the box is not a scroller at all.
571
+
572
+ **The box has a height, or the document scrolls.** Pages may scroll themselves in a
573
+ box the page gives a height to. If the document is what scrolls, it goes on doing
574
+ so, and a page brought in stands where the reader is looking; what is lost is
575
+ `position: sticky` inside the row (see
576
+ [drag_to_travel.md](./drag_to_travel.md#the-browser-scrolls-the-row)).
577
+
578
+ **The URL can say where the hand is heading, not where the reader is.** For a page
579
+ that is not there yet, a page of a window or a tab never visited, it changes once
580
+ the hand has really pulled, and comes back if the page is let go of. A tab already
581
+ built waits for the landing. So a counter, a title or a breadcrumb around the box
582
+ reads `routeTravelSettledPageSignal` first, or it moves for a pull that goes
583
+ nowhere:
561
584
 
562
585
  ```jsx
563
586
  const settledPage = routeTravelSettledPageSignal.value;
564
587
  const shownId = settledPage ? settledPage.params.gameId : currentId;
565
588
  ```
566
589
 
567
- It holds the `{ route, params }` the travel set off from for as long as the
568
- pictures move, and is `null` when they stop, the URL being the truth again. A
569
- page walked whole under the hand has arrived. A tab pressed or a wheel pushed
570
- holds nothing, and `null` is all they ever read. One signal for the document, as
571
- there is one transition at a time: on a page with several boxes, `route` says
572
- which row is travelling.
590
+ It holds the `{ route, params }` the hand set off from, and is `null` when the row
591
+ has come to rest, the URL being the truth again. A tab pressed holds nothing, and
592
+ `null` is all it ever reads. One signal for the document: on a page with several
593
+ boxes, `route` says which row it is.
573
594
 
574
595
  `RouteTravel` is for pages that form a ROW the finger can push; pages related
575
596
  pair by pair are animated with `defineRouteTransition`, and a given pair by one
576
597
  of the two, never both (see
577
598
  [route_transitions.md](./route_transitions.md#route-transitions-and-routetravel--one-pair-one-system)).
578
599
  The gesture itself: [drag_to_travel.md](./drag_to_travel.md). Demos:
579
- `src/nav/demos/route_travel/route_travel.html`, `src/nav/demos/tabs/tabs.html`.
600
+ `src/nav/demos/route_travel/route_travel.html`,
601
+ `src/nav/demos/route_travel/route_travel_window.html`,
602
+ `src/nav/demos/tabs/tabs.html`.
580
603
 
581
604
  ## Where a navigation lands: the scroll
582
605
 
@@ -773,7 +796,7 @@ The tabs of a `<RouteTravel>` navigate by replacing, and yet each one is another
773
796
  route, sharing one scrollport — the document — that the tab on screen makes
774
797
  tall: the moment the arriving tab is shorter, the browser clamps, and the
775
798
  reader's place is gone. The row is the only thing that knows this, so on every
776
- travel — a tab pressed, a thumb, a wheel, a travel put back — it gives the
799
+ movement — a tab pressed, a hand, a page let go of and coming back — it gives the
777
800
  arriving tab the offset it was read at, once it is really rendered; opens a tab
778
801
  never read at its **top**, not wherever its neighbour happened to be; and never
779
802
  records the clamp, which, recorded against the arriving tab's url, would destroy
@@ -576,6 +576,13 @@ that must offer a way out that KEEPS what was chosen offers it with a close
576
576
  cross, or by letting the click outside close** — not by teaching Escape to say
577
577
  something else.
578
578
 
579
+ **One Escape closes one popup: the one it was pressed in.** Each popup answers
580
+ Escape itself, and a popup around it never hears the key, so backing out of a
581
+ sheet in a dialog takes two presses. A `popover="manual"` element of your own has
582
+ no Escape to answer with: in a `layer="local"` dialog, the key does nothing while
583
+ the focus is in it. A modal dialog around it still closes, on the browser's own
584
+ close request.
585
+
579
586
  ### What "cancel" actually undoes
580
587
 
581
588
  Cancelling is not itself an undo: it marks the close, and whoever holds a value
@@ -315,14 +315,7 @@ answering: the click is caught at the document and handed to it when it fell
315
315
  inside its rectangle, and a navigation that is the exact way back of the movement
316
316
  playing turns that movement round. Only on furniture that stands still: a control
317
317
  travelling with the pages has a rectangle where it will be, not where it is seen
318
- (navi warns about one inside the area). `RouteTravel` is the opposite case — a
319
- finger is on the box — and leaves the bars live, so its pictures cover what a bar
320
- paints outside its box, except two things photographed on their own, which stand
321
- over the pages: the bar's `shadow`, and a child wearing
322
- `data-navi-fixed-bar-overhang` (a raised button, the notch around it). That child
323
- is deaf for the length of the travel, and the rest of the bar for its first frames,
324
- when a press falls to the document whatever it was aimed at;
325
- `pressableDuringRouteTransition` on a control keeps it answering in both cases.
318
+ (navi warns about one inside the area).
326
319
 
327
320
  A **modal `Dialog` open on a page** is furniture of the same kind: it travels with
328
321
  its page, or holds if both states have it. One the navigation itself opens or
@@ -461,17 +454,14 @@ once in the console, naming both routes.
461
454
 
462
455
  ## Route transitions and `RouteTravel` — one pair, one system
463
456
 
464
- - **`RouteTravel`** is a ROW: a total order of tabs, plus the drag gesture that
465
- walks it. Use it when the pages are genuinely a row the finger should push.
457
+ - **`RouteTravel`** is a ROW: a total order of pages the browser scrolls. Use it
458
+ when the pages are genuinely a row the finger should push.
466
459
  - **`defineRouteTransition`** declares individual relations, with no gesture and
467
460
  no order beyond each pair.
468
461
 
469
- A given PAIR of routes must be animated by one of the two, never both: a travel's
470
- pictures can be under a finger, and a transition starting on top would skip them
471
- mid-slide — and a page one can drag has promised a translation, which a
472
- cross-fade would break. A travel in flight wins and the route transition is
473
- skipped with a console warning: the sign of a misconfiguration to fix, not a
474
- mechanism to rely on.
462
+ A given PAIR of routes takes one of the two: a page one can push sideways has
463
+ promised a translation, which a cross-fade would break. What a hand does on the row
464
+ asks for no movement, and a tab pressed goes the way its `<Link>` says.
475
465
 
476
466
  The two DO live together in one app, on different pairs, including a
477
467
  `<RouteTravel>` nested inside a `<RouteTransitionArea>` — a row of sections the
package/docs/scroll.md CHANGED
@@ -387,7 +387,9 @@ other flush, instead of each arriving already inset.
387
387
 
388
388
  Pass `travelByKeyboard={false}` when the arrow keys belong to the content (a list
389
389
  one walks through, a picker whose slides are steps): otherwise the right arrow
390
- changes screen mid-reading.
390
+ changes screen mid-reading. A popup opened from a slide does not need it: a key
391
+ pressed in the popup never walks the slides behind it (see
392
+ [interactions.md](./interactions.md#things-worth-knowing-before-guessing)).
391
393
 
392
394
  Reference: `src/layout/slide_container.jsx`, and the "One slide much taller
393
395
  than the others" case in `src/layout/demos/8_slide_container_demo.html`.