@cioky/ripple-transitions 0.1.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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @cioky/ripple-transitions
2
+
3
+ Transition and animation library for the **Ripple** UI framework.
4
+
5
+ ## Installation
6
+
7
+ You can install this package using your preferred package manager:
8
+
9
+ ```bash
10
+ bun add @cioky/ripple-transitions
11
+ # or
12
+ npm install @cioky/ripple-transitions
13
+ # or
14
+ yarn add @cioky/ripple-transitions
15
+ ```
16
+
17
+ Make sure you have `ripple` and `@ripple-ts/vite-plugin` configured in your project.
18
+
19
+ ## Features
20
+
21
+ - **Presence**: Identity-based transition coordinator. Exiting child elements stay in the DOM until their exit animations complete, and new child elements mount concurrently.
22
+ - **Transition**: Show/hide transitions supporting custom handlers and `popLayout` mode.
23
+ - **Motion & Gestures**: Standard transitions (`fade`, `rise`, `scale`, `fly`, `blur`, `draw`, `svelteTransition`, `gestures`, `animate`).
24
+ - **Layout Animations**: FLIP-based layout animations using `layout()` and `useTransitionList()`.
25
+ - **Slide Transitions**: Axis-based height/width accordion slide transitions.
26
+ - **Spring Physics**: Physics-based animations with adjustable stiffness, damping, and mass.
27
+ - **Stagger**: Injects delays sequentially for lists or grid layouts.
28
+
29
+ ## Usage
30
+
31
+ ### Presence & Transition
32
+
33
+ ```tsx
34
+ import { Presence, Transition, fade, slide } from '@cioky/ripple-transitions';
35
+
36
+ export function MyComponent() @{
37
+ let &[show] = track(true);
38
+
39
+ <div>
40
+ <button onClick={() => show = !show}>Toggle</button>
41
+
42
+ <Presence>
43
+ @if (show) {
44
+ <div ref={fade({ duration: 300 })}>
45
+ Transitions on mount and unmount!
46
+ </div>
47
+ }
48
+ </Presence>
49
+ </div>
50
+ }
51
+ ```
52
+
53
+ ### Layout Transitions
54
+
55
+ ```tsx
56
+ import { layout } from '@cioky/ripple-transitions';
57
+
58
+ export function List() @{
59
+ <div ref={layout()}>
60
+ {/* Children animate automatically when they resize or shift positions */}
61
+ </div>
62
+ }
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@cioky/ripple-transitions",
3
+ "version": "0.1.0",
4
+ "description": "Transition and animation library for the Ripple framework",
5
+ "type": "module",
6
+ "main": "./src/index.tsrx",
7
+ "types": "./src/index.tsrx",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.tsrx",
11
+ "default": "./src/index.tsrx"
12
+ }
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md",
17
+ "package.json"
18
+ ],
19
+ "peerDependencies": {
20
+ "ripple": ">=0.3.0"
21
+ },
22
+ "keywords": [
23
+ "ripple",
24
+ "transitions",
25
+ "animation",
26
+ "@cioky/ripple-transitions",
27
+ "svelte-transitions"
28
+ ],
29
+ "license": "MIT",
30
+ "author": "Antigravity <antigravity@google.com>"
31
+ }
package/src/index.tsrx ADDED
@@ -0,0 +1,37 @@
1
+ export type {
2
+ TransitionOptions,
3
+ MotionConfig,
4
+ LayoutOptions,
5
+ GestureConfig,
6
+ SvelteTransitionConfig,
7
+ SvelteTransitionFn
8
+ } from './types.js';
9
+
10
+ export { springValues } from './spring.js';
11
+
12
+ export {
13
+ mergeRefs,
14
+ MotionTimingContext,
15
+ motion,
16
+ fade,
17
+ rise,
18
+ scale,
19
+ fly,
20
+ blur,
21
+ draw,
22
+ svelteTransition,
23
+ gestures,
24
+ animate
25
+ } from './motion.js';
26
+
27
+ export { stagger } from './stagger.js';
28
+
29
+ export { slide } from './slide.js';
30
+
31
+ export { layout, useTransitionList } from './layout.js';
32
+
33
+ export {
34
+ MotionProvider,
35
+ Transition,
36
+ Presence
37
+ } from './presence.tsrx';
package/src/layout.ts ADDED
@@ -0,0 +1,247 @@
1
+ import { track, tick } from 'ripple';
2
+ import { type LayoutOptions, type TransitionOptions } from './types.js';
3
+ import { getSpringKeyframes } from './spring.js';
4
+
5
+ export function layout(options: LayoutOptions = {}) {
6
+ const {
7
+ duration = 300,
8
+ easing = 'cubic-bezier(0.25, 1, 0.5, 1)',
9
+ type,
10
+ stiffness = 300,
11
+ damping = 30,
12
+ mass = 1
13
+ } = options;
14
+ const isSpring = type === 'spring';
15
+
16
+ return (el: HTMLElement) => {
17
+ if (!el) return;
18
+
19
+ const getRelativeRect = () => {
20
+ const rect = el.getBoundingClientRect();
21
+ const parent = el.offsetParent || el.parentElement || el;
22
+ const parentRect = parent.getBoundingClientRect();
23
+ return {
24
+ left: rect.left - parentRect.left,
25
+ top: rect.top - parentRect.top,
26
+ width: rect.width,
27
+ height: rect.height
28
+ };
29
+ };
30
+
31
+ let prevRect = getRelativeRect();
32
+ let animating = false;
33
+ let initialized = false;
34
+
35
+ setTimeout(() => {
36
+ initialized = true;
37
+ prevRect = getRelativeRect();
38
+ }, 150);
39
+
40
+ const ro = new ResizeObserver(() => {
41
+ const newRect = getRelativeRect();
42
+ if (!initialized) {
43
+ prevRect = newRect;
44
+ return;
45
+ }
46
+ if (animating) return;
47
+ const dx = prevRect.left - newRect.left;
48
+ const dy = prevRect.top - newRect.top;
49
+ const dw = prevRect.width - newRect.width;
50
+ const dh = prevRect.height - newRect.height;
51
+
52
+
53
+ if (dx !== 0 || dy !== 0 || dh !== 0 || dw !== 0) {
54
+ animating = true;
55
+ const anims: Promise<any>[] = [];
56
+
57
+ if (dh !== 0 || dw !== 0) {
58
+ const oldOverflow = el.style.overflow;
59
+ el.style.overflow = 'hidden';
60
+ const sizeAnim = el.animate([
61
+ { width: `${prevRect.width}px`, height: `${prevRect.height}px` },
62
+ { width: `${newRect.width}px`, height: `${newRect.height}px` }
63
+ ], { duration, easing });
64
+
65
+ anims.push(sizeAnim.finished.then(() => {
66
+ el.style.overflow = oldOverflow;
67
+ }));
68
+ }
69
+
70
+ if (dx !== 0 || dy !== 0) {
71
+ const startState = { transform: `translate(${dx}px, ${dy}px)` };
72
+ const endState = { transform: 'translate(0px, 0px)' };
73
+
74
+ const keyframes = isSpring
75
+ ? getSpringKeyframes(startState, endState, { delay: 0, duration, easing, type, stiffness, damping, mass } as Required<TransitionOptions>)
76
+ : [startState, endState];
77
+
78
+ const transAnim = el.animate(keyframes, {
79
+ duration,
80
+ easing: isSpring ? 'linear' : easing,
81
+ fill: 'both'
82
+ });
83
+
84
+ anims.push(transAnim.finished.then(() => {
85
+ try { transAnim.commitStyles(); transAnim.cancel(); } catch {}
86
+ el.style.transform = '';
87
+ }));
88
+ }
89
+
90
+ Promise.all(anims).then(() => {
91
+ animating = false;
92
+ prevRect = getRelativeRect();
93
+ });
94
+ } else {
95
+ prevRect = newRect;
96
+ }
97
+ });
98
+
99
+ let parent = el.parentElement;
100
+ while (parent && parent !== document.body) {
101
+ ro.observe(parent);
102
+ parent = parent.parentElement;
103
+ }
104
+ ro.observe(el);
105
+
106
+ return () => {
107
+ ro.disconnect();
108
+ el.style.transition = '';
109
+ el.style.transform = '';
110
+ };
111
+ };
112
+ }
113
+
114
+ export function useTransitionList<T extends { id: string | number }>(
115
+ initialItems: T[],
116
+ options: LayoutOptions | (() => LayoutOptions) = {}
117
+ ) {
118
+ const state = track(initialItems);
119
+ const rects = new Map<string | number, DOMRect>();
120
+ const elements = new Map<string | number, HTMLElement>();
121
+ const refCache = new Map<string | number, (el: HTMLElement) => void>();
122
+
123
+ const register = (key: string | number) => {
124
+ let cached = refCache.get(key);
125
+ if (!cached) {
126
+ cached = (el: HTMLElement) => {
127
+ if (el) {
128
+ elements.set(key, el);
129
+ } else {
130
+ elements.delete(key);
131
+ rects.delete(key);
132
+ refCache.delete(key);
133
+ }
134
+ };
135
+ refCache.set(key, cached);
136
+ }
137
+ return cached;
138
+ };
139
+
140
+ const read = () => {
141
+ for (const [key, el] of elements.entries()) {
142
+ rects.set(key, el.getBoundingClientRect());
143
+ }
144
+ };
145
+
146
+ const flip = () => {
147
+ queueMicrotask(() => {
148
+ const resolvedOptions = typeof options === 'function' ? options() : options;
149
+ const {
150
+ duration = 300,
151
+ easing = 'cubic-bezier(0.25, 1, 0.5, 1)',
152
+ type,
153
+ stiffness = 300,
154
+ damping = 30,
155
+ mass = 1
156
+ } = resolvedOptions;
157
+ const isSpring = type === 'spring';
158
+
159
+ const toAnimate: Array<{ el: HTMLElement; dx: number; dy: number }> = [];
160
+ for (const [key, el] of elements.entries()) {
161
+ const first = rects.get(key);
162
+ if (!first) continue;
163
+ const last = el.getBoundingClientRect();
164
+ const dx = first.left - last.left;
165
+ const dy = first.top - last.top;
166
+ if (dx !== 0 || dy !== 0) {
167
+ el.style.transition = 'none';
168
+ el.style.transform = `translate(${dx}px, ${dy}px)`;
169
+ toAnimate.push({ el, dx, dy });
170
+ }
171
+ }
172
+
173
+ if (toAnimate.length > 0) {
174
+ // Force reflow
175
+ toAnimate[0].el.offsetHeight;
176
+
177
+ for (const { el, dx, dy } of toAnimate) {
178
+ const startState = { transform: `translate(${dx}px, ${dy}px)` };
179
+ const endState = { transform: 'translate(0px, 0px)' };
180
+
181
+ const keyframes = isSpring
182
+ ? getSpringKeyframes(startState, endState, { delay: 0, duration, easing, type, stiffness, damping, mass } as Required<TransitionOptions>)
183
+ : [startState, endState];
184
+
185
+ const transAnim = el.animate(keyframes, {
186
+ duration,
187
+ easing: isSpring ? 'linear' : easing,
188
+ fill: 'both'
189
+ });
190
+
191
+ transAnim.finished.then(() => {
192
+ try { transAnim.commitStyles(); transAnim.cancel(); } catch {}
193
+ el.style.transform = '';
194
+ });
195
+ }
196
+ }
197
+ });
198
+ };
199
+
200
+ return {
201
+ get items() {
202
+ return state.value;
203
+ },
204
+ set items(v) {
205
+ read();
206
+ state.value = v;
207
+ flip();
208
+ },
209
+ ref: register,
210
+ push(item: T) {
211
+ read();
212
+ state.value = [...state.value, item];
213
+ flip();
214
+ },
215
+ insert(item: T, i: number) {
216
+ read();
217
+ const n = [...state.value];
218
+ n.splice(i, 0, item);
219
+ state.value = n;
220
+ flip();
221
+ },
222
+ shuffle() {
223
+ read();
224
+ const c = [...state.value];
225
+ for (let i = c.length - 1; i > 0; i--) {
226
+ const j = (Math.random() * (i + 1)) | 0;
227
+ [c[i], c[j]] = [c[j], c[i]];
228
+ }
229
+ state.value = c;
230
+ flip();
231
+ },
232
+ async remove(id: string | number, animateExit?: (el: HTMLElement) => Promise<any> | void) {
233
+ const el = elements.get(id);
234
+ const exitAnim = el ? ((el as any).__ripple_exit || animateExit) : null;
235
+ if (el && exitAnim) {
236
+ try {
237
+ await exitAnim(el);
238
+ } catch (e) {
239
+ console.error('Error during exit animation:', e);
240
+ }
241
+ }
242
+ read();
243
+ state.value = state.value.filter(item => item.id !== id);
244
+ flip();
245
+ }
246
+ };
247
+ }