@ethlete/core 5.0.0-next.67 → 5.0.0-next.68

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ethlete/core
2
2
 
3
+ ## 5.0.0-next.68
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `attributes` and `movingClass` options to `injectAnimatedListItems`: watch items a filter hides, and clip overflow while items move.
8
+ - Add `injectAnimatedListItems`: items of a list that stay slide to their new place, new items fade in, and removed items fade out where they were.
9
+ - `injectAnimatedListItems` takes an `animateChange` callback; returning `false` measures a change without moving or fading items.
10
+
11
+ ### Patch Changes
12
+
13
+ - Place anchored overlays on whole device pixels, so their content no longer shifts by a pixel when an animation ends.
14
+ - Round a shifted anchored overlay inward, so snapping to device pixels no longer pushes it past the viewport padding.
15
+ - The fade-out copy in `injectAnimatedListItems` keeps the padding, border, colour and text styles its item had from ancestor selectors.
16
+ - `createCanAnimateSignal` cancels its pending frame when its host is destroyed, and `matchesReducedMotion` returns `false` in an environment without `matchMedia` instead of throwing.
17
+ - `injectAnimatedListItems` fades a new item in only after the removed items have faded out, so a select's "No results" or loading row cross-fades with the options instead of overlapping them.
18
+ - `registerScrollRestorationState` now restores a page that registers after the navigation ended, such as a route component behind a nested `<router-outlet>`, on reload and back/forward navigation. The offset waits for that state before it is applied.
19
+ - A quick mouse flick on a snapping scrollable or carousel now carries on to the next child in its direction instead of gliding back to the nearest one.
20
+
3
21
  ## 5.0.0-next.67
4
22
 
5
23
  ### Patch Changes
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { assertInInjectionContext, inject, DestroyRef, ElementRef, TemplateRef, InjectionToken, booleanAttribute, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, untracked, ApplicationRef, runInInjectionContext, NgZone, Injector, numberAttribute, PLATFORM_ID, afterRenderEffect, EnvironmentInjector, createComponent, ViewEncapsulation, Component, Directive, model, ViewContainerRef, input, CSP_NONCE, provideEnvironmentInitializer, output, createEnvironmentInjector, Pipe, makeEnvironmentProviders, afterEveryRender } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
4
- import { Subject, fromEvent, switchMap, startWith, of, defer, from, map, catchError, timer, takeUntil, tap, pairwise, debounceTime, merge, distinctUntilChanged, EMPTY, expand, skip, take, filter, isObservable, ignoreElements, BehaviorSubject, combineLatest, Observable, firstValueFrom, defaultIfEmpty, concatMap, concat, finalize, exhaustMap, share, shareReplay, ReplaySubject } from 'rxjs';
4
+ import { Subject, fromEvent, switchMap, startWith, of, defer, from, map, catchError, timer, takeUntil, tap, pairwise, debounceTime, merge, distinctUntilChanged, EMPTY, expand, skip, take, filter, Subscription, isObservable, ignoreElements, BehaviorSubject, combineLatest, Observable, firstValueFrom, defaultIfEmpty, concatMap, concat, finalize, exhaustMap, share, shareReplay, ReplaySubject } from 'rxjs';
5
5
  import { FormGroup, FormArray, FormControl } from '@angular/forms';
6
6
  import { SIGNAL, signalSetFn } from '@angular/core/primitives/signals';
7
7
  import { isPlatformBrowser, Location, DOCUMENT as DOCUMENT$1, isPlatformServer } from '@angular/common';
