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/dist/core.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  type ExportFilter = 'active' | 'all-animations' | 'all-elements';
2
- type OutputDetailLevel = 'compact' | 'standard' | 'detailed' | 'forensic';
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 — applies captured frame snapshots to the live DOM.
298
+ * Timeline scrubber — seeks through recorded animation state using WAAPI.
251
299
  *
252
- * Ported from the inline JS that the Electron version injected via
253
- * executeJavaScript() in timeline-controller.ts (seekTo / releaseAnimations).
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 SAFE_PROPS_SET;
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 = 'compact' | 'standard' | 'detailed' | 'forensic';
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 — applies captured frame snapshots to the live DOM.
298
+ * Timeline scrubber — seeks through recorded animation state using WAAPI.
251
299
  *
252
- * Ported from the inline JS that the Electron version injected via
253
- * executeJavaScript() in timeline-controller.ts (seekTo / releaseAnimations).
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 SAFE_PROPS_SET;
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 };