saccade 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -2
- package/dist/core.cjs +267 -157
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +63 -11
- package/dist/core.d.ts +63 -11
- package/dist/core.mjs +264 -156
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +299 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -11
- package/dist/index.d.ts +37 -11
- package/dist/index.mjs +297 -193
- package/dist/index.mjs.map +1 -1
- package/dist/install.cjs +1846 -0
- package/dist/install.cjs.map +1 -0
- package/dist/install.d.cts +129 -0
- package/dist/install.d.ts +129 -0
- package/dist/install.mjs +1819 -0
- package/dist/install.mjs.map +1 -0
- package/package.json +12 -1
package/dist/core.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type ExportFilter = 'active' | 'all-animations' | 'all-elements';
|
|
2
|
-
type OutputDetailLevel = '
|
|
2
|
+
type OutputDetailLevel = 'brief' | 'moderate' | 'detailed' | 'granular';
|
|
3
3
|
type Rect = {
|
|
4
4
|
x: number;
|
|
5
5
|
y: number;
|
|
@@ -19,6 +19,8 @@ type AnimationInfo = {
|
|
|
19
19
|
resolvedVars: Record<string, string> | null;
|
|
20
20
|
conflicts: string[] | null;
|
|
21
21
|
layoutAnimation?: boolean;
|
|
22
|
+
rawKeyframes?: Keyframe[];
|
|
23
|
+
rawTiming?: EffectTiming;
|
|
22
24
|
};
|
|
23
25
|
type PropertySnapshot = {
|
|
24
26
|
property: string;
|
|
@@ -99,6 +101,18 @@ declare class SaccadeEngine {
|
|
|
99
101
|
getCapture(): TimelineCapture | null;
|
|
100
102
|
setSpeed(speed: number): void;
|
|
101
103
|
getSpeed(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Install the timing patches immediately, without changing speed.
|
|
106
|
+
*
|
|
107
|
+
* Call this as early as possible (before app code, GSAP, or Framer Motion
|
|
108
|
+
* run) so they capture the patched time functions rather than the originals.
|
|
109
|
+
* Idempotent and harmless to call more than once. `setSpeed` and
|
|
110
|
+
* `startRecording` also install on demand, so this is only needed to win the
|
|
111
|
+
* early-load race against libraries that cache `Date.now`/`performance.now`.
|
|
112
|
+
*/
|
|
113
|
+
install(): void;
|
|
114
|
+
/** Register a module-imported GSAP instance so saccade can slow it. */
|
|
115
|
+
registerGSAP(gsap: any): void;
|
|
102
116
|
startRecording(boundingBox?: Rect | null): void;
|
|
103
117
|
stopRecording(): TimelineCapture;
|
|
104
118
|
seekTo(timeMs: number): void;
|
|
@@ -110,6 +124,10 @@ declare class SaccadeEngine {
|
|
|
110
124
|
destroy(): void;
|
|
111
125
|
}
|
|
112
126
|
|
|
127
|
+
declare function getSharedEngine(): SaccadeEngine;
|
|
128
|
+
/** Test/teardown hook — drops the singleton so the next get() makes a fresh one. */
|
|
129
|
+
declare function resetSharedEngine(): void;
|
|
130
|
+
|
|
113
131
|
/**
|
|
114
132
|
* Timing controller — patches all JS timing APIs to respect a speed factor.
|
|
115
133
|
*
|
|
@@ -120,12 +138,16 @@ declare class TimingController {
|
|
|
120
138
|
private speed;
|
|
121
139
|
private realBaseline;
|
|
122
140
|
private virtualBaseline;
|
|
141
|
+
private dateRealBaseline;
|
|
142
|
+
private dateVirtualBaseline;
|
|
123
143
|
private intervalMap;
|
|
124
144
|
private nextIntervalId;
|
|
125
|
-
private mediaObserver;
|
|
126
|
-
private animObserver;
|
|
127
145
|
private _origAnimate;
|
|
128
146
|
private installed;
|
|
147
|
+
private animPollId;
|
|
148
|
+
private gsapInstance;
|
|
149
|
+
private trackedAnims;
|
|
150
|
+
private trackedMedia;
|
|
129
151
|
private _raf;
|
|
130
152
|
private _caf;
|
|
131
153
|
private _setTimeout;
|
|
@@ -136,14 +158,27 @@ declare class TimingController {
|
|
|
136
158
|
private _dateNow;
|
|
137
159
|
constructor();
|
|
138
160
|
private getVirtualTime;
|
|
161
|
+
private getVirtualDateNow;
|
|
139
162
|
private reanchor;
|
|
163
|
+
/** Effective speed divisor — avoids division by zero at speed=0. */
|
|
164
|
+
private get speedDivisor();
|
|
140
165
|
/** Install timing patches. Safe to call multiple times. */
|
|
141
166
|
install(): void;
|
|
142
167
|
/** Set playback speed. Requires install() first. */
|
|
143
168
|
setSpeed(newSpeed: number): void;
|
|
144
|
-
/** Patch playbackRate on all active CSS transitions/animations via WAAPI. */
|
|
145
|
-
private patchAnimations;
|
|
146
169
|
getSpeed(): number;
|
|
170
|
+
private startAnimationPoll;
|
|
171
|
+
/** Patch playbackRate on all active animations via WAAPI. */
|
|
172
|
+
private patchAnimations;
|
|
173
|
+
/** Patch playbackRate on all video/audio elements. */
|
|
174
|
+
private patchMedia;
|
|
175
|
+
/**
|
|
176
|
+
* Register a GSAP instance (for ES-module imports where window.gsap is
|
|
177
|
+
* undefined). Applies the current timeScale immediately if speed !== 1.
|
|
178
|
+
*/
|
|
179
|
+
registerGSAP(gsap: any): void;
|
|
180
|
+
/** Sync GSAP's global timeline if present. */
|
|
181
|
+
private patchGSAP;
|
|
147
182
|
/** Restore all patched APIs to originals. */
|
|
148
183
|
destroy(): void;
|
|
149
184
|
}
|
|
@@ -197,6 +232,11 @@ declare class TimelineRecorder {
|
|
|
197
232
|
animation: Animation;
|
|
198
233
|
target: Element;
|
|
199
234
|
}[];
|
|
235
|
+
readonly seekableClones: Map<string, {
|
|
236
|
+
animation: Animation;
|
|
237
|
+
element: HTMLElement;
|
|
238
|
+
effect: KeyframeEffect;
|
|
239
|
+
}>;
|
|
200
240
|
private static readonly MAX_DURATION_MS;
|
|
201
241
|
private static readonly MAX_FRAMES;
|
|
202
242
|
private hiddenSince;
|
|
@@ -230,6 +270,12 @@ interface InterceptedAnimation {
|
|
|
230
270
|
animation: Animation;
|
|
231
271
|
target: Element;
|
|
232
272
|
}
|
|
273
|
+
/** A seekable WAAPI clone created from recorded keyframes. */
|
|
274
|
+
interface SeekableClone {
|
|
275
|
+
animation: Animation;
|
|
276
|
+
element: HTMLElement;
|
|
277
|
+
effect: KeyframeEffect;
|
|
278
|
+
}
|
|
233
279
|
/**
|
|
234
280
|
* State handed from the recorder after stopRecording().
|
|
235
281
|
* This is everything the scrubber needs to replay frames.
|
|
@@ -245,26 +291,32 @@ interface ScrubberState {
|
|
|
245
291
|
interceptedAnimations: InterceptedAnimation[];
|
|
246
292
|
/** Set of CSS properties that are safe (visual-only, no layout side-effects) */
|
|
247
293
|
SAFE_PROPS_SET: Set<string>;
|
|
294
|
+
/** Seekable WAAPI clones created from recorded animation keyframes */
|
|
295
|
+
seekableClones: Map<string, SeekableClone>;
|
|
248
296
|
}
|
|
249
297
|
/**
|
|
250
|
-
* Timeline scrubber —
|
|
298
|
+
* Timeline scrubber — seeks through recorded animation state using WAAPI.
|
|
251
299
|
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
300
|
+
* Instead of applying computed styles as inline overrides (which breaks
|
|
301
|
+
* Tailwind/class-driven layouts), this scrubber creates paused WAAPI
|
|
302
|
+
* animation clones and seeks them via animation.currentTime. The browser
|
|
303
|
+
* computes exact intermediate values natively — the same mechanism Chrome
|
|
304
|
+
* DevTools uses for its Animation panel.
|
|
254
305
|
*/
|
|
255
306
|
declare class TimelineScrubber {
|
|
256
307
|
private elements;
|
|
257
308
|
private frames;
|
|
258
309
|
private capturedPortals;
|
|
259
310
|
private interceptedAnimations;
|
|
260
|
-
private
|
|
311
|
+
private seekableClones;
|
|
312
|
+
/** Precomputed frame range per animation for O(1) before/after lookup. */
|
|
313
|
+
private animFrameRanges;
|
|
261
314
|
/** Saved originals for restore on release */
|
|
262
315
|
private _originalAnimate;
|
|
263
316
|
private _originalRaf;
|
|
264
317
|
private _originalRemoveChild;
|
|
265
318
|
private _originalRemove;
|
|
266
319
|
constructor(state: ScrubberState);
|
|
267
|
-
private getSelector;
|
|
268
320
|
seekTo(timeMs: number): void;
|
|
269
321
|
release(): void;
|
|
270
322
|
}
|
|
@@ -273,4 +325,4 @@ declare function getFrameAtTime(frames: FrameSnapshot[], timeMs: number): FrameS
|
|
|
273
325
|
declare function generateExport(animations: AnimationInfo[], frames: FrameSnapshot[], timeMs: number, filter?: ExportFilter): TimelineExport;
|
|
274
326
|
declare function formatExportForLLM(exp: TimelineExport, detail?: OutputDetailLevel): string;
|
|
275
327
|
|
|
276
|
-
export { type AnimationInfo, type ElementSnapshot, type ExportFilter, type FrameAnimation, type FrameSnapshot, type InterceptedAnimation, type OutputDetailLevel, type PropertySnapshot, type Rect, SaccadeEngine, type SaccadeState, type ScrubberState, type TimelineCapture, type TimelineExport, TimelineRecorder, TimelineScrubber, TimingController, formatExportForLLM, generateExport, getFrameAtTime };
|
|
328
|
+
export { type AnimationInfo, type ElementSnapshot, type ExportFilter, type FrameAnimation, type FrameSnapshot, type InterceptedAnimation, type OutputDetailLevel, type PropertySnapshot, type Rect, SaccadeEngine, type SaccadeState, type ScrubberState, type SeekableClone, type TimelineCapture, type TimelineExport, TimelineRecorder, TimelineScrubber, TimingController, formatExportForLLM, generateExport, getFrameAtTime, getSharedEngine, resetSharedEngine };
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type ExportFilter = 'active' | 'all-animations' | 'all-elements';
|
|
2
|
-
type OutputDetailLevel = '
|
|
2
|
+
type OutputDetailLevel = 'brief' | 'moderate' | 'detailed' | 'granular';
|
|
3
3
|
type Rect = {
|
|
4
4
|
x: number;
|
|
5
5
|
y: number;
|
|
@@ -19,6 +19,8 @@ type AnimationInfo = {
|
|
|
19
19
|
resolvedVars: Record<string, string> | null;
|
|
20
20
|
conflicts: string[] | null;
|
|
21
21
|
layoutAnimation?: boolean;
|
|
22
|
+
rawKeyframes?: Keyframe[];
|
|
23
|
+
rawTiming?: EffectTiming;
|
|
22
24
|
};
|
|
23
25
|
type PropertySnapshot = {
|
|
24
26
|
property: string;
|
|
@@ -99,6 +101,18 @@ declare class SaccadeEngine {
|
|
|
99
101
|
getCapture(): TimelineCapture | null;
|
|
100
102
|
setSpeed(speed: number): void;
|
|
101
103
|
getSpeed(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Install the timing patches immediately, without changing speed.
|
|
106
|
+
*
|
|
107
|
+
* Call this as early as possible (before app code, GSAP, or Framer Motion
|
|
108
|
+
* run) so they capture the patched time functions rather than the originals.
|
|
109
|
+
* Idempotent and harmless to call more than once. `setSpeed` and
|
|
110
|
+
* `startRecording` also install on demand, so this is only needed to win the
|
|
111
|
+
* early-load race against libraries that cache `Date.now`/`performance.now`.
|
|
112
|
+
*/
|
|
113
|
+
install(): void;
|
|
114
|
+
/** Register a module-imported GSAP instance so saccade can slow it. */
|
|
115
|
+
registerGSAP(gsap: any): void;
|
|
102
116
|
startRecording(boundingBox?: Rect | null): void;
|
|
103
117
|
stopRecording(): TimelineCapture;
|
|
104
118
|
seekTo(timeMs: number): void;
|
|
@@ -110,6 +124,10 @@ declare class SaccadeEngine {
|
|
|
110
124
|
destroy(): void;
|
|
111
125
|
}
|
|
112
126
|
|
|
127
|
+
declare function getSharedEngine(): SaccadeEngine;
|
|
128
|
+
/** Test/teardown hook — drops the singleton so the next get() makes a fresh one. */
|
|
129
|
+
declare function resetSharedEngine(): void;
|
|
130
|
+
|
|
113
131
|
/**
|
|
114
132
|
* Timing controller — patches all JS timing APIs to respect a speed factor.
|
|
115
133
|
*
|
|
@@ -120,12 +138,16 @@ declare class TimingController {
|
|
|
120
138
|
private speed;
|
|
121
139
|
private realBaseline;
|
|
122
140
|
private virtualBaseline;
|
|
141
|
+
private dateRealBaseline;
|
|
142
|
+
private dateVirtualBaseline;
|
|
123
143
|
private intervalMap;
|
|
124
144
|
private nextIntervalId;
|
|
125
|
-
private mediaObserver;
|
|
126
|
-
private animObserver;
|
|
127
145
|
private _origAnimate;
|
|
128
146
|
private installed;
|
|
147
|
+
private animPollId;
|
|
148
|
+
private gsapInstance;
|
|
149
|
+
private trackedAnims;
|
|
150
|
+
private trackedMedia;
|
|
129
151
|
private _raf;
|
|
130
152
|
private _caf;
|
|
131
153
|
private _setTimeout;
|
|
@@ -136,14 +158,27 @@ declare class TimingController {
|
|
|
136
158
|
private _dateNow;
|
|
137
159
|
constructor();
|
|
138
160
|
private getVirtualTime;
|
|
161
|
+
private getVirtualDateNow;
|
|
139
162
|
private reanchor;
|
|
163
|
+
/** Effective speed divisor — avoids division by zero at speed=0. */
|
|
164
|
+
private get speedDivisor();
|
|
140
165
|
/** Install timing patches. Safe to call multiple times. */
|
|
141
166
|
install(): void;
|
|
142
167
|
/** Set playback speed. Requires install() first. */
|
|
143
168
|
setSpeed(newSpeed: number): void;
|
|
144
|
-
/** Patch playbackRate on all active CSS transitions/animations via WAAPI. */
|
|
145
|
-
private patchAnimations;
|
|
146
169
|
getSpeed(): number;
|
|
170
|
+
private startAnimationPoll;
|
|
171
|
+
/** Patch playbackRate on all active animations via WAAPI. */
|
|
172
|
+
private patchAnimations;
|
|
173
|
+
/** Patch playbackRate on all video/audio elements. */
|
|
174
|
+
private patchMedia;
|
|
175
|
+
/**
|
|
176
|
+
* Register a GSAP instance (for ES-module imports where window.gsap is
|
|
177
|
+
* undefined). Applies the current timeScale immediately if speed !== 1.
|
|
178
|
+
*/
|
|
179
|
+
registerGSAP(gsap: any): void;
|
|
180
|
+
/** Sync GSAP's global timeline if present. */
|
|
181
|
+
private patchGSAP;
|
|
147
182
|
/** Restore all patched APIs to originals. */
|
|
148
183
|
destroy(): void;
|
|
149
184
|
}
|
|
@@ -197,6 +232,11 @@ declare class TimelineRecorder {
|
|
|
197
232
|
animation: Animation;
|
|
198
233
|
target: Element;
|
|
199
234
|
}[];
|
|
235
|
+
readonly seekableClones: Map<string, {
|
|
236
|
+
animation: Animation;
|
|
237
|
+
element: HTMLElement;
|
|
238
|
+
effect: KeyframeEffect;
|
|
239
|
+
}>;
|
|
200
240
|
private static readonly MAX_DURATION_MS;
|
|
201
241
|
private static readonly MAX_FRAMES;
|
|
202
242
|
private hiddenSince;
|
|
@@ -230,6 +270,12 @@ interface InterceptedAnimation {
|
|
|
230
270
|
animation: Animation;
|
|
231
271
|
target: Element;
|
|
232
272
|
}
|
|
273
|
+
/** A seekable WAAPI clone created from recorded keyframes. */
|
|
274
|
+
interface SeekableClone {
|
|
275
|
+
animation: Animation;
|
|
276
|
+
element: HTMLElement;
|
|
277
|
+
effect: KeyframeEffect;
|
|
278
|
+
}
|
|
233
279
|
/**
|
|
234
280
|
* State handed from the recorder after stopRecording().
|
|
235
281
|
* This is everything the scrubber needs to replay frames.
|
|
@@ -245,26 +291,32 @@ interface ScrubberState {
|
|
|
245
291
|
interceptedAnimations: InterceptedAnimation[];
|
|
246
292
|
/** Set of CSS properties that are safe (visual-only, no layout side-effects) */
|
|
247
293
|
SAFE_PROPS_SET: Set<string>;
|
|
294
|
+
/** Seekable WAAPI clones created from recorded animation keyframes */
|
|
295
|
+
seekableClones: Map<string, SeekableClone>;
|
|
248
296
|
}
|
|
249
297
|
/**
|
|
250
|
-
* Timeline scrubber —
|
|
298
|
+
* Timeline scrubber — seeks through recorded animation state using WAAPI.
|
|
251
299
|
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
300
|
+
* Instead of applying computed styles as inline overrides (which breaks
|
|
301
|
+
* Tailwind/class-driven layouts), this scrubber creates paused WAAPI
|
|
302
|
+
* animation clones and seeks them via animation.currentTime. The browser
|
|
303
|
+
* computes exact intermediate values natively — the same mechanism Chrome
|
|
304
|
+
* DevTools uses for its Animation panel.
|
|
254
305
|
*/
|
|
255
306
|
declare class TimelineScrubber {
|
|
256
307
|
private elements;
|
|
257
308
|
private frames;
|
|
258
309
|
private capturedPortals;
|
|
259
310
|
private interceptedAnimations;
|
|
260
|
-
private
|
|
311
|
+
private seekableClones;
|
|
312
|
+
/** Precomputed frame range per animation for O(1) before/after lookup. */
|
|
313
|
+
private animFrameRanges;
|
|
261
314
|
/** Saved originals for restore on release */
|
|
262
315
|
private _originalAnimate;
|
|
263
316
|
private _originalRaf;
|
|
264
317
|
private _originalRemoveChild;
|
|
265
318
|
private _originalRemove;
|
|
266
319
|
constructor(state: ScrubberState);
|
|
267
|
-
private getSelector;
|
|
268
320
|
seekTo(timeMs: number): void;
|
|
269
321
|
release(): void;
|
|
270
322
|
}
|
|
@@ -273,4 +325,4 @@ declare function getFrameAtTime(frames: FrameSnapshot[], timeMs: number): FrameS
|
|
|
273
325
|
declare function generateExport(animations: AnimationInfo[], frames: FrameSnapshot[], timeMs: number, filter?: ExportFilter): TimelineExport;
|
|
274
326
|
declare function formatExportForLLM(exp: TimelineExport, detail?: OutputDetailLevel): string;
|
|
275
327
|
|
|
276
|
-
export { type AnimationInfo, type ElementSnapshot, type ExportFilter, type FrameAnimation, type FrameSnapshot, type InterceptedAnimation, type OutputDetailLevel, type PropertySnapshot, type Rect, SaccadeEngine, type SaccadeState, type ScrubberState, type TimelineCapture, type TimelineExport, TimelineRecorder, TimelineScrubber, TimingController, formatExportForLLM, generateExport, getFrameAtTime };
|
|
328
|
+
export { type AnimationInfo, type ElementSnapshot, type ExportFilter, type FrameAnimation, type FrameSnapshot, type InterceptedAnimation, type OutputDetailLevel, type PropertySnapshot, type Rect, SaccadeEngine, type SaccadeState, type ScrubberState, type SeekableClone, type TimelineCapture, type TimelineExport, TimelineRecorder, TimelineScrubber, TimingController, formatExportForLLM, generateExport, getFrameAtTime, getSharedEngine, resetSharedEngine };
|