elvish-css 1.0.0
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/LICENSE +21 -0
- package/README.md +518 -0
- package/dist/elvish.css +2194 -0
- package/dist/elvish.d.ts +78 -0
- package/dist/elvish.esm.js +2185 -0
- package/dist/elvish.iife.js +2169 -0
- package/dist/elvish.min.css +9 -0
- package/dist/elvish.umd.js +2173 -0
- package/elvish.css +28 -0
- package/elvish.js +81 -0
- package/global/global.css +16 -0
- package/global/modern.css +305 -0
- package/global/reset.css +507 -0
- package/global/tokens.css +154 -0
- package/global/transitions.css +288 -0
- package/global/transitions.js +289 -0
- package/global/utilities.css +151 -0
- package/package.json +61 -0
- package/primitives/adleithian/adleithian.css +16 -0
- package/primitives/adleithian/adleithian.js +63 -0
- package/primitives/bau/bau.css +86 -0
- package/primitives/bau/bau.js +127 -0
- package/primitives/enedh/enedh.css +38 -0
- package/primitives/enedh/enedh.js +110 -0
- package/primitives/esgal/esgal.css +39 -0
- package/primitives/esgal/esgal.js +115 -0
- package/primitives/fano/fano.css +28 -0
- package/primitives/fano/fano.js +108 -0
- package/primitives/gant-thala/gant-thala.css +32 -0
- package/primitives/gant-thala/gant-thala.js +69 -0
- package/primitives/glan-tholl/glan-tholl.css +71 -0
- package/primitives/glan-tholl/glan-tholl.js +147 -0
- package/primitives/glan-veleg/glan-veleg.css +45 -0
- package/primitives/glan-veleg/glan-veleg.js +138 -0
- package/primitives/gonath/gonath.css +57 -0
- package/primitives/gonath/gonath.js +113 -0
- package/primitives/gwistindor/gwistindor.css +52 -0
- package/primitives/gwistindor/gwistindor.js +96 -0
- package/primitives/hath/hath.css +39 -0
- package/primitives/hath/hath.js +107 -0
- package/primitives/him/him.css +43 -0
- package/primitives/him/him.js +169 -0
- package/primitives/miriant/miriant.css +75 -0
- package/primitives/miriant/miriant.js +158 -0
- package/primitives/thann/thann.css +57 -0
- package/primitives/thann/thann.js +96 -0
- package/primitives/tiniath/tiniath.css +16 -0
- package/primitives/tiniath/tiniath.js +88 -0
- package/primitives/vircantie/vircantie.css +24 -0
- package/primitives/vircantie/vircantie.js +83 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Elvish - View Transitions
|
|
3
|
+
*
|
|
4
|
+
* Smooth, buttery transitions between layout states using the View Transitions API.
|
|
5
|
+
*
|
|
6
|
+
* Browser Support (Jan 2026) - Baseline Newly Available since Oct 2025:
|
|
7
|
+
* - Chrome/Edge: ✅ 111+
|
|
8
|
+
* - Safari: ✅ 18+
|
|
9
|
+
* - Firefox: ✅ 144+
|
|
10
|
+
*
|
|
11
|
+
* New features available:
|
|
12
|
+
* - view-transition-name: match-element (auto-naming)
|
|
13
|
+
* - view-transition-class (group styling)
|
|
14
|
+
* - :active-view-transition pseudo-class
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* 1. Add view-transition-name to elements that should animate independently
|
|
18
|
+
* 2. Use startTransition() helper to wrap DOM changes
|
|
19
|
+
* 3. Customize timing with CSS custom properties
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* ===== ENABLE VIEW TRANSITIONS ===== */
|
|
23
|
+
@view-transition {
|
|
24
|
+
navigation: auto; /* Enable for same-document navigations */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ===== TRANSITION TIMING TOKENS ===== */
|
|
28
|
+
:root {
|
|
29
|
+
--transition-duration: 300ms;
|
|
30
|
+
--transition-duration-fast: 150ms;
|
|
31
|
+
--transition-duration-slow: 500ms;
|
|
32
|
+
|
|
33
|
+
/* Easing curves */
|
|
34
|
+
--transition-ease: cubic-bezier(0.25, 0.1, 0.25, 1); /* Smooth default */
|
|
35
|
+
--transition-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Quick start, gentle end */
|
|
36
|
+
--transition-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Symmetric */
|
|
37
|
+
--transition-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Slight overshoot */
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ===== DEFAULT TRANSITION BEHAVIOR ===== */
|
|
41
|
+
/* Old state fades/scales out */
|
|
42
|
+
::view-transition-old(root) {
|
|
43
|
+
animation: var(--transition-duration) var(--transition-ease) both fade-out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* New state fades/scales in */
|
|
47
|
+
::view-transition-new(root) {
|
|
48
|
+
animation: var(--transition-duration) var(--transition-ease) both fade-in;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@keyframes fade-out {
|
|
52
|
+
to { opacity: 0; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes fade-in {
|
|
56
|
+
from { opacity: 0; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ===== NAMED TRANSITION GROUPS ===== */
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* Assign view-transition-name to elements for independent animation.
|
|
63
|
+
* Each named element animates separately from the root transition.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/* Layout primitives can opt-in to independent transitions */
|
|
67
|
+
[data-transition] {
|
|
68
|
+
view-transition-name: var(--transition-name, auto);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Auto-naming for list/grid items - each gets unique name automatically */
|
|
72
|
+
[data-transition="auto"] > * {
|
|
73
|
+
view-transition-name: match-element;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Group styling with view-transition-class */
|
|
77
|
+
.transition-card-group > * {
|
|
78
|
+
view-transition-name: match-element;
|
|
79
|
+
view-transition-class: card;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Style all cards at once using the class */
|
|
83
|
+
::view-transition-group(.card) {
|
|
84
|
+
animation-duration: var(--transition-duration);
|
|
85
|
+
animation-timing-function: var(--transition-ease-spring);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Utility classes for common transition names */
|
|
89
|
+
.transition-hero { view-transition-name: hero; }
|
|
90
|
+
.transition-header { view-transition-name: header; }
|
|
91
|
+
.transition-sidebar { view-transition-name: sidebar; }
|
|
92
|
+
.transition-main { view-transition-name: main; }
|
|
93
|
+
.transition-card { view-transition-name: card; }
|
|
94
|
+
.transition-modal { view-transition-name: modal; }
|
|
95
|
+
|
|
96
|
+
/* ===== TRANSITION ANIMATIONS ===== */
|
|
97
|
+
|
|
98
|
+
/* Morph (default for named elements) - smooth position/size interpolation */
|
|
99
|
+
::view-transition-old(*),
|
|
100
|
+
::view-transition-new(*) {
|
|
101
|
+
animation-duration: var(--transition-duration);
|
|
102
|
+
animation-timing-function: var(--transition-ease-out);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Slide transitions */
|
|
106
|
+
::view-transition-old(slide-left) {
|
|
107
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-out-left;
|
|
108
|
+
}
|
|
109
|
+
::view-transition-new(slide-left) {
|
|
110
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-in-left;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
::view-transition-old(slide-right) {
|
|
114
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-out-right;
|
|
115
|
+
}
|
|
116
|
+
::view-transition-new(slide-right) {
|
|
117
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-in-right;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
::view-transition-old(slide-up) {
|
|
121
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-out-up;
|
|
122
|
+
}
|
|
123
|
+
::view-transition-new(slide-up) {
|
|
124
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-in-up;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
::view-transition-old(slide-down) {
|
|
128
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-out-down;
|
|
129
|
+
}
|
|
130
|
+
::view-transition-new(slide-down) {
|
|
131
|
+
animation: var(--transition-duration) var(--transition-ease-out) both slide-in-down;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@keyframes slide-out-left {
|
|
135
|
+
to { transform: translateX(-100%); opacity: 0; }
|
|
136
|
+
}
|
|
137
|
+
@keyframes slide-in-left {
|
|
138
|
+
from { transform: translateX(100%); opacity: 0; }
|
|
139
|
+
}
|
|
140
|
+
@keyframes slide-out-right {
|
|
141
|
+
to { transform: translateX(100%); opacity: 0; }
|
|
142
|
+
}
|
|
143
|
+
@keyframes slide-in-right {
|
|
144
|
+
from { transform: translateX(-100%); opacity: 0; }
|
|
145
|
+
}
|
|
146
|
+
@keyframes slide-out-up {
|
|
147
|
+
to { transform: translateY(-100%); opacity: 0; }
|
|
148
|
+
}
|
|
149
|
+
@keyframes slide-in-up {
|
|
150
|
+
from { transform: translateY(100%); opacity: 0; }
|
|
151
|
+
}
|
|
152
|
+
@keyframes slide-out-down {
|
|
153
|
+
to { transform: translateY(100%); opacity: 0; }
|
|
154
|
+
}
|
|
155
|
+
@keyframes slide-in-down {
|
|
156
|
+
from { transform: translateY(-100%); opacity: 0; }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Scale transitions */
|
|
160
|
+
::view-transition-old(scale) {
|
|
161
|
+
animation: var(--transition-duration) var(--transition-ease-out) both scale-out;
|
|
162
|
+
}
|
|
163
|
+
::view-transition-new(scale) {
|
|
164
|
+
animation: var(--transition-duration) var(--transition-ease-spring) both scale-in;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@keyframes scale-out {
|
|
168
|
+
to { transform: scale(0.9); opacity: 0; }
|
|
169
|
+
}
|
|
170
|
+
@keyframes scale-in {
|
|
171
|
+
from { transform: scale(1.1); opacity: 0; }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Flip transitions */
|
|
175
|
+
::view-transition-old(flip) {
|
|
176
|
+
animation: var(--transition-duration-slow) var(--transition-ease) both flip-out;
|
|
177
|
+
}
|
|
178
|
+
::view-transition-new(flip) {
|
|
179
|
+
animation: var(--transition-duration-slow) var(--transition-ease) both flip-in;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@keyframes flip-out {
|
|
183
|
+
to { transform: rotateY(90deg); opacity: 0; }
|
|
184
|
+
}
|
|
185
|
+
@keyframes flip-in {
|
|
186
|
+
from { transform: rotateY(-90deg); opacity: 0; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ===== LAYOUT-SPECIFIC TRANSITIONS ===== */
|
|
190
|
+
|
|
191
|
+
/* Sidebar collapse/expand */
|
|
192
|
+
.transition-glan-veleg { view-transition-name: glan-veleg; }
|
|
193
|
+
|
|
194
|
+
::view-transition-old(glan-veleg),
|
|
195
|
+
::view-transition-new(glan-veleg) {
|
|
196
|
+
animation-duration: var(--transition-duration);
|
|
197
|
+
animation-timing-function: var(--transition-ease-out);
|
|
198
|
+
/* Height and width interpolate automatically */
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Switcher horizontal ↔ vertical */
|
|
202
|
+
.transition-gwistindor { view-transition-name: gwistindor; }
|
|
203
|
+
|
|
204
|
+
/* Grid reflow */
|
|
205
|
+
.transition-vircantie { view-transition-name: vircantie; }
|
|
206
|
+
|
|
207
|
+
/* Aspect ratio changes */
|
|
208
|
+
.transition-gant-thala { view-transition-name: gant-thala; }
|
|
209
|
+
|
|
210
|
+
::view-transition-old(gant-thala),
|
|
211
|
+
::view-transition-new(gant-thala) {
|
|
212
|
+
animation-duration: var(--transition-duration);
|
|
213
|
+
animation-timing-function: var(--transition-ease-out);
|
|
214
|
+
/* Aspect ratio morphs smoothly */
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ===== THEME TRANSITIONS ===== */
|
|
218
|
+
.transition-theme { view-transition-name: theme; }
|
|
219
|
+
|
|
220
|
+
/* Cross-fade for theme changes */
|
|
221
|
+
::view-transition-old(theme),
|
|
222
|
+
::view-transition-new(theme) {
|
|
223
|
+
animation: none;
|
|
224
|
+
mix-blend-mode: normal;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
::view-transition-old(theme) {
|
|
228
|
+
animation: var(--transition-duration-slow) var(--transition-ease) both fade-out;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
::view-transition-new(theme) {
|
|
232
|
+
animation: var(--transition-duration-slow) var(--transition-ease) both fade-in;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ===== REDUCED MOTION ===== */
|
|
236
|
+
@media (prefers-reduced-motion: reduce) {
|
|
237
|
+
::view-transition-group(*),
|
|
238
|
+
::view-transition-old(*),
|
|
239
|
+
::view-transition-new(*) {
|
|
240
|
+
animation: none !important;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
:root {
|
|
244
|
+
--transition-duration: 0ms;
|
|
245
|
+
--transition-duration-fast: 0ms;
|
|
246
|
+
--transition-duration-slow: 0ms;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* ===== ACTIVE TRANSITION STATES ===== */
|
|
251
|
+
/* Style the page during an active view transition */
|
|
252
|
+
:root:active-view-transition {
|
|
253
|
+
/* Prevent interaction during transition */
|
|
254
|
+
pointer-events: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
:root:active-view-transition-type(theme) {
|
|
258
|
+
/* Special handling for theme transitions */
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
:root:active-view-transition-type(layout) {
|
|
262
|
+
/* Special handling for layout transitions */
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* ===== FALLBACK FOR UNSUPPORTED BROWSERS ===== */
|
|
266
|
+
/*
|
|
267
|
+
* When View Transitions aren't supported, these CSS transitions
|
|
268
|
+
* provide a graceful fallback for common properties.
|
|
269
|
+
*/
|
|
270
|
+
@supports not (view-transition-name: none) {
|
|
271
|
+
/* Elvish primitives get CSS transition fallbacks */
|
|
272
|
+
i-glan-veleg,
|
|
273
|
+
i-gwistindor,
|
|
274
|
+
i-vircantie,
|
|
275
|
+
i-gant-thala,
|
|
276
|
+
i-enedh,
|
|
277
|
+
i-hath {
|
|
278
|
+
transition:
|
|
279
|
+
width var(--transition-duration, 300ms) var(--transition-ease, ease),
|
|
280
|
+
height var(--transition-duration, 300ms) var(--transition-ease, ease),
|
|
281
|
+
flex-basis var(--transition-duration, 300ms) var(--transition-ease, ease),
|
|
282
|
+
gap var(--transition-duration, 300ms) var(--transition-ease, ease);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
i-gant-thala {
|
|
286
|
+
transition: aspect-ratio var(--transition-duration, 300ms) var(--transition-ease, ease);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Elvish - View Transitions Helper
|
|
3
|
+
*
|
|
4
|
+
* Utilities for smooth view transitions between layout states.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
*
|
|
8
|
+
* import { transition, transitionTo } from './transitions.js';
|
|
9
|
+
*
|
|
10
|
+
* // Wrap any DOM change in a transition
|
|
11
|
+
* transition(() => {
|
|
12
|
+
* element.classList.toggle('collapsed');
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // With custom options
|
|
16
|
+
* transition(() => {
|
|
17
|
+
* sidebar.hidden = true;
|
|
18
|
+
* }, { duration: 500 });
|
|
19
|
+
*
|
|
20
|
+
* // Transition with callback after completion
|
|
21
|
+
* transition(() => {
|
|
22
|
+
* grid.setAttribute('columns', '4');
|
|
23
|
+
* }).then(() => {
|
|
24
|
+
* console.log('Transition complete!');
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if View Transitions API is supported
|
|
30
|
+
*/
|
|
31
|
+
export const supportsViewTransitions = () =>
|
|
32
|
+
typeof document !== 'undefined' &&
|
|
33
|
+
'startViewTransition' in document;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the currently active view transition (if any)
|
|
37
|
+
* @returns {ViewTransition|null}
|
|
38
|
+
*/
|
|
39
|
+
export const getActiveTransition = () =>
|
|
40
|
+
document.activeViewTransition ?? null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Wrap a DOM mutation in a view transition
|
|
44
|
+
*
|
|
45
|
+
* @param {Function} updateCallback - Function that performs DOM changes
|
|
46
|
+
* @param {Object} options - Configuration options
|
|
47
|
+
* @param {number} options.duration - Override transition duration (ms)
|
|
48
|
+
* @param {string} options.easing - Override easing function
|
|
49
|
+
* @param {string[]} options.types - Transition types for :active-view-transition-type()
|
|
50
|
+
* @returns {Promise} Resolves when transition completes
|
|
51
|
+
*/
|
|
52
|
+
export function transition(updateCallback, options = {}) {
|
|
53
|
+
// If View Transitions not supported, just run the callback
|
|
54
|
+
if (!supportsViewTransitions()) {
|
|
55
|
+
updateCallback();
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Apply custom duration/easing if provided
|
|
60
|
+
const root = document.documentElement;
|
|
61
|
+
const originalDuration = root.style.getPropertyValue('--transition-duration');
|
|
62
|
+
const originalEasing = root.style.getPropertyValue('--transition-ease');
|
|
63
|
+
|
|
64
|
+
if (options.duration) {
|
|
65
|
+
root.style.setProperty('--transition-duration', `${options.duration}ms`);
|
|
66
|
+
}
|
|
67
|
+
if (options.easing) {
|
|
68
|
+
root.style.setProperty('--transition-ease', options.easing);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Start the view transition with optional types
|
|
72
|
+
const transitionOptions = options.types
|
|
73
|
+
? { update: updateCallback, types: options.types }
|
|
74
|
+
: updateCallback;
|
|
75
|
+
|
|
76
|
+
const viewTransition = document.startViewTransition(transitionOptions);
|
|
77
|
+
|
|
78
|
+
// Restore original values after transition
|
|
79
|
+
return viewTransition.finished.then(() => {
|
|
80
|
+
if (options.duration) {
|
|
81
|
+
root.style.setProperty('--transition-duration', originalDuration || '');
|
|
82
|
+
}
|
|
83
|
+
if (options.easing) {
|
|
84
|
+
root.style.setProperty('--transition-ease', originalEasing || '');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Transition an element to a new state by changing attributes/classes
|
|
91
|
+
*
|
|
92
|
+
* @param {HTMLElement} element - Element to transition
|
|
93
|
+
* @param {Object} changes - Attribute/class changes to apply
|
|
94
|
+
* @param {Object} changes.attrs - Attributes to set (use null to remove)
|
|
95
|
+
* @param {string[]} changes.addClass - Classes to add
|
|
96
|
+
* @param {string[]} changes.removeClass - Classes to remove
|
|
97
|
+
* @param {string[]} changes.toggleClass - Classes to toggle
|
|
98
|
+
* @param {Object} changes.style - Inline styles to set
|
|
99
|
+
* @param {Object} options - Transition options
|
|
100
|
+
* @returns {Promise}
|
|
101
|
+
*/
|
|
102
|
+
export function transitionTo(element, changes = {}, options = {}) {
|
|
103
|
+
return transition(() => {
|
|
104
|
+
// Apply attribute changes
|
|
105
|
+
if (changes.attrs) {
|
|
106
|
+
Object.entries(changes.attrs).forEach(([key, value]) => {
|
|
107
|
+
if (value === null) {
|
|
108
|
+
element.removeAttribute(key);
|
|
109
|
+
} else {
|
|
110
|
+
element.setAttribute(key, value);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Apply class changes
|
|
116
|
+
if (changes.addClass) {
|
|
117
|
+
element.classList.add(...changes.addClass);
|
|
118
|
+
}
|
|
119
|
+
if (changes.removeClass) {
|
|
120
|
+
element.classList.remove(...changes.removeClass);
|
|
121
|
+
}
|
|
122
|
+
if (changes.toggleClass) {
|
|
123
|
+
changes.toggleClass.forEach(cls => element.classList.toggle(cls));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Apply style changes
|
|
127
|
+
if (changes.style) {
|
|
128
|
+
Object.entries(changes.style).forEach(([prop, value]) => {
|
|
129
|
+
element.style.setProperty(prop, value);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}, options);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Transition between two elements (e.g., swap content)
|
|
137
|
+
*
|
|
138
|
+
* @param {HTMLElement} outElement - Element leaving
|
|
139
|
+
* @param {HTMLElement} inElement - Element entering
|
|
140
|
+
* @param {Object} options - Transition options
|
|
141
|
+
* @returns {Promise}
|
|
142
|
+
*/
|
|
143
|
+
export function crossfade(outElement, inElement, options = {}) {
|
|
144
|
+
return transition(() => {
|
|
145
|
+
outElement.hidden = true;
|
|
146
|
+
inElement.hidden = false;
|
|
147
|
+
}, options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a named transition group for an element
|
|
152
|
+
* Elements with the same view-transition-name animate together
|
|
153
|
+
*
|
|
154
|
+
* @param {HTMLElement} element - Element to name
|
|
155
|
+
* @param {string} name - Transition name
|
|
156
|
+
*/
|
|
157
|
+
export function setTransitionName(element, name) {
|
|
158
|
+
element.style.viewTransitionName = name;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Remove transition name from element
|
|
163
|
+
*
|
|
164
|
+
* @param {HTMLElement} element
|
|
165
|
+
*/
|
|
166
|
+
export function clearTransitionName(element) {
|
|
167
|
+
element.style.viewTransitionName = '';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Batch multiple elements with unique transition names
|
|
172
|
+
* Useful for list/grid items that should animate independently
|
|
173
|
+
*
|
|
174
|
+
* @param {NodeList|HTMLElement[]} elements - Elements to name
|
|
175
|
+
* @param {string} prefix - Name prefix (will add index)
|
|
176
|
+
*/
|
|
177
|
+
export function nameTransitionGroup(elements, prefix = 'item') {
|
|
178
|
+
elements.forEach((el, i) => {
|
|
179
|
+
el.style.viewTransitionName = `${prefix}-${i}`;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Enable auto-naming with match-element for a container's children
|
|
185
|
+
* Each child gets a unique browser-generated name automatically
|
|
186
|
+
*
|
|
187
|
+
* @param {HTMLElement} container - Parent element
|
|
188
|
+
* @param {string} [transitionClass] - Optional view-transition-class for group styling
|
|
189
|
+
*/
|
|
190
|
+
export function enableAutoNaming(container, transitionClass) {
|
|
191
|
+
Array.from(container.children).forEach(child => {
|
|
192
|
+
child.style.viewTransitionName = 'match-element';
|
|
193
|
+
if (transitionClass) {
|
|
194
|
+
child.style.viewTransitionClass = transitionClass;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Disable auto-naming for a container's children
|
|
201
|
+
*
|
|
202
|
+
* @param {HTMLElement} container - Parent element
|
|
203
|
+
*/
|
|
204
|
+
export function disableAutoNaming(container) {
|
|
205
|
+
Array.from(container.children).forEach(child => {
|
|
206
|
+
child.style.viewTransitionName = '';
|
|
207
|
+
child.style.viewTransitionClass = '';
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Elvish-specific: Transition ratio change
|
|
213
|
+
*
|
|
214
|
+
* @param {'golden'|'silver'|'fifth'} ratio - New ratio
|
|
215
|
+
* @param {Object} options - Transition options
|
|
216
|
+
*/
|
|
217
|
+
export function transitionRatio(ratio, options = {}) {
|
|
218
|
+
const ratioVar = `var(--ratio-${ratio})`;
|
|
219
|
+
const measures = { golden: '60ch', fifth: '70ch', silver: '80ch' };
|
|
220
|
+
|
|
221
|
+
return transition(() => {
|
|
222
|
+
const root = document.documentElement;
|
|
223
|
+
root.style.setProperty('--ratio', ratioVar);
|
|
224
|
+
root.style.setProperty('--measure', measures[ratio]);
|
|
225
|
+
root.dataset.ratio = ratio;
|
|
226
|
+
}, { duration: 400, types: ['layout', 'ratio'], ...options });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Elvish-specific: Transition theme change
|
|
231
|
+
*
|
|
232
|
+
* @param {'light'|'dark'|'auto'} theme - New theme
|
|
233
|
+
* @param {Object} options - Transition options
|
|
234
|
+
*/
|
|
235
|
+
export function transitionTheme(theme, options = {}) {
|
|
236
|
+
return transition(() => {
|
|
237
|
+
const root = document.documentElement;
|
|
238
|
+
if (theme === 'auto') {
|
|
239
|
+
root.style.colorScheme = 'light dark';
|
|
240
|
+
} else {
|
|
241
|
+
root.style.colorScheme = theme;
|
|
242
|
+
}
|
|
243
|
+
localStorage.setItem('elvish-theme', theme);
|
|
244
|
+
}, { duration: 300, types: ['theme'], ...options });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Elvish-specific: Transition layout primitive state
|
|
249
|
+
*
|
|
250
|
+
* @param {HTMLElement} primitive - Elvish layout element
|
|
251
|
+
* @param {Object} attrs - New attribute values
|
|
252
|
+
* @param {Object} options - Transition options
|
|
253
|
+
*/
|
|
254
|
+
export function transitionLayout(primitive, attrs, options = {}) {
|
|
255
|
+
// Give the primitive a transition name if it doesn't have one
|
|
256
|
+
if (!primitive.style.viewTransitionName) {
|
|
257
|
+
primitive.style.viewTransitionName = primitive.tagName.toLowerCase();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return transitionTo(primitive, { attrs }, { types: ['layout'], ...options });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Skip/cancel the currently active view transition
|
|
265
|
+
*/
|
|
266
|
+
export function skipTransition() {
|
|
267
|
+
const active = getActiveTransition();
|
|
268
|
+
if (active) {
|
|
269
|
+
active.skipTransition();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Default export for convenient importing
|
|
274
|
+
export default {
|
|
275
|
+
transition,
|
|
276
|
+
transitionTo,
|
|
277
|
+
crossfade,
|
|
278
|
+
setTransitionName,
|
|
279
|
+
clearTransitionName,
|
|
280
|
+
nameTransitionGroup,
|
|
281
|
+
enableAutoNaming,
|
|
282
|
+
disableAutoNaming,
|
|
283
|
+
transitionRatio,
|
|
284
|
+
transitionTheme,
|
|
285
|
+
transitionLayout,
|
|
286
|
+
skipTransition,
|
|
287
|
+
supportsViewTransitions,
|
|
288
|
+
getActiveTransition,
|
|
289
|
+
};
|