@uniweb/runtime 1.3.3 → 1.3.4
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 +4 -4
- package/src/carry-animations.js +50 -0
- package/src/index.jsx +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
"node": ">=22.12"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@uniweb/
|
|
40
|
-
"@uniweb/
|
|
39
|
+
"@uniweb/theming": "~1.2.1",
|
|
40
|
+
"@uniweb/core": "~1.5.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@vitejs/plugin-react": "^4.5.2",
|
|
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.13.
|
|
47
|
+
"@uniweb/build": "1.13.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page's CSS animations carry on across the browser's first render.
|
|
3
|
+
*
|
|
4
|
+
* ⭐ The browser renders over a prerendered page rather than hydrating it (`createRoot`, `index.jsx`),
|
|
5
|
+
* and a CSS animation's clock belongs to its element. So the commit that replaces the page restarts
|
|
6
|
+
* every animation in it, and a page's first commit usually lands while its entrance is still running:
|
|
7
|
+
* a hero already on screen would drop back to invisible and play again. `uniweb dev` never prerenders,
|
|
8
|
+
* so a foundation's author never sees it.
|
|
9
|
+
*
|
|
10
|
+
* Each animation of the new page takes as its start the moment the old page was first painted, which
|
|
11
|
+
* is when its twin started, so it goes on from where its twin was. The observer fires after the commit
|
|
12
|
+
* and before the browser paints, so no frame shows a restart. It disconnects at that first commit, so
|
|
13
|
+
* a later navigation is left as it was.
|
|
14
|
+
*
|
|
15
|
+
* ⭐ The time of first paint rather than the old animations' own start times, read before the commit:
|
|
16
|
+
* an animation that has ended with no fill is gone from `getAnimations()`, and its twin would play
|
|
17
|
+
* again.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ Only a running animation on the document's timeline:
|
|
20
|
+
* - a paused one is waiting for something, like a reveal that starts when its section scrolls into
|
|
21
|
+
* view, and setting a start time would start it;
|
|
22
|
+
* - a scroll-driven one has a timeline of its own, whose time is no moment on this clock.
|
|
23
|
+
*
|
|
24
|
+
* It reaches CSS animations alone. One a component starts from JavaScript when it mounts never ran on
|
|
25
|
+
* the prerendered page: it starts at boot, over whatever that page has shown by then.
|
|
26
|
+
*
|
|
27
|
+
* Nothing to carry: an empty root (`uniweb dev`, a page served without a prerender), and a page not
|
|
28
|
+
* painted yet, whose first render is the first thing a visitor sees. That can include a page painted
|
|
29
|
+
* a frame ago, which the browser has not reported yet: its animations have hardly begun.
|
|
30
|
+
*
|
|
31
|
+
* @param {Element} root - The element React is about to render into.
|
|
32
|
+
*/
|
|
33
|
+
export function carryAnimations(root) {
|
|
34
|
+
if (!root.firstChild || !document.getAnimations) return
|
|
35
|
+
new MutationObserver((_, watch) => {
|
|
36
|
+
watch.disconnect()
|
|
37
|
+
const painted = performance.getEntriesByName('first-contentful-paint')[0]?.startTime
|
|
38
|
+
if (painted == null) return
|
|
39
|
+
for (const animation of document.getAnimations()) {
|
|
40
|
+
if (
|
|
41
|
+
animation.animationName &&
|
|
42
|
+
animation.playState === 'running' &&
|
|
43
|
+
animation.timeline === document.timeline &&
|
|
44
|
+
root.contains(animation.effect?.target)
|
|
45
|
+
) {
|
|
46
|
+
animation.startTime = painted
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}).observe(root, { childList: true })
|
|
50
|
+
}
|
package/src/index.jsx
CHANGED
|
@@ -12,6 +12,7 @@ import { initUniweb, decodeData } from './setup.js'
|
|
|
12
12
|
import { wireTracker } from './wire-foundation.js'
|
|
13
13
|
import { loadFoundation, loadExtensions } from './foundation-loader.js'
|
|
14
14
|
import { initAppearance } from './appearance.js'
|
|
15
|
+
import { carryAnimations } from './carry-animations.js'
|
|
15
16
|
import RuntimeProvider from './RuntimeProvider.jsx'
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -146,6 +147,8 @@ async function initRuntime(foundationSource, options = {}) {
|
|
|
146
147
|
|
|
147
148
|
const app = <RuntimeProvider basename={routerBasename} development={development} />
|
|
148
149
|
|
|
150
|
+
// Armed before the render it watches for: the one that replaces a prerendered page.
|
|
151
|
+
carryAnimations(container)
|
|
149
152
|
createRoot(container).render(app)
|
|
150
153
|
|
|
151
154
|
// Log success
|