@needle-tools/engine 5.1.0-alpha.8 → 5.1.0-beta
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/CHANGELOG.md +17 -0
- package/components.needle.json +1 -1
- package/dist/{materialx-CE2sUv2B.umd.cjs → materialx-B47Bz-xs.umd.cjs} +1 -1
- package/dist/{materialx-u1EqYrhu.min.js → materialx-B85WjP7E.min.js} +1 -1
- package/dist/{materialx-vyB2Zbt4.js → materialx-jkHmVPez.js} +1 -1
- package/dist/{needle-engine.bundle-3LOy9tj8.js → needle-engine.bundle-BWkDSRyF.js} +6248 -6001
- package/dist/{needle-engine.bundle-BuI2pjv1.min.js → needle-engine.bundle-BvgS8xMP.min.js} +171 -171
- package/dist/{needle-engine.bundle-DQy9vQdJ.umd.cjs → needle-engine.bundle-CdVr_iIe.umd.cjs} +142 -142
- package/dist/needle-engine.d.ts +359 -46
- package/dist/needle-engine.js +618 -606
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-ButPLzor.umd.cjs → vendor-1UvpPPSB.umd.cjs} +2 -2
- package/dist/{vendor-DqZC4Is7.min.js → vendor-BQ2Vuntm.min.js} +4 -4
- package/dist/{vendor-DkWSNjMV.js → vendor-BslSKZPo.js} +113 -106
- package/lib/engine/api.d.ts +1 -1
- package/lib/engine/api.js +1 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_audio.d.ts +30 -1
- package/lib/engine/engine_audio.js +57 -13
- package/lib/engine/engine_audio.js.map +1 -1
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +72 -72
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_math.d.ts +1 -1
- package/lib/engine/engine_math.js +10 -4
- package/lib/engine/engine_math.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_scenedata.js +7 -0
- package/lib/engine/engine_scenedata.js.map +1 -1
- package/lib/engine/engine_serialization_core.js +7 -2
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_time_utils.d.ts +10 -6
- package/lib/engine/engine_time_utils.js +10 -6
- package/lib/engine/engine_time_utils.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +46 -2
- package/lib/engine-components/AudioSource.js +268 -60
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Component.js +2 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.js +2 -2
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DragControlsConstraints.d.ts +0 -15
- package/lib/engine-components/DragControlsConstraints.js +14 -8
- package/lib/engine-components/DragControlsConstraints.js.map +1 -1
- package/lib/engine-components/EventList.js +1 -1
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +2 -0
- package/lib/engine-components/SceneSwitcher.js +43 -16
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/SeeThrough.d.ts +39 -0
- package/lib/engine-components/SeeThrough.js +69 -1
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +10 -0
- package/lib/engine-components/codegen/components.js +10 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/timeline/PlayableDirector.js +1 -1
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +33 -0
- package/lib/engine-components/web/ScrollFollow.js +164 -22
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/needle-app.d.ts +18 -0
- package/lib/needle-app.js +19 -0
- package/lib/needle-app.js.map +1 -0
- package/package.json +3 -2
- package/plugins/common/license.js +25 -4
- package/plugins/common/needle-engine.js +5 -2
- package/plugins/types/userconfig.d.ts +22 -0
- package/plugins/vite/dts-generator.d.ts +24 -0
- package/plugins/vite/dts-generator.js +40 -7
- package/plugins/vite/index.d.ts +2 -0
- package/plugins/vite/index.js +17 -0
- package/plugins/vite/license.js +4 -4
- package/plugins/vite/needle-app.js +78 -5
- package/plugins/vite/remote.d.ts +28 -0
- package/plugins/vite/remote.js +124 -0
- package/src/engine/api.ts +1 -1
- package/src/engine/engine_audio.ts +68 -15
- package/src/engine/engine_gameobject.ts +1 -1
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_license.ts +69 -69
- package/src/engine/engine_math.ts +11 -4
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_scenedata.ts +7 -2
- package/src/engine/engine_serialization_core.ts +7 -2
- package/src/engine/engine_time_utils.ts +10 -6
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine-components/AudioSource.ts +256 -55
- package/src/engine-components/Component.ts +2 -1
- package/src/engine-components/DragControls.ts +3 -2
- package/src/engine-components/DragControlsConstraints.ts +16 -8
- package/src/engine-components/EventList.ts +1 -1
- package/src/engine-components/SceneSwitcher.ts +37 -15
- package/src/engine-components/SeeThrough.ts +75 -1
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +10 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/timeline/PlayableDirector.ts +1 -1
- package/src/engine-components/web/ScrollFollow.ts +168 -22
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
// For firefox ViewTimeline support — dynamic import to avoid SSR crashes (polyfill accesses window at module level)
|
|
2
2
|
if (typeof window !== "undefined") import("scroll-timeline-polyfill/dist/scroll-timeline.js");
|
|
3
3
|
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// NOTE / TODO — ScrollMarker tracking vs. the parent window (useTopWindowScroll)
|
|
6
|
+
//
|
|
7
|
+
// The ScrollMarker feature (see handleTimelineTarget) currently relies on `ViewTimeline` to know
|
|
8
|
+
// how far a marker element has travelled through the viewport. This import polyfills `ViewTimeline`
|
|
9
|
+
// on THIS window only — it never patches the parent/top window. So when `useTopWindowScroll` is on
|
|
10
|
+
// and we resolve markers against the host page (top document), tracking only works if the *host*
|
|
11
|
+
// window also has `ViewTimeline`:
|
|
12
|
+
// - Chromium host (native ViewTimeline) ............. works
|
|
13
|
+
// - Firefox/Safari host without the polyfill ........ does NOT work → falls back to linear scroll
|
|
14
|
+
// - cross-origin host ............................... impossible (no access to top.document at all)
|
|
15
|
+
// We cannot reliably install the polyfill onto a (same-origin) parent from inside the iframe — it is
|
|
16
|
+
// designed to patch its own realm at import time, and doing it cross-realm is fragile.
|
|
17
|
+
//
|
|
18
|
+
// POSSIBLE CHANGE: drop `ViewTimeline` entirely and compute the marker's viewport progress manually
|
|
19
|
+
// with getBoundingClientRect, e.g. for the default "cover 0%→100%" range:
|
|
20
|
+
// const rect = el.getBoundingClientRect();
|
|
21
|
+
// const vh = scroller.clientHeight; // top's viewport when useTopWindowScroll
|
|
22
|
+
// const t01 = (vh - rect.top) / (vh + rect.height);
|
|
23
|
+
// This is mathematically equivalent to the current ViewTimeline values (0 = entering from bottom,
|
|
24
|
+
// 0.5 = centered, 1 = exited past top — matches the existing `1 - |t-0.5|*2` weighting).
|
|
25
|
+
// PROS: works with top/parent content in every browser (same-origin); removes the
|
|
26
|
+
// scroll-timeline-polyfill dependency and this SSR-risky top-level import; cheaper
|
|
27
|
+
// (no ViewTimeline objects / WeakMap per marker). `ViewTimeline` is used ONLY in this file.
|
|
28
|
+
// CONS: re-implements view-progress ourselves instead of leaning on the platform/polyfill, so any
|
|
29
|
+
// future spec range features (cover/contain/entry/exit, named ranges) would have to be
|
|
30
|
+
// reproduced by hand; needs careful re-testing against the existing scrollytelling demos.
|
|
31
|
+
// NOTE: cross-origin embeds still cannot work either way — that needs a postMessage scroll bridge.
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
4
34
|
import { Box3, Object3D } from "three";
|
|
5
35
|
|
|
6
36
|
import { isDevEnvironment } from "../../engine/debug/debug.js";
|
|
7
37
|
import { Mathf } from "../../engine/engine_math.js";
|
|
8
38
|
import { serializable } from "../../engine/engine_serialization.js";
|
|
9
39
|
import { getBoundingBox, setVisibleInCustomShadowRendering } from "../../engine/engine_three_utils.js";
|
|
40
|
+
import { validate } from "../../engine/engine_util_decorator.js";
|
|
10
41
|
import { getParam } from "../../engine/engine_utils.js";
|
|
11
42
|
import { Animation } from "../Animation.js";
|
|
12
43
|
import { Animator } from "../Animator.js";
|
|
@@ -115,6 +146,25 @@ export class ScrollFollow extends Behaviour {
|
|
|
115
146
|
@serializable()
|
|
116
147
|
mode: "window" = "window";
|
|
117
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Read the scroll position from the **top-level window** instead of the window Needle Engine
|
|
151
|
+
* runs in. This matters when the canvas is embedded in an iframe:
|
|
152
|
+
* - When the page is **not** embedded this has no effect — the top window *is* the current window.
|
|
153
|
+
* - When embedded in a **same-origin** iframe, the component follows the *host page's* scroll,
|
|
154
|
+
* which is almost always the intent (an embedded iframe usually isn't scrollable itself).
|
|
155
|
+
*
|
|
156
|
+
* Set this to `false` to always follow the engine's own window scroll, e.g. when the iframe
|
|
157
|
+
* itself is scrollable and you want to follow its own scroll rather than the host's.
|
|
158
|
+
*
|
|
159
|
+
* If the top window is **cross-origin** (and therefore inaccessible), the component
|
|
160
|
+
* transparently falls back to the local window and logs a warning in development —
|
|
161
|
+
* cross-origin embeds would require a `postMessage` scroll bridge from the host page.
|
|
162
|
+
* @default true
|
|
163
|
+
*/
|
|
164
|
+
@validate()
|
|
165
|
+
@serializable()
|
|
166
|
+
useTopWindowScroll: boolean = true;
|
|
167
|
+
|
|
118
168
|
/**
|
|
119
169
|
* Event fired when the scroll position changes
|
|
120
170
|
*/
|
|
@@ -134,6 +184,13 @@ export class ScrollFollow extends Behaviour {
|
|
|
134
184
|
private _needsUpdate = false;
|
|
135
185
|
private _firstUpdate = false;
|
|
136
186
|
|
|
187
|
+
/** The window whose scroll position currently drives this component (resolved from {@link useTopWindowScroll}). */
|
|
188
|
+
private _scrollWindow: Window | null = null;
|
|
189
|
+
/** The window the `wheel` listener is attached to — kept so we remove it from the same window. */
|
|
190
|
+
private _wheelTarget: Window | null = null;
|
|
191
|
+
private _crossOriginWarned = false;
|
|
192
|
+
private _viewTimelineWarned = false;
|
|
193
|
+
|
|
137
194
|
awake() {
|
|
138
195
|
this._firstUpdate = true;
|
|
139
196
|
}
|
|
@@ -141,14 +198,70 @@ export class ScrollFollow extends Behaviour {
|
|
|
141
198
|
|
|
142
199
|
/** @internal */
|
|
143
200
|
onEnable() {
|
|
144
|
-
|
|
201
|
+
this.rebindScrollListener();
|
|
145
202
|
this._appliedValue = -1;
|
|
146
203
|
this._needsUpdate = true;
|
|
147
204
|
}
|
|
148
205
|
|
|
149
206
|
/** @internal */
|
|
150
207
|
onDisable() {
|
|
151
|
-
|
|
208
|
+
this._wheelTarget?.removeEventListener("wheel", this.updateCurrentScrollValue);
|
|
209
|
+
this._wheelTarget = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** @internal */
|
|
213
|
+
onValidate(property?: string) {
|
|
214
|
+
// React to a runtime change of `useTopWindowScroll` while the component is active.
|
|
215
|
+
if (property === "useTopWindowScroll" && this._wheelTarget) {
|
|
216
|
+
this.rebindScrollListener();
|
|
217
|
+
this._needsUpdate = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolves which browsing context's scroll position should drive this component.
|
|
223
|
+
* Honors {@link useTopWindowScroll}. When the top window is requested but is cross-origin
|
|
224
|
+
* (and therefore inaccessible) it falls back to the local window and warns once —
|
|
225
|
+
* cross-origin embeds would need a `postMessage` scroll bridge from the host page.
|
|
226
|
+
*/
|
|
227
|
+
private resolveScrollWindow(): Window {
|
|
228
|
+
if (!this.useTopWindowScroll) return window;
|
|
229
|
+
const top = window.top;
|
|
230
|
+
// Not embedded, or we already *are* the top window.
|
|
231
|
+
if (!top || top === window.self) return window;
|
|
232
|
+
try {
|
|
233
|
+
// Any property access on a cross-origin top window throws a SecurityError.
|
|
234
|
+
void top.document;
|
|
235
|
+
return top;
|
|
236
|
+
}
|
|
237
|
+
catch (_e) {
|
|
238
|
+
if (!this._crossOriginWarned) {
|
|
239
|
+
this._crossOriginWarned = true;
|
|
240
|
+
if (debug || isDevEnvironment()) {
|
|
241
|
+
console.warn("[ScrollFollow] useTopWindowScroll is enabled but the top-level window is cross-origin and cannot be read. Falling back to the local window. Supporting cross-origin embeds requires a postMessage scroll bridge from the host page.");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return window;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** (Re)resolves the scroll window and moves the `wheel` listener onto it. */
|
|
249
|
+
private rebindScrollListener() {
|
|
250
|
+
const next = this.resolveScrollWindow();
|
|
251
|
+
this._scrollWindow = next;
|
|
252
|
+
if (next === this._wheelTarget) return;
|
|
253
|
+
this._wheelTarget?.removeEventListener("wheel", this.updateCurrentScrollValue);
|
|
254
|
+
this._wheelTarget = next;
|
|
255
|
+
next.addEventListener("wheel", this.updateCurrentScrollValue, { passive: true });
|
|
256
|
+
// The selector cache may point into the previous document — invalidate it.
|
|
257
|
+
this._lastSelectorValue = null;
|
|
258
|
+
this._lastSelectorElement = null;
|
|
259
|
+
if(debug) {
|
|
260
|
+
console.debug(`[ScrollFollow] Scroll listener bound to ${next === window ? "local window" : "top-level window"}`, {
|
|
261
|
+
scrollWindow: this._scrollWindow,
|
|
262
|
+
isTopWindow: next === window,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
152
265
|
}
|
|
153
266
|
|
|
154
267
|
/** @internal */
|
|
@@ -214,26 +327,34 @@ export class ScrollFollow extends Behaviour {
|
|
|
214
327
|
|
|
215
328
|
private updateCurrentScrollValue = () => {
|
|
216
329
|
|
|
330
|
+
const win = this._scrollWindow ?? window;
|
|
331
|
+
const doc = win.document;
|
|
332
|
+
// The page's scrollable extent lives on the root scrolling element (documentElement in
|
|
333
|
+
// standards mode, body in quirks mode) — document.body.scrollHeight is unreliable and is
|
|
334
|
+
// often just the viewport height even when the page is scrolled. scrollingElement resolves
|
|
335
|
+
// to the correct one for the current document mode.
|
|
336
|
+
const scroller = doc.scrollingElement ?? doc.documentElement;
|
|
337
|
+
|
|
217
338
|
switch (this.mode) {
|
|
218
339
|
case "window":
|
|
219
340
|
if (this.htmlSelector?.length) {
|
|
220
341
|
if (this.htmlSelector !== this._lastSelectorValue) {
|
|
221
|
-
this._lastSelectorElement =
|
|
342
|
+
this._lastSelectorElement = doc.querySelector(this.htmlSelector);
|
|
222
343
|
this._lastSelectorValue = this.htmlSelector;
|
|
223
344
|
}
|
|
224
345
|
if (this._lastSelectorElement) {
|
|
225
346
|
const rect = this._lastSelectorElement.getBoundingClientRect();
|
|
226
|
-
this._target_value = -rect.top / (rect.height -
|
|
347
|
+
this._target_value = -rect.top / (rect.height - win.innerHeight);
|
|
227
348
|
break;
|
|
228
349
|
}
|
|
229
350
|
}
|
|
230
351
|
else {
|
|
231
|
-
|
|
232
|
-
|
|
352
|
+
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
|
|
353
|
+
if (maxScroll <= 0) {
|
|
354
|
+
// Page is not scrollable — keep the current value (still allows triggering timelines etc.).
|
|
233
355
|
}
|
|
234
356
|
else {
|
|
235
|
-
|
|
236
|
-
this._target_value = window.scrollY / (diff || 1);
|
|
357
|
+
this._target_value = scroller.scrollTop / maxScroll;
|
|
237
358
|
}
|
|
238
359
|
}
|
|
239
360
|
|
|
@@ -241,6 +362,10 @@ export class ScrollFollow extends Behaviour {
|
|
|
241
362
|
}
|
|
242
363
|
|
|
243
364
|
if (isNaN(this._target_value) || !isFinite(this._target_value)) this._target_value = -1;
|
|
365
|
+
|
|
366
|
+
if (debug) {
|
|
367
|
+
console.debug(`[ScrollFollow] Scroll value updated: ${this._target_value.toFixed(5)} (ScrollTop: ${scroller.scrollTop}, ScrollHeight: ${scroller.scrollHeight}, ClientHeight: ${scroller.clientHeight})`);
|
|
368
|
+
}
|
|
244
369
|
}
|
|
245
370
|
|
|
246
371
|
|
|
@@ -295,6 +420,10 @@ export class ScrollFollow extends Behaviour {
|
|
|
295
420
|
|
|
296
421
|
private handleTimelineTarget(director: PlayableDirector, value: number) {
|
|
297
422
|
|
|
423
|
+
// Resolve markers against the same browsing context we read scroll from (see useTopWindowScroll).
|
|
424
|
+
const scrollWin = this._scrollWindow ?? window;
|
|
425
|
+
const doc = scrollWin.document;
|
|
426
|
+
|
|
298
427
|
const duration = director.duration;
|
|
299
428
|
let markersArray = timelineMarkerArrays.get(director);
|
|
300
429
|
|
|
@@ -314,7 +443,7 @@ export class ScrollFollow extends Behaviour {
|
|
|
314
443
|
marker.needsUpdate = false;
|
|
315
444
|
try {
|
|
316
445
|
// TODO: with this it's currently not possible to remap markers from HTML. For example if I have two sections and I want to now use the marker["center"] multiple times to stay at that marker for a longer time
|
|
317
|
-
marker.element = tryGetElementsForSelector(index) as HTMLElement | null;
|
|
446
|
+
marker.element = tryGetElementsForSelector(index, doc) as HTMLElement | null;
|
|
318
447
|
if (debug) console.debug(`ScrollMarker #${index} (${marker.time.toFixed(2)}) found`, marker.element);
|
|
319
448
|
if (!marker.element) {
|
|
320
449
|
if (debug || isDevEnvironment()) console.warn(`No HTML element found for ScrollMarker: ${marker.name} (index ${index})`);
|
|
@@ -335,7 +464,7 @@ export class ScrollFollow extends Behaviour {
|
|
|
335
464
|
|
|
336
465
|
// If the timeline has no markers defined we can use timeline-marker elements in the DOM. These must define times then
|
|
337
466
|
if (markersArray.length <= 0) {
|
|
338
|
-
const markers =
|
|
467
|
+
const markers = doc.querySelectorAll(`[data-timeline-marker]`);
|
|
339
468
|
markers.forEach((element) => {
|
|
340
469
|
const value = element.getAttribute("data-timeline-marker");
|
|
341
470
|
const time = parseFloat(value || ("NaN"));
|
|
@@ -351,15 +480,32 @@ export class ScrollFollow extends Behaviour {
|
|
|
351
480
|
});
|
|
352
481
|
}
|
|
353
482
|
|
|
354
|
-
// Init ViewTimeline for markers
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
483
|
+
// Init ViewTimeline for markers. Use the constructor from the *owning* window so that
|
|
484
|
+
// markers living in a same-origin host page (useTopWindowScroll) are tracked in the
|
|
485
|
+
// correct realm rather than against the iframe's own (non-scrolling) document.
|
|
486
|
+
const ViewTimelineCtor = (scrollWin as unknown as { ViewTimeline?: new (opts: { subject: Element, axis: string }) => ViewTimeline }).ViewTimeline;
|
|
487
|
+
if (!ViewTimelineCtor) {
|
|
488
|
+
// ViewTimeline isn't available on the target window yet. This is expected transiently
|
|
489
|
+
// on the "self" path while the async scroll-timeline polyfill is still loading, and
|
|
490
|
+
// permanently when useTopWindowScroll targets a host page without ViewTimeline support.
|
|
491
|
+
// Don't persist the half-built marker set — drop the cache so it's rebuilt next frame
|
|
492
|
+
// (self-heals once the polyfill loads), and fall back to linear scrubbing for now.
|
|
493
|
+
if (!this._viewTimelineWarned && (debug || isDevEnvironment())) {
|
|
494
|
+
this._viewTimelineWarned = true;
|
|
495
|
+
console.warn("[ScrollFollow] ViewTimeline is not available on the target window — falling back to linear scroll. If this persists with useTopWindowScroll, the scroll-timeline polyfill must be present on the host page.");
|
|
496
|
+
}
|
|
497
|
+
timelineMarkerArrays.delete(director);
|
|
498
|
+
markersArray.length = 0;
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
for (const marker of markersArray) {
|
|
502
|
+
if (marker.element) {
|
|
503
|
+
// https://scroll-driven-animations.style/tools/view-timeline/ranges
|
|
504
|
+
marker.timeline = new ViewTimelineCtor({
|
|
505
|
+
subject: marker.element,
|
|
506
|
+
axis: 'block', // https://drafts.csswg.org/scroll-animations/#scroll-notation
|
|
507
|
+
});
|
|
508
|
+
}
|
|
363
509
|
}
|
|
364
510
|
}
|
|
365
511
|
}
|
|
@@ -473,7 +619,7 @@ const weightsArray: OverlapInfo[] = [];
|
|
|
473
619
|
const needleScrollMarkerCache = new Array<Element>();
|
|
474
620
|
let needsScrollMarkerRefresh = true;
|
|
475
621
|
|
|
476
|
-
function tryGetElementsForSelector(index: number): Element | null {
|
|
622
|
+
function tryGetElementsForSelector(index: number, doc: Document): Element | null {
|
|
477
623
|
|
|
478
624
|
if (!needsScrollMarkerRefresh) {
|
|
479
625
|
const element = needleScrollMarkerCache[index] || null;
|
|
@@ -481,12 +627,12 @@ function tryGetElementsForSelector(index: number): Element | null {
|
|
|
481
627
|
}
|
|
482
628
|
needsScrollMarkerRefresh = false;
|
|
483
629
|
needleScrollMarkerCache.length = 0;
|
|
484
|
-
const markers =
|
|
630
|
+
const markers = doc.querySelectorAll(`[data-timeline-marker]`);
|
|
485
631
|
markers.forEach((m, i) => {
|
|
486
632
|
needleScrollMarkerCache[i] = m;
|
|
487
633
|
});
|
|
488
634
|
needsScrollMarkerRefresh = false;
|
|
489
|
-
return tryGetElementsForSelector(index);
|
|
635
|
+
return tryGetElementsForSelector(index, doc);
|
|
490
636
|
}
|
|
491
637
|
|
|
492
638
|
|