@@ -1043,9 +1043,10 @@ const createIsRenderedSignal = () => {
1043
1043
  const createCanAnimateSignal = () => {
1044
1044
  const value = signal(false, /* @ts-ignore */
1045
1045
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1046
- nextFrame(() => {
1046
+ const cancel = nextFrame(() => {
1047
1047
  value.set(true);
1048
1048
  });
1049
+ inject(DestroyRef).onDestroy(cancel);
1049
1050
  return {
1050
1051
  state: value.asReadonly(),
1051
1052
  };
@@ -1490,6 +1491,245 @@ const injectAnimatedBlockSize = (config) => {
1490
1491
  });
1491
1492
  };
1492
1493
 
1494
+ const translateOf = (element) => {
1495
+ const match = /matrix\(([^)]+)\)/.exec(getComputedStyle(element).transform);
1496
+ const values = match?.[1]?.split(',').map(Number);
1497
+ return { x: values?.[4] ?? 0, y: values?.[5] ?? 0 };
1498
+ };
1499
+ const layoutBoxOf = (element, origin, scale) => {
1500
+ const rect = element.getBoundingClientRect();
1501
+ const translate = translateOf(element);
1502
+ return {
1503
+ top: (rect.top - origin.top) / scale - translate.y,
1504
+ left: (rect.left - origin.left) / scale - translate.x,
1505
+ width: element.offsetWidth,
1506
+ height: element.offsetHeight,
1507
+ };
1508
+ };
1509
+ const CONTEXT_STYLES = [
1510
+ 'padding-top',
1511
+ 'border-top-width',
1512
+ 'border-top-style',
1513
+ 'border-top-color',
1514
+ 'padding-right',
1515
+ 'border-right-width',
1516
+ 'border-right-style',
1517
+ 'border-right-color',
1518
+ 'padding-bottom',
1519
+ 'border-bottom-width',
1520
+ 'border-bottom-style',
1521
+ 'border-bottom-color',
1522
+ 'padding-left',
1523
+ 'border-left-width',
1524
+ 'border-left-style',
1525
+ 'border-left-color',
1526
+ 'border-top-left-radius',
1527
+ 'border-top-right-radius',
1528
+ 'border-bottom-right-radius',
1529
+ 'border-bottom-left-radius',
1530
+ 'background-color',
1531
+ 'color',
1532
+ 'font-size',
1533
+ 'font-weight',
1534
+ 'line-height',
1535
+ 'letter-spacing',
1536
+ 'text-transform',
1537
+ ];
1538
+ const scaleOf = (host, hostRect) => host.offsetWidth && hostRect.width ? hostRect.width / host.offsetWidth : 1;
1539
+ /**
1540
+ * Animates the items of a list when its content changes: an item that stays slides from its old spot to
1541
+ * its new one, a new item fades in, and a removed item fades out where it was. Set up once in an injection
1542
+ * context. A change during a running move starts from where the item is now. With `prefers-reduced-motion`,
1543
+ * items only fade.
1544
+ *
1545
+ * @example
1546
+ * injectAnimatedListItems({ host: this.listElement, items: '.my-row' });
1547
+ */
1548
+ const injectAnimatedListItems = (config) => {
1549
+ const hostBinding = config.host ?? inject(ElementRef);
1550
+ const prefersReducedMotion = injectPrefersReducedMotion();
1551
+ const destroyRef = inject(DestroyRef);
1552
+ const isRendered = signalIsRendered();
1553
+ const hostSignal = firstElementSignal(buildElementSignal(hostBinding));
1554
+ const moveDuration = config.moveDuration ?? 300;
1555
+ const moveEasing = config.moveEasing ?? 'ease';
1556
+ const enterDuration = config.enterDuration ?? 150;
1557
+ const leaveDuration = config.leaveDuration ?? 100;
1558
+ const maxMoves = config.maxMoves ?? 30;
1559
+ const copies = new WeakSet();
1560
+ const moves = new Map();
1561
+ let boxes = new Map();
1562
+ let observer = null;
1563
+ let resizeObserver = null;
1564
+ let hostWidth = 0;
1565
+ let running = 0;
1566
+ const track = (animation) => {
1567
+ const host = hostSignal().currentElement;
1568
+ const movingClass = config.movingClass;
1569
+ if (!movingClass || !host)
1570
+ return;
1571
+ running++;
1572
+ host.classList.add(movingClass);
1573
+ const settle = () => {
1574
+ running--;
1575
+ if (running === 0)
1576
+ host.classList.remove(movingClass);
1577
+ };
1578
+ animation.finished.then(settle).catch(settle);
1579
+ };
1580
+ const measure = (host) => {
1581
+ const next = new Map();
1582
+ const hostRect = host.getBoundingClientRect();
1583
+ const scale = scaleOf(host, hostRect);
1584
+ for (const item of Array.from(host.querySelectorAll(config.items))) {
1585
+ if (!copies.has(item) && (item.checkVisibility?.() ?? true))
1586
+ next.set(item, layoutBoxOf(item, hostRect, scale));
1587
+ }
1588
+ return next;
1589
+ };
1590
+ const fadeOutCopy = (host, item, box) => {
1591
+ const copy = item.cloneNode(true);
1592
+ // read before the copy leaves the item's ancestors, whose selectors may style the item
1593
+ if (item.isConnected) {
1594
+ const style = getComputedStyle(item);
1595
+ for (const property of CONTEXT_STYLES) {
1596
+ const value = style.getPropertyValue(property);
1597
+ if (value)
1598
+ copy.style.setProperty(property, value);
1599
+ }
1600
+ }
1601
+ copy.removeAttribute('id');
1602
+ for (const attribute of config.attributes ?? [])
1603
+ copy.removeAttribute(attribute);
1604
+ copy.setAttribute('aria-hidden', 'true');
1605
+ copy.inert = true;
1606
+ Object.assign(copy.style, {
1607
+ position: 'absolute',
1608
+ top: '0',
1609
+ left: '0',
1610
+ width: `${box.width}px`,
1611
+ height: `${box.height}px`,
1612
+ boxSizing: 'border-box',
1613
+ margin: '0',
1614
+ pointerEvents: 'none',
1615
+ });
1616
+ copies.add(copy);
1617
+ host.append(copy);
1618
+ const hostRect = host.getBoundingClientRect();
1619
+ const scale = scaleOf(host, hostRect);
1620
+ const containingBlock = layoutBoxOf(copy, hostRect, scale);
1621
+ copy.style.top = `${box.top - containingBlock.top}px`;
1622
+ copy.style.left = `${box.left - containingBlock.left}px`;
1623
+ const fade = copy.animate([{ opacity: 0 }], { duration: leaveDuration, easing: 'ease-out' });
1624
+ const remove = () => copy.remove();
1625
+ track(fade);
1626
+ fade.finished.then(remove).catch(remove);
1627
+ };
1628
+ const run = (host) => {
1629
+ if (config.animateChange?.() === false) {
1630
+ boxes = measure(host);
1631
+ return;
1632
+ }
1633
+ const previous = boxes;
1634
+ const next = measure(host);
1635
+ const shifts = [];
1636
+ const entering = [];
1637
+ boxes = next;
1638
+ for (const [item, box] of next) {
1639
+ const before = previous.get(item);
1640
+ if (!before) {
1641
+ entering.push(item);
1642
+ continue;
1643
+ }
1644
+ const translate = translateOf(item);
1645
+ const x = before.left + translate.x - box.left;
1646
+ const y = before.top + translate.y - box.top;
1647
+ if (Math.abs(x) >= 1 || Math.abs(y) >= 1)
1648
+ shifts.push({ item, x, y });
1649
+ }
1650
+ let removesItems = false;
1651
+ for (const [item, box] of previous) {
1652
+ if (next.has(item))
1653
+ continue;
1654
+ removesItems = true;
1655
+ fadeOutCopy(host, item, box);
1656
+ }
1657
+ const movesItems = shifts.length > 0 && !prefersReducedMotion() && shifts.length <= maxMoves;
1658
+ // A new item waits until moving items have passed its spot and removed ones have faded, or their text overlaps.
1659
+ const enterDelay = Math.max(movesItems ? moveDuration * 0.4 : 0, removesItems ? leaveDuration : 0);
1660
+ for (const item of entering) {
1661
+ item.animate([{ opacity: 0, offset: 0 }], {
1662
+ duration: enterDuration,
1663
+ delay: enterDelay,
1664
+ easing: 'ease-out',
1665
+ fill: 'backwards',
1666
+ });
1667
+ }
1668
+ if (!movesItems) {
1669
+ for (const move of moves.values())
1670
+ move.cancel();
1671
+ moves.clear();
1672
+ return;
1673
+ }
1674
+ for (const { item, x, y } of shifts) {
1675
+ moves.get(item)?.cancel();
1676
+ const move = item.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: 'none' }], {
1677
+ duration: moveDuration,
1678
+ easing: moveEasing,
1679
+ });
1680
+ moves.set(item, move);
1681
+ track(move);
1682
+ const forget = () => {
1683
+ if (moves.get(item) === move)
1684
+ moves.delete(item);
1685
+ };
1686
+ move.finished.then(forget).catch(forget);
1687
+ }
1688
+ };
1689
+ const touchesOnlyCopies = (record) => record.type === 'childList' &&
1690
+ [...Array.from(record.addedNodes), ...Array.from(record.removedNodes)].every((node) => copies.has(node));
1691
+ effect(() => {
1692
+ const host = hostSignal().currentElement;
1693
+ const rendered = isRendered();
1694
+ untracked(() => {
1695
+ observer?.disconnect();
1696
+ resizeObserver?.disconnect();
1697
+ boxes = new Map();
1698
+ if (!host || !rendered || typeof MutationObserver === 'undefined')
1699
+ return;
1700
+ boxes = measure(host);
1701
+ observer ??= new MutationObserver((records) => {
1702
+ const current = hostSignal().currentElement;
1703
+ if (current && !records.every(touchesOnlyCopies))
1704
+ run(current);
1705
+ });
1706
+ observer.observe(host, {
1707
+ childList: true,
1708
+ subtree: true,
1709
+ ...(config.attributes?.length ? { attributes: true, attributeFilter: config.attributes } : {}),
1710
+ });
1711
+ if (typeof ResizeObserver === 'undefined')
1712
+ return;
1713
+ hostWidth = host.offsetWidth;
1714
+ // Boxes taken while the host was still narrow give fading copies the wrong width.
1715
+ resizeObserver ??= new ResizeObserver(() => {
1716
+ const current = hostSignal().currentElement;
1717
+ if (!current || current.offsetWidth === hostWidth)
1718
+ return;
1719
+ hostWidth = current.offsetWidth;
1720
+ boxes = measure(current);
1721
+ });
1722
+ resizeObserver.observe(host);
1723
+ });
1724
+ });
1725
+ destroyRef.onDestroy(() => {
1726
+ observer?.disconnect();
1727
+ resizeObserver?.disconnect();
1728
+ for (const move of moves.values())
1729
+ move.cancel();
1730
+ });
1731
+ };
1732
+
1493
1733
  /**
1494
1734
  * Creates a signal that animates the transition of a numeric signal's value over time.
1495
1735
  */
