@jsenv/navi 0.29.416 → 0.29.417
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 +1481 -1104
- package/dist/dev/jsenv_navi.js.map +18 -16
- package/dist/jsenv_navi.js +912 -549
- package/dist/jsenv_navi.js.map +18 -16
- package/docs/AI_INSTRUCTIONS.md +8 -4
- package/docs/mobile_touch.md +35 -0
- package/docs/resource.md +13 -0
- package/docs/route_transitions.md +26 -7
- package/package.json +2 -2
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -79,7 +79,10 @@ to start when unsure which export solves a problem.
|
|
|
79
79
|
map; a pair of routes is animated by `RouteTravel` or by a transition, never
|
|
80
80
|
both; a test waits for the page arriving, never for its address; the page
|
|
81
81
|
being left stays mounted, hidden, until the movement is over (so what it
|
|
82
|
-
registers globally must let the last arrival win
|
|
82
|
+
registers globally must let the last arrival win, and an action it reads
|
|
83
|
+
goes through `useAsyncData`, which leaves a page no longer shown alone —
|
|
84
|
+
`.value` on the action's signals re-renders it hidden); what a transition
|
|
85
|
+
costs.
|
|
83
86
|
Read before animating a navigation, before writing a custom movement, and
|
|
84
87
|
before believing a symptom that only appears once a pair is animated.
|
|
85
88
|
- `view_transitions.md` — what the browser does to any transition an app
|
|
@@ -221,9 +224,10 @@ to start when unsure which export solves a problem.
|
|
|
221
224
|
- `mobile_touch.md` — what Chrome and Safari do with a finger a gesture also
|
|
222
225
|
reads, measured: they commit it to their own pan ~8px in (read a finger's
|
|
223
226
|
intent before that), `touch-action` restarts at every scroller, when the
|
|
224
|
-
click survives, the tap Chrome drops after a touch drag,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
click survives, the tap Chrome drops after a touch drag, the capture every
|
|
228
|
+
finger lands with (which `hasPointerCapture` misses on a field), and how to
|
|
229
|
+
reproduce all of it without a device (Chrome's mobile touch emulator, Safari
|
|
230
|
+
in the iOS simulator). Read before reading a finger yourself, and before concluding a
|
|
227
231
|
gesture works because it works with a mouse.
|
|
228
232
|
|
|
229
233
|
### Layout, CSS and text
|
package/docs/mobile_touch.md
CHANGED
|
@@ -19,6 +19,7 @@ changes, measure again before trusting a number here.
|
|
|
19
19
|
- [Only the start is contested](#only-the-start-is-contested)
|
|
20
20
|
- [The click is the browser's call as much as ours](#the-click-is-the-browsers-call-as-much-as-ours)
|
|
21
21
|
- [A tap dropped after a touch drag](#a-tap-dropped-after-a-touch-drag)
|
|
22
|
+
- [A finger is captured to what it touched, inside shadow trees too](#a-finger-is-captured-to-what-it-touched-inside-shadow-trees-too)
|
|
22
23
|
- [A finger never makes a wheel event](#a-finger-never-makes-a-wheel-event)
|
|
23
24
|
- [Verifying without a device](#verifying-without-a-device)
|
|
24
25
|
|
|
@@ -160,6 +161,40 @@ Chromium's `gesture_provider.cc` (`ignore_single_tap_`, reset on the next down)
|
|
|
160
161
|
and `tap_suppression_controller.cc` (the tap that stops a fling, 180ms) for the
|
|
161
162
|
two mechanisms that do NOT explain it.
|
|
162
163
|
|
|
164
|
+
## A finger is captured to what it touched, inside shadow trees too
|
|
165
|
+
|
|
166
|
+
A touch pointer is captured the moment it lands, by the browser, to the element
|
|
167
|
+
it landed on — nobody calls `setPointerCapture`. A mouse and a pen are not.
|
|
168
|
+
That capture is announced like any other, with a `gotpointercapture` just
|
|
169
|
+
before the first pointer event that follows, and a finger resting still sends
|
|
170
|
+
one soon: its contact changes, so `pointermove`s with unchanged coordinates
|
|
171
|
+
come within the first 100ms or so. Nearly every touch press therefore sees a
|
|
172
|
+
`gotpointercapture` it never asked for. Code that reads it as "another gesture
|
|
173
|
+
took this press" gives up on almost every finger.
|
|
174
|
+
|
|
175
|
+
Recognising that capture is where Chrome misleads: **it goes to the deepest
|
|
176
|
+
element under the finger, shadow trees included.** On an `<input>` or a
|
|
177
|
+
`<textarea>` that is the inner editor, inside the field's own shadow tree; in a
|
|
178
|
+
web component, whatever its shadow root holds there. `hasPointerCapture` on
|
|
179
|
+
what the page sees — the field, the host — answers `false` at `pointerdown`,
|
|
180
|
+
after `gotpointercapture`, and for the whole press. Yet the `gotpointercapture`
|
|
181
|
+
reaches the page retargeted to that same element. An open shadow root shows
|
|
182
|
+
both halves: the host answers `false`, the element inside it `true`.
|
|
183
|
+
|
|
184
|
+
So the browser's capture is recognised by where it lands, never asked for: on
|
|
185
|
+
a touch press, a capture announced on the press's own target is the browser's,
|
|
186
|
+
whenever it arrives. Compare both events from the same tree: each is
|
|
187
|
+
retargeted to the shadow host from outside. A gesture that takes the press onto
|
|
188
|
+
that same element cannot be told apart from it, so it has to say so another way.
|
|
189
|
+
|
|
190
|
+
Measured in Chromium 153 through `Input.dispatchTouchEvent`. The capture does
|
|
191
|
+
not depend on the slop, so the desktop path shows it; see
|
|
192
|
+
[Verifying without a device](#verifying-without-a-device). The same `false` on
|
|
193
|
+
a field shows on an Android phone (Chrome 153). Not measured in Safari.
|
|
194
|
+
|
|
195
|
+
Reference: `implicitCaptureHolder` and `takePress` in `@jsenv/dom`'s
|
|
196
|
+
`press_held.js`.
|
|
197
|
+
|
|
163
198
|
## A finger never makes a wheel event
|
|
164
199
|
|
|
165
200
|
No mobile browser turns a touch into `wheel` events: those come from a mouse
|
package/docs/resource.md
CHANGED
|
@@ -300,6 +300,19 @@ store are written, and re-renders when its row is — `useArray` to the whole
|
|
|
300
300
|
store. An entry of `useAllByIds` given by a unique key rather than by id can
|
|
301
301
|
only be found by reading the whole store, and that read follows it too.
|
|
302
302
|
|
|
303
|
+
A row counts as written only when something in it changed:
|
|
304
|
+
|
|
305
|
+
- **An answer** — what a callback returns — is compared to the row held in
|
|
306
|
+
depth. Parsed JSON makes every object nested in a row a new one; a
|
|
307
|
+
revalidation returning the same rows keeps every one of them, and their
|
|
308
|
+
readers stay still.
|
|
309
|
+
- **A relation** changes its row when the row points at other children. What
|
|
310
|
+
happens to the children themselves is followed through the relation:
|
|
311
|
+
`game.place.label` read in a render re-renders it when the place is written,
|
|
312
|
+
and the game row stays the one it was.
|
|
313
|
+
- **A value written with `store.upsert()`** is compared by reference: a new
|
|
314
|
+
object is a change, whatever it holds.
|
|
315
|
+
|
|
303
316
|
## Relations: pick one of the four methods
|
|
304
317
|
|
|
305
318
|
A backend sub-route (`/games/:id/candidates`, `/games/:id/candidates/:userId/seen`)
|
|
@@ -552,9 +552,11 @@ Measured on that page, first frame of the movement: 63 → 45 ms at full speed,
|
|
|
552
552
|
What that means for a page and for the application around it:
|
|
553
553
|
|
|
554
554
|
- **It is still mounted for the length of the movement.** Its effects keep
|
|
555
|
-
running, and it still reads the address:
|
|
556
|
-
|
|
557
|
-
[What a transition costs](#what-a-transition-costs)).
|
|
555
|
+
running, and it still reads the address: a component reading the URL, a
|
|
556
|
+
route or a signal directly re-renders in it too, hidden (see
|
|
557
|
+
[What a transition costs](#what-a-transition-costs)). What it reads through
|
|
558
|
+
`useAsyncData` does not: navi leaves a page alone as soon as its container
|
|
559
|
+
would no longer show it.
|
|
558
560
|
- **It is taken down after the page arriving is up.** A cleanup that puts back
|
|
559
561
|
what it found — a document title, a class on `<body>`, a value in a shared
|
|
560
562
|
store — puts a stale value back over the new page's. Such a registration is
|
|
@@ -646,10 +648,27 @@ elements):
|
|
|
646
648
|
thirty cards: 90 re-renders gone, first frame 58 → 41 ms). A row read with
|
|
647
649
|
`RESOURCE.useById` follows that row only, so the page arriving loading rows
|
|
648
650
|
of its own into the same store leaves the cards of the page being left as
|
|
649
|
-
they were. What
|
|
650
|
-
— its params, its data and its state all
|
|
651
|
-
|
|
652
|
-
|
|
651
|
+
they were. What moves last is the page's own reading of its route's action
|
|
652
|
+
— its params, its data and its state all change as the route stops
|
|
653
|
+
matching. `useAsyncData` gives no render to a page its container no longer
|
|
654
|
+
shows: the page is about to be taken down, or hidden until the movement is
|
|
655
|
+
over (on the list of thirty cards: about 450 component renders gone from the
|
|
656
|
+
callback, first frame 10–20 ms sooner at 4× CPU, nothing measurable at full
|
|
657
|
+
speed). A component reading the action's signals itself
|
|
658
|
+
(`action.dataSignal.value`) does not get that, and re-renders in the hidden
|
|
659
|
+
page.
|
|
660
|
+
- **Restyling the page arriving more than once.** Built inside the update
|
|
661
|
+
callback, it is styled as it lands; anything written afterwards that its
|
|
662
|
+
elements inherit, or that re-selects them, restyles all of them again. On the
|
|
663
|
+
list of thirty cards (~7,000 elements) three did, 16–19 ms each at full
|
|
664
|
+
speed: the height of a list group's sticky label, written on the group once
|
|
665
|
+
measured (navi keeps it from being inherited, which took the movement 17 ms
|
|
666
|
+
sooner); the room a fixed bar takes (`--navi-fixed-bar-space-*`, feeding the
|
|
667
|
+
inherited `--navi-safe-area-inset-*`), which changes on the root when a bar
|
|
668
|
+
arrives or leaves with the page; and an app's `:has()` rule on the list
|
|
669
|
+
container over its rows (`.navi_list_container:has(.card) …`), evaluated
|
|
670
|
+
again as the rows land. An attribute the app sets on the container says the
|
|
671
|
+
same thing as that `:has()`, for nothing.
|
|
653
672
|
- **Re-rendering what did not change, on the press itself.** A component that
|
|
654
673
|
only wants a yes or a no reads a computed flag, not the signal it is derived
|
|
655
674
|
from: a list whose rows each read the count of runs in flight re-rendered
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.417",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"preact": "^11.0.0-rc.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jsenv/dom": "0.17.
|
|
37
|
+
"@jsenv/dom": "0.17.84",
|
|
38
38
|
"@jsenv/humanize": "1.8.6",
|
|
39
39
|
"@jsenv/validity": "0.4.5"
|
|
40
40
|
},
|