@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/dist/dev/jsenv_navi.js +343 -99
- package/dist/dev/jsenv_navi.js.map +19 -20
- package/dist/jsenv_navi.js +343 -99
- package/dist/jsenv_navi.js.map +19 -20
- package/docs/actions.md +15 -0
- package/docs/mobile_touch.md +56 -0
- package/docs/navigation.md +4 -2
- package/docs/popup_open.md +17 -0
- package/docs/resource.md +21 -0
- package/package.json +1 -1
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:
|
package/docs/mobile_touch.md
CHANGED
|
@@ -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:
|
package/docs/navigation.md
CHANGED
|
@@ -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)).
|
|
788
|
-
|
|
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
|
package/docs/popup_open.md
CHANGED
|
@@ -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:
|