@jsenv/navi 0.29.458 → 0.29.460

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/docs/actions.md CHANGED
@@ -222,6 +222,21 @@ decided to write, whatever its parameter, and `{ run: true }` says it — a toke
222
222
  a share button must already hold when pressed, for instance, since the OS share
223
223
  sheet only opens inside the gesture, never after an `await`.
224
224
 
225
+ **A head start is `{ prerun: true }`.** When the params are known before the
226
+ component owning the read exists — a card already says which chapter a book
227
+ has, the book page draws its pages once the book is there — a component
228
+ drawing nothing can send the read ahead, on the instance the owner will read:
229
+
230
+ ```jsx
231
+ // draws nothing: the pages leave with the book instead of after it
232
+ useAsyncData(chapterPagesAction, { prerun: true });
233
+ ```
234
+
235
+ It needs no `<Loading>`, and its failure reaches no boundary: nothing on screen
236
+ asked for a prerun, so the owner's `{ run: true }` asks again, once, when it
237
+ reads it. A failure of the owner's own run is shown, and asked again by a
238
+ `rerun()`, never by a render. A head start still running is the owner's wait.
239
+
225
240
  `{ onLoad }` is what the screen does with the data **once, when it becomes
226
241
  known** — seed the fields someone is about to edit, focus something, remember
227
242
  where a list was:
