@ecopages/react-router 0.2.0-alpha.1
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/browser.d.ts +13 -0
- package/browser.js +11 -0
- package/browser.ts +17 -0
- package/package.json +42 -0
- package/src/adapter.d.ts +28 -0
- package/src/adapter.js +22 -0
- package/src/adapter.ts +48 -0
- package/src/context.d.ts +16 -0
- package/src/context.js +11 -0
- package/src/context.ts +25 -0
- package/src/head-morpher.d.ts +15 -0
- package/src/head-morpher.js +94 -0
- package/src/head-morpher.ts +170 -0
- package/src/index.d.ts +14 -0
- package/src/index.js +13 -0
- package/src/index.ts +21 -0
- package/src/manage-scroll.d.ts +17 -0
- package/src/manage-scroll.js +25 -0
- package/src/manage-scroll.ts +47 -0
- package/src/navigation.d.ts +65 -0
- package/src/navigation.js +120 -0
- package/src/navigation.ts +247 -0
- package/src/props-script.d.ts +11 -0
- package/src/props-script.js +11 -0
- package/src/props-script.ts +19 -0
- package/src/router.d.ts +73 -0
- package/src/router.js +225 -0
- package/src/router.ts +348 -0
- package/src/scroll-persist.d.ts +40 -0
- package/src/scroll-persist.js +57 -0
- package/src/scroll-persist.ts +96 -0
- package/src/styles.css +200 -0
- package/src/types.d.ts +49 -0
- package/src/types.js +12 -0
- package/src/types.ts +64 -0
- package/src/view-transition-manager.d.ts +5 -0
- package/src/view-transition-manager.js +16 -0
- package/src/view-transition-manager.ts +30 -0
- package/src/view-transition-utils.d.ts +13 -0
- package/src/view-transition-utils.js +60 -0
- package/src/view-transition-utils.ts +95 -0
package/src/styles.css
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default View Transition CSS for EcoPages React Router
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This file is not exported from the package.
|
|
5
|
+
* JSR does not support CSS exports yet: https://github.com/jsr-io/jsr/issues/293
|
|
6
|
+
*
|
|
7
|
+
* Use the ecopages npm package instead:
|
|
8
|
+
* @import 'ecopages/css/view-transitions.css';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* Disable root animation to prevent full page flash during transitions */
|
|
12
|
+
::view-transition-old(root),
|
|
13
|
+
::view-transition-new(root) {
|
|
14
|
+
animation: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Shared element transitions animate position/size smoothly */
|
|
18
|
+
::view-transition-group(*) {
|
|
19
|
+
animation-duration: 180ms;
|
|
20
|
+
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Prevent transitioning elements from affecting layout of non-transitioning elements */
|
|
24
|
+
::view-transition-image-pair(*) {
|
|
25
|
+
isolation: isolate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Respect reduced motion preference */
|
|
29
|
+
@media (prefers-reduced-motion: reduce) {
|
|
30
|
+
::view-transition-group(*),
|
|
31
|
+
::view-transition-old(*),
|
|
32
|
+
::view-transition-new(*) {
|
|
33
|
+
animation: none !important;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Slide transition for elements with view-transition-name: eco-slide */
|
|
38
|
+
::view-transition-image-pair(eco-slide) {
|
|
39
|
+
isolation: isolate;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
::view-transition-old(eco-slide),
|
|
43
|
+
::view-transition-new(eco-slide) {
|
|
44
|
+
animation-duration: 180ms;
|
|
45
|
+
animation-timing-function: ease-out;
|
|
46
|
+
mix-blend-mode: normal;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
::view-transition-old(eco-slide) {
|
|
50
|
+
animation-name: eco-slide-out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
::view-transition-new(eco-slide) {
|
|
54
|
+
animation-name: eco-slide-in;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes eco-slide-out {
|
|
58
|
+
from {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
transform: translateX(0);
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
transform: translateX(-30px);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@keyframes eco-slide-in {
|
|
69
|
+
from {
|
|
70
|
+
opacity: 0;
|
|
71
|
+
transform: translateX(30px);
|
|
72
|
+
}
|
|
73
|
+
to {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
transform: translateX(0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Persistent elements should not animate */
|
|
80
|
+
[data-eco-persist] {
|
|
81
|
+
view-transition-name: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Declarative transition attributes */
|
|
85
|
+
[data-eco-transition='slide'] {
|
|
86
|
+
view-transition-name: eco-slide;
|
|
87
|
+
}
|
|
88
|
+
[data-eco-transition='fade'] {
|
|
89
|
+
view-transition-name: eco-fade;
|
|
90
|
+
}
|
|
91
|
+
[data-eco-transition='zoom'] {
|
|
92
|
+
view-transition-name: eco-zoom;
|
|
93
|
+
}
|
|
94
|
+
[data-eco-transition='slide-up'] {
|
|
95
|
+
view-transition-name: eco-slide-up;
|
|
96
|
+
}
|
|
97
|
+
[data-eco-transition='slide-down'] {
|
|
98
|
+
view-transition-name: eco-slide-down;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* FADE */
|
|
102
|
+
::view-transition-old(eco-fade) {
|
|
103
|
+
animation: eco-fade-out 180ms ease-out both;
|
|
104
|
+
}
|
|
105
|
+
::view-transition-new(eco-fade) {
|
|
106
|
+
animation: eco-fade-in 180ms ease-out both;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ZOOM */
|
|
110
|
+
::view-transition-image-pair(eco-zoom) {
|
|
111
|
+
isolation: isolate;
|
|
112
|
+
}
|
|
113
|
+
::view-transition-old(eco-zoom) {
|
|
114
|
+
animation: eco-zoom-out 180ms ease-in both;
|
|
115
|
+
}
|
|
116
|
+
::view-transition-new(eco-zoom) {
|
|
117
|
+
animation: eco-zoom-in 180ms ease-out both;
|
|
118
|
+
}
|
|
119
|
+
@keyframes eco-zoom-out {
|
|
120
|
+
from {
|
|
121
|
+
opacity: 1;
|
|
122
|
+
transform: scale(1);
|
|
123
|
+
}
|
|
124
|
+
to {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
transform: scale(0.9);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
@keyframes eco-zoom-in {
|
|
130
|
+
from {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
transform: scale(0.9);
|
|
133
|
+
}
|
|
134
|
+
to {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
transform: scale(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* SLIDE UP */
|
|
141
|
+
::view-transition-image-pair(eco-slide-up) {
|
|
142
|
+
isolation: isolate;
|
|
143
|
+
}
|
|
144
|
+
::view-transition-old(eco-slide-up) {
|
|
145
|
+
animation: eco-slide-up-out 180ms ease-in both;
|
|
146
|
+
}
|
|
147
|
+
::view-transition-new(eco-slide-up) {
|
|
148
|
+
animation: eco-slide-up-in 180ms ease-out both;
|
|
149
|
+
}
|
|
150
|
+
@keyframes eco-slide-up-out {
|
|
151
|
+
from {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
transform: translateY(0);
|
|
154
|
+
}
|
|
155
|
+
to {
|
|
156
|
+
opacity: 0;
|
|
157
|
+
transform: translateY(-30px);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
@keyframes eco-slide-up-in {
|
|
161
|
+
from {
|
|
162
|
+
opacity: 0;
|
|
163
|
+
transform: translateY(30px);
|
|
164
|
+
}
|
|
165
|
+
to {
|
|
166
|
+
opacity: 1;
|
|
167
|
+
transform: translateY(0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* SLIDE DOWN */
|
|
172
|
+
::view-transition-image-pair(eco-slide-down) {
|
|
173
|
+
isolation: isolate;
|
|
174
|
+
}
|
|
175
|
+
::view-transition-old(eco-slide-down) {
|
|
176
|
+
animation: eco-slide-down-out 180ms ease-in both;
|
|
177
|
+
}
|
|
178
|
+
::view-transition-new(eco-slide-down) {
|
|
179
|
+
animation: eco-slide-down-in 180ms ease-out both;
|
|
180
|
+
}
|
|
181
|
+
@keyframes eco-slide-down-out {
|
|
182
|
+
from {
|
|
183
|
+
opacity: 1;
|
|
184
|
+
transform: translateY(0);
|
|
185
|
+
}
|
|
186
|
+
to {
|
|
187
|
+
opacity: 0;
|
|
188
|
+
transform: translateY(30px);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
@keyframes eco-slide-down-in {
|
|
192
|
+
from {
|
|
193
|
+
opacity: 0;
|
|
194
|
+
transform: translateY(-30px);
|
|
195
|
+
}
|
|
196
|
+
to {
|
|
197
|
+
opacity: 1;
|
|
198
|
+
transform: translateY(0);
|
|
199
|
+
}
|
|
200
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the EcoRouter
|
|
3
|
+
*/
|
|
4
|
+
export interface EcoRouterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* CSS selector for links to intercept
|
|
7
|
+
* @default 'a[href]'
|
|
8
|
+
*/
|
|
9
|
+
linkSelector?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Attribute that forces a full page reload when present on a link
|
|
12
|
+
* @default 'data-eco-reload'
|
|
13
|
+
*/
|
|
14
|
+
reloadAttribute?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to use the View Transitions API for page animations.
|
|
17
|
+
* When enabled and supported, page transitions animate using CSS view-transition pseudo-elements.
|
|
18
|
+
* Falls back to instant swap if not supported by the browser.
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
viewTransitions?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Enable debug logging to console
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Scroll behavior after navigation:
|
|
29
|
+
* - 'top': Always scroll to top (default)
|
|
30
|
+
* - 'preserve': Keep current scroll position
|
|
31
|
+
* - 'auto': Scroll to top only when pathname changes
|
|
32
|
+
*/
|
|
33
|
+
scrollBehavior?: 'top' | 'preserve' | 'auto';
|
|
34
|
+
/**
|
|
35
|
+
* Whether to use smooth scrolling during navigation.
|
|
36
|
+
* If true, uses 'smooth' behavior. If false, uses 'instant' behavior.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
smoothScroll?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Keep layouts mounted between navigations.
|
|
42
|
+
* When enabled, if consecutive pages share the same layout component,
|
|
43
|
+
* the layout stays mounted and only the page content re-renders.
|
|
44
|
+
* This preserves layout state including scroll positions.
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
persistLayouts?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare const DEFAULT_OPTIONS: Required<EcoRouterOptions>;
|
package/src/types.js
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the EcoRouter
|
|
3
|
+
*/
|
|
4
|
+
export interface EcoRouterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* CSS selector for links to intercept
|
|
7
|
+
* @default 'a[href]'
|
|
8
|
+
*/
|
|
9
|
+
linkSelector?: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Attribute that forces a full page reload when present on a link
|
|
13
|
+
* @default 'data-eco-reload'
|
|
14
|
+
*/
|
|
15
|
+
reloadAttribute?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether to use the View Transitions API for page animations.
|
|
19
|
+
* When enabled and supported, page transitions animate using CSS view-transition pseudo-elements.
|
|
20
|
+
* Falls back to instant swap if not supported by the browser.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
viewTransitions?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enable debug logging to console
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
debug?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Scroll behavior after navigation:
|
|
33
|
+
* - 'top': Always scroll to top (default)
|
|
34
|
+
* - 'preserve': Keep current scroll position
|
|
35
|
+
* - 'auto': Scroll to top only when pathname changes
|
|
36
|
+
*/
|
|
37
|
+
scrollBehavior?: 'top' | 'preserve' | 'auto';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether to use smooth scrolling during navigation.
|
|
41
|
+
* If true, uses 'smooth' behavior. If false, uses 'instant' behavior.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
smoothScroll?: boolean;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Keep layouts mounted between navigations.
|
|
48
|
+
* When enabled, if consecutive pages share the same layout component,
|
|
49
|
+
* the layout stays mounted and only the page content re-renders.
|
|
50
|
+
* This preserves layout state including scroll positions.
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
persistLayouts?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const DEFAULT_OPTIONS: Required<EcoRouterOptions> = {
|
|
57
|
+
linkSelector: 'a[href]',
|
|
58
|
+
reloadAttribute: 'data-eco-reload',
|
|
59
|
+
viewTransitions: true,
|
|
60
|
+
debug: false,
|
|
61
|
+
scrollBehavior: 'top',
|
|
62
|
+
smoothScroll: false,
|
|
63
|
+
persistLayouts: true,
|
|
64
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function isViewTransitionSupported() {
|
|
2
|
+
return "startViewTransition" in document;
|
|
3
|
+
}
|
|
4
|
+
async function withViewTransition(callback) {
|
|
5
|
+
if (!isViewTransitionSupported()) {
|
|
6
|
+
callback();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const transition = document.startViewTransition(() => {
|
|
10
|
+
callback();
|
|
11
|
+
});
|
|
12
|
+
await transition.finished;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
withViewTransition
|
|
16
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages View Transition API integration
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type ViewTransitionDocument = Document & {
|
|
7
|
+
startViewTransition: (callback: () => void | Promise<void>) => {
|
|
8
|
+
finished: Promise<void>;
|
|
9
|
+
ready: Promise<void>;
|
|
10
|
+
updateCallbackDone: Promise<void>;
|
|
11
|
+
skipTransition(): void;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function isViewTransitionSupported(): boolean {
|
|
16
|
+
return 'startViewTransition' in document;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function withViewTransition(callback: () => void): Promise<void> {
|
|
20
|
+
if (!isViewTransitionSupported()) {
|
|
21
|
+
callback();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const transition = (document as ViewTransitionDocument).startViewTransition(() => {
|
|
26
|
+
callback();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await transition.finished;
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View transition utilities for applying transition names from data attributes.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Applies view-transition-name CSS property to elements with data-view-transition attribute.
|
|
7
|
+
* Also handles data-view-transition-animate="morph" (default) logic and custom duration.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyViewTransitionNames(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Clears view-transition-name CSS property from all elements.
|
|
12
|
+
*/
|
|
13
|
+
export declare function clearViewTransitionNames(): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const VIEW_TRANSITION_ATTR = "data-view-transition";
|
|
2
|
+
const VIEW_TRANSITION_ANIMATE_ATTR = "data-view-transition-animate";
|
|
3
|
+
const VIEW_TRANSITION_DURATION_ATTR = "data-view-transition-duration";
|
|
4
|
+
function applyViewTransitionNames() {
|
|
5
|
+
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
6
|
+
const morphNames = [];
|
|
7
|
+
const customDurations = [];
|
|
8
|
+
elements.forEach((el) => {
|
|
9
|
+
const name = el.getAttribute(VIEW_TRANSITION_ATTR);
|
|
10
|
+
if (name) {
|
|
11
|
+
el.style.viewTransitionName = name;
|
|
12
|
+
const animate = el.getAttribute(VIEW_TRANSITION_ANIMATE_ATTR);
|
|
13
|
+
if (animate !== "fade") {
|
|
14
|
+
morphNames.push(name);
|
|
15
|
+
}
|
|
16
|
+
const duration = el.getAttribute(VIEW_TRANSITION_DURATION_ATTR);
|
|
17
|
+
if (duration) {
|
|
18
|
+
customDurations.push({ name, duration });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
if (morphNames.length > 0 || customDurations.length > 0) {
|
|
23
|
+
injectDynamicStyles(morphNames, customDurations);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function injectDynamicStyles(morphNames, customDurations) {
|
|
27
|
+
let styleEl = document.getElementById("eco-vt-dynamic-styles");
|
|
28
|
+
if (!styleEl) {
|
|
29
|
+
styleEl = document.createElement("style");
|
|
30
|
+
styleEl.id = "eco-vt-dynamic-styles";
|
|
31
|
+
styleEl.setAttribute("data-eco-persist", "");
|
|
32
|
+
document.head.appendChild(styleEl);
|
|
33
|
+
}
|
|
34
|
+
const morphCss = morphNames.map(
|
|
35
|
+
(name) => `
|
|
36
|
+
::view-transition-old(${name}) { display: none !important; }
|
|
37
|
+
::view-transition-new(${name}) { animation: none !important; opacity: 1 !important; }
|
|
38
|
+
`
|
|
39
|
+
).join("\n");
|
|
40
|
+
const durationCss = customDurations.map(
|
|
41
|
+
({ name, duration }) => `
|
|
42
|
+
::view-transition-group(${name}) { animation-duration: ${duration} !important; }
|
|
43
|
+
`
|
|
44
|
+
).join("\n");
|
|
45
|
+
styleEl.textContent = morphCss + "\n" + durationCss;
|
|
46
|
+
}
|
|
47
|
+
function clearViewTransitionNames() {
|
|
48
|
+
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
49
|
+
elements.forEach((el) => {
|
|
50
|
+
el.style.viewTransitionName = "";
|
|
51
|
+
});
|
|
52
|
+
const styleEl = document.getElementById("eco-vt-dynamic-styles");
|
|
53
|
+
if (styleEl) {
|
|
54
|
+
styleEl.textContent = "";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
applyViewTransitionNames,
|
|
59
|
+
clearViewTransitionNames
|
|
60
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View transition utilities for applying transition names from data attributes.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const VIEW_TRANSITION_ATTR = 'data-view-transition';
|
|
7
|
+
const VIEW_TRANSITION_ANIMATE_ATTR = 'data-view-transition-animate';
|
|
8
|
+
const VIEW_TRANSITION_DURATION_ATTR = 'data-view-transition-duration';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Applies view-transition-name CSS property to elements with data-view-transition attribute.
|
|
12
|
+
* Also handles data-view-transition-animate="morph" (default) logic and custom duration.
|
|
13
|
+
*/
|
|
14
|
+
export function applyViewTransitionNames(): void {
|
|
15
|
+
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
16
|
+
const morphNames: string[] = [];
|
|
17
|
+
const customDurations: { name: string; duration: string }[] = [];
|
|
18
|
+
|
|
19
|
+
elements.forEach((el) => {
|
|
20
|
+
const name = el.getAttribute(VIEW_TRANSITION_ATTR);
|
|
21
|
+
if (name) {
|
|
22
|
+
(el as HTMLElement).style.viewTransitionName = name;
|
|
23
|
+
/**
|
|
24
|
+
* By default, we apply a clean geometric morph (no cross-fade/ghosting).
|
|
25
|
+
* The 'fade' value is reserved for opting out of this behavior.
|
|
26
|
+
*/
|
|
27
|
+
const animate = el.getAttribute(VIEW_TRANSITION_ANIMATE_ATTR);
|
|
28
|
+
if (animate !== 'fade') {
|
|
29
|
+
morphNames.push(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const duration = el.getAttribute(VIEW_TRANSITION_DURATION_ATTR);
|
|
33
|
+
if (duration) {
|
|
34
|
+
customDurations.push({ name, duration });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (morphNames.length > 0 || customDurations.length > 0) {
|
|
40
|
+
injectDynamicStyles(morphNames, customDurations);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Injects dynamic CSS to hide old snapshots and apply custom durations.
|
|
46
|
+
*/
|
|
47
|
+
function injectDynamicStyles(morphNames: string[], customDurations: { name: string; duration: string }[]) {
|
|
48
|
+
let styleEl = document.getElementById('eco-vt-dynamic-styles');
|
|
49
|
+
if (!styleEl) {
|
|
50
|
+
styleEl = document.createElement('style');
|
|
51
|
+
styleEl.id = 'eco-vt-dynamic-styles';
|
|
52
|
+
/**
|
|
53
|
+
* Persistence is required to prevent the head-morpher from removing this style tag during navigation.
|
|
54
|
+
*/
|
|
55
|
+
styleEl.setAttribute('data-eco-persist', '');
|
|
56
|
+
document.head.appendChild(styleEl);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const morphCss = morphNames
|
|
60
|
+
.map(
|
|
61
|
+
(name) => `
|
|
62
|
+
::view-transition-old(${name}) { display: none !important; }
|
|
63
|
+
::view-transition-new(${name}) { animation: none !important; opacity: 1 !important; }
|
|
64
|
+
`,
|
|
65
|
+
)
|
|
66
|
+
.join('\n');
|
|
67
|
+
|
|
68
|
+
const durationCss = customDurations
|
|
69
|
+
.map(
|
|
70
|
+
({ name, duration }) => `
|
|
71
|
+
::view-transition-group(${name}) { animation-duration: ${duration} !important; }
|
|
72
|
+
`,
|
|
73
|
+
)
|
|
74
|
+
.join('\n');
|
|
75
|
+
|
|
76
|
+
styleEl.textContent = morphCss + '\n' + durationCss;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Clears view-transition-name CSS property from all elements.
|
|
81
|
+
*/
|
|
82
|
+
export function clearViewTransitionNames(): void {
|
|
83
|
+
const elements = document.querySelectorAll(`[${VIEW_TRANSITION_ATTR}]`);
|
|
84
|
+
elements.forEach((el) => {
|
|
85
|
+
(el as HTMLElement).style.viewTransitionName = '';
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Cleanup dynamic styles to ensure a clean slate for the next transition.
|
|
90
|
+
*/
|
|
91
|
+
const styleEl = document.getElementById('eco-vt-dynamic-styles');
|
|
92
|
+
if (styleEl) {
|
|
93
|
+
styleEl.textContent = '';
|
|
94
|
+
}
|
|
95
|
+
}
|