@any-tdf/react-motion 0.0.0-alpha.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 +83 -0
- package/dist/animate.d.ts +40 -0
- package/dist/animate.js +105 -0
- package/dist/easing.d.ts +66 -0
- package/dist/easing.js +106 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/internal.d.ts +26 -0
- package/dist/internal.js +226 -0
- package/dist/interpolate.d.ts +1 -0
- package/dist/interpolate.js +40 -0
- package/dist/motion.d.ts +90 -0
- package/dist/motion.js +452 -0
- package/dist/react.d.ts +33 -0
- package/dist/react.js +132 -0
- package/dist/transition.d.ts +80 -0
- package/dist/transition.js +164 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getInterpolator: <T>(a: T, b: T) => ((t: number) => T);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
2
|
+
export const getInterpolator = (a, b) => {
|
|
3
|
+
if (Object.is(a, b) || a !== a)
|
|
4
|
+
return () => a;
|
|
5
|
+
const type = typeof a;
|
|
6
|
+
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
|
|
7
|
+
throw new Error('Cannot interpolate values of different type');
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
10
|
+
const interpolators = b.map((item, index) => getInterpolator(a[index], item));
|
|
11
|
+
return ((t) => interpolators.map((interpolate) => interpolate(t)));
|
|
12
|
+
}
|
|
13
|
+
if (type === 'object') {
|
|
14
|
+
if (!a || !b) {
|
|
15
|
+
throw new Error('Object cannot be null');
|
|
16
|
+
}
|
|
17
|
+
if (isDate(a) && isDate(b)) {
|
|
18
|
+
const start = a.getTime();
|
|
19
|
+
const delta = b.getTime() - start;
|
|
20
|
+
return ((t) => new Date(start + t * delta));
|
|
21
|
+
}
|
|
22
|
+
const resultKeys = Object.keys(b);
|
|
23
|
+
const interpolators = new Map();
|
|
24
|
+
for (const key of resultKeys) {
|
|
25
|
+
interpolators.set(key, getInterpolator(a[key], b[key]));
|
|
26
|
+
}
|
|
27
|
+
return ((t) => {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const key of resultKeys) {
|
|
30
|
+
result[key] = interpolators.get(key)?.(t);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (type === 'number') {
|
|
36
|
+
const delta = b - a;
|
|
37
|
+
return ((t) => a + t * delta);
|
|
38
|
+
}
|
|
39
|
+
return () => b;
|
|
40
|
+
};
|
package/dist/motion.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type EasingFunction } from './easing';
|
|
2
|
+
export { getInterpolator } from './interpolate';
|
|
3
|
+
export type Subscriber<T> = (value: T) => void;
|
|
4
|
+
export type Unsubscriber = () => void;
|
|
5
|
+
export type Updater<T> = (targetValue: T, value: T) => T;
|
|
6
|
+
export interface Readable<T> {
|
|
7
|
+
subscribe(run: Subscriber<T>, invalidate?: () => void): Unsubscriber;
|
|
8
|
+
}
|
|
9
|
+
export interface SpringOptions {
|
|
10
|
+
stiffness?: number;
|
|
11
|
+
damping?: number;
|
|
12
|
+
precision?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SpringUpdateOptions {
|
|
15
|
+
hard?: any;
|
|
16
|
+
soft?: string | number | boolean;
|
|
17
|
+
instant?: boolean;
|
|
18
|
+
preserveMomentum?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface TweenOptions<T> {
|
|
21
|
+
delay?: number;
|
|
22
|
+
duration?: number | ((from: T, to: T) => number);
|
|
23
|
+
easing?: EasingFunction;
|
|
24
|
+
interpolate?: (a: T, b: T) => (t: number) => T;
|
|
25
|
+
}
|
|
26
|
+
export interface Tweened<T> extends Readable<T> {
|
|
27
|
+
set(value: T, opts?: TweenOptions<T>): Promise<void>;
|
|
28
|
+
update(updater: Updater<T>, opts?: TweenOptions<T>): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
declare class ObservableValue<T> {
|
|
31
|
+
protected value: T;
|
|
32
|
+
private subscribers;
|
|
33
|
+
constructor(value: T);
|
|
34
|
+
protected publish(value: T): void;
|
|
35
|
+
subscribe(run: Subscriber<T>, _invalidate?: () => void): () => void;
|
|
36
|
+
}
|
|
37
|
+
export declare class Tween<T> extends ObservableValue<T> {
|
|
38
|
+
private task;
|
|
39
|
+
private defaults;
|
|
40
|
+
private targetValue;
|
|
41
|
+
constructor(value: T, options?: TweenOptions<T>);
|
|
42
|
+
static of<U>(fn: () => U, options?: TweenOptions<U>): Tween<U>;
|
|
43
|
+
set(value: T, options?: TweenOptions<T>): Promise<void>;
|
|
44
|
+
update(updater: Updater<T>, options?: TweenOptions<T>): Promise<void>;
|
|
45
|
+
get current(): T;
|
|
46
|
+
get target(): T;
|
|
47
|
+
set target(value: T);
|
|
48
|
+
}
|
|
49
|
+
export declare class Spring<T = unknown> extends ObservableValue<T> {
|
|
50
|
+
private task;
|
|
51
|
+
private lastValue;
|
|
52
|
+
private lastTime;
|
|
53
|
+
private inverseMass;
|
|
54
|
+
private inverseMassRecoveryRate;
|
|
55
|
+
private targetValue;
|
|
56
|
+
private resolveCurrent;
|
|
57
|
+
private rejectCurrent;
|
|
58
|
+
stiffness: number;
|
|
59
|
+
damping: number;
|
|
60
|
+
precision: number;
|
|
61
|
+
constructor(value: T, options?: SpringOptions);
|
|
62
|
+
static of<U>(fn: () => U, options?: SpringOptions): Spring<U>;
|
|
63
|
+
set(value: T, options?: SpringUpdateOptions): Promise<void>;
|
|
64
|
+
get current(): T;
|
|
65
|
+
get target(): T;
|
|
66
|
+
set target(value: T);
|
|
67
|
+
}
|
|
68
|
+
export interface Spring<T = unknown> extends Readable<T> {
|
|
69
|
+
update(updater: Updater<T>, options?: SpringUpdateOptions): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
export declare const tweened: <T>(value?: T, defaults?: TweenOptions<T>) => Tweened<T | undefined>;
|
|
72
|
+
export declare const spring: <T = unknown>(value?: T, opts?: SpringOptions) => Spring<T | undefined>;
|
|
73
|
+
declare class PrefersReducedMotion implements Readable<boolean> {
|
|
74
|
+
private query;
|
|
75
|
+
private subscribers;
|
|
76
|
+
current: boolean;
|
|
77
|
+
constructor();
|
|
78
|
+
private handleChange;
|
|
79
|
+
subscribe(run: Subscriber<boolean>, _invalidate?: () => void): () => void;
|
|
80
|
+
}
|
|
81
|
+
export declare const prefersReducedMotion: PrefersReducedMotion;
|
|
82
|
+
export declare const usePrefersReducedMotion: () => boolean;
|
|
83
|
+
export declare const useTween: <T>(target: T, options?: TweenOptions<T>) => {
|
|
84
|
+
current: T;
|
|
85
|
+
tween: Tween<T>;
|
|
86
|
+
};
|
|
87
|
+
export declare const useSpring: <T>(target: T, options?: SpringOptions) => {
|
|
88
|
+
current: T;
|
|
89
|
+
spring: Spring<T>;
|
|
90
|
+
};
|
package/dist/motion.js
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { linear } from './easing';
|
|
3
|
+
import { getInterpolator } from './interpolate';
|
|
4
|
+
export { getInterpolator } from './interpolate';
|
|
5
|
+
const now = () => (typeof performance === 'undefined' ? Date.now() : performance.now());
|
|
6
|
+
const frame = (callback) => {
|
|
7
|
+
if (typeof requestAnimationFrame === 'undefined') {
|
|
8
|
+
const id = setTimeout(() => callback(now()), 16);
|
|
9
|
+
return id;
|
|
10
|
+
}
|
|
11
|
+
return requestAnimationFrame(callback);
|
|
12
|
+
};
|
|
13
|
+
const cancelFrame = (id) => {
|
|
14
|
+
if (typeof cancelAnimationFrame === 'undefined') {
|
|
15
|
+
clearTimeout(id);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
cancelAnimationFrame(id);
|
|
19
|
+
};
|
|
20
|
+
const isDateValue = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
21
|
+
class ObservableValue {
|
|
22
|
+
constructor(value) {
|
|
23
|
+
this.subscribers = new Set();
|
|
24
|
+
this.value = value;
|
|
25
|
+
}
|
|
26
|
+
publish(value) {
|
|
27
|
+
this.value = value;
|
|
28
|
+
for (const subscriber of this.subscribers) {
|
|
29
|
+
subscriber(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
subscribe(run, _invalidate) {
|
|
33
|
+
this.subscribers.add(run);
|
|
34
|
+
run(this.value);
|
|
35
|
+
return () => {
|
|
36
|
+
this.subscribers.delete(run);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class WritableValue extends ObservableValue {
|
|
41
|
+
set(value) {
|
|
42
|
+
this.publish(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class Tween extends ObservableValue {
|
|
46
|
+
constructor(value, options = {}) {
|
|
47
|
+
super(value);
|
|
48
|
+
this.task = null;
|
|
49
|
+
this.targetValue = value;
|
|
50
|
+
this.defaults = options;
|
|
51
|
+
}
|
|
52
|
+
static of(fn, options) {
|
|
53
|
+
return new Tween(fn(), options);
|
|
54
|
+
}
|
|
55
|
+
set(value, options) {
|
|
56
|
+
this.targetValue = value;
|
|
57
|
+
const merged = { ...this.defaults, ...options };
|
|
58
|
+
let { delay = 0, duration = 400, easing = linear, interpolate = getInterpolator } = merged;
|
|
59
|
+
const previousTask = this.task;
|
|
60
|
+
if (duration === 0) {
|
|
61
|
+
previousTask?.cancel();
|
|
62
|
+
if (this.task === previousTask)
|
|
63
|
+
this.task = null;
|
|
64
|
+
this.publish(value);
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
const startTime = now() + delay;
|
|
68
|
+
let started = false;
|
|
69
|
+
let interpolateValue = () => value;
|
|
70
|
+
let taskId = null;
|
|
71
|
+
let cancelled = false;
|
|
72
|
+
const task = {
|
|
73
|
+
cancel: () => {
|
|
74
|
+
cancelled = true;
|
|
75
|
+
if (taskId !== null) {
|
|
76
|
+
cancelFrame(taskId);
|
|
77
|
+
taskId = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
this.task = task;
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const tick = (time) => {
|
|
84
|
+
if (cancelled)
|
|
85
|
+
return;
|
|
86
|
+
if (time < startTime) {
|
|
87
|
+
taskId = frame(tick);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!started) {
|
|
91
|
+
started = true;
|
|
92
|
+
const fromValue = this.value;
|
|
93
|
+
interpolateValue = interpolate(this.value, value);
|
|
94
|
+
if (typeof duration === 'function')
|
|
95
|
+
duration = duration(fromValue, value);
|
|
96
|
+
previousTask?.cancel();
|
|
97
|
+
}
|
|
98
|
+
const elapsed = time - startTime;
|
|
99
|
+
if (elapsed > duration) {
|
|
100
|
+
if (this.task === task)
|
|
101
|
+
this.task = null;
|
|
102
|
+
this.publish(value);
|
|
103
|
+
resolve();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.publish(interpolateValue(easing(elapsed / duration)));
|
|
107
|
+
taskId = frame(tick);
|
|
108
|
+
};
|
|
109
|
+
taskId = frame(tick);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
update(updater, options) {
|
|
113
|
+
return this.set(updater(this.targetValue, this.value), options);
|
|
114
|
+
}
|
|
115
|
+
get current() {
|
|
116
|
+
return this.value;
|
|
117
|
+
}
|
|
118
|
+
get target() {
|
|
119
|
+
return this.targetValue;
|
|
120
|
+
}
|
|
121
|
+
set target(value) {
|
|
122
|
+
void this.set(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
126
|
+
const tickSpring = (ctx, lastValue, currentValue, targetValue) => {
|
|
127
|
+
if (typeof currentValue === 'number' || isDateValue(currentValue)) {
|
|
128
|
+
const current = isDateValue(currentValue) ? currentValue.getTime() : currentValue;
|
|
129
|
+
const last = isDateValue(lastValue) ? lastValue.getTime() : lastValue;
|
|
130
|
+
const target = isDateValue(targetValue) ? targetValue.getTime() : targetValue;
|
|
131
|
+
const delta = target - current;
|
|
132
|
+
const velocity = (current - last) / (ctx.dt || 1 / 60);
|
|
133
|
+
const springForce = ctx.stiffness * delta;
|
|
134
|
+
const damper = ctx.damping * velocity;
|
|
135
|
+
const acceleration = (springForce - damper) * ctx.invMass;
|
|
136
|
+
const d = (velocity + acceleration) * ctx.dt;
|
|
137
|
+
if (Math.abs(d) < ctx.precision && Math.abs(delta) < ctx.precision) {
|
|
138
|
+
return targetValue;
|
|
139
|
+
}
|
|
140
|
+
ctx.settled = false;
|
|
141
|
+
const next = current + d;
|
|
142
|
+
return (isDateValue(currentValue) ? new Date(next) : next);
|
|
143
|
+
}
|
|
144
|
+
if (Array.isArray(currentValue) && Array.isArray(lastValue) && Array.isArray(targetValue)) {
|
|
145
|
+
return currentValue.map((_, index) => tickSpring(ctx, lastValue[index], currentValue[index], targetValue[index]));
|
|
146
|
+
}
|
|
147
|
+
if (typeof currentValue === 'object' && currentValue && typeof lastValue === 'object' && lastValue && typeof targetValue === 'object' && targetValue) {
|
|
148
|
+
const next = {};
|
|
149
|
+
for (const key of Object.keys(currentValue)) {
|
|
150
|
+
next[key] = tickSpring(ctx, lastValue[key], currentValue[key], targetValue[key]);
|
|
151
|
+
}
|
|
152
|
+
return next;
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Cannot spring ${typeof currentValue} values`);
|
|
155
|
+
};
|
|
156
|
+
export class Spring extends ObservableValue {
|
|
157
|
+
constructor(value, options = {}) {
|
|
158
|
+
super(value);
|
|
159
|
+
this.task = null;
|
|
160
|
+
this.lastTime = 0;
|
|
161
|
+
this.inverseMass = 1;
|
|
162
|
+
this.inverseMassRecoveryRate = Infinity;
|
|
163
|
+
this.resolveCurrent = null;
|
|
164
|
+
this.rejectCurrent = null;
|
|
165
|
+
this.targetValue = value;
|
|
166
|
+
this.lastValue = value;
|
|
167
|
+
this.stiffness = clamp(options.stiffness ?? 0.15, 0, 1);
|
|
168
|
+
this.damping = clamp(options.damping ?? 0.8, 0, 1);
|
|
169
|
+
this.precision = options.precision ?? 0.01;
|
|
170
|
+
}
|
|
171
|
+
static of(fn, options) {
|
|
172
|
+
return new Spring(fn(), options);
|
|
173
|
+
}
|
|
174
|
+
set(value, options = {}) {
|
|
175
|
+
this.targetValue = value;
|
|
176
|
+
this.rejectCurrent?.(new Error('Aborted'));
|
|
177
|
+
this.resolveCurrent = null;
|
|
178
|
+
this.rejectCurrent = null;
|
|
179
|
+
if (this.value === undefined || options.instant) {
|
|
180
|
+
if (this.task !== null) {
|
|
181
|
+
cancelFrame(this.task);
|
|
182
|
+
this.task = null;
|
|
183
|
+
}
|
|
184
|
+
this.lastTime = now();
|
|
185
|
+
this.lastValue = value;
|
|
186
|
+
this.publish(value);
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
if (options.preserveMomentum) {
|
|
190
|
+
this.inverseMassRecoveryRate = 1000 / (options.preserveMomentum * 60);
|
|
191
|
+
this.inverseMass = 0;
|
|
192
|
+
}
|
|
193
|
+
if (this.value === null) {
|
|
194
|
+
this.lastValue = value;
|
|
195
|
+
this.publish(value);
|
|
196
|
+
}
|
|
197
|
+
if (this.task === null) {
|
|
198
|
+
this.lastTime = now();
|
|
199
|
+
const run = (time) => {
|
|
200
|
+
this.inverseMass = Math.min(this.inverseMass + this.inverseMassRecoveryRate, 1);
|
|
201
|
+
const elapsed = Math.min(time - this.lastTime, 1000 / 30);
|
|
202
|
+
const ctx = {
|
|
203
|
+
invMass: this.inverseMass,
|
|
204
|
+
stiffness: this.stiffness,
|
|
205
|
+
damping: this.damping,
|
|
206
|
+
precision: this.precision,
|
|
207
|
+
settled: true,
|
|
208
|
+
dt: (elapsed * 60) / 1000
|
|
209
|
+
};
|
|
210
|
+
const nextValue = tickSpring(ctx, this.lastValue, this.value, this.targetValue);
|
|
211
|
+
this.lastTime = time;
|
|
212
|
+
this.lastValue = this.value;
|
|
213
|
+
this.publish(nextValue);
|
|
214
|
+
if (ctx.settled) {
|
|
215
|
+
this.task = null;
|
|
216
|
+
const resolve = this.resolveCurrent;
|
|
217
|
+
this.resolveCurrent = null;
|
|
218
|
+
this.rejectCurrent = null;
|
|
219
|
+
resolve?.();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.task = frame(run);
|
|
223
|
+
};
|
|
224
|
+
this.task = frame(run);
|
|
225
|
+
}
|
|
226
|
+
const promise = new Promise((resolve, reject) => {
|
|
227
|
+
this.resolveCurrent = resolve;
|
|
228
|
+
this.rejectCurrent = reject;
|
|
229
|
+
});
|
|
230
|
+
promise.catch(() => { });
|
|
231
|
+
return promise;
|
|
232
|
+
}
|
|
233
|
+
update(updater, options) {
|
|
234
|
+
return this.set(updater(this.targetValue, this.value), options);
|
|
235
|
+
}
|
|
236
|
+
get current() {
|
|
237
|
+
return this.value;
|
|
238
|
+
}
|
|
239
|
+
get target() {
|
|
240
|
+
return this.targetValue;
|
|
241
|
+
}
|
|
242
|
+
set target(value) {
|
|
243
|
+
void this.set(value);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export const tweened = (value, defaults) => {
|
|
247
|
+
const store = new WritableValue(value);
|
|
248
|
+
let currentValue = value;
|
|
249
|
+
let targetValue = value;
|
|
250
|
+
let task = null;
|
|
251
|
+
const set = (nextValue, opts) => {
|
|
252
|
+
targetValue = nextValue;
|
|
253
|
+
if (currentValue == null) {
|
|
254
|
+
store.set((currentValue = nextValue));
|
|
255
|
+
return Promise.resolve();
|
|
256
|
+
}
|
|
257
|
+
const previousTask = task;
|
|
258
|
+
let { delay = 0, duration = 400, easing = linear, interpolate = getInterpolator } = { ...defaults, ...opts };
|
|
259
|
+
if (duration === 0) {
|
|
260
|
+
previousTask?.cancel();
|
|
261
|
+
if (task === previousTask)
|
|
262
|
+
task = null;
|
|
263
|
+
store.set((currentValue = targetValue));
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
const startTime = now() + delay;
|
|
267
|
+
let started = false;
|
|
268
|
+
let interpolateValue = () => nextValue;
|
|
269
|
+
let taskId = null;
|
|
270
|
+
let cancelled = false;
|
|
271
|
+
const currentTask = {
|
|
272
|
+
cancel: () => {
|
|
273
|
+
cancelled = true;
|
|
274
|
+
if (taskId !== null) {
|
|
275
|
+
cancelFrame(taskId);
|
|
276
|
+
taskId = null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
task = currentTask;
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
const tick = (time) => {
|
|
283
|
+
if (cancelled)
|
|
284
|
+
return;
|
|
285
|
+
if (time < startTime) {
|
|
286
|
+
taskId = frame(tick);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!started) {
|
|
290
|
+
started = true;
|
|
291
|
+
const fromValue = currentValue;
|
|
292
|
+
interpolateValue = interpolate(fromValue, nextValue);
|
|
293
|
+
if (typeof duration === 'function')
|
|
294
|
+
duration = duration(fromValue, nextValue);
|
|
295
|
+
previousTask?.cancel();
|
|
296
|
+
}
|
|
297
|
+
const elapsed = time - startTime;
|
|
298
|
+
if (elapsed > duration) {
|
|
299
|
+
if (task === currentTask)
|
|
300
|
+
task = null;
|
|
301
|
+
store.set((currentValue = nextValue));
|
|
302
|
+
resolve();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
store.set((currentValue = interpolateValue(easing(elapsed / duration))));
|
|
306
|
+
taskId = frame(tick);
|
|
307
|
+
};
|
|
308
|
+
taskId = frame(tick);
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
set,
|
|
313
|
+
update: (updater, opts) => set(updater(targetValue, currentValue), opts),
|
|
314
|
+
subscribe: (run, invalidate) => store.subscribe(run, invalidate)
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
export const spring = (value, opts = {}) => {
|
|
318
|
+
const store = new WritableValue(value);
|
|
319
|
+
let currentValue = value;
|
|
320
|
+
let targetValue = value;
|
|
321
|
+
let lastValue = value;
|
|
322
|
+
let lastTime = 0;
|
|
323
|
+
let task = null;
|
|
324
|
+
let currentToken = null;
|
|
325
|
+
let invMass = 1;
|
|
326
|
+
let invMassRecoveryRate = 0;
|
|
327
|
+
let cancelTask = false;
|
|
328
|
+
const springStore = {
|
|
329
|
+
stiffness: opts.stiffness ?? 0.15,
|
|
330
|
+
damping: opts.damping ?? 0.8,
|
|
331
|
+
precision: opts.precision ?? 0.01,
|
|
332
|
+
set: (nextValue, options = {}) => {
|
|
333
|
+
targetValue = nextValue;
|
|
334
|
+
const token = {};
|
|
335
|
+
currentToken = token;
|
|
336
|
+
if (currentValue == null || options.hard || (springStore.stiffness >= 1 && springStore.damping >= 1)) {
|
|
337
|
+
cancelTask = true;
|
|
338
|
+
if (task !== null) {
|
|
339
|
+
cancelFrame(task);
|
|
340
|
+
task = null;
|
|
341
|
+
}
|
|
342
|
+
lastTime = now();
|
|
343
|
+
lastValue = nextValue;
|
|
344
|
+
store.set((currentValue = targetValue));
|
|
345
|
+
return Promise.resolve();
|
|
346
|
+
}
|
|
347
|
+
if (options.soft) {
|
|
348
|
+
const rate = options.soft === true ? 0.5 : Number(options.soft);
|
|
349
|
+
invMassRecoveryRate = 1 / (rate * 60);
|
|
350
|
+
invMass = 0;
|
|
351
|
+
}
|
|
352
|
+
if (task === null) {
|
|
353
|
+
lastTime = now();
|
|
354
|
+
cancelTask = false;
|
|
355
|
+
const run = (time) => {
|
|
356
|
+
if (cancelTask) {
|
|
357
|
+
cancelTask = false;
|
|
358
|
+
task = null;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
invMass = Math.min(invMass + invMassRecoveryRate, 1);
|
|
362
|
+
const elapsed = Math.min(time - lastTime, 1000 / 30);
|
|
363
|
+
const ctx = {
|
|
364
|
+
invMass,
|
|
365
|
+
stiffness: springStore.stiffness,
|
|
366
|
+
damping: springStore.damping,
|
|
367
|
+
precision: springStore.precision,
|
|
368
|
+
settled: true,
|
|
369
|
+
dt: (elapsed * 60) / 1000
|
|
370
|
+
};
|
|
371
|
+
const next = tickSpring(ctx, lastValue, currentValue, targetValue);
|
|
372
|
+
lastTime = time;
|
|
373
|
+
lastValue = currentValue;
|
|
374
|
+
store.set((currentValue = next));
|
|
375
|
+
if (ctx.settled) {
|
|
376
|
+
task = null;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
task = frame(run);
|
|
380
|
+
};
|
|
381
|
+
task = frame(run);
|
|
382
|
+
}
|
|
383
|
+
return new Promise((resolve) => {
|
|
384
|
+
const wait = () => {
|
|
385
|
+
if (task === null) {
|
|
386
|
+
if (token === currentToken)
|
|
387
|
+
resolve();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
frame(wait);
|
|
391
|
+
};
|
|
392
|
+
wait();
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
update: (updater, options) => springStore.set(updater(targetValue, currentValue), options),
|
|
396
|
+
subscribe: (run, invalidate) => store.subscribe(run, invalidate)
|
|
397
|
+
};
|
|
398
|
+
return springStore;
|
|
399
|
+
};
|
|
400
|
+
class PrefersReducedMotion {
|
|
401
|
+
constructor() {
|
|
402
|
+
this.query = null;
|
|
403
|
+
this.subscribers = new Set();
|
|
404
|
+
this.current = false;
|
|
405
|
+
this.handleChange = (event) => {
|
|
406
|
+
this.current = event.matches;
|
|
407
|
+
for (const subscriber of this.subscribers) {
|
|
408
|
+
subscriber(this.current);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
412
|
+
this.query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
413
|
+
this.current = this.query.matches;
|
|
414
|
+
this.query.addEventListener('change', this.handleChange);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
subscribe(run, _invalidate) {
|
|
418
|
+
this.subscribers.add(run);
|
|
419
|
+
run(this.current);
|
|
420
|
+
return () => {
|
|
421
|
+
this.subscribers.delete(run);
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
export const prefersReducedMotion = new PrefersReducedMotion();
|
|
426
|
+
export const usePrefersReducedMotion = () => {
|
|
427
|
+
const [current, setCurrent] = useState(prefersReducedMotion.current);
|
|
428
|
+
useEffect(() => prefersReducedMotion.subscribe(setCurrent), []);
|
|
429
|
+
return current;
|
|
430
|
+
};
|
|
431
|
+
export const useTween = (target, options) => {
|
|
432
|
+
const tweenRef = useRef(null);
|
|
433
|
+
if (!tweenRef.current)
|
|
434
|
+
tweenRef.current = new Tween(target, options);
|
|
435
|
+
const [current, setCurrent] = useState(tweenRef.current.current);
|
|
436
|
+
useEffect(() => tweenRef.current?.subscribe(setCurrent), []);
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
void tweenRef.current?.set(target, options);
|
|
439
|
+
}, [target, options]);
|
|
440
|
+
return { current, tween: tweenRef.current };
|
|
441
|
+
};
|
|
442
|
+
export const useSpring = (target, options) => {
|
|
443
|
+
const springRef = useRef(null);
|
|
444
|
+
if (!springRef.current)
|
|
445
|
+
springRef.current = new Spring(target, options);
|
|
446
|
+
const [current, setCurrent] = useState(springRef.current.current);
|
|
447
|
+
useEffect(() => springRef.current?.subscribe(setCurrent), []);
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
void springRef.current?.set(target);
|
|
450
|
+
}, [target]);
|
|
451
|
+
return { current, spring: springRef.current };
|
|
452
|
+
};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type TransitionName, type TransitionFunction } from './transition';
|
|
3
|
+
export type TransitionLike<P = unknown> = TransitionName | TransitionFunction<P> | null;
|
|
4
|
+
export type TransitionMode = 'bidirectional' | 'separate';
|
|
5
|
+
export type TransitionStatus = 'entering' | 'entered' | 'exiting' | 'exited';
|
|
6
|
+
export interface UseTransitionOptions<P = unknown> {
|
|
7
|
+
transition?: TransitionLike<P>;
|
|
8
|
+
params?: P;
|
|
9
|
+
inTransition?: TransitionLike<P>;
|
|
10
|
+
outTransition?: TransitionLike<P>;
|
|
11
|
+
inParams?: P;
|
|
12
|
+
outParams?: P;
|
|
13
|
+
mode?: TransitionMode;
|
|
14
|
+
intro?: boolean;
|
|
15
|
+
onIntroStart?: () => void;
|
|
16
|
+
onIntroEnd?: () => void;
|
|
17
|
+
onOutroStart?: () => void;
|
|
18
|
+
onOutroEnd?: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const useTransition: <T extends Element = HTMLDivElement, P = unknown>(visible: boolean, options?: UseTransitionOptions<P>) => {
|
|
21
|
+
ref: React.RefObject<T | null>;
|
|
22
|
+
shouldRender: boolean;
|
|
23
|
+
status: TransitionStatus;
|
|
24
|
+
};
|
|
25
|
+
export interface TransitionProps<P = unknown> extends UseTransitionOptions<P> {
|
|
26
|
+
visible: boolean;
|
|
27
|
+
as?: keyof React.JSX.IntrinsicElements;
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: unknown;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
export declare const Transition: <P>({ visible, as, children, transition, params, inTransition, outTransition, inParams, outParams, mode, intro, onIntroStart, onIntroEnd, onOutroStart, onOutroEnd, ...rest }: TransitionProps<P>) => React.DOMElement<React.DOMAttributes<Element>, Element> | null;
|