@@ -17,6 +17,7 @@ changes, measure again before trusting a number here.
17
17
  - [The browser decides about 8px in, and does not wait](#the-browser-decides-about-8px-in-and-does-not-wait)
18
18
  - [touch-action stops at the first scroller](#touch-action-stops-at-the-first-scroller)
19
19
  - [Only the start is contested](#only-the-start-is-contested)
20
+ - [A rounded clip makes the scroll wait for the main thread](#a-rounded-clip-makes-the-scroll-wait-for-the-main-thread)
20
21
  - [The click is the browser's call as much as ours](#the-click-is-the-browsers-call-as-much-as-ours)
21
22
  - [A tap dropped after a touch drag](#a-tap-dropped-after-a-touch-drag)
22
23
  - [A finger is captured to what it touched, inside shadow trees too](#a-finger-is-captured-to-what-it-touched-inside-shadow-trees-too)
@@ -122,6 +123,48 @@ it back:
122
123
  A gesture that has won the start keeps the touch. What loses gestures is the
123
124
  first ~8px.
124
125
 
126
+ ## A rounded clip makes the scroll wait for the main thread
127
+
128
+ Before it scrolls under a finger, Chrome's compositor finds the scroller under
129
+ it. Where it cannot tell on its own, it asks the main thread, and the scroll
130
+ starts when the main thread answers — after whatever task is running there.
131
+ Meanwhile the finger keeps moving: the content holds still, then catches up in
132
+ one step. A small scroll is mostly its start, so that is where it is felt. The
133
+ wheel asks the same way on a desktop, where the main thread is rarely busy at
134
+ that moment.
135
+
136
+ Two things make Chrome ask (Chrome 155, mobile emulation; not measured in
137
+ Safari):
138
+
139
+ - **A rounded clip on the scroller or around it**: `overflow` other than
140
+ `visible` with a `border-radius`, or a rounded `clip-path`. Every scroll
141
+ start inside it is asked, wherever the finger lands. A popup that scrolls is
142
+ one: a rounded `overflow: auto` box.
143
+ - **Anything painted over the scroller**, even with `pointer-events: none` and
144
+ square corners (an overlay drawing an edge above the items, say), but only
145
+ where it covers it: a 6px band along the top leaves the rest decided on the
146
+ compositor.
147
+
148
+ The wait costs whatever runs at the touch: 0.1–2ms when nothing does; with a
149
+ render built on the touch at 6× CPU, 7.5ms median and up to 27ms, and a scroll
150
+ started a frame late one time in three. So navi keeps both away from what
151
+ scrolls. A framed `List` does not clip to its rounded corners: its edge stays
152
+ under the items, and one with a background covers the inside of the curve. A
153
+ picker popup whose list scrolls itself does not clip either. And a finger on
154
+ something that opens builds nothing before it has been held ~80ms. Wrapping a
155
+ `List` in a rounded box of your own with `overflow: hidden` brings the wait
156
+ back.
157
+
158
+ A box that stops clipping needs `min-height: 0` if it is a flex or grid item
159
+ holding the scroller: without a clip its minimum height is its content's, so a
160
+ box given a height grows to all its items and the page scrolls instead. Nothing
161
+ waits for the main thread then, because the wrong thing scrolls — check that
162
+ the scroller's own `scrollTop` moved.
163
+
164
+ Reference: `.navi_list_container` in `src/control/list/list.jsx`, the
165
+ `[data-picker-popup]` rules in `src/control/picker/picker_custom.jsx`,
166
+ `TOUCH_WARM_HOLD_MS` in `src/layout/popup_content_mount.js`.
167
+
125
168
  ## The click is the browser's call as much as ours
126
169
 
127
170
  - **Chrome** clicks after a touch that moved up to 8px and never beyond, whether
@@ -221,6 +264,19 @@ configuration: "mobile" })`, then `Input.dispatchMouseEvent` with
221
264
  through the desktop configuration instead: a 15px slop. Anything deciding at
222
265
  10px wins there, and the race that loses swipes on a phone is invisible.
223
266
 
267
+ Where a scroll's start is decided shows only in a trace, never in the page's
268
+ events: `Tracing.start` with `input`, `cc` and `disabled-by-default-cc.debug`.
269
+ After the last `InputHandlerProxy::HandleTouchStart`, a
270
+ `WidgetInputHandlerManager::FindScrollTargetOnMainThread` is the scroller looked
271
+ up on the main thread, the gap between the two
272
+ `InputHandlerProxy::HandleGestureScrollBegin` is how long that took, and a
273
+ `delta_y` larger than one move on the first
274
+ `InputHandler::ScrollLatchedScroller` says the first step merged two moves.
275
+ Not under Playwright: its injected script puts a non-passive `touchstart`
276
+ listener on `window`, which makes every touch blocking and changes the path
277
+ being measured. Start Chrome by hand (`--headless=new
278
+ --remote-debugging-port`) and drive the page's websocket directly.
279
+
224
280
  **Safari**: the iOS simulator, driven by `safaridriver`, which turns W3C touch
225
281
  actions into real UIKit touches — Safari's own pan recognizers compete for
226
282
  them exactly as on a phone:
@@ -784,8 +784,10 @@ Every piece of it answers something:
784
784
  off screen;
785
785
  - **no `defineRouteTransition` for it.** The page beneath does not change, so
786
786
  there is no pair of pages to move between — route transitions are for pages
787
- replacing pages ([route_transitions.md](./route_transitions.md)). When a link
788
- inside the layer does change it, see
787
+ replacing pages ([route_transitions.md](./route_transitions.md)). Nor does
788
+ what is open on it: a popup or a section kept with `navState` stays open under
789
+ the layer ([popup_open.md](./popup_open.md#navstate--the-history-entry-holds-it)).
790
+ When a link inside the layer does change the page, see
789
791
  [below](#a-link-inside-the-layer-leads-to-a-page).
790
792
 
791
793
  ### Places inside the layer
@@ -680,6 +680,23 @@ picker open and come back to is a new mount, and the state stays in the entry
680
680
  with nothing to read it (navi warns). A picker whose popup leads somewhere, a
681
681
  link inside it, has an `id`.
682
682
 
683
+ What a new entry inherits is decided by the page, not by the address. A push to
684
+ another path starts its entry empty: a popup open on the page being left
685
+ describes that page alone, and carried onto the next one it would open whatever
686
+ mounts there under the same key. A push that keeps the path — a
687
+ [layer](./navigation.md#a-layer-over-the-screen-what-its-address-may-say)
688
+ opening over the page, a
689
+ [state whose values are places](./navigation.md#a-state-whose-values-are-places-history-push)
690
+ — stacks an entry over a page that is still mounted, and that entry starts with
691
+ what the one below it held: an `Expandable` section open under the layer stays
692
+ open, and closing the layer finds it as it was, with nothing played again. The
693
+ scroll reads the same rule
694
+ ([navigation.md](./navigation.md#where-a-navigation-lands-the-scroll)).
695
+
696
+ A link inside the popup follows the same rule: one to the same path with other
697
+ search params leaves the popup open on the entry it pushes. A link whose press
698
+ is a choice that ends the popup says so itself, with `command="--navi-close"`.
699
+
683
700
  The two meet when the signal IS a route's: a search-param `stateSignal` given to
684
701
  `signal` puts the open state in the address itself, where a link can point at
685
702
  it — the shape of a popup that is a LAYER over the screen, whose address is
package/docs/resource.md CHANGED
@@ -613,6 +613,27 @@ is inferred — a relation that really is a sub-route of the parent is modelled
613
613
  with the relationship methods ([above](#relations-and-autorerun)), never
614
614
  declared as a dependency.
615
615
 
616
+ When only some writes change what the reads show — a count of videos moves
617
+ when one is created or deleted, not when a chapter is rated — name them with
618
+ `on`:
619
+
620
+ ```js
621
+ const SUMMARY = resource("summary", {
622
+ dependencies: [
623
+ BOOK, // every write
624
+ { resource: VIDEO, on: ["PATCH_MANY", "DELETE"] },
625
+ { resource: VIDEO_FILE, on: ["POST", "DELETE"] },
626
+ ],
627
+ GET: …,
628
+ });
629
+ ```
630
+
631
+ `on` names callbacks, not verbs: `PATCH` does not cover `PATCH_MANY` the way it
632
+ does in `rerunOn`, so each write keeps its own answer. A name that is not a
633
+ write, or that the resource does not define, throws at declaration — a
634
+ dependency that never fires leaves a count on its old value with nothing to say
635
+ so.
636
+
616
637
  ## A function calling the verb, or the instance
617
638
 
618
639
  Both forms are real, and what separates them is narrower than it looks:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.458",
3
+ "version": "0.29.460",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {