@jsenv/navi 0.29.413 → 0.29.415
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 +555 -339
- package/dist/dev/jsenv_navi.js.map +21 -17
- package/dist/jsenv_navi.js +548 -336
- package/dist/jsenv_navi.js.map +21 -17
- package/docs/AI_INSTRUCTIONS.md +10 -5
- package/docs/autofocus.md +19 -0
- package/docs/route_transitions.md +101 -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.
|
package/docs/autofocus.md
CHANGED
|
@@ -15,6 +15,7 @@ at the end of this page.
|
|
|
15
15
|
- [What a field says about itself](#what-a-field-says-about-itself)
|
|
16
16
|
- [When the opening places nothing](#when-the-opening-places-nothing)
|
|
17
17
|
- [The ring is decided too](#the-ring-is-decided-too)
|
|
18
|
+
- [When a popup closes](#when-a-popup-closes)
|
|
18
19
|
- [Moving the focus yourself](#moving-the-focus-yourself)
|
|
19
20
|
|
|
20
21
|
## What we want
|
|
@@ -202,6 +203,24 @@ focused while the input is). Style the standard name and you get that for free.
|
|
|
202
203
|
}
|
|
203
204
|
```
|
|
204
205
|
|
|
206
|
+
## When a popup closes
|
|
207
|
+
|
|
208
|
+
The focus goes back to what held it when the popup opened — the trigger, most
|
|
209
|
+
of the time — so the user carries on where they were.
|
|
210
|
+
|
|
211
|
+
A popup that closes because it **leaves the tree** — its component unmounted,
|
|
212
|
+
the page holding it left — gives the focus back once that change is over, and
|
|
213
|
+
only to an element still in the document: the trigger often leaves in the same
|
|
214
|
+
render, and focusing it just before it goes would be focusing nothing (and
|
|
215
|
+
making its removal cost more). It gives the focus back only into a void, too:
|
|
216
|
+
when something else took the focus meanwhile — the page arriving, focusing a
|
|
217
|
+
field of its own — that one keeps it.
|
|
218
|
+
|
|
219
|
+
So `{editing && <Dialog open>…</Dialog>}` unmounted from a button inside it
|
|
220
|
+
lands the focus back on the button that opened it, and a dialog left along
|
|
221
|
+
with the page around it lands it nowhere (`<body>`), exactly as if the page
|
|
222
|
+
had simply been removed.
|
|
223
|
+
|
|
205
224
|
## Moving the focus yourself
|
|
206
225
|
|
|
207
226
|
Prefer saying where the focus belongs over moving it: `autoFocus` on the field
|
|
@@ -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,49 @@ 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
|
+
- **The focus it holds goes where its removal would have sent it** — to no
|
|
571
|
+
element — when it is hidden: a hidden element keeps the focus otherwise,
|
|
572
|
+
and would receive the keys pressed during the movement.
|
|
573
|
+
- **Never while a modal dialog is open.** An open modal keeps everything but
|
|
574
|
+
itself out of reach — the page arriving could not even take the focus — and
|
|
575
|
+
one in a page kept while leaving would stay open for the whole movement. The
|
|
576
|
+
page is then taken down at once, as without a movement.
|
|
577
|
+
- **Only where the page would have been taken down anyway.** A page rendered by
|
|
578
|
+
the same element on both sides — two routes, one component — is the same
|
|
579
|
+
instance carried over, as without a movement.
|
|
580
|
+
|
|
519
581
|
## Waiting for a navigation: the address is not the page
|
|
520
582
|
|
|
521
583
|
A navigation changes the URL first and the screen after — always. Under a
|
|
@@ -559,10 +621,48 @@ without a movement is a real one every time. No thumb moves in one frame; an
|
|
|
559
621
|
automated click continues in the same millisecond. This is a testing trap, not
|
|
560
622
|
a user-facing behaviour.
|
|
561
623
|
|
|
624
|
+
## What a transition costs
|
|
625
|
+
|
|
626
|
+
A transition adds one frame to a navigation: the browser photographs the page
|
|
627
|
+
being left at its next rendering opportunity, and the page arriving is built
|
|
628
|
+
only then, inside the update callback. Anything beyond that is avoidable, and
|
|
629
|
+
each of these was measured doing it on a list of thirty cards (~4,700
|
|
630
|
+
elements):
|
|
631
|
+
|
|
632
|
+
- **Restyling the whole page being left in the frame it is photographed** —
|
|
633
|
+
17–21 ms at full speed, 60–90 ms at 4× CPU. Two browser facts cause it: a
|
|
634
|
+
`::highlight()` rule every element matches, and a custom property changed on
|
|
635
|
+
the root as the movement starts (see
|
|
636
|
+
[view_transitions.md](./view_transitions.md#what-makes-a-transition-restyle-the-whole-document)).
|
|
637
|
+
navi's movements declare nothing inheritable on the root; a custom type
|
|
638
|
+
publishing values of its own writes them on `::view-transition` (see
|
|
639
|
+
[Custom movements](#custom-movements)).
|
|
640
|
+
- **The page being left reacting to what the navigation changed.** Every
|
|
641
|
+
component in it that re-renders on the change does so inside the update
|
|
642
|
+
callback, next to the page arriving being built. A link or a button pointing
|
|
643
|
+
at a route (`<Link route>`, `<Button route>`) reads one flag — whether it is
|
|
644
|
+
current — and stays still unless that flips: a profile opening leaves the
|
|
645
|
+
links to a hundred other profiles exactly as they were (on the list of
|
|
646
|
+
thirty cards: 90 re-renders gone, first frame 58 → 41 ms). What else reacts
|
|
647
|
+
is worth keeping to what has to — measured: every card of the page being
|
|
648
|
+
left re-rendering when the page arriving loads its own games into the store
|
|
649
|
+
the cards read theirs from.
|
|
650
|
+
- **Re-rendering what did not change, on the press itself.** A component that
|
|
651
|
+
only wants a yes or a no reads a computed flag, not the signal it is derived
|
|
652
|
+
from: a list whose rows each read the count of runs in flight re-rendered
|
|
653
|
+
all thirty cards twice when a link in one of them was pressed — 774
|
|
654
|
+
component renders before the navigation even started, 18 once the rows read
|
|
655
|
+
whether the list is full instead.
|
|
656
|
+
- **Nothing is gained by starting at the press.** The click's own work is the
|
|
657
|
+
navigation, and the picture can only be taken on the frame after it; work
|
|
658
|
+
moved to the pointer-down is paid again, for nothing, by every press that
|
|
659
|
+
turns into a scroll.
|
|
660
|
+
|
|
562
661
|
## The rest, briefly
|
|
563
662
|
|
|
564
663
|
- Pace: `--navi-route-transition-duration` (CSS, default 300ms) for everyone;
|
|
565
|
-
a per-relation `{ type, duration }` for one relation
|
|
664
|
+
a per-relation `{ type, duration }` for one relation, worn by that
|
|
665
|
+
movement's pictures only.
|
|
566
666
|
- The URL leads: transitions play on navigations somebody else started (a
|
|
567
667
|
`<Link>`, the back button, `history.back()`). Nothing here navigates.
|
|
568
668
|
- 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.415",
|
|
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
|
},
|