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,244 @@
|
|
|
1
|
+
import type { SpringState } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export interface ActiveTrackState extends SpringState {
|
|
4
|
+
unit?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ActiveTrackHandoffState extends ActiveTrackState {
|
|
8
|
+
terminal?: true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ActiveTrackSource {
|
|
12
|
+
state(globalTime?: number): ActiveTrackState;
|
|
13
|
+
restore(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ActiveTrackRegistration {
|
|
17
|
+
isActive(): boolean;
|
|
18
|
+
isOwner(): boolean;
|
|
19
|
+
activate(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Marks a normally completed track as terminal. A terminal owner remains as
|
|
22
|
+
* the handoff baseline while covered historical tracks are discarded.
|
|
23
|
+
*/
|
|
24
|
+
retire(): boolean;
|
|
25
|
+
release(options?: { restore?: boolean }): boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TrackEntry {
|
|
29
|
+
active: boolean;
|
|
30
|
+
discarded: boolean;
|
|
31
|
+
order: number;
|
|
32
|
+
source: ActiveTrackSource;
|
|
33
|
+
terminal: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type PropertyTracks = Map<string, TrackEntry[]>;
|
|
37
|
+
|
|
38
|
+
const objectTracks = new WeakMap<object, PropertyTracks>();
|
|
39
|
+
let nextTrackOrder = 0;
|
|
40
|
+
|
|
41
|
+
function isObjectKey(target: unknown): target is object {
|
|
42
|
+
return (
|
|
43
|
+
(typeof target === 'object' && target !== null) ||
|
|
44
|
+
typeof target === 'function'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function tracksFor(
|
|
49
|
+
target: unknown,
|
|
50
|
+
create: boolean,
|
|
51
|
+
): PropertyTracks | undefined {
|
|
52
|
+
if (!isObjectKey(target)) {
|
|
53
|
+
if (create) {
|
|
54
|
+
throw new TypeError('active spring tracks require an object target');
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const existing = objectTracks.get(target);
|
|
60
|
+
if (existing || !create) return existing;
|
|
61
|
+
const tracks = new Map<string, TrackEntry[]>();
|
|
62
|
+
objectTracks.set(target, tracks);
|
|
63
|
+
return tracks;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function stackFor(
|
|
67
|
+
target: unknown,
|
|
68
|
+
property: string,
|
|
69
|
+
create: boolean,
|
|
70
|
+
): TrackEntry[] | undefined {
|
|
71
|
+
const tracks = tracksFor(target, create);
|
|
72
|
+
if (!tracks) return undefined;
|
|
73
|
+
const existing = tracks.get(property);
|
|
74
|
+
if (existing || !create) return existing;
|
|
75
|
+
const stack: TrackEntry[] = [];
|
|
76
|
+
tracks.set(property, stack);
|
|
77
|
+
return stack;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cleanup(target: unknown, property: string): void {
|
|
81
|
+
const tracks = tracksFor(target, false);
|
|
82
|
+
if (!tracks) return;
|
|
83
|
+
const stack = tracks.get(property);
|
|
84
|
+
if (stack?.length === 0) tracks.delete(property);
|
|
85
|
+
if (tracks.size !== 0) return;
|
|
86
|
+
if (isObjectKey(target)) objectTracks.delete(target);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function activeTrackState(
|
|
90
|
+
target: unknown,
|
|
91
|
+
property: string,
|
|
92
|
+
globalTime?: number,
|
|
93
|
+
): ActiveTrackHandoffState | undefined {
|
|
94
|
+
const owner = stackFor(target, property, false)?.at(-1);
|
|
95
|
+
if (!owner) return undefined;
|
|
96
|
+
return {
|
|
97
|
+
...owner.source.state(globalTime),
|
|
98
|
+
...(owner.terminal ? { terminal: true as const } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Invalidates a terminal handoff baseline after an external write. The whole
|
|
104
|
+
* property history is discarded so reversing an older tween cannot restore a
|
|
105
|
+
* value that no longer represents the target.
|
|
106
|
+
*/
|
|
107
|
+
function discardTerminalTrackHistory(
|
|
108
|
+
target: unknown,
|
|
109
|
+
property: string,
|
|
110
|
+
): boolean {
|
|
111
|
+
const stack = stackFor(target, property, false);
|
|
112
|
+
if (!stack?.at(-1)?.terminal) return false;
|
|
113
|
+
for (const entry of stack) {
|
|
114
|
+
entry.active = false;
|
|
115
|
+
entry.discarded = true;
|
|
116
|
+
entry.terminal = false;
|
|
117
|
+
}
|
|
118
|
+
stack.length = 0;
|
|
119
|
+
cleanup(target, property);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function positionsMatch(first: number, second: number): boolean {
|
|
124
|
+
const floatingPointTolerance =
|
|
125
|
+
Number.EPSILON * Math.max(1, Math.abs(first), Math.abs(second)) * 8;
|
|
126
|
+
return Math.abs(first - second) <= Math.max(1e-9, floatingPointTolerance);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Preserves a live/unchanged handoff, but atomically invalidates terminal
|
|
131
|
+
* history when the target was externally changed after completion.
|
|
132
|
+
*/
|
|
133
|
+
export function reconcileActiveTrackHandoff(
|
|
134
|
+
target: unknown,
|
|
135
|
+
property: string,
|
|
136
|
+
inherited: ActiveTrackHandoffState | undefined,
|
|
137
|
+
actualPosition: number,
|
|
138
|
+
actualUnit: string | undefined,
|
|
139
|
+
): ActiveTrackHandoffState | undefined {
|
|
140
|
+
if (
|
|
141
|
+
!inherited?.terminal ||
|
|
142
|
+
(positionsMatch(inherited.position, actualPosition) &&
|
|
143
|
+
inherited.unit === actualUnit)
|
|
144
|
+
) {
|
|
145
|
+
return inherited;
|
|
146
|
+
}
|
|
147
|
+
discardTerminalTrackHistory(target, property);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function registerActiveTrack(
|
|
152
|
+
target: unknown,
|
|
153
|
+
property: string,
|
|
154
|
+
source: ActiveTrackSource,
|
|
155
|
+
): ActiveTrackRegistration {
|
|
156
|
+
const entry: TrackEntry = {
|
|
157
|
+
active: false,
|
|
158
|
+
discarded: false,
|
|
159
|
+
order: nextTrackOrder++,
|
|
160
|
+
source,
|
|
161
|
+
terminal: false,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const activate = (): void => {
|
|
165
|
+
if (entry.active || entry.discarded) return;
|
|
166
|
+
const stack = stackFor(target, property, true)!;
|
|
167
|
+
const nextIndex = stack.findIndex(
|
|
168
|
+
(candidate) => candidate.order > entry.order,
|
|
169
|
+
);
|
|
170
|
+
if (nextIndex < 0) stack.push(entry);
|
|
171
|
+
else stack.splice(nextIndex, 0, entry);
|
|
172
|
+
entry.active = true;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const release = (options: { restore?: boolean } = {}): boolean => {
|
|
176
|
+
if (!entry.active) return false;
|
|
177
|
+
const stack = stackFor(target, property, false);
|
|
178
|
+
if (!stack) {
|
|
179
|
+
entry.active = false;
|
|
180
|
+
entry.terminal = false;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const index = stack.indexOf(entry);
|
|
184
|
+
if (index < 0) {
|
|
185
|
+
entry.active = false;
|
|
186
|
+
entry.terminal = false;
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const wasOwner = index === stack.length - 1;
|
|
190
|
+
stack.splice(index, 1);
|
|
191
|
+
entry.active = false;
|
|
192
|
+
entry.terminal = false;
|
|
193
|
+
const previous = wasOwner ? stack.at(-1) : undefined;
|
|
194
|
+
if (previous && options.restore !== false) previous.source.restore();
|
|
195
|
+
cleanup(target, property);
|
|
196
|
+
return previous !== undefined && options.restore !== false;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const retire = (): boolean => {
|
|
200
|
+
if (!entry.active || entry.discarded) return false;
|
|
201
|
+
const stack = stackFor(target, property, false);
|
|
202
|
+
if (!stack) {
|
|
203
|
+
entry.active = false;
|
|
204
|
+
entry.terminal = false;
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
const index = stack.indexOf(entry);
|
|
208
|
+
if (index < 0) {
|
|
209
|
+
entry.active = false;
|
|
210
|
+
entry.terminal = false;
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (index === stack.length - 1) {
|
|
215
|
+
const covered = stack.splice(0, index);
|
|
216
|
+
for (const candidate of covered) {
|
|
217
|
+
candidate.active = false;
|
|
218
|
+
candidate.terminal = false;
|
|
219
|
+
}
|
|
220
|
+
entry.terminal = true;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
stack.splice(index, 1);
|
|
225
|
+
entry.active = false;
|
|
226
|
+
entry.terminal = false;
|
|
227
|
+
cleanup(target, property);
|
|
228
|
+
return true;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
activate();
|
|
232
|
+
return Object.freeze({
|
|
233
|
+
isActive(): boolean {
|
|
234
|
+
return entry.active;
|
|
235
|
+
},
|
|
236
|
+
isOwner(): boolean {
|
|
237
|
+
if (!entry.active) return false;
|
|
238
|
+
return stackFor(target, property, false)?.at(-1) === entry;
|
|
239
|
+
},
|
|
240
|
+
activate,
|
|
241
|
+
retire,
|
|
242
|
+
release,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
interface GlobalTimeAnimation {
|
|
2
|
+
globalTime(rawTime?: number): number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function withGlobalTime(animation: gsap.core.Tween): GlobalTimeAnimation {
|
|
6
|
+
return animation as gsap.core.Tween & GlobalTimeAnimation;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function globalTimeAt(
|
|
10
|
+
animation: gsap.core.Tween,
|
|
11
|
+
localTime: number,
|
|
12
|
+
): number {
|
|
13
|
+
return withGlobalTime(animation).globalTime(localTime);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function localTimeAt(
|
|
17
|
+
animation: gsap.core.Tween,
|
|
18
|
+
globalTime: number,
|
|
19
|
+
): number {
|
|
20
|
+
if (animation.paused()) return animation.totalTime();
|
|
21
|
+
const globalStart = globalTimeAt(animation, 0);
|
|
22
|
+
const globalNextSecond = globalTimeAt(animation, 1);
|
|
23
|
+
const globalSecondsPerLocalSecond = globalNextSecond - globalStart;
|
|
24
|
+
if (
|
|
25
|
+
!Number.isFinite(globalSecondsPerLocalSecond) ||
|
|
26
|
+
globalSecondsPerLocalSecond === 0
|
|
27
|
+
) {
|
|
28
|
+
return animation.time();
|
|
29
|
+
}
|
|
30
|
+
return Math.max(0, (globalTime - globalStart) / globalSecondsPerLocalSecond);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface LocalCycle {
|
|
34
|
+
time: number;
|
|
35
|
+
direction: 1 | 0 | -1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cycleAtTotalTime(
|
|
39
|
+
animation: gsap.core.Tween,
|
|
40
|
+
totalTime: number,
|
|
41
|
+
): LocalCycle {
|
|
42
|
+
const duration = animation.duration();
|
|
43
|
+
const playbackDirection = animation.reversed() ? -1 : 1;
|
|
44
|
+
if (!(duration > 0)) return { time: 0, direction: playbackDirection };
|
|
45
|
+
|
|
46
|
+
const repeat = animation.repeat();
|
|
47
|
+
if (repeat === 0) {
|
|
48
|
+
return {
|
|
49
|
+
time: Math.min(totalTime, duration),
|
|
50
|
+
direction: playbackDirection,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const cycleDuration = duration + animation.repeatDelay();
|
|
55
|
+
if (!(cycleDuration > 0)) return { time: 0, direction: playbackDirection };
|
|
56
|
+
|
|
57
|
+
const finiteEnd = repeat >= 0 ? animation.totalDuration() : Infinity;
|
|
58
|
+
const boundedTotalTime = Math.min(totalTime, finiteEnd);
|
|
59
|
+
if (repeat >= 0 && boundedTotalTime >= finiteEnd) {
|
|
60
|
+
const yoyoReversed = animation.yoyo() && repeat % 2 === 1;
|
|
61
|
+
return {
|
|
62
|
+
time: yoyoReversed ? 0 : duration,
|
|
63
|
+
direction: (((yoyoReversed ? -1 : 1) * playbackDirection) as 1 | -1),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cycle = Math.floor(boundedTotalTime / cycleDuration);
|
|
68
|
+
const yoyoReversed = animation.yoyo() && cycle % 2 === 1;
|
|
69
|
+
const cycleTime = boundedTotalTime - cycle * cycleDuration;
|
|
70
|
+
const forwardTime = Math.min(cycleTime, duration);
|
|
71
|
+
return {
|
|
72
|
+
time: yoyoReversed ? duration - forwardTime : forwardTime,
|
|
73
|
+
direction:
|
|
74
|
+
cycleTime > duration
|
|
75
|
+
? 0
|
|
76
|
+
: (((yoyoReversed ? -1 : 1) * playbackDirection) as 1 | -1),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function currentLocalCycle(animation: gsap.core.Tween): LocalCycle {
|
|
81
|
+
return cycleAtTotalTime(animation, animation.totalTime());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Maps a global instant to GSAP's cycle-local time, including repeat/yoyo. */
|
|
85
|
+
export function localCycleAt(
|
|
86
|
+
animation: gsap.core.Tween,
|
|
87
|
+
globalTime: number,
|
|
88
|
+
): LocalCycle {
|
|
89
|
+
const totalTime = localTimeAt(animation, globalTime);
|
|
90
|
+
return cycleAtTotalTime(animation, totalTime);
|
|
91
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export interface PluginTweenTiming {
|
|
2
|
+
finiteDuration: number;
|
|
3
|
+
infinite: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PluginTweenParticipant {
|
|
7
|
+
timing(): PluginTweenTiming;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PluginTweenRegistration {
|
|
12
|
+
recompute(): void;
|
|
13
|
+
remove(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PluginTweenState {
|
|
17
|
+
tween: gsap.core.Tween;
|
|
18
|
+
baseDuration: number;
|
|
19
|
+
baseRepeat: number;
|
|
20
|
+
participants: Set<PluginTweenParticipant>;
|
|
21
|
+
generationTargets: Set<object>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const states = new WeakMap<gsap.core.Tween, PluginTweenState>();
|
|
25
|
+
function applyTiming(state: PluginTweenState): void {
|
|
26
|
+
let finiteDuration = 0;
|
|
27
|
+
let infinite = false;
|
|
28
|
+
for (const participant of state.participants) {
|
|
29
|
+
const timing = participant.timing();
|
|
30
|
+
finiteDuration = Math.max(finiteDuration, timing.finiteDuration);
|
|
31
|
+
infinite ||= timing.infinite;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (infinite) {
|
|
35
|
+
state.tween.duration(1);
|
|
36
|
+
state.tween.repeat(-1);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
state.tween.repeat(state.baseRepeat);
|
|
41
|
+
state.tween.duration(
|
|
42
|
+
gsapSafeDuration(
|
|
43
|
+
state.participants.size === 0 ? state.baseDuration : finiteDuration,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function disposeParticipants(state: PluginTweenState): void {
|
|
49
|
+
for (const participant of state.participants) participant.dispose();
|
|
50
|
+
state.participants.clear();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Starts one GSAP plugin init generation. Seeing the same target again marks a
|
|
55
|
+
* fresh generation after `invalidate()`, including stagger children whose
|
|
56
|
+
* source-array index isn't zero.
|
|
57
|
+
*/
|
|
58
|
+
export function beginPluginTweenInit(
|
|
59
|
+
tween: gsap.core.Tween,
|
|
60
|
+
target: object,
|
|
61
|
+
options: { baseDuration: number; baseRepeat: number },
|
|
62
|
+
): PluginTweenState {
|
|
63
|
+
let state = states.get(tween);
|
|
64
|
+
if (!state) {
|
|
65
|
+
state = {
|
|
66
|
+
tween,
|
|
67
|
+
baseDuration: options.baseDuration,
|
|
68
|
+
baseRepeat: options.baseRepeat,
|
|
69
|
+
participants: new Set(),
|
|
70
|
+
generationTargets: new Set([target]),
|
|
71
|
+
};
|
|
72
|
+
states.set(tween, state);
|
|
73
|
+
|
|
74
|
+
const previousInterrupt = tween.eventCallback('onInterrupt');
|
|
75
|
+
tween.eventCallback('onInterrupt', () => {
|
|
76
|
+
disposeParticipants(state!);
|
|
77
|
+
previousInterrupt?.apply(
|
|
78
|
+
tween.vars.callbackScope ?? tween,
|
|
79
|
+
tween.vars.onInterruptParams ?? [],
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
} else if (state.generationTargets.has(target)) {
|
|
83
|
+
disposeParticipants(state);
|
|
84
|
+
applyTiming(state);
|
|
85
|
+
state.generationTargets.clear();
|
|
86
|
+
}
|
|
87
|
+
state.generationTargets.add(target);
|
|
88
|
+
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerPluginTweenParticipant(
|
|
93
|
+
state: PluginTweenState,
|
|
94
|
+
participant: PluginTweenParticipant,
|
|
95
|
+
): PluginTweenRegistration {
|
|
96
|
+
state.participants.add(participant);
|
|
97
|
+
applyTiming(state);
|
|
98
|
+
let active = true;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
recompute(): void {
|
|
102
|
+
if (active) applyTiming(state);
|
|
103
|
+
},
|
|
104
|
+
remove(): void {
|
|
105
|
+
if (!active) return;
|
|
106
|
+
active = false;
|
|
107
|
+
state.participants.delete(participant);
|
|
108
|
+
applyTiming(state);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
import { gsapSafeDuration } from './spring-track-policy.js';
|