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/README.md +6 -6
- package/dist/core.cjs +74 -58
- 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 +73 -57
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +105 -85
- 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 +101 -81
- 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
|
|
@@ -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
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
1720
|
+
// src/react/SaccadeContext.tsx
|
|
1705
1721
|
import { jsx } from "react/jsx-runtime";
|
|
1706
|
-
var
|
|
1707
|
-
function
|
|
1722
|
+
var SaccadeContext = createContext(null);
|
|
1723
|
+
function SaccadeProvider({ children }) {
|
|
1708
1724
|
const engineRef = useRef(null);
|
|
1709
1725
|
if (!engineRef.current) {
|
|
1710
|
-
engineRef.current = new
|
|
1726
|
+
engineRef.current = new SaccadeEngine();
|
|
1711
1727
|
}
|
|
1712
|
-
return /* @__PURE__ */ jsx(
|
|
1728
|
+
return /* @__PURE__ */ jsx(SaccadeContext.Provider, { value: engineRef.current, children });
|
|
1713
1729
|
}
|
|
1714
|
-
function
|
|
1715
|
-
const engine = useContext(
|
|
1716
|
-
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>");
|
|
1717
1733
|
return engine;
|
|
1718
1734
|
}
|
|
1719
1735
|
|
|
1720
|
-
// src/react/
|
|
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 =
|
|
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
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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 =
|
|
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/
|
|
2302
|
+
// src/react/SaccadePanel.tsx
|
|
2283
2303
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2284
|
-
function
|
|
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/
|
|
2764
|
+
// src/react/Saccade.tsx
|
|
2745
2765
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
2746
|
-
function
|
|
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(
|
|
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
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2804
|
+
Saccade,
|
|
2805
|
+
SaccadeEngine,
|
|
2806
|
+
SaccadeProvider,
|
|
2807
|
+
useSaccadeEngine,
|
|
2788
2808
|
useSpeed,
|
|
2789
2809
|
useTimeline
|
|
2790
2810
|
};
|