@jsenv/navi 0.29.418 → 0.29.419

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/dev/jsenv_navi.js +192 -43
  3. package/dist/dev/jsenv_navi.js.map +10 -10
  4. package/dist/jsenv_navi.js +190 -41
  5. package/dist/jsenv_navi.js.map +10 -10
  6. package/docs/AI_INSTRUCTIONS.md +177 -261
  7. package/docs/actions.md +164 -251
  8. package/docs/autofocus.md +66 -83
  9. package/docs/control_object.md +117 -214
  10. package/docs/control_value.md +121 -185
  11. package/docs/create_and_edit.md +159 -254
  12. package/docs/css_architecture.md +196 -260
  13. package/docs/data_states.md +41 -44
  14. package/docs/dialog_shape.md +91 -134
  15. package/docs/drag_interactions.md +115 -174
  16. package/docs/drag_to_travel.md +10 -9
  17. package/docs/dynamic_import.md +119 -136
  18. package/docs/error_handling.md +75 -127
  19. package/docs/field_validation.md +49 -89
  20. package/docs/form_changed.md +61 -78
  21. package/docs/group.md +141 -0
  22. package/docs/i18n.md +31 -76
  23. package/docs/interactions.md +6 -4
  24. package/docs/list_action.md +36 -51
  25. package/docs/list_refresh.md +73 -115
  26. package/docs/mobile_touch.md +56 -62
  27. package/docs/navigation.md +377 -723
  28. package/docs/network_policy.md +61 -75
  29. package/docs/pan_zoom.md +62 -103
  30. package/docs/popup_backdrop.md +128 -168
  31. package/docs/popup_lift.md +179 -268
  32. package/docs/popup_open.md +380 -651
  33. package/docs/resource.md +228 -408
  34. package/docs/route_transitions.md +333 -482
  35. package/docs/safe_area.md +102 -89
  36. package/docs/scroll.md +175 -335
  37. package/docs/state_binding.md +2 -1
  38. package/docs/testid.md +18 -23
  39. package/docs/typography.md +111 -141
  40. package/docs/view_transitions.md +106 -104
  41. package/docs/z_index.md +61 -109
  42. package/package.json +2 -2
  43. package/docs/badge_list.md +0 -83
  44. package/docs/control_group.md +0 -168
  45. package/docs/mobile_layout_pitfalls.md +0 -145
@@ -1,26 +1,26 @@
1
1
  # Code loaded on demand
2
2
 
3
- What an app wants from an `import()` is that **a screen's code arrives the way
4
- its data does**: one wait, one failure, one place that shows both. A module
5
- fetched on demand is a request like any other — it takes time, it fails when
6
- the network is gone or a deploy happened in between — and nothing on screen
7
- should have to know whether what it waits for is bytes of data or bytes of
8
- code.
9
-
10
- So navi has nothing lazy-shaped: **the import is an action**, started by what
11
- asks for it — the address through `routeAction`, a state through
12
- `actionRunEffect` — and read by the hook a screen already reads its data
13
- with. Running it from the hook itself is the fallback, as for data.
3
+ What an app wants from an `import()` is that **a screen's code arrives the way its
4
+ data does**: one wait, one failure, one place that shows both. A module fetched on
5
+ demand is a request like any other — it takes time, it fails when the network is
6
+ gone or a deploy happened in between — and nothing on screen should have to know
7
+ whether what it waits for is bytes of data or bytes of code.
8
+
9
+ So navi has nothing lazy-shaped: **the import is an action**, started by what asks
10
+ for it — the address through `routeAction`, a state through `actionRunEffect` —
11
+ and read by the hook a screen already reads its data with. Running it from the
12
+ hook itself is the fallback, as for data.
14
13
 
15
14
  ## A page: its code is a route action
16
15
 
17
16
  The address asks for the page's data through a route action; it asks for the
18
- page's code the same way, and the two are in flight together from the url
19
- change. The page component in the entry reads the code like data — with
20
- `loading: true` and `error: true`, drawing the page's own pending screen and
21
- its own unavailable screen where the page will stand:
17
+ page's code the same way, and the two are in flight together from the url change.
18
+ The page component in the entry reads the code like data — with `loading: true`
19
+ and `error: true`, drawing the page's own pending screen and its own unavailable
20
+ screen where the page will stand:
22
21
 
