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 CHANGED
@@ -13,13 +13,13 @@ npm install saccade -D
13
13
  ## Usage
14
14
 
15
15
  ```tsx
16
- import { Lapse } from 'saccade'
16
+ import { Saccade } from 'saccade'
17
17
 
18
18
  function App() {
19
19
  return (
20
20
  <>
21
21
  <YourApp />
22
- <Lapse />
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 { LapseEngine } from 'saccade/core'
54
+ import { SaccadeEngine } from 'saccade/core'
55
55
 
56
- const engine = new LapseEngine()
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 { LapseProvider, useLapseEngine, useTimeline, useSpeed } from 'saccade'
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 `<Lapse>` component)
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
- LapseEngine: () => LapseEngine,
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
- try {
1056
- const lapseStyle = document.createElement("style");
1057
- lapseStyle.id = "__lapse-state-rules";
1058
- let allCss = "";
1059
- for (const style of document.querySelectorAll("style")) {
1060
- if (style.id === "__lapse-state-rules") continue;
1061
- allCss += style.textContent + "\n";
1062
- }
1063
- for (const sheet of document.styleSheets) {
1064
- try {
1065
- let walk2 = function(rules) {
1066
- for (const rule of rules) {
1067
- if (rule.cssRules) {
1068
- walk2(rule.cssRules);
1069
- continue;
1070
- }
1071
- const t = rule.cssText;
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
- var walk = walk2;
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
- if (selector.includes(":focus-visible")) {
1099
- lapseStyle.textContent += selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
1100
- } else if (selector.includes(":focus-within")) {
1101
- lapseStyle.textContent += selector.replace(
1102
- /:focus-within/g,
1103
- ":has([data-lapse-focus])"
1104
- ) + " { " + newBody + " }\n";
1105
- } else if (selector.includes(":focus")) {
1106
- lapseStyle.textContent += selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
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
- document.head.appendChild(lapseStyle);
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 LapseEngine = class {
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
- const capture = this.recorder.stopRecording();
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
- LapseEngine,
1744
+ SaccadeEngine,
1729
1745
  TimelineRecorder,
1730
1746
  TimelineScrubber,
1731
1747
  TimingController,