@jsenv/navi 0.29.66 → 0.29.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +692 -253
- package/dist/jsenv_navi.js.map +92 -55
- package/docs/AI_INSTRUCTIONS.md +8 -4
- package/docs/list_refresh.md +33 -2
- package/docs/navigation.md +26 -0
- package/docs/route_transitions.md +101 -13
- package/package.json +1 -1
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -72,8 +72,11 @@ consistency across the app, not from any single call site.
|
|
|
72
72
|
what updates from a response without any request, `rerunOn` and its defaults,
|
|
73
73
|
and how a paginated `<List.Items>` re-reads its slices without disappearing,
|
|
74
74
|
including on its way back from a screen that unmounted it.
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
It also says who decides a re-read on the way back — the source the list
|
|
76
|
+
reads through, never the navigation and never a route transition.
|
|
77
|
+
Read it before adding verbs to `rerunOn`, hiding a list on `loading`,
|
|
78
|
+
remounting a list with a `key` to refresh it, or reporting that a page
|
|
79
|
+
stopped refreshing since a transition was defined on its pair.
|
|
77
80
|
- `docs/error_handling.md` — the two kinds of error and how navi keeps them
|
|
78
81
|
apart: where an error is shown depending on where it came from (a control's
|
|
79
82
|
action shows it on what was clicked, a route action replaces the page, a
|
|
@@ -168,8 +171,9 @@ consistency across the app, not from any single call site.
|
|
|
168
171
|
- `docs/navigation.md` — how to build navigation: declaring routes
|
|
169
172
|
(`route()` / `setupRoutes()`), when a section is a route of its own rather
|
|
170
173
|
than a param, search params bound to signals, rendering with `<Route>`,
|
|
171
|
-
tab rows (`Nav` / `Link` / `RouteTravel`),
|
|
172
|
-
|
|
174
|
+
tab rows (`Nav` / `Link` / `RouteTravel`), where a navigation lands
|
|
175
|
+
(scroll: a push arrives at the top, a back or forward lands where the page
|
|
176
|
+
was left), and the few cases where tabs are legitimately not URLs. Read it before writing any routing code — the
|
|
173
177
|
position of the user belongs in the URL by default, and that decision is
|
|
174
178
|
not retrofittable.
|
|
175
179
|
- Source code and demos on GitHub:
|
package/docs/list_refresh.md
CHANGED
|
@@ -132,8 +132,9 @@ stale copy of anything.
|
|
|
132
132
|
A run that finds a composition takes the `refreshing` line of the table above
|
|
133
133
|
rather than the loading one: the rows are on screen while it asks again for the
|
|
134
134
|
window it draws. So the two lists an app cannot tell apart from the outside —
|
|
135
|
-
one reading `GET_MANY`, one reading `GET_RANGE` —
|
|
136
|
-
|
|
135
|
+
one reading `GET_MANY`, one reading `GET_RANGE` — look the same on the way back:
|
|
136
|
+
neither blanks, neither shows a first load. What they do behind that is not the
|
|
137
|
+
same, and the next section is about exactly that.
|
|
137
138
|
|
|
138
139
|
What a composition is about is the **values** its params hold, not the reader
|
|
139
140
|
instance: `GET_RANGE.bindParams({ scope: "thread" })` called from two places
|
|
@@ -145,6 +146,36 @@ The rest follows the rules already stated: a verb in `rerunOn.GET_RANGE`, or
|
|
|
145
146
|
gone — and `memoryBudget` (1000 ranks by default) trims the ranks far from any
|
|
146
147
|
window, which are asked for again if the user goes back to them.
|
|
147
148
|
|
|
149
|
+
### Who decides the re-read — and who does not
|
|
150
|
+
|
|
151
|
+
The navigation decides nothing. Neither the back button, nor the movement
|
|
152
|
+
playing between the two pages: the **source** the list reads through is what
|
|
153
|
+
answers, and it answers the same way whether the user arrived by a link, by
|
|
154
|
+
`history.back()`, or under a route transition.
|
|
155
|
+
|
|
156
|
+
| The list reads through | Coming back to it |
|
|
157
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| `routeAction` over `GET_MANY` | **nothing goes out** — the action holds its response, and `.run()` on a `COMPLETED` action is a no-op |
|
|
159
|
+
| `<List.Items>` over `GET_RANGE` | **one ask goes out** — the reader kept ranks, not rows, and revalidates the window it draws |
|
|
160
|
+
|
|
161
|
+
Both are deliberate, and they are not in tension: an action that kept its answer
|
|
162
|
+
has the answer, while a composition is a claim about an order that any write
|
|
163
|
+
elsewhere may have made false. What a `GET_MANY` list wants on the way back it
|
|
164
|
+
has to say itself — `.rerun()` when the route becomes current again, or a verb
|
|
165
|
+
in `rerunOn` if a write is what makes it stale.
|
|
166
|
+
|
|
167
|
+
Nothing above changes when `defineRouteTransition` is written for the pair the
|
|
168
|
+
list is walked through. A transition states a relation between two pages (see
|
|
169
|
+
[route_transitions.md](./route_transitions.md)); it takes the document's
|
|
170
|
+
rendering hold for the one frame the browser needs to photograph it, and gives
|
|
171
|
+
it back. It never decides what the page arriving is allowed to ask for. Held by
|
|
172
|
+
`tests/route_transition_list_revisit/`, which mounts the same app twice — with
|
|
173
|
+
and without a relation on the pair — and compares both counts.
|
|
174
|
+
|
|
175
|
+
So a list that stopped refreshing after a transition was added has, in the
|
|
176
|
+
overwhelming majority of cases, never refreshed on its own: it was reading
|
|
177
|
+
through `GET_MANY`, and something else in the app was doing the re-read.
|
|
178
|
+
|
|
148
179
|
## `rerunOn`, verb by verb
|
|
149
180
|
|
|
150
181
|
`rerunOn` says which verbs invalidate this resource's `GET` / `GET_MANY` /
|
package/docs/navigation.md
CHANGED
|
@@ -294,6 +294,32 @@ settings page rising over whatever showed it — are animated with
|
|
|
294
294
|
`defineRouteTransition` instead, and a given pair must be animated by one of
|
|
295
295
|
the two, never both: see [route_transitions.md](./route_transitions.md).
|
|
296
296
|
|
|
297
|
+
## Where a navigation lands: the scroll
|
|
298
|
+
|
|
299
|
+
Three cases, and they are not a policy to configure but three different facts:
|
|
300
|
+
|
|
301
|
+
- **Going somewhere new** (a `<Link>`, anything that pushes) lands at the top.
|
|
302
|
+
It is an arrival: the offset one had elsewhere means nothing here, and left
|
|
303
|
+
alone the new entry would be born holding the previous page's offset — which
|
|
304
|
+
the browser would then hand back as if it were this page's own.
|
|
305
|
+
- **Going back or forward** (the browser's buttons, `navBack()`,
|
|
306
|
+
`history.back()`) lands where that page was left. navi keeps the position and
|
|
307
|
+
puts it back once the page is really rendered, which is what the browser
|
|
308
|
+
cannot do: it restores at the instant the entry changes, when the document
|
|
309
|
+
still holds the page being left, so anything further down than that page is
|
|
310
|
+
tall is clamped away.
|
|
311
|
+
- **A reload** lands where one was, as it would have without navi.
|
|
312
|
+
|
|
313
|
+
The consequence worth knowing when building a screen: **a "back" written as a
|
|
314
|
+
`<Link>` is a push**, so it lands at the top like any arrival — no mechanism
|
|
315
|
+
can guess that a push was morally a return. A back arrow that should feel like
|
|
316
|
+
one is `navBack()`. Where there may be nothing to go back to (a shared link
|
|
317
|
+
opened cold), decide what the arrow does from the history, not from the link.
|
|
318
|
+
|
|
319
|
+
What is not covered: a page whose height depends on something still loading is
|
|
320
|
+
not tall enough at the moment its position is put back, so a deep position is
|
|
321
|
+
clamped as it was before. Only the page knows when it is whole.
|
|
322
|
+
|
|
297
323
|
## Creating something, then editing it
|
|
298
324
|
|
|
299
325
|
The create screen, the page of what was created, the edit screen — three routes,
|
|
@@ -6,9 +6,10 @@ decides which movement (if any) a navigation deserves. The API grammar itself
|
|
|
6
6
|
(accepted forms, shipped type names) lives in the JSDoc of
|
|
7
7
|
`defineRouteTransition`; this file holds what a signature cannot say.
|
|
8
8
|
|
|
9
|
-
Demos:
|
|
10
|
-
[
|
|
11
|
-
[
|
|
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_fixed_bars/route_transition_fixed_bars.html),
|
|
12
|
+
[with a RouteTravel inside](../src/nav/demos/route_transition/route_transition_with_travel.html)
|
|
12
13
|
|
|
13
14
|
## What a transition is for
|
|
14
15
|
|
|
@@ -76,21 +77,69 @@ A default has no direction (nothing says which of two arbitrary pages is
|
|
|
76
77
|
"before" the other), so only directionless movements make sense there. Written
|
|
77
78
|
relations, and `"none"`, always win over it.
|
|
78
79
|
|
|
79
|
-
## Pages between fixed bars:
|
|
80
|
+
## Pages between fixed bars: the transition area
|
|
80
81
|
|
|
81
82
|
By default the movement plays on the document itself — right when pages are the
|
|
82
83
|
whole viewport. With fixed bars it is not: the root snapshot spans the viewport
|
|
83
|
-
and the bars' regions are blank in it, so
|
|
84
|
-
|
|
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:
|
|
85
86
|
|
|
86
|
-
```
|
|
87
|
-
<
|
|
87
|
+
```jsx
|
|
88
|
+
<RouteTransitionArea className="app">
|
|
89
|
+
<Route>…</Route>
|
|
90
|
+
</RouteTransitionArea>
|
|
88
91
|
```
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
A page that was **scrolled** is photographed where the reader was, and travels
|
|
114
|
+
from there: the document is not put back to its top until the picture has been
|
|
115
|
+
taken. Without that wait the picture keeps only the band the browser had
|
|
116
|
+
already painted at the new offset, and the movement carries a fragment of the
|
|
117
|
+
page instead of the page.
|
|
118
|
+
|
|
119
|
+
With an area marked, the page around it is left LIVE rather than photographed:
|
|
120
|
+
the bars keep answering the pointer for the whole movement, which a captured
|
|
121
|
+
element cannot do. The flip side is that anything around the area which must
|
|
122
|
+
_animate_ rather than stand still — a title that changes with the route — needs
|
|
123
|
+
a `view-transition-name` of its own; named, the browser moves it on the same
|
|
124
|
+
clock as the pages.
|
|
125
|
+
|
|
126
|
+
It is a `Box`, so the layout the pages need is written on it directly (`flex`,
|
|
127
|
+
`className`, `style`, …). An app that already has an element holding its pages
|
|
128
|
+
can mark that one with `data-navi-route-transition-area` rather than nesting
|
|
129
|
+
another — the component does exactly that.
|
|
130
|
+
|
|
131
|
+
**The area is a real box, and it has to be**: what gets photographed and
|
|
132
|
+
clipped IS its rectangle. So `display: contents` cannot be used on it — an
|
|
133
|
+
element with no box is never captured, and the movement then plays on nothing
|
|
134
|
+
(the browser also aborts the transition). This is measured behaviour, not a
|
|
135
|
+
precaution.
|
|
136
|
+
|
|
137
|
+
Three misconfigurations are silent enough to be worth a console warning, each
|
|
138
|
+
said once: an area that was not captured (the case above), several elements
|
|
139
|
+
marked at once (they would share one `view-transition-name`, and the browser
|
|
140
|
+
then refuses **every** view transition of the document), and pages travelling
|
|
141
|
+
on the document while something else is captured on its own — the blank band.
|
|
142
|
+
A warning here is a bug to fix, not a mechanism to lean on.
|
|
94
143
|
|
|
95
144
|
## Custom movements
|
|
96
145
|
|
|
@@ -111,13 +160,52 @@ shape, and the `spin` type in the demo for a working one.
|
|
|
111
160
|
- **`defineRouteTransition`** declares individual relations, with no gesture
|
|
112
161
|
and no order beyond each pair.
|
|
113
162
|
|
|
114
|
-
A given
|
|
163
|
+
A given PAIR of routes must be animated by one of the two, never both: a
|
|
115
164
|
travel's pictures can be under a finger, and a transition starting on top would
|
|
116
165
|
skip them mid-slide — and a page one can drag has promised a translation, which
|
|
117
166
|
a cross-fade would break. The runtime enforces the priority (a travel in flight
|
|
118
167
|
wins; the route transition is skipped with a console warning); the warning is
|
|
119
168
|
the sign of a misconfiguration to fix, not a mechanism to rely on.
|
|
120
169
|
|
|
170
|
+
The two DO live together in one app, on different pairs, including a
|
|
171
|
+
`<RouteTravel>` nested inside a `<RouteTransitionArea>` — a row of sections the
|
|
172
|
+
thumb pushes, inside pages one goes into. Write the relations on the routes the
|
|
173
|
+
row does not own: the tabs of the row travel, and opening something from any of
|
|
174
|
+
them plays its own movement. A relation written on a bare route covers every
|
|
175
|
+
one of its params at once, which is what makes "from any section" one line
|
|
176
|
+
rather than one per tab. Demo:
|
|
177
|
+
[../src/nav/demos/route_transition/route_transition_with_travel.html](../src/nav/demos/route_transition/route_transition_with_travel.html)
|
|
178
|
+
|
|
179
|
+
One trap that belongs to `RouteTravel` rather than to transitions, but bites
|
|
180
|
+
here first: the travelling box must stay MOUNTED across the changes it
|
|
181
|
+
animates. A `<RouteTravel>` rendered inside the `element` of each of the routes
|
|
182
|
+
it travels between is destroyed mid-travel by the router. Give the row a single
|
|
183
|
+
branch — its tabs as params of one route is the usual shape.
|
|
184
|
+
|
|
185
|
+
## A transition says nothing about data
|
|
186
|
+
|
|
187
|
+
A relation is about the map of the app, and about nothing else. Defining one
|
|
188
|
+
does not change what the page arriving loads, reloads, or keeps:
|
|
189
|
+
|
|
190
|
+
- an action that `COMPLETED` still holds its response, on the way back as on the
|
|
191
|
+
way in — a page wanting fresh data says `.rerun()`, with or without a
|
|
192
|
+
movement;
|
|
193
|
+
- a `<List.Items>` reading through `GET_RANGE` still revalidates the window it
|
|
194
|
+
draws when it is mounted again.
|
|
195
|
+
|
|
196
|
+
What a transition takes is the document's **rendering** for the one frame the
|
|
197
|
+
browser needs to photograph the page being left (see `rendering_hold.js`), and
|
|
198
|
+
it gives it back in the same callback. The hold is about a picture, not about
|
|
199
|
+
data: nothing waits on it, nothing is skipped because of it.
|
|
200
|
+
|
|
201
|
+
So "this list stopped refreshing since we animated the pair" is a claim worth
|
|
202
|
+
checking twice before believing: it is held by
|
|
203
|
+
`tests/route_transition_list_revisit/`, which mounts one app twice — the two
|
|
204
|
+
mounts differing by a single `defineRouteTransition` line — and compares what
|
|
205
|
+
each one sends to the network on the way back. See
|
|
206
|
+
[list_refresh.md](./list_refresh.md#who-decides-the-re-read--and-who-does-not)
|
|
207
|
+
for which source refreshes on a revisit and which does not.
|
|
208
|
+
|
|
121
209
|
## The rest, briefly
|
|
122
210
|
|
|
123
211
|
- Pace: `--navi-route-transition-duration` (CSS, default 300ms) for everyone;
|