saccade 0.0.2 → 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/README.md +6 -6
- package/dist/core.cjs +72 -57
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +4 -4
- package/dist/core.d.ts +4 -4
- package/dist/core.mjs +71 -56
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +103 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.mjs +99 -80
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
6
|
-
type
|
|
5
|
+
type SaccadePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
6
|
+
type SaccadeProps = {
|
|
7
7
|
/** Panel position. Default: 'bottom-left' */
|
|
8
|
-
position?:
|
|
8
|
+
position?: SaccadePosition;
|
|
9
9
|
};
|
|
10
|
-
declare function
|
|
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
|
|
102
|
-
declare class
|
|
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():
|
|
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
|
|
124
|
+
declare function SaccadeProvider({ children }: {
|
|
125
125
|
children: ReactNode;
|
|
126
126
|
}): react_jsx_runtime.JSX.Element;
|
|
127
|
-
declare function
|
|
127
|
+
declare function useSaccadeEngine(): SaccadeEngine;
|
|
128
128
|
|
|
129
129
|
declare function useTimeline(): {
|
|
130
|
-
state:
|
|
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,
|
|
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
|
|
6
|
-
type
|
|
5
|
+
type SaccadePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
6
|
+
type SaccadeProps = {
|
|
7
7
|
/** Panel position. Default: 'bottom-left' */
|
|
8
|
-
position?:
|
|
8
|
+
position?: SaccadePosition;
|
|
9
9
|
};
|
|
10
|
-
declare function
|
|
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
|
|
102
|
-
declare class
|
|
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():
|
|
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
|
|
124
|
+
declare function SaccadeProvider({ children }: {
|
|
125
125
|
children: ReactNode;
|
|
126
126
|
}): react_jsx_runtime.JSX.Element;
|
|
127
|
-
declare function
|
|
127
|
+
declare function useSaccadeEngine(): SaccadeEngine;
|
|
128
128
|
|
|
129
129
|
declare function useTimeline(): {
|
|
130
|
-
state:
|
|
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,
|
|
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/
|
|
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/
|
|
7
|
+
// src/react/SaccadeContext.tsx
|
|
8
8
|
import { createContext, useContext, useRef } from "react";
|
|
9
9
|
|
|
10
10
|
// src/core/timing.ts
|
|
@@ -1030,64 +1030,61 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1030
1030
|
blocker.title = "Clear the timeline to interact with the page";
|
|
1031
1031
|
document.body.appendChild(blocker);
|
|
1032
1032
|
this.blockerEl = blocker;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1051
|
-
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
|
+
}
|
|
1052
1050
|
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
walk2(sheet.cssRules);
|
|
1057
|
-
} catch (_) {
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
const stateRegex = /([^{}]*(?::hover|:focus-visible|:focus-within|:focus(?!-))[^{}]*)\{([^{}]*)\}/g;
|
|
1061
|
-
let match;
|
|
1062
|
-
while ((match = stateRegex.exec(allCss)) !== null) {
|
|
1063
|
-
const selector = match[1].trim();
|
|
1064
|
-
const body = match[2].trim();
|
|
1065
|
-
if (!body) continue;
|
|
1066
|
-
const newBody = body.replace(
|
|
1067
|
-
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1068
|
-
(m, prop, val, end) => {
|
|
1069
|
-
if (val.includes("!important")) return m;
|
|
1070
|
-
return prop + ": " + val.trim() + " !important" + end;
|
|
1051
|
+
};
|
|
1052
|
+
walk(sheet.cssRules);
|
|
1053
|
+
} catch (_) {
|
|
1071
1054
|
}
|
|
1072
|
-
);
|
|
1073
|
-
if (selector.includes(":hover")) {
|
|
1074
|
-
lapseStyle.textContent += selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }\n";
|
|
1075
1055
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
+
}
|
|
1085
1081
|
}
|
|
1082
|
+
lapseStyle.textContent = parts.join("\n");
|
|
1083
|
+
document.head.appendChild(lapseStyle);
|
|
1084
|
+
this.lapseStyleEl = lapseStyle;
|
|
1085
|
+
} catch (_) {
|
|
1086
1086
|
}
|
|
1087
|
-
|
|
1088
|
-
this.lapseStyleEl = lapseStyle;
|
|
1089
|
-
} catch (_) {
|
|
1090
|
-
}
|
|
1087
|
+
}, 0);
|
|
1091
1088
|
if (this.frames.length > 0) {
|
|
1092
1089
|
const frame0 = this.frames[0];
|
|
1093
1090
|
if (frame0.elementSnapshots) {
|
|
@@ -1600,7 +1597,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1600
1597
|
}
|
|
1601
1598
|
|
|
1602
1599
|
// src/core/engine.ts
|
|
1603
|
-
var
|
|
1600
|
+
var SaccadeEngine = class {
|
|
1604
1601
|
constructor() {
|
|
1605
1602
|
this.timing = new TimingController();
|
|
1606
1603
|
this.recorder = new TimelineRecorder();
|
|
@@ -1642,7 +1639,25 @@ var LapseEngine = class {
|
|
|
1642
1639
|
boundingBox: null
|
|
1643
1640
|
};
|
|
1644
1641
|
}
|
|
1645
|
-
|
|
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
|
+
}
|
|
1646
1661
|
this.capture = capture;
|
|
1647
1662
|
const scrubberState = {
|
|
1648
1663
|
elements: this.recorder.elements,
|
|
@@ -1702,23 +1717,23 @@ var LapseEngine = class {
|
|
|
1702
1717
|
}
|
|
1703
1718
|
};
|
|
1704
1719
|
|
|
1705
|
-
// src/react/
|
|
1720
|
+
// src/react/SaccadeContext.tsx
|
|
1706
1721
|
import { jsx } from "react/jsx-runtime";
|
|
1707
|
-
var
|
|
1708
|
-
function
|
|
1722
|
+
var SaccadeContext = createContext(null);
|
|
1723
|
+
function SaccadeProvider({ children }) {
|
|
1709
1724
|
const engineRef = useRef(null);
|
|
1710
1725
|
if (!engineRef.current) {
|
|
1711
|
-
engineRef.current = new
|
|
1726
|
+
engineRef.current = new SaccadeEngine();
|
|
1712
1727
|
}
|
|
1713
|
-
return /* @__PURE__ */ jsx(
|
|
1728
|
+
return /* @__PURE__ */ jsx(SaccadeContext.Provider, { value: engineRef.current, children });
|
|
1714
1729
|
}
|
|
1715
|
-
function
|
|
1716
|
-
const engine = useContext(
|
|
1717
|
-
if (!engine) throw new Error("
|
|
1730
|
+
function useSaccadeEngine() {
|
|
1731
|
+
const engine = useContext(SaccadeContext);
|
|
1732
|
+
if (!engine) throw new Error("useSaccadeEngine must be used within <SaccadeProvider>");
|
|
1718
1733
|
return engine;
|
|
1719
1734
|
}
|
|
1720
1735
|
|
|
1721
|
-
// src/react/
|
|
1736
|
+
// src/react/SaccadePanel.tsx
|
|
1722
1737
|
import { useRef as useRef5, useCallback as useCallback5 } from "react";
|
|
1723
1738
|
|
|
1724
1739
|
// src/react/Timeline.tsx
|
|
@@ -2112,7 +2127,7 @@ function SpeedControl({ speed, isPaused, onSetSpeed, onTogglePause }) {
|
|
|
2112
2127
|
import { useState as useState2, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef4, useSyncExternalStore } from "react";
|
|
2113
2128
|
var DETAIL_LEVELS = ["compact", "standard", "detailed", "forensic"];
|
|
2114
2129
|
function useTimeline() {
|
|
2115
|
-
const engine =
|
|
2130
|
+
const engine = useSaccadeEngine();
|
|
2116
2131
|
const state = useSyncExternalStore(
|
|
2117
2132
|
(cb) => engine.subscribe(cb),
|
|
2118
2133
|
() => engine.state
|
|
@@ -2146,9 +2161,13 @@ function useTimeline() {
|
|
|
2146
2161
|
[engine]
|
|
2147
2162
|
);
|
|
2148
2163
|
const stopRecording = useCallback3(() => {
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2164
|
+
try {
|
|
2165
|
+
const result = engine.stopRecording();
|
|
2166
|
+
setCapture(result);
|
|
2167
|
+
setScrubTime(0);
|
|
2168
|
+
} catch (e) {
|
|
2169
|
+
console.error("[Saccade] stopRecording failed:", e);
|
|
2170
|
+
}
|
|
2152
2171
|
}, [engine]);
|
|
2153
2172
|
const seek = useCallback3(
|
|
2154
2173
|
(timeMs) => {
|
|
@@ -2217,7 +2236,7 @@ function useTimeline() {
|
|
|
2217
2236
|
// src/react/useSpeed.ts
|
|
2218
2237
|
import { useState as useState3, useCallback as useCallback4, useEffect as useEffect3 } from "react";
|
|
2219
2238
|
function useSpeed() {
|
|
2220
|
-
const engine =
|
|
2239
|
+
const engine = useSaccadeEngine();
|
|
2221
2240
|
const [speed, setSpeedState] = useState3(1);
|
|
2222
2241
|
const [previousSpeed, setPreviousSpeed] = useState3(1);
|
|
2223
2242
|
const [isPaused, setIsPaused] = useState3(false);
|
|
@@ -2280,9 +2299,9 @@ function useSpeed() {
|
|
|
2280
2299
|
return { speed, isPaused, setSpeed, togglePause };
|
|
2281
2300
|
}
|
|
2282
2301
|
|
|
2283
|
-
// src/react/
|
|
2302
|
+
// src/react/SaccadePanel.tsx
|
|
2284
2303
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2285
|
-
function
|
|
2304
|
+
function SaccadePanel() {
|
|
2286
2305
|
const timeline = useTimeline();
|
|
2287
2306
|
const { speed, isPaused, setSpeed, togglePause } = useSpeed();
|
|
2288
2307
|
const panelRef = useRef5(null);
|
|
@@ -2742,9 +2761,9 @@ var PANEL_STYLES = (
|
|
|
2742
2761
|
`
|
|
2743
2762
|
);
|
|
2744
2763
|
|
|
2745
|
-
// src/react/
|
|
2764
|
+
// src/react/Saccade.tsx
|
|
2746
2765
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
2747
|
-
function
|
|
2766
|
+
function Saccade({ position = "bottom-left" }) {
|
|
2748
2767
|
const hostRef = useRef6(null);
|
|
2749
2768
|
const [shadowRoot, setShadowRoot] = useState4(null);
|
|
2750
2769
|
useEffect4(() => {
|
|
@@ -2775,17 +2794,17 @@ function Lapse({ position = "bottom-left" }) {
|
|
|
2775
2794
|
...positionOffset
|
|
2776
2795
|
},
|
|
2777
2796
|
children: shadowRoot && createPortal(
|
|
2778
|
-
/* @__PURE__ */ jsx5(
|
|
2797
|
+
/* @__PURE__ */ jsx5(SaccadeProvider, { children: /* @__PURE__ */ jsx5(SaccadePanel, {}) }),
|
|
2779
2798
|
shadowRoot.lastElementChild || shadowRoot
|
|
2780
2799
|
)
|
|
2781
2800
|
}
|
|
2782
2801
|
);
|
|
2783
2802
|
}
|
|
2784
2803
|
export {
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2804
|
+
Saccade,
|
|
2805
|
+
SaccadeEngine,
|
|
2806
|
+
SaccadeProvider,
|
|
2807
|
+
useSaccadeEngine,
|
|
2789
2808
|
useSpeed,
|
|
2790
2809
|
useTimeline
|
|
2791
2810
|
};
|