@llui/transitions 0.0.2 → 0.0.3
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/README.md +98 -10
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/route-transition.d.ts +29 -0
- package/dist/route-transition.d.ts.map +1 -0
- package/dist/route-transition.js +41 -0
- package/dist/spring.d.ts +28 -0
- package/dist/spring.d.ts.map +1 -0
- package/dist/spring.js +70 -0
- package/dist/stagger.d.ts +26 -0
- package/dist/stagger.d.ts.map +1 -0
- package/dist/stagger.js +134 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,20 +26,104 @@ view({ show, text }) {
|
|
|
26
26
|
|
|
27
27
|
### Core
|
|
28
28
|
|
|
29
|
-
| Function
|
|
30
|
-
|
|
|
29
|
+
| Function | Description |
|
|
30
|
+
| ------------------------------ | ------------------------------------------------------- |
|
|
31
31
|
| `transition({ enter, leave })` | Core transition -- define custom enter/leave animations |
|
|
32
|
-
| `mergeTransitions(a, b)`
|
|
32
|
+
| `mergeTransitions(a, b)` | Combine two transitions into one |
|
|
33
33
|
|
|
34
34
|
### Presets
|
|
35
35
|
|
|
36
|
-
| Function | Options
|
|
37
|
-
| -------------------- |
|
|
38
|
-
| `fade(options?)` | `duration`, `easing`
|
|
36
|
+
| Function | Options | Description |
|
|
37
|
+
| -------------------- | --------------------------------- | ---------------------------------------------------- |
|
|
38
|
+
| `fade(options?)` | `duration`, `easing` | Fade in/out |
|
|
39
39
|
| `slide(options?)` | `direction`, `duration`, `easing` | Slide from direction (`up`, `down`, `left`, `right`) |
|
|
40
|
-
| `scale(options?)` | `from`, `duration`, `easing`
|
|
41
|
-
| `collapse(options?)` | `duration`, `easing`
|
|
42
|
-
| `flip(options?)` | `duration`, `easing`
|
|
40
|
+
| `scale(options?)` | `from`, `duration`, `easing` | Scale transform in/out |
|
|
41
|
+
| `collapse(options?)` | `duration`, `easing` | Height collapse/expand |
|
|
42
|
+
| `flip(options?)` | `duration`, `easing` | FLIP reorder animation for `each()` |
|
|
43
|
+
|
|
44
|
+
### Spring Physics
|
|
45
|
+
|
|
46
|
+
| Function | Options | Description |
|
|
47
|
+
| ------------------ | --------------------------------------------------------------------- | -------------------------------- |
|
|
48
|
+
| `spring(options?)` | `stiffness`, `damping`, `mass`, `precision`, `property`, `from`, `to` | Spring-physics animation via rAF |
|
|
49
|
+
|
|
50
|
+
Uses a damped spring simulation instead of CSS easing. The animation runs via `requestAnimationFrame` and settles naturally based on physics parameters.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { spring } from '@llui/transitions'
|
|
54
|
+
|
|
55
|
+
// Default: opacity 0 → 1 with react-spring-like defaults
|
|
56
|
+
show({ when: (s) => s.open, render: () => content(), ...spring() })
|
|
57
|
+
|
|
58
|
+
// Custom spring feel
|
|
59
|
+
show({
|
|
60
|
+
when: (s) => s.open,
|
|
61
|
+
render: () => content(),
|
|
62
|
+
...spring({ stiffness: 300, damping: 15, property: 'opacity' }),
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Route Transitions
|
|
67
|
+
|
|
68
|
+
| Function | Options | Description |
|
|
69
|
+
| --------------------------- | ---------------------------------------------- | -------------------------------------------- |
|
|
70
|
+
| `routeTransition(options?)` | `duration`, `easing`, `slide`, `slideDistance` | Fade + slide for `branch()` page transitions |
|
|
71
|
+
|
|
72
|
+
Convenience wrapper for animating page transitions in a `branch()`:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { routeTransition, fade } from '@llui/transitions'
|
|
76
|
+
|
|
77
|
+
// Default: fade + slight upward slide (250ms)
|
|
78
|
+
branch({
|
|
79
|
+
on: (s) => s.route.page,
|
|
80
|
+
cases: { home: () => [...], about: () => [...] },
|
|
81
|
+
...routeTransition(),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Custom duration
|
|
85
|
+
branch({ on, cases, ...routeTransition({ duration: 200 }) })
|
|
86
|
+
|
|
87
|
+
// Fade only (no slide)
|
|
88
|
+
branch({ on, cases, ...routeTransition({ duration: 200, slide: false }) })
|
|
89
|
+
|
|
90
|
+
// Pass any preset directly
|
|
91
|
+
branch({ on, cases, ...routeTransition(fade({ duration: 200 })) })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Stagger
|
|
95
|
+
|
|
96
|
+
| Function | Options | Description |
|
|
97
|
+
| ------------------------------- | ---------------------------- | ------------------------------------------------- |
|
|
98
|
+
| `stagger(transition, options?)` | `delayPerItem`, `leaveOrder` | Stagger enter/leave animations for `each()` items |
|
|
99
|
+
|
|
100
|
+
Wraps any transition preset so batch-entered items animate with incremental delays:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { stagger, fade, slide } from '@llui/transitions'
|
|
104
|
+
|
|
105
|
+
each({
|
|
106
|
+
items: (s) => s.items,
|
|
107
|
+
key: (i) => i.id,
|
|
108
|
+
render: ({ item }) => [...],
|
|
109
|
+
...stagger(fade({ duration: 150 }), { delayPerItem: 30 }),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Works with any preset
|
|
113
|
+
each({
|
|
114
|
+
items: (s) => s.items,
|
|
115
|
+
key: (i) => i.id,
|
|
116
|
+
render: ({ item }) => [...],
|
|
117
|
+
...stagger(slide({ direction: 'up' }), { delayPerItem: 50 }),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Stagger leave animations too (default is simultaneous)
|
|
121
|
+
each({
|
|
122
|
+
...stagger(fade(), { delayPerItem: 30, leaveOrder: 'sequential' }),
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Items entering within the same microtask are considered a "batch" and get sequential delays. The counter resets after the microtask boundary, so the next batch starts from index 0.
|
|
43
127
|
|
|
44
128
|
### Integration
|
|
45
129
|
|
|
@@ -53,7 +137,11 @@ show({ when: (s) => s.open, render: () => content(), ...fade() })
|
|
|
53
137
|
each({
|
|
54
138
|
items: (s) => s.list,
|
|
55
139
|
key: (item) => item.id,
|
|
56
|
-
render: (item) =>
|
|
140
|
+
render: (item) =>
|
|
141
|
+
li(
|
|
142
|
+
{},
|
|
143
|
+
text(() => item.name),
|
|
144
|
+
),
|
|
57
145
|
...flip({ duration: 200 }),
|
|
58
146
|
})
|
|
59
147
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export { transition } from './transition';
|
|
2
2
|
export { fade, slide, scale, collapse } from './presets';
|
|
3
3
|
export type { FadeOptions, SlideOptions, SlideDirection, ScaleOptions, CollapseOptions, } from './presets';
|
|
4
|
+
export { spring } from './spring';
|
|
5
|
+
export type { SpringOptions } from './spring';
|
|
4
6
|
export { flip, mergeTransitions } from './flip';
|
|
5
7
|
export type { FlipOptions } from './flip';
|
|
8
|
+
export { routeTransition } from './route-transition';
|
|
9
|
+
export type { RouteTransitionOptions } from './route-transition';
|
|
10
|
+
export { stagger } from './stagger';
|
|
11
|
+
export type { StaggerOptions } from './stagger';
|
|
6
12
|
export type { TransitionSpec, TransitionValue, Styles } from './types';
|
|
7
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAGzC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAGzC,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAGzC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAGzC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAGhE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAG/C,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,11 @@
|
|
|
2
2
|
export { transition } from './transition';
|
|
3
3
|
// Presets
|
|
4
4
|
export { fade, slide, scale, collapse } from './presets';
|
|
5
|
+
// Spring physics
|
|
6
|
+
export { spring } from './spring';
|
|
5
7
|
// Reorder animation + composition
|
|
6
8
|
export { flip, mergeTransitions } from './flip';
|
|
9
|
+
// Route transitions
|
|
10
|
+
export { routeTransition } from './route-transition';
|
|
11
|
+
// Stagger
|
|
12
|
+
export { stagger } from './stagger';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TransitionOptions } from '@llui/dom';
|
|
2
|
+
export interface RouteTransitionOptions {
|
|
3
|
+
/** Duration in milliseconds (default: 250). */
|
|
4
|
+
duration?: number;
|
|
5
|
+
/** Easing function (default: 'ease-out'). */
|
|
6
|
+
easing?: string;
|
|
7
|
+
/** Enable a slight vertical slide alongside the fade (default: true). */
|
|
8
|
+
slide?: boolean;
|
|
9
|
+
/** Slide distance in pixels (default: 12). */
|
|
10
|
+
slideDistance?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convenience wrapper that returns `{ enter, leave }` hooks suitable for
|
|
14
|
+
* spreading into a `branch()` call to animate page transitions.
|
|
15
|
+
*
|
|
16
|
+
* Can be called two ways:
|
|
17
|
+
*
|
|
18
|
+
* 1. With route-specific options (produces a fade + optional slide):
|
|
19
|
+
* ```ts
|
|
20
|
+
* branch({ on, cases, ...routeTransition({ duration: 200 }) })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. With a pre-built `TransitionOptions` (e.g. from any preset):
|
|
24
|
+
* ```ts
|
|
25
|
+
* branch({ on, cases, ...routeTransition(fade({ duration: 200 })) })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function routeTransition(opts?: RouteTransitionOptions | TransitionOptions): TransitionOptions;
|
|
29
|
+
//# sourceMappingURL=route-transition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-transition.d.ts","sourceRoot":"","sources":["../src/route-transition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAKlD,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,IAAI,CAAC,EAAE,sBAAsB,GAAG,iBAAiB,GAChD,iBAAiB,CAyBnB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fade } from './presets';
|
|
2
|
+
import { slide } from './presets';
|
|
3
|
+
import { mergeTransitions } from './flip';
|
|
4
|
+
/**
|
|
5
|
+
* Convenience wrapper that returns `{ enter, leave }` hooks suitable for
|
|
6
|
+
* spreading into a `branch()` call to animate page transitions.
|
|
7
|
+
*
|
|
8
|
+
* Can be called two ways:
|
|
9
|
+
*
|
|
10
|
+
* 1. With route-specific options (produces a fade + optional slide):
|
|
11
|
+
* ```ts
|
|
12
|
+
* branch({ on, cases, ...routeTransition({ duration: 200 }) })
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* 2. With a pre-built `TransitionOptions` (e.g. from any preset):
|
|
16
|
+
* ```ts
|
|
17
|
+
* branch({ on, cases, ...routeTransition(fade({ duration: 200 })) })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function routeTransition(opts) {
|
|
21
|
+
// If opts already has enter/leave, treat it as a pre-built TransitionOptions.
|
|
22
|
+
if (opts && ('enter' in opts || 'leave' in opts)) {
|
|
23
|
+
return opts;
|
|
24
|
+
}
|
|
25
|
+
const config = (opts ?? {});
|
|
26
|
+
const duration = config.duration ?? 250;
|
|
27
|
+
const easing = config.easing ?? 'ease-out';
|
|
28
|
+
const withSlide = config.slide !== false;
|
|
29
|
+
const slideDistance = config.slideDistance ?? 12;
|
|
30
|
+
const fadeT = fade({ duration, easing });
|
|
31
|
+
if (!withSlide)
|
|
32
|
+
return fadeT;
|
|
33
|
+
const slideT = slide({
|
|
34
|
+
direction: 'up',
|
|
35
|
+
distance: slideDistance,
|
|
36
|
+
duration,
|
|
37
|
+
easing,
|
|
38
|
+
fade: false,
|
|
39
|
+
});
|
|
40
|
+
return mergeTransitions(fadeT, slideT);
|
|
41
|
+
}
|
package/dist/spring.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TransitionOptions } from '@llui/dom';
|
|
2
|
+
export interface SpringOptions {
|
|
3
|
+
/** Spring stiffness (default: 170). */
|
|
4
|
+
stiffness?: number;
|
|
5
|
+
/** Damping coefficient (default: 26). */
|
|
6
|
+
damping?: number;
|
|
7
|
+
/** Mass (default: 1). */
|
|
8
|
+
mass?: number;
|
|
9
|
+
/** Stop threshold for velocity and position (default: 0.01). */
|
|
10
|
+
precision?: number;
|
|
11
|
+
/** CSS property to animate (default: 'opacity'). */
|
|
12
|
+
property?: string;
|
|
13
|
+
/** Start value (default: 0). */
|
|
14
|
+
from?: number;
|
|
15
|
+
/** End value (default: 1). */
|
|
16
|
+
to?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Spring-physics transition. Returns `{ enter, leave }` that animate a CSS
|
|
20
|
+
* property using a damped spring simulation driven by `requestAnimationFrame`.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* show({ when: (s) => s.open, render: () => content(), ...spring() })
|
|
24
|
+
* show({ ...spring({ property: 'transform', from: 0, to: 1 }) })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function spring(opts?: SpringOptions): TransitionOptions;
|
|
28
|
+
//# sourceMappingURL=spring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spring.d.ts","sourceRoot":"","sources":["../src/spring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAGlD,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yBAAyB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8BAA8B;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AA4ED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,iBAAiB,CAoBlE"}
|
package/dist/spring.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { asElements } from './style-utils';
|
|
2
|
+
function simulateSpring(state, target, stiffness, damping, mass, dt) {
|
|
3
|
+
const acceleration = (-stiffness * (state.position - target) - damping * state.velocity) / mass;
|
|
4
|
+
state.velocity += acceleration * dt;
|
|
5
|
+
state.position += state.velocity * dt;
|
|
6
|
+
}
|
|
7
|
+
function isSettled(state, target, precision) {
|
|
8
|
+
return Math.abs(state.velocity) < precision && Math.abs(state.position - target) < precision;
|
|
9
|
+
}
|
|
10
|
+
function animateSpring(els, from, to, property, stiffness, damping, mass, precision) {
|
|
11
|
+
if (els.length === 0)
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const state = { position: from, velocity: 0 };
|
|
15
|
+
let lastTime = null;
|
|
16
|
+
function step(time) {
|
|
17
|
+
if (lastTime === null) {
|
|
18
|
+
lastTime = time;
|
|
19
|
+
}
|
|
20
|
+
// dt in seconds, clamped to avoid spiral on tab-switch
|
|
21
|
+
const dt = Math.min((time - lastTime) / 1000, 0.064);
|
|
22
|
+
lastTime = time;
|
|
23
|
+
simulateSpring(state, to, stiffness, damping, mass, dt);
|
|
24
|
+
for (const el of els) {
|
|
25
|
+
el.style.setProperty(property, String(state.position));
|
|
26
|
+
}
|
|
27
|
+
if (isSettled(state, to, precision)) {
|
|
28
|
+
// Snap to exact target
|
|
29
|
+
for (const el of els) {
|
|
30
|
+
el.style.setProperty(property, String(to));
|
|
31
|
+
}
|
|
32
|
+
resolve();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
requestAnimationFrame(step);
|
|
36
|
+
}
|
|
37
|
+
// Apply initial value
|
|
38
|
+
for (const el of els) {
|
|
39
|
+
el.style.setProperty(property, String(from));
|
|
40
|
+
}
|
|
41
|
+
requestAnimationFrame(step);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Spring-physics transition. Returns `{ enter, leave }` that animate a CSS
|
|
46
|
+
* property using a damped spring simulation driven by `requestAnimationFrame`.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* show({ when: (s) => s.open, render: () => content(), ...spring() })
|
|
50
|
+
* show({ ...spring({ property: 'transform', from: 0, to: 1 }) })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function spring(opts = {}) {
|
|
54
|
+
const stiffness = opts.stiffness ?? 170;
|
|
55
|
+
const damping = opts.damping ?? 26;
|
|
56
|
+
const mass = opts.mass ?? 1;
|
|
57
|
+
const precision = opts.precision ?? 0.01;
|
|
58
|
+
const property = opts.property ?? 'opacity';
|
|
59
|
+
const from = opts.from ?? 0;
|
|
60
|
+
const to = opts.to ?? 1;
|
|
61
|
+
const enter = (nodes) => {
|
|
62
|
+
const els = asElements(nodes);
|
|
63
|
+
void animateSpring(els, from, to, property, stiffness, damping, mass, precision);
|
|
64
|
+
};
|
|
65
|
+
const leave = (nodes) => {
|
|
66
|
+
const els = asElements(nodes);
|
|
67
|
+
return animateSpring(els, to, from, property, stiffness, damping, mass, precision);
|
|
68
|
+
};
|
|
69
|
+
return { enter, leave };
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TransitionOptions } from '@llui/dom';
|
|
2
|
+
export interface StaggerOptions {
|
|
3
|
+
/** Delay between each item in milliseconds (default: 30). */
|
|
4
|
+
delayPerItem?: number;
|
|
5
|
+
/** How to stagger leave animations: 'sequential' (same order as enter),
|
|
6
|
+
* 'reverse', or 'simultaneous' (no stagger). Default: 'simultaneous'. */
|
|
7
|
+
leaveOrder?: 'sequential' | 'reverse' | 'simultaneous';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap any transition preset so that batch-entered items get staggered delays.
|
|
11
|
+
*
|
|
12
|
+
* Items entering within the same microtask are considered a "batch" and get
|
|
13
|
+
* sequential delays (`index * delayPerItem`). The counter resets after the
|
|
14
|
+
* microtask, so the next batch starts from 0.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* each({
|
|
18
|
+
* items: s => s.items,
|
|
19
|
+
* key: i => i.id,
|
|
20
|
+
* render: ({ item }) => [...],
|
|
21
|
+
* ...stagger(fade({ duration: 150 }), { delayPerItem: 30 }),
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function stagger(spec: TransitionOptions, opts?: StaggerOptions): TransitionOptions;
|
|
26
|
+
//# sourceMappingURL=stagger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stagger.d.ts","sourceRoot":"","sources":["../src/stagger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAElD,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;8EAC0E;IAC1E,UAAU,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,cAAc,CAAA;CACvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAwHzF"}
|
package/dist/stagger.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap any transition preset so that batch-entered items get staggered delays.
|
|
3
|
+
*
|
|
4
|
+
* Items entering within the same microtask are considered a "batch" and get
|
|
5
|
+
* sequential delays (`index * delayPerItem`). The counter resets after the
|
|
6
|
+
* microtask, so the next batch starts from 0.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* each({
|
|
10
|
+
* items: s => s.items,
|
|
11
|
+
* key: i => i.id,
|
|
12
|
+
* render: ({ item }) => [...],
|
|
13
|
+
* ...stagger(fade({ duration: 150 }), { delayPerItem: 30 }),
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function stagger(spec, opts) {
|
|
18
|
+
const delayPerItem = opts?.delayPerItem ?? 30;
|
|
19
|
+
const leaveOrder = opts?.leaveOrder ?? 'simultaneous';
|
|
20
|
+
// ── Enter stagger ──────────────────────────────────────────────
|
|
21
|
+
let enterIndex = 0;
|
|
22
|
+
let enterResetScheduled = false;
|
|
23
|
+
function resetEnterIndex() {
|
|
24
|
+
enterIndex = 0;
|
|
25
|
+
enterResetScheduled = false;
|
|
26
|
+
}
|
|
27
|
+
// ── Leave stagger ─────────────────────────────────────────────
|
|
28
|
+
let leaveIndex = 0;
|
|
29
|
+
let leaveResetScheduled = false;
|
|
30
|
+
let leaveBatchSize = 0;
|
|
31
|
+
function resetLeaveIndex() {
|
|
32
|
+
leaveIndex = 0;
|
|
33
|
+
leaveBatchSize = 0;
|
|
34
|
+
leaveResetScheduled = false;
|
|
35
|
+
}
|
|
36
|
+
const out = {};
|
|
37
|
+
if (spec.enter) {
|
|
38
|
+
const baseEnter = spec.enter;
|
|
39
|
+
out.enter = (nodes) => {
|
|
40
|
+
const idx = enterIndex++;
|
|
41
|
+
if (!enterResetScheduled) {
|
|
42
|
+
enterResetScheduled = true;
|
|
43
|
+
queueMicrotask(resetEnterIndex);
|
|
44
|
+
}
|
|
45
|
+
const delay = idx * delayPerItem;
|
|
46
|
+
if (delay === 0) {
|
|
47
|
+
return baseEnter(nodes);
|
|
48
|
+
}
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
const result = baseEnter(nodes);
|
|
52
|
+
if (result && typeof result.then === 'function') {
|
|
53
|
+
;
|
|
54
|
+
result.then(resolve, resolve);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
resolve();
|
|
58
|
+
}
|
|
59
|
+
}, delay);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (spec.leave) {
|
|
64
|
+
const baseLeave = spec.leave;
|
|
65
|
+
out.leave = (nodes) => {
|
|
66
|
+
if (leaveOrder === 'simultaneous') {
|
|
67
|
+
return baseLeave(nodes);
|
|
68
|
+
}
|
|
69
|
+
const idx = leaveIndex++;
|
|
70
|
+
leaveBatchSize = leaveIndex;
|
|
71
|
+
if (!leaveResetScheduled) {
|
|
72
|
+
leaveResetScheduled = true;
|
|
73
|
+
queueMicrotask(resetLeaveIndex);
|
|
74
|
+
}
|
|
75
|
+
// For reverse order, compute delay after all items in the batch are known.
|
|
76
|
+
// Since we can't know the batch size ahead of time, we use a microtask
|
|
77
|
+
// to capture it, but the delay must be applied now. For reverse, we use
|
|
78
|
+
// a deferred approach: schedule the animation after the microtask.
|
|
79
|
+
if (leaveOrder === 'reverse') {
|
|
80
|
+
const capturedIdx = idx;
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
// Wait for microtask to know batch size, then schedule with reverse delay.
|
|
83
|
+
queueMicrotask(() => {
|
|
84
|
+
const reverseIdx = leaveBatchSize - 1 - capturedIdx;
|
|
85
|
+
const delay = reverseIdx * delayPerItem;
|
|
86
|
+
if (delay === 0) {
|
|
87
|
+
const result = baseLeave(nodes);
|
|
88
|
+
if (result && typeof result.then === 'function') {
|
|
89
|
+
;
|
|
90
|
+
result.then(resolve, resolve);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
resolve();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
const result = baseLeave(nodes);
|
|
99
|
+
if (result && typeof result.then === 'function') {
|
|
100
|
+
;
|
|
101
|
+
result.then(resolve, resolve);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
}, delay);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// sequential
|
|
112
|
+
const delay = idx * delayPerItem;
|
|
113
|
+
if (delay === 0) {
|
|
114
|
+
return baseLeave(nodes);
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
const result = baseLeave(nodes);
|
|
119
|
+
if (result && typeof result.then === 'function') {
|
|
120
|
+
;
|
|
121
|
+
result.then(resolve, resolve);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
}, delay);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (spec.onTransition) {
|
|
131
|
+
out.onTransition = spec.onTransition;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/transitions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "vitest run"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@llui/dom": "^0.0.
|
|
20
|
+
"@llui/dom": "^0.0.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@llui/dom": "workspace:*",
|