@devzwo/ngx-magic-scroll 1.0.0 → 1.1.1
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 +133 -0
- package/README.md +1 -1
- package/ai/usage.md +97 -0
- package/docs/concepts/anchors.md +129 -0
- package/docs/concepts/data-readiness-and-restoration.md +102 -0
- package/docs/concepts/position-preservation.md +149 -0
- package/docs/concepts/url-state-and-navigation.md +118 -0
- package/docs/getting-started.md +144 -0
- package/docs/migrations/v0.0.1-rc.1-to-0.0.1-rc.2.md +177 -0
- package/docs/migrations/v0.0.1-rc.2-to-1.0.0.md +176 -0
- package/docs/migrations/v1.0-to-v1.1.md +145 -0
- package/docs/recipes/material-drawer.md +99 -0
- package/docs/recipes/ngrx-signal-store.md +132 -0
- package/docs/recipes/paginated-lists.md +206 -0
- package/docs/recipes/return-to-list.md +110 -0
- package/docs/recipes/rx-resource.md +87 -0
- package/docs/recipes/scroll-to-anchor.md +148 -0
- package/docs/recipes/sticky-master-detail.md +138 -0
- package/docs/recipes/tanstack-query.md +96 -0
- package/fesm2022/devzwo-ngx-magic-scroll.mjs +309 -19
- package/fesm2022/devzwo-ngx-magic-scroll.mjs.map +1 -1
- package/package.json +1 -1
- package/types/devzwo-ngx-magic-scroll.d.ts +118 -8
|
@@ -139,8 +139,13 @@ var ScrollService = class ScrollService {
|
|
|
139
139
|
const isTopInView = rect.top >= containerTop + topOffset - 1 && rect.top < containerTop + viewportHeight;
|
|
140
140
|
const isBottomInView = rect.bottom <= containerTop + viewportHeight && rect.bottom > containerTop;
|
|
141
141
|
if (ignoreWhenInView == "top" && isTopInView || ignoreWhenInView == "full" && isTopInView && isBottomInView || ignoreWhenInView == "always" && (isTopInView || isBottomInView)) return false;
|
|
142
|
+
const displacement = options?.displacement ?? 0;
|
|
143
|
+
const delta = rect.top - containerTop - topOffset - displacement;
|
|
144
|
+
const maxScrollTop = scrollable.scrollHeight > 0 && viewportHeight > 0 ? Math.max(0, scrollable.scrollHeight - viewportHeight) : Infinity;
|
|
145
|
+
const targetScrollTop = Math.min(Math.max(scrollable.scrollTop + delta, 0), maxScrollTop);
|
|
146
|
+
if (Math.abs(targetScrollTop - scrollable.scrollTop) < 1) return true;
|
|
142
147
|
scrollable.scrollTo({
|
|
143
|
-
top:
|
|
148
|
+
top: targetScrollTop,
|
|
144
149
|
behavior
|
|
145
150
|
});
|
|
146
151
|
return true;
|
|
@@ -185,7 +190,8 @@ const defaults = {
|
|
|
185
190
|
debounceTime: 500,
|
|
186
191
|
ignoreWhenInView: "none",
|
|
187
192
|
restoreOnDataChange: true,
|
|
188
|
-
preserveOnResize: true
|
|
193
|
+
preserveOnResize: true,
|
|
194
|
+
preserveOnLayoutChange: false
|
|
189
195
|
};
|
|
190
196
|
function resolveMagicScrollOptions(parent, options) {
|
|
191
197
|
return {
|
|
@@ -202,7 +208,8 @@ function resolveMagicScrollOptions(parent, options) {
|
|
|
202
208
|
debounceTime: options.debounceTime ?? parent.debounceTime,
|
|
203
209
|
ignoreWhenInView: options.ignoreWhenInView ?? parent.ignoreWhenInView,
|
|
204
210
|
restoreOnDataChange: options.restoreOnDataChange ?? parent.restoreOnDataChange,
|
|
205
|
-
preserveOnResize: options.preserveOnResize ?? parent.preserveOnResize
|
|
211
|
+
preserveOnResize: options.preserveOnResize ?? parent.preserveOnResize,
|
|
212
|
+
preserveOnLayoutChange: options.preserveOnLayoutChange ?? parent.preserveOnLayoutChange
|
|
206
213
|
};
|
|
207
214
|
}
|
|
208
215
|
const MAGIC_SCROLL_OPTIONS = new InjectionToken("Magic scroll defaults", {
|
|
@@ -255,6 +262,15 @@ const SCROLL_KEYS = /* @__PURE__ */ new Set([
|
|
|
255
262
|
"Spacebar",
|
|
256
263
|
"Space"
|
|
257
264
|
]);
|
|
265
|
+
function isNaturallyFocusable(el) {
|
|
266
|
+
if (el.hasAttribute("tabindex")) return true;
|
|
267
|
+
const tag = el.tagName.toLowerCase();
|
|
268
|
+
if (tag === "button" || tag === "select" || tag === "textarea") return !el.disabled;
|
|
269
|
+
if (tag === "input") return el.type !== "hidden" && !el.disabled;
|
|
270
|
+
if (tag === "a" || tag === "area") return el.hasAttribute("href");
|
|
271
|
+
if (tag === "iframe") return true;
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
258
274
|
function isEditableTarget(target) {
|
|
259
275
|
if (!target || !(target instanceof Element)) return false;
|
|
260
276
|
const tagName = target.tagName.toLowerCase();
|
|
@@ -285,12 +301,25 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
285
301
|
scrollRevision = signal(0, ...ngDevMode ? [{ debugName: "scrollRevision" }] : /* istanbul ignore next */ []);
|
|
286
302
|
initialized = false;
|
|
287
303
|
userInterrupted = false;
|
|
304
|
+
savedDisplacement = 0;
|
|
288
305
|
pendingDestination = this.initialAnchor ?? null;
|
|
289
306
|
lastProcessedFragment = void 0;
|
|
290
307
|
target;
|
|
291
308
|
timer;
|
|
292
309
|
retryObserver;
|
|
293
310
|
retryTimeoutTimer;
|
|
311
|
+
layoutCorrectionFrameId;
|
|
312
|
+
temporaryTabindexCleanups = /* @__PURE__ */ new Set();
|
|
313
|
+
pendingFocusTarget;
|
|
314
|
+
scrollEndHandler;
|
|
315
|
+
scrollEndTarget;
|
|
316
|
+
focusTimeoutTimer;
|
|
317
|
+
focusInitiator;
|
|
318
|
+
isSmoothNavigating = false;
|
|
319
|
+
pendingLayoutPreservation = false;
|
|
320
|
+
smoothNavEndHandler;
|
|
321
|
+
smoothNavTarget;
|
|
322
|
+
smoothNavTimeoutTimer;
|
|
294
323
|
constructor() {
|
|
295
324
|
const destroyRef = inject(DestroyRef);
|
|
296
325
|
afterNextRender(() => {
|
|
@@ -302,6 +331,11 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
302
331
|
this.document.defaultView?.removeEventListener("resize", this.onResize);
|
|
303
332
|
clearTimeout(this.timer);
|
|
304
333
|
this.cancelDestinationRetry();
|
|
334
|
+
this.cancelScheduledFocus();
|
|
335
|
+
this.cancelLayoutPreservation();
|
|
336
|
+
this.cancelSmoothNavigation();
|
|
337
|
+
for (const cleanup of this.temporaryTabindexCleanups) cleanup();
|
|
338
|
+
this.temporaryTabindexCleanups.clear();
|
|
305
339
|
this.pendingDestination = null;
|
|
306
340
|
});
|
|
307
341
|
afterRenderEffect((onCleanup) => {
|
|
@@ -321,6 +355,55 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
321
355
|
onCleanup(() => observer.disconnect());
|
|
322
356
|
}
|
|
323
357
|
});
|
|
358
|
+
afterRenderEffect((onCleanup) => {
|
|
359
|
+
this.scrollSource();
|
|
360
|
+
if (this.options().preserveOnLayoutChange && typeof ResizeObserver !== "undefined" && typeof window !== "undefined") {
|
|
361
|
+
const observedDimensions = /* @__PURE__ */ new Map();
|
|
362
|
+
const measureAndStore = (el) => {
|
|
363
|
+
const rect = el.getBoundingClientRect();
|
|
364
|
+
const dims = {
|
|
365
|
+
width: rect.width,
|
|
366
|
+
height: rect.height
|
|
367
|
+
};
|
|
368
|
+
observedDimensions.set(el, dims);
|
|
369
|
+
return dims;
|
|
370
|
+
};
|
|
371
|
+
const observer = new ResizeObserver((entries) => {
|
|
372
|
+
let hasRealChange = false;
|
|
373
|
+
if (!entries || !Array.isArray(entries) || entries.length === 0) hasRealChange = true;
|
|
374
|
+
else for (const entry of entries) {
|
|
375
|
+
const target = entry.target;
|
|
376
|
+
const prev = observedDimensions.get(target);
|
|
377
|
+
const rect = target.getBoundingClientRect?.();
|
|
378
|
+
const width = entry.contentRect?.width ?? rect?.width ?? 0;
|
|
379
|
+
const height = entry.contentRect?.height ?? rect?.height ?? 0;
|
|
380
|
+
if (!prev) observedDimensions.set(target, {
|
|
381
|
+
width,
|
|
382
|
+
height
|
|
383
|
+
});
|
|
384
|
+
else if (prev.width !== width || prev.height !== height) {
|
|
385
|
+
observedDimensions.set(target, {
|
|
386
|
+
width,
|
|
387
|
+
height
|
|
388
|
+
});
|
|
389
|
+
hasRealChange = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (hasRealChange) this.scheduleLayoutPreservation();
|
|
393
|
+
});
|
|
394
|
+
measureAndStore(this.host);
|
|
395
|
+
observer.observe(this.host);
|
|
396
|
+
for (const anchor of this.anchors()) {
|
|
397
|
+
measureAndStore(anchor);
|
|
398
|
+
observer.observe(anchor);
|
|
399
|
+
}
|
|
400
|
+
onCleanup(() => {
|
|
401
|
+
observer.disconnect();
|
|
402
|
+
observedDimensions.clear();
|
|
403
|
+
this.cancelLayoutPreservation();
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
});
|
|
324
407
|
afterRenderEffect(() => {
|
|
325
408
|
const source = this.scrollSource();
|
|
326
409
|
if (!readScrollSource(source).ready) return;
|
|
@@ -332,15 +415,14 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
332
415
|
if (this.pendingDestination) this.scheduleDestinationRetry();
|
|
333
416
|
}
|
|
334
417
|
} else {
|
|
335
|
-
this.
|
|
418
|
+
const nearest = this.calculateNearestAnchor() ?? null;
|
|
419
|
+
this.liveAnchor.set(nearest);
|
|
420
|
+
if (nearest) this.updateSavedDisplacement(nearest);
|
|
336
421
|
this.cancelDestinationRetry();
|
|
337
422
|
}
|
|
338
|
-
} else if (!this.userInterrupted) {
|
|
339
|
-
if (!this.tryExecutePendingDestination(this.options().behavior.restoration))
|
|
340
|
-
|
|
341
|
-
else if (source !== NO_SCROLL_SOURCE && this.options().restoreOnDataChange) this.preserve("top");
|
|
342
|
-
}
|
|
343
|
-
}
|
|
423
|
+
} else if (this.pendingDestination && !this.userInterrupted) {
|
|
424
|
+
if (!this.tryExecutePendingDestination(this.options().behavior.restoration)) this.scheduleDestinationRetry();
|
|
425
|
+
} else if (source !== NO_SCROLL_SOURCE && this.options().restoreOnDataChange) this.preserve();
|
|
344
426
|
});
|
|
345
427
|
});
|
|
346
428
|
effect(() => {
|
|
@@ -352,7 +434,12 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
352
434
|
if (prev === void 0) return;
|
|
353
435
|
if (frag) {
|
|
354
436
|
if (!this.isInternalNavigation()) {
|
|
437
|
+
this.cancelScheduledFocus();
|
|
438
|
+
this.cancelLayoutPreservation();
|
|
439
|
+
this.cancelSmoothNavigation();
|
|
440
|
+
this.pendingLayoutPreservation = false;
|
|
355
441
|
this.userInterrupted = false;
|
|
442
|
+
this.savedDisplacement = 0;
|
|
356
443
|
this.pendingDestination = frag;
|
|
357
444
|
const state = readScrollSource(this.scrollSource());
|
|
358
445
|
if (this.initialized && state.ready) {
|
|
@@ -360,6 +447,11 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
360
447
|
} else this.cancelDestinationRetry();
|
|
361
448
|
}
|
|
362
449
|
} else {
|
|
450
|
+
this.cancelScheduledFocus();
|
|
451
|
+
this.cancelLayoutPreservation();
|
|
452
|
+
this.cancelSmoothNavigation();
|
|
453
|
+
this.pendingLayoutPreservation = false;
|
|
454
|
+
this.savedDisplacement = 0;
|
|
363
455
|
this.pendingDestination = null;
|
|
364
456
|
this.cancelDestinationRetry();
|
|
365
457
|
}
|
|
@@ -378,23 +470,44 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
378
470
|
return nav?.extras?.info?.[MAGIC_SCROLL_ORIGIN_KEY] === "scroll-spy" || nav?.trigger !== "popstate" && stateOrigin === "scroll-spy";
|
|
379
471
|
}
|
|
380
472
|
scrollTo(anchor, options = {}) {
|
|
473
|
+
this.refreshTarget();
|
|
474
|
+
this.cancelScheduledFocus();
|
|
475
|
+
const clean = anchor.replace(/^#/, "");
|
|
476
|
+
const targetEl = this.anchors().find((el) => el.id === clean);
|
|
477
|
+
const willScroll = targetEl ? this.willScroll(targetEl, options) : false;
|
|
478
|
+
const behavior = options.behavior ?? this.options().behavior.interaction;
|
|
479
|
+
if (willScroll && this.hasSmoothScrollBehavior(behavior)) this.beginSmoothNavigation();
|
|
480
|
+
else this.cancelSmoothNavigation();
|
|
381
481
|
const executed = this.execute(anchor, {
|
|
382
482
|
behavior: this.options().behavior.interaction,
|
|
383
483
|
ignoreWhenInView: this.options().ignoreWhenInView,
|
|
384
|
-
...options
|
|
484
|
+
...options,
|
|
485
|
+
displacement: 0
|
|
385
486
|
});
|
|
386
487
|
if (executed) {
|
|
387
|
-
const clean = anchor.replace(/^#/, "");
|
|
388
488
|
this.liveAnchor.set(clean);
|
|
489
|
+
this.savedDisplacement = 0;
|
|
389
490
|
this.pendingDestination = null;
|
|
390
491
|
this.cancelDestinationRetry();
|
|
492
|
+
this.cancelLayoutPreservation();
|
|
391
493
|
this.fragment.set(clean);
|
|
392
494
|
}
|
|
495
|
+
if (options.focus && targetEl) {
|
|
496
|
+
if (executed && willScroll && this.hasSmoothScrollBehavior(behavior)) this.scheduleFocus(targetEl);
|
|
497
|
+
else this.applyFocus(targetEl);
|
|
498
|
+
}
|
|
393
499
|
return executed;
|
|
394
500
|
}
|
|
395
501
|
tryExecutePendingDestination(behavior) {
|
|
396
502
|
if (!this.pendingDestination) return false;
|
|
397
|
-
|
|
503
|
+
const clean = this.pendingDestination.replace(/^#/, "");
|
|
504
|
+
const targetEl = this.anchors().find((el) => el.id === clean);
|
|
505
|
+
if ((targetEl ? this.willScroll(targetEl, { behavior }) : false) && this.hasSmoothScrollBehavior(behavior)) this.beginSmoothNavigation();
|
|
506
|
+
if (this.execute(this.pendingDestination, {
|
|
507
|
+
behavior,
|
|
508
|
+
displacement: 0
|
|
509
|
+
})) {
|
|
510
|
+
this.savedDisplacement = 0;
|
|
398
511
|
this.pendingDestination = null;
|
|
399
512
|
this.cancelDestinationRetry();
|
|
400
513
|
return true;
|
|
@@ -455,12 +568,63 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
455
568
|
...options
|
|
456
569
|
});
|
|
457
570
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
571
|
+
willScroll(targetEl, options) {
|
|
572
|
+
const scrollable = this.scrollable() ?? this.document.scrollingElement ?? this.document.documentElement;
|
|
573
|
+
const isDocument = scrollable === this.document.documentElement || scrollable === this.document.body;
|
|
574
|
+
const containerTop = isDocument ? 0 : scrollable.getBoundingClientRect().top;
|
|
575
|
+
const viewportHeight = isDocument ? this.document.documentElement.clientHeight : scrollable.clientHeight;
|
|
576
|
+
const topOffset = options.topOffset ?? this.offset();
|
|
577
|
+
const delta = targetEl.getBoundingClientRect().top - containerTop - topOffset;
|
|
578
|
+
const maxScrollTop = scrollable.scrollHeight > 0 && viewportHeight > 0 ? Math.max(0, scrollable.scrollHeight - viewportHeight) : Infinity;
|
|
579
|
+
const targetScrollTop = Math.min(Math.max(scrollable.scrollTop + delta, 0), maxScrollTop);
|
|
580
|
+
return Math.abs(targetScrollTop - scrollable.scrollTop) >= 1;
|
|
581
|
+
}
|
|
582
|
+
hasSmoothScrollBehavior(behavior) {
|
|
583
|
+
if (behavior === "smooth") return true;
|
|
584
|
+
if (behavior !== "auto") return false;
|
|
585
|
+
const scrollable = this.scrollable() ?? this.document.documentElement;
|
|
586
|
+
return this.document.defaultView?.getComputedStyle(scrollable).scrollBehavior === "smooth";
|
|
587
|
+
}
|
|
588
|
+
preserve() {
|
|
589
|
+
if (this.initialized) {
|
|
590
|
+
if (this.isSmoothNavigating) {
|
|
591
|
+
this.pendingLayoutPreservation = true;
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const anchor = this.liveAnchor();
|
|
595
|
+
if (!anchor) return;
|
|
596
|
+
this.execute(anchor, {
|
|
597
|
+
behavior: "instant",
|
|
598
|
+
displacement: this.savedDisplacement
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
scheduleLayoutPreservation() {
|
|
603
|
+
if (!this.initialized || typeof window === "undefined") return;
|
|
604
|
+
if (this.isSmoothNavigating) {
|
|
605
|
+
this.pendingLayoutPreservation = true;
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (this.layoutCorrectionFrameId !== void 0) return;
|
|
609
|
+
this.layoutCorrectionFrameId = window.requestAnimationFrame(() => {
|
|
610
|
+
this.layoutCorrectionFrameId = void 0;
|
|
611
|
+
this.preserve();
|
|
462
612
|
});
|
|
463
613
|
}
|
|
614
|
+
cancelLayoutPreservation() {
|
|
615
|
+
if (this.layoutCorrectionFrameId !== void 0 && typeof window !== "undefined") {
|
|
616
|
+
window.cancelAnimationFrame(this.layoutCorrectionFrameId);
|
|
617
|
+
this.layoutCorrectionFrameId = void 0;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
updateSavedDisplacement(anchorId) {
|
|
621
|
+
const clean = anchorId.replace(/^#/, "");
|
|
622
|
+
const anchorEl = this.anchors().find((el) => el.id === clean);
|
|
623
|
+
if (!anchorEl) return;
|
|
624
|
+
const scrollable = this.scrollable();
|
|
625
|
+
const topBoundary = (!scrollable || scrollable === this.document.documentElement || scrollable === this.document.body ? 0 : scrollable.getBoundingClientRect().top) + this.offset();
|
|
626
|
+
this.savedDisplacement = anchorEl.getBoundingClientRect().top - topBoundary;
|
|
627
|
+
}
|
|
464
628
|
refreshTarget() {
|
|
465
629
|
const target = this.scrollable() ?? this.document;
|
|
466
630
|
if (target !== this.target) {
|
|
@@ -477,8 +641,66 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
477
641
|
target.addEventListener("keydown", this.onKeyDown);
|
|
478
642
|
this.document.addEventListener("keydown", this.onKeyDown);
|
|
479
643
|
}
|
|
644
|
+
applyFocus(targetEl) {
|
|
645
|
+
if (!this.document.defaultView || typeof targetEl.focus !== "function" || !targetEl.isConnected || !this.anchors().includes(targetEl)) return;
|
|
646
|
+
const addedTabindex = !isNaturallyFocusable(targetEl);
|
|
647
|
+
let cleanup;
|
|
648
|
+
if (addedTabindex) {
|
|
649
|
+
targetEl.setAttribute("tabindex", "-1");
|
|
650
|
+
const onBlur = () => cleanupFn();
|
|
651
|
+
const cleanupFn = () => {
|
|
652
|
+
targetEl.removeEventListener("blur", onBlur);
|
|
653
|
+
if (targetEl.getAttribute("tabindex") === "-1") targetEl.removeAttribute("tabindex");
|
|
654
|
+
this.temporaryTabindexCleanups.delete(cleanupFn);
|
|
655
|
+
};
|
|
656
|
+
cleanup = cleanupFn;
|
|
657
|
+
this.temporaryTabindexCleanups.add(cleanupFn);
|
|
658
|
+
targetEl.addEventListener("blur", onBlur, { once: true });
|
|
659
|
+
}
|
|
660
|
+
targetEl.focus({ preventScroll: true });
|
|
661
|
+
if (this.document.activeElement !== targetEl && cleanup) cleanup();
|
|
662
|
+
}
|
|
663
|
+
scheduleFocus(targetEl) {
|
|
664
|
+
this.refreshTarget();
|
|
665
|
+
this.cancelScheduledFocus();
|
|
666
|
+
this.pendingFocusTarget = targetEl;
|
|
667
|
+
this.focusInitiator = this.document.activeElement;
|
|
668
|
+
this.document.addEventListener("focusin", this.onFocusIn);
|
|
669
|
+
this.document.addEventListener("keydown", this.onFocusTransferKeyDown);
|
|
670
|
+
this.document.addEventListener("pointerdown", this.onFocusTransferPointerDown, true);
|
|
671
|
+
this.document.addEventListener("wheel", this.onFocusScrollIntent, { passive: true });
|
|
672
|
+
this.document.addEventListener("touchstart", this.onFocusScrollIntent, { passive: true });
|
|
673
|
+
this.document.addEventListener("touchmove", this.onFocusScrollIntent, { passive: true });
|
|
674
|
+
const target = this.target;
|
|
675
|
+
this.scrollEndTarget = target;
|
|
676
|
+
this.scrollEndHandler = () => {
|
|
677
|
+
this.cancelScheduledFocus();
|
|
678
|
+
this.applyFocus(targetEl);
|
|
679
|
+
};
|
|
680
|
+
target.addEventListener("scrollend", this.scrollEndHandler, { once: true });
|
|
681
|
+
this.focusTimeoutTimer = setTimeout(() => this.cancelScheduledFocus(), 5e3);
|
|
682
|
+
}
|
|
683
|
+
cancelScheduledFocus() {
|
|
684
|
+
if (this.scrollEndHandler && this.scrollEndTarget) {
|
|
685
|
+
this.scrollEndTarget.removeEventListener("scrollend", this.scrollEndHandler);
|
|
686
|
+
this.scrollEndHandler = void 0;
|
|
687
|
+
this.scrollEndTarget = void 0;
|
|
688
|
+
}
|
|
689
|
+
clearTimeout(this.focusTimeoutTimer);
|
|
690
|
+
this.focusTimeoutTimer = void 0;
|
|
691
|
+
this.document.removeEventListener("focusin", this.onFocusIn);
|
|
692
|
+
this.document.removeEventListener("keydown", this.onFocusTransferKeyDown);
|
|
693
|
+
this.document.removeEventListener("pointerdown", this.onFocusTransferPointerDown, true);
|
|
694
|
+
this.document.removeEventListener("wheel", this.onFocusScrollIntent);
|
|
695
|
+
this.document.removeEventListener("touchstart", this.onFocusScrollIntent);
|
|
696
|
+
this.document.removeEventListener("touchmove", this.onFocusScrollIntent);
|
|
697
|
+
this.focusInitiator = void 0;
|
|
698
|
+
this.pendingFocusTarget = void 0;
|
|
699
|
+
}
|
|
480
700
|
detachListeners() {
|
|
481
701
|
if (!this.target) return;
|
|
702
|
+
this.cancelScheduledFocus();
|
|
703
|
+
this.cancelSmoothNavigation();
|
|
482
704
|
this.target.removeEventListener("scroll", this.onScroll);
|
|
483
705
|
this.target.removeEventListener("wheel", this.onUserIntent);
|
|
484
706
|
this.target.removeEventListener("touchstart", this.onUserIntent);
|
|
@@ -490,6 +712,10 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
490
712
|
this.userInterrupted = true;
|
|
491
713
|
this.pendingDestination = null;
|
|
492
714
|
this.cancelDestinationRetry();
|
|
715
|
+
this.cancelScheduledFocus();
|
|
716
|
+
this.cancelLayoutPreservation();
|
|
717
|
+
this.cancelSmoothNavigation();
|
|
718
|
+
this.pendingLayoutPreservation = false;
|
|
493
719
|
};
|
|
494
720
|
onKeyDown = (event) => {
|
|
495
721
|
if (!(event instanceof KeyboardEvent)) return;
|
|
@@ -501,12 +727,40 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
501
727
|
this.userInterrupted = true;
|
|
502
728
|
this.pendingDestination = null;
|
|
503
729
|
this.cancelDestinationRetry();
|
|
730
|
+
this.cancelScheduledFocus();
|
|
731
|
+
this.cancelLayoutPreservation();
|
|
732
|
+
this.cancelSmoothNavigation();
|
|
733
|
+
this.pendingLayoutPreservation = false;
|
|
504
734
|
}
|
|
505
735
|
};
|
|
736
|
+
onFocusScrollIntent = () => {
|
|
737
|
+
this.cancelScheduledFocus();
|
|
738
|
+
};
|
|
739
|
+
onFocusIn = (event) => {
|
|
740
|
+
if (this.pendingFocusTarget && event.target !== this.focusInitiator) this.cancelScheduledFocus();
|
|
741
|
+
};
|
|
742
|
+
onFocusTransferKeyDown = (event) => {
|
|
743
|
+
if (!(event instanceof KeyboardEvent)) return;
|
|
744
|
+
if (event.key === "Tab") {
|
|
745
|
+
this.cancelScheduledFocus();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (isEditableTarget(event.target)) return;
|
|
749
|
+
if (SCROLL_KEYS.has(event.key)) this.cancelScheduledFocus();
|
|
750
|
+
};
|
|
751
|
+
onFocusTransferPointerDown = (event) => {
|
|
752
|
+
const target = event.target;
|
|
753
|
+
if (this.pendingFocusTarget && target instanceof Node && target !== this.focusInitiator && !this.focusInitiator?.contains(target)) this.cancelScheduledFocus();
|
|
754
|
+
};
|
|
506
755
|
onScroll = () => {
|
|
507
756
|
if (!this.initialized) return;
|
|
508
|
-
|
|
509
|
-
|
|
757
|
+
if (!this.isSmoothNavigating) {
|
|
758
|
+
const anchor = this.calculateNearestAnchor();
|
|
759
|
+
if (anchor) {
|
|
760
|
+
this.liveAnchor.set(anchor);
|
|
761
|
+
this.updateSavedDisplacement(anchor);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
510
764
|
clearTimeout(this.timer);
|
|
511
765
|
this.timer = setTimeout(() => this.scrollRevision.update((revision) => revision + 1), this.options().debounceTime);
|
|
512
766
|
};
|
|
@@ -514,6 +768,42 @@ var MagicScrollDirective = class MagicScrollDirective {
|
|
|
514
768
|
this.refreshTarget();
|
|
515
769
|
if (this.options().preserveOnResize) this.preserve();
|
|
516
770
|
};
|
|
771
|
+
beginSmoothNavigation() {
|
|
772
|
+
this.cancelSmoothNavigation();
|
|
773
|
+
this.isSmoothNavigating = true;
|
|
774
|
+
this.refreshTarget();
|
|
775
|
+
const target = this.target;
|
|
776
|
+
this.smoothNavTarget = target;
|
|
777
|
+
this.smoothNavEndHandler = () => {
|
|
778
|
+
this.endSmoothNavigation();
|
|
779
|
+
};
|
|
780
|
+
target.addEventListener("scrollend", this.smoothNavEndHandler, { once: true });
|
|
781
|
+
this.smoothNavTimeoutTimer = setTimeout(() => {
|
|
782
|
+
this.endSmoothNavigation();
|
|
783
|
+
}, 3e3);
|
|
784
|
+
}
|
|
785
|
+
endSmoothNavigation() {
|
|
786
|
+
this.cancelSmoothNavigation();
|
|
787
|
+
const anchor = this.calculateNearestAnchor();
|
|
788
|
+
if (anchor) {
|
|
789
|
+
this.liveAnchor.set(anchor);
|
|
790
|
+
this.updateSavedDisplacement(anchor);
|
|
791
|
+
}
|
|
792
|
+
if (this.pendingLayoutPreservation) {
|
|
793
|
+
this.pendingLayoutPreservation = false;
|
|
794
|
+
this.scheduleLayoutPreservation();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
cancelSmoothNavigation() {
|
|
798
|
+
if (this.smoothNavEndHandler && this.smoothNavTarget) {
|
|
799
|
+
this.smoothNavTarget.removeEventListener("scrollend", this.smoothNavEndHandler);
|
|
800
|
+
this.smoothNavEndHandler = void 0;
|
|
801
|
+
this.smoothNavTarget = void 0;
|
|
802
|
+
}
|
|
803
|
+
clearTimeout(this.smoothNavTimeoutTimer);
|
|
804
|
+
this.smoothNavTimeoutTimer = void 0;
|
|
805
|
+
this.isSmoothNavigating = false;
|
|
806
|
+
}
|
|
517
807
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
518
808
|
minVersion: "12.0.0",
|
|
519
809
|
version: "22.2.1",
|