saccade 0.0.2 → 0.1.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 +6 -6
- package/dist/core.cjs +288 -204
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +43 -13
- package/dist/core.d.ts +43 -13
- package/dist/core.mjs +287 -203
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +334 -255
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.mjs +330 -251
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,33 +21,38 @@ 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
|
|
41
|
+
var MEDIA_RATE_MIN = 0.0625;
|
|
42
|
+
var MEDIA_RATE_MAX = 16;
|
|
43
|
+
var ZERO_SPEED_DIVISOR = 1e-4;
|
|
41
44
|
var TimingController = class {
|
|
42
45
|
constructor() {
|
|
43
46
|
this.speed = 1;
|
|
44
47
|
this.virtualBaseline = 0;
|
|
45
48
|
this.intervalMap = /* @__PURE__ */ new Map();
|
|
46
49
|
this.nextIntervalId = 1e6;
|
|
47
|
-
this.mediaObserver = null;
|
|
48
|
-
this.animObserver = null;
|
|
49
50
|
this._origAnimate = null;
|
|
50
51
|
this.installed = false;
|
|
52
|
+
this.animPollId = 0;
|
|
53
|
+
// WeakMap tracking for animations and media
|
|
54
|
+
this.trackedAnims = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
this.trackedMedia = /* @__PURE__ */ new WeakMap();
|
|
51
56
|
this._raf = requestAnimationFrame.bind(window);
|
|
52
57
|
this._caf = cancelAnimationFrame.bind(window);
|
|
53
58
|
this._setTimeout = setTimeout.bind(window);
|
|
@@ -57,19 +62,34 @@ var TimingController = class {
|
|
|
57
62
|
this._perfNow = performance.now.bind(performance);
|
|
58
63
|
this._dateNow = Date.now;
|
|
59
64
|
this.realBaseline = this._perfNow();
|
|
65
|
+
this.dateRealBaseline = this._dateNow();
|
|
66
|
+
this.dateVirtualBaseline = this.dateRealBaseline;
|
|
60
67
|
}
|
|
61
68
|
getVirtualTime() {
|
|
62
69
|
const realElapsed = this._perfNow() - this.realBaseline;
|
|
63
70
|
return this.virtualBaseline + realElapsed * this.speed;
|
|
64
71
|
}
|
|
72
|
+
getVirtualDateNow() {
|
|
73
|
+
const realElapsed = this._dateNow() - this.dateRealBaseline;
|
|
74
|
+
return this.dateVirtualBaseline + realElapsed * this.speed;
|
|
75
|
+
}
|
|
65
76
|
reanchor() {
|
|
66
77
|
const virtualNow = this.getVirtualTime();
|
|
67
78
|
this.realBaseline = this._perfNow();
|
|
68
79
|
this.virtualBaseline = virtualNow;
|
|
80
|
+
const virtualDateNow = this.getVirtualDateNow();
|
|
81
|
+
this.dateRealBaseline = this._dateNow();
|
|
82
|
+
this.dateVirtualBaseline = virtualDateNow;
|
|
83
|
+
}
|
|
84
|
+
/** Effective speed divisor — avoids division by zero at speed=0. */
|
|
85
|
+
get speedDivisor() {
|
|
86
|
+
return this.speed || ZERO_SPEED_DIVISOR;
|
|
69
87
|
}
|
|
70
88
|
/** Install timing patches. Safe to call multiple times. */
|
|
71
89
|
install() {
|
|
72
90
|
if (this.installed) return;
|
|
91
|
+
if (window.__saccadeInstalled) return;
|
|
92
|
+
window.__saccadeInstalled = true;
|
|
73
93
|
this.installed = true;
|
|
74
94
|
const self = this;
|
|
75
95
|
window.__LAPSE_ORIGINAL_RAF__ = this._raf;
|
|
@@ -78,21 +98,27 @@ var TimingController = class {
|
|
|
78
98
|
Element.prototype.animate = function(...args) {
|
|
79
99
|
const anim = origAnimate.apply(this, args);
|
|
80
100
|
if (self.speed !== 1) {
|
|
81
|
-
|
|
101
|
+
const originalRate = anim.playbackRate;
|
|
102
|
+
const applied = originalRate * (self.speed || 1e-3);
|
|
103
|
+
anim.playbackRate = applied;
|
|
104
|
+
self.trackedAnims.set(anim, { original: originalRate, applied });
|
|
82
105
|
}
|
|
83
106
|
return anim;
|
|
84
107
|
};
|
|
85
108
|
performance.now = () => self.getVirtualTime();
|
|
86
|
-
|
|
87
|
-
Date.now = () => dateBaseline + self.getVirtualTime();
|
|
109
|
+
Date.now = () => self.getVirtualDateNow();
|
|
88
110
|
window.requestAnimationFrame = (callback) => {
|
|
89
111
|
return self._raf(() => {
|
|
112
|
+
if (self.speed === 0) {
|
|
113
|
+
window.requestAnimationFrame(callback);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
90
116
|
callback(self.getVirtualTime());
|
|
91
117
|
});
|
|
92
118
|
};
|
|
93
119
|
window.cancelAnimationFrame = this._caf;
|
|
94
120
|
window.setTimeout = ((handler, delay, ...args) => {
|
|
95
|
-
const scaledDelay = (delay ?? 0) /
|
|
121
|
+
const scaledDelay = (delay ?? 0) / self.speedDivisor;
|
|
96
122
|
return self._setTimeout(handler, scaledDelay, ...args);
|
|
97
123
|
});
|
|
98
124
|
window.clearTimeout = this._clearTimeout;
|
|
@@ -100,7 +126,7 @@ var TimingController = class {
|
|
|
100
126
|
const id = self.nextIntervalId++;
|
|
101
127
|
const baseDelay = delay ?? 0;
|
|
102
128
|
function tick() {
|
|
103
|
-
const scaledDelay = baseDelay /
|
|
129
|
+
const scaledDelay = baseDelay / self.speedDivisor;
|
|
104
130
|
const realId = self._setTimeout(() => {
|
|
105
131
|
if (typeof handler === "function") {
|
|
106
132
|
;
|
|
@@ -125,61 +151,93 @@ var TimingController = class {
|
|
|
125
151
|
self._clearInterval(id);
|
|
126
152
|
}
|
|
127
153
|
});
|
|
128
|
-
this.
|
|
129
|
-
for (const mutation of mutations) {
|
|
130
|
-
for (const node of mutation.addedNodes) {
|
|
131
|
-
if (node instanceof HTMLVideoElement || node instanceof HTMLAudioElement) {
|
|
132
|
-
node.playbackRate = self.speed;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
if (document.body) {
|
|
138
|
-
this.mediaObserver.observe(document.body, { childList: true, subtree: true });
|
|
139
|
-
}
|
|
154
|
+
this.startAnimationPoll();
|
|
140
155
|
}
|
|
141
156
|
/** Set playback speed. Requires install() first. */
|
|
142
157
|
setSpeed(newSpeed) {
|
|
143
158
|
if (!this.installed) this.install();
|
|
144
159
|
this.reanchor();
|
|
145
160
|
this.speed = newSpeed;
|
|
146
|
-
document.querySelectorAll("video, audio").forEach((el) => {
|
|
147
|
-
;
|
|
148
|
-
el.playbackRate = newSpeed;
|
|
149
|
-
});
|
|
150
161
|
this.patchAnimations();
|
|
162
|
+
this.patchMedia();
|
|
163
|
+
this.patchGSAP();
|
|
164
|
+
}
|
|
165
|
+
getSpeed() {
|
|
166
|
+
return this.speed;
|
|
167
|
+
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Animation polling — per-frame via original rAF
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
startAnimationPoll() {
|
|
172
|
+
const poll = () => {
|
|
173
|
+
if (!this.installed) return;
|
|
174
|
+
this.patchAnimations();
|
|
175
|
+
this.patchMedia();
|
|
176
|
+
this.animPollId = this._raf(poll);
|
|
177
|
+
};
|
|
178
|
+
this.animPollId = this._raf(poll);
|
|
151
179
|
}
|
|
152
|
-
/** Patch playbackRate on all active
|
|
180
|
+
/** Patch playbackRate on all active animations via WAAPI. */
|
|
153
181
|
patchAnimations() {
|
|
154
182
|
try {
|
|
155
183
|
const anims = document.getAnimations();
|
|
156
184
|
for (const anim of anims) {
|
|
157
185
|
const target = anim.effect?.target;
|
|
158
186
|
if (target?.closest?.("[data-lapse-panel]")) continue;
|
|
159
|
-
|
|
187
|
+
if (target?.closest?.("[data-saccade-exclude]")) continue;
|
|
188
|
+
const effectiveSpeed = this.speed || 1e-3;
|
|
189
|
+
let tracked = this.trackedAnims.get(anim);
|
|
190
|
+
if (!tracked) {
|
|
191
|
+
tracked = { original: anim.playbackRate, applied: anim.playbackRate };
|
|
192
|
+
this.trackedAnims.set(anim, tracked);
|
|
193
|
+
} else if (anim.playbackRate !== tracked.applied) {
|
|
194
|
+
tracked.original = anim.playbackRate;
|
|
195
|
+
}
|
|
196
|
+
const desired = tracked.original * effectiveSpeed;
|
|
197
|
+
if (anim.playbackRate !== desired) {
|
|
198
|
+
anim.playbackRate = desired;
|
|
199
|
+
tracked.applied = desired;
|
|
200
|
+
}
|
|
160
201
|
}
|
|
161
202
|
} catch {
|
|
162
203
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
204
|
+
}
|
|
205
|
+
/** Patch playbackRate on all video/audio elements. */
|
|
206
|
+
patchMedia() {
|
|
207
|
+
try {
|
|
208
|
+
document.querySelectorAll("video, audio").forEach((node) => {
|
|
209
|
+
const el = node;
|
|
210
|
+
if (el.closest?.("[data-lapse-panel]")) return;
|
|
211
|
+
if (el.closest?.("[data-saccade-exclude]")) return;
|
|
212
|
+
let tracked = this.trackedMedia.get(el);
|
|
213
|
+
if (!tracked) {
|
|
214
|
+
tracked = { original: el.playbackRate, applied: el.playbackRate };
|
|
215
|
+
this.trackedMedia.set(el, tracked);
|
|
216
|
+
} else if (el.playbackRate !== tracked.applied) {
|
|
217
|
+
tracked.original = el.playbackRate;
|
|
218
|
+
}
|
|
219
|
+
const desired = Math.min(MEDIA_RATE_MAX, Math.max(MEDIA_RATE_MIN, tracked.original * (this.speed || MEDIA_RATE_MIN)));
|
|
220
|
+
if (el.playbackRate !== desired) {
|
|
221
|
+
el.playbackRate = desired;
|
|
222
|
+
tracked.applied = desired;
|
|
176
223
|
}
|
|
177
|
-
}
|
|
224
|
+
});
|
|
225
|
+
} catch {
|
|
178
226
|
}
|
|
179
227
|
}
|
|
180
|
-
|
|
181
|
-
|
|
228
|
+
/** Sync GSAP's global timeline if present. */
|
|
229
|
+
patchGSAP() {
|
|
230
|
+
try {
|
|
231
|
+
const gsap = window.gsap;
|
|
232
|
+
if (gsap?.globalTimeline) {
|
|
233
|
+
gsap.globalTimeline.timeScale(this.speed || 1e-3);
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
182
237
|
}
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Cleanup
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
183
241
|
/** Restore all patched APIs to originals. */
|
|
184
242
|
destroy() {
|
|
185
243
|
if (!this.installed) return;
|
|
@@ -199,19 +257,32 @@ var TimingController = class {
|
|
|
199
257
|
Element.prototype.animate = this._origAnimate;
|
|
200
258
|
this._origAnimate = null;
|
|
201
259
|
}
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this._clearInterval(this.animObserver);
|
|
206
|
-
this.animObserver = null;
|
|
260
|
+
if (this.animPollId) {
|
|
261
|
+
this._caf(this.animPollId);
|
|
262
|
+
this.animPollId = 0;
|
|
207
263
|
}
|
|
208
264
|
try {
|
|
209
265
|
for (const anim of document.getAnimations()) {
|
|
210
|
-
|
|
266
|
+
const tracked = this.trackedAnims.get(anim);
|
|
267
|
+
anim.playbackRate = tracked?.original ?? 1;
|
|
211
268
|
}
|
|
212
269
|
} catch {
|
|
213
270
|
}
|
|
271
|
+
try {
|
|
272
|
+
document.querySelectorAll("video, audio").forEach((node) => {
|
|
273
|
+
const el = node;
|
|
274
|
+
const tracked = this.trackedMedia.get(el);
|
|
275
|
+
el.playbackRate = tracked?.original ?? 1;
|
|
276
|
+
});
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const gsap = window.gsap;
|
|
281
|
+
if (gsap?.globalTimeline) gsap.globalTimeline.timeScale(1);
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
214
284
|
delete window.__LAPSE_ORIGINAL_RAF__;
|
|
285
|
+
delete window.__saccadeInstalled;
|
|
215
286
|
this.installed = false;
|
|
216
287
|
}
|
|
217
288
|
};
|
|
@@ -281,6 +352,10 @@ var SNAPSHOT_ATTRS = [
|
|
|
281
352
|
"data-hover",
|
|
282
353
|
"data-at-boundary",
|
|
283
354
|
"data-scrubbing",
|
|
355
|
+
"data-starting-style",
|
|
356
|
+
"data-ending-style",
|
|
357
|
+
"data-panel-open",
|
|
358
|
+
"data-hidden",
|
|
284
359
|
"aria-checked",
|
|
285
360
|
"aria-selected",
|
|
286
361
|
"aria-expanded",
|
|
@@ -294,6 +369,7 @@ var SNAPSHOT_ATTRS = [
|
|
|
294
369
|
"checked",
|
|
295
370
|
"disabled",
|
|
296
371
|
"hidden",
|
|
372
|
+
"inert",
|
|
297
373
|
"value",
|
|
298
374
|
"class",
|
|
299
375
|
"style"
|
|
@@ -425,6 +501,8 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
425
501
|
// ---- WAAPI interception --------------------------------------------------
|
|
426
502
|
/** Animations captured via Element.prototype.animate monkey-patch. */
|
|
427
503
|
this.interceptedAnimations = [];
|
|
504
|
+
// ---- Seekable WAAPI clones (created in stopRecording for scrubbing) -----
|
|
505
|
+
this.seekableClones = /* @__PURE__ */ new Map();
|
|
428
506
|
this.hiddenSince = null;
|
|
429
507
|
this.onVisibilityChange = null;
|
|
430
508
|
/** Set to true when the capture loop self-terminates due to limits. */
|
|
@@ -475,6 +553,7 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
475
553
|
this.portalIdCounter = 0;
|
|
476
554
|
this.currentPortalIds.clear();
|
|
477
555
|
this.capturedPortals.clear();
|
|
556
|
+
this.seekableClones.clear();
|
|
478
557
|
this.prevInlineStyles.clear();
|
|
479
558
|
this.jsAnimStartTimes.clear();
|
|
480
559
|
this.jsAnimLastSeen.clear();
|
|
@@ -828,6 +907,13 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
828
907
|
} catch (_) {
|
|
829
908
|
}
|
|
830
909
|
}
|
|
910
|
+
const cleanedKeyframes = keyframes2.map((kf) => {
|
|
911
|
+
const clean = {};
|
|
912
|
+
for (const [k, v] of Object.entries(kf)) {
|
|
913
|
+
if (k !== "computedOffset" && k !== "composite") clean[k] = v;
|
|
914
|
+
}
|
|
915
|
+
return clean;
|
|
916
|
+
});
|
|
831
917
|
this.animations.set(id, {
|
|
832
918
|
id,
|
|
833
919
|
name,
|
|
@@ -839,7 +925,9 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
839
925
|
type,
|
|
840
926
|
source,
|
|
841
927
|
resolvedVars: Object.keys(resolvedVars).length > 0 ? resolvedVars : null,
|
|
842
|
-
conflicts
|
|
928
|
+
conflicts,
|
|
929
|
+
rawKeyframes: cleanedKeyframes,
|
|
930
|
+
rawTiming: { ...timing, fill: "both" }
|
|
843
931
|
});
|
|
844
932
|
}
|
|
845
933
|
const keyframes = a.effect?.getKeyframes?.() || [];
|
|
@@ -1060,96 +1148,83 @@ var _TimelineRecorder = class _TimelineRecorder {
|
|
|
1060
1148
|
blocker.title = "Clear the timeline to interact with the page";
|
|
1061
1149
|
document.body.appendChild(blocker);
|
|
1062
1150
|
this.blockerEl = blocker;
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1081
|
-
allCss += t + "\n";
|
|
1151
|
+
setTimeout(() => {
|
|
1152
|
+
try {
|
|
1153
|
+
const lapseStyle = document.createElement("style");
|
|
1154
|
+
lapseStyle.id = "__lapse-state-rules";
|
|
1155
|
+
const hoverFocusRules = [];
|
|
1156
|
+
for (const sheet of document.styleSheets) {
|
|
1157
|
+
try {
|
|
1158
|
+
const walk = (rules) => {
|
|
1159
|
+
for (const rule of rules) {
|
|
1160
|
+
if (rule.cssRules) {
|
|
1161
|
+
walk(rule.cssRules);
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
const t = rule.cssText;
|
|
1165
|
+
if (t && (t.includes(":hover") || t.includes(":focus"))) {
|
|
1166
|
+
hoverFocusRules.push(t);
|
|
1167
|
+
}
|
|
1082
1168
|
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
walk2(sheet.cssRules);
|
|
1087
|
-
} catch (_) {
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
const stateRegex = /([^{}]*(?::hover|:focus-visible|:focus-within|:focus(?!-))[^{}]*)\{([^{}]*)\}/g;
|
|
1091
|
-
let match;
|
|
1092
|
-
while ((match = stateRegex.exec(allCss)) !== null) {
|
|
1093
|
-
const selector = match[1].trim();
|
|
1094
|
-
const body = match[2].trim();
|
|
1095
|
-
if (!body) continue;
|
|
1096
|
-
const newBody = body.replace(
|
|
1097
|
-
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1098
|
-
(m, prop, val, end) => {
|
|
1099
|
-
if (val.includes("!important")) return m;
|
|
1100
|
-
return prop + ": " + val.trim() + " !important" + end;
|
|
1169
|
+
};
|
|
1170
|
+
walk(sheet.cssRules);
|
|
1171
|
+
} catch (_) {
|
|
1101
1172
|
}
|
|
1102
|
-
);
|
|
1103
|
-
if (selector.includes(":hover")) {
|
|
1104
|
-
lapseStyle.textContent += selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }\n";
|
|
1105
|
-
}
|
|
1106
|
-
if (selector.includes(":focus-visible")) {
|
|
1107
|
-
lapseStyle.textContent += selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
|
|
1108
|
-
} else if (selector.includes(":focus-within")) {
|
|
1109
|
-
lapseStyle.textContent += selector.replace(
|
|
1110
|
-
/:focus-within/g,
|
|
1111
|
-
":has([data-lapse-focus])"
|
|
1112
|
-
) + " { " + newBody + " }\n";
|
|
1113
|
-
} else if (selector.includes(":focus")) {
|
|
1114
|
-
lapseStyle.textContent += selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }\n";
|
|
1115
1173
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (SAFE_PROPS_SET.has(prop)) {
|
|
1130
|
-
el.style.setProperty(prop, value, "important");
|
|
1131
|
-
}
|
|
1174
|
+
const parts = [];
|
|
1175
|
+
for (const ruleText of hoverFocusRules) {
|
|
1176
|
+
const braceIdx = ruleText.indexOf("{");
|
|
1177
|
+
if (braceIdx === -1) continue;
|
|
1178
|
+
const selector = ruleText.slice(0, braceIdx).trim();
|
|
1179
|
+
const bodyEnd = ruleText.lastIndexOf("}");
|
|
1180
|
+
const body = ruleText.slice(braceIdx + 1, bodyEnd).trim();
|
|
1181
|
+
if (!body) continue;
|
|
1182
|
+
const newBody = body.replace(
|
|
1183
|
+
/([^;:]+):\s*([^;]+)(;|$)/g,
|
|
1184
|
+
(m, prop, val, end) => {
|
|
1185
|
+
if (val.includes("!important")) return m;
|
|
1186
|
+
return prop + ": " + val.trim() + " !important" + end;
|
|
1132
1187
|
}
|
|
1188
|
+
);
|
|
1189
|
+
if (selector.includes(":hover")) {
|
|
1190
|
+
parts.push(selector.replace(/:hover/g, "[data-lapse-hover]") + " { " + newBody + " }");
|
|
1133
1191
|
}
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
el.className = value;
|
|
1141
|
-
} else if (attr === "style") {
|
|
1142
|
-
} else if (attr === "value" && value != null) {
|
|
1143
|
-
;
|
|
1144
|
-
el.value = value;
|
|
1145
|
-
} else if (value == null) {
|
|
1146
|
-
el.removeAttribute(attr);
|
|
1147
|
-
} else {
|
|
1148
|
-
el.setAttribute(attr, value);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1192
|
+
if (selector.includes(":focus-visible")) {
|
|
1193
|
+
parts.push(selector.replace(/:focus-visible/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1194
|
+
} else if (selector.includes(":focus-within")) {
|
|
1195
|
+
parts.push(selector.replace(/:focus-within/g, ":has([data-lapse-focus])") + " { " + newBody + " }");
|
|
1196
|
+
} else if (selector.includes(":focus")) {
|
|
1197
|
+
parts.push(selector.replace(/:focus(?!-)/g, "[data-lapse-focus]") + " { " + newBody + " }");
|
|
1151
1198
|
}
|
|
1152
1199
|
}
|
|
1200
|
+
lapseStyle.textContent = parts.join("\n");
|
|
1201
|
+
document.head.appendChild(lapseStyle);
|
|
1202
|
+
this.lapseStyleEl = lapseStyle;
|
|
1203
|
+
} catch (_) {
|
|
1204
|
+
}
|
|
1205
|
+
}, 0);
|
|
1206
|
+
this.seekableClones.clear();
|
|
1207
|
+
for (const [animId, animInfo] of this.animations) {
|
|
1208
|
+
if (!animInfo.rawKeyframes?.length || !animInfo.rawTiming) continue;
|
|
1209
|
+
if (animInfo.type === "JSAnimation") continue;
|
|
1210
|
+
const firstColon = animId.indexOf(":");
|
|
1211
|
+
const secondColon = animId.indexOf(":", firstColon + 1);
|
|
1212
|
+
const elSelector = secondColon >= 0 ? animId.substring(secondColon + 1) : "";
|
|
1213
|
+
const el = this.elements.get(elSelector);
|
|
1214
|
+
if (!el?.isConnected) continue;
|
|
1215
|
+
try {
|
|
1216
|
+
const clone = el.animate(animInfo.rawKeyframes, {
|
|
1217
|
+
...animInfo.rawTiming,
|
|
1218
|
+
fill: "both"
|
|
1219
|
+
});
|
|
1220
|
+
clone.pause();
|
|
1221
|
+
clone.currentTime = 0;
|
|
1222
|
+
this.seekableClones.set(animId, {
|
|
1223
|
+
animation: clone,
|
|
1224
|
+
element: el,
|
|
1225
|
+
effect: clone.effect
|
|
1226
|
+
});
|
|
1227
|
+
} catch (_) {
|
|
1153
1228
|
}
|
|
1154
1229
|
}
|
|
1155
1230
|
const duration = this.frames.length > 0 ? this.frames[this.frames.length - 1].time : 0;
|
|
@@ -1172,6 +1247,8 @@ var TimelineRecorder = _TimelineRecorder;
|
|
|
1172
1247
|
// src/core/scrubber.ts
|
|
1173
1248
|
var TimelineScrubber = class {
|
|
1174
1249
|
constructor(state) {
|
|
1250
|
+
/** Precomputed frame range per animation for O(1) before/after lookup. */
|
|
1251
|
+
this.animFrameRanges = /* @__PURE__ */ new Map();
|
|
1175
1252
|
/** Saved originals for restore on release */
|
|
1176
1253
|
this._originalAnimate = null;
|
|
1177
1254
|
this._originalRaf = null;
|
|
@@ -1181,36 +1258,24 @@ var TimelineScrubber = class {
|
|
|
1181
1258
|
this.frames = state.frames;
|
|
1182
1259
|
this.capturedPortals = state.capturedPortals;
|
|
1183
1260
|
this.interceptedAnimations = state.interceptedAnimations;
|
|
1184
|
-
this.
|
|
1261
|
+
this.seekableClones = state.seekableClones;
|
|
1185
1262
|
this._originalAnimate = window.__LAPSE_ORIGINAL_ANIMATE__ ?? null;
|
|
1186
1263
|
this._originalRaf = window.__LAPSE_ORIGINAL_RAF__ ?? null;
|
|
1187
1264
|
this._originalRemoveChild = window.__LAPSE_TIMELINE__?._removeChild ?? null;
|
|
1188
1265
|
this._originalRemove = window.__LAPSE_TIMELINE__?._remove ?? null;
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
let current = el;
|
|
1198
|
-
for (let i = 0; i < 5 && current && current.tagName && current.tagName !== "HTML"; i++) {
|
|
1199
|
-
const tag = current.tagName.toLowerCase();
|
|
1200
|
-
const parent = current.parentElement;
|
|
1201
|
-
if (parent) {
|
|
1202
|
-
const siblings = Array.from(parent.children);
|
|
1203
|
-
const idx = siblings.indexOf(current) + 1;
|
|
1204
|
-
parts.unshift(`${tag}:nth-child(${idx})`);
|
|
1205
|
-
} else {
|
|
1206
|
-
parts.unshift(tag);
|
|
1266
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
1267
|
+
for (const fa of this.frames[i].animations) {
|
|
1268
|
+
const range = this.animFrameRanges.get(fa.animationId);
|
|
1269
|
+
if (!range) {
|
|
1270
|
+
this.animFrameRanges.set(fa.animationId, { first: i, last: i });
|
|
1271
|
+
} else {
|
|
1272
|
+
range.last = i;
|
|
1273
|
+
}
|
|
1207
1274
|
}
|
|
1208
|
-
current = parent;
|
|
1209
1275
|
}
|
|
1210
|
-
return parts.join(" > ");
|
|
1211
1276
|
}
|
|
1212
1277
|
// ---------------------------------------------------------------------------
|
|
1213
|
-
// seekTo — scrub
|
|
1278
|
+
// seekTo — scrub to a specific timestamp using WAAPI-native seeking
|
|
1214
1279
|
// ---------------------------------------------------------------------------
|
|
1215
1280
|
seekTo(timeMs) {
|
|
1216
1281
|
if (!this.frames.length) return;
|
|
@@ -1258,6 +1323,34 @@ var TimelineScrubber = class {
|
|
|
1258
1323
|
}
|
|
1259
1324
|
}
|
|
1260
1325
|
}
|
|
1326
|
+
const activeAnimIds = /* @__PURE__ */ new Map();
|
|
1327
|
+
for (const fa of frame.animations || []) {
|
|
1328
|
+
activeAnimIds.set(fa.animationId, fa);
|
|
1329
|
+
}
|
|
1330
|
+
for (const [animId, clone] of this.seekableClones) {
|
|
1331
|
+
const frameAnim = activeAnimIds.get(animId);
|
|
1332
|
+
try {
|
|
1333
|
+
if (frameAnim) {
|
|
1334
|
+
if (!clone.animation.effect) {
|
|
1335
|
+
clone.animation.effect = clone.effect;
|
|
1336
|
+
}
|
|
1337
|
+
clone.animation.currentTime = frameAnim.currentTime;
|
|
1338
|
+
} else {
|
|
1339
|
+
const range = this.animFrameRanges.get(animId);
|
|
1340
|
+
if (!range || lo < range.first) {
|
|
1341
|
+
clone.animation.effect = null;
|
|
1342
|
+
} else {
|
|
1343
|
+
if (!clone.animation.effect) {
|
|
1344
|
+
clone.animation.effect = clone.effect;
|
|
1345
|
+
}
|
|
1346
|
+
const timing = clone.effect.getTiming();
|
|
1347
|
+
const endTime = (typeof timing.duration === "number" ? timing.duration : 0) + (timing.delay || 0);
|
|
1348
|
+
clone.animation.currentTime = endTime;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1261
1354
|
for (const entry of this.interceptedAnimations) {
|
|
1262
1355
|
try {
|
|
1263
1356
|
const anim = entry.animation;
|
|
@@ -1272,34 +1365,13 @@ var TimelineScrubber = class {
|
|
|
1272
1365
|
const el = this.elements.get(sel);
|
|
1273
1366
|
if (!el || !el.isConnected) continue;
|
|
1274
1367
|
if (el.closest?.("[data-lapse-panel]")) continue;
|
|
1275
|
-
const hasAnimation = (frame.animations || []).some(
|
|
1276
|
-
(a) => a.animationId.endsWith(":" + sel) || a.animationId.includes(":" + sel.split(" > ").pop())
|
|
1277
|
-
);
|
|
1278
1368
|
const snapTyped = snap;
|
|
1279
|
-
if (snapTyped.__styles) {
|
|
1280
|
-
for (const [prop, value] of Object.entries(snapTyped.__styles)) {
|
|
1281
|
-
if (this.SAFE_PROPS_SET.has(prop) || hasAnimation) {
|
|
1282
|
-
el.style.setProperty(prop, value, "important");
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
1369
|
if (snapTyped.__attrs) {
|
|
1287
1370
|
for (const [attr, value] of Object.entries(snapTyped.__attrs)) {
|
|
1371
|
+
if (attr === "class" || attr === "style") continue;
|
|
1288
1372
|
if (attr === "checked") {
|
|
1289
1373
|
;
|
|
1290
1374
|
el.checked = value === "true";
|
|
1291
|
-
} else if (attr === "class") {
|
|
1292
|
-
if (value != null) el.className = value;
|
|
1293
|
-
} else if (attr === "style") {
|
|
1294
|
-
if (value) {
|
|
1295
|
-
el.setAttribute("style", value);
|
|
1296
|
-
el.style.transition = "none";
|
|
1297
|
-
if (snapTyped.__styles) {
|
|
1298
|
-
for (const [prop, val] of Object.entries(snapTyped.__styles)) {
|
|
1299
|
-
el.style.setProperty(prop, val, "important");
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
1375
|
} else if (attr === "value") {
|
|
1304
1376
|
if (value != null) el.value = value;
|
|
1305
1377
|
} else if (value == null) {
|
|
@@ -1310,39 +1382,31 @@ var TimelineScrubber = class {
|
|
|
1310
1382
|
}
|
|
1311
1383
|
}
|
|
1312
1384
|
}
|
|
1313
|
-
for (const
|
|
1314
|
-
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1385
|
+
for (const fa of frame.animations || []) {
|
|
1386
|
+
if (!fa.animationId.startsWith("JSAnimation:")) continue;
|
|
1387
|
+
const firstColon = fa.animationId.indexOf(":");
|
|
1388
|
+
const secondColon = fa.animationId.indexOf(":", firstColon + 1);
|
|
1389
|
+
const elSel = secondColon >= 0 ? fa.animationId.substring(secondColon + 1) : "";
|
|
1390
|
+
const el = this.elements.get(elSel);
|
|
1391
|
+
if (!el || !el.isConnected) continue;
|
|
1392
|
+
for (const prop of fa.properties) {
|
|
1320
1393
|
if (prop.value) {
|
|
1321
|
-
|
|
1394
|
+
el.style.setProperty(prop.property, prop.value, "important");
|
|
1322
1395
|
}
|
|
1323
1396
|
}
|
|
1324
1397
|
}
|
|
1325
|
-
const animatedSels = /* @__PURE__ */ new Set();
|
|
1326
|
-
for (const anim of frame.animations || []) {
|
|
1327
|
-
const fc = anim.animationId.indexOf(":");
|
|
1328
|
-
const sc = anim.animationId.indexOf(":", fc + 1);
|
|
1329
|
-
if (sc >= 0) animatedSels.add(anim.animationId.substring(sc + 1));
|
|
1330
|
-
}
|
|
1331
|
-
document.querySelectorAll(".checkbox-indicator, .radio-indicator").forEach((rawEl) => {
|
|
1332
|
-
const el = rawEl;
|
|
1333
|
-
const sel = this.getSelector(el);
|
|
1334
|
-
if (sel && !animatedSels.has(sel)) {
|
|
1335
|
-
el.style.removeProperty("opacity");
|
|
1336
|
-
el.style.removeProperty("transform");
|
|
1337
|
-
el.style.removeProperty("filter");
|
|
1338
|
-
el.style.removeProperty("stroke-dashoffset");
|
|
1339
|
-
}
|
|
1340
|
-
});
|
|
1341
1398
|
}
|
|
1342
1399
|
// ---------------------------------------------------------------------------
|
|
1343
1400
|
// release — tear down all scrub state and restore the page to normal
|
|
1344
1401
|
// ---------------------------------------------------------------------------
|
|
1345
1402
|
release() {
|
|
1403
|
+
for (const [, clone] of this.seekableClones) {
|
|
1404
|
+
try {
|
|
1405
|
+
clone.animation.cancel();
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
this.seekableClones.clear();
|
|
1346
1410
|
for (const entry of this.interceptedAnimations) {
|
|
1347
1411
|
try {
|
|
1348
1412
|
entry.animation.cancel();
|
|
@@ -1404,6 +1468,7 @@ var TimelineScrubber = class {
|
|
|
1404
1468
|
this.elements.clear();
|
|
1405
1469
|
this.frames.length = 0;
|
|
1406
1470
|
this.capturedPortals.clear();
|
|
1471
|
+
this.animFrameRanges.clear();
|
|
1407
1472
|
}
|
|
1408
1473
|
};
|
|
1409
1474
|
|
|
@@ -1630,7 +1695,7 @@ function formatExportForLLM(exp, detail = "standard") {
|
|
|
1630
1695
|
}
|
|
1631
1696
|
|
|
1632
1697
|
// src/core/engine.ts
|
|
1633
|
-
var
|
|
1698
|
+
var SaccadeEngine = class {
|
|
1634
1699
|
constructor() {
|
|
1635
1700
|
this.timing = new TimingController();
|
|
1636
1701
|
this.recorder = new TimelineRecorder();
|
|
@@ -1672,14 +1737,33 @@ var LapseEngine = class {
|
|
|
1672
1737
|
boundingBox: null
|
|
1673
1738
|
};
|
|
1674
1739
|
}
|
|
1675
|
-
|
|
1740
|
+
let capture;
|
|
1741
|
+
try {
|
|
1742
|
+
capture = this.recorder.stopRecording();
|
|
1743
|
+
} catch (e) {
|
|
1744
|
+
if (typeof console !== "undefined") console.error("[Saccade] stopRecording failed:", e);
|
|
1745
|
+
document.getElementById("__lapse-scrub-blocker")?.remove();
|
|
1746
|
+
document.getElementById("__lapse-no-transitions")?.remove();
|
|
1747
|
+
document.getElementById("__lapse-state-rules")?.remove();
|
|
1748
|
+
this._state = "idle";
|
|
1749
|
+
this.notify();
|
|
1750
|
+
return {
|
|
1751
|
+
startTime: 0,
|
|
1752
|
+
endTime: 0,
|
|
1753
|
+
duration: 0,
|
|
1754
|
+
animations: [],
|
|
1755
|
+
frames: [],
|
|
1756
|
+
boundingBox: null
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1676
1759
|
this.capture = capture;
|
|
1677
1760
|
const scrubberState = {
|
|
1678
1761
|
elements: this.recorder.elements,
|
|
1679
1762
|
frames: capture.frames,
|
|
1680
1763
|
capturedPortals: this.recorder.capturedPortalIds,
|
|
1681
1764
|
interceptedAnimations: this.recorder.interceptedAnimations,
|
|
1682
|
-
SAFE_PROPS_SET: this.recorder.SAFE_PROPS_SET
|
|
1765
|
+
SAFE_PROPS_SET: this.recorder.SAFE_PROPS_SET,
|
|
1766
|
+
seekableClones: this.recorder.seekableClones
|
|
1683
1767
|
};
|
|
1684
1768
|
this.scrubber = new TimelineScrubber(scrubberState);
|
|
1685
1769
|
this._state = "scrubbing";
|
|
@@ -1732,23 +1816,23 @@ var LapseEngine = class {
|
|
|
1732
1816
|
}
|
|
1733
1817
|
};
|
|
1734
1818
|
|
|
1735
|
-
// src/react/
|
|
1819
|
+
// src/react/SaccadeContext.tsx
|
|
1736
1820
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1737
|
-
var
|
|
1738
|
-
function
|
|
1821
|
+
var SaccadeContext = (0, import_react.createContext)(null);
|
|
1822
|
+
function SaccadeProvider({ children }) {
|
|
1739
1823
|
const engineRef = (0, import_react.useRef)(null);
|
|
1740
1824
|
if (!engineRef.current) {
|
|
1741
|
-
engineRef.current = new
|
|
1825
|
+
engineRef.current = new SaccadeEngine();
|
|
1742
1826
|
}
|
|
1743
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1827
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SaccadeContext.Provider, { value: engineRef.current, children });
|
|
1744
1828
|
}
|
|
1745
|
-
function
|
|
1746
|
-
const engine = (0, import_react.useContext)(
|
|
1747
|
-
if (!engine) throw new Error("
|
|
1829
|
+
function useSaccadeEngine() {
|
|
1830
|
+
const engine = (0, import_react.useContext)(SaccadeContext);
|
|
1831
|
+
if (!engine) throw new Error("useSaccadeEngine must be used within <SaccadeProvider>");
|
|
1748
1832
|
return engine;
|
|
1749
1833
|
}
|
|
1750
1834
|
|
|
1751
|
-
// src/react/
|
|
1835
|
+
// src/react/SaccadePanel.tsx
|
|
1752
1836
|
var import_react6 = require("react");
|
|
1753
1837
|
|
|
1754
1838
|
// src/react/Timeline.tsx
|
|
@@ -2142,7 +2226,7 @@ function SpeedControl({ speed, isPaused, onSetSpeed, onTogglePause }) {
|
|
|
2142
2226
|
var import_react4 = require("react");
|
|
2143
2227
|
var DETAIL_LEVELS = ["compact", "standard", "detailed", "forensic"];
|
|
2144
2228
|
function useTimeline() {
|
|
2145
|
-
const engine =
|
|
2229
|
+
const engine = useSaccadeEngine();
|
|
2146
2230
|
const state = (0, import_react4.useSyncExternalStore)(
|
|
2147
2231
|
(cb) => engine.subscribe(cb),
|
|
2148
2232
|
() => engine.state
|
|
@@ -2176,9 +2260,13 @@ function useTimeline() {
|
|
|
2176
2260
|
[engine]
|
|
2177
2261
|
);
|
|
2178
2262
|
const stopRecording = (0, import_react4.useCallback)(() => {
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2263
|
+
try {
|
|
2264
|
+
const result = engine.stopRecording();
|
|
2265
|
+
setCapture(result);
|
|
2266
|
+
setScrubTime(0);
|
|
2267
|
+
} catch (e) {
|
|
2268
|
+
console.error("[Saccade] stopRecording failed:", e);
|
|
2269
|
+
}
|
|
2182
2270
|
}, [engine]);
|
|
2183
2271
|
const seek = (0, import_react4.useCallback)(
|
|
2184
2272
|
(timeMs) => {
|
|
@@ -2247,7 +2335,7 @@ function useTimeline() {
|
|
|
2247
2335
|
// src/react/useSpeed.ts
|
|
2248
2336
|
var import_react5 = require("react");
|
|
2249
2337
|
function useSpeed() {
|
|
2250
|
-
const engine =
|
|
2338
|
+
const engine = useSaccadeEngine();
|
|
2251
2339
|
const [speed, setSpeedState] = (0, import_react5.useState)(1);
|
|
2252
2340
|
const [previousSpeed, setPreviousSpeed] = (0, import_react5.useState)(1);
|
|
2253
2341
|
const [isPaused, setIsPaused] = (0, import_react5.useState)(false);
|
|
@@ -2310,9 +2398,9 @@ function useSpeed() {
|
|
|
2310
2398
|
return { speed, isPaused, setSpeed, togglePause };
|
|
2311
2399
|
}
|
|
2312
2400
|
|
|
2313
|
-
// src/react/
|
|
2401
|
+
// src/react/SaccadePanel.tsx
|
|
2314
2402
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
2315
|
-
function
|
|
2403
|
+
function SaccadePanel() {
|
|
2316
2404
|
const timeline = useTimeline();
|
|
2317
2405
|
const { speed, isPaused, setSpeed, togglePause } = useSpeed();
|
|
2318
2406
|
const panelRef = (0, import_react6.useRef)(null);
|
|
@@ -2772,17 +2860,18 @@ var PANEL_STYLES = (
|
|
|
2772
2860
|
`
|
|
2773
2861
|
);
|
|
2774
2862
|
|
|
2775
|
-
// src/react/
|
|
2863
|
+
// src/react/Saccade.tsx
|
|
2776
2864
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2777
|
-
function
|
|
2778
|
-
const hostRef = (0, import_react7.useRef)(null);
|
|
2865
|
+
function Saccade({ position = "bottom-left" }) {
|
|
2779
2866
|
const [shadowRoot, setShadowRoot] = (0, import_react7.useState)(null);
|
|
2867
|
+
const hostRef = (0, import_react7.useRef)(null);
|
|
2780
2868
|
(0, import_react7.useEffect)(() => {
|
|
2781
|
-
const host =
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2869
|
+
const host = document.createElement("div");
|
|
2870
|
+
host.setAttribute("data-lapse-panel", "");
|
|
2871
|
+
const positionOffset = position === "top-left" ? "top:20px;left:20px" : position === "top-right" ? "top:20px;right:20px" : position === "bottom-left" ? "bottom:20px;left:20px" : "bottom:20px;right:20px";
|
|
2872
|
+
host.style.cssText = `position:fixed;z-index:2147483647;pointer-events:auto;${positionOffset}`;
|
|
2873
|
+
document.body.appendChild(host);
|
|
2874
|
+
hostRef.current = host;
|
|
2786
2875
|
const shadow = host.attachShadow({ mode: "open" });
|
|
2787
2876
|
const style = document.createElement("style");
|
|
2788
2877
|
style.textContent = PANEL_STYLES;
|
|
@@ -2790,33 +2879,23 @@ function Lapse({ position = "bottom-left" }) {
|
|
|
2790
2879
|
const mount = document.createElement("div");
|
|
2791
2880
|
shadow.appendChild(mount);
|
|
2792
2881
|
setShadowRoot(shadow);
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
zIndex: 2147483647,
|
|
2803
|
-
// max int — must sit above the scrub blocker (z-index: 999999)
|
|
2804
|
-
pointerEvents: "auto",
|
|
2805
|
-
...positionOffset
|
|
2806
|
-
},
|
|
2807
|
-
children: shadowRoot && (0, import_react_dom.createPortal)(
|
|
2808
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LapseProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LapsePanel, {}) }),
|
|
2809
|
-
shadowRoot.lastElementChild || shadowRoot
|
|
2810
|
-
)
|
|
2811
|
-
}
|
|
2882
|
+
return () => {
|
|
2883
|
+
host.remove();
|
|
2884
|
+
hostRef.current = null;
|
|
2885
|
+
};
|
|
2886
|
+
}, [position]);
|
|
2887
|
+
if (!shadowRoot) return null;
|
|
2888
|
+
return (0, import_react_dom.createPortal)(
|
|
2889
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaccadePanel, {}) }),
|
|
2890
|
+
shadowRoot.lastElementChild || shadowRoot
|
|
2812
2891
|
);
|
|
2813
2892
|
}
|
|
2814
2893
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2815
2894
|
0 && (module.exports = {
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2895
|
+
Saccade,
|
|
2896
|
+
SaccadeEngine,
|
|
2897
|
+
SaccadeProvider,
|
|
2898
|
+
useSaccadeEngine,
|
|
2820
2899
|
useSpeed,
|
|
2821
2900
|
useTimeline
|
|
2822
2901
|
});
|