@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.
@@ -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. Read before
81
- animating a navigation, and before believing a symptom that only appears once
82
- a pair is animated.
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
- and the two frames that show the live document. Read before calling
88
- `document.startViewTransition`.
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
@@ -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.413",
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.82",
37
+ "@jsenv/dom": "0.17.83",
38
38
  "@jsenv/humanize": "1.8.6",
39
39
  "@jsenv/validity": "0.4.5"
40
40
  },