23
22
  ```jsx
23
+ // after setupRoutes(): a route's paramsSignal is null until then
24
24
  const GAME_PAGE_ACTION = routeAction(GAME_ROUTE, GAME.GET, () => ({
25
25
  gameId: GAME_ROUTE.paramsSignal.value.gameId,
26
26
  }));
@@ -45,51 +45,43 @@ const GamePage = () => {
45
45
  <Route route={GAME_ROUTE} element={GamePage} />;
46
46
  ```
47
47
 
48
- Why a branch rather than a `<Loading>` around it: suspending takes the
49
- subtree away and puts the boundary's fallback in its place, then mounts the
50
- page again — a cost worth paying when a page already on screen re-reads its
51
- data (see [data_states.md](./data_states.md)), and worth nothing here, where
52
- the page is not there yet and has nothing to keep. `loading: true` never
53
- suspends: one component, no wrapper, the pending screen is the page's own
54
- shape, and the failure sits beside it (what it offers: below, "When the code
55
- does not come"). What is delegated stays delegated — the page's data, read inside
56
- `game_page.jsx` with `useAsyncData(GAME_PAGE_ACTION)`, still waits in the
57
- `<Loading>` written between the routes, as [navigation.md](./navigation.md)
58
- says.
48
+ Why a branch rather than a `<Loading>` around it: suspending takes the subtree
49
+ away, puts the boundary's fallback in its place, then mounts the page again — a
50
+ cost worth paying when a page already on screen re-reads its data (see
51
+ [data_states.md](./data_states.md)), and worth nothing here, where the page is not
52
+ there yet. `loading: true` never suspends: one component, no wrapper, a pending
53
+ screen in the page's own shape, and the failure beside it. What is delegated stays
54
+ delegated — the page's data, read inside `game_page.jsx` with
55
+ `useAsyncData(GAME_PAGE_ACTION)`, still waits in the `<Loading>` written between
56
+ the routes ([navigation.md](./navigation.md#loading-data)). That page module
57
+ imports the routes module back for `GAME_PAGE_ACTION`; a dynamic import is not a
58
+ load-order cycle.
59
59
 
60
60
  Everything the data layer decides then holds for the code:
61
61
 
62
- - **One failure.** A rejected import fails the run like a failed read; drawn
63
- by the branch above, or by the nearest `<ErrorBoundary>` when the page reads
64
- its code without `error: true`.
62
+ - **One failure.** A rejected import fails the run like a failed read, drawn by the
63
+ branch above, or by the nearest `<ErrorBoundary>` when the page reads its code
64
+ without `error: true`.
65
65
  - **Fetched once.** A route action left behind is aborted, not reset, and a
66
- completed one is not run again: coming back to the page renders
67
- synchronously, only the data is asked for per params.
68
- - **Fetched ahead.** A route action that asks nothing of the address is prerun
69
- on intent (below).
70
- - **Counted.** The routing is busy until the code is there, like for the
71
- data.
72
-
73
- The page module itself is ordinary — it reads its data with
74
- `useAsyncData(GAME_PAGE_ACTION)` like any page — and imports the routes module
75
- back for it; a dynamic import is not a load-order cycle.
66
+ completed one is not run again: coming back to the page renders synchronously,
67
+ only the data is asked for per params.
68
+ - **Fetched ahead**, on intent ([below](#ahead-of-the-render-intent)).
76
69
 
77
70
  ## A component inside a page that stays: who asks for its code
78
71
 
79
- A plan drawn on demand inside a page that stays knows where it is drawn and
80
- what stands there — a frame, a line, a button. The wait and the failure are
81
- drawn where the frame is, with `loading: true` and `error: true`, the same way
82
- as for a page. What differs is **who starts the import**, and the order of
83
- preference is the one `useAsyncData` states for data: **the run belongs to
84
- what asks, not to the render.**
85
-
86
- 1. **The address asks** → `routeAction`, above. A section of a page that has
87
- an address (`/places/:id/plan`) is a page.
88
- 2. **A state asks** → `actionRunEffect`. A plan drawn once a mode is entered,
89
- a tab chosen, a popup opened: the signal that says so is what starts the
90
- import, declared once at module scope — one action for every instance,
91
- started by the state change rather than by the render that follows it,
92
- and holding its answer across mounts:
72
+ A plan drawn on demand inside a page that stays draws its wait and its failure
73
+ where its frame is, with `loading: true` and `error: true`, as a page does. What
74
+ differs is **who starts the import**, and the order of preference is the one
75
+ `useAsyncData` states for data: **the run belongs to what asks, not to the
76
+ render.**
77
+
78
+ 1. **The address asks** → `routeAction`, above. A section of a page that has an
79
+ address (`/places/:id/plan`) is a page.
80
+ 2. **A state asks** → `actionRunEffect`. A plan drawn once a mode is entered, a
81
+ tab chosen, a popup opened: the signal that says so starts the import, declared
82
+ once at module scope — one action for every instance, started by the state
83
+ change rather than by the render that follows it, and holding its answer across
84
+ mounts:
93
85
 
94
86
  ```jsx
95
87
  const PLAN_CODE = actionRunEffect(
@@ -105,7 +97,9 @@ what asks, not to the render.**
105
97
  return (
106
98
  <PlanFrame>
107
99
  {loading ? <Text>…</Text> : null}
108
- {error ? <Button action={() => reload()}>Recharger</Button> : null}
100
+ {error ? (
101
+ <Button action={() => window.location.reload()}>Recharger</Button>
102
+ ) : null}
109
103
  {Plan ? <Plan /> : null}
110
104
  </PlanFrame>
111
105
  );
@@ -114,8 +108,7 @@ what asks, not to the render.**
114
108
 
115
109
  3. **Nothing above the component asks** → `useAsyncData` with a function, the
116
110
  fallback: the component's own existence is the only thing that asks, so the
117
- request is its own, made into an action once on the first render and kept
118
- for the life of the instance.
111
+ request is its own, for the life of the instance.
119
112
 
120
113
  ```jsx
121
114
  const [Plan, loading, error] = useAsyncData(
@@ -124,118 +117,108 @@ what asks, not to the render.**
124
117
  );
125
118
  ```
126
119
 
127
- It starts one render late, and per instance: a component fetched this way
128
- in three places asks three times (the browser fetches the module once), and
120
+ It starts one render late, and per instance: a component fetched this way in
121
+ three places asks three times (the browser fetches the module once), and
129
122
  coming back to it draws its wait again for the microtask the import takes to
130
123
  answer from the module map. Reach for it when there is genuinely no state to
131
124
  read the request off — not to save declaring one.
132
125
 
133
- Delegating instead (the hook without `loading: true`, under a `<Loading>`
134
- around the frame) follows the rule for data: **the `<Loading>` goes where the
135
- fallback should land.** A boundary around the whole router takes the router
136
- away while a page loads — top bar, tabs and all.
137
-
138
- Nothing has to be kept out of a subtree loaded this way: the suspension
139
- happens before the module's components exist, so nothing inside it is parked
140
- by the code arriving.
126
+ Delegating instead (the hook without `loading: true`, under a `<Loading>` around
127
+ the frame) follows the rule for data: **the `<Loading>` goes where the fallback
128
+ should land.** A boundary around the whole router takes the router away while a
129
+ page loads — top bar, tabs and all. Nothing has to be kept out of a subtree loaded
130
+ this way: the suspension happens before the module's components exist, so nothing
131
+ inside it is parked by the code arriving.
141
132
 
142
133
  ## Ahead of the render: intent
143
134
 
144
135
  **A link preloads where it leads when the pointer or the focus reaches it.**
145
- `<Link>` and `<Button>` with an `href` or a `route` do it by default;
146
- `prefetch={false}` opts one out. The url is matched against the routes on
147
- screen, a section and the page inside it alike, and what is prerun is **every
148
- route action that asks nothing of the address** — a page's code, a read
149
- without params. An action whose params come from the address is not
150
- prefetched by a link: a link's prefetch has no address, the id may still be
151
- chosen.
152
- `routeAction(route, action, undefined, { prefetch: false })` keeps a
153
- param-less read that is not worth a hover out of it.
154
-
155
- A prefetch that fails is not reported: nothing on screen asked for it. The
156
- failure stays on the action, where the arrival asks again — a failed action is
157
- run, only a running or completed one is left alone — and shows its own.
158
-
159
- The same from code: `route.preload()` for a route something is about to
160
- navigate to, `preloadUrl(url)` for an address. A press that computes its
161
- destination has nothing to preload — the navigation itself asks.
136
+ `<Link>` and `<Button>` with an `href` or a `route` do it by default
137
+ (`prefetch={false}` opts one out): every route of `setupRoutes()` the url matches,
138
+ a section and the page inside it alike, preruns **its route actions declared
139
+ without a params function** — a page's code, a read without params. One whose
140
+ params come from the address waits for the arrival (a link's prefetch has no
141
+ address, the id may still be chosen), and so does one given a params function
142
+ that asks for nothing (`() => true`).
143
+ `routeAction(route, action, undefined, { prefetch: false })` keeps a param-less
144
+ read that is not worth a hover out of it.
145
+
146
+ A prefetch that fails is not reported: nothing on screen asked for it. The failure
147
+ stays on the action, and a failed action is run again by whatever asks next — the
148
+ arrival, which shows its own failure, and every later hover alike.
149
+
150
+ From code: `route.preload()` for a route about to be navigated to,
151
+ `preloadUrl(url)` for an address. A press that computes its destination has
152
+ nothing to preload — the navigation itself asks.
162
153
 
163
154
  ## What a transition photographs
164
155
 
165
156
  A route element still waiting is what the container rendered — its pending
166
- screen, or the fallback of the boundary it suspended into — so a route
167
- transition or a `RouteTravel` takes its picture of **that**, code and data
168
- alike. Navi does not hold a navigation for a chunk: the honest answer
169
- to "a chunk is a hundred milliseconds" is the prefetch above — the code is
170
- there before the press — and a skeleton that is the page's own shape is what
171
- gets photographed when it is not.
157
+ screen, or the fallback of the boundary it suspended into — so a route transition
158
+ or a `RouteTravel` takes its picture of **that**, code and data alike. navi does
159
+ not hold a navigation for a chunk: the honest answer to "a chunk is a hundred
160
+ milliseconds" is the prefetch above — the code is there before the press — and a
161
+ skeleton in the page's own shape is what gets photographed when it is not.
172
162
 
173
163
  ## `import.meta.css` on the far side of a split
174
164
 
175
165
  A module's stylesheet ships **inside its chunk** and is adopted when the
176
- assignment runs (see [css_architecture.md](./css_architecture.md)). Three
177
- consequences:
178
-
179
- - **The fallback cannot use a class the lazy module defines.** It is rendered
180
- by the eager side, before the chunk exists. The frame of what it stands in
181
- for — background, border, aspect ratio — belongs to the eager module, the
182
- lazy one owning only the inside; duplicating the frame into the eager side
183
- is the same answer.
184
- - **Layers are what the text says.** `@layer navi` holds navi's defaults
185
- whether the sheet came with the entry or with a chunk, and an app's unlayered
186
- rule wins over it from either side.
187
- - **Between unlayered sheets, the last adopted wins at equal specificity.** A
188
- chunk's sheet lands after everything already adopted, so a chunk beats the
189
- entry; between two chunks the order is the order they ran in, which nothing
190
- in the build fixes. A rule that depends on it is a rule to make more
191
- specific.
166
+ assignment runs (see [css_architecture.md](./css_architecture.md)) — for the
167
+ app's own modules: code from `node_modules` goes to a shared vendor chunk, its
168
+ stylesheet with it. Two consequences:
169
+
170
+ - **The fallback cannot use a class the lazy module defines.** It is rendered by
171
+ the eager side, before the chunk exists. The frame of what it stands in for —
172
+ background, border, aspect ratio — belongs to the eager module, the lazy one
173
+ owning only the inside; duplicating the frame into the eager side is the same
174
+ answer.
175
+ - **Between unlayered sheets, the last adopted wins at equal specificity**, and a
176
+ sheet is adopted when its module first assigns it — for navi's components, on
177
+ their first render. Which of the entry and a chunk, or of two chunks, comes last
178
+ is therefore nothing the build fixes: a rule that depends on it is a rule to
179
+ make more specific.
192
180
 
193
181
  ## When the code does not come
194
182
 
195
- Someone has the app open, a deploy moves the chunks, and the next `import()`
196
- is a 404; or the network is gone. The browser says both the same way — a
197
- `TypeError` with no status — and neither is a bug of the page: there is
198
- nothing in the app's code to point at.
183
+ Someone has the app open, a deploy moves the chunks, and the next `import()` is a
184
+ 404; or the network is gone. The browser says both the same way — a `TypeError`
185
+ with no status — and neither is a bug of the page: there is nothing in the app's
186
+ code to point at.
199
187
 
200
188
  **Asking again never fetches.** The document remembers a module whose fetch
201
189
  failed as failed: a second `import()` of the same address is refused without a
202
- request, for the life of the document. So a retry on an import has nothing to
203
- do — `error.action.rerun()` fails again at once, on the same failure — and
204
- the one way out is a fresh document: **`reload()`**, which the screen offers
205
- in words the reader understands. Navi does not reload on its own: a document
206
- reloading itself is a loop waiting to happen. It is also why a prefetch that
207
- failed is not asked again on the arrival in any useful sense: the run is made,
208
- refused at once, and the screen says it.
209
-
210
- The page or the component reading its code says it where it stands, as
211
- above. A failure left to a boundary — a page reading its code without
212
- `error: true` — reaches it like a failed read does; a boundary that shows only
213
- what came from the network and rethrows the rest as a bug recognises an import
214
- by its action rather than by the browser's message. `error.action` is the
190
+ request, for the life of the document. So a retry has nothing to do —
191
+ `error.action.rerun()` fails again at once, on the same failure — and the one way
192
+ out is a fresh document: **`window.location.reload()`**, which the screen offers
193
+ in words the reader understands. navi does not reload on its own: a document
194
+ reloading itself is a loop waiting to happen.
195
+
196
+ The page or the component reading its code says it where it stands, as above. A
197
+ failure left to a boundary reaches it like a failed read does; a boundary that
198
+ shows only what came from the network and rethrows the rest as a bug recognises
199
+ an import by its action, not by the browser's message. `error.action` is the
215
200
  instance that ran, not the route action itself (a route action hands out the
216
201
  instance for its params), and every instance bound from one action carries its
217
202
  `callback`: `error.action.callback === GAME_PAGE_CODE.callback`.
218
203
 
219
204
  ## Preact's `lazy()` is not this
220
205
 
221
- `preact/compat` has a `lazy()`. It suspends without saying anything to
222
- `<Loading>`: the boundary keeps the last reason it knows — "idle" by default —
223
- and draws nothing while the module loads; a failure is not an action's, so no
224
- `rerun` brings the page back. Navi warns in dev when a `<Loading>` catches a
225
- suspension it cannot attribute to an action. Read the import through an
226
- action.
206
+ `preact/compat`'s `lazy()` suspends without telling `<Loading>` what it waits
207
+ for, so the boundary has no action to follow — on a fresh one, it draws nothing
208
+ while the module loads — and a failure is not an action's, so no `rerun` brings
209
+ the page back. Read the import through an action.
227
210
 
228
211
  ## The build
229
212
 
230
- Every `import()` becomes a chunk of its own under `js/`, versioned with the
231
- rest, and a target without dynamic import gets the fallback. Nothing to
232
- configure.
213
+ `import()` becomes a chunk of its own under `js/`, versioned with the rest —
214
+ unless the module is also imported statically, which keeps it where the static
215
+ import puts it; what it imports from `node_modules` goes to the shared vendor
216
+ chunk. A target without dynamic import gets the fallback.
233
217
 
234
218
  ## Reference
235
219
 
236
- - `src/nav/demos/code_splitting/code_splitting_demo.html` — two pages whose
237
- code is a route action, one prefetched on hover and one opted out, and a
238
- component inside a page that stays, drawing its own wait and failure; every
239
- import is drawn on the backend's frontier so a wait or a failure can be
240
- played by hand.
220
+ - `src/nav/demos/code_splitting/code_splitting_demo.html` — two pages whose code
221
+ is a route action, one prefetched on hover and one opted out, and a component
222
+ inside a page that stays, drawing its own wait and failure; every import is
223
+ drawn on the backend's frontier so a wait or a failure can be played by hand.
241
224
  - `src/nav/route_action.js`, `src/nav/use_preload_on_intent.js`