tensum 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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/coupled-system.d.ts +50 -0
- package/dist/coupled-system.d.ts.map +1 -0
- package/dist/coupled-system.js +269 -0
- package/dist/coupled-system.js.map +1 -0
- package/dist/coupled.d.ts +3 -0
- package/dist/coupled.d.ts.map +1 -0
- package/dist/coupled.js +2 -0
- package/dist/coupled.js.map +1 -0
- package/dist/css-linear.d.ts +20 -0
- package/dist/css-linear.d.ts.map +1 -0
- package/dist/css-linear.js +95 -0
- package/dist/css-linear.js.map +1 -0
- package/dist/css.d.ts +3 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +2 -0
- package/dist/css.js.map +1 -0
- package/dist/gsap/active-tracks.d.ts +32 -0
- package/dist/gsap/active-tracks.d.ts.map +1 -0
- package/dist/gsap/active-tracks.js +177 -0
- package/dist/gsap/active-tracks.js.map +1 -0
- package/dist/gsap/gsap-time.d.ts +10 -0
- package/dist/gsap/gsap-time.d.ts.map +1 -0
- package/dist/gsap/gsap-time.js +62 -0
- package/dist/gsap/gsap-time.js.map +1 -0
- package/dist/gsap/plugin-tween-coordinator.d.ts +30 -0
- package/dist/gsap/plugin-tween-coordinator.d.ts.map +1 -0
- package/dist/gsap/plugin-tween-coordinator.js +72 -0
- package/dist/gsap/plugin-tween-coordinator.js.map +1 -0
- package/dist/gsap/plugin.d.ts +84 -0
- package/dist/gsap/plugin.d.ts.map +1 -0
- package/dist/gsap/plugin.js +552 -0
- package/dist/gsap/plugin.js.map +1 -0
- package/dist/gsap/spring-to.d.ts +93 -0
- package/dist/gsap/spring-to.d.ts.map +1 -0
- package/dist/gsap/spring-to.js +569 -0
- package/dist/gsap/spring-to.js.map +1 -0
- package/dist/gsap/spring-track-policy.d.ts +24 -0
- package/dist/gsap/spring-track-policy.d.ts.map +1 -0
- package/dist/gsap/spring-track-policy.js +76 -0
- package/dist/gsap/spring-track-policy.js.map +1 -0
- package/dist/gsap/track-lifecycle.d.ts +16 -0
- package/dist/gsap/track-lifecycle.d.ts.map +1 -0
- package/dist/gsap/track-lifecycle.js +33 -0
- package/dist/gsap/track-lifecycle.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +9 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +38 -0
- package/dist/math.js.map +1 -0
- package/dist/model.d.ts +14 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +38 -0
- package/dist/model.js.map +1 -0
- package/dist/motion-compatibility.d.ts +18 -0
- package/dist/motion-compatibility.d.ts.map +1 -0
- package/dist/motion-compatibility.js +83 -0
- package/dist/motion-compatibility.js.map +1 -0
- package/dist/parameters.d.ts +52 -0
- package/dist/parameters.d.ts.map +1 -0
- package/dist/parameters.js +124 -0
- package/dist/parameters.js.map +1 -0
- package/dist/settling.d.ts +12 -0
- package/dist/settling.d.ts.map +1 -0
- package/dist/settling.js +107 -0
- package/dist/settling.js.map +1 -0
- package/dist/solver.d.ts +17 -0
- package/dist/solver.d.ts.map +1 -0
- package/dist/solver.js +190 -0
- package/dist/solver.js.map +1 -0
- package/dist/spring.d.ts +9 -0
- package/dist/spring.d.ts.map +1 -0
- package/dist/spring.js +91 -0
- package/dist/spring.js.map +1 -0
- package/dist/support/additive-spring-value.d.ts +39 -0
- package/dist/support/additive-spring-value.d.ts.map +1 -0
- package/dist/support/additive-spring-value.js +281 -0
- package/dist/support/additive-spring-value.js.map +1 -0
- package/dist/support/inertia.d.ts +34 -0
- package/dist/support/inertia.d.ts.map +1 -0
- package/dist/support/inertia.js +293 -0
- package/dist/support/inertia.js.map +1 -0
- package/dist/support/keyframes.d.ts +34 -0
- package/dist/support/keyframes.d.ts.map +1 -0
- package/dist/support/keyframes.js +117 -0
- package/dist/support/keyframes.js.map +1 -0
- package/dist/support/spring-value.d.ts +38 -0
- package/dist/support/spring-value.d.ts.map +1 -0
- package/dist/support/spring-value.js +289 -0
- package/dist/support/spring-value.js.map +1 -0
- package/dist/support/velocity.d.ts +14 -0
- package/dist/support/velocity.d.ts.map +1 -0
- package/dist/support/velocity.js +75 -0
- package/dist/support/velocity.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vector.d.ts +32 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +121 -0
- package/dist/vector.js.map +1 -0
- package/package.json +68 -0
- package/src/coupled-system.ts +383 -0
- package/src/coupled.ts +10 -0
- package/src/css-linear.ts +148 -0
- package/src/css.ts +6 -0
- package/src/gsap/active-tracks.ts +244 -0
- package/src/gsap/gsap-time.ts +91 -0
- package/src/gsap/plugin-tween-coordinator.ts +112 -0
- package/src/gsap/plugin.ts +861 -0
- package/src/gsap/spring-to.ts +836 -0
- package/src/gsap/spring-track-policy.ts +133 -0
- package/src/gsap/track-lifecycle.ts +53 -0
- package/src/index.ts +130 -0
- package/src/math.ts +46 -0
- package/src/model.ts +70 -0
- package/src/motion-compatibility.ts +131 -0
- package/src/parameters.ts +226 -0
- package/src/settling.ts +141 -0
- package/src/solver.ts +262 -0
- package/src/spring.ts +119 -0
- package/src/support/additive-spring-value.ts +343 -0
- package/src/support/inertia.ts +367 -0
- package/src/support/keyframes.ts +188 -0
- package/src/support/spring-value.ts +399 -0
- package/src/support/velocity.ts +119 -0
- package/src/types.ts +70 -0
- package/src/vector.ts +191 -0
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
import { createSpring } from '../spring.js';
|
|
2
|
+
import type {
|
|
3
|
+
SettlingResult,
|
|
4
|
+
SpringParameters,
|
|
5
|
+
SpringSettleInput,
|
|
6
|
+
SpringSolution,
|
|
7
|
+
SpringState,
|
|
8
|
+
} from '../types.js';
|
|
9
|
+
import { gsap } from 'gsap';
|
|
10
|
+
import {
|
|
11
|
+
activeTrackState,
|
|
12
|
+
reconcileActiveTrackHandoff,
|
|
13
|
+
registerActiveTrack,
|
|
14
|
+
} from './active-tracks.js';
|
|
15
|
+
import type { ActiveTrackRegistration } from './active-tracks.js';
|
|
16
|
+
import { currentLocalCycle, localCycleAt, localTimeAt } from './gsap-time.js';
|
|
17
|
+
import {
|
|
18
|
+
beginPluginTweenInit,
|
|
19
|
+
registerPluginTweenParticipant,
|
|
20
|
+
} from './plugin-tween-coordinator.js';
|
|
21
|
+
import type { PluginTweenRegistration } from './plugin-tween-coordinator.js';
|
|
22
|
+
import {
|
|
23
|
+
gsapSafeDuration,
|
|
24
|
+
springTrackState,
|
|
25
|
+
springTrackTiming,
|
|
26
|
+
validateUnsettledPolicy,
|
|
27
|
+
} from './spring-track-policy.js';
|
|
28
|
+
import type {
|
|
29
|
+
SpringTrackTiming,
|
|
30
|
+
UnsettledPolicy,
|
|
31
|
+
} from './spring-track-policy.js';
|
|
32
|
+
import {
|
|
33
|
+
retireActiveTrackRegistrations,
|
|
34
|
+
syncActiveTrackRegistration,
|
|
35
|
+
} from './track-lifecycle.js';
|
|
36
|
+
import {
|
|
37
|
+
SUPPORTED_PROPERTIES,
|
|
38
|
+
accessFor,
|
|
39
|
+
optionsFor,
|
|
40
|
+
parseNumericValue,
|
|
41
|
+
velocityFor,
|
|
42
|
+
} from './spring-to.js';
|
|
43
|
+
import type {
|
|
44
|
+
RequestedTarget,
|
|
45
|
+
SpringProperty,
|
|
46
|
+
SpringPropertyAdapter,
|
|
47
|
+
SpringPropertyOptions,
|
|
48
|
+
SpringStateMap,
|
|
49
|
+
SpringTargetValue,
|
|
50
|
+
SpringTargets,
|
|
51
|
+
SpringToSnapshot,
|
|
52
|
+
SpringTrackConfig,
|
|
53
|
+
SpringVelocities,
|
|
54
|
+
} from './spring-to.js';
|
|
55
|
+
|
|
56
|
+
/** Spring configuration shared by the timeline effect and direct helper. */
|
|
57
|
+
export interface MotionSpringVars {
|
|
58
|
+
x?: SpringTargetValue;
|
|
59
|
+
y?: SpringTargetValue;
|
|
60
|
+
scale?: SpringTargetValue;
|
|
61
|
+
rotation?: SpringTargetValue;
|
|
62
|
+
values?: SpringTargets;
|
|
63
|
+
parameters: SpringParameters & { settle?: SpringSettleInput };
|
|
64
|
+
velocity?: number | SpringVelocities;
|
|
65
|
+
properties?: Readonly<Partial<Record<SpringProperty, SpringPropertyOptions>>>;
|
|
66
|
+
adapters?: Readonly<Partial<Record<SpringProperty, SpringPropertyAdapter>>>;
|
|
67
|
+
units?: Readonly<Partial<Record<SpringProperty, string>>>;
|
|
68
|
+
/** Behavior when a track reaches `maxDuration` without settling. */
|
|
69
|
+
unsettled?: UnsettledPolicy;
|
|
70
|
+
/**
|
|
71
|
+
* Runs once per target at each forward logical-completion crossing. The
|
|
72
|
+
* snapshot does not include the target or its array index.
|
|
73
|
+
*/
|
|
74
|
+
onLogicalComplete?: (snapshot: SpringToSnapshot) => void;
|
|
75
|
+
/**
|
|
76
|
+
* Runs once per settled target at each forward settlement crossing. The
|
|
77
|
+
* snapshot does not include the target or its array index.
|
|
78
|
+
*/
|
|
79
|
+
onSettle?: (snapshot: SpringToSnapshot) => void;
|
|
80
|
+
/**
|
|
81
|
+
* Runs once per unsettled target at each forward `maxDuration` crossing. The
|
|
82
|
+
* snapshot does not include the target or its array index.
|
|
83
|
+
*/
|
|
84
|
+
onUnsettled?: (snapshot: SpringToSnapshot) => void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* GSAP options forwarded by the preflighted effect. Spring owns `duration` and
|
|
89
|
+
* `ease`, so those keys are rejected here.
|
|
90
|
+
*/
|
|
91
|
+
export type MotionSpringEffectTweenVars = Omit<
|
|
92
|
+
gsap.TweenVars,
|
|
93
|
+
'duration' | 'ease'
|
|
94
|
+
> & {
|
|
95
|
+
duration?: never;
|
|
96
|
+
ease?: never;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Configuration for the preflighted GSAP effect. The effect resolves its
|
|
101
|
+
* driver duration before GSAP inserts the returned tween into a timeline.
|
|
102
|
+
*
|
|
103
|
+
* `from` snapshots an explicit future starting state. Supply it when building
|
|
104
|
+
* a timeline whose target will be changed by an earlier child; without it,
|
|
105
|
+
* preflight intentionally reads the target at effect-construction time.
|
|
106
|
+
*/
|
|
107
|
+
export interface MotionSpringEffectVars extends MotionSpringVars {
|
|
108
|
+
/**
|
|
109
|
+
* Explicit starting snapshot captured during effect construction. One map is
|
|
110
|
+
* applied to every resolved target. Without it, preflight reads each target.
|
|
111
|
+
*/
|
|
112
|
+
from?: SpringTargets;
|
|
113
|
+
/** GSAP driver options such as `paused`, `stagger`, `repeat`, and `yoyo`. */
|
|
114
|
+
tween?: MotionSpringEffectTweenVars;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface PluginTrack {
|
|
118
|
+
property: SpringProperty;
|
|
119
|
+
target: number;
|
|
120
|
+
unit?: string;
|
|
121
|
+
duration: number;
|
|
122
|
+
settling: SettlingResult;
|
|
123
|
+
spring: SpringSolution;
|
|
124
|
+
registration?: ActiveTrackRegistration;
|
|
125
|
+
lastTime: number;
|
|
126
|
+
write(value: number): void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface MotionSpringDriverScope extends gsap.PluginScope {
|
|
130
|
+
tracks: PluginTrack[];
|
|
131
|
+
tween: gsap.core.Tween;
|
|
132
|
+
policy: UnsettledPolicy;
|
|
133
|
+
duration: number;
|
|
134
|
+
finiteDuration: number;
|
|
135
|
+
logicalDuration: number;
|
|
136
|
+
unsettledAt: number;
|
|
137
|
+
hasUnsettled: boolean;
|
|
138
|
+
killed: boolean;
|
|
139
|
+
didLogicalComplete: boolean;
|
|
140
|
+
didSettle: boolean;
|
|
141
|
+
didNotifyUnsettled: boolean;
|
|
142
|
+
callbacks: Pick<
|
|
143
|
+
MotionSpringVars,
|
|
144
|
+
'onLogicalComplete' | 'onSettle' | 'onUnsettled'
|
|
145
|
+
>;
|
|
146
|
+
coordinator?: PluginTweenRegistration;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface PreparedTrack {
|
|
150
|
+
target: number;
|
|
151
|
+
unit?: string;
|
|
152
|
+
duration: number;
|
|
153
|
+
settling: SettlingResult;
|
|
154
|
+
spring: SpringSolution;
|
|
155
|
+
write(value: number): void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface PreparedTarget {
|
|
159
|
+
tracks: Readonly<Record<SpringProperty, PreparedTrack>>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface PreparedMotionSpring {
|
|
163
|
+
targets: WeakMap<object, PreparedTarget>;
|
|
164
|
+
baseDuration: number;
|
|
165
|
+
baseRepeat: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const preparedMotionSprings = new WeakMap<
|
|
169
|
+
MotionSpringVars,
|
|
170
|
+
PreparedMotionSpring
|
|
171
|
+
>();
|
|
172
|
+
const INTERNAL_DRIVER_PROPERTY = '__tensumSpringDriver';
|
|
173
|
+
|
|
174
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
175
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function validateMapProperty(property: string): void {
|
|
179
|
+
parseNumericValue(0, property);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function normalizeOptionalRecord(
|
|
183
|
+
value: unknown,
|
|
184
|
+
name: string,
|
|
185
|
+
): Record<string, unknown> | undefined {
|
|
186
|
+
if (value === undefined) return undefined;
|
|
187
|
+
if (!isRecord(value)) {
|
|
188
|
+
throw new TypeError(`${name} must be an object when provided`);
|
|
189
|
+
}
|
|
190
|
+
return { ...value };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeTargets(
|
|
194
|
+
value: unknown,
|
|
195
|
+
name: string,
|
|
196
|
+
): SpringTargets | undefined {
|
|
197
|
+
const record = normalizeOptionalRecord(value, name);
|
|
198
|
+
if (!record) return undefined;
|
|
199
|
+
for (const [property, target] of Object.entries(record)) {
|
|
200
|
+
parseNumericValue(target, property);
|
|
201
|
+
}
|
|
202
|
+
return record as SpringTargets;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeProperties(value: unknown): MotionSpringVars['properties'] {
|
|
206
|
+
const record = normalizeOptionalRecord(value, 'motionSpring properties');
|
|
207
|
+
if (!record) return undefined;
|
|
208
|
+
const properties = Object.create(null) as Record<
|
|
209
|
+
string,
|
|
210
|
+
SpringPropertyOptions
|
|
211
|
+
>;
|
|
212
|
+
for (const [property, options] of Object.entries(record)) {
|
|
213
|
+
validateMapProperty(property);
|
|
214
|
+
if (!isRecord(options)) {
|
|
215
|
+
throw new TypeError(`Properties for ${property} must be an object`);
|
|
216
|
+
}
|
|
217
|
+
const normalized = { ...options };
|
|
218
|
+
for (const parameter of [
|
|
219
|
+
'mass',
|
|
220
|
+
'stiffness',
|
|
221
|
+
'damping',
|
|
222
|
+
'velocity',
|
|
223
|
+
] as const) {
|
|
224
|
+
const parameterValue = normalized[parameter];
|
|
225
|
+
if (
|
|
226
|
+
parameterValue !== undefined &&
|
|
227
|
+
(typeof parameterValue !== 'number' || !Number.isFinite(parameterValue))
|
|
228
|
+
) {
|
|
229
|
+
throw new TypeError(
|
|
230
|
+
`${parameter} for ${property} must be a finite number`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (normalized['settle'] !== undefined) {
|
|
235
|
+
if (!isRecord(normalized['settle'])) {
|
|
236
|
+
throw new TypeError(`settle for ${property} must be an object`);
|
|
237
|
+
}
|
|
238
|
+
normalized['settle'] = { ...normalized['settle'] };
|
|
239
|
+
}
|
|
240
|
+
properties[property] = normalized as SpringPropertyOptions;
|
|
241
|
+
}
|
|
242
|
+
return properties;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizeAdapters(value: unknown): MotionSpringVars['adapters'] {
|
|
246
|
+
const record = normalizeOptionalRecord(value, 'motionSpring adapters');
|
|
247
|
+
if (!record) return undefined;
|
|
248
|
+
const adapters = Object.create(null) as Record<string, SpringPropertyAdapter>;
|
|
249
|
+
for (const [property, adapter] of Object.entries(record)) {
|
|
250
|
+
validateMapProperty(property);
|
|
251
|
+
if (!isRecord(adapter)) {
|
|
252
|
+
throw new TypeError(`Adapter for ${property} must be an object`);
|
|
253
|
+
}
|
|
254
|
+
if (
|
|
255
|
+
typeof adapter['read'] !== 'function' ||
|
|
256
|
+
typeof adapter['write'] !== 'function'
|
|
257
|
+
) {
|
|
258
|
+
throw new TypeError(
|
|
259
|
+
`Adapter for ${property} must provide read and write functions`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (adapter['unit'] !== undefined && typeof adapter['unit'] !== 'string') {
|
|
263
|
+
throw new TypeError(`Adapter unit for ${property} must be a string`);
|
|
264
|
+
}
|
|
265
|
+
adapters[property] = adapter as unknown as SpringPropertyAdapter;
|
|
266
|
+
}
|
|
267
|
+
return adapters;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function normalizeUnits(value: unknown): MotionSpringVars['units'] {
|
|
271
|
+
const record = normalizeOptionalRecord(value, 'motionSpring units');
|
|
272
|
+
if (!record) return undefined;
|
|
273
|
+
const units = Object.create(null) as Record<string, string>;
|
|
274
|
+
for (const [property, unit] of Object.entries(record)) {
|
|
275
|
+
validateMapProperty(property);
|
|
276
|
+
if (typeof unit !== 'string') {
|
|
277
|
+
throw new TypeError(`Unit for ${property} must be a string`);
|
|
278
|
+
}
|
|
279
|
+
units[property] = unit;
|
|
280
|
+
}
|
|
281
|
+
return units;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function normalizeVelocity(value: unknown): MotionSpringVars['velocity'] {
|
|
285
|
+
if (value === undefined) return undefined;
|
|
286
|
+
if (typeof value === 'number') {
|
|
287
|
+
if (!Number.isFinite(value)) {
|
|
288
|
+
throw new TypeError('motionSpring velocity must be a finite number');
|
|
289
|
+
}
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
const record = normalizeOptionalRecord(value, 'motionSpring velocity')!;
|
|
293
|
+
const velocities = Object.create(null) as Record<string, number>;
|
|
294
|
+
for (const [property, velocity] of Object.entries(record)) {
|
|
295
|
+
validateMapProperty(property);
|
|
296
|
+
if (typeof velocity !== 'number' || !Number.isFinite(velocity)) {
|
|
297
|
+
throw new TypeError(`Velocity for ${property} must be a finite number`);
|
|
298
|
+
}
|
|
299
|
+
velocities[property] = velocity;
|
|
300
|
+
}
|
|
301
|
+
return velocities;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeMotionSpringVars(
|
|
305
|
+
value: unknown,
|
|
306
|
+
context = 'motionSpring',
|
|
307
|
+
): MotionSpringVars {
|
|
308
|
+
if (!isRecord(value)) {
|
|
309
|
+
throw new TypeError(`${context} requires a configuration object`);
|
|
310
|
+
}
|
|
311
|
+
const own = { ...value };
|
|
312
|
+
if (!isRecord(own['parameters'])) {
|
|
313
|
+
throw new TypeError(`${context} requires a parameters object`);
|
|
314
|
+
}
|
|
315
|
+
const parameters = { ...own['parameters'] };
|
|
316
|
+
if (parameters['settle'] !== undefined) {
|
|
317
|
+
if (!isRecord(parameters['settle'])) {
|
|
318
|
+
throw new TypeError(`${context} parameters.settle must be an object`);
|
|
319
|
+
}
|
|
320
|
+
parameters['settle'] = { ...parameters['settle'] };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const normalized = {
|
|
324
|
+
...own,
|
|
325
|
+
parameters,
|
|
326
|
+
values: normalizeTargets(own['values'], `${context} values`),
|
|
327
|
+
properties: normalizeProperties(own['properties']),
|
|
328
|
+
adapters: normalizeAdapters(own['adapters']),
|
|
329
|
+
units: normalizeUnits(own['units']),
|
|
330
|
+
velocity: normalizeVelocity(own['velocity']),
|
|
331
|
+
} as unknown as MotionSpringVars;
|
|
332
|
+
|
|
333
|
+
for (const callback of [
|
|
334
|
+
'onLogicalComplete',
|
|
335
|
+
'onSettle',
|
|
336
|
+
'onUnsettled',
|
|
337
|
+
] as const) {
|
|
338
|
+
const callbackValue = normalized[callback];
|
|
339
|
+
if (callbackValue !== undefined && typeof callbackValue !== 'function') {
|
|
340
|
+
throw new TypeError(`${context} ${callback} must be a function`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (normalized.unsettled !== undefined) {
|
|
344
|
+
validateUnsettledPolicy(normalized.unsettled, `${context} unsettled`);
|
|
345
|
+
}
|
|
346
|
+
return normalized;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function pluginTargetsFrom(
|
|
350
|
+
vars: MotionSpringVars,
|
|
351
|
+
): Record<string, RequestedTarget> {
|
|
352
|
+
const requested = Object.create(null) as Record<string, RequestedTarget>;
|
|
353
|
+
for (const property of SUPPORTED_PROPERTIES) {
|
|
354
|
+
const value = Object.hasOwn(vars, property) ? vars[property] : undefined;
|
|
355
|
+
if (value !== undefined)
|
|
356
|
+
requested[property] = parseNumericValue(value, property);
|
|
357
|
+
}
|
|
358
|
+
for (const [property, value] of Object.entries(vars.values ?? {})) {
|
|
359
|
+
requested[property] = parseNumericValue(value, property);
|
|
360
|
+
}
|
|
361
|
+
return requested;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function trackConfigFrom(vars: MotionSpringVars): SpringTrackConfig {
|
|
365
|
+
return {
|
|
366
|
+
spring: vars.parameters,
|
|
367
|
+
...(vars.velocity === undefined ? {} : { velocity: vars.velocity }),
|
|
368
|
+
...(vars.properties === undefined ? {} : { properties: vars.properties }),
|
|
369
|
+
...(vars.adapters === undefined ? {} : { adapters: vars.adapters }),
|
|
370
|
+
...(vars.units === undefined ? {} : { units: vars.units }),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function explicitStateFrom(
|
|
375
|
+
from: SpringTargets | undefined,
|
|
376
|
+
property: SpringProperty,
|
|
377
|
+
): RequestedTarget | undefined {
|
|
378
|
+
const value =
|
|
379
|
+
from && Object.hasOwn(from, property) ? from[property] : undefined;
|
|
380
|
+
return value === undefined ? undefined : parseNumericValue(value, property);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function preflightTarget(
|
|
384
|
+
target: object,
|
|
385
|
+
vars: MotionSpringVars,
|
|
386
|
+
from: SpringTargets | undefined,
|
|
387
|
+
): PreparedTarget {
|
|
388
|
+
const requested = pluginTargetsFrom(vars);
|
|
389
|
+
if (Object.keys(requested).length === 0) {
|
|
390
|
+
throw new TypeError(
|
|
391
|
+
'motionSpring requires at least one numeric target property',
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const config = trackConfigFrom(vars);
|
|
396
|
+
const tracks = Object.create(null) as Record<SpringProperty, PreparedTrack>;
|
|
397
|
+
for (const [property, destination] of Object.entries(requested)) {
|
|
398
|
+
const explicit = explicitStateFrom(from, property);
|
|
399
|
+
let inherited =
|
|
400
|
+
explicit === undefined ? activeTrackState(target, property) : undefined;
|
|
401
|
+
const initialUnit =
|
|
402
|
+
explicit?.unit ?? (inherited?.terminal ? undefined : inherited?.unit);
|
|
403
|
+
const resolvedDestination =
|
|
404
|
+
destination.unit === undefined && initialUnit !== undefined
|
|
405
|
+
? { ...destination, unit: initialUnit }
|
|
406
|
+
: destination;
|
|
407
|
+
const access = accessFor(target, property, resolvedDestination, config);
|
|
408
|
+
if (explicit === undefined) {
|
|
409
|
+
inherited = reconcileActiveTrackHandoff(
|
|
410
|
+
target,
|
|
411
|
+
property,
|
|
412
|
+
inherited,
|
|
413
|
+
access.from,
|
|
414
|
+
access.unit,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const handoffUnit = explicit?.unit ?? inherited?.unit;
|
|
418
|
+
if (
|
|
419
|
+
handoffUnit !== undefined &&
|
|
420
|
+
access.unit !== undefined &&
|
|
421
|
+
handoffUnit !== access.unit
|
|
422
|
+
) {
|
|
423
|
+
throw new TypeError(
|
|
424
|
+
`Unit mismatch for ${property}: expected ${handoffUnit}, received ${access.unit}`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const spring = createSpring({
|
|
428
|
+
from: explicit?.value ?? inherited?.position ?? access.from,
|
|
429
|
+
to: destination.value,
|
|
430
|
+
velocity:
|
|
431
|
+
inherited?.velocity ??
|
|
432
|
+
config.properties?.[property]?.velocity ??
|
|
433
|
+
velocityFor(config.velocity, property),
|
|
434
|
+
...optionsFor(config, property),
|
|
435
|
+
});
|
|
436
|
+
const settling = spring.getSettlingResult();
|
|
437
|
+
tracks[property] = {
|
|
438
|
+
target: destination.value,
|
|
439
|
+
...(access.unit === undefined ? {} : { unit: access.unit }),
|
|
440
|
+
duration: settling.duration,
|
|
441
|
+
settling,
|
|
442
|
+
spring,
|
|
443
|
+
write: access.write,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return { tracks };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Creates a preflighted GSAP tween whose duration is final before it is added
|
|
451
|
+
* to a timeline. Prefer the registered `timeline.motionSpring()` effect for
|
|
452
|
+
* sequential, staggered, or nested composition. Calling `invalidate()` reuses
|
|
453
|
+
* the prepared snapshot; create a new effect tween to preflight changed
|
|
454
|
+
* starting values, destinations, or parameters.
|
|
455
|
+
*/
|
|
456
|
+
export function createMotionSpringTween(
|
|
457
|
+
targets: gsap.TweenTarget,
|
|
458
|
+
rawVars: MotionSpringEffectVars,
|
|
459
|
+
instance: typeof gsap = gsap,
|
|
460
|
+
): gsap.core.Tween {
|
|
461
|
+
if (!isRecord(rawVars)) {
|
|
462
|
+
throw new TypeError('motionSpring effect requires a configuration object');
|
|
463
|
+
}
|
|
464
|
+
const resolvedTargets = instance.utils.toArray<object>(targets);
|
|
465
|
+
if (resolvedTargets.length === 0) {
|
|
466
|
+
throw new TypeError('motionSpring effect requires at least one target');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ownVars = { ...rawVars } as Record<string, unknown>;
|
|
470
|
+
const from = normalizeTargets(ownVars['from'], 'motionSpring effect from');
|
|
471
|
+
const tweenOptions = normalizeOptionalRecord(
|
|
472
|
+
ownVars['tween'],
|
|
473
|
+
'motionSpring effect tween',
|
|
474
|
+
) as MotionSpringEffectTweenVars | undefined;
|
|
475
|
+
delete ownVars['from'];
|
|
476
|
+
delete ownVars['tween'];
|
|
477
|
+
const pluginVars = normalizeMotionSpringVars(ownVars, 'motionSpring effect');
|
|
478
|
+
const preparedTargets = new WeakMap<object, PreparedTarget>();
|
|
479
|
+
let finiteDuration = 0;
|
|
480
|
+
let hasUnsettled = false;
|
|
481
|
+
for (const target of resolvedTargets) {
|
|
482
|
+
const prepared = preflightTarget(target, pluginVars, from);
|
|
483
|
+
preparedTargets.set(target, prepared);
|
|
484
|
+
for (const track of Object.values(prepared.tracks)) {
|
|
485
|
+
finiteDuration = Math.max(finiteDuration, track.duration);
|
|
486
|
+
hasUnsettled ||= !track.settling.settled;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const policy = validateUnsettledPolicy(
|
|
491
|
+
pluginVars.unsettled ?? 'stop',
|
|
492
|
+
'motionSpring unsettled',
|
|
493
|
+
);
|
|
494
|
+
if (policy === 'error' && hasUnsettled) {
|
|
495
|
+
throw new RangeError(
|
|
496
|
+
'motionSpring cannot start an unsettled spring in error mode',
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
const infinite = policy === 'continue' && hasUnsettled;
|
|
500
|
+
preparedMotionSprings.set(pluginVars, {
|
|
501
|
+
targets: preparedTargets,
|
|
502
|
+
baseDuration: gsapSafeDuration(finiteDuration),
|
|
503
|
+
baseRepeat:
|
|
504
|
+
typeof tweenOptions?.['repeat'] === 'number' ? tweenOptions['repeat'] : 0,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const safeTweenOptions = { ...(tweenOptions ?? {}) } as gsap.TweenVars;
|
|
508
|
+
delete safeTweenOptions.duration;
|
|
509
|
+
delete safeTweenOptions.ease;
|
|
510
|
+
delete safeTweenOptions[INTERNAL_DRIVER_PROPERTY];
|
|
511
|
+
if (infinite) safeTweenOptions.repeat = -1;
|
|
512
|
+
|
|
513
|
+
return instance.to(resolvedTargets, {
|
|
514
|
+
...safeTweenOptions,
|
|
515
|
+
duration: infinite ? 1 : gsapSafeDuration(finiteDuration),
|
|
516
|
+
ease: 'none',
|
|
517
|
+
[INTERNAL_DRIVER_PROPERTY]: pluginVars,
|
|
518
|
+
} as gsap.TweenVars);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function currentTime(scope: MotionSpringDriverScope): number {
|
|
522
|
+
return scope.policy === 'continue' && scope.hasUnsettled
|
|
523
|
+
? scope.tween.totalTime()
|
|
524
|
+
: scope.tween.time();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function timingFor(
|
|
528
|
+
tracks: readonly PluginTrack[],
|
|
529
|
+
policy: UnsettledPolicy,
|
|
530
|
+
): SpringTrackTiming {
|
|
531
|
+
return springTrackTiming(tracks, policy);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function updateScopeTiming(scope: MotionSpringDriverScope): void {
|
|
535
|
+
const timing = timingFor(scope.tracks, scope.policy);
|
|
536
|
+
scope.finiteDuration = timing.finiteDuration;
|
|
537
|
+
scope.hasUnsettled = timing.hasUnsettled;
|
|
538
|
+
scope.duration =
|
|
539
|
+
scope.policy === 'continue' && timing.hasUnsettled
|
|
540
|
+
? Number.POSITIVE_INFINITY
|
|
541
|
+
: timing.finiteDuration;
|
|
542
|
+
scope.logicalDuration = timing.logicalDuration;
|
|
543
|
+
scope.unsettledAt = timing.unsettledAt;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function snapshotAt(
|
|
547
|
+
scope: MotionSpringDriverScope,
|
|
548
|
+
time: number,
|
|
549
|
+
tracks: readonly PluginTrack[] = scope.tracks,
|
|
550
|
+
): SpringToSnapshot {
|
|
551
|
+
const states: Record<string, SpringState> = {};
|
|
552
|
+
for (const track of tracks) {
|
|
553
|
+
states[track.property] = springTrackState(track, time, scope.policy);
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
elapsed: Number.isFinite(scope.duration)
|
|
557
|
+
? Math.min(time, scope.duration)
|
|
558
|
+
: time,
|
|
559
|
+
duration: scope.duration,
|
|
560
|
+
states: states as SpringStateMap,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function renderAt(scope: MotionSpringDriverScope, time: number): void {
|
|
565
|
+
if (scope.killed) return;
|
|
566
|
+
const owners: PluginTrack[] = [];
|
|
567
|
+
const retainAtCompletedYoyoStart =
|
|
568
|
+
scope.tween.repeat() > 0 &&
|
|
569
|
+
scope.tween.yoyo() &&
|
|
570
|
+
scope.tween.repeat() % 2 === 1 &&
|
|
571
|
+
scope.tween.totalTime() >= scope.tween.totalDuration();
|
|
572
|
+
for (const track of scope.tracks) {
|
|
573
|
+
const registration = track.registration;
|
|
574
|
+
if (!registration) continue;
|
|
575
|
+
const transition = syncActiveTrackRegistration(
|
|
576
|
+
registration,
|
|
577
|
+
time,
|
|
578
|
+
track.lastTime,
|
|
579
|
+
{ retainAtStart: retainAtCompletedYoyoStart },
|
|
580
|
+
);
|
|
581
|
+
track.lastTime = time;
|
|
582
|
+
if (transition.releasedAtStart) {
|
|
583
|
+
if (!transition.restoredPrevious) {
|
|
584
|
+
track.write(springTrackState(track, 0, scope.policy).position);
|
|
585
|
+
}
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (transition.isOwner) owners.push(track);
|
|
589
|
+
}
|
|
590
|
+
if (owners.length === 0) return;
|
|
591
|
+
|
|
592
|
+
const snapshot = snapshotAt(scope, time, owners);
|
|
593
|
+
for (const track of owners) {
|
|
594
|
+
const state = snapshot.states[track.property];
|
|
595
|
+
if (state) track.write(state.position);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const timing = timingFor(owners, scope.policy);
|
|
599
|
+
|
|
600
|
+
if (time < timing.logicalDuration) scope.didLogicalComplete = false;
|
|
601
|
+
if (time < timing.finiteDuration) scope.didSettle = false;
|
|
602
|
+
if (time < timing.unsettledAt) scope.didNotifyUnsettled = false;
|
|
603
|
+
|
|
604
|
+
if (!scope.didLogicalComplete && time >= timing.logicalDuration) {
|
|
605
|
+
scope.didLogicalComplete = true;
|
|
606
|
+
scope.callbacks.onLogicalComplete?.(snapshot);
|
|
607
|
+
if (scope.killed || !scope.tween.parent) return;
|
|
608
|
+
}
|
|
609
|
+
if (
|
|
610
|
+
!timing.hasUnsettled &&
|
|
611
|
+
!scope.didSettle &&
|
|
612
|
+
time >= timing.finiteDuration
|
|
613
|
+
) {
|
|
614
|
+
scope.didSettle = true;
|
|
615
|
+
scope.callbacks.onSettle?.(snapshot);
|
|
616
|
+
if (scope.killed || !scope.tween.parent) return;
|
|
617
|
+
}
|
|
618
|
+
if (
|
|
619
|
+
timing.hasUnsettled &&
|
|
620
|
+
!scope.didNotifyUnsettled &&
|
|
621
|
+
time >= timing.unsettledAt
|
|
622
|
+
) {
|
|
623
|
+
scope.didNotifyUnsettled = true;
|
|
624
|
+
scope.callbacks.onUnsettled?.(snapshot);
|
|
625
|
+
if (scope.killed || !scope.tween.parent) return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (
|
|
629
|
+
scope.tween.repeat() >= 0 &&
|
|
630
|
+
scope.tween.totalTime() >= scope.tween.totalDuration()
|
|
631
|
+
) {
|
|
632
|
+
retireActiveTrackRegistrations(
|
|
633
|
+
scope.tracks.map((track) => track.registration),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function registerMotionSpringEffect(instance: typeof gsap): void {
|
|
639
|
+
instance.registerEffect({
|
|
640
|
+
name: 'motionSpring',
|
|
641
|
+
extendTimeline: true,
|
|
642
|
+
effect(targets: object[], vars: MotionSpringEffectVars): gsap.core.Tween {
|
|
643
|
+
return createMotionSpringTween(targets, vars, instance);
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const pluginDefinition = {
|
|
649
|
+
version: '0.1.0',
|
|
650
|
+
name: INTERNAL_DRIVER_PROPERTY,
|
|
651
|
+
headless: true,
|
|
652
|
+
rawVars: 1,
|
|
653
|
+
register(instance: typeof gsap): void {
|
|
654
|
+
registerMotionSpringEffect(instance);
|
|
655
|
+
},
|
|
656
|
+
init(
|
|
657
|
+
this: MotionSpringDriverScope,
|
|
658
|
+
target: object,
|
|
659
|
+
rawValue: MotionSpringVars,
|
|
660
|
+
tween: gsap.core.Tween,
|
|
661
|
+
_targetIndex: number,
|
|
662
|
+
): boolean {
|
|
663
|
+
const prepared = isRecord(rawValue)
|
|
664
|
+
? preparedMotionSprings.get(rawValue as MotionSpringVars)
|
|
665
|
+
: undefined;
|
|
666
|
+
if (!prepared) {
|
|
667
|
+
throw new TypeError(
|
|
668
|
+
'The Tensum GSAP driver is internal; use timeline.motionSpring() or createMotionSpringTween()',
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
const value = rawValue;
|
|
672
|
+
const requested = pluginTargetsFrom(value);
|
|
673
|
+
if (Object.keys(requested).length === 0) {
|
|
674
|
+
throw new TypeError(
|
|
675
|
+
'motionSpring requires at least one numeric target property',
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const coordinator = beginPluginTweenInit(tween, target, {
|
|
679
|
+
baseDuration: prepared.baseDuration,
|
|
680
|
+
baseRepeat: prepared.baseRepeat,
|
|
681
|
+
});
|
|
682
|
+
const preparedTarget = prepared.targets.get(target);
|
|
683
|
+
if (!preparedTarget) {
|
|
684
|
+
throw new TypeError(
|
|
685
|
+
'The preflighted motionSpring target is unavailable; create a new effect tween',
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (
|
|
689
|
+
Object.keys(preparedTarget.tracks).length !==
|
|
690
|
+
Object.keys(requested).length ||
|
|
691
|
+
Object.keys(requested).some(
|
|
692
|
+
(property) => preparedTarget.tracks[property] === undefined,
|
|
693
|
+
)
|
|
694
|
+
) {
|
|
695
|
+
throw new TypeError(
|
|
696
|
+
'A preflighted motionSpring configuration cannot change before init; create a new effect tween instead',
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const tracks: PluginTrack[] = [];
|
|
700
|
+
for (const [property, destination] of Object.entries(requested)) {
|
|
701
|
+
const preparedTrack = preparedTarget.tracks[property]!;
|
|
702
|
+
if (
|
|
703
|
+
destination.value !== preparedTrack.target ||
|
|
704
|
+
(destination.unit !== undefined &&
|
|
705
|
+
destination.unit !== preparedTrack.unit)
|
|
706
|
+
) {
|
|
707
|
+
throw new TypeError(
|
|
708
|
+
'A preflighted motionSpring configuration cannot change before init; create a new effect tween instead',
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
tracks.push({
|
|
712
|
+
property,
|
|
713
|
+
target: preparedTrack.target,
|
|
714
|
+
...(preparedTrack.unit === undefined
|
|
715
|
+
? {}
|
|
716
|
+
: { unit: preparedTrack.unit }),
|
|
717
|
+
duration: preparedTrack.duration,
|
|
718
|
+
settling: preparedTrack.settling,
|
|
719
|
+
spring: preparedTrack.spring,
|
|
720
|
+
lastTime: 0,
|
|
721
|
+
write: preparedTrack.write,
|
|
722
|
+
});
|
|
723
|
+
this._props.push(property);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const policy = validateUnsettledPolicy(
|
|
727
|
+
value.unsettled ?? 'stop',
|
|
728
|
+
'motionSpring unsettled',
|
|
729
|
+
);
|
|
730
|
+
const timing = timingFor(tracks, policy);
|
|
731
|
+
if (policy === 'error' && timing.hasUnsettled) {
|
|
732
|
+
throw new RangeError(
|
|
733
|
+
'motionSpring cannot start an unsettled spring in error mode',
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const infinite = policy === 'continue' && timing.hasUnsettled;
|
|
737
|
+
|
|
738
|
+
this.tracks = tracks;
|
|
739
|
+
this.tween = tween;
|
|
740
|
+
this.policy = policy;
|
|
741
|
+
this.duration = infinite ? Number.POSITIVE_INFINITY : timing.finiteDuration;
|
|
742
|
+
this.finiteDuration = timing.finiteDuration;
|
|
743
|
+
this.logicalDuration = timing.logicalDuration;
|
|
744
|
+
this.unsettledAt = timing.unsettledAt;
|
|
745
|
+
this.hasUnsettled = timing.hasUnsettled;
|
|
746
|
+
this.killed = false;
|
|
747
|
+
this.didLogicalComplete = false;
|
|
748
|
+
this.didSettle = false;
|
|
749
|
+
this.didNotifyUnsettled = false;
|
|
750
|
+
this.callbacks = {
|
|
751
|
+
...(value.onLogicalComplete === undefined
|
|
752
|
+
? {}
|
|
753
|
+
: { onLogicalComplete: value.onLogicalComplete }),
|
|
754
|
+
...(value.onSettle === undefined ? {} : { onSettle: value.onSettle }),
|
|
755
|
+
...(value.onUnsettled === undefined
|
|
756
|
+
? {}
|
|
757
|
+
: { onUnsettled: value.onUnsettled }),
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
for (const track of tracks) {
|
|
761
|
+
track.registration = registerActiveTrack(target, track.property, {
|
|
762
|
+
state: (globalTime) => {
|
|
763
|
+
let time = currentTime(this);
|
|
764
|
+
let direction: 1 | 0 | -1 = 1;
|
|
765
|
+
if (this.policy === 'continue' && this.hasUnsettled) {
|
|
766
|
+
direction = this.tween.reversed() ? -1 : 1;
|
|
767
|
+
if (globalTime !== undefined) {
|
|
768
|
+
time = localTimeAt(this.tween, globalTime);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
const cycle =
|
|
772
|
+
globalTime === undefined
|
|
773
|
+
? currentLocalCycle(this.tween)
|
|
774
|
+
: localCycleAt(this.tween, globalTime);
|
|
775
|
+
time = cycle.time;
|
|
776
|
+
direction = cycle.direction;
|
|
777
|
+
}
|
|
778
|
+
const state = springTrackState(track, time, this.policy);
|
|
779
|
+
return {
|
|
780
|
+
...state,
|
|
781
|
+
velocity: state.velocity * direction,
|
|
782
|
+
...(track.unit === undefined ? {} : { unit: track.unit }),
|
|
783
|
+
};
|
|
784
|
+
},
|
|
785
|
+
restore: () => {
|
|
786
|
+
track.write(
|
|
787
|
+
springTrackState(track, currentTime(this), this.policy).position,
|
|
788
|
+
);
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
this.coordinator = registerPluginTweenParticipant(coordinator, {
|
|
793
|
+
timing: () => ({
|
|
794
|
+
finiteDuration: timingFor(this.tracks, this.policy).finiteDuration,
|
|
795
|
+
infinite:
|
|
796
|
+
this.policy === 'continue' &&
|
|
797
|
+
this.tracks.some((track) => !track.settling.settled),
|
|
798
|
+
}),
|
|
799
|
+
dispose: () => {
|
|
800
|
+
for (const track of this.tracks) track.registration?.release();
|
|
801
|
+
this.killed = true;
|
|
802
|
+
this.tracks.length = 0;
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
return true;
|
|
806
|
+
},
|
|
807
|
+
render(_ratio: number, data: gsap.PluginScope): void {
|
|
808
|
+
const scope = data as MotionSpringDriverScope;
|
|
809
|
+
renderAt(scope, currentTime(scope));
|
|
810
|
+
},
|
|
811
|
+
kill(this: MotionSpringDriverScope, property?: string): boolean {
|
|
812
|
+
if (!property || property === INTERNAL_DRIVER_PROPERTY) {
|
|
813
|
+
for (const track of this.tracks) track.registration?.release();
|
|
814
|
+
this.killed = true;
|
|
815
|
+
this.tracks.length = 0;
|
|
816
|
+
this.coordinator?.remove();
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
for (const track of this.tracks) {
|
|
820
|
+
if (track.property === property) track.registration?.release();
|
|
821
|
+
}
|
|
822
|
+
this.tracks = this.tracks.filter((track) => track.property !== property);
|
|
823
|
+
this._props = this._props.filter((tracked) => tracked !== property);
|
|
824
|
+
updateScopeTiming(this);
|
|
825
|
+
if (this.tracks.length === 0) {
|
|
826
|
+
this.killed = true;
|
|
827
|
+
this.coordinator?.remove();
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
this.coordinator?.recompute();
|
|
831
|
+
return false;
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const motionSpringDriverPlugin = pluginDefinition as unknown as gsap.Plugin;
|
|
836
|
+
|
|
837
|
+
/** Registers the preflighted `motionSpring` effect and timeline extension. */
|
|
838
|
+
export function registerSpringPlugin(instance: typeof gsap = gsap): void {
|
|
839
|
+
instance.registerPlugin(motionSpringDriverPlugin);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
declare global {
|
|
843
|
+
namespace gsap {
|
|
844
|
+
interface EffectsMap {
|
|
845
|
+
motionSpring(
|
|
846
|
+
targets: TweenTarget,
|
|
847
|
+
vars: MotionSpringEffectVars,
|
|
848
|
+
): core.Tween;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
namespace core {
|
|
852
|
+
interface Timeline {
|
|
853
|
+
motionSpring(
|
|
854
|
+
targets: TweenTarget,
|
|
855
|
+
vars: MotionSpringEffectVars,
|
|
856
|
+
position?: Position,
|
|
857
|
+
): this;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|