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.cjs
CHANGED
|
@@ -21,20 +21,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Saccade: () => Saccade,
|
|
25
|
+
SaccadeEngine: () => SaccadeEngine,
|
|
26
|
+
SaccadeProvider: () => SaccadeProvider,
|
|
27
|
+
useSaccadeEngine: () => useSaccadeEngine,
|
|
28
28
|
useSpeed: () => useSpeed,
|
|
29
29
|
useTimeline: () => useTimeline
|
|
30
30
|
});
|
|
31
31
|
module.exports = __toCommonJS(src_exports);
|
|
32
32
|
|
|
33
|
-
// src/react/
|
|
33
|
+
// src/react/Saccade.tsx
|
|
34
34
|
var import_react7 = require("react");
|
|
35
35
|
var import_react_dom = require("react-dom");
|
|
36
36
|
|
|
37
|
-
// src/react/
|
|
37
|
+
// src/react/SaccadeContext.tsx
|
|
38
38
|
var import_react = require("react");
|
|
39
39
|
|
|
40
40
|
// src/core/timing.ts
|
|
@@ -1043,11 +1043,12 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1043
1043
|
document.removeEventListener("pointerup", this.onPointerUp, true);
|
|
1044
1044
|
try {
|
|
1045
1045
|
for (const anim of document.getAnimations()) {
|
|
1046
|
+
const target = anim.effect?.target;
|
|
1047
|
+
if (target?.closest?.("[data-lapse-panel]")) continue;
|
|
1046
1048
|
anim.cancel();
|
|
1047
1049
|
}
|
|
1048
1050
|
} catch (_) {
|
|
1049
1051
|
}
|
|
1050
|
-
window.requestAnimationFrame = () => 0;
|
|
1051
1052
|
const noTransitions = document.createElement("style");
|
|
1052
1053
|
noTransitions.id = "__lapse-no-transitions";
|
|
1053
1054
|
noTransitions.textContent = "*, *::before, *::after { transition: none !important; animation: none !important; }";
|
|
@@ -1059,64 +1060,61 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1059
1060
|
blocker.title = "Clear the timeline to interact with the page";
|
|
1060
1061
|
document.body.appendChild(blocker);
|
|
1061
1062
|
this.blockerEl = blocker;
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1080
|
-
allCss += t + "\n";
|
|
1063
|
+
setTimeout(() => {
|
|
1064
|
+
try {
|
|
1065
|
+
const lapseStyle = document.createElement("style");
|
|
1066
|
+
lapseStyle.id = "__lapse-state-rules";
|
|
1067
|
+
const hoverFocusRules = [];
|
|
1068
|
+
for (const sheet of document.styleSheets) {
|
|
1069
|
+
try {
|
|
1070
|
+
const walk = (rules) => {
|
|
1071
|
+
for (const rule of rules) {
|
|
1072
|
+
if (rule.cssRules) {
|
|
1073
|
+
walk(rule.cssRules);
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
const t = rule.cssText;
|
|
1077
|
+
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1078
|
+
hoverFocusRules.push(t);
|
|
1079
|
+
}
|
|
1081
1080
|
}
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
walk2(sheet.cssRules);
|
|
1086
|
-
} catch (_) {
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
const stateRegex = /([^{}]*(?::hover|:focus-visible|:focus-within|:focus(?!-))[^{}]*)\{([^{}]*)\}/g;
|
|
1090
|
-
let match;
|
|
1091
|
-
while ((match = stateRegex.exec(allCss)) !== null) {
|
|
1092
|
-
const selector = match[1].trim();
|
|
1093
|
-
const body = match[2].trim();
|
|
1094
|
-
if (!body) continue;
|
|
1095
|
-
const newBody = body.replace(
|
|
1096
|
-
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1097
|
-
(m, prop, val, end) => {
|
|
1098
|
-
if (val.includes("!important")) return m;
|
|
1099
|
-
return prop + ": " + val.trim() + " !important" + end;
|
|
1081
|
+
};
|
|
1082
|
+
walk(sheet.cssRules);
|
|
1083
|
+
} catch (_) {
|
|
1100
1084
|
}
|
|
1101
|
-
);
|
|
1102
|
-
if (selector.includes(":hover")) {
|
|
1103
|
-
lapseStyle.textContent += selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }\n";
|
|
1104
1085
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1086
|
+
const parts = [];
|
|
1087
|
+
for (const ruleText of hoverFocusRules) {
|
|
1088
|
+
const braceIdx = ruleText.indexOf("{");
|
|
1089
|
+
if (braceIdx === -1) continue;
|
|
1090
|
+
const selector = ruleText.slice(0, braceIdx).trim();
|
|
1091
|
+
const bodyEnd = ruleText.lastIndexOf("}");
|
|
1092
|
+
const body = ruleText.slice(braceIdx + 1, bodyEnd).trim();
|
|
1093
|
+
if (!body) continue;
|
|
1094
|
+
const newBody = body.replace(
|
|
1095
|
+
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1096
|
+
(m, prop, val, end) => {
|
|
1097
|
+
if (val.includes("!important")) return m;
|
|
1098
|
+
return prop + ": " + val.trim() + " !important" + end;
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1101
|
+
if (selector.includes(":hover")) {
|
|
1102
|
+
parts.push(selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }");
|
|
1103
|
+
}
|
|
1104
|
+
if (selector.includes(":focus-visible")) {
|
|
1105
|
+
parts.push(selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1106
|
+
} else if (selector.includes(":focus-within")) {
|
|
1107
|
+
parts.push(selector.replace(/:focus-within/g, ":has([data-lapse-focus])") + " { " + newBody + " }");
|
|
1108
|
+
} else if (selector.includes(":focus")) {
|
|
1109
|
+
parts.push(selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1110
|
+
}
|
|
1114
1111
|
}
|
|
1112
|
+
lapseStyle.textContent = parts.join("\n");
|
|
1113
|
+
document.head.appendChild(lapseStyle);
|
|
1114
|
+
this.lapseStyleEl = lapseStyle;
|
|
1115
|
+
} catch (_) {
|
|
1115
1116
|
}
|
|
1116
|
-
|
|
1117
|
-
this.lapseStyleEl = lapseStyle;
|
|
1118
|
-
} catch (_) {
|
|
1119
|
-
}
|
|
1117
|
+
}, 0);
|
|
1120
1118
|
if (this.frames.length > 0) {
|
|
1121
1119
|
const frame0 = this.frames[0];
|
|
1122
1120
|
if (frame0.elementSnapshots) {
|
|
@@ -1629,7 +1627,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1629
1627
|
}
|
|
1630
1628
|
|
|
1631
1629
|
// src/core/engine.ts
|
|
1632
|
-
var
|
|
1630
|
+
var SaccadeEngine = class {
|
|
1633
1631
|
constructor() {
|
|
1634
1632
|
this.timing = new TimingController();
|
|
1635
1633
|
this.recorder = new TimelineRecorder();
|
|
@@ -1671,7 +1669,25 @@ var LapseEngine = class {
|
|
|
1671
1669
|
boundingBox: null
|
|
1672
1670
|
};
|
|
1673
1671
|
}
|
|
1674
|
-
|
|
1672
|
+
let capture;
|
|
1673
|
+
try {
|
|
1674
|
+
capture = this.recorder.stopRecording();
|
|
1675
|
+
} catch (e) {
|
|
1676
|
+
console.error("[Saccade] stopRecording failed:", e);
|
|
1677
|
+
document.getElementById("__lapse-scrub-blocker")?.remove();
|
|
1678
|
+
document.getElementById("__lapse-no-transitions")?.remove();
|
|
1679
|
+
document.getElementById("__lapse-state-rules")?.remove();
|
|
1680
|
+
this._state = "idle";
|
|
1681
|
+
this.notify();
|
|
1682
|
+
return {
|
|
1683
|
+
startTime: 0,
|
|
1684
|
+
endTime: 0,
|
|
1685
|
+
duration: 0,
|
|
1686
|
+
animations: [],
|
|
1687
|
+
frames: [],
|
|
1688
|
+
boundingBox: null
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1675
1691
|
this.capture = capture;
|
|
1676
1692
|
const scrubberState = {
|
|
1677
1693
|
elements: this.recorder.elements,
|
|
@@ -1731,23 +1747,23 @@ var LapseEngine = class {
|
|
|
1731
1747
|
}
|
|
1732
1748
|
};
|
|
1733
1749
|
|
|
1734
|
-
// src/react/
|
|
1750
|
+
// src/react/SaccadeContext.tsx
|
|
1735
1751
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1736
|
-
var
|
|
1737
|
-
function
|
|
1752
|
+
var SaccadeContext = (0, import_react.createContext)(null);
|
|
1753
|
+
function SaccadeProvider({ children }) {
|
|
1738
1754
|
const engineRef = (0, import_react.useRef)(null);
|
|
1739
1755
|
if (!engineRef.current) {
|
|
1740
|
-
engineRef.current = new
|
|
1756
|
+
engineRef.current = new SaccadeEngine();
|
|
1741
1757
|
}
|
|
1742
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1758
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SaccadeContext.Provider, { value: engineRef.current, children });
|
|
1743
1759
|
}
|
|
1744
|
-
function
|
|
1745
|
-
const engine = (0, import_react.useContext)(
|
|
1746
|
-
if (!engine) throw new Error("
|
|
1760
|
+
function useSaccadeEngine() {
|
|
1761
|
+
const engine = (0, import_react.useContext)(SaccadeContext);
|
|
1762
|
+
if (!engine) throw new Error("useSaccadeEngine must be used within <SaccadeProvider>");
|
|
1747
1763
|
return engine;
|
|
1748
1764
|
}
|
|
1749
1765
|
|
|
1750
|
-
// src/react/
|
|
1766
|
+
// src/react/SaccadePanel.tsx
|
|
1751
1767
|
var import_react6 = require("react");
|
|
1752
1768
|
|
|
1753
1769
|
// src/react/Timeline.tsx
|
|
@@ -2141,7 +2157,7 @@ function SpeedControl({ speed, isPaused, onSetSpeed, onTogglePause }) {
|
|
|
2141
2157
|
var import_react4 = require("react");
|
|
2142
2158
|
var DETAIL_LEVELS = ["compact", "standard", "detailed", "forensic"];
|
|
2143
2159
|
function useTimeline() {
|
|
2144
|
-
const engine =
|
|
2160
|
+
const engine = useSaccadeEngine();
|
|
2145
2161
|
const state = (0, import_react4.useSyncExternalStore)(
|
|
2146
2162
|
(cb) => engine.subscribe(cb),
|
|
2147
2163
|
() => engine.state
|
|
@@ -2175,9 +2191,13 @@ function useTimeline() {
|
|
|
2175
2191
|
[engine]
|
|
2176
2192
|
);
|
|
2177
2193
|
const stopRecording = (0, import_react4.useCallback)(() => {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2194
|
+
try {
|
|
2195
|
+
const result = engine.stopRecording();
|
|
2196
|
+
setCapture(result);
|
|
2197
|
+
setScrubTime(0);
|
|
2198
|
+
} catch (e) {
|
|
2199
|
+
console.error("[Saccade] stopRecording failed:", e);
|
|
2200
|
+
}
|
|
2181
2201
|
}, [engine]);
|
|
2182
2202
|
const seek = (0, import_react4.useCallback)(
|
|
2183
2203
|
(timeMs) => {
|
|
@@ -2246,7 +2266,7 @@ function useTimeline() {
|
|
|
2246
2266
|
// src/react/useSpeed.ts
|
|
2247
2267
|
var import_react5 = require("react");
|
|
2248
2268
|
function useSpeed() {
|
|
2249
|
-
const engine =
|
|
2269
|
+
const engine = useSaccadeEngine();
|
|
2250
2270
|
const [speed, setSpeedState] = (0, import_react5.useState)(1);
|
|
2251
2271
|
const [previousSpeed, setPreviousSpeed] = (0, import_react5.useState)(1);
|
|
2252
2272
|
const [isPaused, setIsPaused] = (0, import_react5.useState)(false);
|
|
@@ -2309,9 +2329,9 @@ function useSpeed() {
|
|
|
2309
2329
|
return { speed, isPaused, setSpeed, togglePause };
|
|
2310
2330
|
}
|
|
2311
2331
|
|
|
2312
|
-
// src/react/
|
|
2332
|
+
// src/react/SaccadePanel.tsx
|
|
2313
2333
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
2314
|
-
function
|
|
2334
|
+
function SaccadePanel() {
|
|
2315
2335
|
const timeline = useTimeline();
|
|
2316
2336
|
const { speed, isPaused, setSpeed, togglePause } = useSpeed();
|
|
2317
2337
|
const panelRef = (0, import_react6.useRef)(null);
|
|
@@ -2771,9 +2791,9 @@ var PANEL_STYLES = (
|
|
|
2771
2791
|
`
|
|
2772
2792
|
);
|
|
2773
2793
|
|
|
2774
|
-
// src/react/
|
|
2794
|
+
// src/react/Saccade.tsx
|
|
2775
2795
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2776
|
-
function
|
|
2796
|
+
function Saccade({ position = "bottom-left" }) {
|
|
2777
2797
|
const hostRef = (0, import_react7.useRef)(null);
|
|
2778
2798
|
const [shadowRoot, setShadowRoot] = (0, import_react7.useState)(null);
|
|
2779
2799
|
(0, import_react7.useEffect)(() => {
|
|
@@ -2804,7 +2824,7 @@ function Lapse({ position = "bottom-left" }) {
|
|
|
2804
2824
|
...positionOffset
|
|
2805
2825
|
},
|
|
2806
2826
|
children: shadowRoot && (0, import_react_dom.createPortal)(
|
|
2807
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2827
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadePanel, {}) }),
|
|
2808
2828
|
shadowRoot.lastElementChild || shadowRoot
|
|
2809
2829
|
)
|
|
2810
2830
|
}
|
|
@@ -2812,10 +2832,10 @@ function Lapse({ position = "bottom-left" }) {
|
|
|
2812
2832
|
}
|
|
2813
2833
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2814
2834
|
0 && (module.exports = {
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2835
|
+
Saccade,
|
|
2836
|
+
SaccadeEngine,
|
|
2837
|
+
SaccadeProvider,
|
|
2838
|
+
useSaccadeEngine,
|
|
2819
2839
|
useSpeed,
|
|
2820
2840
|
useTimeline
|
|
2821
2841
|
});
|