@ethlete/core 5.0.0-next.60 → 5.0.0-next.62

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.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { assertInInjectionContext, inject, DestroyRef, ElementRef, TemplateRef, InjectionToken, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, untracked, ApplicationRef, runInInjectionContext, NgZone, Injector, booleanAttribute, numberAttribute, PLATFORM_ID, EnvironmentInjector, createComponent, ViewEncapsulation, Component, Directive, model, ViewContainerRef, input, CSP_NONCE, provideEnvironmentInitializer, output, createEnvironmentInjector, Pipe, afterEveryRender } from '@angular/core';
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, 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, pairwise, debounceTime, merge, distinctUntilChanged, EMPTY, expand, skip, tap, take, filter, BehaviorSubject, combineLatest, Observable, isObservable, firstValueFrom, defaultIfEmpty, concatMap, concat, finalize, exhaustMap, share, shareReplay } 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, BehaviorSubject, combineLatest, Observable, isObservable, 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
- import { isPlatformBrowser, DOCUMENT as DOCUMENT$1 } from '@angular/common';
7
+ import { isPlatformBrowser, Location, DOCUMENT as DOCUMENT$1 } from '@angular/common';
8
8
  import { Router, NavigationEnd, NavigationSkipped, NavigationStart, NavigationError, RouterLink } from '@angular/router';
9
9
  import { Title, Meta, DomSanitizer } from '@angular/platform-browser';
10
- import { offset, flip, shift, limitShift, computePosition, autoUpdate, hide, arrow, size } from '@floating-ui/dom';
10
+ import { platform, offset, flip, shift, limitShift, computePosition, autoUpdate, hide, arrow, size } from '@floating-ui/dom';
11
11
 
12
12
  const ANIMATION_DEBUG_STORAGE_KEY = 'et-overlay-debug';
13
13
  let cachedEnabled = null;
@@ -63,6 +63,9 @@ const randomId = () => {
63
63
  return `et-id-${(randomIdFallbackCounter++).toString(36)}-${Date.now().toString(36)}`;
64
64
  };
65
65
 
