@uniweb/runtime 1.3.12 → 1.3.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,7 +44,7 @@
44
44
  "esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
45
45
  "vite": "^7.3.1",
46
46
  "vitest": "^4.1.7",
47
- "@uniweb/build": "1.14.1"
47
+ "@uniweb/build": "1.14.3"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": "^19.0.0",
@@ -5,7 +5,9 @@
5
5
  * each layout area a `view-transition-name` so the browser animates it on its
6
6
  * own — persistent chrome (header, sidebar, footer) morphs in place while
7
7
  * everything else, the body included, crossfades as the page. With no names the
8
- * whole layout (chrome included) would flicker on every navigation.
8
+ * whole layout (chrome included) would flicker on every navigation. An area with
9
+ * no height has nothing to capture, so a navigation folds it back into the page
10
+ * (`foldEmptyAreas`, `navigation.js`).
9
11
  *
10
12
  * ⛔ **The body takes no name by default, which is what keeps a phone usable.** A
11
13
  * named element is captured as one image. WebKit captures it whole, off-screen part
package/src/navigation.js CHANGED
@@ -74,6 +74,29 @@ function pageAt(to) {
74
74
  return website.getPage(route.split(/[?#]/)[0]) || null
75
75
  }
76
76
 
77
+ /**
78
+ * Folds an area with no height back into the page's crossfade, as a `null` in a layout's `transitions`
79
+ * would.
80
+ *
81
+ * ⛔ A named element is captured at its own size, so a box with no height is captured as nothing,
82
+ * whatever overflows it. A bar that overlays the page is that box: `fixed`, or `absolute` in a sticky
83
+ * box it doesn't fill. Its name took it out of the page's capture and its own capture drew nothing,
84
+ * so the bar was gone for the length of every transition and came back at the end. Measured
85
+ * 2026-11-02 in Chromium and WebKit.
86
+ *
87
+ * An area with nothing in it keeps its name, since a sidebar that fills on the next page should still
88
+ * move on its own. A folded name stays folded: React wrote it and has no reason to write it again,
89
+ * except on a new element, so this runs again once a page has committed, for a layout change.
90
+ */
91
+ function foldEmptyAreas() {
92
+ for (const area of document.querySelectorAll('[style*="view-transition-name"]')) {
93
+ const box = area.getClientRects()[0]
94
+ if (box && !box.height && [...area.querySelectorAll('*')].some((el) => el.getBoundingClientRect().height)) {
95
+ area.style.viewTransitionName = 'none'
96
+ }
97
+ }
98
+ }
99
+
77
100
  /** Whether `page`'s content can be shown now: `PageRenderer`'s own test. */
78
101
  function isReady(page) {
79
102
  return !page || !page.hasContent() || page.isContentLoaded()
@@ -206,11 +229,12 @@ export function navigateInApp(navigate, to, options) {
206
229
  navigate(to, options)
207
230
  return
208
231
  }
232
+ foldEmptyAreas()
209
233
  const transition = document.startViewTransition(() => {
210
234
  // Asked before navigating: a router that commits synchronously settles it from inside `navigate`.
211
235
  const committed = nextPageCommit()
212
236
  navigate(to, options)
213
- return committed
237
+ return committed.then(foldEmptyAreas)
214
238
  })
215
239
  // A transition the browser skips, for a duplicate `view-transition-name` or a newer
216
240
  // transition, still runs its callback, so the page is swapped all the same. The browser