@jsenv/navi 0.29.65 → 0.29.67

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.
@@ -159,6 +159,12 @@ consistency across the app, not from any single call site.
159
159
  it.
160
160
  - `docs/MOBILE_LAYOUT_PITFALLS.md` — mobile-specific layout gotchas (viewport
161
161
  units, virtual keyboard, safe areas).
162
+ - `docs/route_transitions.md` — how pages move against each other on
163
+ navigation (`defineRouteTransition`): a transition states a relation the
164
+ user reads as a map, which movement fits which relation, when a global
165
+ default is right, marking the page area between fixed bars, and why a pair
166
+ of routes is animated by `RouteTravel` or by a route transition but never
167
+ both. Read it before animating any navigation.
162
168
  - `docs/navigation.md` — how to build navigation: declaring routes
163
169
  (`route()` / `setupRoutes()`), when a section is a route of its own rather
164
170
  than a param, search params bound to signals, rendering with `<Route>`,
@@ -288,6 +288,12 @@ Demo: [../src/nav/demos/route_travel/route_travel.html](../src/nav/demos/route_t
288
288
  and [../src/nav/demos/tabs/tabs.html](../src/nav/demos/tabs/tabs.html). The full
289
289
  spec of the gesture is [drag_to_travel.md](./drag_to_travel.md).
290
290
 
291
+ `RouteTravel` is for pages that form a ROW the finger can push. Pages related
292
+ pair by pair without being a row — a game opened from several places, a
293
+ settings page rising over whatever showed it — are animated with
294
+ `defineRouteTransition` instead, and a given pair must be animated by one of
295
+ the two, never both: see [route_transitions.md](./route_transitions.md).
296
+
291
297
  ## Creating something, then editing it
292
298
 
293
299
  The create screen, the page of what was created, the edit screen — three routes,
@@ -0,0 +1,187 @@
1
+ # Route transitions
2
+
3
+ How pages of an app move against each other when the user navigates —
4
+ `defineRouteTransition`, `defineRouteDefaultTransition`, and the thinking that
5
+ decides which movement (if any) a navigation deserves. The API grammar itself
6
+ (accepted forms, shipped type names) lives in the JSDoc of
7
+ `defineRouteTransition`; this file holds what a signature cannot say.
8
+
9
+ Demos: [the movements](../src/nav/demos/route_transition/route_transition.html),
10
+ [a default transition](../src/nav/demos/route_transition/route_transition_default.html),
11
+ [pages between fixed bars](../src/nav/demos/route_transition/route_transition_fixed_bars.html),
12
+ [with a RouteTravel inside](../src/nav/demos/route_transition/route_transition_with_travel.html)
13
+
14
+ ## What a transition is for
15
+
16
+ A transition is not decoration: it states a **relation** between two pages, and
17
+ the user reads it as a map. A page sliding in from the right says "this place is
18
+ deeper, the way back is to the left" — which is why the back arrow then feels
19
+ inevitable rather than learned. A movement that states a relation the app does
20
+ not actually have (a slide between two sibling tabs) teaches a false map, and a
21
+ false map is worse than no animation at all.
22
+
23
+ So the unit of declaration is the pair, not the app:
24
+
25
+ ```js
26
+ defineRouteTransition(MY_GAMES_PAGE, GAME_PAGE, "slide-x");
27
+ defineRouteTransition(RADAR_PAGE, GAME_PAGE, "slide-x");
28
+ ```
29
+
30
+ and two pages never written in the same relation play **nothing** between each
31
+ other. That silence is a statement too: "Mes parties" and "Radars" are two tabs
32
+ of a bottom bar, side by side, neither before the other — a cut is the honest
33
+ rendering of that fact. Resist the urge to fill every navigation with movement;
34
+ declare the relations that exist and let the rest cut.
35
+
36
+ ## Choosing a movement
37
+
38
+ - **`slide-x`** — going INTO something: a list item opened, a card followed, a
39
+ notification tapped. The page is deeper on the same plane; leaving it slides
40
+ back out. The most common relation in an app, and the one every phone has
41
+ taught.
42
+ - **`slide-y`** — the same relation on a vertical arrangement, when the layout
43
+ genuinely reads as a column.
44
+ - **`cover-x` / `cover-y`** — a page that INTERRUPTS rather than continues:
45
+ settings, a composer, anything modal-like that one returns from to find the
46
+ page beneath unchanged. The covered page holding still is the point — it
47
+ promises "you are not leaving, this is on top".
48
+ - **`zoom`** — a detail brought closer: a photo, a card expanded into a page.
49
+ - **`cross-fade`** — a soft change with no spatial claim. Use it where a cut
50
+ feels harsh but no direction would be true.
51
+ - **`none`** — silence, written down. Needed only to override: one way of a
52
+ pair, or the default.
53
+
54
+ Two recommendations that matter more than the individual choices:
55
+
56
+ - **One movement per KIND of relation, app-wide.** If opening a game slides
57
+ from the right, opening a profile should too — the user learns one grammar,
58
+ not one rule per page.
59
+ - **Keep reciprocity.** The way back being the same movement reversed is what
60
+ makes the map hold together; it is the default, and breaking it (a relation
61
+ written for the exact way travelled wins over being the reverse of another)
62
+ should answer a real asymmetry in the app, not a styling whim.
63
+
64
+ ## A default transition — when
65
+
66
+ `defineRouteDefaultTransition("cross-fade")` plays on every navigation no
67
+ relation was written for. Two situations, two answers:
68
+
69
+ - **App-shaped UI** (bars, tabs, pages one goes into): don't. The silence
70
+ between sibling tabs is part of the grammar, and a default erases it. Declare
71
+ the relations by hand.
72
+ - **Content-shaped site** (documents, articles, browsing): a global cross-fade
73
+ can be right — every navigation is a soft change of subject and no pair
74
+ deserves a direction. This is the case the export exists for.
75
+
76
+ A default has no direction (nothing says which of two arbitrary pages is
77
+ "before" the other), so only directionless movements make sense there. Written
78
+ relations, and `"none"`, always win over it.
79
+
80
+ ## Pages between fixed bars: the transition area
81
+
82
+ By default the movement plays on the document itself — right when pages are the
83
+ whole viewport. With fixed bars it is not: the root snapshot spans the viewport
84
+ and the bars' regions are blank in it, so the moving picture drags a blank band
85
+ across the screen where they stand. Wrap the pages instead:
86
+
87
+ ```jsx
88
+ <RouteTransitionArea className="app">
89
+ <Route>…</Route>
90
+ </RouteTransitionArea>
91
+ ```
92
+
93
+ The movement then plays on that region's own pictures, and the bars never move
94
+ — without being named one by one. An app with fixed bars should consider this
95
+ part of declaring transitions at all, not an option.
96
+
97
+ The pages are cut twice: at the area's own bounds, and at the app's **safe
98
+ area** (see [safe_area.md](./safe_area.md)). The second cut is not a detail. A
99
+ fixed bar is fixed to the window while the area is a long box in the document —
100
+ the room the bar gives back is padding, so the content runs under it by design.
101
+ The pictures are drawn in the top layer, above everything the document can
102
+ clip, so without that cut a page taller than the screen, or a scrolled one,
103
+ would be watched sliding over the bars for the whole movement. The band is read
104
+ from the safe area rather than from the bars, so every kind of furniture is
105
+ covered at once and one that unmounts mid-movement is followed without anything
106
+ being told.
107
+
108
+ For the same reason, how far a page travels is the **window** it is seen
109
+ through, not the page's own size: a page is as tall as its content, and a
110
+ vertical movement measured on the picture would send it several screens away —
111
+ off screen for most of the transition, flying past at the end.
112
+
113
+ With an area marked, the page around it is left LIVE rather than photographed:
114
+ the bars keep answering the pointer for the whole movement, which a captured
115
+ element cannot do. The flip side is that anything around the area which must
116
+ _animate_ rather than stand still — a title that changes with the route — needs
117
+ a `view-transition-name` of its own; named, the browser moves it on the same
118
+ clock as the pages.
119
+
120
+ It is a `Box`, so the layout the pages need is written on it directly (`flex`,
121
+ `className`, `style`, …). An app that already has an element holding its pages
122
+ can mark that one with `data-navi-route-transition-area` rather than nesting
123
+ another — the component does exactly that.
124
+
125
+ **The area is a real box, and it has to be**: what gets photographed and
126
+ clipped IS its rectangle. So `display: contents` cannot be used on it — an
127
+ element with no box is never captured, and the movement then plays on nothing
128
+ (the browser also aborts the transition). This is measured behaviour, not a
129
+ precaution.
130
+
131
+ Three misconfigurations are silent enough to be worth a console warning, each
132
+ said once: an area that was not captured (the case above), several elements
133
+ marked at once (they would share one `view-transition-name`, and the browser
134
+ then refuses **every** view transition of the document), and pages travelling
135
+ on the document while something else is captured on its own — the blank band.
136
+ A warning here is a bug to fix, not a mechanism to lean on.
137
+
138
+ ## Custom movements
139
+
140
+ A type navi does not ship belongs to the application: the name is written on
141
+ the root for the length of the transition
142
+ (`data-navi-route-transition-type="<type>"`, next to
143
+ `data-navi-route-transition="forward"|"back"`), and the app's CSS defines the
144
+ movement against the view transition pseudo-elements — of the document, or of
145
+ the marked area. See the JSDoc of `defineRouteTransition` for the selector
146
+ shape, and the `spin` type in the demo for a working one.
147
+
148
+ ## Route transitions and `RouteTravel` — one pair, one system
149
+
150
+ `RouteTravel` and `defineRouteTransition` answer different questions:
151
+
152
+ - **`RouteTravel`** is a ROW: a total order of tabs, plus the drag gesture that
153
+ walks it. Use it when the pages are genuinely a row the finger should push.
154
+ - **`defineRouteTransition`** declares individual relations, with no gesture
155
+ and no order beyond each pair.
156
+
157
+ A given PAIR of routes must be animated by one of the two, never both: a
158
+ travel's pictures can be under a finger, and a transition starting on top would
159
+ skip them mid-slide — and a page one can drag has promised a translation, which
160
+ a cross-fade would break. The runtime enforces the priority (a travel in flight
161
+ wins; the route transition is skipped with a console warning); the warning is
162
+ the sign of a misconfiguration to fix, not a mechanism to rely on.
163
+
164
+ The two DO live together in one app, on different pairs, including a
165
+ `<RouteTravel>` nested inside a `<RouteTransitionArea>` — a row of sections the
166
+ thumb pushes, inside pages one goes into. Write the relations on the routes the
167
+ row does not own: the tabs of the row travel, and opening something from any of
168
+ them plays its own movement. A relation written on a bare route covers every
169
+ one of its params at once, which is what makes "from any section" one line
170
+ rather than one per tab. Demo:
171
+ [../src/nav/demos/route_transition/route_transition_with_travel.html](../src/nav/demos/route_transition/route_transition_with_travel.html)
172
+
173
+ One trap that belongs to `RouteTravel` rather than to transitions, but bites
174
+ here first: the travelling box must stay MOUNTED across the changes it
175
+ animates. A `<RouteTravel>` rendered inside the `element` of each of the routes
176
+ it travels between is destroyed mid-travel by the router. Give the row a single
177
+ branch — its tabs as params of one route is the usual shape.
178
+
179
+ ## The rest, briefly
180
+
181
+ - Pace: `--navi-route-transition-duration` (CSS, default 300ms) for everyone;
182
+ a per-relation `{ type, duration }` for one relation.
183
+ - The URL leads: transitions play on navigations somebody else started (a
184
+ `<Link>`, the back button, `history.back()`). Nothing here navigates.
185
+ - A browser without view transitions (Firefox) navigates with a cut. The app
186
+ must remain fully usable that way — which it is, if the transitions state
187
+ relations rather than carry information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.65",
3
+ "version": "0.29.67",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {