@octanejs/spring 0.0.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/UPSTREAM.md +103 -0
  4. package/package.json +73 -0
  5. package/src/animated/Animated.ts +24 -0
  6. package/src/animated/AnimatedArray.ts +26 -0
  7. package/src/animated/index.ts +3 -0
  8. package/src/browser.ts +246 -0
  9. package/src/components.tsrx +60 -0
  10. package/src/components.tsrx.d.ts +33 -0
  11. package/src/context.ts +13 -0
  12. package/src/core/AnimationConfig.ts +60 -0
  13. package/src/core/AnimationResult.ts +19 -0
  14. package/src/core/Controller.ts +200 -0
  15. package/src/core/FrameValue.ts +16 -0
  16. package/src/core/Interpolation.ts +46 -0
  17. package/src/core/SpringRef.ts +26 -0
  18. package/src/core/SpringValue.ts +384 -0
  19. package/src/core/index.ts +9 -0
  20. package/src/core/runAsync.ts +17 -0
  21. package/src/core/scheduleProps.ts +34 -0
  22. package/src/core/valueInterpolation.ts +54 -0
  23. package/src/engine.ts +9 -0
  24. package/src/hooks.ts +610 -0
  25. package/src/index.ts +26 -0
  26. package/src/parallax.ts +8 -0
  27. package/src/parallax.tsrx +321 -0
  28. package/src/parallax.tsrx.d.ts +60 -0
  29. package/src/shared/FrameLoop.ts +75 -0
  30. package/src/shared/createInterpolator.ts +85 -0
  31. package/src/shared/globals.ts +7 -0
  32. package/src/shared/index.ts +4 -0
  33. package/src/shared/stringInterpolation.ts +56 -0
  34. package/src/spring-component.ts +36 -0
  35. package/src/types/animated.ts +10 -0
  36. package/src/types/common.ts +29 -0
  37. package/src/types/fluid.ts +9 -0
  38. package/src/types/index.ts +9 -0
  39. package/src/types/interpolate.ts +50 -0
  40. package/src/types/interpolation.ts +18 -0
  41. package/src/types/objects.ts +17 -0
  42. package/src/types/props.ts +275 -0
  43. package/src/types/utils.ts +57 -0
  44. package/src/upstream-compat.ts +164 -0
  45. package/src/web/AnimatedStyle.ts +51 -0
  46. package/src/web/animated.ts +118 -0
  47. package/src/web/applyAnimatedValues.ts +92 -0
  48. package/src/web/index.ts +3 -0
@@ -0,0 +1,60 @@
1
+ // Behavioral port of react-spring v10.1.2 packages/core/src/AnimationConfig.ts.
2
+ export class AnimationConfig {
3
+ tension = 170;
4
+ friction = 26;
5
+ frequency?: number;
6
+ damping = 1;
7
+ mass = 1;
8
+ velocity: number | number[] = 0;
9
+ restVelocity?: number;
10
+ precision?: number;
11
+ progress?: number;
12
+ duration?: number;
13
+ easing = (value: number): number => value;
14
+ clamp = false;
15
+ bounce?: number;
16
+ decay?: boolean | number;
17
+ round?: number;
18
+ }
19
+
20
+ export function mergeConfig(
21
+ config: AnimationConfig,
22
+ newConfig: Partial<AnimationConfig>,
23
+ defaultConfig?: Partial<AnimationConfig>,
24
+ ): AnimationConfig {
25
+ let props = newConfig;
26
+ if (defaultConfig) {
27
+ const defaults = { ...defaultConfig };
28
+ sanitizeConfig(defaults, newConfig);
29
+ props = { ...defaults, ...newConfig };
30
+ }
31
+ sanitizeConfig(config, props);
32
+ Object.assign(config, props);
33
+ if (config.frequency !== undefined) {
34
+ config.frequency = Math.max(0.01, config.frequency);
35
+ config.damping = Math.max(0, config.damping);
36
+ config.tension = Math.pow((2 * Math.PI) / config.frequency, 2) * config.mass;
37
+ config.friction = (4 * Math.PI * config.damping * config.mass) / config.frequency;
38
+ }
39
+ return config;
40
+ }
41
+
42
+ function sanitizeConfig(config: Partial<AnimationConfig>, props: Partial<AnimationConfig>): void {
43
+ if (props.decay !== undefined) {
44
+ config.duration = undefined;
45
+ } else {
46
+ const isTensionConfig = props.tension !== undefined || props.friction !== undefined;
47
+ if (
48
+ isTensionConfig ||
49
+ props.frequency !== undefined ||
50
+ props.damping !== undefined ||
51
+ props.mass !== undefined
52
+ ) {
53
+ config.duration = undefined;
54
+ config.decay = undefined;
55
+ }
56
+ if (isTensionConfig) {
57
+ config.frequency = undefined;
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,19 @@
1
+ // Ported from react-spring v10.1.2 packages/core/src/AnimationResult.ts.
2
+ export interface AnimationResult<T> {
3
+ value: T;
4
+ finished: boolean;
5
+ cancelled: boolean;
6
+ noop?: boolean;
7
+ }
8
+
9
+ export function getFinishedResult<T>(value: T, finished: boolean): AnimationResult<T> {
10
+ return { value, finished, cancelled: false };
11
+ }
12
+
13
+ export function getCancelledResult<T>(value: T): AnimationResult<T> {
14
+ return { value, finished: false, cancelled: true };
15
+ }
16
+
17
+ export function getNoopResult<T>(value: T): AnimationResult<T> {
18
+ return { value, finished: true, cancelled: false, noop: true };
19
+ }
@@ -0,0 +1,200 @@
1
+ // React-free behavioral port of react-spring v10.1.2 packages/core/src/Controller.ts.
2
+ import { hasReservedProps, inferTo } from '../upstream-compat';
3
+ import {
4
+ type AnimationResult,
5
+ getCancelledResult,
6
+ getFinishedResult,
7
+ getNoopResult,
8
+ } from './AnimationResult';
9
+ import { type SpringConfig, type SpringUpdate, SpringValue } from './SpringValue';
10
+
11
+ type StateRecord = Record<string, any>;
12
+ export type ControllerTo<State extends StateRecord> =
13
+ | Partial<State>
14
+ | ((
15
+ next: (update: ControllerUpdate<State> | Partial<State>) => Promise<AnimationResult<State>>,
16
+ ) => Promise<void> | void);
17
+
18
+ export interface ControllerUpdate<State extends StateRecord> {
19
+ default?: boolean | Partial<ControllerUpdate<State>>;
20
+ from?: Partial<State>;
21
+ to?: ControllerTo<State>;
22
+ config?: SpringConfig | ((key: keyof State) => SpringConfig);
23
+ immediate?: boolean | ((key: keyof State) => boolean);
24
+ delay?: number;
25
+ cancel?: boolean;
26
+ pause?: boolean;
27
+ reset?: boolean;
28
+ loop?: boolean | (() => boolean | Partial<ControllerUpdate<State>>);
29
+ onStart?: (result: AnimationResult<State>, controller: Controller<State>) => void;
30
+ onChange?: (result: AnimationResult<State>, controller: Controller<State>) => void;
31
+ onRest?: (result: AnimationResult<State>, controller: Controller<State>) => void;
32
+ onResolve?: (result: AnimationResult<State>, controller: Controller<State>) => void;
33
+ }
34
+
35
+ export class Controller<State extends StateRecord = StateRecord> {
36
+ springs: { [Key in keyof State]: SpringValue<State[Key]> } = {} as any;
37
+ private runId = 0;
38
+ private paused = false;
39
+ private defaults: Partial<ControllerUpdate<State>> = {};
40
+ constructor(props: ControllerUpdate<State> = {}) {
41
+ props = normalizeUpdate(props);
42
+ if (props.default === true) this.defaults = withoutTargets(props);
43
+ else if (typeof props.default === 'object') this.defaults = props.default;
44
+ const initial = {
45
+ ...props.from,
46
+ ...(props.from ? undefined : typeof props.to === 'object' ? props.to : undefined),
47
+ } as State;
48
+ for (const key in initial) this.springs[key] = new SpringValue(initial[key]);
49
+ }
50
+ get(): State {
51
+ const value: StateRecord = {};
52
+ for (const key in this.springs) value[key] = this.springs[key].get();
53
+ return value as State;
54
+ }
55
+ set(values: Partial<State>): this {
56
+ for (const key in values)
57
+ this.ensure(key as keyof State, values[key] as State[keyof State]).set(values[key]!);
58
+ return this;
59
+ }
60
+ start(update: ControllerUpdate<State> = {}): Promise<AnimationResult<State>> {
61
+ update = normalizeUpdate(update);
62
+ if (update.default === true) this.defaults = { ...this.defaults, ...withoutTargets(update) };
63
+ else if (typeof update.default === 'object')
64
+ this.defaults = { ...this.defaults, ...update.default };
65
+ update = normalizeUpdate({ ...this.defaults, ...update });
66
+ const runId = ++this.runId;
67
+ return this.run(update, runId);
68
+ }
69
+ pause(): this {
70
+ if (!this.paused) {
71
+ this.paused = true;
72
+ for (const key in this.springs) this.springs[key].pause();
73
+ }
74
+ return this;
75
+ }
76
+ resume(): this {
77
+ if (this.paused) {
78
+ this.paused = false;
79
+ for (const key in this.springs) this.springs[key].resume();
80
+ }
81
+ return this;
82
+ }
83
+ stop(cancel = false): this {
84
+ this.runId++;
85
+ for (const key in this.springs) this.springs[key].stop(cancel);
86
+ return this;
87
+ }
88
+
89
+ private async run(
90
+ update: ControllerUpdate<State>,
91
+ runId: number,
92
+ ): Promise<AnimationResult<State>> {
93
+ if (update.cancel) {
94
+ this.stop(true);
95
+ return getCancelledResult(this.get());
96
+ }
97
+ if (update.pause === true) {
98
+ this.pause();
99
+ return getFinishedResult(this.get(), false);
100
+ }
101
+ if (update.pause === false) this.resume();
102
+ if ((update.delay ?? 0) > 0) {
103
+ await new Promise<void>((resolve) => setTimeout(resolve, update.delay));
104
+ if (runId !== this.runId) return getCancelledResult(this.get());
105
+ update = { ...update, delay: undefined };
106
+ }
107
+ const to = update.to;
108
+ if (typeof to === 'function') {
109
+ let last = getNoopResult(this.get());
110
+ await to(async (next) => {
111
+ if (runId !== this.runId) return getCancelledResult(this.get());
112
+ const step = isControllerUpdate(next)
113
+ ? { ...update, ...next, loop: undefined }
114
+ : { ...update, to: next, loop: undefined };
115
+ last = await this.run(normalizeUpdate(step), runId);
116
+ return last;
117
+ });
118
+ if (runId !== this.runId) return getCancelledResult(this.get());
119
+ const result = { ...last, value: this.get() };
120
+ update.onRest?.(result, this);
121
+ update.onResolve?.(result, this);
122
+ return result;
123
+ }
124
+ const from: Partial<State> = update.from ?? {};
125
+ const target: Partial<State> = to ?? {};
126
+ for (const key in from) this.ensure(key as keyof State, from[key] as State[keyof State]);
127
+ let started = false;
128
+ const promises = Object.keys(target).map((key) => {
129
+ const typedKey = key as keyof State;
130
+ const spring = this.ensure(
131
+ typedKey,
132
+ (from[typedKey] ?? target[typedKey]) as State[typeof typedKey],
133
+ );
134
+ const props: SpringUpdate<State[typeof typedKey]> = {
135
+ to: target[typedKey]!,
136
+ reset: update.reset,
137
+ delay: update.delay,
138
+ config: typeof update.config === 'function' ? update.config(typedKey) : update.config,
139
+ immediate:
140
+ typeof update.immediate === 'function' ? update.immediate(typedKey) : update.immediate,
141
+ onStart: () => {
142
+ if (!started) {
143
+ started = true;
144
+ update.onStart?.(getFinishedResult(this.get(), false), this);
145
+ }
146
+ },
147
+ onChange: () => update.onChange?.(getFinishedResult(this.get(), false), this),
148
+ };
149
+ // Upstream ignores `from` except on reset; initial values come from construction.
150
+ if (update.reset && from[typedKey] !== undefined) props.from = from[typedKey];
151
+ return spring.start(props);
152
+ });
153
+ const results = await Promise.all(promises);
154
+ if (runId !== this.runId) return getCancelledResult(this.get());
155
+ let result: AnimationResult<State> = {
156
+ value: this.get(),
157
+ finished: results.every((item) => item.finished),
158
+ cancelled: results.some((item) => item.cancelled),
159
+ ...(results.length === 0 ? { noop: true } : {}),
160
+ };
161
+ const loop = update.loop;
162
+ const nextLoop = typeof loop === 'function' ? loop() : loop;
163
+ if (nextLoop && result.finished) {
164
+ result = await this.run(
165
+ { ...update, ...(typeof nextLoop === 'object' ? nextLoop : {}), reset: true, loop },
166
+ runId,
167
+ );
168
+ return result;
169
+ }
170
+ update.onRest?.(result, this);
171
+ update.onResolve?.(result, this);
172
+ return result;
173
+ }
174
+ private ensure<Key extends keyof State>(key: Key, initial: State[Key]): SpringValue<State[Key]> {
175
+ return (this.springs[key] ??= new SpringValue(initial));
176
+ }
177
+ }
178
+
179
+ function normalizeUpdate<State extends StateRecord>(
180
+ update: ControllerUpdate<State> | Partial<State>,
181
+ ): ControllerUpdate<State> {
182
+ return inferTo(update as object) as ControllerUpdate<State>;
183
+ }
184
+
185
+ function withoutTargets<State extends StateRecord>(
186
+ update: ControllerUpdate<State>,
187
+ ): Partial<ControllerUpdate<State>> {
188
+ const { from: _from, to: _to, default: _default, ...defaults } = update;
189
+ return defaults;
190
+ }
191
+
192
+ function isControllerUpdate<State extends StateRecord>(
193
+ value: ControllerUpdate<State> | Partial<State>,
194
+ ): value is ControllerUpdate<State> {
195
+ // Check reserved keys on the raw object. Do not run inferTo here: shorthand
196
+ // steps like `{ x: 1 }` must stay on the `to: next` path so the parent async
197
+ // `to` function is replaced rather than left in place. Lifecycle keys such as
198
+ // onRest/onStart must still count as updates so they are not animated.
199
+ return hasReservedProps(value as object);
200
+ }
@@ -0,0 +1,16 @@
1
+ // React-free observable base adapted from react-spring v10.1.2 packages/core/src/FrameValue.ts.
2
+ export type ChangeListener<T> = (value: T) => void;
3
+
4
+ export abstract class FrameValue<T> {
5
+ protected readonly listeners = new Set<ChangeListener<T>>();
6
+ idle = true;
7
+ priority = 0;
8
+ abstract get(): T;
9
+ onChange(listener: ChangeListener<T>): () => void {
10
+ this.listeners.add(listener);
11
+ return () => this.listeners.delete(listener);
12
+ }
13
+ protected emitChange(value: T): void {
14
+ for (const listener of this.listeners) listener(value);
15
+ }
16
+ }
@@ -0,0 +1,46 @@
1
+ // React-free behavioral port of react-spring v10.1.2 packages/core/src/Interpolation.ts.
2
+ import { createInterpolator } from '../shared/index';
3
+ import type { Interpolator } from '../types/interpolate';
4
+ import type { ExtrapolateType, InterpolatorConfig } from '../types/index';
5
+ import { FrameValue } from './FrameValue';
6
+
7
+ export class Interpolation<T = any> extends FrameValue<T> {
8
+ private readonly cleanups: Array<() => void>;
9
+ constructor(
10
+ private readonly sources: Array<FrameValue<any>>,
11
+ private readonly calc: (...values: any[]) => T,
12
+ ) {
13
+ super();
14
+ this.idle = sources.every((source) => source.idle);
15
+ this.cleanups = sources.map((source) =>
16
+ source.onChange(() => {
17
+ this.idle = this.sources.every((parent) => parent.idle);
18
+ this.emitChange(this.get());
19
+ }),
20
+ );
21
+ }
22
+ get(): T {
23
+ return this.calc(...this.sources.map((source) => source.get()));
24
+ }
25
+ dispose(): void {
26
+ for (const cleanup of this.cleanups) cleanup();
27
+ this.cleanups.length = 0;
28
+ this.listeners.clear();
29
+ this.idle = true;
30
+ }
31
+ }
32
+
33
+ export const to: Interpolator = function to(source: any, ...args: any[]): Interpolation<any> {
34
+ if (args.length >= 2 && Array.isArray(args[0]) && Array.isArray(args[1])) {
35
+ const range = args[0] as readonly number[];
36
+ const output = args[1] as readonly unknown[];
37
+ const extrapolate = args[2] as ExtrapolateType | undefined;
38
+ return to(source, { range, output, extrapolate } as InterpolatorConfig);
39
+ }
40
+ const calc = args[0];
41
+ const interpolate =
42
+ typeof calc === 'function' ? calc : createInterpolator(calc as InterpolatorConfig);
43
+ return new Interpolation(Array.isArray(source) ? source : [source], interpolate);
44
+ };
45
+
46
+ export const interpolate: Interpolator = to;
@@ -0,0 +1,26 @@
1
+ // React-free port of react-spring v10.1.2 packages/core/src/SpringRef.ts.
2
+ import type { AnimationResult } from './AnimationResult';
3
+ import { Controller, type ControllerUpdate } from './Controller';
4
+
5
+ export class SpringRef<State extends Record<string, any> = Record<string, any>> {
6
+ current: Controller<State>[] = [];
7
+ add(controller: Controller<State>): void {
8
+ if (!this.current.includes(controller)) this.current.push(controller);
9
+ }
10
+ delete(controller: Controller<State>): void {
11
+ const index = this.current.indexOf(controller);
12
+ if (index >= 0) this.current.splice(index, 1);
13
+ }
14
+ start(update?: ControllerUpdate<State>): Promise<Array<AnimationResult<State>>> {
15
+ return Promise.all(this.current.map((controller) => controller.start(update ?? {})));
16
+ }
17
+ pause(): void {
18
+ for (const controller of this.current) controller.pause();
19
+ }
20
+ resume(): void {
21
+ for (const controller of this.current) controller.resume();
22
+ }
23
+ stop(cancel = false): void {
24
+ for (const controller of this.current) controller.stop(cancel);
25
+ }
26
+ }