@@ -2315,7 +2555,7 @@ const scrollToElement = (options) => {
2315
2555
 
2316
2556
  const MIN_SNAP_DELTA_PX = 1;
2317
2557
  const FULLY_VISIBLE_RATIO = 0.99;
2318
- const getScrollSnapTarget = (items, container, direction, origin, margin = 0) => {
2558
+ const getScrollSnapTarget = (items, container, direction, origin, margin = 0, towards) => {
2319
2559
  if (!items.length)
2320
2560
  return null;
2321
2561
  const containerRect = container.getBoundingClientRect();
@@ -2324,6 +2564,10 @@ const getScrollSnapTarget = (items, container, direction, origin, margin = 0) =>
2324
2564
  let bestOrigin = 'start';
2325
2565
  let bestAbsDelta = Infinity;
2326
2566
  const updateBest = (element, o, delta) => {
2567
+ if (towards === 'forward' && delta < MIN_SNAP_DELTA_PX)
2568
+ return;
2569
+ if (towards === 'backward' && delta > -MIN_SNAP_DELTA_PX)
2570
+ return;
2327
2571
  const abs = Math.abs(delta);
2328
2572
  if (abs < bestAbsDelta) {
2329
2573
  bestAbsDelta = abs;
@@ -2783,6 +3027,8 @@ const writeViewportSizeToCssVariables = () => {
2783
3027
  const CURSOR_DRAG_SCROLL_DEADZONE = 5;
2784
3028
  /** `MouseEvent.button` for the primary button. A secondary click opens a menu; it does not drag. */
2785
3029
  const PRIMARY_MOUSE_BUTTON = 0;
3030
+ /** How far back from the release the pointer's velocity is measured. */
3031
+ const RELEASE_VELOCITY_WINDOW_MS = 100;
2786
3032
  /** The class that is added to the element when the cursor is being dragged. */
2787
3033
  const CURSOR_DRAG_SCROLLING_CLASS = 'et-cursor-drag-scroll--scrolling';
2788
3034
  const CURSOR_DRAG_INIT_CLASS = 'et-cursor-drag-scroll--init';
@@ -2803,6 +3049,9 @@ const useCursorDragScroll = (el, options) => {
2803
3049
  ...(ngDevMode ? [{ debugName: "initialScrollPosition" }] : /* istanbul ignore next */ []));
2804
3050
  const dragAmount = signal({ x: 0, y: 0 }, /* @ts-ignore */
2805
3051
  ...(ngDevMode ? [{ debugName: "dragAmount" }] : /* istanbul ignore next */ []));
3052
+ const releaseVelocity = signal({ x: 0, y: 0 }, /* @ts-ignore */
3053
+ ...(ngDevMode ? [{ debugName: "releaseVelocity" }] : /* istanbul ignore next */ []));
3054
+ let samples = [];
2806
3055
  const document = inject(DOCUMENT);
2807
3056
  // Only measured when the caller hasn't already: this costs a MutationObserver and a ResizeObserver.
2808
3057
  const suppliedCanScroll = options?.canScroll;
@@ -2911,11 +3160,30 @@ const useCursorDragScroll = (el, options) => {
2911
3160
  const dx = (e.clientX - initialDragPosition().x) * -1;
2912
3161
  const dy = (e.clientY - initialDragPosition().y) * -1;
2913
3162
  dragAmount.set({ x: dx, y: dy });
3163
+ recordSample(dx, dy);
2914
3164
  if (Math.abs(dx) > CURSOR_DRAG_SCROLL_DEADZONE || Math.abs(dy) > CURSOR_DRAG_SCROLL_DEADZONE) {
2915
3165
  isDragging.set(true);
2916
3166
  }
2917
3167
  };
3168
+ const recordSample = (x, y) => {
3169
+ const t = performance.now();
3170
+ samples.push({ t, x, y });
3171
+ samples = samples.filter((sample) => t - sample.t <= RELEASE_VELOCITY_WINDOW_MS);
3172
+ };
3173
+ const measureReleaseVelocity = () => {
3174
+ const now = performance.now();
3175
+ const recent = samples.filter((sample) => now - sample.t <= RELEASE_VELOCITY_WINDOW_MS);
3176
+ const first = recent[0];
3177
+ const last = recent.at(-1);
3178
+ const elapsed = first ? now - first.t : 0;
3179
+ if (!first || !last || elapsed <= 0)
3180
+ return { x: 0, y: 0 };
3181
+ return { x: (last.x - first.x) / elapsed, y: (last.y - first.y) / elapsed };
3182
+ };
2918
3183
  const updateDraggingEnd = () => {
3184
+ // Set before `isDragging` flips, so whatever reacts to the release reads this drag's velocity.
3185
+ releaseVelocity.set(isDragging() ? measureReleaseVelocity() : { x: 0, y: 0 });
3186
+ samples = [];
2919
3187
  isDragging.set(false);
2920
3188
  isInitDragging.set(false);
2921
3189
  initialDragPosition.set({ x: 0, y: 0 });
@@ -2939,6 +3207,7 @@ const useCursorDragScroll = (el, options) => {
2939
3207
  .subscribe();
2940
3208
  initialDragPosition.set({ x: e.clientX, y: e.clientY });
2941
3209
  initialScrollPosition.set({ x: el.scrollLeft, y: el.scrollTop });
3210
+ samples = [{ t: performance.now(), x: 0, y: 0 }];
2942
3211
  isInitDragging.set(true);
2943
3212
  };
2944
3213
  toObservable(element)
@@ -2947,6 +3216,8 @@ const useCursorDragScroll = (el, options) => {
2947
3216
  return {
2948
3217
  isDragging: isDragging.asReadonly(),
2949
3218
  currentDragAmount: dragAmount.asReadonly(),
3219
+ /** The scroll velocity over the last 100ms of the most recent drag, set just before `isDragging` turns false. */
3220
+ releaseVelocity: releaseVelocity.asReadonly(),
2950
3221
  };
2951
3222
  };
2952
3223
 
@@ -3275,9 +3546,12 @@ const routerRestoreScroll = () => ({ [ET_RESTORE_SCROLL]: true });
3275
3546
  const SCROLL_RESTORATION_HOLDS_DEF = /* @__PURE__ */ defineRootProvider(() => {
3276
3547
  const holds = new Set();
3277
3548
  const states = new Set();
3549
+ const stateWatchers = new Set();
3278
3550
  return {
3279
3551
  addState: (state) => {
3280
3552
  states.add(state);
3553
+ for (const watcher of stateWatchers)
3554
+ watcher(state);
3281
3555
  return () => states.delete(state);
3282
3556
  },
3283
3557
  saveStates: () => {
@@ -3289,16 +3563,11 @@ const SCROLL_RESTORATION_HOLDS_DEF = /* @__PURE__ */ defineRootProvider(() => {
3289
3563
  }
3290
3564
  return values;
3291
3565
  },
3292
- restoreStates: (values) => {
3293
- const pending = [];
3294
- for (const state of states) {
3295
- if (!(state.key in values))
3296
- continue;
3297
- const result = state.restore(values[state.key]);
3298
- if (isObservable(result))
3299
- pending.push(result);
3300
- }
3301
- return pending;
3566
+ watchStates: (watcher) => {
3567
+ stateWatchers.add(watcher);
3568
+ for (const state of [...states])
3569
+ watcher(state);
3570
+ return () => stateWatchers.delete(watcher);
3302
3571
  },
3303
3572
  add: (isPending) => {
3304
3573
  holds.add(isPending);
@@ -3452,19 +3721,21 @@ const setupScrollRestoration = (config = {}) => {
3452
3721
  cancelPendingRestore();
3453
3722
  const target = entry.top;
3454
3723
  let frame = 0;
3455
- let started = false;
3456
3724
  let cancelled = false;
3457
3725
  let renderRef = null;
3458
3726
  let fallback;
3459
- let stateSubscription = null;
3727
+ const stateSubscriptions = new Subscription();
3460
3728
  let stateDeadline;
3729
+ let pendingStateCount = 0;
3730
+ let stopWatchingStates = null;
3731
+ const unrestoredStates = { ...entry.state };
3461
3732
  // The user scrolling always wins over a restoration we have not committed yet.
3462
3733
  const abortByUser = () => cancelPendingRestore();
3463
3734
  const hardDeadline = Date.now() + restoreMaxTimeout;
3464
3735
  let deadline = Date.now() + restoreTimeout;
3465
3736
  const step = () => {
3466
3737
  frame = 0;
3467
- if (cancelled)
3738
+ if (cancelled || pendingStateCount)
3468
3739
  return;
3469
3740
  const el = resolveScrollElement();
3470
3741
  const maxOffset = Math.max(0, el.scrollHeight - el.clientHeight);
@@ -3492,19 +3763,41 @@ const setupScrollRestoration = (config = {}) => {
3492
3763
  // components exist but the DOM still shows the previous page, whose height would be measured
3493
3764
  // instead. The timeout is a safety net in case no render pass follows.
3494
3765
  const start = () => {
3495
- if (started || cancelled)
3766
+ if (frame || cancelled || pendingStateCount)
3496
3767
  return;
3497
- started = true;
3498
3768
  frame = requestAnimationFrame(step);
3499
3769
  };
3500
3770
  const waitForRender = () => {
3501
3771
  if (cancelled)
3502
3772
  return;
3503
3773
  clearTimeout(stateDeadline);
3774
+ clearTimeout(fallback);
3775
+ renderRef?.destroy();
3504
3776
  renderRef = afterNextRender({ read: start }, { injector });
3505
3777
  fallback = setTimeout(start);
3506
3778
  };
3507
- const pendingStates = entry.state ? holds.restoreStates(entry.state) : [];
3779
+ const onStateSettled = () => {
3780
+ pendingStateCount--;
3781
+ if (!pendingStateCount)
3782
+ waitForRender();
3783
+ };
3784
+ // A route component behind a nested outlet is created on the first render after `NavigationEnd`,
3785
+ // so a state can register after this restoration was scheduled. It must still restore before the offset.
3786
+ const restoreState = (state) => {
3787
+ if (cancelled || !(state.key in unrestoredStates))
3788
+ return;
3789
+ const value = unrestoredStates[state.key];
3790
+ delete unrestoredStates[state.key];
3791
+ const result = state.restore(value);
3792
+ if (!isObservable(result))
3793
+ return;
3794
+ if (!pendingStateCount) {
3795
+ clearTimeout(stateDeadline);
3796
+ stateDeadline = setTimeout(cancelPendingRestore, restoreMaxTimeout);
3797
+ }
3798
+ pendingStateCount++;
3799
+ stateSubscriptions.add(result.pipe(ignoreElements()).subscribe({ complete: onStateSettled, error: onStateSettled }));
3800
+ };
3508
3801
  for (const eventName of INTERACTION_EVENTS) {
3509
3802
  document.addEventListener(eventName, abortByUser, { capture: true, passive: true });
3510
3803
  }
@@ -3513,7 +3806,8 @@ const setupScrollRestoration = (config = {}) => {
3513
3806
  // not apply its (now stale) offset even if its hook had already been queued to run.
3514
3807
  cancelled = true;
3515
3808
  renderRef?.destroy();
3516
- stateSubscription?.unsubscribe();
3809
+ stopWatchingStates?.();
3810
+ stateSubscriptions.unsubscribe();
3517
3811
  clearTimeout(stateDeadline);
3518
3812
  if (frame)
3519
3813
  cancelAnimationFrame(frame);
@@ -3522,15 +3816,10 @@ const setupScrollRestoration = (config = {}) => {
3522
3816
  document.removeEventListener(eventName, abortByUser, { capture: true });
3523
3817
  }
3524
3818
  };
3525
- if (pendingStates.length) {
3526
- stateDeadline = setTimeout(cancelPendingRestore, restoreMaxTimeout);
3527
- stateSubscription = merge(...pendingStates)
3528
- .pipe(ignoreElements())
3529
- .subscribe({ complete: waitForRender, error: waitForRender });
3530
- }
3531
- else {
3819
+ if (entry.state)
3820
+ stopWatchingStates = holds.watchStates(restoreState);
3821
+ if (!pendingStateCount)
3532
3822
  waitForRender();
3533
- }
3534
3823
  };
3535
3824
  /**
3536
3825
  * The offset the most recently left entry for this page had. A target without query params means
@@ -4990,7 +5279,7 @@ const forceReflow = (element) => {
4990
5279
  */
4991
5280
  const matchesReducedMotion = (element) => {
4992
5281
  const view = element?.ownerDocument.defaultView ?? (typeof window === 'undefined' ? null : window);
4993
- return view?.matchMedia('(prefers-reduced-motion: reduce)').matches ?? false;
5282
+ return view?.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
4994
5283
  };
4995
5284
 
4996
5285
  const ANIMATED_LIFECYCLE_TOKEN = new InjectionToken('ANIMATED_LIFECYCLE_DIRECTIVE_TOKEN');
@@ -8125,6 +8414,11 @@ const setupPositioning = (config, hostElement, paneElement, overlayRef, renderer
8125
8414
  return onOverlayViewportInsetsChange(() => applyViewportInsets(hostElement, renderer));
8126
8415
  };
8127
8416
 
8417
+ const roundToDevicePixel = (value, shifted = 0) => {
8418
+ const ratio = window.devicePixelRatio || 1;
8419
+ const round = shifted > 0 ? Math.ceil : shifted < 0 ? Math.floor : Math.round;
8420
+ return round(Number((value * ratio).toFixed(4))) / ratio;
8421
+ };
8128
8422
  let middlewareExtras = null;
8129
8423
  /**
8130
8424
  * Installs the `size` / `arrow` / `hide` middleware. Called by
@@ -8366,9 +8660,13 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
8366
8660
  platform: strategy.autoResize && strategy.minAvailableSpace === undefined
8367
8661
  ? platformMeasuringUncappedPaneFirst(paneElement, renderer)
8368
8662
  : platform,
8369
- }).then(({ x, y, placement, middlewareData }) => {
8663
+ }).then(({ x: rawX, y: rawY, placement, middlewareData }) => {
8370
8664
  if (destroyed || currentUpdateId !== updateId)
8371
8665
  return;
8666
+ // A fractional offset snaps the panel's content by a pixel whenever a child gets its own layer.
8667
+ // A shifted pane sits on the viewport padding, so it rounds inward instead of past it.
8668
+ const x = roundToDevicePixel(rawX, middlewareData.shift?.x);
8669
+ const y = roundToDevicePixel(rawY, middlewareData.shift?.y);
8372
8670
  if (middlewareData.hide?.referenceHidden) {
8373
8671
  handleUnusableReference();
8374
8672
  return;
@@ -10867,11 +11165,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
10867
11165
 
10868
11166
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
10869
11167
  /** The version of `@ethlete/core` this build was cut from. */
10870
- const CORE_VERSION = '5.0.0-next.67';
11168
+ const CORE_VERSION = '5.0.0-next.68';
10871
11169
 
10872
11170
  /**
10873
11171
  * Generated bundle index. Do not edit.
10874
11172
  */
10875
11173
 
10876
- export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, ANIMATION_ERROR_CODES, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective, AutoSurfaceDirective, BOUNDARY_ELEMENT_TOKEN, BREAKPOINT_INSTANCE_TOKEN, BREAKPOINT_ORDER, COLOR_PALETTE, COLOR_PROVIDER, CORE_VERSION, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, DEFAULT_OVERLAY_LAYER, DEFAULT_VIEWPORT_CONFIG, DISABLE_LOGGER_PARAM, DragHandleDirective, ET_DISABLE_SCROLL_TOP, ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE, ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE, ET_PROPERTY_REMOVED, ET_RESTORE_SCROLL, EasingTokensStylesComponent, EthleteValidators, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MARKDOWN_VERBATIM_ATTR, MUST_MATCH, MarkdownToHtmlPipe, MatchStateType, MustMatch, NOTIFICATION_CLICK_MESSAGE_TYPE, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, OVERLAY_LAYER_ATTRIBUTE, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RestoreScrollDirective, RuntimeError, SURFACE_COLOR, SURFACE_PROVIDER, SURFACE_TYPE, SWIPE_VELOCITY_WINDOW_MS, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, UNSAVED_CHANGES_CONFIRM, UNSAVED_CHANGES_ERROR_CODES, UNSAVED_CHANGES_TITLE_MARKER, ValidateAtLeastOneRequired, VisuallyHiddenStylesComponent, anchoredOverlayPosition, animationDebugLog, applyAlternateBinding, applyAlternateLanguagesBindings, applyArticleBindings, applyAuthorBinding, applyCanonicalBinding, applyCenteredPosition, applyDescriptionBinding, applyFaviconOverlay, applyGlobalPosition, applyHeadBinding, applyHeadTitleBinding, applyHeadTitleMarker, applyHostListener, applyHostListeners, applyInitialFocus, applyKeywordsBinding, applyLinkBinding, applyMetaBinding, applyNextBinding, applyOgBinding, applyOpenGraphBindings, applyPrevBinding, applyResourceHintsBindings, applyRobotsBinding, applySocialMediaBindings, applyStructuredDataBinding, applyTwitterCardBindings, boolBreakpointTransform, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, copyToClipboard, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDestroy, createDocumentElementSignal, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createIsRenderedSignal, createLogger, createObjectUrlHandle, createPropertyBinding, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSwatchCss, createSwipeTracker, createTailwindColorThemes, createTailwindCssVar, createTailwindRgbVar, createThemeStyle, createUnsavedChangesGuard, createUnsavedChangesTabLock, createUnsavedChangesTracker, createUserConsentProvider, deferredSignal, defineLabels, defineProvider, defineRootProvider, defineStaticProvider, defineStaticRootProvider, deleteCookie, dragGestureFrom, easeElastic, easeIn, easeInOut, easeLinear, easeOut, easeOutBack, easeOutBackStrong, elementCanScroll, enableAnchoredOverlayPositionExtras, equal, fetchDeployedBuildFingerprint$, firstElementSignal, focusElement, forceReflow, formatRuntimeError, fromNextFrame, getCookie, getDomain, getElementScrollCoordinates, getFocusableElements, getFormGroupValue, getGroupMatchPoints, getGroupMatchScore, getHeadingElement, getKnockoutMatchScore, getMatchScoreSubLine, getObjectProperty, getScrollContainerTarget, getScrollItemTarget, getScrollSnapTarget, hasAnchoredOverlayPositionExtras, hasCookie, holdScrollRestoration, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectAnimatedBlockSize, injectAppUpdates, injectAppUpdatesConfig, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorPalette, injectColorScheme, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDefaultColorTheme, injectDefaultSurfaceTheme, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFaviconStore, injectFileDownload, injectFocusVisibleTracker, injectFragment, injectHasPrecisionInput, injectHasTouchInput, injectHostElement, injectIs2Xl, injectIsDocumentVisible, injectIsLandscape, injectIsLg, injectIsMd, injectIsPortrait, injectIsRouterInitialized, injectIsSm, injectIsXl, injectIsXs, injectLinkStore, injectLinkStoreConfig, injectLocale, injectMediaQueryIsMatched, injectMetaConfig, injectMetaStore, injectNotifications, injectObserveBreakpoint, injectObserveMediaQuery, injectOverlayRuntime, injectParentSurface, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamAll, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRootSurfaceTheme, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterNavigationState, injectRouterState, injectScrollbarDimensions, injectSemanticColorTheme, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceColorPalette, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectSurfaceType, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUnsavedChangesCoordinator, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElement, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isHTMLOrSVGElement, isKnockoutMatch, isObject, isOnHigherOverlayLayer, isSafeLinkUrl, isSafeUrl, isStaleBuildError, markdownToHtml, matchesReducedMotion, maybeSignalValue, memoizeSignal, mountEasingTokens, mountVisuallyHidden, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, normalizeUnsavedChangesSource, numberBreakpointTransform, onOverlayViewportInsetsChange, optionalBooleanAttribute, overlayViewportInsets, overlayViewportInsetsFor, ownsActiveElement, previousSignalValue, provideAppUpdates, provideAppUpdatesConfig, provideAppUpdatesInstance, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorPalette, provideColorScheme, provideColorThemes, provideColorThemesWithTailwind4, provideFaviconStore, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideNotifications, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideUnsavedChangesConfirm, provideUnsavedChangesCoordinator, provideViewportConfig, randomId, readBuildFingerprint, readFromClipboard, registerScrollRestorationState, reserveOverlayViewportSpace, resolveAppRootColorProvider, resolveColorThemeVariant, resolveOverlayLayer, resolveSurfaceByElevation, round, routerDisableScrollTop, routerRestoreScroll, scrollToElement, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalCountdown, signalDeferredLoading, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, suppressTextSelection, switchQueryListChanges, syncSignal, toArray, toArrayTrackByFn, toInjectFn, toProvideFn, toStringBinding, toToken, transformOrReturn, typedBreakpointTransform, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
11174
+ export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, ANIMATION_ERROR_CODES, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective, AutoSurfaceDirective, BOUNDARY_ELEMENT_TOKEN, BREAKPOINT_INSTANCE_TOKEN, BREAKPOINT_ORDER, COLOR_PALETTE, COLOR_PROVIDER, CORE_VERSION, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, DEFAULT_OVERLAY_LAYER, DEFAULT_VIEWPORT_CONFIG, DISABLE_LOGGER_PARAM, DragHandleDirective, ET_DISABLE_SCROLL_TOP, ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE, ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE, ET_PROPERTY_REMOVED, ET_RESTORE_SCROLL, EasingTokensStylesComponent, EthleteValidators, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MARKDOWN_VERBATIM_ATTR, MUST_MATCH, MarkdownToHtmlPipe, MatchStateType, MustMatch, NOTIFICATION_CLICK_MESSAGE_TYPE, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, OVERLAY_LAYER_ATTRIBUTE, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RestoreScrollDirective, RuntimeError, SURFACE_COLOR, SURFACE_PROVIDER, SURFACE_TYPE, SWIPE_VELOCITY_WINDOW_MS, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, UNSAVED_CHANGES_CONFIRM, UNSAVED_CHANGES_ERROR_CODES, UNSAVED_CHANGES_TITLE_MARKER, ValidateAtLeastOneRequired, VisuallyHiddenStylesComponent, anchoredOverlayPosition, animationDebugLog, applyAlternateBinding, applyAlternateLanguagesBindings, applyArticleBindings, applyAuthorBinding, applyCanonicalBinding, applyCenteredPosition, applyDescriptionBinding, applyFaviconOverlay, applyGlobalPosition, applyHeadBinding, applyHeadTitleBinding, applyHeadTitleMarker, applyHostListener, applyHostListeners, applyInitialFocus, applyKeywordsBinding, applyLinkBinding, applyMetaBinding, applyNextBinding, applyOgBinding, applyOpenGraphBindings, applyPrevBinding, applyResourceHintsBindings, applyRobotsBinding, applySocialMediaBindings, applyStructuredDataBinding, applyTwitterCardBindings, boolBreakpointTransform, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, copyToClipboard, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDestroy, createDocumentElementSignal, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createIsRenderedSignal, createLogger, createObjectUrlHandle, createPropertyBinding, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSwatchCss, createSwipeTracker, createTailwindColorThemes, createTailwindCssVar, createTailwindRgbVar, createThemeStyle, createUnsavedChangesGuard, createUnsavedChangesTabLock, createUnsavedChangesTracker, createUserConsentProvider, deferredSignal, defineLabels, defineProvider, defineRootProvider, defineStaticProvider, defineStaticRootProvider, deleteCookie, dragGestureFrom, easeElastic, easeIn, easeInOut, easeLinear, easeOut, easeOutBack, easeOutBackStrong, elementCanScroll, enableAnchoredOverlayPositionExtras, equal, fetchDeployedBuildFingerprint$, firstElementSignal, focusElement, forceReflow, formatRuntimeError, fromNextFrame, getCookie, getDomain, getElementScrollCoordinates, getFocusableElements, getFormGroupValue, getGroupMatchPoints, getGroupMatchScore, getHeadingElement, getKnockoutMatchScore, getMatchScoreSubLine, getObjectProperty, getScrollContainerTarget, getScrollItemTarget, getScrollSnapTarget, hasAnchoredOverlayPositionExtras, hasCookie, holdScrollRestoration, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectAnimatedBlockSize, injectAnimatedListItems, injectAppUpdates, injectAppUpdatesConfig, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorPalette, injectColorScheme, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDefaultColorTheme, injectDefaultSurfaceTheme, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFaviconStore, injectFileDownload, injectFocusVisibleTracker, injectFragment, injectHasPrecisionInput, injectHasTouchInput, injectHostElement, injectIs2Xl, injectIsDocumentVisible, injectIsLandscape, injectIsLg, injectIsMd, injectIsPortrait, injectIsRouterInitialized, injectIsSm, injectIsXl, injectIsXs, injectLinkStore, injectLinkStoreConfig, injectLocale, injectMediaQueryIsMatched, injectMetaConfig, injectMetaStore, injectNotifications, injectObserveBreakpoint, injectObserveMediaQuery, injectOverlayRuntime, injectParentSurface, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamAll, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRootSurfaceTheme, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterNavigationState, injectRouterState, injectScrollbarDimensions, injectSemanticColorTheme, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceColorPalette, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectSurfaceType, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUnsavedChangesCoordinator, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElement, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isHTMLOrSVGElement, isKnockoutMatch, isObject, isOnHigherOverlayLayer, isSafeLinkUrl, isSafeUrl, isStaleBuildError, markdownToHtml, matchesReducedMotion, maybeSignalValue, memoizeSignal, mountEasingTokens, mountVisuallyHidden, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, normalizeUnsavedChangesSource, numberBreakpointTransform, onOverlayViewportInsetsChange, optionalBooleanAttribute, overlayViewportInsets, overlayViewportInsetsFor, ownsActiveElement, previousSignalValue, provideAppUpdates, provideAppUpdatesConfig, provideAppUpdatesInstance, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorPalette, provideColorScheme, provideColorThemes, provideColorThemesWithTailwind4, provideFaviconStore, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideNotifications, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideUnsavedChangesConfirm, provideUnsavedChangesCoordinator, provideViewportConfig, randomId, readBuildFingerprint, readFromClipboard, registerScrollRestorationState, reserveOverlayViewportSpace, resolveAppRootColorProvider, resolveColorThemeVariant, resolveOverlayLayer, resolveSurfaceByElevation, round, routerDisableScrollTop, routerRestoreScroll, scrollToElement, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalCountdown, signalDeferredLoading, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, suppressTextSelection, switchQueryListChanges, syncSignal, toArray, toArrayTrackByFn, toInjectFn, toProvideFn, toStringBinding, toToken, transformOrReturn, typedBreakpointTransform, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
10877
11175
  //# sourceMappingURL=ethlete-core.mjs.map