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/README.md
CHANGED
|
@@ -13,13 +13,13 @@ npm install saccade -D
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import {
|
|
16
|
+
import { Saccade } from 'saccade'
|
|
17
17
|
|
|
18
18
|
function App() {
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
21
|
<YourApp />
|
|
22
|
-
<
|
|
22
|
+
<Saccade />
|
|
23
23
|
</>
|
|
24
24
|
)
|
|
25
25
|
}
|
|
@@ -51,9 +51,9 @@ Saccade patches timing APIs (`setTimeout`, `setInterval`, `requestAnimationFrame
|
|
|
51
51
|
For non-React usage or programmatic control:
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
|
-
import {
|
|
54
|
+
import { SaccadeEngine } from 'saccade/core'
|
|
55
55
|
|
|
56
|
-
const engine = new
|
|
56
|
+
const engine = new SaccadeEngine()
|
|
57
57
|
|
|
58
58
|
// Speed control
|
|
59
59
|
engine.setSpeed(0.25) // quarter speed
|
|
@@ -77,7 +77,7 @@ engine.destroy()
|
|
|
77
77
|
## React Hooks
|
|
78
78
|
|
|
79
79
|
```tsx
|
|
80
|
-
import {
|
|
80
|
+
import { SaccadeProvider, useSaccadeEngine, useTimeline, useSpeed } from 'saccade'
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
### `useSpeed`
|
|
@@ -127,7 +127,7 @@ import type {
|
|
|
127
127
|
|
|
128
128
|
## Requirements
|
|
129
129
|
|
|
130
|
-
- React 18+ (for the `<
|
|
130
|
+
- React 18+ (for the `<Saccade>` component)
|
|
131
131
|
- No React dependency needed for `saccade/core`
|
|
132
132
|
|
|
133
133
|
## License
|
package/dist/core.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/core/index.ts
|
|
21
21
|
var core_exports = {};
|
|
22
22
|
__export(core_exports, {
|
|
23
|
-
|
|
23
|
+
SaccadeEngine: () => SaccadeEngine,
|
|
24
24
|
TimelineRecorder: () => TimelineRecorder,
|
|
25
25
|
TimelineScrubber: () => TimelineScrubber,
|
|
26
26
|
TimingController: () => TimingController,
|
|
@@ -1036,11 +1036,12 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1036
1036
|
document.removeEventListener("pointerup", this.onPointerUp, true);
|
|
1037
1037
|
try {
|
|
1038
1038
|
for (const anim of document.getAnimations()) {
|
|
1039
|
+
const target = anim.effect?.target;
|
|
1040
|
+
if (target?.closest?.("[data-lapse-panel]")) continue;
|
|
1039
1041
|
anim.cancel();
|
|
1040
1042
|
}
|
|
1041
1043
|
} catch (_) {
|
|
1042
1044
|
}
|
|
1043
|
-
window.requestAnimationFrame = () => 0;
|
|
1044
1045
|
const noTransitions = document.createElement("style");
|
|
1045
1046
|
noTransitions.id = "__lapse-no-transitions";
|
|
1046
1047
|
noTransitions.textContent = "*, *::before, *::after { transition: none !important; animation: none !important; }";
|
|
@@ -1052,64 +1053,61 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1052
1053
|
blocker.title = "Clear the timeline to interact with the page";
|
|
1053
1054
|
document.body.appendChild(blocker);
|
|
1054
1055
|
this.blockerEl = blocker;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1073
|
-
allCss += t + "\n";
|
|
1056
|
+
setTimeout(() => {
|
|
1057
|
+
try {
|
|
1058
|
+
const lapseStyle = document.createElement("style");
|
|
1059
|
+
lapseStyle.id = "__lapse-state-rules";
|
|
1060
|
+
const hoverFocusRules = [];
|
|
1061
|
+
for (const sheet of document.styleSheets) {
|
|
1062
|
+
try {
|
|
1063
|
+
const walk = (rules) => {
|
|
1064
|
+
for (const rule of rules) {
|
|
1065
|
+
if (rule.cssRules) {
|
|
1066
|
+
walk(rule.cssRules);
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const t = rule.cssText;
|
|
1070
|
+
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1071
|
+
hoverFocusRules.push(t);
|
|
1072
|
+
}
|
|
1074
1073
|
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
walk2(sheet.cssRules);
|
|
1079
|
-
} catch (_) {
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
const stateRegex = /([^{}]*(?::hover|:focus-visible|:focus-within|:focus(?!-))[^{}]*)\{([^{}]*)\}/g;
|
|
1083
|
-
let match;
|
|
1084
|
-
while ((match = stateRegex.exec(allCss)) !== null) {
|
|
1085
|
-
const selector = match[1].trim();
|
|
1086
|
-
const body = match[2].trim();
|
|
1087
|
-
if (!body) continue;
|
|
1088
|
-
const newBody = body.replace(
|
|
1089
|
-
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1090
|
-
(m, prop, val, end) => {
|
|
1091
|
-
if (val.includes("!important")) return m;
|
|
1092
|
-
return prop + ": " + val.trim() + " !important" + end;
|
|
1074
|
+
};
|
|
1075
|
+
walk(sheet.cssRules);
|
|
1076
|
+
} catch (_) {
|
|
1093
1077
|
}
|
|
1094
|
-
);
|
|
1095
|
-
if (selector.includes(":hover")) {
|
|
1096
|
-
lapseStyle.textContent += selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }\n";
|
|
1097
1078
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1079
|
+
const parts = [];
|
|
1080
|
+
for (const ruleText of hoverFocusRules) {
|
|
1081
|
+
const braceIdx = ruleText.indexOf("{");
|
|
1082
|
+
if (braceIdx === -1) continue;
|
|
1083
|
+
const selector = ruleText.slice(0, braceIdx).trim();
|
|
1084
|
+
const bodyEnd = ruleText.lastIndexOf("}");
|
|
1085
|
+
const body = ruleText.slice(braceIdx + 1, bodyEnd).trim();
|
|
1086
|
+
if (!body) continue;
|
|
1087
|
+
const newBody = body.replace(
|
|
1088
|
+
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1089
|
+
(m, prop, val, end) => {
|
|
1090
|
+
if (val.includes("!important")) return m;
|
|
1091
|
+
return prop + ": " + val.trim() + " !important" + end;
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
if (selector.includes(":hover")) {
|
|
1095
|
+
parts.push(selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }");
|
|
1096
|
+
}
|
|
1097
|
+
if (selector.includes(":focus-visible")) {
|
|
1098
|
+
parts.push(selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1099
|
+
} else if (selector.includes(":focus-within")) {
|
|
1100
|
+
parts.push(selector.replace(/:focus-within/g, ":has([data-lapse-focus])") + " { " + newBody + " }");
|
|
1101
|
+
} else if (selector.includes(":focus")) {
|
|
1102
|
+
parts.push(selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1103
|
+
}
|
|
1107
1104
|
}
|
|
1105
|
+
lapseStyle.textContent = parts.join("\n");
|
|
1106
|
+
document.head.appendChild(lapseStyle);
|
|
1107
|
+
this.lapseStyleEl = lapseStyle;
|
|
1108
|
+
} catch (_) {
|
|
1108
1109
|
}
|
|
1109
|
-
|
|
1110
|
-
this.lapseStyleEl = lapseStyle;
|
|
1111
|
-
} catch (_) {
|
|
1112
|
-
}
|
|
1110
|
+
}, 0);
|
|
1113
1111
|
if (this.frames.length > 0) {
|
|
1114
1112
|
const frame0 = this.frames[0];
|
|
1115
1113
|
if (frame0.elementSnapshots) {
|
|
@@ -1622,7 +1620,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1622
1620
|
}
|
|
1623
1621
|
|
|
1624
1622
|
// src/core/engine.ts
|
|
1625
|
-
var
|
|
1623
|
+
var SaccadeEngine = class {
|
|
1626
1624
|
constructor() {
|
|
1627
1625
|
this.timing = new TimingController();
|
|
1628
1626
|
this.recorder = new TimelineRecorder();
|
|
@@ -1664,7 +1662,25 @@ var LapseEngine = class {
|
|
|
1664
1662
|
boundingBox: null
|
|
1665
1663
|
};
|
|
1666
1664
|
}
|
|
1667
|
-
|
|
1665
|
+
let capture;
|
|
1666
|
+
try {
|
|
1667
|
+
capture = this.recorder.stopRecording();
|
|
1668
|
+
} catch (e) {
|
|
1669
|
+
console.error("[Saccade] stopRecording failed:", e);
|
|
1670
|
+
document.getElementById("__lapse-scrub-blocker")?.remove();
|
|
1671
|
+
document.getElementById("__lapse-no-transitions")?.remove();
|
|
1672
|
+
document.getElementById("__lapse-state-rules")?.remove();
|
|
1673
|
+
this._state = "idle";
|
|
1674
|
+
this.notify();
|
|
1675
|
+
return {
|
|
1676
|
+
startTime: 0,
|
|
1677
|
+
endTime: 0,
|
|
1678
|
+
duration: 0,
|
|
1679
|
+
animations: [],
|
|
1680
|
+
frames: [],
|
|
1681
|
+
boundingBox: null
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1668
1684
|
this.capture = capture;
|
|
1669
1685
|
const scrubberState = {
|
|
1670
1686
|
elements: this.recorder.elements,
|
|
@@ -1725,7 +1741,7 @@ var LapseEngine = class {
|
|
|
1725
1741
|
};
|
|
1726
1742
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1727
1743
|
0 && (module.exports = {
|
|
1728
|
-
|
|
1744
|
+
SaccadeEngine,
|
|
1729
1745
|
TimelineRecorder,
|
|
1730
1746
|
TimelineScrubber,
|
|
1731
1747
|
TimingController,
|