saccade 0.0.1 → 0.0.3

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/index.d.cts CHANGED
@@ -2,12 +2,12 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
 
5
- type LapsePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
- type LapseProps = {
5
+ type SaccadePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
+ type SaccadeProps = {
7
7
  /** Panel position. Default: 'bottom-left' */
8
- position?: LapsePosition;
8
+ position?: SaccadePosition;
9
9
  };
10
- declare function Lapse({ position }: LapseProps): react_jsx_runtime.JSX.Element;
10
+ declare function Saccade({ position }: SaccadeProps): react_jsx_runtime.JSX.Element;
11
11
 
12
12
  type ExportFilter = 'active' | 'all-animations' | 'all-elements';
13
13
  type OutputDetailLevel = 'compact' | 'standard' | 'detailed' | 'forensic';
@@ -98,15 +98,15 @@ type TimelineExport = {
98
98
  }[];
99
99
  };
100
100
 
101
- type LapseState = 'idle' | 'recording' | 'scrubbing';
102
- declare class LapseEngine {
101
+ type SaccadeState = 'idle' | 'recording' | 'scrubbing';
102
+ declare class SaccadeEngine {
103
103
  private timing;
104
104
  private recorder;
105
105
  private scrubber;
106
106
  private capture;
107
107
  private _state;
108
108
  private listeners;
109
- get state(): LapseState;
109
+ get state(): SaccadeState;
110
110
  getCapture(): TimelineCapture | null;
111
111
  setSpeed(speed: number): void;
112
112
  getSpeed(): number;
@@ -121,13 +121,13 @@ declare class LapseEngine {
121
121
  destroy(): void;
122
122
  }
123
123
 
124
- declare function LapseProvider({ children }: {
124
+ declare function SaccadeProvider({ children }: {
125
125
  children: ReactNode;
126
126
  }): react_jsx_runtime.JSX.Element;
127
- declare function useLapseEngine(): LapseEngine;
127
+ declare function useSaccadeEngine(): SaccadeEngine;
128
128
 
129
129
  declare function useTimeline(): {
130
- state: LapseState;
130
+ state: SaccadeState;
131
131
  capture: TimelineCapture | null;
132
132
  scrubTime: number;
133
133
  copied: boolean;
@@ -149,4 +149,4 @@ declare function useSpeed(): {
149
149
  togglePause: () => void;
150
150
  };
151
151
 
152
- export { type AnimationInfo, type ExportFilter, type FrameSnapshot, Lapse, LapseEngine, type LapsePosition, type LapseProps, LapseProvider, type LapseState, type OutputDetailLevel, type Rect, type TimelineCapture, type TimelineExport, useLapseEngine, useSpeed, useTimeline };
152
+ export { type AnimationInfo, type ExportFilter, type FrameSnapshot, type OutputDetailLevel, type Rect, Saccade, SaccadeEngine, type SaccadePosition, type SaccadeProps, SaccadeProvider, type SaccadeState, type TimelineCapture, type TimelineExport, useSaccadeEngine, useSpeed, useTimeline };
package/dist/index.d.ts CHANGED
@@ -2,12 +2,12 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
 
5
- type LapsePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
- type LapseProps = {
5
+ type SaccadePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
+ type SaccadeProps = {
7
7
  /** Panel position. Default: 'bottom-left' */
8
- position?: LapsePosition;
8
+ position?: SaccadePosition;
9
9
  };
10
- declare function Lapse({ position }: LapseProps): react_jsx_runtime.JSX.Element;
10
+ declare function Saccade({ position }: SaccadeProps): react_jsx_runtime.JSX.Element;
11
11
 
12
12
  type ExportFilter = 'active' | 'all-animations' | 'all-elements';
13
13
  type OutputDetailLevel = 'compact' | 'standard' | 'detailed' | 'forensic';
@@ -98,15 +98,15 @@ type TimelineExport = {
98
98
  }[];
99
99
  };
100
100
 
101
- type LapseState = 'idle' | 'recording' | 'scrubbing';
102
- declare class LapseEngine {
101
+ type SaccadeState = 'idle' | 'recording' | 'scrubbing';
102
+ declare class SaccadeEngine {
103
103
  private timing;
104
104
  private recorder;
105
105
  private scrubber;
106
106
  private capture;
107
107
  private _state;
108
108
  private listeners;
109
- get state(): LapseState;
109
+ get state(): SaccadeState;
110
110
  getCapture(): TimelineCapture | null;
111
111
  setSpeed(speed: number): void;
112
112
  getSpeed(): number;
@@ -121,13 +121,13 @@ declare class LapseEngine {
121
121
  destroy(): void;
122
122
  }
123
123
 
124
- declare function LapseProvider({ children }: {
124
+ declare function SaccadeProvider({ children }: {
125
125
  children: ReactNode;
126
126
  }): react_jsx_runtime.JSX.Element;
127
- declare function useLapseEngine(): LapseEngine;
127
+ declare function useSaccadeEngine(): SaccadeEngine;
128
128
 
129
129
  declare function useTimeline(): {
130
- state: LapseState;
130
+ state: SaccadeState;
131
131
  capture: TimelineCapture | null;
132
132
  scrubTime: number;
133
133
  copied: boolean;
@@ -149,4 +149,4 @@ declare function useSpeed(): {
149
149
  togglePause: () => void;
150
150
  };
151
151
 
152
- export { type AnimationInfo, type ExportFilter, type FrameSnapshot, Lapse, LapseEngine, type LapsePosition, type LapseProps, LapseProvider, type LapseState, type OutputDetailLevel, type Rect, type TimelineCapture, type TimelineExport, useLapseEngine, useSpeed, useTimeline };
152
+ export { type AnimationInfo, type ExportFilter, type FrameSnapshot, type OutputDetailLevel, type Rect, Saccade, SaccadeEngine, type SaccadePosition, type SaccadeProps, SaccadeProvider, type SaccadeState, type TimelineCapture, type TimelineExport, useSaccadeEngine, useSpeed, useTimeline };
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- // src/react/Lapse.tsx
3
+ // src/react/Saccade.tsx
4
4
  import { useRef as useRef6, useState as useState4, useEffect as useEffect4 } from "react";
5
5
  import { createPortal } from "react-dom";
6
6
 
7
- // src/react/LapseContext.tsx
7
+ // src/react/SaccadeContext.tsx
8
8
  import { createContext, useContext, useRef } from "react";
9
9
 
10
10
  // src/core/timing.ts
@@ -1013,11 +1013,12 @@ var _TimelineRecorder = class _TimelineRecorder {
1013
1013
  document.removeEventListener("pointerup", this.onPointerUp, true);
1014
1014
  try {
1015
1015
  for (const anim of document.getAnimations()) {
1016
+ const target = anim.effect?.target;
1017
+ if (target?.closest?.("[data-lapse-panel]")) continue;
1016
1018
  anim.cancel();
1017
1019
  }
1018
1020
  } catch (_) {
1019
1021
  }
1020
- window.requestAnimationFrame = () => 0;
1021
1022
  const noTransitions = document.createElement("style");
1022
1023
  noTransitions.id = "__lapse-no-transitions";
1023
1024
  noTransitions.textContent = "*, *::before, *::after { transition: none !important; animation: none !important; }";
@@ -1029,64 +1030,61 @@ var _TimelineRecorder = class _TimelineRecorder {
1029
1030
  blocker.title = "Clear the timeline to interact with the page";
1030
1031
  document.body.appendChild(blocker);
1031
1032
  this.blockerEl = blocker;
1032
- try {
1033
- const lapseStyle = document.createElement("style");
1034
- lapseStyle.id = "__lapse-state-rules";
1035
- let allCss = "";
1036
- for (const style of document.querySelectorAll("style")) {
1037
- if (style.id === "__lapse-state-rules") continue;
1038
- allCss += style.textContent + "\n";
1039
- }
1040
- for (const sheet of document.styleSheets) {
1041
- try {
1042
- let walk2 = function(rules) {
1043
- for (const rule of rules) {
1044
- if (rule.cssRules) {
1045
- walk2(rule.cssRules);
1046
- continue;
1047
- }
1048
- const t = rule.cssText;
1049
- if (t && (t.includes(":hover") || t.includes(":focus"))) {
1050
- allCss += t + "\n";
1033
+ setTimeout(() => {
1034
+ try {
1035
+ const lapseStyle = document.createElement("style");
1036
+ lapseStyle.id = "__lapse-state-rules";
1037
+ const hoverFocusRules = [];
1038
+ for (const sheet of document.styleSheets) {
1039
+ try {
1040
+ const walk = (rules) => {
1041
+ for (const rule of rules) {
1042
+ if (rule.cssRules) {
1043
+ walk(rule.cssRules);
1044
+ continue;
1045
+ }
1046
+ const t = rule.cssText;
1047
+ if (t && (t.includes(":hover") || t.includes(":focus"))) {
1048
+ hoverFocusRules.push(t);
1049
+ }
1051
1050
  }
1052
- }
1053
- };
1054
- var walk = walk2;
1055
- walk2(sheet.cssRules);
1056
- } catch (_) {
1057
- }
1058
- }
1059
- const stateRegex = /([^{}]*(?::hover|:focus-visible|:focus-within|:focus(?!-))[^{}]*)\{([^{}]*)\}/g;
1060
- let match;
1061
- while ((match = stateRegex.exec(allCss)) !== null) {
1062
- const selector = match[1].trim();
1063
- const body = match[2].trim();
1064
- if (!body) continue;
1065
- const newBody = body.replace(
1066
- /([^;:]+):\s*([^;]+)(;|$)/g,
1067
- (m, prop, val, end) => {
1068
- if (val.includes("!important")) return m;
1069
- return prop + ": " + val.trim() + " !important" + end;
1051
+ };
1052
+ walk(sheet.cssRules);
1053
+ } catch (_) {
1070
1054
  }
1071
- );
1072
- if (selector.includes(":hover")) {
1073
- lapseStyle.textContent += selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }\n";
1074
1055
  }
1075
- if (selector.includes(":focus-visible")) {
1076
- lapseStyle.textContent += selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
1077
- } else if (selector.includes(":focus-within")) {
1078
- lapseStyle.textContent += selector.replace(
1079
- /:focus-within/g,
1080
- ":has([data-lapse-focus])"
1081
- ) + " { " + newBody + " }\n";
1082
- } else if (selector.includes(":focus")) {
1083
- lapseStyle.textContent += selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
1056
+ const parts = [];
1057
+ for (const ruleText of hoverFocusRules) {
1058
+ const braceIdx = ruleText.indexOf("{");
1059
+ if (braceIdx === -1) continue;
1060
+ const selector = ruleText.slice(0, braceIdx).trim();
1061
+ const bodyEnd = ruleText.lastIndexOf("}");
1062
+ const body = ruleText.slice(braceIdx + 1, bodyEnd).trim();
1063
+ if (!body) continue;
1064
+ const newBody = body.replace(
1065
+ /([^;:]+):\s*([^;]+)(;|$)/g,
1066
+ (m, prop, val, end) => {
1067
+ if (val.includes("!important")) return m;
1068
+ return prop + ": " + val.trim() + " !important" + end;
1069
+ }
1070
+ );
1071
+ if (selector.includes(":hover")) {
1072
+ parts.push(selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }");
1073
+ }
1074
+ if (selector.includes(":focus-visible")) {
1075
+ parts.push(selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }");
1076
+ } else if (selector.includes(":focus-within")) {
1077
+ parts.push(selector.replace(/:focus-within/g, ":has([data-lapse-focus])") + " { " + newBody + " }");
1078
+ } else if (selector.includes(":focus")) {
1079
+ parts.push(selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }");
1080
+ }
1084
1081
  }
1082
+ lapseStyle.textContent = parts.join("\n");
1083
+ document.head.appendChild(lapseStyle);
1084
+ this.lapseStyleEl = lapseStyle;
1085
+ } catch (_) {
1085
1086
  }
1086
- document.head.appendChild(lapseStyle);
1087
- this.lapseStyleEl = lapseStyle;
1088
- } catch (_) {
1089
- }
1087
+ }, 0);
1090
1088
  if (this.frames.length > 0) {
1091
1089
  const frame0 = this.frames[0];
1092
1090
  if (frame0.elementSnapshots) {
@@ -1599,7 +1597,7 @@ function formatExportForLLM(exp, detail = "standard") {
1599
1597
  }
1600
1598
 
1601
1599
  // src/core/engine.ts
1602
- var LapseEngine = class {
1600
+ var SaccadeEngine = class {
1603
1601
  constructor() {
1604
1602
  this.timing = new TimingController();
1605
1603
  this.recorder = new TimelineRecorder();
@@ -1641,7 +1639,25 @@ var LapseEngine = class {
1641
1639
  boundingBox: null
1642
1640
  };
1643
1641
  }
1644
- const capture = this.recorder.stopRecording();
1642
+ let capture;
1643
+ try {
1644
+ capture = this.recorder.stopRecording();
1645
+ } catch (e) {
1646
+ console.error("[Saccade] stopRecording failed:", e);
1647
+ document.getElementById("__lapse-scrub-blocker")?.remove();
1648
+ document.getElementById("__lapse-no-transitions")?.remove();
1649
+ document.getElementById("__lapse-state-rules")?.remove();
1650
+ this._state = "idle";
1651
+ this.notify();
1652
+ return {
1653
+ startTime: 0,
1654
+ endTime: 0,
1655
+ duration: 0,
1656
+ animations: [],
1657
+ frames: [],
1658
+ boundingBox: null
1659
+ };
1660
+ }
1645
1661
  this.capture = capture;
1646
1662
  const scrubberState = {
1647
1663
  elements: this.recorder.elements,
@@ -1701,23 +1717,23 @@ var LapseEngine = class {
1701
1717
  }
1702
1718
  };
1703
1719
 
1704
- // src/react/LapseContext.tsx
1720
+ // src/react/SaccadeContext.tsx
1705
1721
  import { jsx } from "react/jsx-runtime";
1706
- var LapseContext = createContext(null);
1707
- function LapseProvider({ children }) {
1722
+ var SaccadeContext = createContext(null);
1723
+ function SaccadeProvider({ children }) {
1708
1724
  const engineRef = useRef(null);
1709
1725
  if (!engineRef.current) {
1710
- engineRef.current = new LapseEngine();
1726
+ engineRef.current = new SaccadeEngine();
1711
1727
  }
1712
- return /* @__PURE__ */ jsx(LapseContext.Provider, { value: engineRef.current, children });
1728
+ return /* @__PURE__ */ jsx(SaccadeContext.Provider, { value: engineRef.current, children });
1713
1729
  }
1714
- function useLapseEngine() {
1715
- const engine = useContext(LapseContext);
1716
- if (!engine) throw new Error("useLapseEngine must be used within <LapseProvider>");
1730
+ function useSaccadeEngine() {
1731
+ const engine = useContext(SaccadeContext);
1732
+ if (!engine) throw new Error("useSaccadeEngine must be used within <SaccadeProvider>");
1717
1733
  return engine;
1718
1734
  }
1719
1735
 
1720
- // src/react/LapsePanel.tsx
1736
+ // src/react/SaccadePanel.tsx
1721
1737
  import { useRef as useRef5, useCallback as useCallback5 } from "react";
1722
1738
 
1723
1739
  // src/react/Timeline.tsx
@@ -2111,7 +2127,7 @@ function SpeedControl({ speed, isPaused, onSetSpeed, onTogglePause }) {
2111
2127
  import { useState as useState2, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef4, useSyncExternalStore } from "react";
2112
2128
  var DETAIL_LEVELS = ["compact", "standard", "detailed", "forensic"];
2113
2129
  function useTimeline() {
2114
- const engine = useLapseEngine();
2130
+ const engine = useSaccadeEngine();
2115
2131
  const state = useSyncExternalStore(
2116
2132
  (cb) => engine.subscribe(cb),
2117
2133
  () => engine.state
@@ -2145,9 +2161,13 @@ function useTimeline() {
2145
2161
  [engine]
2146
2162
  );
2147
2163
  const stopRecording = useCallback3(() => {
2148
- const result = engine.stopRecording();
2149
- setCapture(result);
2150
- setScrubTime(0);
2164
+ try {
2165
+ const result = engine.stopRecording();
2166
+ setCapture(result);
2167
+ setScrubTime(0);
2168
+ } catch (e) {
2169
+ console.error("[Saccade] stopRecording failed:", e);
2170
+ }
2151
2171
  }, [engine]);
2152
2172
  const seek = useCallback3(
2153
2173
  (timeMs) => {
@@ -2216,7 +2236,7 @@ function useTimeline() {
2216
2236
  // src/react/useSpeed.ts
2217
2237
  import { useState as useState3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
2218
2238
  function useSpeed() {
2219
- const engine = useLapseEngine();
2239
+ const engine = useSaccadeEngine();
2220
2240
  const [speed, setSpeedState] = useState3(1);
2221
2241
  const [previousSpeed, setPreviousSpeed] = useState3(1);
2222
2242
  const [isPaused, setIsPaused] = useState3(false);
@@ -2279,9 +2299,9 @@ function useSpeed() {
2279
2299
  return { speed, isPaused, setSpeed, togglePause };
2280
2300
  }
2281
2301
 
2282
- // src/react/LapsePanel.tsx
2302
+ // src/react/SaccadePanel.tsx
2283
2303
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2284
- function LapsePanel() {
2304
+ function SaccadePanel() {
2285
2305
  const timeline = useTimeline();
2286
2306
  const { speed, isPaused, setSpeed, togglePause } = useSpeed();
2287
2307
  const panelRef = useRef5(null);
@@ -2741,9 +2761,9 @@ var PANEL_STYLES = (
2741
2761
  `
2742
2762
  );
2743
2763
 
2744
- // src/react/Lapse.tsx
2764
+ // src/react/Saccade.tsx
2745
2765
  import { jsx as jsx5 } from "react/jsx-runtime";
2746
- function Lapse({ position = "bottom-left" }) {
2766
+ function Saccade({ position = "bottom-left" }) {
2747
2767
  const hostRef = useRef6(null);
2748
2768
  const [shadowRoot, setShadowRoot] = useState4(null);
2749
2769
  useEffect4(() => {
@@ -2774,17 +2794,17 @@ function Lapse({ position = "bottom-left" }) {
2774
2794
  ...positionOffset
2775
2795
  },
2776
2796
  children: shadowRoot && createPortal(
2777
- /* @__PURE__ */ jsx5(LapseProvider, { children: /* @__PURE__ */ jsx5(LapsePanel, {}) }),
2797
+ /* @__PURE__ */ jsx5(SaccadeProvider, { children: /* @__PURE__ */ jsx5(SaccadePanel, {}) }),
2778
2798
  shadowRoot.lastElementChild || shadowRoot
2779
2799
  )
2780
2800
  }
2781
2801
  );
2782
2802
  }
2783
2803
  export {
2784
- Lapse,
2785
- LapseEngine,
2786
- LapseProvider,
2787
- useLapseEngine,
2804
+ Saccade,
2805
+ SaccadeEngine,
2806
+ SaccadeProvider,
2807
+ useSaccadeEngine,
2788
2808
  useSpeed,
2789
2809
  useTimeline
2790
2810
  };