66
+ /**
67
+ * @deprecated Use `takeUntilDestroyed()` from `@angular/core/rxjs-interop`. `yarn nx g @ethlete/core:migrate-create-destroy` rewrites the common pattern.
68
+ */
66
69
  const createDestroy = () => {
67
70
  assertInInjectionContext(createDestroy);
68
71
  const destroy$ = new Subject();
@@ -91,11 +94,13 @@ const getObjectProperty = (obj, prop) => {
91
94
  if (!isObject(value))
92
95
  return undefined;
93
96
  if (prop.includes('[')) {
94
- const [key, index] = prop.split('[').map((part) => part.replace(']', ''));
95
- const arr = value[key];
96
- if (!Array.isArray(arr))
97
- return undefined;
98
- value = arr[+index];
97
+ const [key, ...indexes] = prop.split('[').map((part) => part.replace(']', ''));
98
+ value = value[key];
99
+ for (const index of indexes) {
100
+ if (!Array.isArray(value))
101
+ return undefined;
102
+ value = value[+index];
103
+ }
99
104
  }
100
105
  else {
101
106
  value = value[prop];
@@ -187,7 +192,8 @@ const createValueProviders = (token, defaultValue, valueOverride, options) => [
187
192
  ];
188
193
  const maybeMergeValues = (defaultValue, valueOverride) => {
189
194
  if (isObject(valueOverride) && isObject(defaultValue)) {
190
- return { ...defaultValue, ...valueOverride };
195
+ const definedOverride = Object.fromEntries(Object.entries(valueOverride).filter(([, value]) => value !== undefined));
196
+ return { ...defaultValue, ...definedOverride };
191
197
  }
192
198
  return valueOverride ?? defaultValue;
193
199
  };
@@ -248,6 +254,12 @@ const applyHostListeners = (listeners, options) => {
248
254
  }
249
255
  };
250
256
 
257
+ function optionalBooleanAttribute(value) {
258
+ if (value === null || value === undefined)
259
+ return value;
260
+ return booleanAttribute(value);
261
+ }
262
+
251
263
  class TypedQueryList extends QueryList {
252
264
  get changes() {
253
265
  return super.changes;
@@ -540,7 +552,7 @@ const equal = (foo, bar) => {
540
552
  if (!tmp)
541
553
  return false;
542
554
  }
543
- if (!equal(len[1], bar.get(tmp))) {
555
+ if (!bar.has(tmp) || !equal(len[1], bar.get(tmp))) {
544
556
  return false;
545
557
  }
546
558
  }
@@ -580,6 +592,9 @@ const equal = (foo, bar) => {
580
592
  return foo !== foo && bar !== bar;
581
593
  };
582
594
 
595
+ // Only what a cookie value cannot hold is escaped, so base64 and JWT values stay byte-identical for
596
+ // servers and older readers that do not decode them.
597
+ const encodeCookieValue = (value) => encodeURIComponent(value).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent);
583
598
  const hasCookie = (name) => {
584
599
  if (typeof document === 'undefined') {
585
600
  return false;
@@ -593,38 +608,63 @@ const getCookie = (name) => {
593
608
  return null;
594
609
  }
595
610
  const cookie = document.cookie.split(';').find((entry) => entry.trim().startsWith(`${name}=`));
596
- return cookie?.trim().slice(name.length + 1) ?? null;
611
+ const raw = cookie?.trim().slice(name.length + 1);
612
+ if (raw === undefined)
613
+ return null;
614
+ try {
615
+ return decodeURIComponent(raw);
616
+ }
617
+ catch {
618
+ return raw;
619
+ }
597
620
  };
598
- const setCookie = (name, data, expiresInDays, domain = getDomain(), path = '/', sameSite = 'lax') => {
621
+ const setCookie = (name, data, expiresInDays, domain, path = '/', sameSite = 'lax') => {
599
622
  if (typeof document === 'undefined') {
600
623
  return;
601
624
  }
625
+ const derivedDomain = domain === undefined;
626
+ const resolvedDomain = derivedDomain ? getDomain() : domain;
602
627
  const sameSiteUpper = sameSite.toUpperCase();
603
628
  const expires = expiresInDays ?? 30;
604
- let cookieString = `${name}=${data}; path=${path}`;
629
+ let cookieString = `${name}=${encodeCookieValue(data)}; path=${path}`;
605
630
  if (expiresInDays !== null) {
606
631
  const date = new Date();
607
632
  date.setTime(date.getTime() + expires * 24 * 60 * 60 * 1000);
608
633
  cookieString += `; expires=${date.toUTCString()}`;
609
634
  }
610
- if (domain) {
611
- cookieString += `; domain=${domain}`;
612
- }
613
- cookieString += `; SameSite=${sameSiteUpper}`;
635
+ const attributes = (withDomain) => {
636
+ let result = cookieString;
637
+ if (withDomain && resolvedDomain) {
638
+ result += `; domain=${resolvedDomain}`;
639
+ }
640
+ return result;
641
+ };
642
+ let suffix = `; SameSite=${sameSiteUpper}`;
614
643
  if (sameSite === 'none' || (typeof window !== 'undefined' && window.location.protocol === 'https:')) {
615
- cookieString += '; Secure';
644
+ suffix += '; Secure';
645
+ }
646
+ suffix += ';';
647
+ document.cookie = attributes(true) + suffix;
648
+ if (derivedDomain && resolvedDomain && getCookie(name) !== data) {
649
+ document.cookie = attributes(false) + suffix;
616
650
  }
617
- cookieString += ';';
618
- document.cookie = cookieString;
619
651
  };
620
- const deleteCookie = (name, path = '/', domain = getDomain()) => {
621
- if (hasCookie(name)) {
652
+ const deleteCookie = (name, path = '/', domain) => {
653
+ if (!hasCookie(name))
654
+ return;
655
+ const derivedDomain = domain === undefined;
656
+ const resolvedDomain = derivedDomain ? getDomain() : domain;
657
+ const expire = (withDomain) => {
622
658
  document.cookie =
623
659
  name +
624
660
  '=' +
625
661
  (path ? ';path=' + path : '') +
626
- (domain ? ';domain=' + domain : '') +
662
+ (withDomain && resolvedDomain ? ';domain=' + resolvedDomain : '') +
627
663
  ';expires=Thu, 01 Jan 1970 00:00:01 GMT';
664
+ };
665
+ expire(true);
666
+ if (derivedDomain && hasCookie(name)) {
667
+ expire(false);
628
668
  }
629
669
  };
630
670
  const getDomain = () => {
@@ -646,6 +686,7 @@ const getDomain = () => {
646
686
  return hostname;
647
687
  };
648
688
 
689
+ /** @deprecated Keyed on the deprecated `KeyboardEvent.keyCode`. Compare `KeyboardEvent.key` instead. */
649
690
  class KeyPressManager {
650
691
  constructor(key) {
651
692
  this.key = key;
@@ -660,8 +701,8 @@ class KeyPressManager {
660
701
  this.keyPressCount++;
661
702
  this._stopTimeout$.next();
662
703
  timer(100)
663
- .pipe(takeUntil(this._stopTimeout$))
664
- .subscribe(() => this.clear());
704
+ .pipe(takeUntil(this._stopTimeout$), tap(() => this.clear()))
705
+ .subscribe();
665
706
  }
666
707
  else {
667
708
  this.clear();
@@ -1493,7 +1534,7 @@ const signalAnimatedNumber = (source, options = {}) => {
1493
1534
  if (destroyed)
1494
1535
  return;
1495
1536
  const elapsed = timestamp - startTime;
1496
- const progress = Math.min(elapsed / duration, 1);
1537
+ const progress = duration > 0 ? Math.min(Math.max(elapsed / duration, 0), 1) : 1;
1497
1538
  const easedProgress = easing(progress);
1498
1539
  const value = startValue + delta * easedProgress;
1499
1540
  currentAnimatedValue = value;
@@ -1693,24 +1734,36 @@ const resolveFromMap = (map, bp, defaultValue) => {
1693
1734
  }
1694
1735
  return defaultValue;
1695
1736
  };
1737
+ /** Builds an input transform that coerces a value or a per-breakpoint map to `T` for the current breakpoint. */
1696
1738
  const breakpointTransformBase = (coerce, defaultValue) => {
1697
1739
  const currentBp = injectCurrentBreakpoint();
1698
1740
  const injector = inject(Injector);
1699
1741
  const raw = signal(undefined, /* @ts-ignore */
1700
1742
  ...(ngDevMode ? [{ debugName: "raw" }] : /* istanbul ignore next */ []));
1743
+ let warnedMissingInstance = false;
1701
1744
  let cachedSig = null;
1702
1745
  const transformFn = (value) => {
1703
- const coerced = isBreakpointMap(value) ? value : coerce(value);
1704
- raw.set(coerced);
1705
- return isBreakpointMap(coerced) ? resolveFromMap(coerced, currentBp(), defaultValue) : coerced;
1746
+ if (isBreakpointMap(value)) {
1747
+ const map = value;
1748
+ raw.set(map);
1749
+ return resolveFromMap(map, currentBp(), defaultValue);
1750
+ }
1751
+ raw.set(undefined);
1752
+ return coerce(value);
1706
1753
  };
1707
1754
  effect(() => {
1708
1755
  const bp = currentBp();
1709
1756
  const r = raw();
1710
1757
  if (!cachedSig) {
1711
1758
  const instance = injector.get(BREAKPOINT_INSTANCE_TOKEN, null);
1712
- if (!instance)
1759
+ if (!instance) {
1760
+ if (ngDevMode && r !== undefined && !warnedMissingInstance) {
1761
+ warnedMissingInstance = true;
1762
+ console.warn('[ethlete] A breakpoint map was bound to an input, but its component does not call ' +
1763
+ 'provideBreakpointInstance(), so the value will not follow the breakpoint after the first render.');
1764
+ }
1713
1765
  return;
1766
+ }
1714
1767
  for (const key of Object.keys(instance)) {
1715
1768
  const val = instance[key];
1716
1769
  if (val && typeof val === 'function' && val[SIGNAL]?.transformFn === transformFn) {
@@ -1719,7 +1772,7 @@ const breakpointTransformBase = (coerce, defaultValue) => {
1719
1772
  }
1720
1773
  }
1721
1774
  }
1722
- if (!cachedSig || r === undefined || !isBreakpointMap(r))
1775
+ if (!cachedSig || r === undefined)
1723
1776
  return;
1724
1777
  const resolved = resolveFromMap(r, bp, defaultValue);
1725
1778
  untracked(() => setInputSignal(cachedSig, resolved));
@@ -1852,8 +1905,8 @@ const signalCountdown = (deadline) => {
1852
1905
  const remainingMs = endsAt - Date.now();
1853
1906
  return remainingMs <= 0 ? EMPTY : timer(remainingMs % SECOND_MS || SECOND_MS);
1854
1907
  }), skip(1));
1855
- }), takeUntilDestroyed())
1856
- .subscribe(() => tick.update((count) => count + 1));
1908
+ }), tap(() => tick.update((count) => count + 1)), takeUntilDestroyed())
1909
+ .subscribe();
1857
1910
  }
1858
1911
  return computed(() => {
1859
1912
  tick();
@@ -1897,13 +1950,13 @@ const signalDeferredLoading = (source, options) => {
1897
1950
  }
1898
1951
  const remaining = minDuration - (performance.now() - shownAt);
1899
1952
  return remaining <= 0 ? of(false) : timer(remaining).pipe(map(() => false));
1900
- }), takeUntilDestroyed())
1901
- .subscribe((isVisible) => {
1953
+ }), tap((isVisible) => {
1902
1954
  if (isVisible) {
1903
1955
  shownAt = performance.now();
1904
1956
  }
1905
1957
  visible.set(isVisible);
1906
- });
1958
+ }), takeUntilDestroyed())
1959
+ .subscribe();
1907
1960
  return visible.asReadonly();
1908
1961
  };
1909
1962
 
@@ -1948,8 +2001,6 @@ const signalElementChildren = (el, options) => {
1948
2001
  if (!isRendered())
1949
2002
  return [];
1950
2003
  const els = firstEl();
1951
- // We are not interested what the mutation is, just that there is one.
1952
- // Changes to the DOM may affect the children of the element.
1953
2004
  elementMutations();
1954
2005
  if (!els.currentElement)
1955
2006
  return [];
@@ -1967,14 +2018,13 @@ const signalElementChildren = (el, options) => {
1967
2018
  const buildSignalEffects = (el, config) => {
1968
2019
  const elements = buildElementSignal(el);
1969
2020
  const injector = inject(Injector);
2021
+ const liveTokens = new Map(Object.entries(config.tokenMap));
1970
2022
  effect(() => {
1971
2023
  const { currentElements, previousElements } = elements();
1972
2024
  for (const previousEl of previousElements) {
1973
2025
  if (currentElements.includes(previousEl))
1974
2026
  continue;
1975
- const tokens = Object.keys(config.tokenMap)
1976
- .map((key) => key.split(' '))
1977
- .flat();
2027
+ const tokens = [...liveTokens.keys()].flatMap((key) => key.split(' '));
1978
2028
  if (!tokens.length)
1979
2029
  continue;
1980
2030
  config.cleanupFn(previousEl, tokens);
@@ -1982,7 +2032,7 @@ const buildSignalEffects = (el, config) => {
1982
2032
  for (const currentEl of currentElements) {
1983
2033
  if (previousElements.includes(currentEl))
1984
2034
  continue;
1985
- for (const [tokens, condition] of Object.entries(config.tokenMap)) {
2035
+ for (const [tokens, condition] of liveTokens) {
1986
2036
  untracked(() => {
1987
2037
  const tokenArray = tokens.split(' ');
1988
2038
  if (!tokenArray.length)
@@ -1997,6 +2047,7 @@ const buildSignalEffects = (el, config) => {
1997
2047
  const push = (tokens, signal) => {
1998
2048
  if (has(tokens))
1999
2049
  return;
2050
+ liveTokens.set(tokens, signal);
2000
2051
  runInInjectionContext(injector, () => {
2001
2052
  effects[tokens] = effect(() => {
2002
2053
  const { currentElements } = untracked(() => elements());
@@ -2018,6 +2069,7 @@ const buildSignalEffects = (el, config) => {
2018
2069
  const remove = (tokens) => {
2019
2070
  effects[tokens]?.destroy();
2020
2071
  delete effects[tokens];
2072
+ liveTokens.delete(tokens);
2021
2073
  for (const el of elements().currentElements) {
2022
2074
  const tokenArray = tokens.split(' ');
2023
2075
  if (!tokenArray.length)
@@ -2099,8 +2151,6 @@ const signalStyles = (el, styleMap) => {
2099
2151
  const signalHostStyles = (styleMap) => signalStyles(inject(ElementRef), styleMap);
2100
2152
 
2101
2153
  const elementCanScroll = (element, direction) => {
2102
- // Omitting the element means "the document scroller". Resolved lazily and guarded so a call on the
2103
- // server answers "cannot scroll" instead of throwing on a missing `document`.
2104
2154
  const el = element || (typeof document === 'undefined' ? null : document.documentElement);
2105
2155
  if (!el) {
2106
2156
  return false;
@@ -2115,9 +2165,6 @@ const elementCanScroll = (element, direction) => {
2115
2165
  return scrollHeight > clientHeight || scrollWidth > clientWidth;
2116
2166
  };
2117
2167
  const createViewportRect = () => {
2118
- // Collapses to a zero rect where there is no window (server). `isElementVisible` never reaches the
2119
- // intersection math there anyway - `elementCanScroll` already reports the document as unscrollable
2120
- // - so the rect only has to be a valid shape.
2121
2168
  const hasWindow = typeof window !== 'undefined';
2122
2169
  const width = hasWindow ? window.innerWidth : 0;
2123
2170
  const height = hasWindow ? window.innerHeight : 0;
@@ -2169,9 +2216,22 @@ const isElementVisible = (options) => {
2169
2216
  intersectionRatio: Math.round(Math.min(inlineIntersectionPercentage, blockIntersectionPercentage) * 100) / 100,
2170
2217
  };
2171
2218
  };
2219
+ const nearestAlignment = (elStart, elEnd, conStart, conEnd) => {
2220
+ const startOutside = elStart < conStart;
2221
+ const endOutside = elEnd > conEnd;
2222
+ const fits = elEnd - elStart <= conEnd - conStart;
2223
+ if (startOutside === endOutside)
2224
+ return null;
2225
+ if (startOutside === fits)
2226
+ return 'start';
2227
+ return 'end';
2228
+ };
2172
2229
  const getElementScrollCoordinates = (options) => {
2173
2230
  const { container, element, direction, behavior = 'smooth', origin = 'nearest', scrollBlockMargin = 0, scrollInlineMargin = 0, } = options;
2174
- if (!element || !container || !elementCanScroll(container)) {
2231
+ if (!element ||
2232
+ !container ||
2233
+ (container.isConnected && !container.contains(element)) ||
2234
+ !elementCanScroll(container)) {
2175
2235
  return {
2176
2236
  behavior,
2177
2237
  left: undefined,
@@ -2199,33 +2259,33 @@ const getElementScrollCoordinates = (options) => {
2199
2259
  let scrollTopTo = scrollTop;
2200
2260
  const relativeTop = elTop - conTop;
2201
2261
  const relativeLeft = elLeft - conLeft;
2262
+ const calculateInlineStart = () => (scrollLeftTo = scrollLeft + relativeLeft - scrollInlineMargin);
2263
+ const calculateBlockStart = () => (scrollTopTo = scrollTop + relativeTop - scrollBlockMargin);
2264
+ const calculateInlineEnd = () => (scrollLeftTo = scrollLeft + relativeLeft - conWidth + elWidth + scrollInlineMargin);
2265
+ const calculateBlockEnd = () => (scrollTopTo = scrollTop + relativeTop - conHeight + elHeight + scrollBlockMargin);
2202
2266
  const calculateScrollToStart = () => {
2203
- scrollLeftTo = scrollLeft + relativeLeft - scrollInlineMargin;
2204
- scrollTopTo = scrollTop + relativeTop - scrollBlockMargin;
2267
+ calculateInlineStart();
2268
+ calculateBlockStart();
2205
2269
  };
2206
2270
  const calculateScrollToEnd = () => {
2207
- scrollLeftTo = scrollLeft + relativeLeft - conWidth + elWidth + scrollInlineMargin;
2208
- scrollTopTo = scrollTop + relativeTop - conHeight + elHeight + scrollBlockMargin;
2271
+ calculateInlineEnd();
2272
+ calculateBlockEnd();
2209
2273
  };
2210
2274
  const calculateScrollToCenter = () => {
2211
2275
  scrollLeftTo = scrollLeft + relativeLeft - conWidth / 2 + elWidth / 2;
2212
2276
  scrollTopTo = scrollTop + relativeTop - conHeight / 2 + elHeight / 2;
2213
2277
  };
2214
2278
  const calculateScrollToNearest = () => {
2215
- const isAbove = elBottom < conTop;
2216
- const isPartialAbove = elTop < conTop && elBottom > conTop;
2217
- const isBelow = elTop > conBottom;
2218
- const isPartialBelow = elTop < conBottom && elBottom > conBottom;
2219
- const isLeft = elRight < conLeft;
2220
- const isPartialLeft = elLeft < conLeft && elRight > conLeft;
2221
- const isRight = elLeft > conRight;
2222
- const isPartialRight = elLeft < conRight && elRight > conRight;
2223
- if (isAbove || isPartialAbove || isLeft || isPartialLeft) {
2224
- calculateScrollToStart();
2225
- }
2226
- else if (isBelow || isPartialBelow || isRight || isPartialRight) {
2227
- calculateScrollToEnd();
2228
- }
2279
+ const inline = nearestAlignment(elLeft, elRight, conLeft, conRight);
2280
+ const block = nearestAlignment(elTop, elBottom, conTop, conBottom);
2281
+ if (inline === 'start')
2282
+ calculateInlineStart();
2283
+ else if (inline === 'end')
2284
+ calculateInlineEnd();
2285
+ if (block === 'start')
2286
+ calculateBlockStart();
2287
+ else if (block === 'end')
2288
+ calculateBlockEnd();
2229
2289
  };
2230
2290
  switch (origin) {
2231
2291
  case 'start':
@@ -2252,6 +2312,7 @@ const scrollToElement = (options) => {
2252
2312
  };
2253
2313
 
2254
2314
  const MIN_SNAP_DELTA_PX = 1;
2315
+ const FULLY_VISIBLE_RATIO = 0.99;
2255
2316
  const getScrollSnapTarget = (items, container, direction, origin, margin = 0) => {
2256
2317
  if (!items.length)
2257
2318
  return null;
@@ -2371,7 +2432,7 @@ const getScrollItemTarget = (entries, container, direction, scrollOrigin, axisDi
2371
2432
  }
2372
2433
  const entry = direction === 'start' ? firstVisible : lastVisible;
2373
2434
  const entryIndex = direction === 'start' ? firstIndex : lastIndex;
2374
- if (Math.round(entry.intersectionRatio) === 1) {
2435
+ if (entry.intersectionRatio >= FULLY_VISIBLE_RATIO) {
2375
2436
  if (direction === 'start' && entryIndex === 0)
2376
2437
  return null;
2377
2438
  if (direction === 'end' && entryIndex === entries.length - 1)
@@ -2408,8 +2469,10 @@ const createPositionObject = (entry) => {
2408
2469
  isVisible,
2409
2470
  };
2410
2471
  };
2472
+ const unwrap = (value) => (isSignal(value) ? value() : value);
2473
+ const isZeroMargin = (margin) => !margin || /^(0(px|%)?\s*)+$/.test(margin.trim());
2411
2474
  const signalElementIntersection = (el, options) => {
2412
- const { root: rootBinding, enabled, ...observerOptions } = options ?? {};
2475
+ const { root: rootBinding, enabled, rootMargin, threshold, ...observerOptions } = options ?? {};
2413
2476
  const destroyRef = inject(DestroyRef);
2414
2477
  const elements = buildElementSignal(el);
2415
2478
  const root = firstElementSignal(rootBinding ? buildElementSignal(rootBinding) : createEmptyElementSignal());
@@ -2421,6 +2484,7 @@ const signalElementIntersection = (el, options) => {
2421
2484
  const observer = signal(null, /* @ts-ignore */
2422
2485
  ...(ngDevMode ? [{ debugName: "observer" }] : /* istanbul ignore next */ []));
2423
2486
  const currentlyObservedElements = new Set();
2487
+ let observedRootMargin;
2424
2488
  const updateIntersections = (entries) => {
2425
2489
  let currentValues = [...elementIntersectionSignal()];
2426
2490
  for (const entry of entries) {
@@ -2450,7 +2514,7 @@ const signalElementIntersection = (el, options) => {
2450
2514
  }
2451
2515
  zone.run(() => elementIntersectionSignal.set(currentValues));
2452
2516
  };
2453
- const updateIntersectionObserver = (rendered, enabled, rootEl) => {
2517
+ const updateIntersectionObserver = (rendered, enabled, rootEl, margin, thresholdValue) => {
2454
2518
  observer()?.disconnect();
2455
2519
  currentlyObservedElements.clear();
2456
2520
  if (!rendered || !enabled) {
@@ -2460,8 +2524,11 @@ const signalElementIntersection = (el, options) => {
2460
2524
  }
2461
2525
  const newObserver = new IntersectionObserver((entries) => updateIntersections(entries), {
2462
2526
  ...observerOptions,
2527
+ ...(margin !== undefined && { rootMargin: margin }),
2528
+ ...(thresholdValue !== undefined && { threshold: thresholdValue }),
2463
2529
  root: rootEl,
2464
2530
  });
2531
+ observedRootMargin = margin;
2465
2532
  observer.set(newObserver);
2466
2533
  };
2467
2534
  const updateObservedElements = (observer, elements) => {
@@ -2474,15 +2541,16 @@ const signalElementIntersection = (el, options) => {
2474
2541
  const newIntersectionValue = [];
2475
2542
  for (const el of elements.currentElements) {
2476
2543
  if (currentlyObservedElements.has(el)) {
2477
- const existingEntryIndex = currIntersectionValue.findIndex((v) => v.target === el);
2478
- const existingEntry = currIntersectionValue[existingEntryIndex];
2479
- if (!existingEntry) {
2480
- console.warn('Could not find existing entry for element. The intersection observer might be broken now.', el);
2481
- continue;
2482
- }
2483
- newIntersectionValue.push(existingEntry);
2544
+ const existingEntry = currIntersectionValue.find((v) => v.target === el);
2545
+ if (existingEntry)
2546
+ newIntersectionValue.push(existingEntry);
2484
2547
  continue;
2485
2548
  }
2549
+ currentlyObservedElements.add(el);
2550
+ observer.observe(el);
2551
+ // isElementVisible ignores rootMargin, so with one set the observer's own first entry is the only correct one.
2552
+ if (!isZeroMargin(observedRootMargin))
2553
+ continue;
2486
2554
  const initialElementVisibility = isElementVisible({
2487
2555
  container: rootEl,
2488
2556
  element: el,
@@ -2502,8 +2570,6 @@ const signalElementIntersection = (el, options) => {
2502
2570
  ...intersectionEntry,
2503
2571
  ...createPositionObject(intersectionEntry),
2504
2572
  });
2505
- currentlyObservedElements.add(el);
2506
- observer.observe(el);
2507
2573
  }
2508
2574
  for (const el of elements.previousElements) {
2509
2575
  if (elements.currentElements.includes(el))
@@ -2517,9 +2583,13 @@ const signalElementIntersection = (el, options) => {
2517
2583
  const rootEl = root().currentElement;
2518
2584
  const rendered = isRendered();
2519
2585
  const enabled = isEnabled();
2520
- untracked(() => updateIntersectionObserver(rendered, enabled, rootEl));
2586
+ const margin = rootMargin === undefined ? undefined : unwrap(rootMargin);
2587
+ const thresholdValue = threshold === undefined ? undefined : unwrap(threshold);
2588
+ untracked(() => updateIntersectionObserver(rendered, enabled, rootEl, margin, thresholdValue));
2521
2589
  });
2522
- effect(() => {
2590
+ // The initial entries are measured here, not in an effect: an effect runs during change detection,
2591
+ // before elements rendered in the same pass have their layout.
2592
+ afterRenderEffect(() => {
2523
2593
  const els = elements();
2524
2594
  const obs = observer();
2525
2595
  untracked(() => updateObservedElements(obs, els));
@@ -2545,6 +2615,8 @@ const signalElementLastScrollDirection = (el) => {
2545
2615
  lastScrollLeft = 0;
2546
2616
  return of(null);
2547
2617
  }
2618
+ lastScrollTop = currentElement.scrollTop;
2619
+ lastScrollLeft = currentElement.scrollLeft;
2548
2620
  return fromEvent(currentElement, 'scroll').pipe(tap(() => {
2549
2621
  const { scrollTop, scrollLeft } = currentElement;
2550
2622
  const time = Date.now();
@@ -2623,8 +2695,6 @@ const signalElementScrollState = (el, options) => {
2623
2695
  return computed(() => {
2624
2696
  const element = observedEl().currentElement;
2625
2697
  const dimensions = elementDimensions();
2626
- // We are not interested what the mutation is, just that there is one.
2627
- // Changes to the DOM can affect the scroll state of the element.
2628
2698
  elementMutations();
2629
2699
  if (!element || !isRendered())
2630
2700
  return notScrollable(dimensions);
@@ -2651,11 +2721,12 @@ const writeScrollbarSizeToCssVariables = () => {
2651
2721
  if (!isPlatformBrowser(inject(PLATFORM_ID))) {
2652
2722
  return;
2653
2723
  }
2654
- const environmentInjector = inject(EnvironmentInjector);
2655
- if (scrollbarSizeWriters.has(environmentInjector)) {
2724
+ // The app's injector, not the caller's: a writer started from a component must outlive it.
2725
+ const appInjector = inject(ApplicationRef).injector;
2726
+ if (scrollbarSizeWriters.has(appInjector)) {
2656
2727
  return;
2657
2728
  }
2658
- scrollbarSizeWriters.add(environmentInjector);
2729
+ scrollbarSizeWriters.add(appInjector);
2659
2730
  const document = inject(DOCUMENT);
2660
2731
  const renderer = injectRenderer();
2661
2732
  const scrollbarDimensions = injectScrollbarDimensions();
@@ -2667,7 +2738,7 @@ const writeScrollbarSizeToCssVariables = () => {
2667
2738
  '--et-sw': `${dimensions.width}px`,
2668
2739
  '--et-sh': `${dimensions.height}px`,
2669
2740
  });
2670
- });
2741
+ }, { injector: appInjector });
2671
2742
  };
2672
2743
  const viewportSizeWriters = /* @__PURE__ */ new WeakSet();
2673
2744
  /**
@@ -2679,11 +2750,11 @@ const writeViewportSizeToCssVariables = () => {
2679
2750
  if (!isPlatformBrowser(inject(PLATFORM_ID))) {
2680
2751
  return;
2681
2752
  }
2682
- const environmentInjector = inject(EnvironmentInjector);
2683
- if (viewportSizeWriters.has(environmentInjector)) {
2753
+ const appInjector = inject(ApplicationRef).injector;
2754
+ if (viewportSizeWriters.has(appInjector)) {
2684
2755
  return;
2685
2756
  }
2686
- viewportSizeWriters.add(environmentInjector);
2757
+ viewportSizeWriters.add(appInjector);
2687
2758
  const document = inject(DOCUMENT);
2688
2759
  const renderer = injectRenderer();
2689
2760
  const htmlElementDimensions = injectViewportDimensions();
@@ -2695,7 +2766,7 @@ const writeViewportSizeToCssVariables = () => {
2695
2766
  '--et-vw': `${dimensions.width}px`,
2696
2767
  '--et-vh': `${dimensions.height}px`,
2697
2768
  });
2698
- });
2769
+ }, { injector: appInjector });
2699
2770
  };
2700
2771
 
2701
2772
  /** The deadzone in pixels after which the cursor drag scroll will take effect. */
@@ -2745,14 +2816,12 @@ const useCursorDragScroll = (el, options) => {
2745
2816
  // A host destroyed mid-drag never re-runs the styling effect, so the grabbing cursor it wrote on
2746
2817
  // `<html>` would outlive the page it was dragged on.
2747
2818
  destroyRef.onDestroy(() => renderer.removeStyle(document.documentElement, 'cursor'));
2748
- // Cleanup if the element the cursor drag scroll is bound to gets changed
2749
2819
  effect(() => {
2750
2820
  const { previousElement } = element();
2751
2821
  if (previousElement) {
2752
2822
  renderer.removeStyle(previousElement, 'cursor');
2753
2823
  }
2754
2824
  });
2755
- // Conditionally apply styles/classes to the element and the document
2756
2825
  effect(() => {
2757
2826
  const currCanScroll = canScroll();
2758
2827
  const isEnabled = enabled();
@@ -2792,7 +2861,6 @@ const useCursorDragScroll = (el, options) => {
2792
2861
  }
2793
2862
  });
2794
2863
  });
2795
- // Update the element's scroll position when the user drags
2796
2864
  effect(() => {
2797
2865
  const currDragAmount = dragAmount();
2798
2866
  untracked(() => {
@@ -2886,7 +2954,8 @@ const createInitialRoute = () => {
2886
2954
  if (!isBrowser) {
2887
2955
  return router.url;
2888
2956
  }
2889
- return window.location.pathname + window.location.search + window.location.hash;
2957
+ const path = inject(Location).path(true);
2958
+ return path.startsWith('/') ? path : `/${path}`;
2890
2959
  };
2891
2960
  /** Inject the current router event */
2892
2961
  const injectRouterEvent = /* @__PURE__ */ memoizeSignal(() => {
@@ -2953,6 +3022,7 @@ const injectRoute = /* @__PURE__ */ memoizeSignal(() => {
2953
3022
  return withoutFragment;
2954
3023
  });
2955
3024
  });
3025
+ const withInjector = (config, fn) => config?.injector ? runInInjectionContext(config.injector, fn) : fn();
2956
3026
  const createRouterState = (router) => {
2957
3027
  let route = router.routerState.snapshot.root;
2958
3028
  while (route.firstChild) {
@@ -2992,37 +3062,35 @@ const injectRouterState = /* @__PURE__ */ memoizeSignal(() => {
2992
3062
  });
2993
3063
  /** Inject a signal containing the current route fragment (the part after the # inside the url if present) */
2994
3064
  const injectFragment = (config) => {
2995
- const routerState = config?.injector
2996
- ? runInInjectionContext(config.injector, injectRouterState)
2997
- : injectRouterState();
3065
+ const routerState = withInjector(config, injectRouterState);
2998
3066
  const fragment = computed(() => routerState().fragment, /* @ts-ignore */
2999
3067
  ...(ngDevMode ? [{ debugName: "fragment" }] : /* istanbul ignore next */ []));
3000
3068
  return transformOrReturn(fragment, config);
3001
3069
  };
3002
3070
  /** Inject all currently available query parameters as a signal */
3003
- const injectQueryParams = () => {
3004
- const routerState = injectRouterState();
3071
+ const injectQueryParams = (config) => {
3072
+ const routerState = withInjector(config, injectRouterState);
3005
3073
  const queryParams = computed(() => routerState().queryParams, /* @ts-ignore */
3006
3074
  ...(ngDevMode ? [{ debugName: "queryParams" }] : /* istanbul ignore next */ []));
3007
3075
  return queryParams;
3008
3076
  };
3009
3077
  /** Inject all currently available route data as a signal */
3010
- const injectRouteData = () => {
3011
- const routerState = injectRouterState();
3078
+ const injectRouteData = (config) => {
3079
+ const routerState = withInjector(config, injectRouterState);
3012
3080
  const data = computed(() => routerState().data, /* @ts-ignore */
3013
3081
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
3014
3082
  return data;
3015
3083
  };
3016
3084
  /** Inject the current route title as a signal */
3017
3085
  const injectRouteTitle = (config) => {
3018
- const routerState = injectRouterState();
3086
+ const routerState = withInjector(config, injectRouterState);
3019
3087
  const title = computed(() => routerState().title, /* @ts-ignore */
3020
3088
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
3021
3089
  return transformOrReturn(title, config);
3022
3090
  };
3023
3091
  /** Inject all currently available path parameters as a signal */
3024
- const injectPathParams = () => {
3025
- const routerState = injectRouterState();
3092
+ const injectPathParams = (config) => {
3093
+ const routerState = withInjector(config, injectRouterState);
3026
3094
  const pathParams = computed(() => routerState().pathParams, /* @ts-ignore */
3027
3095
  ...(ngDevMode ? [{ debugName: "pathParams" }] : /* istanbul ignore next */ []));
3028
3096
  return pathParams;
@@ -3033,21 +3101,48 @@ const getQueryParamFromUrl = (key) => {
3033
3101
  const urlParams = new URLSearchParams(window.location.search);
3034
3102
  return urlParams.get(key);
3035
3103
  };
3036
- /** Inject a specific query parameter as a signal */
3104
+ const getAllQueryParamsFromUrl = (key) => {
3105
+ if (typeof window === 'undefined')
3106
+ return [];
3107
+ return new URLSearchParams(window.location.search).getAll(key);
3108
+ };
3109
+ const toValues = (value) => {
3110
+ if (value === undefined || value === null)
3111
+ return [];
3112
+ return Array.isArray(value) ? value : [value];
3113
+ };
3114
+ /**
3115
+ * Inject a specific query parameter as a signal. A repeated parameter (`?tag=a&tag=b`) resolves to its first value;
3116
+ * use `injectQueryParamAll` to read every value.
3117
+ */
3037
3118
  const injectQueryParam = (key, config) => {
3038
- const queryParams = injectQueryParams();
3039
- const src = computed(() => queryParams()[key] ?? (config?.requireSync ? getQueryParamFromUrl(key) : null));
3119
+ const queryParams = injectQueryParams(config);
3120
+ const src = computed(() => {
3121
+ const first = toValues(queryParams()[key])[0];
3122
+ return first ?? (config?.requireSync ? getQueryParamFromUrl(key) : null);
3123
+ }, /* @ts-ignore */
3124
+ ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
3125
+ return transformOrReturn(src, config);
3126
+ };
3127
+ /** Inject every value of a specific query parameter as a signal. Resolves to an empty array when it is absent. */
3128
+ const injectQueryParamAll = (key, config) => {
3129
+ const queryParams = injectQueryParams(config);
3130
+ const src = computed(() => {
3131
+ const values = toValues(queryParams()[key]);
3132
+ return values.length || !config?.requireSync ? values : getAllQueryParamsFromUrl(key);
3133
+ }, /* @ts-ignore */
3134
+ ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
3040
3135
  return transformOrReturn(src, config);
3041
3136
  };
3042
3137
  /** Inject a specific route data item as a signal */
3043
3138
  const injectRouteDataItem = (key, config) => {
3044
- const data = injectRouteData();
3139
+ const data = injectRouteData(config);
3045
3140
  const src = computed(() => data()[key] ?? null);
3046
3141
  return transformOrReturn(src, config);
3047
3142
  };
3048
3143
  /** Inject a specific path parameter as a signal */
3049
3144
  const injectPathParam = (key, config) => {
3050
- const pathParams = injectPathParams();
3145
+ const pathParams = injectPathParams(config);
3051
3146
  const src = computed(() => pathParams()[key] ?? null);
3052
3147
  return transformOrReturn(src, config);
3053
3148
  };
@@ -3120,9 +3215,9 @@ const injectPathParamChanges = /* @__PURE__ */ memoizeSignal(() => {
3120
3215
  * protected cameFromCart = this.navState?.fromCart ?? false;
3121
3216
  * }
3122
3217
  */
3123
- const injectRouterNavigationState = () => {
3124
- const router = inject(Router);
3125
- return router.getCurrentNavigation()?.extras.state ?? null;
3218
+ const injectRouterNavigationState = (config) => {
3219
+ const router = withInjector(config, () => inject(Router));
3220
+ return router.currentNavigation()?.extras.state ?? null;
3126
3221
  };
3127
3222
 
3128
3223
  const ET_DISABLE_SCROLL_TOP = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP');
@@ -3351,7 +3446,7 @@ const setupScrollRestoration = (config = {}) => {
3351
3446
  // Read off the navigation rather than `history.state`: a symbol key does not survive being
3352
3447
  // structured-cloned into the history entry, which is what keeps the mark from outliving this
3353
3448
  // navigation and re-firing when the user later pops back to the entry it created.
3354
- const navigationState = router.getCurrentNavigation()?.extras.state;
3449
+ const navigationState = router.currentNavigation()?.extras.state;
3355
3450
  wantsRestore = !isPopstate && navigationState?.[ET_RESTORE_SCROLL] === true;
3356
3451
  };
3357
3452
  const onNavigationEnd = (event) => {
@@ -3425,14 +3520,14 @@ const setupScrollRestoration = (config = {}) => {
3425
3520
  }
3426
3521
  };
3427
3522
  router.events
3428
- .pipe(filter((e) => e instanceof NavigationStart || e instanceof NavigationEnd || e instanceof NavigationSkipped), takeUntilDestroyed())
3429
- .subscribe((event) => {
3523
+ .pipe(filter((e) => e instanceof NavigationStart || e instanceof NavigationEnd || e instanceof NavigationSkipped), tap((event) => {
3430
3524
  if (event instanceof NavigationStart) {
3431
3525
  onNavigationStart(event);
3432
3526
  return;
3433
3527
  }
3434
3528
  onNavigationEnd(event);
3435
- });
3529
+ }), takeUntilDestroyed())
3530
+ .subscribe();
3436
3531
  destroyRef.onDestroy(() => {
3437
3532
  cancelPendingRestore();
3438
3533
  if (restoreEnabled && previousScrollRestoration !== null) {
@@ -3444,7 +3539,7 @@ const setupScrollRestoration = (config = {}) => {
3444
3539
  const DISABLE_LOGGER_PARAM = 'et-logger-quiet';
3445
3540
  const createLogger = (config) => {
3446
3541
  const { scope, feature } = config;
3447
- const disableLogging = injectQueryParam(DISABLE_LOGGER_PARAM);
3542
+ const disableLogging = injectQueryParam(DISABLE_LOGGER_PARAM, { transform: (value) => value !== null });
3448
3543
  return {
3449
3544
  log: (...args) => {
3450
3545
  if (!disableLogging())
@@ -3470,15 +3565,31 @@ const escapeHtmlPreservingEntities = (str) => str
3470
3565
  .replace(/</g, '&lt;')
3471
3566
  .replace(/>/g, '&gt;')
3472
3567
  .replace(/"/g, '&quot;');
3568
+ /** Attribute that makes `htmlToMarkdown` write an element's text verbatim, without Markdown escaping:
3569
+ * `<span data-markdown-verbatim>{{user:_x}}</span>` becomes `{{user:_x}}`. The element must hold
3570
+ * text only. */
3571
+ const MARKDOWN_VERBATIM_ATTR = 'data-markdown-verbatim';
3572
+ const VERBATIM_RE = /* @__PURE__ */ new RegExp(`<span\\b[^>]*\\b${MARKDOWN_VERBATIM_ATTR}\\b[^>]*>([^<]*)<\\/span>`, 'gi');
3573
+ const codePointToString = (codePoint) => (codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : '\ufffd');
3473
3574
  const decodeUrlCharacterReferences = (url) => url
3474
- .replace(/&#(\d+);?/g, (_, value) => String.fromCodePoint(Number(value)))
3475
- .replace(/&#x([0-9a-f]+);?/gi, (_, value) => String.fromCodePoint(Number.parseInt(value, 16)))
3575
+ .replace(/&#(\d+);?/g, (_, value) => codePointToString(Number(value)))
3576
+ .replace(/&#x([0-9a-f]+);?/gi, (_, value) => codePointToString(Number.parseInt(value, 16)))
3476
3577
  .replace(/&(colon|tab|newline);/gi, (_, entity) => entity.toLowerCase() === 'colon' ? ':' : entity.toLowerCase() === 'tab' ? '\t' : '\n');
3578
+ /** Whether `url` may be embedded as an image `src`: everything except `javascript:` and `vbscript:`
3579
+ * URLs and non-image `data:` URLs, however they are cased, spaced or entity-encoded. */
3477
3580
  const isSafeUrl = (url) => {
3478
3581
  // eslint-disable-next-line no-control-regex -- URL parsers ignore these characters inside schemes.
3479
3582
  const normalized = decodeUrlCharacterReferences(url).replace(/[\s\u0000-\u001f]/g, '');
3480
3583
  return (!/^(javascript|vbscript):/i.test(normalized) && !/^data:(?!image\/(?:avif|gif|jpeg|png|webp);)/i.test(normalized));
3481
3584
  };
3585
+ /** Whether `url` may become a link: `http:`, `https:`, `mailto:` and `tel:` URLs, relative paths and
3586
+ * fragments pass; any other scheme (`javascript:`, `data:`, `vbscript:`, ...) is refused, however
3587
+ * it is cased, spaced or entity-encoded. */
3588
+ const isSafeLinkUrl = (url) => {
3589
+ // eslint-disable-next-line no-control-regex -- URL parsers ignore these characters inside schemes.
3590
+ const normalized = decodeUrlCharacterReferences(url).replace(/[\s\u0000-\u001f]/g, '');
3591
+ return !/^[a-z][a-z0-9+.-]*:/i.test(normalized) || /^(?:https?|mailto|tel):/i.test(normalized);
3592
+ };
3482
3593
  const unescapeHtml = (str) => str
3483
3594
  .replace(/&amp;/g, '&')
3484
3595
  .replace(/&lt;/g, '<')
@@ -3527,7 +3638,7 @@ const sanitizeInlineHtml = (html) => {
3527
3638
  const name = tag.toLowerCase();
3528
3639
  if (name === 'a') {
3529
3640
  const href = closing ? null : /\bhref\s*=\s*"([^"]*)"/i.exec(full)?.[1];
3530
- const anchor = closing ? '</a>' : href && isSafeUrl(href) ? `<a href="${href}">` : '<a>';
3641
+ const anchor = closing ? '</a>' : href && isSafeLinkUrl(href) ? `<a href="${href}">` : '<a>';
3531
3642
  return makePlaceholder('TAG', kept.push(anchor) - 1);
3532
3643
  }
3533
3644
  if (!SAFE_INLINE_TAGS.has(name))
@@ -3538,10 +3649,10 @@ const sanitizeInlineHtml = (html) => {
3538
3649
  };
3539
3650
  const processInline = (text) => {
3540
3651
  const inlineCodes = [];
3541
- text = text.replace(/`([^`]+)`/g, (_, code) => {
3542
- const idx = inlineCodes.push(`<code>${escapeHtml(code)}</code>`) - 1;
3543
- return makePlaceholder('IC', idx);
3544
- });
3652
+ const escapes = [];
3653
+ text = text.replace(/\\([!-/:-@[-`{-~])|`([^`]+)`/g, (_, escaped, code) => escaped !== undefined
3654
+ ? makePlaceholder('ESC', escapes.push(escapeHtml(escaped)) - 1)
3655
+ : makePlaceholder('IC', inlineCodes.push(`<code>${escapeHtml(code ?? '')}</code>`) - 1));
3545
3656
  // "Open in new tab" links round-trip as raw HTML because Markdown has no `target` syntax. Extract
3546
3657
  // them before the raw-HTML escape below, keeping only a safe href, `target="_blank"` and a forced
3547
3658
  // `rel="noopener noreferrer"` (any other attribute - including event handlers - is dropped); their
@@ -3550,9 +3661,11 @@ const processInline = (text) => {
3550
3661
  text = text.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (full, attrs, inner) => {
3551
3662
  const href = /\bhref\s*=\s*"([^"]*)"/i.exec(attrs)?.[1];
3552
3663
  const newTab = /\btarget\s*=\s*"_blank"/i.test(attrs);
3553
- if (!newTab || !href || !isSafeUrl(href))
3664
+ if (!newTab || !href)
3554
3665
  return full;
3555
- const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`;
3666
+ const anchor = isSafeLinkUrl(href)
3667
+ ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`
3668
+ : processInline(inner);
3556
3669
  return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
3557
3670
  });
3558
3671
  // Raw HTML in Markdown text is escaped, not rendered - the editor writes this HTML straight
@@ -3570,10 +3683,16 @@ const processInline = (text) => {
3570
3683
  text = text.replace(/~~([^\s~](?:.*?[^\s~])?)~~/g, '<del>$1</del>');
3571
3684
  // Images before links (order matters); URLs with script-running schemes stay literal text
3572
3685
  text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => isSafeUrl(src) ? `<img src="${src}" alt="${alt}">` : match);
3573
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, href) => isSafeUrl(href) ? `<a href="${href}">${label}</a>` : match);
3686
+ text = text.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => isSafeLinkUrl(href) ? `<a href="${href}">${label}</a>` : label);
3687
+ // New-tab links first: their inner markup still holds this call's code and escape placeholders.
3574
3688
  return text
3575
- .replace(placeholderRe('IC'), (_, i) => inlineCodes[+i] ?? '')
3576
- .replace(placeholderRe('NTLINK'), (_, i) => newTabLinks[+i] ?? '');
3689
+ .replace(placeholderRe('NTLINK'), (_, i) => newTabLinks[+i] ?? '')
3690
+ .replace(placeholderRe('IC'), (match, i) => inlineCodes[+i] ?? match)
3691
+ .replace(placeholderRe('ESC'), (match, i) => escapes[+i] ?? match);
3692
+ };
3693
+ const parseStartNumber = (openTag) => {
3694
+ const value = /\bstart\s*=\s*["']?(-?\d+)/i.exec(openTag)?.[1];
3695
+ return value === undefined ? 1 : Number(value);
3577
3696
  };
3578
3697
  /** The first top-level `<ul>`/`<ol>` in `html`, matched with balanced nesting. */
3579
3698
  const findList = (html) => {
@@ -3593,6 +3712,7 @@ const findList = (html) => {
3593
3712
  start: open.index,
3594
3713
  end: tag.lastIndex,
3595
3714
  ordered: openTagName.toLowerCase() === 'ol',
3715
+ startNumber: parseStartNumber(open[0]),
3596
3716
  inner: html.slice(innerStart, match.index),
3597
3717
  };
3598
3718
  }
@@ -3692,15 +3812,15 @@ const buildBlockquoteHtml = (lines) => {
3692
3812
  return `<blockquote>${html || '<br>'}</blockquote>`;
3693
3813
  };
3694
3814
  /** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
3695
- const listToMarkdown = (inner, ordered, depth) => {
3815
+ const listToMarkdown = (inner, ordered, startNumber, depth) => {
3696
3816
  const indent = ' '.repeat(depth);
3697
- let n = 1;
3817
+ let n = startNumber;
3698
3818
  return findListItems(inner)
3699
3819
  .map((itemInner) => {
3700
3820
  let content = itemInner;
3701
3821
  let nestedMarkdown = '';
3702
3822
  for (let nested = findList(content); nested; nested = findList(content)) {
3703
- nestedMarkdown += `\n${listToMarkdown(nested.inner, nested.ordered, depth + 1)}`;
3823
+ nestedMarkdown += `\n${listToMarkdown(nested.inner, nested.ordered, nested.startNumber, depth + 1)}`;
3704
3824
  content = content.slice(0, nested.start) + content.slice(nested.end);
3705
3825
  }
3706
3826
  const marker = ordered ? `${n++}. ` : '- ';
@@ -3714,7 +3834,12 @@ const parseListLines = (lines) => lines
3714
3834
  .map((line) => {
3715
3835
  const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
3716
3836
  return match
3717
- ? { indent: Math.floor((match[1] ?? '').length / 2), ordered: /\d/.test(match[2] ?? ''), text: match[3] ?? '' }
3837
+ ? {
3838
+ indent: Math.floor((match[1] ?? '').length / 2),
3839
+ ordered: /\d/.test(match[2] ?? ''),
3840
+ number: Number.parseInt(match[2] ?? '', 10),
3841
+ text: match[3] ?? '',
3842
+ }
3718
3843
  : null;
3719
3844
  })
3720
3845
  .filter((line) => line !== null);
@@ -3735,17 +3860,53 @@ const buildListHtml = (lines, start, baseIndent) => {
3735
3860
  items += `<li>${item}</li>`;
3736
3861
  }
3737
3862
  const tag = ordered ? 'ol' : 'ul';
3738
- return { html: `<${tag}>${items}</${tag}>`, next: i };
3863
+ const startNumber = ordered ? (lines[start]?.number ?? 1) : 1;
3864
+ const startAttr = startNumber === 1 ? '' : ` start="${startNumber}"`;
3865
+ return { html: `<${tag}${startAttr}>${items}</${tag}>`, next: i };
3866
+ };
3867
+ const escapeMarkdownText = (text) => text
3868
+ .replace(/[\\`[\]]/g, '\\$&')
3869
+ .replace(/(?<!\s)\*|\*(?!\s)/g, '\\*')
3870
+ .replace(/_+/g, (run, offset, whole) => run.length === 1 && /[a-z0-9]/i.test(whole[offset - 1] ?? '') && /[a-z0-9]/i.test(whole[offset + 1] ?? '')
3871
+ ? run
3872
+ : run.replace(/_/g, '\\_'))
3873
+ .replace(/~{2,}/g, (run) => run.replace(/~/g, '\\~'))
3874
+ .replace(/&lt;(?=\/?(?:u|a|p|h[1-6]|div)\b)/gi, '\\&lt;')
3875
+ .replace(/(^|\n)([ \t]*)(?=[-+*#|:]|&gt;|\d+\.)/g, '$1$2\u{E002}');
3876
+ const escapeMarkdownAlt = (alt) => alt.replace(/[\\[\]]/g, '\\$&');
3877
+ const escapeTextNodes = (html) => {
3878
+ let codeDepth = 0;
3879
+ return html.replace(/(<[^>]*>)|([^<]+)/g, (_, tag, text) => {
3880
+ if (tag) {
3881
+ const code = /^<(\/?)(?:pre|code)\b/i.exec(tag);
3882
+ if (code)
3883
+ codeDepth = Math.max(0, codeDepth + (code[1] ? -1 : 1));
3884
+ return tag;
3885
+ }
3886
+ return codeDepth > 0 ? (text ?? '') : escapeMarkdownText(text ?? '');
3887
+ });
3739
3888
  };
3889
+ const escapeLineStarts = (md) => md
3890
+ .replace(/^([ \t]*(?:>[ \t]?)*)\u{E002}(?:([-+*])(?=[ \t]|$|--[ \t]*$)|(#{1,6})(?=[ \t]|$)|([>|])|(\d+)\.(?=[ \t]|$)|(?=[-:| \t]*\|[-:| \t]*$)(?=.*-))/gmu, (_, prefix, bullet, hashes, marker, digits) => digits !== undefined ? `${prefix}${digits}\\.` : `${prefix}\\${bullet ?? hashes ?? marker ?? ''}`)
3891
+ .replace(/\u{E002}/gu, '');
3740
3892
  /**
3741
3893
  * Converts a markdown string to HTML.
3742
3894
  * Covers headings, bold, italic, strikethrough, inline code, fenced code blocks,
3743
3895
  * links, images, block quotes, unordered/ordered (and nested) lists, tables, horizontal rules, and paragraphs.
3896
+ * A backslash before a punctuation character keeps that character literal.
3897
+ * Text matching `options.verbatim` is emitted as plain text, untouched by any Markdown syntax:
3898
+ * `markdownToHtml('{{field:__x__}}', { verbatim: /\{\{[^}]+\}\}/ })` returns `<p>{{field:__x__}}</p>`.
3744
3899
  */
3745
- const markdownToHtml = (markdown) => {
3900
+ const markdownToHtml = (markdown, options = {}) => {
3746
3901
  if (!markdown)
3747
3902
  return '';
3748
3903
  let text = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
3904
+ const verbatims = [];
3905
+ const { verbatim } = options;
3906
+ if (verbatim) {
3907
+ const pattern = new RegExp(verbatim.source, verbatim.flags.includes('g') ? verbatim.flags : `${verbatim.flags}g`);
3908
+ text = text.replace(pattern, (match) => match ? makePlaceholder('VERB', verbatims.push(escapeHtmlPreservingEntities(match)) - 1) : match);
3909
+ }
3749
3910
  // Extract fenced code blocks before any other processing
3750
3911
  const codeBlocks = [];
3751
3912
  text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
@@ -3822,17 +3983,23 @@ const markdownToHtml = (markdown) => {
3822
3983
  })
3823
3984
  .filter(Boolean)
3824
3985
  .join('\n');
3825
- return html.replace(placeholderRe('CODE'), (_, i) => codeBlocks[+i] ?? '');
3986
+ return html
3987
+ .replace(placeholderRe('CODE'), (_, i) => codeBlocks[+i] ?? '')
3988
+ .replace(placeholderRe('VERB'), (match, i) => verbatims[+i] ?? match);
3826
3989
  };
3827
3990
  /**
3828
3991
  * Converts an HTML string to markdown.
3829
3992
  * Covers headings, bold, italic, strikethrough, inline code, fenced code blocks,
3830
3993
  * links, images, block quotes, unordered/ordered lists, tables, horizontal rules, and paragraphs.
3994
+ * Text that would read as Markdown is backslash-escaped (code and {@link MARKDOWN_VERBATIM_ATTR}
3995
+ * elements are left as is), and a link whose URL
3996
+ * fails {@link isSafeLinkUrl} keeps only its text.
3831
3997
  */
3832
3998
  const htmlToMarkdown = (html) => {
3833
3999
  if (!html)
3834
4000
  return '';
3835
- let md = html;
4001
+ const verbatims = [];
4002
+ let md = html.replace(VERBATIM_RE, (_, text) => makePlaceholder('VERB', verbatims.push(unescapeHtml(text)) - 1));
3836
4003
  // Aligned blocks: text-align has no Markdown form, so preserve them as HTML (their inner markup
3837
4004
  // stays HTML) and round-trip via a placeholder - extracted before the block passes below rewrite
3838
4005
  // them, restored after the final tag-strip.
@@ -3841,6 +4008,7 @@ const htmlToMarkdown = (html) => {
3841
4008
  const name = tag.toLowerCase();
3842
4009
  return makePlaceholder('ALIGN', alignedBlocks.push(`<${name}${alignClass(alignOf(attrs))}>${inner}</${name}>`) - 1);
3843
4010
  });
4011
+ md = escapeTextNodes(md);
3844
4012
  // Code blocks - process before inline code
3845
4013
  md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
3846
4014
  md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
@@ -3865,14 +4033,16 @@ const htmlToMarkdown = (html) => {
3865
4033
  const newTabLinks = [];
3866
4034
  md = md.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, inner) => {
3867
4035
  const href = /\bhref\s*=\s*"([^"]*)"/i.exec(attrs)?.[1] ?? '';
4036
+ if (!isSafeLinkUrl(href))
4037
+ return inner;
3868
4038
  if (/\btarget\s*=\s*"_blank"/i.test(attrs) && href) {
3869
4039
  const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${inner}</a>`;
3870
4040
  return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
3871
4041
  }
3872
4042
  return href ? `[${inner}](${href})` : inner;
3873
4043
  });
3874
- md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '![$2]($1)');
3875
- md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '![$1]($2)');
4044
+ md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, (_, src, alt) => `![${escapeMarkdownAlt(alt)}](${src})`);
4045
+ md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, (_, alt, src) => `![${escapeMarkdownAlt(alt)}](${src})`);
3876
4046
  md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '![]($1)');
3877
4047
  // Underline has no Markdown form - preserve it as raw <u> so it round-trips. Extract it now (its
3878
4048
  // inner markup is already converted above) so the block passes below don't strip the tag; the
@@ -3886,7 +4056,7 @@ const htmlToMarkdown = (html) => {
3886
4056
  }
3887
4057
  // Lists - replace each top-level list with its recursively-serialized Markdown (handles nesting).
3888
4058
  for (let list = findList(md); list; list = findList(md)) {
3889
- md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
4059
+ md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, list.startNumber, 0)}\n${md.slice(list.end)}`;
3890
4060
  }
3891
4061
  // Tables
3892
4062
  md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
@@ -3926,7 +4096,7 @@ const htmlToMarkdown = (html) => {
3926
4096
  // aligned blocks are block-level, so pad with blank lines before collapsing so they survive as
3927
4097
  // their own Markdown block
3928
4098
  md = md.replace(placeholderRe('ALIGN'), (_, i) => `\n\n${alignedBlocks[+i] ?? ''}\n\n`);
3929
- return md.replace(/\n{3,}/g, '\n\n').trim();
4099
+ return escapeLineStarts(md.replace(/\n{3,}/g, '\n\n').trim()).replace(placeholderRe('VERB'), (_, i) => verbatims[+i] ?? '');
3930
4100
  };
3931
4101
 
3932
4102
  const clamp = (value, min = 0, max = 100) => {
@@ -4197,8 +4367,15 @@ const suppressTextSelection = (doc) => {
4197
4367
  existing.count++;
4198
4368
  }
4199
4369
  else {
4200
- suppressions.set(doc, { count: 1, previousInlineValue: doc.documentElement.style.userSelect });
4201
- doc.documentElement.style.userSelect = 'none';
4370
+ const style = doc.documentElement.style;
4371
+ suppressions.set(doc, {
4372
+ count: 1,
4373
+ previousUserSelect: style.userSelect,
4374
+ previousWebkitUserSelect: style.webkitUserSelect,
4375
+ });
4376
+ style.userSelect = 'none';
4377
+ // Safari and WebKitGTK ignore the unprefixed `user-select`.
4378
+ style.webkitUserSelect = 'none';
4202
4379
  }
4203
4380
  let released = false;
4204
4381
  return () => {
@@ -4210,7 +4387,8 @@ const suppressTextSelection = (doc) => {
4210
4387
  if (state.count > 0)
4211
4388
  return;
4212
4389
  suppressions.delete(doc);
4213
- doc.documentElement.style.userSelect = state.previousInlineValue;
4390
+ doc.documentElement.style.userSelect = state.previousUserSelect;
4391
+ doc.documentElement.style.webkitUserSelect = state.previousWebkitUserSelect;
4214
4392
  };
4215
4393
  };
4216
4394
 
@@ -4476,6 +4654,22 @@ const createUserConsentProvider = (options) => ({
4476
4654
  }),
4477
4655
  });
4478
4656
 
4657
+ /**
4658
+ * Declares the `et-visually-hidden` class as a styles-only component: content that stays in the
4659
+ * accessibility tree but is clipped out of the layout. Mount it from any component whose template
4660
+ * uses the class.
4661
+ */
4662
+ class VisuallyHiddenStylesComponent {
4663
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: VisuallyHiddenStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4664
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: VisuallyHiddenStylesComponent, isStandalone: true, selector: "et-visually-hidden-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-visually-hidden{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
4665
+ }
4666
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: VisuallyHiddenStylesComponent, decorators: [{
4667
+ type: Component,
4668
+ args: [{ selector: 'et-visually-hidden-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-visually-hidden{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}}\n"] }]
4669
+ }] });
4670
+ /** Mounts {@link VisuallyHiddenStylesComponent}. Call from an injection context. */
4671
+ const mountVisuallyHidden = () => injectStyleManager().mount(VisuallyHiddenStylesComponent);
4672
+
4479
4673
  /**
4480
4674
  * Declares the `--ease-*` custom properties that every animated stylesheet in the SDK
4481
4675
  * transitions with, as a styles-only component. Mount it from any component or directive whose
@@ -4610,25 +4804,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
4610
4804
  }]
4611
4805
  }], ctorParameters: () => [] });
4612
4806
 
4807
+ // codes 9000-9099
4808
+ const ANIMATION_ERROR_CODES = {
4809
+ /** `*etAnimatedIf` was used without an `[etAnimatedLifecycle]` element around it. */
4810
+ MISSING_ANIMATED_LIFECYCLE: 9000,
4811
+ };
4812
+
4813
+ /** Runs `cb` two frames from now. Returns a function that cancels it if it has not run yet. */
4613
4814
  const nextFrame = (cb) => {
4614
4815
  if (typeof requestAnimationFrame === 'undefined') {
4615
- return;
4816
+ return () => undefined;
4616
4817
  }
4617
- requestAnimationFrame(() => {
4618
- requestAnimationFrame(cb);
4818
+ let id = requestAnimationFrame(() => {
4819
+ id = requestAnimationFrame(cb);
4619
4820
  });
4821
+ return () => cancelAnimationFrame(id);
4620
4822
  };
4621
4823
  const fromNextFrame = () => {
4622
- return new Observable((observer) => {
4623
- nextFrame(() => {
4624
- observer.next();
4625
- observer.complete();
4626
- });
4627
- });
4824
+ return new Observable((observer) => nextFrame(() => {
4825
+ observer.next();
4826
+ observer.complete();
4827
+ }));
4628
4828
  };
4629
4829
  const forceReflow = (element) => {
4630
- // The `document.body` fallback is resolved lazily and guarded: there is nothing to reflow on the
4631
- // server, so this becomes a no-op instead of throwing on a missing `document`.
4632
4830
  const el = element ?? (typeof document === 'undefined' ? null : document.body);
4633
4831
  return el?.offsetHeight ?? 0;
4634
4832
  };
@@ -4671,7 +4869,12 @@ class AnimatedLifecycleDirective {
4671
4869
  this.transitionIdCounter = 0;
4672
4870
  this.forcedThisFrame = false;
4673
4871
  this.forcedFrameResetId = null;
4674
- this.state$ = new BehaviorSubject('init');
4872
+ this.stateSubject$ = new BehaviorSubject('init');
4873
+ this._state = signal('init', /* @ts-ignore */
4874
+ ...(ngDevMode ? [{ debugName: "_state" }] : /* istanbul ignore next */ []));
4875
+ this.state = this._state.asReadonly();
4876
+ /** Emits the current lifecycle state synchronously on subscribe, then every change. */
4877
+ this.state$ = this.stateSubject$.asObservable();
4675
4878
  this.stateChange = outputFromObservable(this.state$);
4676
4879
  this.skipNextEnter = model(false, /* @ts-ignore */
4677
4880
  ...(ngDevMode ? [{ debugName: "skipNextEnter" }] : /* istanbul ignore next */ []));
@@ -4694,7 +4897,7 @@ class AnimatedLifecycleDirective {
4694
4897
  this.isConstructed = true;
4695
4898
  }
4696
4899
  enter() {
4697
- const currentState = this.state$.value;
4900
+ const currentState = this.state();
4698
4901
  if (currentState === 'entering')
4699
4902
  return;
4700
4903
  if ((currentState === 'init' && !this.isConstructed) || this.skipNextEnter()) {
@@ -4764,7 +4967,7 @@ class AnimatedLifecycleDirective {
4764
4967
  previousCancel$.complete();
4765
4968
  }
4766
4969
  leave() {
4767
- const currentState = this.state$.value;
4970
+ const currentState = this.state();
4768
4971
  if (currentState === 'leaving')
4769
4972
  return;
4770
4973
  if (currentState === 'init') {
@@ -4865,11 +5068,11 @@ class AnimatedLifecycleDirective {
4865
5068
  this.addClass(activeClass);
4866
5069
  fromNextFrame()
4867
5070
  .pipe(tap(() => {
4868
- if (this.state$.value === expectedState) {
5071
+ if (this.state() === expectedState) {
4869
5072
  this.removeClass(fromClass);
4870
5073
  this.addClass(toClass);
4871
5074
  }
4872
- }), switchMap(() => this.whenAnimationsSettled()), tap(() => this.debugLog(`${transitionId}: settled`)), filter(() => this.state$.value === expectedState), tap(onComplete), take(1), takeUntil(cancelSignal), takeUntilDestroyed(this.destroyRef))
5075
+ }), switchMap(() => this.whenAnimationsSettled()), tap(() => this.debugLog(`${transitionId}: settled`)), filter(() => this.state() === expectedState), tap(onComplete), take(1), takeUntil(cancelSignal), takeUntilDestroyed(this.destroyRef))
4873
5076
  .subscribe();
4874
5077
  }
4875
5078
  handleInterruptedTransition(config) {
@@ -4877,7 +5080,7 @@ class AnimatedLifecycleDirective {
4877
5080
  this.removeClasses(...removeClasses);
4878
5081
  addClasses.forEach((cls) => this.addClass(cls));
4879
5082
  this.whenAnimationsSettled()
4880
- .pipe(tap(() => this.debugLog(`${transitionId}: interrupt settled`)), filter(() => this.state$.value === expectedState), tap(onComplete), take(1), takeUntil(cancelSignal), takeUntilDestroyed(this.destroyRef))
5083
+ .pipe(tap(() => this.debugLog(`${transitionId}: interrupt settled`)), filter(() => this.state() === expectedState), tap(onComplete), take(1), takeUntil(cancelSignal), takeUntilDestroyed(this.destroyRef))
4881
5084
  .subscribe();
4882
5085
  }
4883
5086
  /**
@@ -4912,8 +5115,9 @@ class AnimatedLifecycleDirective {
4912
5115
  this.renderer.removeClass(this.element, ...classNames);
4913
5116
  }
4914
5117
  updateState(newState) {
4915
- this.debugLog(`state: ${this.state$.value} → ${newState}`);
4916
- this.state$.next(newState);
5118
+ this.debugLog(`state: ${this.state()} → ${newState}`);
5119
+ this._state.set(newState);
5120
+ this.stateSubject$.next(newState);
4917
5121
  }
4918
5122
  debugLog(message, data) {
4919
5123
  const el = this.element;
@@ -4953,27 +5157,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
4953
5157
 
4954
5158
  const ANIMATED_IF_TOKEN = new InjectionToken('ANIMATED_IF_TOKEN');
4955
5159
  class AnimatedIfDirective {
5160
+ static ngTemplateContextGuard(_dir, _ctx) {
5161
+ return true;
5162
+ }
4956
5163
  constructor() {
4957
5164
  this.templateRef = inject(TemplateRef);
4958
5165
  this.viewContainerRef = inject(ViewContainerRef);
4959
- this.animatedLifecycle = inject(ANIMATED_LIFECYCLE_TOKEN);
5166
+ this.animatedLifecycle = inject(ANIMATED_LIFECYCLE_TOKEN, { optional: true });
4960
5167
  this.hasView = false;
5168
+ this.context = { $implicit: null, etAnimatedIf: null };
4961
5169
  this.ifValue = input(null, { ...(ngDevMode ? { debugName: "ifValue" } : /* istanbul ignore next */ {}), alias: 'etAnimatedIf' });
5170
+ const animatedLifecycle = this.animatedLifecycle;
5171
+ if (!animatedLifecycle) {
5172
+ throw new RuntimeError(ANIMATION_ERROR_CODES.MISSING_ANIMATED_LIFECYCLE, '*etAnimatedIf needs an element with [etAnimatedLifecycle] around it to animate against.');
5173
+ }
4962
5174
  toObservable(this.ifValue)
4963
5175
  .pipe(switchMap((value) => {
4964
5176
  if (value) {
5177
+ this.context.$implicit = value;
5178
+ this.context.etAnimatedIf = value;
4965
5179
  if (!this.hasView) {
4966
- this.viewContainerRef.createEmbeddedView(this.templateRef);
5180
+ this.viewContainerRef.createEmbeddedView(this.templateRef, this.context);
4967
5181
  this.hasView = true;
4968
5182
  }
4969
- this.animatedLifecycle.enter();
5183
+ if (animatedLifecycle.state() !== 'entered')
5184
+ animatedLifecycle.enter();
4970
5185
  return of(null);
4971
5186
  }
4972
5187
  if (!this.hasView) {
4973
5188
  return of(null);
4974
5189
  }
4975
- this.animatedLifecycle.leave();
4976
- return this.animatedLifecycle.state$.pipe(filter((state) => state === 'left'), take(1), tap(() => {
5190
+ animatedLifecycle.leave();
5191
+ return animatedLifecycle.state$.pipe(filter((state) => state === 'left'), take(1), tap(() => {
4977
5192
  this.viewContainerRef.clear();
4978
5193
  this.hasView = false;
4979
5194
  }));
@@ -5030,14 +5245,16 @@ const createFlipAnimationGroup = (config) => {
5030
5245
  }
5031
5246
  };
5032
5247
  flips.forEach((animation) => {
5033
- animation.onStart$.subscribe(() => {
5248
+ animation.onStart$
5249
+ .pipe(tap(() => {
5034
5250
  startedCount++;
5035
5251
  if (startedCount === flips.length) {
5036
5252
  onStart$.next();
5037
5253
  }
5038
- });
5039
- animation.onFinish$.subscribe(() => settle(false));
5040
- animation.onCancel$.subscribe(() => settle(true));
5254
+ }))
5255
+ .subscribe();
5256
+ animation.onFinish$.pipe(tap(() => settle(false))).subscribe();
5257
+ animation.onCancel$.pipe(tap(() => settle(true))).subscribe();
5041
5258
  });
5042
5259
  const updateInit = () => {
5043
5260
  flips.forEach((animation) => animation.updateInit());
@@ -5147,6 +5364,23 @@ const createFlipAnimation = (config) => {
5147
5364
  };
5148
5365
  };
5149
5366
 
5367
+ /** The confirm an unsaved-changes tracker or guard runs when its own config has none. */
5368
+ const UNSAVED_CHANGES_CONFIRM = /* @__PURE__ */ new InjectionToken('UNSAVED_CHANGES_CONFIRM');
5369
+ /**
5370
+ * Sets the confirm every unsaved-changes tracker and guard below this injector runs when its config has no
5371
+ * `confirm`. `factory` runs once, in an injection context, and returns the confirm.
5372
+ *
5373
+ * @example
5374
+ * provideUnsavedChangesConfirm(() => {
5375
+ * const dialogs = inject(MyDialogs);
5376
+ * return () => dialogs.confirmDiscard();
5377
+ * });
5378
+ */
5379
+ const provideUnsavedChangesConfirm = (factory) => ({
5380
+ provide: UNSAVED_CHANGES_CONFIRM,
5381
+ useFactory: factory,
5382
+ });
5383
+
5150
5384
  const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() => {
5151
5385
  const _registrations = signal([], /* @ts-ignore */
5152
5386
  ...(ngDevMode ? [{ debugName: "_registrations" }] : /* istanbul ignore next */ []));
@@ -5155,7 +5389,7 @@ const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() =>
5155
5389
  const hasUnsavedChanges = computed(() => _registrations().some((registration) => registration.hasChanges()), /* @ts-ignore */
5156
5390
  ...(ngDevMode ? [{ debugName: "hasUnsavedChanges" }] : /* istanbul ignore next */ []));
5157
5391
  let pending = null;
5158
- /** @internal Runs a tracker's confirm under the single-flight + abandon regime. */
5392
+ /** Runs a tracker's confirm under the single-flight + abandon regime. */
5159
5393
  const runCheck = (runner) => {
5160
5394
  // A confirm is already on screen - adopt its answer rather than opening a second dialog.
5161
5395
  if (pending) {
@@ -5183,7 +5417,6 @@ const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() =>
5183
5417
  _isCheckPending.set(true);
5184
5418
  return promise;
5185
5419
  };
5186
- /** @internal */
5187
5420
  const register = (registration) => {
5188
5421
  _registrations.update((current) => [...current, registration]);
5189
5422
  return () => _registrations.update((current) => current.filter((entry) => entry !== registration));
@@ -5231,6 +5464,11 @@ const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() =>
5231
5464
  const provideUnsavedChangesCoordinator = /* @__PURE__ */ toProvideFn(UNSAVED_CHANGES_COORDINATOR_DEF);
5232
5465
  const injectUnsavedChangesCoordinator = /* @__PURE__ */ toInjectFn(UNSAVED_CHANGES_COORDINATOR_DEF);
5233
5466
 
5467
+ const UNSAVED_CHANGES_ERROR_CODES = {
5468
+ /** An unsaved-changes tracker or guard has no `confirm`, and no `provideUnsavedChangesConfirm()` is in scope. */
5469
+ MISSING_CONFIRM: 9100,
5470
+ };
5471
+
5234
5472
  const isAbstractControl = (value) => !!value &&
5235
5473
  typeof value === 'object' &&
5236
5474
  typeof value.getRawValue === 'function' &&
@@ -5254,22 +5492,25 @@ const normalizeUnsavedChangesSource = (source) => {
5254
5492
  const peek = untracked(source);
5255
5493
  // A signal that currently holds a FieldTree - or nothing yet (the late/async form case).
5256
5494
  if (peek === null || peek === undefined || isFieldTree(peek)) {
5257
- const fieldSignal = source;
5495
+ const lateSource = source;
5258
5496
  return {
5259
5497
  value: computed(() => {
5260
- const field = fieldSignal();
5261
- return isFieldTree(field) ? field().value() : null;
5498
+ const current = lateSource();
5499
+ if (isFieldTree(current))
5500
+ return current().value();
5501
+ return (current ?? null);
5262
5502
  }),
5263
5503
  setValue: (next) => {
5264
- const field = untracked(fieldSignal);
5265
- if (isFieldTree(field)) {
5266
- field().value.set(next);
5504
+ const current = untracked(lateSource);
5505
+ if (isFieldTree(current)) {
5506
+ current().value.set(next);
5507
+ }
5508
+ else if (current !== null && current !== undefined && 'set' in source) {
5509
+ source.set(next);
5267
5510
  }
5268
5511
  },
5269
5512
  };
5270
5513
  }
5271
- // A plain writable value signal (escape hatch). Initialize it with a non-null value so it isn't
5272
- // mistaken for a late FieldTree source above.
5273
5514
  const writable = source;
5274
5515
  return { value: writable, setValue: (next) => writable.set(next) };
5275
5516
  }
@@ -5438,6 +5679,7 @@ const FAVICON_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
5438
5679
  }
5439
5680
  return new Promise((resolve) => {
5440
5681
  const image = new Image();
5682
+ image.crossOrigin = 'anonymous';
5441
5683
  image.onload = () => {
5442
5684
  baseImage = image;
5443
5685
  resolve(image);
@@ -5670,6 +5912,9 @@ const LINK_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
5670
5912
  }, { name: 'Link Store' });
5671
5913
  const provideLinkStore = /* @__PURE__ */ toProvideFn(LINK_STORE_DEF);
5672
5914
  const injectLinkStore = /* @__PURE__ */ toInjectFn(LINK_STORE_DEF);
5915
+ /**
5916
+ * Binds a link tag. Only removes the elements it created; pre-existing links are left in place, unlike meta bindings.
5917
+ */
5673
5918
  const applyLinkBinding = (binding) => {
5674
5919
  const linkStore = injectLinkStore();
5675
5920
  const linkId = Symbol('link-tag');
@@ -5814,7 +6059,6 @@ const applyHeadTitleMarker = (binding) => {
5814
6059
  };
5815
6060
 
5816
6061
  const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
5817
- // Open Graph - Media
5818
6062
  'property="og:image"',
5819
6063
  'property="og:image:url"',
5820
6064
  'property="og:image:secure_url"',
@@ -5824,32 +6068,23 @@ const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
5824
6068
  'property="og:audio"',
5825
6069
  'property="og:audio:url"',
5826
6070
  'property="og:audio:secure_url"',
5827
- // Open Graph - Locale alternatives
5828
6071
  'property="og:locale:alternate"',
5829
- // Twitter Card
5830
6072
  'name="twitter:image"',
5831
6073
  'name="twitter:player"',
5832
- // Article tags (arrays)
5833
6074
  'property="article:author"',
5834
6075
  'property="article:tag"',
5835
- // Music tags (arrays)
5836
6076
  'property="music:song"',
5837
6077
  'property="music:musician"',
5838
6078
  'property="music:album"',
5839
- // Video tags (arrays)
5840
6079
  'property="video:actor"',
5841
6080
  'property="video:director"',
5842
6081
  'property="video:writer"',
5843
6082
  'property="video:tag"',
5844
- // Book tags (arrays)
5845
6083
  'property="book:author"',
5846
6084
  'property="book:tag"',
5847
- // Profile (arrays)
5848
6085
  'property="profile:username"',
5849
- // Product tags (arrays)
5850
6086
  'property="product:category"',
5851
6087
  'property="product:retailer_item_id"',
5852
- // App Links (multiple platforms)
5853
6088
  'property="al:ios:url"',
5854
6089
  'property="al:ios:app_store_id"',
5855
6090
  'property="al:ios:app_name"',
@@ -5866,16 +6101,12 @@ const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
5866
6101
  'property="al:windows_universal:url"',
5867
6102
  'property="al:windows_universal:app_id"',
5868
6103
  'property="al:windows_universal:app_name"',
5869
- // Dublin Core (can have multiple)
5870
6104
  'name="DC.creator"',
5871
6105
  'name="DC.contributor"',
5872
6106
  'name="DC.subject"',
5873
- // Schema.org (via meta tags - though JSON-LD is preferred)
5874
6107
  'itemprop="image"',
5875
6108
  'itemprop="author"',
5876
- // Apple iOS
5877
6109
  'name="apple-itunes-app"', // Can have multiple app arguments
5878
- // Verification tags (multiple services)
5879
6110
  'name="google-site-verification"',
5880
6111
  'name="msvalidate.01"',
5881
6112
  'name="yandex-verification"',
@@ -6004,6 +6235,9 @@ const META_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
6004
6235
  }, { name: 'Meta Store' });
6005
6236
  const provideMetaStore = /* @__PURE__ */ toProvideFn(META_STORE_DEF);
6006
6237
  const injectMetaStore = /* @__PURE__ */ toInjectFn(META_STORE_DEF);
6238
+ /**
6239
+ * Binds a meta tag. Removes any pre-existing tags matching the same selector, unlike link bindings, which leave pre-existing links alone.
6240
+ */
6007
6241
  const applyMetaBinding = (binding) => {
6008
6242
  const metaStore = injectMetaStore();
6009
6243
  const tagId = Symbol('meta-tag');
@@ -6199,7 +6433,7 @@ const STRUCTURED_DATA_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
6199
6433
  renderer.setAttributes(script, { type: 'application/ld+json' });
6200
6434
  if (nonce)
6201
6435
  renderer.setAttribute(script, 'nonce', nonce);
6202
- renderer.setTextContent(script, JSON.stringify(data));
6436
+ renderer.setTextContent(script, JSON.stringify(data).replace(/</g, '\\u003C'));
6203
6437
  const target = getTargetElement();
6204
6438
  renderer.appendChild(target, script);
6205
6439
  newScripts.set(id, script);
@@ -6437,12 +6671,12 @@ const createUnsavedChangesTabLock = (config) => {
6437
6671
  return { destroy };
6438
6672
  };
6439
6673
 
6440
- const toBooleanPromise = (result) => {
6674
+ const toBooleanPromise = (result, signal) => {
6441
6675
  if (result instanceof Promise) {
6442
6676
  return result.then(Boolean);
6443
6677
  }
6444
6678
  if (isObservable(result)) {
6445
- return firstValueFrom(result.pipe(take(1), map(Boolean), defaultIfEmpty(false), catchError(() => of(false))));
6679
+ return firstValueFrom(result.pipe(take(1), map(Boolean), takeUntil(fromEvent(signal, 'abort')), defaultIfEmpty(false), catchError(() => of(false))));
6446
6680
  }
6447
6681
  return Promise.resolve(Boolean(result));
6448
6682
  };
@@ -6463,7 +6697,11 @@ const toBooleanPromise = (result) => {
6463
6697
  */
6464
6698
  const createUnsavedChangesTracker = (config) => {
6465
6699
  assertInInjectionContext(createUnsavedChangesTracker);
6466
- const { compareFn, confirm } = config;
6700
+ const { compareFn } = config;
6701
+ const confirm = config.confirm ?? inject(UNSAVED_CHANGES_CONFIRM, { optional: true });
6702
+ if (!confirm && isDevMode()) {
6703
+ throw new RuntimeError(UNSAVED_CHANGES_ERROR_CODES.MISSING_CONFIRM, '[createUnsavedChangesTracker] No confirm: pass `confirm` or call `provideUnsavedChangesConfirm()` in a parent injector.');
6704
+ }
6467
6705
  const normalized = normalizeUnsavedChangesSource(config.source);
6468
6706
  const hasExplicitDefault = config.defaultValue !== undefined;
6469
6707
  const initialDefault = hasExplicitDefault
@@ -6514,7 +6752,10 @@ const createUnsavedChangesTracker = (config) => {
6514
6752
  if (untracked(_isAbandoned) || !untracked(hasChanges)) {
6515
6753
  return Promise.resolve(true);
6516
6754
  }
6517
- return coordinator.runCheck((context) => toBooleanPromise(confirm(untracked(normalized.value), context)));
6755
+ if (!confirm) {
6756
+ return Promise.resolve(true);
6757
+ }
6758
+ return coordinator.runCheck((context) => toBooleanPromise(confirm(untracked(normalized.value), context), context.signal));
6518
6759
  };
6519
6760
  const refreshDefaultValue = () => _defaultValue.set(untracked(normalized.value));
6520
6761
  const restoreDefaultValue = () => {
@@ -6550,6 +6791,26 @@ const createUnsavedChangesGuard = (config) => {
6550
6791
  };
6551
6792
 
6552
6793
  const isCrossOrigin = (source) => /^(https?:)?\/\//.test(source);
6794
+ const HTML_COMMENT = /<!--[\s\S]*?-->/g;
6795
+ const SCRIPT_SRC = /<script\b[^>]*?\ssrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi;
6796
+ const CHARACTER_REFERENCE = /&(?:#(\d+)|#[xX]([\da-fA-F]+)|(amp|quot|apos|lt|gt));/g;
6797
+ const NAMED_REFERENCES = { amp: '&', quot: '"', apos: "'", lt: '<', gt: '>' };
6798
+ const decodeCharacterReferences = (value) => value.replace(CHARACTER_REFERENCE, (_match, decimal, hex, name) => decimal
6799
+ ? String.fromCodePoint(Number(decimal))
6800
+ : hex
6801
+ ? String.fromCodePoint(parseInt(hex, 16))
6802
+ : (NAMED_REFERENCES[name ?? ''] ?? ''));
6803
+ const toFingerprint = (sources) => sources
6804
+ .filter((source) => !!source && !isCrossOrigin(source))
6805
+ .sort()
6806
+ .join('|');
6807
+ /**
6808
+ * Must decode what `getAttribute('src')` decodes, or every check reports an update.
6809
+ *
6810
+ * Not `DOMParser`: its document inherits the page's CSP, and under a nonce `style-src` each `<style>`
6811
+ * in the fetched document, with its fresh nonce, logs a violation.
6812
+ */
6813
+ const readBuildFingerprintFromHtml = (html) => toFingerprint(Array.from(html.replace(HTML_COMMENT, '').matchAll(SCRIPT_SRC), ([, doubleQuoted, singleQuoted, unquoted]) => decodeCharacterReferences(doubleQuoted ?? singleQuoted ?? unquoted ?? '')));
6553
6814
  /**
6554
6815
  * Identifies the build a document boots from, by its entry script filenames. Those carry a content
6555
6816
  * hash in any production build, so two documents share a fingerprint exactly when reloading one would
@@ -6559,32 +6820,17 @@ const isCrossOrigin = (source) => /^(https?:)?\/\//.test(source);
6559
6820
  * Cross-origin scripts are left out: an analytics tag with a cache-busting query would otherwise read
6560
6821
  * as a new deploy on every check.
6561
6822
  */
6562
- const readBuildFingerprint = (document) => Array.from(document.querySelectorAll('script[src]'))
6563
- .map((script) => script.getAttribute('src') ?? '')
6564
- .filter((source) => !!source && !isCrossOrigin(source))
6565
- .sort()
6566
- .join('|');
6823
+ const readBuildFingerprint = (document) => toFingerprint(Array.from(document.querySelectorAll('script[src]'), (script) => script.getAttribute('src') ?? ''));
6567
6824
  /**
6568
- * Reads the currently deployed build's fingerprint from the app's entry document.
6825
+ * Reads the currently deployed build's fingerprint from the app's entry document. Cold: nothing is fetched
6826
+ * until it is subscribed.
6569
6827
  *
6570
6828
  * `null` for anything that is not a usable answer - a network failure, a non-2xx response, or a body
6571
6829
  * with no entry scripts in it. Callers must treat that as "no information" rather than as a change:
6572
6830
  * an error page parses perfectly well as HTML, and acting on its empty fingerprint would reload the
6573
6831
  * app in a loop.
6574
6832
  */
6575
- const fetchDeployedBuildFingerprint = async (url) => {
6576
- try {
6577
- const response = await fetch(url, { cache: 'no-store', headers: { accept: 'text/html' } });
6578
- if (!response.ok) {
6579
- return null;
6580
- }
6581
- const parsed = new DOMParser().parseFromString(await response.text(), 'text/html');
6582
- return readBuildFingerprint(parsed) || null;
6583
- }
6584
- catch {
6585
- return null;
6586
- }
6587
- };
6833
+ const fetchDeployedBuildFingerprint$ = (url) => defer(() => fetch(url, { cache: 'no-store', headers: { accept: 'text/html' } })).pipe(switchMap((response) => (response.ok ? response.text() : of(null))), map((html) => (html === null ? null : readBuildFingerprintFromHtml(html) || null)), catchError(() => of(null)));
6588
6834
 
6589
6835
  /**
6590
6836
  * How each engine words a lazy chunk that could not be loaded. There is no error code and no shared
@@ -6668,19 +6914,15 @@ const APP_UPDATES_DEF = /* @__PURE__ */ defineRootProvider(() => {
6668
6914
  }, /* @ts-ignore */
6669
6915
  ...(ngDevMode ? [{ debugName: "isAvailable" }] : /* istanbul ignore next */ []));
6670
6916
  let lastCheckAt = 0;
6671
- const check = async () => {
6917
+ const check$ = () => defer(() => {
6672
6918
  lastCheckAt = Date.now();
6673
- const deployed = await fetchDeployedBuildFingerprint(config.entryUrl);
6919
+ return fetchDeployedBuildFingerprint$(config.entryUrl);
6920
+ }).pipe(tap((deployed) => {
6674
6921
  if (deployed) {
6675
6922
  _deployedBuild.set(deployed);
6676
6923
  }
6677
- };
6678
- const checkIfDue = async () => {
6679
- if (Date.now() - lastCheckAt < config.minCheckInterval) {
6680
- return;
6681
- }
6682
- await check();
6683
- };
6924
+ }), map(() => undefined));
6925
+ const checkIfDue$ = () => defer(() => (Date.now() - lastCheckAt < config.minCheckInterval ? EMPTY : check$()));
6684
6926
  const reload = () => {
6685
6927
  // The reload is deliberate and the user has already answered for it, so release every
6686
6928
  // beforeunload lock rather than have the browser ask the same question again.
@@ -6728,7 +6970,7 @@ const APP_UPDATES_DEF = /* @__PURE__ */ defineRootProvider(() => {
6728
6970
  if (config.pollInterval > 0) {
6729
6971
  const isDocumentVisible = injectIsDocumentVisible();
6730
6972
  toObservable(isDocumentVisible)
6731
- .pipe(switchMap((isVisible) => (isVisible ? timer(0, config.pollInterval) : EMPTY)), switchMap(() => from(checkIfDue())), takeUntilDestroyed(destroyRef))
6973
+ .pipe(switchMap((isVisible) => (isVisible ? timer(0, config.pollInterval) : EMPTY)), switchMap(() => checkIfDue$()), takeUntilDestroyed(destroyRef))
6732
6974
  .subscribe();
6733
6975
  }
6734
6976
  }
@@ -6736,7 +6978,7 @@ const APP_UPDATES_DEF = /* @__PURE__ */ defineRootProvider(() => {
6736
6978
  isAvailable,
6737
6979
  isRequired: _isRequired.asReadonly(),
6738
6980
  wouldDiscardChanges: coordinator.hasUnsavedChanges,
6739
- check,
6981
+ check$,
6740
6982
  reload,
6741
6983
  };
6742
6984
  }, { name: 'App Updates' });
@@ -6825,12 +7067,19 @@ class ClickOutsideDirective {
6825
7067
  this.elementRef = inject(ElementRef);
6826
7068
  this.document = inject(DOCUMENT);
6827
7069
  this.didClickOutside = output({ alias: 'etClickOutside' });
7070
+ this.pressStartedInside = false;
7071
+ fromEvent(this.document.documentElement, 'pointerdown', { capture: true })
7072
+ .pipe(tap((event) => {
7073
+ this.pressStartedInside = event.composedPath().includes(this.elementRef.nativeElement);
7074
+ }), takeUntilDestroyed())
7075
+ .subscribe();
6828
7076
  fromEvent(this.document.documentElement, 'click')
6829
7077
  .pipe(tap((event) => {
6830
7078
  const hostElement = this.elementRef.nativeElement;
6831
7079
  const activeElement = event.target;
6832
- const isInside = hostElement.contains(activeElement);
6833
- if (isInside)
7080
+ const startedInside = this.pressStartedInside;
7081
+ this.pressStartedInside = false;
7082
+ if (startedInside || event.composedPath().includes(hostElement))
6834
7083
  return;
6835
7084
  if (isOnHigherOverlayLayer(activeElement, resolveOverlayLayer(hostElement)))
6836
7085
  return;
@@ -6854,7 +7103,7 @@ class RepeatDirective {
6854
7103
  this.viewContainerRef = inject(ViewContainerRef);
6855
7104
  this.repeatCount = input(2, { ...(ngDevMode ? { debugName: "repeatCount" } : /* istanbul ignore next */ {}), alias: 'etRepeat', transform: numberAttribute });
6856
7105
  effect(() => {
6857
- const count = Math.max(0, this.repeatCount());
7106
+ const count = Math.max(0, this.repeatCount()) || 0;
6858
7107
  while (this.viewContainerRef.length < count) {
6859
7108
  this.viewContainerRef.createEmbeddedView(this.mainTemplateRef);
6860
7109
  }
@@ -6931,12 +7180,10 @@ class ScrollObserverDirective {
6931
7180
  this.isAtEnd = computed(() => this._endIntersection()[0]?.isIntersecting ?? false, /* @ts-ignore */
6932
7181
  ...(ngDevMode ? [{ debugName: "isAtEnd" }] : /* istanbul ignore next */ []));
6933
7182
  }
6934
- /** @internal */
6935
7183
  registerStart(el) {
6936
7184
  this._startEl.set(el);
6937
7185
  return () => this._startEl.update((current) => (current === el ? null : current));
6938
7186
  }
6939
- /** @internal */
6940
7187
  registerEnd(el) {
6941
7188
  this._endEl.set(el);
6942
7189
  return () => this._endEl.update((current) => (current === el ? null : current));
@@ -6986,7 +7233,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
6986
7233
 
6987
7234
  // jsdom and other non-browser DOMs ship no pointer capture; the document listeners track the
6988
7235
  // gesture either way, so a failure here only costs events over a cross-origin frame.
6989
- const capturePointer = (el, pointerId) => {
7236
+ const capturePointer$1 = (el, pointerId) => {
6990
7237
  try {
6991
7238
  el.setPointerCapture(pointerId);
6992
7239
  }
@@ -7016,7 +7263,7 @@ const setupDragObservable = (startEvent, el, commitThreshold) => {
7016
7263
  return EMPTY;
7017
7264
  }
7018
7265
  committed = true;
7019
- capturePointer(el, pointerId);
7266
+ capturePointer$1(el, pointerId);
7020
7267
  lastX = e.clientX;
7021
7268
  lastY = e.clientY;
7022
7269
  // Emit the catch-up move together with start so consumers snap to the pointer
@@ -7229,6 +7476,7 @@ const NOTIFICATIONS_DEF = /* @__PURE__ */ defineRootProvider(() => {
7229
7476
  }).pipe(tap((state) => permission.set(state)));
7230
7477
  let nextTagId = 0;
7231
7478
  const clickHandlers = new Map();
7479
+ const confirmedTags = new Set();
7232
7480
  const openCloseFns = new Set();
7233
7481
  let isClickRelayAttached = false;
7234
7482
  const attachClickRelay = () => {
@@ -7250,6 +7498,18 @@ const NOTIFICATIONS_DEF = /* @__PURE__ */ defineRootProvider(() => {
7250
7498
  ? // Firefox rejects getNotifications() when the worker is not the one that showed them.
7251
7499
  from(registration.getNotifications(tag ? { tag } : undefined)).pipe(catchError(() => of([])))
7252
7500
  : of([])), tap((notifications) => notifications.forEach((notification) => notification.close())));
7501
+ const pruneDismissedHandlers = (registration) => from(registration.getNotifications()).pipe(catchError(() => of(null)), map((shown) => {
7502
+ if (!shown) {
7503
+ return;
7504
+ }
7505
+ const shownTags = new Set(shown.map((notification) => notification.tag));
7506
+ for (const tag of confirmedTags) {
7507
+ if (!shownTags.has(tag)) {
7508
+ confirmedTags.delete(tag);
7509
+ clickHandlers.delete(tag);
7510
+ }
7511
+ }
7512
+ }));
7253
7513
  const show = (config) => {
7254
7514
  const tag = config.tag ?? `et-notification-${++nextTagId}`;
7255
7515
  const options = toNotificationOptions(config, tag);
@@ -7268,6 +7528,7 @@ const NOTIFICATIONS_DEF = /* @__PURE__ */ defineRootProvider(() => {
7268
7528
  closed$.next();
7269
7529
  closed$.complete();
7270
7530
  clickHandlers.delete(tag);
7531
+ confirmedTags.delete(tag);
7271
7532
  openCloseFns.delete(close);
7272
7533
  pageNotification?.close();
7273
7534
  // A no-op unless this notification took the persistent path.
@@ -7301,16 +7562,27 @@ const NOTIFICATIONS_DEF = /* @__PURE__ */ defineRootProvider(() => {
7301
7562
  if (!registration || isClosed) {
7302
7563
  return of('unavailable');
7303
7564
  }
7304
- if (config.onClick) {
7305
- clickHandlers.set(tag, config.onClick);
7306
- attachClickRelay();
7307
- }
7308
- return from(registration.showNotification(config.title, options)).pipe(switchMap(() => isClosed
7309
- ? // Closed while the worker was still putting it up - take back what just appeared.
7310
- closePersistentNotifications(tag).pipe(map(() => 'unavailable'))
7311
- : of('service-worker')), catchError(() => {
7312
- clickHandlers.delete(tag);
7313
- return of('unavailable');
7565
+ return pruneDismissedHandlers(registration).pipe(switchMap(() => {
7566
+ if (isClosed) {
7567
+ return of('unavailable');
7568
+ }
7569
+ if (config.onClick) {
7570
+ clickHandlers.set(tag, config.onClick);
7571
+ attachClickRelay();
7572
+ }
7573
+ return from(registration.showNotification(config.title, options)).pipe(switchMap(() => {
7574
+ if (isClosed) {
7575
+ // Closed while the worker was still putting it up - take back what just appeared.
7576
+ return closePersistentNotifications(tag).pipe(map(() => 'unavailable'));
7577
+ }
7578
+ if (config.onClick) {
7579
+ confirmedTags.add(tag);
7580
+ }
7581
+ return of('service-worker');
7582
+ }), catchError(() => {
7583
+ clickHandlers.delete(tag);
7584
+ return of('unavailable');
7585
+ }));
7314
7586
  }));
7315
7587
  }));
7316
7588
  const shown$ = defer(() => {
@@ -7556,8 +7828,6 @@ const overlayViewportInsetsFor = (element) => overlayViewportInsets(resolveOverl
7556
7828
  /**
7557
7829
  * Runs `listener` whenever a reservation is made or released, so an already positioned overlay can be
7558
7830
  * laid out again. Returns the unsubscribe callback.
7559
- *
7560
- * @internal
7561
7831
  */
7562
7832
  const onOverlayViewportInsetsChange = (listener) => {
7563
7833
  listeners.add(listener);
@@ -7566,10 +7836,6 @@ const onOverlayViewportInsetsChange = (listener) => {
7566
7836
  };
7567
7837
  };
7568
7838
 
7569
- /**
7570
- * Shrinks the host box to the part of the viewport nothing above this overlay has reserved, so a
7571
- * centered or globally placed pane is laid out inside it instead of under a docked panel.
7572
- */
7573
7839
  const applyViewportInsets = (hostElement, renderer) => {
7574
7840
  const insets = overlayViewportInsetsFor(hostElement);
7575
7841
  renderer.setStyle(hostElement, {
@@ -7667,8 +7933,6 @@ let anchoredPositionSetup = null;
7667
7933
  * Installs the anchored positioning implementation. Called by `anchoredOverlayPosition()`, which is
7668
7934
  * the only thing that pulls `@floating-ui/dom` into the bundle - apps that never anchor an overlay
7669
7935
  * do not ship it.
7670
- *
7671
- * @internal
7672
7936
  */
7673
7937
  const registerAnchoredPositionSetup = (setup) => {
7674
7938
  anchoredPositionSetup = setup;
@@ -7700,8 +7964,6 @@ let middlewareExtras = null;
7700
7964
  /**
7701
7965
  * Installs the `size` / `arrow` / `hide` middleware. Called by
7702
7966
  * `enableAnchoredOverlayPositionExtras()`.
7703
- *
7704
- * @internal
7705
7967
  */
7706
7968
  const registerAnchoredPositionMiddlewareExtras = (extras) => {
7707
7969
  middlewareExtras = extras;
@@ -7773,10 +8035,6 @@ const toSideObject = (padding) => typeof padding === 'number'
7773
8035
  bottom: padding.bottom ?? 0,
7774
8036
  left: padding.left ?? 0,
7775
8037
  };
7776
- /**
7777
- * The strategy's own viewport padding plus the space reserved on each edge. Every middleware that
7778
- * measures overflow gets it, so an edge a docked panel covers is as unavailable as the viewport edge.
7779
- */
7780
8038
  const paddingWithInsets = (padding, insets) => {
7781
8039
  const own = toSideObject(padding);
7782
8040
  return {
@@ -7786,6 +8044,43 @@ const paddingWithInsets = (padding, insets) => {
7786
8044
  left: own.left + insets.left,
7787
8045
  };
7788
8046
  };
8047
+ /**
8048
+ * A platform whose first measurement of the pane, in each `computePosition` run, ignores the cap
8049
+ * `size` set in the previous run. `flip` reads that first measurement: against a capped pane, the side
8050
+ * the pane was squeezed onto always fits, so a pane whose content grows after it opened never flips
8051
+ * to the roomier side. Later measurements in the same run see the cap, and `size` resets with them,
8052
+ * so the final position is computed from the pane as it renders.
8053
+ */
8054
+ const platformMeasuringUncappedPaneFirst = (paneElement, renderer) => {
8055
+ let measuredUncapped = false;
8056
+ return {
8057
+ ...platform,
8058
+ getDimensions: (element) => {
8059
+ if (element !== paneElement || measuredUncapped)
8060
+ return platform.getDimensions(element);
8061
+ measuredUncapped = true;
8062
+ const maxWidth = paneElement.style.getPropertyValue('--et-overlay-max-width');
8063
+ const maxHeight = paneElement.style.getPropertyValue('--et-overlay-max-height');
8064
+ if (!maxWidth && !maxHeight)
8065
+ return platform.getDimensions(element);
8066
+ // The uncapped layout clamps the scroll offset of every scroller the cap made scrollable.
8067
+ const scrolled = [...paneElement.querySelectorAll('*')]
8068
+ .filter((el) => el.scrollTop || el.scrollLeft)
8069
+ .map((el) => ({ el, top: el.scrollTop, left: el.scrollLeft }));
8070
+ renderer.setCssProperties(paneElement, { '--et-overlay-max-width': null, '--et-overlay-max-height': null });
8071
+ const dimensions = platform.getDimensions(element);
8072
+ renderer.setCssProperties(paneElement, {
8073
+ '--et-overlay-max-width': maxWidth || null,
8074
+ '--et-overlay-max-height': maxHeight || null,
8075
+ });
8076
+ for (const { el, top, left } of scrolled) {
8077
+ el.scrollTop = top;
8078
+ el.scrollLeft = left;
8079
+ }
8080
+ return dimensions;
8081
+ },
8082
+ };
8083
+ };
7789
8084
  const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, renderer) => {
7790
8085
  // mirrorWidth needs a real element to measure; virtual references fall back to max-content
7791
8086
  const mirrorWidthElement = strategy.mirrorWidth && isHTMLElement(strategy.referenceElement) ? strategy.referenceElement : null;
@@ -7898,6 +8193,9 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
7898
8193
  placement: strategy.placement ?? 'bottom',
7899
8194
  strategy: 'absolute',
7900
8195
  middleware,
8196
+ platform: strategy.autoResize && strategy.minAvailableSpace === undefined
8197
+ ? platformMeasuringUncappedPaneFirst(paneElement, renderer)
8198
+ : platform,
7901
8199
  }).then(({ x, y, placement, middlewareData }) => {
7902
8200
  if (destroyed || currentUpdateId !== updateId)
7903
8201
  return;
@@ -7964,10 +8262,10 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
7964
8262
  ...(ngDevMode ? [{ debugName: "_componentInstance" }] : /* istanbul ignore next */ []));
7965
8263
  let positionUpdater = null;
7966
8264
  let backdropUpdater = null;
7967
- const beforeOpenedSubject = new Subject();
7968
- const afterOpenedSubject = new Subject();
7969
- const beforeClosedSubject = new Subject();
7970
- const afterClosedSubject = new Subject();
8265
+ const beforeOpenedSubject = new ReplaySubject(1);
8266
+ const afterOpenedSubject = new ReplaySubject(1);
8267
+ const beforeClosedSubject = new ReplaySubject(1);
8268
+ const afterClosedSubject = new ReplaySubject(1);
7971
8269
  const closeGuards = new Set();
7972
8270
  return {
7973
8271
  id,
@@ -7975,8 +8273,6 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
7975
8273
  elements,
7976
8274
  state: _state.asReadonly(),
7977
8275
  componentInstance: _componentInstance.asReadonly(),
7978
- /** @internal */
7979
- beforeOpenedSubject,
7980
8276
  close(result, source = 'api') {
7981
8277
  if (_state() === 'closing' || _state() === 'closed') {
7982
8278
  return;
@@ -8016,11 +8312,13 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8016
8312
  afterClosed() {
8017
8313
  return afterClosedSubject.asObservable();
8018
8314
  },
8019
- /** @internal */
8020
8315
  attachComponentRef(componentRef) {
8021
8316
  _componentInstance.set(componentRef.instance);
8022
8317
  },
8023
- /** @internal */
8318
+ markBeforeOpened() {
8319
+ beforeOpenedSubject.next();
8320
+ beforeOpenedSubject.complete();
8321
+ },
8024
8322
  attachPositionUpdater(updater) {
8025
8323
  positionUpdater = updater;
8026
8324
  },
@@ -8030,7 +8328,6 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8030
8328
  }
8031
8329
  positionUpdater?.(strategy);
8032
8330
  },
8033
- /** @internal */
8034
8331
  attachBackdropUpdater(updater) {
8035
8332
  backdropUpdater = updater;
8036
8333
  },
@@ -8044,7 +8341,6 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8044
8341
  }
8045
8342
  backdropUpdater?.(hasBackdrop);
8046
8343
  },
8047
- /** @internal */
8048
8344
  markOpened() {
8049
8345
  if (_state() !== 'mounting') {
8050
8346
  return;
@@ -8053,7 +8349,6 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8053
8349
  afterOpenedSubject.next();
8054
8350
  afterOpenedSubject.complete();
8055
8351
  },
8056
- /** @internal */
8057
8352
  beginClose(closeEvent) {
8058
8353
  if (_state() === 'closing' || _state() === 'closed') {
8059
8354
  return false;
@@ -8063,7 +8358,6 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
8063
8358
  beforeClosedSubject.complete();
8064
8359
  return true;
8065
8360
  },
8066
- /** @internal */
8067
8361
  finishClose(closeEvent) {
8068
8362
  if (_state() === 'closed') {
8069
8363
  return;
@@ -8093,6 +8387,14 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8093
8387
  // Synchronous teardown for each currently-mounted overlay, run when the runtime's injector is
8094
8388
  // destroyed (app teardown). Registered on mount, removed once the overlay is destroyed normally.
8095
8389
  const mountedTeardowns = new Set();
8390
+ const armedReopenSwallows = new Set();
8391
+ const focusRestoreChains = new WeakMap();
8392
+ const resolveFocusRestoreChain = (focused) => {
8393
+ if (!focused)
8394
+ return [];
8395
+ const owner = openEntriesState().find((entry) => entry.elements.hostElement.contains(focused));
8396
+ return [focused, ...((owner && focusRestoreChains.get(owner)) ?? [])];
8397
+ };
8096
8398
  const getRootElement = (targetDocument, zIndex) => {
8097
8399
  let documentRoots = rootElements.get(targetDocument);
8098
8400
  if (!documentRoots) {
@@ -8165,9 +8467,7 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8165
8467
  const paneElement = renderer.createElement('div');
8166
8468
  const backdropElement = signal(null, /* @ts-ignore */
8167
8469
  ...(ngDevMode ? [{ debugName: "backdropElement" }] : /* istanbul ignore next */ []));
8168
- const previousFocusedElement = isHTMLOrSVGElement(targetDocument.activeElement)
8169
- ? targetDocument.activeElement
8170
- : null;
8470
+ const focusRestoreChain = resolveFocusRestoreChain(isHTMLOrSVGElement(targetDocument.activeElement) ? targetDocument.activeElement : null);
8171
8471
  const autoFocus = config.autoFocus ?? 'first-tabbable';
8172
8472
  let currentHasBackdrop = config.hasBackdrop !== false;
8173
8473
  let currentPositionStrategy = config.positionStrategy;
@@ -8210,8 +8510,6 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8210
8510
  paneElement,
8211
8511
  }, (result, source) => beginClose({ result, source }));
8212
8512
  animationDebugLog(`runtime ${config.id}`, 'mount');
8213
- // matches the previous overlay behavior: escape/outside-pointer closes are
8214
- // ignored until the enter transition has started plus one frame
8215
8513
  let interactiveCloseReady = false;
8216
8514
  const cleanupFns = [];
8217
8515
  const parentInjector = config.injector ?? config.viewContainerRef?.injector ?? environmentInjector;
@@ -8225,19 +8523,29 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8225
8523
  // `config.providers`.
8226
8524
  providers: [{ provide: EnvironmentInjector, useValue: overlayEnvironmentInjector }],
8227
8525
  });
8228
- const componentRef = createComponent(config.component, {
8229
- environmentInjector: overlayEnvironmentInjector,
8230
- elementInjector,
8231
- hostElement: paneElement,
8232
- bindings: config.bindings ?? [],
8233
- });
8526
+ let componentRef;
8527
+ try {
8528
+ componentRef = createComponent(config.component, {
8529
+ environmentInjector: overlayEnvironmentInjector,
8530
+ elementInjector,
8531
+ hostElement: paneElement,
8532
+ bindings: config.bindings ?? [],
8533
+ });
8534
+ }
8535
+ catch (error) {
8536
+ elementInjector.destroy();
8537
+ overlayEnvironmentInjector.destroy();
8538
+ renderer.removeChild(root, hostElement);
8539
+ maybeDestroyRootElements(targetDocument);
8540
+ throw error;
8541
+ }
8234
8542
  // pane classes are applied after component creation - Angular replaces the host
8235
8543
  // element's class attribute with the component's static host class on creation
8236
8544
  renderer.addClass(paneElement, 'et-overlay-runtime-pane', ...(config.paneClass ?? []));
8237
8545
  appRef.attachView(componentRef.hostView);
8238
8546
  overlayRef.attachComponentRef(componentRef);
8239
- overlayRef.beforeOpenedSubject.next();
8240
- overlayRef.beforeOpenedSubject.complete();
8547
+ overlayRef.markBeforeOpened();
8548
+ focusRestoreChains.set(overlayRef, focusRestoreChain);
8241
8549
  openEntriesState.update((entries) => [...entries, overlayRef]);
8242
8550
  let positionCleanup = setupPositioning(activeConfig(), hostElement, paneElement, overlayRef, renderer);
8243
8551
  cleanupFns.push(() => positionCleanup());
@@ -8292,8 +8600,8 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8292
8600
  }
8293
8601
  openEntriesState.update((entries) => entries.filter((entry) => entry !== overlayRef));
8294
8602
  maybeDestroyRootElements(targetDocument);
8295
- if (config.restoreFocus !== false && overlayStillOwnsFocus && previousFocusedElement?.isConnected) {
8296
- previousFocusedElement.focus({ preventScroll: true });
8603
+ if (config.restoreFocus !== false && overlayStillOwnsFocus) {
8604
+ focusRestoreChain.find((element) => element.isConnected)?.focus({ preventScroll: true });
8297
8605
  }
8298
8606
  overlayRef.finishClose(closeEvent);
8299
8607
  };
@@ -8311,7 +8619,7 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8311
8619
  return;
8312
8620
  }
8313
8621
  const lifecycle = getAnimatedLifecycle(componentRef);
8314
- animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state$.value ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
8622
+ animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state() ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
8315
8623
  // The reference element is gone - there is nothing left to animate away from, so tear the
8316
8624
  // overlay down synchronously instead of playing a leave transition from a stale position.
8317
8625
  if (!lifecycle || closeEvent.source === 'reference-detached') {
@@ -8325,10 +8633,8 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8325
8633
  lifecycle.leave();
8326
8634
  }
8327
8635
  lifecycle.state$
8328
- .pipe(filter((state) => state === 'left'), take(1))
8329
- .subscribe(() => {
8330
- destroyMountedOverlay(closeEvent);
8331
- });
8636
+ .pipe(filter((state) => state === 'left'), take(1), tap(() => destroyMountedOverlay(closeEvent)))
8637
+ .subscribe();
8332
8638
  };
8333
8639
  const closeOnEscape = config.closeOnEscape ?? true;
8334
8640
  if (closeOnEscape) {
@@ -8370,12 +8676,22 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8370
8676
  const originElement = getOriginElement();
8371
8677
  if (originElement && originElement.contains(target)) {
8372
8678
  const swallowReopenClick = (clickEvent) => {
8679
+ disarmReopenSwallow();
8373
8680
  if (isElement(clickEvent.target) && originElement.contains(clickEvent.target)) {
8374
8681
  clickEvent.stopImmediatePropagation();
8375
8682
  clickEvent.preventDefault();
8376
8683
  }
8377
8684
  };
8378
- targetDocument.addEventListener('click', swallowReopenClick, { capture: true, once: true });
8685
+ // A press that ends without a click (cancelled, dragged off) must not leave the swallow
8686
+ // armed for the next, unrelated click on the origin.
8687
+ const disarmReopenSwallow = () => {
8688
+ targetDocument.removeEventListener('click', swallowReopenClick, true);
8689
+ targetDocument.removeEventListener('pointerdown', disarmReopenSwallow, true);
8690
+ armedReopenSwallows.delete(disarmReopenSwallow);
8691
+ };
8692
+ targetDocument.addEventListener('click', swallowReopenClick, true);
8693
+ targetDocument.addEventListener('pointerdown', disarmReopenSwallow, true);
8694
+ armedReopenSwallows.add(disarmReopenSwallow);
8379
8695
  }
8380
8696
  };
8381
8697
  targetDocument.addEventListener('pointerdown', onPointerDown, true);
@@ -8394,14 +8710,14 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8394
8710
  overlayRef.markOpened();
8395
8711
  return;
8396
8712
  }
8397
- animationDebugLog(`runtime ${config.id}`, `enter (delegate ${config.animationDelegate?.enter ? 'yes' : 'no'}, lifecycle state "${lifecycle.state$.value}")`);
8713
+ animationDebugLog(`runtime ${config.id}`, `enter (delegate ${config.animationDelegate?.enter ? 'yes' : 'no'}, lifecycle state "${lifecycle.state()}")`);
8398
8714
  const readySubscription = lifecycle.state$
8399
- .pipe(filter((state) => state === 'entering' || state === 'entered'), take(1))
8400
- .subscribe(() => {
8715
+ .pipe(filter((state) => state === 'entering' || state === 'entered'), take(1), tap(() => {
8401
8716
  nextFrame(() => {
8402
8717
  interactiveCloseReady = true;
8403
8718
  });
8404
- });
8719
+ }))
8720
+ .subscribe();
8405
8721
  cleanupFns.push(() => readySubscription.unsubscribe());
8406
8722
  if (config.animationDelegate?.enter) {
8407
8723
  config.animationDelegate.enter({ lifecycle, elements: overlayRef.elements });
@@ -8410,16 +8726,17 @@ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
8410
8726
  lifecycle.enter();
8411
8727
  }
8412
8728
  lifecycle.state$
8413
- .pipe(filter((state) => state === 'entered'), take(1))
8414
- .subscribe(() => {
8729
+ .pipe(filter((state) => state === 'entered'), take(1), tap(() => {
8415
8730
  applyInitialFocus(paneElement, autoFocus, targetDocument);
8416
8731
  overlayRef.markOpened();
8417
- });
8732
+ }))
8733
+ .subscribe();
8418
8734
  });
8419
8735
  return overlayRef;
8420
8736
  };
8421
8737
  destroyRef.onDestroy(() => {
8422
8738
  [...mountedTeardowns].forEach((teardown) => teardown());
8739
+ [...armedReopenSwallows].forEach((disarm) => disarm());
8423
8740
  [...rootElements.keys()].forEach((targetDocument) => maybeDestroyRootElements(targetDocument));
8424
8741
  });
8425
8742
  return {
@@ -9010,7 +9327,7 @@ const initials = (value, maxLength = 2) => {
9010
9327
  return '';
9011
9328
  return words
9012
9329
  .slice(0, maxLength)
9013
- .map((word) => word[0]?.toUpperCase() ?? '')
9330
+ .map((word) => [...word.normalize()][0]?.toUpperCase() ?? '')
9014
9331
  .join('');
9015
9332
  };
9016
9333
 
@@ -9089,20 +9406,38 @@ const EDGE_CURSORS = {
9089
9406
  nw: 'nwse-resize',
9090
9407
  se: 'nwse-resize',
9091
9408
  };
9092
- const setupResizeObservable = (startEvent, edge, doc) => {
9409
+ // jsdom and other non-browser DOMs ship no pointer capture; the document listeners track the
9410
+ // gesture either way, so a failure here only costs events over a cross-origin frame.
9411
+ const capturePointer = (el, pointerId) => {
9412
+ try {
9413
+ el.setPointerCapture(pointerId);
9414
+ }
9415
+ catch {
9416
+ return;
9417
+ }
9418
+ };
9419
+ const setupResizeObservable = (startEvent, handle, edge, doc) => {
9093
9420
  const pointerId = startEvent.pointerId;
9094
9421
  const startX = startEvent.clientX;
9095
9422
  const startY = startEvent.clientY;
9096
9423
  let cancelled = false;
9424
+ let last = { edge, totalDx: 0, totalDy: 0, clientX: startX, clientY: startY };
9097
9425
  const end$ = merge(fromEvent(doc, 'pointerup'), fromEvent(doc, 'pointercancel')).pipe(filter((e) => e.pointerId === pointerId), take(1),
9098
9426
  // Read below by the terminating `defer`, which only runs once this has completed the moves.
9099
9427
  tap((e) => (cancelled = e.type === 'pointercancel')));
9100
9428
  return defer(() => {
9101
9429
  const releaseSelection = suppressTextSelection(doc);
9102
- return concat(of({ type: 'start', edge }), fromEvent(doc, 'pointermove').pipe(filter((e) => e.pointerId === pointerId), map((e) => ({
9103
- type: 'move',
9104
- data: { edge, dx: e.clientX - startX, dy: e.clientY - startY, clientX: e.clientX, clientY: e.clientY },
9105
- })), takeUntil(end$)), defer(() => of({ type: cancelled ? 'cancelled' : 'end' }))).pipe(finalize(releaseSelection));
9430
+ capturePointer(handle, pointerId);
9431
+ return concat(of({ type: 'start', edge }), fromEvent(doc, 'pointermove').pipe(filter((e) => e.pointerId === pointerId), map((e) => {
9432
+ last = {
9433
+ edge,
9434
+ totalDx: e.clientX - startX,
9435
+ totalDy: e.clientY - startY,
9436
+ clientX: e.clientX,
9437
+ clientY: e.clientY,
9438
+ };
9439
+ return { type: 'move', data: last };
9440
+ }), takeUntil(end$)), defer(() => of(cancelled ? { type: 'cancelled' } : { type: 'end', data: last }))).pipe(finalize(releaseSelection));
9106
9441
  });
9107
9442
  };
9108
9443
  class ResizeHandlesComponent {
@@ -9112,10 +9447,10 @@ class ResizeHandlesComponent {
9112
9447
  ...(ngDevMode ? [{ debugName: "edges" }] : /* istanbul ignore next */ []));
9113
9448
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9114
9449
  this.gestureStart$ = new Subject();
9115
- this.gesture$ = this.gestureStart$.pipe(exhaustMap(({ event, edge }) => setupResizeObservable(event, edge, this.el.nativeElement.ownerDocument)), share(), takeUntilDestroyed());
9450
+ this.gesture$ = this.gestureStart$.pipe(exhaustMap(({ event, handle, edge }) => setupResizeObservable(event, handle, edge, this.el.nativeElement.ownerDocument)), share(), takeUntilDestroyed());
9116
9451
  this.resizeStarted = outputFromObservable(this.gesture$.pipe(filter((e) => e.type === 'start'), map((e) => e.edge)));
9117
9452
  this.resizeMoved = outputFromObservable(this.gesture$.pipe(filter((e) => e.type === 'move'), map((e) => e.data)));
9118
- this.resizeEnded = outputFromObservable(this.gesture$.pipe(filter((e) => e.type === 'end'), map(() => undefined)));
9453
+ this.resizeEnded = outputFromObservable(this.gesture$.pipe(filter((e) => e.type === 'end'), map((e) => e.data)));
9119
9454
  /**
9120
9455
  * The browser took the gesture away mid-resize (a system gesture, an incoming call, the tab going
9121
9456
  * to the background). Revert to the size the resize started from - the user never let go, so there
@@ -9136,7 +9471,7 @@ class ResizeHandlesComponent {
9136
9471
  if (event.button !== 0 || this.disabled())
9137
9472
  return;
9138
9473
  event.stopPropagation();
9139
- this.gestureStart$.next({ event, edge });
9474
+ this.gestureStart$.next({ event, handle: event.currentTarget, edge });
9140
9475
  }
9141
9476
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ResizeHandlesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9142
9477
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ResizeHandlesComponent, isStandalone: true, selector: "et-resize-handles", inputs: { edges: { classPropertyName: "edges", publicName: "edges", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { resizeStarted: "resizeStarted", resizeMoved: "resizeMoved", resizeEnded: "resizeEnded", resizeCancelled: "resizeCancelled" }, host: { properties: { "attr.inert": "disabled() ? \"\" : null", "attr.data-active-edge": "activeEdge()" }, classAttribute: "et-resize-handles" }, ngImport: i0, template: `
@@ -9398,66 +9733,300 @@ const injectDefaultSurfaceTheme = (type) => {
9398
9733
  return defaults.length === 1 ? (defaults[0] ?? null) : null;
9399
9734
  };
9400
9735
 
9401
- const SURFACE_PROVIDER = new InjectionToken('SurfaceProvider');
9402
- const FORCED_SURFACE_UNSET = /* @__PURE__ */ Symbol('FORCED_SURFACE_UNSET');
9403
- class ProvideSurfaceDirective {
9404
- constructor() {
9405
- this.themes = injectSurfaceThemes({ optional: true });
9406
- this.prefix = injectSurfaceThemesPrefix({ optional: true });
9407
- this.injector = inject(Injector);
9408
- this.parentProvider = inject(SURFACE_PROVIDER, { optional: true, skipSelf: true });
9409
- this.defaultTheme = injectDefaultSurfaceTheme();
9410
- this.currentProviderSync = null;
9411
- this.forcedSurface = signal(FORCED_SURFACE_UNSET, /* @ts-ignore */
9412
- ...(ngDevMode ? [{ debugName: "forcedSurface" }] : /* istanbul ignore next */ []));
9413
- this.surface = input(undefined, { ...(ngDevMode ? { debugName: "surface" } : /* istanbul ignore next */ {}), alias: 'etProvideSurface' });
9414
- this.effectiveSurface = computed(() => {
9415
- const forcedSurface = this.forcedSurface();
9416
- if (forcedSurface !== FORCED_SURFACE_UNSET) {
9417
- return forcedSurface;
9418
- }
9419
- return this.surface();
9420
- }, /* @ts-ignore */
9421
- ...(ngDevMode ? [{ debugName: "effectiveSurface" }] : /* istanbul ignore next */ []));
9422
- this.resolvedTheme = computed(() => {
9423
- const value = this.effectiveSurface();
9424
- if (!this.themes)
9425
- return null;
9426
- // Only the outermost provider adopts the app default. A nested one left unset must keep
9427
- // resolving to `null` so it reports the surface it inherits instead of resetting elevation.
9428
- if (!value)
9429
- return this.parentProvider ? null : this.defaultTheme;
9430
- const theme = this.themes.find((t) => t.name === value) ?? null;
9431
- if (isDevMode() && !theme) {
9432
- console.error(`Surface theme ${value} does not exist. Please make sure to add it to provideSurfaceThemesWithTailwind4()`);
9433
- }
9434
- return theme;
9435
- }, /* @ts-ignore */
9436
- ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
9437
- /**
9438
- * The surface theme in effect on this element - the theme this provider resolves itself, or,
9439
- * where it is left unset, the one it inherits from the closest ancestor provider. The outermost
9440
- * provider resolves the app default, so the chain ends there.
9441
- */
9442
- this.activeTheme = computed(() => this.resolvedTheme() ?? this.parentProvider?.activeTheme() ?? null, /* @ts-ignore */
9443
- ...(ngDevMode ? [{ debugName: "activeTheme" }] : /* istanbul ignore next */ []));
9444
- this.elevation = computed(() => this.activeTheme()?.elevation ?? 0, /* @ts-ignore */
9445
- ...(ngDevMode ? [{ debugName: "elevation" }] : /* istanbul ignore next */ []));
9446
- this.surfaceType = computed(() => this.activeTheme()?.type ?? null, /* @ts-ignore */
9447
- ...(ngDevMode ? [{ debugName: "surfaceType" }] : /* istanbul ignore next */ []));
9448
- this.surfaceName = computed(() => {
9449
- const value = this.effectiveSurface();
9450
- if (!this.themes || !value)
9451
- return;
9452
- return createCssSurfaceName(value);
9453
- }, /* @ts-ignore */
9454
- ...(ngDevMode ? [{ debugName: "surfaceName" }] : /* istanbul ignore next */ []));
9455
- this.surfaceClass = computed(() => {
9736
+ /**
9737
+ * Reserved color name. Resolves the color tokens from the ambient surface's neutral swatch
9738
+ * (`SurfaceTheme['interactionColor']`) instead of a registered accent, so a secondary action can
9739
+ * read as chrome without the app registering a neutral color theme. Apps must not register a color
9740
+ * theme under this name.
9741
+ */
9742
+ const SURFACE_COLOR = 'surface';
9743
+ const createCssColorThemeName = (name) => name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
9744
+ const COLOR_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
9745
+ name: 'Color Themes',
9746
+ });
9747
+ const ɵProvideColorThemes = /* @__PURE__ */ toProvideFn(COLOR_THEMES_DEF);
9748
+ const injectColorThemes = /* @__PURE__ */ toInjectFn(COLOR_THEMES_DEF);
9749
+ const COLOR_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
9750
+ name: 'Themes Prefix',
9751
+ });
9752
+ const ɵProvideColorThemesPrefix = /* @__PURE__ */ toProvideFn(COLOR_THEMES_PREFIX_DEF);
9753
+ const injectColorThemesPrefix = /* @__PURE__ */ toInjectFn(COLOR_THEMES_PREFIX_DEF);
9754
+ const provideColorThemesWithTailwind4 = (themes, prefix = 'et') => {
9755
+ if (isDevMode() && themes.some((theme) => theme.name === SURFACE_COLOR)) {
9756
+ console.error(`[provideColorThemesWithTailwind4] "${SURFACE_COLOR}" is a reserved color name that resolves to the ambient surface's neutral swatch. Rename your theme.`);
9757
+ }
9758
+ return [ɵProvideColorThemes(themes), ɵProvideColorThemesPrefix(prefix)];
9759
+ };
9760
+ const findColorThemeByType = (themes, type, caller) => {
9761
+ if (!themes) {
9762
+ throw new Error(`[${caller}] No color themes provided. Call provideColorThemesWithTailwind4() in your app config.`);
9763
+ }
9764
+ const theme = themes.find((t) => t.type === type);
9765
+ if (!theme) {
9766
+ throw new Error(`[${caller}] No color theme with type "${type}" found. Add a theme with type: "${type}" to provideColorThemesWithTailwind4().`);
9767
+ }
9768
+ if (isDevMode()) {
9769
+ const duplicates = themes.filter((t) => t.type === type);
9770
+ if (duplicates.length > 1) {
9771
+ console.error(`[${caller}] Multiple themes with type "${type}" found: ${duplicates.map((t) => t.name).join(', ')}. Only the first one will be used.`);
9772
+ }
9773
+ }
9774
+ return theme;
9775
+ };
9776
+ const injectColorThemeByType = (type) => findColorThemeByType(injectColorThemes({ optional: true }), type, 'injectColorThemeByType');
9777
+ /**
9778
+ * A signal of the color theme for `type` on the surface this element renders on: the theme the nearest
9779
+ * surface names in `semanticColorThemes`, else the first registered theme of that `type`. Reading it throws
9780
+ * when the app registered no theme of that `type`, so read it only for a state that renders.
9781
+ *
9782
+ * @example
9783
+ * protected successTheme = injectSemanticColorTheme('success');
9784
+ * // <span [etProvideColor]="successTheme()">+12%</span>
9785
+ */
9786
+ const injectSemanticColorTheme = (type) => {
9787
+ const themes = injectColorThemes({ optional: true });
9788
+ const surfaceProvider = inject(SURFACE_PROVIDER, { optional: true });
9789
+ const defaultSurface = injectDefaultSurfaceTheme();
9790
+ return computed(() => {
9791
+ const surface = surfaceProvider?.activeTheme() ?? defaultSurface;
9792
+ const mappedName = surface?.semanticColorThemes?.[type];
9793
+ const mapped = mappedName ? themes?.find((t) => t.name === mappedName) : undefined;
9794
+ if (mapped)
9795
+ return mapped;
9796
+ if (mappedName && isDevMode()) {
9797
+ console.error(`[injectSemanticColorTheme] Surface theme "${surface?.name}" maps "${type}" to the color theme "${mappedName}", which is not registered. Add it to provideColorThemesWithTailwind4().`);
9798
+ }
9799
+ return findColorThemeByType(themes, type, 'injectSemanticColorTheme');
9800
+ });
9801
+ };
9802
+ /** @deprecated Use `injectSemanticColorTheme('error')`, which follows the surface the element renders on. */
9803
+ const injectErrorTheme = () => injectColorThemeByType('error');
9804
+ /** @deprecated Use `injectSemanticColorTheme('warning')`, which follows the surface the element renders on. */
9805
+ const injectWarningTheme = () => injectColorThemeByType('warning');
9806
+ /** @deprecated Use `injectSemanticColorTheme('success')`, which follows the surface the element renders on. */
9807
+ const injectSuccessTheme = () => injectColorThemeByType('success');
9808
+ const injectDefaultColorTheme = () => {
9809
+ const themes = injectColorThemes({ optional: true });
9810
+ if (!themes) {
9811
+ throw new Error(`[injectDefaultColorTheme] No color themes provided. Call provideColorThemesWithTailwind4() in your app config.`);
9812
+ }
9813
+ const theme = themes.find((t) => t.isDefault);
9814
+ if (!theme) {
9815
+ throw new Error(`[injectDefaultColorTheme] No color theme with isDefault: true found. Mark one theme as the default in provideColorThemesWithTailwind4().`);
9816
+ }
9817
+ if (isDevMode()) {
9818
+ const duplicates = themes.filter((t) => t.isDefault);
9819
+ if (duplicates.length > 1) {
9820
+ console.error(`[injectDefaultColorTheme] Multiple themes with isDefault: true found: ${duplicates.map((t) => t.name).join(', ')}. Only the first one will be used.`);
9821
+ }
9822
+ }
9823
+ return theme;
9824
+ };
9825
+
9826
+ const COLOR_PROVIDER = new InjectionToken('ColorProvider');
9827
+ /**
9828
+ * The color provider attached to a bootstrapped root component (e.g. `ProvideColorDirective` in
9829
+ * the app component's `hostDirectives`), if any. Detached DOM such as overlay panes can't reach
9830
+ * it through element DI, so overlay containers fall back to this when resolving the provider to
9831
+ * `syncWithProvider()` - resolve at sync time, not at injection time, since root components only
9832
+ * register with `ApplicationRef` once bootstrap completes.
9833
+ */
9834
+ const resolveAppRootColorProvider = (appRef) => {
9835
+ for (const componentRef of appRef.components) {
9836
+ const provider = componentRef.injector.get(COLOR_PROVIDER, null);
9837
+ if (provider) {
9838
+ return provider;
9839
+ }
9840
+ }
9841
+ return null;
9842
+ };
9843
+ const FORCED_COLOR_UNSET = /* @__PURE__ */ Symbol('FORCED_COLOR_UNSET');
9844
+ const appsWarnedAboutMissingThemes = /* @__PURE__ */ new WeakSet();
9845
+ class ProvideColorDirective {
9846
+ constructor() {
9847
+ this.themes = injectColorThemes({ optional: true });
9848
+ this.prefix = injectColorThemesPrefix({ optional: true });
9849
+ this.injector = inject(Injector);
9850
+ this.parentProvider = inject(COLOR_PROVIDER, { optional: true, skipSelf: true });
9851
+ this.appRef = inject(ApplicationRef);
9852
+ this.currentProviderSync = null;
9853
+ this.forcedColor = signal(FORCED_COLOR_UNSET, /* @ts-ignore */
9854
+ ...(ngDevMode ? [{ debugName: "forcedColor" }] : /* istanbul ignore next */ []));
9855
+ this.color = input(undefined, { ...(ngDevMode ? { debugName: "color" } : /* istanbul ignore next */ {}), alias: 'etProvideColor' });
9856
+ this.effectiveColor = computed(() => {
9857
+ const forcedColor = this.forcedColor();
9858
+ if (forcedColor !== FORCED_COLOR_UNSET) {
9859
+ return forcedColor;
9860
+ }
9861
+ return this.color();
9862
+ }, /* @ts-ignore */
9863
+ ...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
9864
+ /**
9865
+ * The color that actually applies at this provider's location: its own effective color, or -
9866
+ * when this provider is passive (no input, nothing forced) - the nearest ancestor provider's.
9867
+ * Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
9868
+ * consumers that re-apply context across detached DOM (overlay panes) must use this instead
9869
+ * of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
9870
+ */
9871
+ this.resolvedColor = computed(() => {
9872
+ const own = this.effectiveColor();
9873
+ const ownName = typeof own === 'object' && own !== null ? own.name : own;
9874
+ if (ownName) {
9875
+ return own;
9876
+ }
9877
+ return this.parentProvider?.resolvedColor();
9878
+ }, /* @ts-ignore */
9879
+ ...(ngDevMode ? [{ debugName: "resolvedColor" }] : /* istanbul ignore next */ []));
9880
+ this.colorName = computed(() => {
9881
+ const raw = this.effectiveColor();
9882
+ const value = typeof raw === 'object' && raw !== null ? raw.name : raw;
9883
+ if (value === SURFACE_COLOR)
9884
+ return SURFACE_COLOR;
9885
+ if (!value)
9886
+ return;
9887
+ if (!this.themes) {
9888
+ if (isDevMode() && !appsWarnedAboutMissingThemes.has(this.appRef)) {
9889
+ appsWarnedAboutMissingThemes.add(this.appRef);
9890
+ console.warn(`etProvideColor="${value}" has no effect because no color themes are registered. Please make sure to add provideColorThemesWithTailwind4()`);
9891
+ }
9892
+ return;
9893
+ }
9894
+ if (isDevMode() && !this.themes.some((theme) => theme.name === value)) {
9895
+ console.error(`Theme ${value} does not exist. Please make sure to add it to provideColorThemesWithTailwind4()`);
9896
+ }
9897
+ return createCssColorThemeName(value);
9898
+ }, /* @ts-ignore */
9899
+ ...(ngDevMode ? [{ debugName: "colorName" }] : /* istanbul ignore next */ []));
9900
+ this.themeClass = computed(() => {
9456
9901
  const prefix = this.prefix || 'et';
9457
- if (this.surfaceName()) {
9458
- return `${prefix}-surface--${this.surfaceName()}`;
9902
+ if (this.colorName()) {
9903
+ return `${prefix}-color--${this.colorName()}`;
9904
+ }
9905
+ return `${prefix}-color--inherited`;
9906
+ }, /* @ts-ignore */
9907
+ ...(ngDevMode ? [{ debugName: "themeClass" }] : /* istanbul ignore next */ []));
9908
+ }
9909
+ syncWithProvider(provider) {
9910
+ this.currentProviderSync?.destroy();
9911
+ runInInjectionContext(this.injector, () => {
9912
+ this.currentProviderSync = effect(() => {
9913
+ const provideColor = provider.resolvedColor();
9914
+ untracked(() => {
9915
+ if (provideColor === undefined) {
9916
+ this.clearForcedColor();
9917
+ return;
9918
+ }
9919
+ this.forceColor(provideColor);
9920
+ });
9921
+ }, /* @ts-ignore */
9922
+ ...(ngDevMode ? [{ debugName: "currentProviderSync" }] : /* istanbul ignore next */ []));
9923
+ });
9924
+ }
9925
+ stopSyncWithProvider() {
9926
+ const hadProviderSync = !!this.currentProviderSync;
9927
+ this.currentProviderSync?.destroy();
9928
+ this.currentProviderSync = null;
9929
+ if (hadProviderSync) {
9930
+ this.clearForcedColor();
9931
+ }
9932
+ }
9933
+ forceColor(color) {
9934
+ this.forcedColor.set(color);
9935
+ }
9936
+ clearForcedColor() {
9937
+ if (this.forcedColor() === FORCED_COLOR_UNSET) {
9938
+ return;
9939
+ }
9940
+ this.forcedColor.set(FORCED_COLOR_UNSET);
9941
+ }
9942
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProvideColorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9943
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ProvideColorDirective, isStandalone: true, selector: "[etProvideColor]", inputs: { color: { classPropertyName: "color", publicName: "etProvideColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "themeClass()" } }, providers: [{ provide: COLOR_PROVIDER, useExisting: ProvideColorDirective }], ngImport: i0 }); }
9944
+ }
9945
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProvideColorDirective, decorators: [{
9946
+ type: Directive,
9947
+ args: [{
9948
+ selector: '[etProvideColor]',
9949
+ providers: [{ provide: COLOR_PROVIDER, useExisting: ProvideColorDirective }],
9950
+ host: {
9951
+ '[class]': 'themeClass()',
9952
+ },
9953
+ }]
9954
+ }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "etProvideColor", required: false }] }] } });
9955
+
9956
+ const SURFACE_PROVIDER = new InjectionToken('SurfaceProvider');
9957
+ const FORCED_SURFACE_UNSET = /* @__PURE__ */ Symbol('FORCED_SURFACE_UNSET');
9958
+ class ProvideSurfaceDirective {
9959
+ constructor() {
9960
+ this.themes = injectSurfaceThemes({ optional: true });
9961
+ this.prefix = injectSurfaceThemesPrefix({ optional: true });
9962
+ this.injector = inject(Injector);
9963
+ this.parentProvider = inject(SURFACE_PROVIDER, { optional: true, skipSelf: true });
9964
+ this.defaultTheme = injectDefaultSurfaceTheme();
9965
+ this.colorThemes = injectColorThemes({ optional: true });
9966
+ this.colorPrefix = injectColorThemesPrefix({ optional: true });
9967
+ this.colorProvider = inject(COLOR_PROVIDER, { optional: true });
9968
+ this.currentProviderSync = null;
9969
+ this.forcedSurface = signal(FORCED_SURFACE_UNSET, /* @ts-ignore */
9970
+ ...(ngDevMode ? [{ debugName: "forcedSurface" }] : /* istanbul ignore next */ []));
9971
+ this.surface = input(undefined, { ...(ngDevMode ? { debugName: "surface" } : /* istanbul ignore next */ {}), alias: 'etProvideSurface' });
9972
+ this.effectiveSurface = computed(() => {
9973
+ const forcedSurface = this.forcedSurface();
9974
+ if (forcedSurface !== FORCED_SURFACE_UNSET) {
9975
+ return forcedSurface;
9976
+ }
9977
+ return this.surface();
9978
+ }, /* @ts-ignore */
9979
+ ...(ngDevMode ? [{ debugName: "effectiveSurface" }] : /* istanbul ignore next */ []));
9980
+ this.resolvedTheme = computed(() => {
9981
+ const value = this.effectiveSurface();
9982
+ if (!this.themes)
9983
+ return null;
9984
+ // Only the outermost provider adopts the app default. A nested one left unset must keep
9985
+ // resolving to `null` so it reports the surface it inherits instead of resetting elevation.
9986
+ if (!value)
9987
+ return this.parentProvider ? null : this.defaultTheme;
9988
+ const theme = this.themes.find((t) => t.name === value) ?? null;
9989
+ if (isDevMode() && !theme) {
9990
+ console.error(`Surface theme ${value} does not exist. Please make sure to add it to provideSurfaceThemesWithTailwind4()`);
9991
+ }
9992
+ return theme;
9993
+ }, /* @ts-ignore */
9994
+ ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
9995
+ /**
9996
+ * The surface theme in effect on this element - the theme this provider resolves itself, or,
9997
+ * where it is left unset, the one it inherits from the closest ancestor provider. The outermost
9998
+ * provider resolves the app default, so the chain ends there.
9999
+ */
10000
+ this.activeTheme = computed(() => this.resolvedTheme() ?? this.parentProvider?.activeTheme() ?? null, /* @ts-ignore */
10001
+ ...(ngDevMode ? [{ debugName: "activeTheme" }] : /* istanbul ignore next */ []));
10002
+ this.elevation = computed(() => this.activeTheme()?.elevation ?? 0, /* @ts-ignore */
10003
+ ...(ngDevMode ? [{ debugName: "elevation" }] : /* istanbul ignore next */ []));
10004
+ this.surfaceType = computed(() => this.activeTheme()?.type ?? null, /* @ts-ignore */
10005
+ ...(ngDevMode ? [{ debugName: "surfaceType" }] : /* istanbul ignore next */ []));
10006
+ this.surfaceName = computed(() => {
10007
+ const value = this.effectiveSurface();
10008
+ if (!this.themes || !value)
10009
+ return;
10010
+ return createCssSurfaceName(value);
10011
+ }, /* @ts-ignore */
10012
+ ...(ngDevMode ? [{ debugName: "surfaceName" }] : /* istanbul ignore next */ []));
10013
+ this.colorThemeClass = computed(() => {
10014
+ const colorTheme = this.resolvedTheme()?.colorTheme;
10015
+ if (!colorTheme || this.colorProvider?.resolvedColor())
10016
+ return null;
10017
+ if (isDevMode() && !this.colorThemes?.some((theme) => theme.name === colorTheme)) {
10018
+ console.error(`Surface theme ${this.resolvedTheme()?.name} names the color theme ${colorTheme}, which does not exist. Please make sure to add it to provideColorThemesWithTailwind4()`);
9459
10019
  }
9460
- return `${prefix}-surface--inherited`;
10020
+ return `${this.colorPrefix || 'et'}-color--${createCssColorThemeName(colorTheme)}`;
10021
+ }, /* @ts-ignore */
10022
+ ...(ngDevMode ? [{ debugName: "colorThemeClass" }] : /* istanbul ignore next */ []));
10023
+ this.surfaceClass = computed(() => {
10024
+ const prefix = this.prefix || 'et';
10025
+ const surfaceClass = this.surfaceName()
10026
+ ? `${prefix}-surface--${this.surfaceName()}`
10027
+ : `${prefix}-surface--inherited`;
10028
+ const colorThemeClass = this.colorThemeClass();
10029
+ return colorThemeClass ? `${surfaceClass} ${colorThemeClass}` : surfaceClass;
9461
10030
  }, /* @ts-ignore */
9462
10031
  ...(ngDevMode ? [{ debugName: "surfaceClass" }] : /* istanbul ignore next */ []));
9463
10032
  }
@@ -9485,11 +10054,9 @@ class ProvideSurfaceDirective {
9485
10054
  this.clearForcedSurface();
9486
10055
  }
9487
10056
  }
9488
- /** @internal */
9489
10057
  forceSurface(surface) {
9490
10058
  this.forcedSurface.set(surface);
9491
10059
  }
9492
- /** @internal */
9493
10060
  clearForcedSurface() {
9494
10061
  if (this.forcedSurface() === FORCED_SURFACE_UNSET) {
9495
10062
  return;
@@ -9586,15 +10153,9 @@ class AutoSurfaceDirective {
9586
10153
  */
9587
10154
  this.surfaceProvider = input(null, /* @ts-ignore */
9588
10155
  ...(ngDevMode ? [{ debugName: "surfaceProvider" }] : /* istanbul ignore next */ []));
9589
- // An overlay *panel* that IS its overlay's own painted surface opts in (see
9590
- // matchOverlaySurface) so it adopts the overlay's registered elevation exactly,
9591
- // rather than layering one level above a parent surface.
9592
10156
  this.isOverlaySurface = signal(false, /* @ts-ignore */
9593
10157
  ...(ngDevMode ? [{ debugName: "isOverlaySurface" }] : /* istanbul ignore next */ []));
9594
- // Bumped by the afterEveryRender watcher in the constructor whenever this element's overlay
9595
- // containment changes. surfaceForElement() below does a non-reactive element.contains() read,
9596
- // so the computed must be re-triggered when this element is (re)grafted into an overlay pane -
9597
- // otherwise a value cached from before the graft (one elevation too low) sticks forever.
10158
+ // surfaceForElement() reads the DOM non-reactively; bumping this re-runs resolvedSurface after a re-graft.
9598
10159
  this.domSettleTick = signal(0, /* @ts-ignore */
9599
10160
  ...(ngDevMode ? [{ debugName: "domSettleTick" }] : /* istanbul ignore next */ []));
9600
10161
  this.resolvedSurface = computed(() => {
@@ -9602,40 +10163,25 @@ class AutoSurfaceDirective {
9602
10163
  if (!themes) {
9603
10164
  return null;
9604
10165
  }
9605
- // Establish a dependency on the DOM-settle tick so the non-reactive containment read below
9606
- // is re-evaluated after the element is grafted into its final overlay pane.
9607
10166
  this.domSettleTick();
9608
10167
  const explicitProvider = this.surfaceProvider();
9609
10168
  const contextTheme = explicitProvider ? explicitProvider.activeTheme() : this.parentSurface();
9610
10169
  const contextElevation = contextTheme?.elevation ?? null;
9611
10170
  const contextType = contextTheme?.type ?? null;
9612
- // An overlay's projected/portaled content keeps the injector of where it was
9613
- // *declared* (the trigger location), not the pane it renders into - so the
9614
- // injector-derived parent surface is one elevation too low. The surface-context
9615
- // tracker records each open overlay's surface *and* its pane element, so we consult
9616
- // only the overlay whose pane actually contains this element in the DOM (portaling
9617
- // moves the DOM into the pane even though the injector stays at the trigger). An
9618
- // auto-surface on the base page therefore stays put when an overlay opens elsewhere.
10171
+ // Portaled content keeps the trigger's injector, so only the pane that contains this element in the DOM counts.
9619
10172
  const overlaySurface = this.surfaceContextTracker.surfaceForElement(this.elementRef.nativeElement);
9620
10173
  const overlayElevation = overlaySurface?.elevation ?? null;
9621
10174
  const overlayType = overlaySurface?.type ?? null;
9622
- // A panel that IS its overlay's own surface (see matchOverlaySurface) must paint exactly the
9623
- // overlay's elevation: the overlay container already resolved it (one above the trigger) and
9624
- // registered it in the tracker. It must NOT re-derive from its declaration injector - that
9625
- // points back at the trigger location and disagrees with the pane whenever the trigger itself
9626
- // sits on a surface, which would double-elevate the panel relative to its own pane.
10175
+ // Must not re-derive from the declaration injector: it points at the trigger and would double-elevate the panel.
9627
10176
  if (this.isOverlaySurface()) {
9628
10177
  if (overlayElevation !== null) {
9629
10178
  return resolveSurfaceByElevation(themes, overlayType ?? contextType ?? 'dark', overlayElevation)?.name ?? null;
9630
10179
  }
9631
- // Rendered outside any tracked overlay: fall back to the injector context unchanged.
9632
10180
  if (contextElevation !== null) {
9633
10181
  return resolveSurfaceByElevation(themes, contextType ?? 'dark', contextElevation)?.name ?? null;
9634
10182
  }
9635
10183
  return null;
9636
10184
  }
9637
- // A regular auto-surface sits one elevation *above* the deeper of its injector context and the
9638
- // overlay pane it renders into (content painted onto the surface it lives on).
9639
10185
  let parentElevation;
9640
10186
  let parentType;
9641
10187
  if (overlayElevation !== null && (contextElevation === null || overlayElevation > contextElevation)) {
@@ -9652,14 +10198,8 @@ class AutoSurfaceDirective {
9652
10198
  return resolveSurfaceByElevation(themes, parentType, parentElevation + 1)?.name ?? null;
9653
10199
  }, /* @ts-ignore */
9654
10200
  ...(ngDevMode ? [{ debugName: "resolvedSurface" }] : /* istanbul ignore next */ []));
9655
- // Projected/portaled auto-surface content (e.g. a select option's avatar) can have its
9656
- // resolvedSurface computed run *before* it is grafted into the overlay pane it visually lives
9657
- // in - or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
9658
- // surfaceForElement()'s element.contains() check then returns the wrong (or no) overlay, so it
9659
- // resolves off the declaration injector (the trigger location) one elevation too low. That check
9660
- // is a plain DOM read, not a signal, so nothing re-runs the computed once the element reaches its
9661
- // final pane. Watch the tracker result across renders and re-trigger the computed whenever the
9662
- // element's overlay containment changes, settling (and unsubscribing) once it stops moving.
10201
+ // The containment check is a plain DOM read, so nothing re-runs resolvedSurface once the element
10202
+ // reaches its final pane (windowed lists mount it in an off-pane container first). Watch it across renders.
9663
10203
  let lastElevation = this.surfaceContextTracker.surfaceForElement(this.elementRef.nativeElement)?.elevation ?? null;
9664
10204
  let stableRenders = 0;
9665
10205
  const settleWatcher = afterEveryRender(() => {
@@ -9671,7 +10211,6 @@ class AutoSurfaceDirective {
9671
10211
  this.domSettleTick.update((v) => v + 1);
9672
10212
  }
9673
10213
  else if (el.isConnected && ++stableRenders >= 2) {
9674
- // containment has held for a couple of renders and the element is in the DOM - done moving
9675
10214
  settleWatcher.destroy();
9676
10215
  }
9677
10216
  });
@@ -9696,87 +10235,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
9696
10235
  const COLOR_PALETTE_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
9697
10236
  name: 'Color Palette',
9698
10237
  });
10238
+ const injectColorPaletteConfig = /* @__PURE__ */ toInjectFn(COLOR_PALETTE_DEF);
10239
+ const selectPalette = (config, surface) => {
10240
+ if (Array.isArray(config))
10241
+ return config;
10242
+ return (surface === null ? undefined : config[surface]) ?? config.default;
10243
+ };
9699
10244
  /**
9700
10245
  * The color themes a subtree may offer a user as pickable choices, in the order they should appear.
9701
10246
  * A curated slice of what `provideColorThemesWithTailwind4` registered: an app registers every theme
9702
10247
  * it renders with, including ones nobody should pick by hand (`error`, `warning`), and adds the
9703
10248
  * user-facing label a swatch needs.
9704
10249
  *
9705
- * Components that let a user choose a color read it optionally and fall back to accepting a raw
9706
- * theme name when it is absent - the scheduler's `colorToken` field is the reference consumer.
10250
+ * Pass one list, or one list per registered surface theme name plus a `default`. The charts draw with
10251
+ * the list of the surface they sit on; a color picker always offers the `default` list.
9707
10252
  *
9708
10253
  * @example
9709
10254
  * provideColorPalette([
9710
10255
  * { token: 'brand', label: 'Team green' },
9711
10256
  * { token: 'ocean', label: 'Training blue' },
9712
10257
  * ]);
10258
+ *
10259
+ * @example
10260
+ * provideColorPalette({
10261
+ * default: [{ token: 'ocean', label: 'Training' }],
10262
+ * 'dark-card': [{ token: 'ocean-bright', label: 'Training' }],
10263
+ * });
9713
10264
  */
9714
- const provideColorPalette = /* @__PURE__ */ toProvideFn(COLOR_PALETTE_DEF);
9715
- /** The palette from {@link provideColorPalette}. Pass `{ optional: true }` - most apps provide none. */
9716
- const injectColorPalette = /* @__PURE__ */ toInjectFn(COLOR_PALETTE_DEF);
9717
- const COLOR_PALETTE = /* @__PURE__ */ toToken(COLOR_PALETTE_DEF);
9718
-
10265
+ const provideColorPalette = (palette) => COLOR_PALETTE_DEF.provide(palette);
10266
+ function injectColorPalette(options) {
10267
+ const config = options ? injectColorPaletteConfig(options) : injectColorPaletteConfig();
10268
+ return config ? selectPalette(config, null) : null;
10269
+ }
9719
10270
  /**
9720
- * Reserved color name. Resolves the color tokens from the ambient surface's neutral swatch
9721
- * (`SurfaceTheme['interactionColor']`) instead of a registered accent, so a secondary action can
9722
- * read as chrome without the app registering a neutral color theme. Apps must not register a color
9723
- * theme under this name.
10271
+ * The palette from {@link provideColorPalette} for the surface the current subtree renders on: that
10272
+ * surface theme's list, else the `default` list. `null` when no palette is provided.
9724
10273
  */
9725
- const SURFACE_COLOR = 'surface';
9726
- const createCssColorThemeName = (name) => name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
9727
- const COLOR_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
9728
- name: 'Color Themes',
9729
- });
9730
- const ɵProvideColorThemes = /* @__PURE__ */ toProvideFn(COLOR_THEMES_DEF);
9731
- const injectColorThemes = /* @__PURE__ */ toInjectFn(COLOR_THEMES_DEF);
9732
- const COLOR_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
9733
- name: 'Themes Prefix',
9734
- });
9735
- const ɵProvideColorThemesPrefix = /* @__PURE__ */ toProvideFn(COLOR_THEMES_PREFIX_DEF);
9736
- const injectColorThemesPrefix = /* @__PURE__ */ toInjectFn(COLOR_THEMES_PREFIX_DEF);
9737
- const provideColorThemesWithTailwind4 = (themes, prefix = 'et') => {
9738
- if (isDevMode() && themes.some((theme) => theme.name === SURFACE_COLOR)) {
9739
- console.error(`[provideColorThemesWithTailwind4] "${SURFACE_COLOR}" is a reserved color name that resolves to the ambient surface's neutral swatch. Rename your theme.`);
9740
- }
9741
- return [ɵProvideColorThemes(themes), ɵProvideColorThemesPrefix(prefix)];
9742
- };
9743
- const injectColorThemeByType = (type) => {
9744
- const themes = injectColorThemes({ optional: true });
9745
- if (!themes) {
9746
- throw new Error(`[injectColorThemeByType] No color themes provided. Call provideColorThemesWithTailwind4() in your app config.`);
9747
- }
9748
- const theme = themes.find((t) => t.type === type);
9749
- if (!theme) {
9750
- throw new Error(`[injectColorThemeByType] No color theme with type "${type}" found. Add a theme with type: "${type}" to provideColorThemesWithTailwind4().`);
9751
- }
9752
- if (isDevMode()) {
9753
- const duplicates = themes.filter((t) => t.type === type);
9754
- if (duplicates.length > 1) {
9755
- console.error(`[injectColorThemeByType] Multiple themes with type "${type}" found: ${duplicates.map((t) => t.name).join(', ')}. Only the first one will be used.`);
9756
- }
9757
- }
9758
- return theme;
9759
- };
9760
- const injectErrorTheme = () => injectColorThemeByType('error');
9761
- const injectWarningTheme = () => injectColorThemeByType('warning');
9762
- const injectSuccessTheme = () => injectColorThemeByType('success');
9763
- const injectDefaultColorTheme = () => {
9764
- const themes = injectColorThemes({ optional: true });
9765
- if (!themes) {
9766
- throw new Error(`[injectDefaultColorTheme] No color themes provided. Call provideColorThemesWithTailwind4() in your app config.`);
9767
- }
9768
- const theme = themes.find((t) => t.isDefault);
9769
- if (!theme) {
9770
- throw new Error(`[injectDefaultColorTheme] No color theme with isDefault: true found. Mark one theme as the default in provideColorThemesWithTailwind4().`);
9771
- }
9772
- if (isDevMode()) {
9773
- const duplicates = themes.filter((t) => t.isDefault);
9774
- if (duplicates.length > 1) {
9775
- console.error(`[injectDefaultColorTheme] Multiple themes with isDefault: true found: ${duplicates.map((t) => t.name).join(', ')}. Only the first one will be used.`);
9776
- }
9777
- }
9778
- return theme;
10274
+ const injectSurfaceColorPalette = () => {
10275
+ const config = injectColorPaletteConfig({ optional: true });
10276
+ const surface = injectParentSurface();
10277
+ return computed(() => (config ? selectPalette(config, surface()?.name ?? null) : null));
9779
10278
  };
10279
+ const COLOR_PALETTE = /* @__PURE__ */ toToken(COLOR_PALETTE_DEF);
9780
10280
 
9781
10281
  /** Reads the nonce where Angular's default `CSP_NONCE` does, since these run outside any injector. */
9782
10282
  const createNoncedStyle = () => {
@@ -9979,131 +10479,6 @@ const provideColorThemes = (themes) => {
9979
10479
  return [ɵProvideColorThemes(themes), ɵProvideColorThemesPrefix('et')];
9980
10480
  };
9981
10481
 
9982
- const COLOR_PROVIDER = new InjectionToken('ColorProvider');
9983
- /**
9984
- * The color provider attached to a bootstrapped root component (e.g. `ProvideColorDirective` in
9985
- * the app component's `hostDirectives`), if any. Detached DOM such as overlay panes can't reach
9986
- * it through element DI, so overlay containers fall back to this when resolving the provider to
9987
- * `syncWithProvider()` - resolve at sync time, not at injection time, since root components only
9988
- * register with `ApplicationRef` once bootstrap completes.
9989
- *
9990
- * @internal
9991
- */
9992
- const resolveAppRootColorProvider = (appRef) => {
9993
- for (const componentRef of appRef.components) {
9994
- const provider = componentRef.injector.get(COLOR_PROVIDER, null);
9995
- if (provider) {
9996
- return provider;
9997
- }
9998
- }
9999
- return null;
10000
- };
10001
- const FORCED_COLOR_UNSET = /* @__PURE__ */ Symbol('FORCED_COLOR_UNSET');
10002
- class ProvideColorDirective {
10003
- constructor() {
10004
- this.themes = injectColorThemes({ optional: true });
10005
- this.prefix = injectColorThemesPrefix({ optional: true });
10006
- this.injector = inject(Injector);
10007
- this.parentProvider = inject(COLOR_PROVIDER, { optional: true, skipSelf: true });
10008
- this.currentProviderSync = null;
10009
- this.forcedColor = signal(FORCED_COLOR_UNSET, /* @ts-ignore */
10010
- ...(ngDevMode ? [{ debugName: "forcedColor" }] : /* istanbul ignore next */ []));
10011
- this.color = input(undefined, { ...(ngDevMode ? { debugName: "color" } : /* istanbul ignore next */ {}), alias: 'etProvideColor' });
10012
- this.effectiveColor = computed(() => {
10013
- const forcedColor = this.forcedColor();
10014
- if (forcedColor !== FORCED_COLOR_UNSET) {
10015
- return forcedColor;
10016
- }
10017
- return this.color();
10018
- }, /* @ts-ignore */
10019
- ...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
10020
- /**
10021
- * The color that actually applies at this provider's location: its own effective color, or -
10022
- * when this provider is passive (no input, nothing forced) - the nearest ancestor provider's.
10023
- * Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
10024
- * consumers that re-apply context across detached DOM (overlay panes) must use this instead
10025
- * of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
10026
- */
10027
- this.resolvedColor = computed(() => {
10028
- const own = this.effectiveColor();
10029
- const ownName = typeof own === 'object' && own !== null ? own.name : own;
10030
- if (ownName) {
10031
- return own;
10032
- }
10033
- return this.parentProvider?.resolvedColor();
10034
- }, /* @ts-ignore */
10035
- ...(ngDevMode ? [{ debugName: "resolvedColor" }] : /* istanbul ignore next */ []));
10036
- this.colorName = computed(() => {
10037
- const raw = this.effectiveColor();
10038
- const value = typeof raw === 'object' && raw !== null ? raw.name : raw;
10039
- if (value === SURFACE_COLOR)
10040
- return SURFACE_COLOR;
10041
- if (!this.themes || !value)
10042
- return;
10043
- if (isDevMode() && !this.themes.some((theme) => theme.name === value) && value !== null) {
10044
- console.error(`Theme ${value} does not exist. Please make sure to add it to provideColorThemesWithTailwind4()`);
10045
- }
10046
- return createCssColorThemeName(value);
10047
- }, /* @ts-ignore */
10048
- ...(ngDevMode ? [{ debugName: "colorName" }] : /* istanbul ignore next */ []));
10049
- this.themeClass = computed(() => {
10050
- const prefix = this.prefix || 'et';
10051
- if (this.colorName()) {
10052
- return `${prefix}-color--${this.colorName()}`;
10053
- }
10054
- return `${prefix}-color--inherited`;
10055
- }, /* @ts-ignore */
10056
- ...(ngDevMode ? [{ debugName: "themeClass" }] : /* istanbul ignore next */ []));
10057
- }
10058
- syncWithProvider(provider) {
10059
- this.currentProviderSync?.destroy();
10060
- runInInjectionContext(this.injector, () => {
10061
- this.currentProviderSync = effect(() => {
10062
- const provideColor = provider.resolvedColor();
10063
- untracked(() => {
10064
- if (provideColor === undefined) {
10065
- this.clearForcedColor();
10066
- return;
10067
- }
10068
- this.forceColor(provideColor);
10069
- });
10070
- }, /* @ts-ignore */
10071
- ...(ngDevMode ? [{ debugName: "currentProviderSync" }] : /* istanbul ignore next */ []));
10072
- });
10073
- }
10074
- stopSyncWithProvider() {
10075
- const hadProviderSync = !!this.currentProviderSync;
10076
- this.currentProviderSync?.destroy();
10077
- this.currentProviderSync = null;
10078
- if (hadProviderSync) {
10079
- this.clearForcedColor();
10080
- }
10081
- }
10082
- /** @internal */
10083
- forceColor(color) {
10084
- this.forcedColor.set(color);
10085
- }
10086
- /** @internal */
10087
- clearForcedColor() {
10088
- if (this.forcedColor() === FORCED_COLOR_UNSET) {
10089
- return;
10090
- }
10091
- this.forcedColor.set(FORCED_COLOR_UNSET);
10092
- }
10093
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProvideColorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10094
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ProvideColorDirective, isStandalone: true, selector: "[etProvideColor]", inputs: { color: { classPropertyName: "color", publicName: "etProvideColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "themeClass()" } }, providers: [{ provide: COLOR_PROVIDER, useExisting: ProvideColorDirective }], ngImport: i0 }); }
10095
- }
10096
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProvideColorDirective, decorators: [{
10097
- type: Directive,
10098
- args: [{
10099
- selector: '[etProvideColor]',
10100
- providers: [{ provide: COLOR_PROVIDER, useExisting: ProvideColorDirective }],
10101
- host: {
10102
- '[class]': 'themeClass()',
10103
- },
10104
- }]
10105
- }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "etProvideColor", required: false }] }] } });
10106
-
10107
10482
  class SurfaceInteractiveStylesComponent {
10108
10483
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SurfaceInteractiveStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10109
10484
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: SurfaceInteractiveStylesComponent, isStandalone: true, selector: "et-surface-interactive-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{:where(.et-surface-interactive){--et-surface-interaction-rgb: var(--et-surface-interaction);--et-surface-interaction-solid: rgb(var(--et-surface-interaction-rgb))}@media(hover:hover){:where(.et-surface-interactive:hover){--et-surface-interaction-rgb: var(--et-surface-interaction-hover, var(--et-surface-interaction));--et-surface-interaction-solid: rgb(var(--et-surface-interaction-rgb))}}:where(.et-surface-interactive:focus-visible){--et-surface-interaction-rgb: var( --et-surface-interaction-focus, var(--et-surface-interaction-hover, var(--et-surface-interaction)) );--et-surface-interaction-solid: rgb(var(--et-surface-interaction-rgb))}:where(.et-surface-interactive:active){--et-surface-interaction-rgb: var( --et-surface-interaction-active, var(--et-surface-interaction-focus, var(--et-surface-interaction-hover, var(--et-surface-interaction))) );--et-surface-interaction-solid: rgb(var(--et-surface-interaction-rgb))}:where(.et-surface-interactive[disabled]),:where(.et-surface-interactive[aria-disabled=true]){--et-surface-interaction-rgb: var( --et-surface-interaction-disabled, var( --et-surface-interaction-active, var(--et-surface-interaction-focus, var(--et-surface-interaction-hover, var(--et-surface-interaction))) ) );--et-surface-interaction-solid: rgb(var(--et-surface-interaction-rgb))}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
@@ -10144,11 +10519,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
10144
10519
 
10145
10520
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
10146
10521
  /** The version of `@ethlete/core` this build was cut from. */
10147
- const CORE_VERSION = '5.0.0-next.60';
10522
+ const CORE_VERSION = '5.0.0-next.62';
10148
10523
 
10149
10524
  /**
10150
10525
  * Generated bundle index. Do not edit.
10151
10526
  */
10152
10527
 
10153
- export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, 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, 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_TITLE_MARKER, ValidateAtLeastOneRequired, 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, createOverlayRuntimeRef, 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, hasCookie, holdScrollRestoration, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectAnimatedBlockSize, injectAppUpdates, injectAppUpdatesConfig, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorPalette, 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, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterNavigationState, injectRouterState, injectScrollbarDimensions, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, 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, isStaleBuildError, markdownToHtml, matchesReducedMotion, maybeSignalValue, memoizeSignal, mountEasingTokens, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, normalizeUnsavedChangesSource, numberBreakpointTransform, onOverlayViewportInsetsChange, overlayViewportInsets, overlayViewportInsetsFor, ownsActiveElement, previousSignalValue, provideAppUpdates, provideAppUpdatesConfig, provideAppUpdatesInstance, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorPalette, provideColorThemes, provideColorThemesWithTailwind4, provideFaviconStore, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideNotifications, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideUnsavedChangesCoordinator, provideViewportConfig, randomId, readBuildFingerprint, readFromClipboard, registerAnchoredPositionMiddlewareExtras, registerAnchoredPositionSetup, reserveOverlayViewportSpace, resetPositioningStyles, resolveAppRootColorProvider, resolveOverlayLayer, resolveSurfaceByElevation, round, routerDisableScrollTop, routerRestoreScroll, scrollToElement, setBackdropStyles, setBaseElementStyles, 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 };
10528
+ 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, hasCookie, holdScrollRestoration, htmlToMarkdown, inferMimeType, initials, injectAngularRootElement, injectAnimatedBlockSize, injectAppUpdates, injectAppUpdatesConfig, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorPalette, 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, 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, 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, reserveOverlayViewportSpace, resolveAppRootColorProvider, 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 };
10154
10529
  //# sourceMappingURL=ethlete-core.mjs.map