@jsenv/navi 0.29.413 → 0.29.414
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 +379 -290
- package/dist/dev/jsenv_navi.js.map +10 -8
- package/dist/jsenv_navi.js +379 -290
- package/dist/jsenv_navi.js.map +10 -8
- package/docs/AI_INSTRUCTIONS.md +10 -5
- package/docs/route_transitions.md +90 -1
- package/docs/view_transitions.md +61 -0
- package/package.json +2 -2
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -77,15 +77,20 @@ to start when unsure which export solves a problem.
|
|
|
77
77
|
writing any routing code, and before scrolling to an element on arrival.
|
|
78
78
|
- `route_transitions.md` — a transition states a relation the user reads as a
|
|
79
79
|
map; a pair of routes is animated by `RouteTravel` or by a transition, never
|
|
80
|
-
both; a test waits for the page arriving, never for its address
|
|
81
|
-
|
|
82
|
-
a
|
|
80
|
+
both; a test waits for the page arriving, never for its address; the page
|
|
81
|
+
being left stays mounted, hidden, until the movement is over (so what it
|
|
82
|
+
registers globally must let the last arrival win); what a transition costs.
|
|
83
|
+
Read before animating a navigation, before writing a custom movement, and
|
|
84
|
+
before believing a symptom that only appears once a pair is animated.
|
|
83
85
|
- `view_transitions.md` — what the browser does to any transition an app
|
|
84
86
|
starts itself: names unique per document, the fallback fade, rendering
|
|
85
87
|
suspended for the whole callback, `ready` rejecting on a skip while
|
|
86
88
|
`finished` fulfills, the top layer painted only through the root's picture,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
the two frames that show the live document, what makes the start of a
|
|
90
|
+
transition restyle the whole document (an unscoped `::highlight()`, a value
|
|
91
|
+
changed on `:root`), and a hidden popup photographed on one side only. Read
|
|
92
|
+
before calling `document.startViewTransition`, and before putting a value on
|
|
93
|
+
`:root` for a movement.
|
|
89
94
|
- `drag_to_travel.md` — a pointer or a wheel pushing a whole screen aside, and
|
|
90
95
|
who owns a press several boxes want. Read before putting anything that reads the pointer
|
|
91
96
|
inside a box that travels.
|
|
@@ -23,7 +23,9 @@ Demos: [the movements](../src/nav/demos/route_transition/route_transition.html),
|
|
|
23
23
|
- [Two routes matching one url](#two-routes-matching-one-url)
|
|
24
24
|
- [Route transitions and `RouteTravel` — one pair, one system](#route-transitions-and-routetravel--one-pair-one-system)
|
|
25
25
|
- [A transition says nothing about data](#a-transition-says-nothing-about-data)
|
|
26
|
+
- [The page being left stays until its movement is over](#the-page-being-left-stays-until-its-movement-is-over)
|
|
26
27
|
- [Waiting for a navigation: the address is not the page](#waiting-for-a-navigation-the-address-is-not-the-page)
|
|
28
|
+
- [What a transition costs](#what-a-transition-costs)
|
|
27
29
|
- [The rest, briefly](#the-rest-briefly)
|
|
28
30
|
|
|
29
31
|
## What a transition is for
|
|
@@ -417,6 +419,23 @@ pages so the sheet comes over it (see the transition area above). The shipped
|
|
|
417
419
|
`cover-*` types say exactly this; a movement where neither page covers the
|
|
418
420
|
other publishes nothing.
|
|
419
421
|
|
|
422
|
+
Those three values are read by navi from the root, so they are written there,
|
|
423
|
+
and navi registers them as not inherited: writing them costs nothing. Any other
|
|
424
|
+
value a custom movement's pictures read goes on `::view-transition` instead:
|
|
425
|
+
|
|
426
|
+
```css
|
|
427
|
+
:root[data-navi-route-transition-type="spin"] {
|
|
428
|
+
&::view-transition {
|
|
429
|
+
--spin-turns: 0.5;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The pictures inherit it from there, and no element of the document does.
|
|
435
|
+
Written on the root, every element would, and the page being left would be
|
|
436
|
+
restyled in full in the frame it is photographed (see
|
|
437
|
+
[What a transition costs](#what-a-transition-costs)).
|
|
438
|
+
|
|
420
439
|
## Two routes matching one url
|
|
421
440
|
|
|
422
441
|
A relation is written between two pages, and it is resolved through **which page
|
|
@@ -516,6 +535,42 @@ below. See
|
|
|
516
535
|
[list_refresh.md](./list_refresh.md#who-decides-the-re-read--and-who-does-not)
|
|
517
536
|
for which source refreshes on a revisit and which does not.
|
|
518
537
|
|
|
538
|
+
## The page being left stays until its movement is over
|
|
539
|
+
|
|
540
|
+
The picture of the page being left is taken before the change; the page
|
|
541
|
+
arriving is built inside the transition's update callback, and everything done
|
|
542
|
+
there delays the first frame of the movement. Taking a page down is a large
|
|
543
|
+
part of it — every hook of every component in it cleaned up: on a page of
|
|
544
|
+
thirty game cards, about 67 of the callback's 90 ms with the CPU throttled 4×,
|
|
545
|
+
more than building the page arriving. Its picture already exists, so it only
|
|
546
|
+
has to stay out of the second one: a container that changes page during the
|
|
547
|
+
callback keeps the page it was showing, hidden (`display: none`), and takes it
|
|
548
|
+
down once the movement is over (`keepLeavingPages` in `nav/route.jsx`).
|
|
549
|
+
Measured on that page, first frame of the movement: 63 → 45 ms at full speed,
|
|
550
|
+
211 → 188 ms at 4×.
|
|
551
|
+
|
|
552
|
+
What that means for a page and for the application around it:
|
|
553
|
+
|
|
554
|
+
- **It is still mounted for the length of the movement.** Its effects keep
|
|
555
|
+
running, and it still reads the address: whatever re-renders when the URL
|
|
556
|
+
changes re-renders in it too, hidden (see
|
|
557
|
+
[What a transition costs](#what-a-transition-costs)).
|
|
558
|
+
- **It is taken down after the page arriving is up.** A cleanup that puts back
|
|
559
|
+
what it found — a document title, a class on `<body>`, a value in a shared
|
|
560
|
+
store — puts a stale value back over the new page's. Such a registration is
|
|
561
|
+
written as "the last to arrive wins": navi's `<Head>` keeps its titles as a
|
|
562
|
+
stack, and a slot shows the last `SlotFill` to arrive.
|
|
563
|
+
- **What it fills outside its own nodes leaves at once.** A `SlotFill` in a
|
|
564
|
+
page kept while leaving steps out of its slot, so a bar each page fills shows
|
|
565
|
+
the page arriving — or nothing, when that page fills none.
|
|
566
|
+
- **Its popups leave with it.** A popup it holds open is no longer rendered: it
|
|
567
|
+
is photographed on the old side only, leaves with its page, and is closed
|
|
568
|
+
when the page is taken down (see
|
|
569
|
+
[view_transitions.md](./view_transitions.md#a-hidden-element-is-not-photographed-even-in-the-top-layer)).
|
|
570
|
+
- **Only where the page would have been taken down anyway.** A page rendered by
|
|
571
|
+
the same element on both sides — two routes, one component — is the same
|
|
572
|
+
instance carried over, as without a movement.
|
|
573
|
+
|
|
519
574
|
## Waiting for a navigation: the address is not the page
|
|
520
575
|
|
|
521
576
|
A navigation changes the URL first and the screen after — always. Under a
|
|
@@ -559,10 +614,44 @@ without a movement is a real one every time. No thumb moves in one frame; an
|
|
|
559
614
|
automated click continues in the same millisecond. This is a testing trap, not
|
|
560
615
|
a user-facing behaviour.
|
|
561
616
|
|
|
617
|
+
## What a transition costs
|
|
618
|
+
|
|
619
|
+
A transition adds one frame to a navigation: the browser photographs the page
|
|
620
|
+
being left at its next rendering opportunity, and the page arriving is built
|
|
621
|
+
only then, inside the update callback. Anything beyond that is avoidable, and
|
|
622
|
+
each of these was measured doing it on a list of thirty cards (~4,700
|
|
623
|
+
elements):
|
|
624
|
+
|
|
625
|
+
- **Restyling the whole page being left in the frame it is photographed** —
|
|
626
|
+
17–21 ms at full speed, 60–90 ms at 4× CPU. Two browser facts cause it: a
|
|
627
|
+
`::highlight()` rule every element matches, and a custom property changed on
|
|
628
|
+
the root as the movement starts (see
|
|
629
|
+
[view_transitions.md](./view_transitions.md#what-makes-a-transition-restyle-the-whole-document)).
|
|
630
|
+
navi's movements declare nothing inheritable on the root; a custom type
|
|
631
|
+
publishing values of its own writes them on `::view-transition` (see
|
|
632
|
+
[Custom movements](#custom-movements)).
|
|
633
|
+
- **The page being left reacting to the address it no longer matches.** Every
|
|
634
|
+
component in it that re-renders on a URL change does so inside the update
|
|
635
|
+
callback, next to the page arriving being built — measured: about 2,500
|
|
636
|
+
component renders for the list of thirty cards, its links and its list
|
|
637
|
+
first. Taking the page down there instead would drop them, and still costs
|
|
638
|
+
more; what re-renders on the address is worth keeping to what has to.
|
|
639
|
+
- **Re-rendering what did not change, on the press itself.** A component that
|
|
640
|
+
only wants a yes or a no reads a computed flag, not the signal it is derived
|
|
641
|
+
from: a list whose rows each read the count of runs in flight re-rendered
|
|
642
|
+
all thirty cards twice when a link in one of them was pressed — 774
|
|
643
|
+
component renders before the navigation even started, 18 once the rows read
|
|
644
|
+
whether the list is full instead.
|
|
645
|
+
- **Nothing is gained by starting at the press.** The click's own work is the
|
|
646
|
+
navigation, and the picture can only be taken on the frame after it; work
|
|
647
|
+
moved to the pointer-down is paid again, for nothing, by every press that
|
|
648
|
+
turns into a scroll.
|
|
649
|
+
|
|
562
650
|
## The rest, briefly
|
|
563
651
|
|
|
564
652
|
- Pace: `--navi-route-transition-duration` (CSS, default 300ms) for everyone;
|
|
565
|
-
a per-relation `{ type, duration }` for one relation
|
|
653
|
+
a per-relation `{ type, duration }` for one relation, worn by that
|
|
654
|
+
movement's pictures only.
|
|
566
655
|
- The URL leads: transitions play on navigations somebody else started (a
|
|
567
656
|
`<Link>`, the back button, `history.back()`). Nothing here navigates.
|
|
568
657
|
- A browser without view transitions navigates with a cut. The app
|
package/docs/view_transitions.md
CHANGED
|
@@ -12,6 +12,8 @@ they are met once.
|
|
|
12
12
|
- [`ready` rejects when the transition is skipped, `finished` when the update fails](#ready-rejects-when-the-transition-is-skipped-finished-when-the-update-fails)
|
|
13
13
|
- [The top layer is painted through the root's picture](#the-top-layer-is-painted-through-the-roots-picture)
|
|
14
14
|
- [Two frames show the live document](#two-frames-show-the-live-document)
|
|
15
|
+
- [What makes a transition restyle the whole document](#what-makes-a-transition-restyle-the-whole-document)
|
|
16
|
+
- [A hidden element is not photographed, even in the top layer](#a-hidden-element-is-not-photographed-even-in-the-top-layer)
|
|
15
17
|
|
|
16
18
|
## A name is unique per document
|
|
17
19
|
|
|
@@ -129,6 +131,65 @@ Two consequences for the switch itself:
|
|
|
129
131
|
lands in the wrong place; and on the capture frame it is displayed while the
|
|
130
132
|
real thing is still there.
|
|
131
133
|
|
|
134
|
+
## What makes a transition restyle the whole document
|
|
135
|
+
|
|
136
|
+
Starting a transition restyles what changed and nothing more: in a bare page,
|
|
137
|
+
the capture of the old state restyles a handful of elements, however long the
|
|
138
|
+
page is. Two things make it restyle every one of them, in the frame the old
|
|
139
|
+
state is photographed — on a page of ~4,700 elements, 17–21 ms at full speed
|
|
140
|
+
and 60–90 ms with the CPU throttled 4×, added to every transition before
|
|
141
|
+
anything moves:
|
|
142
|
+
|
|
143
|
+
- **A `::highlight()` rule every element matches.** Written for the whole
|
|
144
|
+
document, `::highlight(x) { … }` makes Chrome restyle every element when a
|
|
145
|
+
view transition starts (Chrome 153, bare page: 10,009 of 10,009 elements; 6
|
|
146
|
+
once the rule is scoped). Scope it to the elements that hold the ranges —
|
|
147
|
+
`.results::highlight(x), .results ::highlight(x)` (both: a browser without
|
|
148
|
+
highlight inheritance only paints what the rule matches). navi's own search
|
|
149
|
+
highlight is scoped to list rows.
|
|
150
|
+
- **A custom property changed on `:root`.** Every element inherits it, so
|
|
151
|
+
changing one restyles the document — and a movement changes its values as it
|
|
152
|
+
starts. What the pictures read belongs on `::view-transition` instead: the
|
|
153
|
+
pseudo-element tree inherits from it, and no element of the document does.
|
|
154
|
+
|
|
155
|
+
```css
|
|
156
|
+
:root[data-my-movement] {
|
|
157
|
+
&::view-transition {
|
|
158
|
+
--my-distance: 120px;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
A value computed in JS cannot get there through an inline style, which
|
|
164
|
+
cannot target a pseudo-element: it goes in a rule of a sheet of its own,
|
|
165
|
+
`:root::view-transition {}`, set with `rule.style.setProperty()` (navi does
|
|
166
|
+
this in `nav/transition_values.js`).
|
|
167
|
+
|
|
168
|
+
A value JS has to read back is the exception: what JS reads off
|
|
169
|
+
`::view-transition` is not reliable outside a live transition —
|
|
170
|
+
`getComputedStyle(root, "::view-transition")` answers while none runs, but
|
|
171
|
+
in Chrome and WebKit alike it went on answering empty for rules added after
|
|
172
|
+
its first read. Such a value stays on `:root`, registered with `@property`
|
|
173
|
+
and `inherits: false` so that the document does not inherit it, and is
|
|
174
|
+
handed to the pictures explicitly: `--x: inherit` on `::view-transition` and
|
|
175
|
+
on the pseudo-elements below it.
|
|
176
|
+
|
|
177
|
+
Whatever does depend on the root is paid once, by the frame of the capture —
|
|
178
|
+
unless a style read follows a write on the root, which forces the restyle on
|
|
179
|
+
the spot, in the click. Read what the transition needs (names, rectangles)
|
|
180
|
+
before writing anything on the root.
|
|
181
|
+
|
|
182
|
+
## A hidden element is not photographed, even in the top layer
|
|
183
|
+
|
|
184
|
+
A popover or a modal `<dialog>` whose ancestor is `display: none` still matches
|
|
185
|
+
`:popover-open` or `:modal`, but it is not rendered: `checkVisibility()` is
|
|
186
|
+
false, its rectangle is empty, and a transition taking its pictures then has it
|
|
187
|
+
on the old side only — it leaves, as if it were gone. `isConnected` says
|
|
188
|
+
nothing about it; `checkVisibility()` is what tells which of the two states an
|
|
189
|
+
element is part of. navi relies on it to keep the page left by a route
|
|
190
|
+
transition mounted but out of the second picture (see
|
|
191
|
+
[route_transitions.md](./route_transitions.md#the-page-being-left-stays-until-its-movement-is-over)).
|
|
192
|
+
|
|
132
193
|
## See also
|
|
133
194
|
|
|
134
195
|
- [route_transitions.md](./route_transitions.md) — pages moving against each
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.414",
|
|
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.83",
|
|
38
38
|
"@jsenv/humanize": "1.8.6",
|
|
39
39
|
"@jsenv/validity": "0.4.5"
|
|
40
40
|
},
|