saccade 0.1.0 → 0.2.0
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 +65 -2
- package/dist/core.cjs +54 -13
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +24 -2
- package/dist/core.d.ts +24 -2
- package/dist/core.mjs +51 -12
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +71 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -10
- package/dist/index.d.ts +34 -10
- package/dist/index.mjs +69 -25
- package/dist/index.mjs.map +1 -1
- package/dist/install.cjs +1846 -0
- package/dist/install.cjs.map +1 -0
- package/dist/install.d.cts +129 -0
- package/dist/install.d.ts +129 -0
- package/dist/install.mjs +1819 -0
- package/dist/install.mjs.map +1 -0
- package/package.json +12 -1
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,8 @@ __export(src_exports, {
|
|
|
24
24
|
Saccade: () => Saccade,
|
|
25
25
|
SaccadeEngine: () => SaccadeEngine,
|
|
26
26
|
SaccadeProvider: () => SaccadeProvider,
|
|
27
|
+
getSharedEngine: () => getSharedEngine,
|
|
28
|
+
resetSharedEngine: () => resetSharedEngine,
|
|
27
29
|
useSaccadeEngine: () => useSaccadeEngine,
|
|
28
30
|
useSpeed: () => useSpeed,
|
|
29
31
|
useTimeline: () => useTimeline
|
|
@@ -50,6 +52,7 @@ var TimingController = class {
|
|
|
50
52
|
this._origAnimate = null;
|
|
51
53
|
this.installed = false;
|
|
52
54
|
this.animPollId = 0;
|
|
55
|
+
this.gsapInstance = null;
|
|
53
56
|
// WeakMap tracking for animations and media
|
|
54
57
|
this.trackedAnims = /* @__PURE__ */ new WeakMap();
|
|
55
58
|
this.trackedMedia = /* @__PURE__ */ new WeakMap();
|
|
@@ -225,13 +228,19 @@ var TimingController = class {
|
|
|
225
228
|
} catch {
|
|
226
229
|
}
|
|
227
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Register a GSAP instance (for ES-module imports where window.gsap is
|
|
233
|
+
* undefined). Applies the current timeScale immediately if speed !== 1.
|
|
234
|
+
*/
|
|
235
|
+
registerGSAP(gsap) {
|
|
236
|
+
this.gsapInstance = gsap;
|
|
237
|
+
if (this.speed !== 1) this.patchGSAP();
|
|
238
|
+
}
|
|
228
239
|
/** Sync GSAP's global timeline if present. */
|
|
229
240
|
patchGSAP() {
|
|
230
241
|
try {
|
|
231
|
-
const gsap = window.gsap;
|
|
232
|
-
|
|
233
|
-
gsap.globalTimeline.timeScale(this.speed || 1e-3);
|
|
234
|
-
}
|
|
242
|
+
const gsap = this.gsapInstance ?? window.gsap;
|
|
243
|
+
gsap?.globalTimeline?.timeScale(this.speed || 1e-3);
|
|
235
244
|
} catch {
|
|
236
245
|
}
|
|
237
246
|
}
|
|
@@ -277,10 +286,11 @@ var TimingController = class {
|
|
|
277
286
|
} catch {
|
|
278
287
|
}
|
|
279
288
|
try {
|
|
280
|
-
const gsap = window.gsap;
|
|
281
|
-
|
|
289
|
+
const gsap = this.gsapInstance ?? window.gsap;
|
|
290
|
+
gsap?.globalTimeline?.timeScale(1);
|
|
282
291
|
} catch {
|
|
283
292
|
}
|
|
293
|
+
this.gsapInstance = null;
|
|
284
294
|
delete window.__LAPSE_ORIGINAL_RAF__;
|
|
285
295
|
delete window.__saccadeInstalled;
|
|
286
296
|
this.installed = false;
|
|
@@ -1555,7 +1565,7 @@ function generateExport(animations, frames, timeMs, filter = "active") {
|
|
|
1555
1565
|
animations: animExports
|
|
1556
1566
|
};
|
|
1557
1567
|
}
|
|
1558
|
-
function formatExportForLLM(exp, detail = "
|
|
1568
|
+
function formatExportForLLM(exp, detail = "moderate") {
|
|
1559
1569
|
const lines = [];
|
|
1560
1570
|
const grouped = /* @__PURE__ */ new Map();
|
|
1561
1571
|
for (const anim of exp.animations) {
|
|
@@ -1570,7 +1580,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1570
1580
|
const [from, to] = prop.range.split(" \u2192 ");
|
|
1571
1581
|
return !(from && to && from.trim() === to.trim());
|
|
1572
1582
|
}
|
|
1573
|
-
if (detail === "
|
|
1583
|
+
if (detail === "brief") {
|
|
1574
1584
|
lines.push(`# Animation State at ${exp.timestamp}`);
|
|
1575
1585
|
lines.push("");
|
|
1576
1586
|
for (const [, group] of grouped) {
|
|
@@ -1595,7 +1605,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1595
1605
|
}
|
|
1596
1606
|
lines.push(`# Animation State at ${exp.timestamp}`);
|
|
1597
1607
|
lines.push("");
|
|
1598
|
-
if (detail === "
|
|
1608
|
+
if (detail === "granular") {
|
|
1599
1609
|
lines.push("**Environment:**");
|
|
1600
1610
|
lines.push(`- Viewport: ${window.innerWidth}\xD7${window.innerHeight}`);
|
|
1601
1611
|
lines.push(`- URL: ${window.location.href}`);
|
|
@@ -1662,7 +1672,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1662
1672
|
lines.push(`Transitions: ${[...transitionSet].join(", ")}`);
|
|
1663
1673
|
lines.push("");
|
|
1664
1674
|
for (const line of cssPropLines) lines.push(line);
|
|
1665
|
-
if (detail === "detailed" || detail === "
|
|
1675
|
+
if (detail === "detailed" || detail === "granular") {
|
|
1666
1676
|
const allVars = {};
|
|
1667
1677
|
for (const anim of cssAnims) {
|
|
1668
1678
|
if (anim.resolvedVars) Object.assign(allVars, anim.resolvedVars);
|
|
@@ -1717,6 +1727,22 @@ var SaccadeEngine = class {
|
|
|
1717
1727
|
getSpeed() {
|
|
1718
1728
|
return this.timing.getSpeed();
|
|
1719
1729
|
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Install the timing patches immediately, without changing speed.
|
|
1732
|
+
*
|
|
1733
|
+
* Call this as early as possible (before app code, GSAP, or Framer Motion
|
|
1734
|
+
* run) so they capture the patched time functions rather than the originals.
|
|
1735
|
+
* Idempotent and harmless to call more than once. `setSpeed` and
|
|
1736
|
+
* `startRecording` also install on demand, so this is only needed to win the
|
|
1737
|
+
* early-load race against libraries that cache `Date.now`/`performance.now`.
|
|
1738
|
+
*/
|
|
1739
|
+
install() {
|
|
1740
|
+
this.timing.install();
|
|
1741
|
+
}
|
|
1742
|
+
/** Register a module-imported GSAP instance so saccade can slow it. */
|
|
1743
|
+
registerGSAP(gsap) {
|
|
1744
|
+
this.timing.registerGSAP(gsap);
|
|
1745
|
+
}
|
|
1720
1746
|
// -- Timeline recording ---------------------------------------------------
|
|
1721
1747
|
startRecording(boundingBox) {
|
|
1722
1748
|
if (this._state !== "idle") return;
|
|
@@ -1791,7 +1817,7 @@ var SaccadeEngine = class {
|
|
|
1791
1817
|
filter
|
|
1792
1818
|
);
|
|
1793
1819
|
}
|
|
1794
|
-
exportForLLM(timeMs, filter = "active", detail = "
|
|
1820
|
+
exportForLLM(timeMs, filter = "active", detail = "moderate") {
|
|
1795
1821
|
const exp = this.generateExport(timeMs, filter);
|
|
1796
1822
|
if (!exp) return "";
|
|
1797
1823
|
return formatExportForLLM(exp, detail);
|
|
@@ -1816,13 +1842,29 @@ var SaccadeEngine = class {
|
|
|
1816
1842
|
}
|
|
1817
1843
|
};
|
|
1818
1844
|
|
|
1845
|
+
// src/core/shared.ts
|
|
1846
|
+
var KEY = "__saccadeSharedEngine__";
|
|
1847
|
+
function getSharedEngine() {
|
|
1848
|
+
const g = globalThis;
|
|
1849
|
+
if (!g[KEY]) g[KEY] = new SaccadeEngine();
|
|
1850
|
+
return g[KEY];
|
|
1851
|
+
}
|
|
1852
|
+
function resetSharedEngine() {
|
|
1853
|
+
const g = globalThis;
|
|
1854
|
+
g[KEY]?.destroy();
|
|
1855
|
+
g[KEY] = null;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1819
1858
|
// src/react/SaccadeContext.tsx
|
|
1820
1859
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1821
1860
|
var SaccadeContext = (0, import_react.createContext)(null);
|
|
1822
|
-
function SaccadeProvider({
|
|
1861
|
+
function SaccadeProvider({
|
|
1862
|
+
children,
|
|
1863
|
+
engine
|
|
1864
|
+
}) {
|
|
1823
1865
|
const engineRef = (0, import_react.useRef)(null);
|
|
1824
1866
|
if (!engineRef.current) {
|
|
1825
|
-
engineRef.current =
|
|
1867
|
+
engineRef.current = engine ?? getSharedEngine();
|
|
1826
1868
|
}
|
|
1827
1869
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SaccadeContext.Provider, { value: engineRef.current, children });
|
|
1828
1870
|
}
|
|
@@ -1839,10 +1881,10 @@ var import_react6 = require("react");
|
|
|
1839
1881
|
var import_react2 = require("react");
|
|
1840
1882
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1841
1883
|
var DETAIL_LABELS = {
|
|
1842
|
-
|
|
1843
|
-
|
|
1884
|
+
brief: "Brief",
|
|
1885
|
+
moderate: "Moderate",
|
|
1844
1886
|
detailed: "Detailed",
|
|
1845
|
-
|
|
1887
|
+
granular: "Granular"
|
|
1846
1888
|
};
|
|
1847
1889
|
function CopyCheckIcon({ copied }) {
|
|
1848
1890
|
const spring = "cubic-bezier(0.34, 1.15, 0.64, 1)";
|
|
@@ -1896,10 +1938,10 @@ function CopyCheckIcon({ copied }) {
|
|
|
1896
1938
|
] });
|
|
1897
1939
|
}
|
|
1898
1940
|
var DETAIL_BRIGHT_COUNT = {
|
|
1899
|
-
|
|
1900
|
-
|
|
1941
|
+
brief: 1,
|
|
1942
|
+
moderate: 2,
|
|
1901
1943
|
detailed: 3,
|
|
1902
|
-
|
|
1944
|
+
granular: 4
|
|
1903
1945
|
};
|
|
1904
1946
|
function DetailIcon({ level }) {
|
|
1905
1947
|
const bright = DETAIL_BRIGHT_COUNT[level];
|
|
@@ -2224,7 +2266,9 @@ function SpeedControl({ speed, isPaused, onSetSpeed, onTogglePause }) {
|
|
|
2224
2266
|
|
|
2225
2267
|
// src/react/useTimeline.ts
|
|
2226
2268
|
var import_react4 = require("react");
|
|
2227
|
-
var
|
|
2269
|
+
var _realSetTimeout = setTimeout.bind(window);
|
|
2270
|
+
var _realClearTimeout = clearTimeout.bind(window);
|
|
2271
|
+
var DETAIL_LEVELS = ["brief", "moderate", "detailed", "granular"];
|
|
2228
2272
|
function useTimeline() {
|
|
2229
2273
|
const engine = useSaccadeEngine();
|
|
2230
2274
|
const state = (0, import_react4.useSyncExternalStore)(
|
|
@@ -2235,7 +2279,7 @@ function useTimeline() {
|
|
|
2235
2279
|
const [scrubTime, setScrubTime] = (0, import_react4.useState)(0);
|
|
2236
2280
|
const [copied, setCopied] = (0, import_react4.useState)(false);
|
|
2237
2281
|
const [exportFilter, setExportFilter] = (0, import_react4.useState)("all-animations");
|
|
2238
|
-
const [detailLevel, setDetailLevel] = (0, import_react4.useState)("
|
|
2282
|
+
const [detailLevel, setDetailLevel] = (0, import_react4.useState)("moderate");
|
|
2239
2283
|
const copiedTimeout = (0, import_react4.useRef)(null);
|
|
2240
2284
|
const pendingSeek = (0, import_react4.useRef)(null);
|
|
2241
2285
|
const rafId = (0, import_react4.useRef)(0);
|
|
@@ -2309,8 +2353,8 @@ function useTimeline() {
|
|
|
2309
2353
|
navigator.clipboard.writeText(text).catch(() => {
|
|
2310
2354
|
});
|
|
2311
2355
|
setCopied(true);
|
|
2312
|
-
if (copiedTimeout.current)
|
|
2313
|
-
copiedTimeout.current =
|
|
2356
|
+
if (copiedTimeout.current) _realClearTimeout(copiedTimeout.current);
|
|
2357
|
+
copiedTimeout.current = _realSetTimeout(() => setCopied(false), 1800);
|
|
2314
2358
|
return text;
|
|
2315
2359
|
},
|
|
2316
2360
|
[engine, capture, scrubTime, exportFilter, detailLevel]
|
|
@@ -2862,7 +2906,7 @@ var PANEL_STYLES = (
|
|
|
2862
2906
|
|
|
2863
2907
|
// src/react/Saccade.tsx
|
|
2864
2908
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2865
|
-
function Saccade({ position = "bottom-left" }) {
|
|
2909
|
+
function Saccade({ position = "bottom-left", engine }) {
|
|
2866
2910
|
const [shadowRoot, setShadowRoot] = (0, import_react7.useState)(null);
|
|
2867
2911
|
const hostRef = (0, import_react7.useRef)(null);
|
|
2868
2912
|
(0, import_react7.useEffect)(() => {
|
|
@@ -2886,7 +2930,7 @@ function Saccade({ position = "bottom-left" }) {
|
|
|
2886
2930
|
}, [position]);
|
|
2887
2931
|
if (!shadowRoot) return null;
|
|
2888
2932
|
return (0, import_react_dom.createPortal)(
|
|
2889
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadePanel, {}) }),
|
|
2933
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadeProvider, { engine, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadePanel, {}) }),
|
|
2890
2934
|
shadowRoot.lastElementChild || shadowRoot
|
|
2891
2935
|
);
|
|
2892
2936
|
}
|
|
@@ -2895,6 +2939,8 @@ function Saccade({ position = "bottom-left" }) {
|
|
|
2895
2939
|
Saccade,
|
|
2896
2940
|
SaccadeEngine,
|
|
2897
2941
|
SaccadeProvider,
|
|
2942
|
+
getSharedEngine,
|
|
2943
|
+
resetSharedEngine,
|
|
2898
2944
|
useSaccadeEngine,
|
|
2899
2945
|
useSpeed,
|
|
2900
2946
|
useTimeline
|