@ethlete/core 5.0.0-next.36 → 5.0.0-next.38

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,16 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, ElementRef, Directive, assertInInjectionContext, DestroyRef, TemplateRef, QueryList, RendererFactory2, RendererStyleFlags2, isSignal, DOCUMENT, signal, linkedSignal, isDevMode, computed, afterNextRender, effect, NgZone, untracked, EnvironmentInjector, runInInjectionContext, Injector, booleanAttribute, numberAttribute, PLATFORM_ID, ApplicationRef, createComponent, model, ViewContainerRef, input, output, Pipe, ViewEncapsulation, Component, afterEveryRender } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
4
- import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, defer, from, catchError, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat, exhaustMap, share, isObservable, firstValueFrom, defaultIfEmpty } from 'rxjs';
4
+ import { Subject, BehaviorSubject, debounceTime, combineLatest, map, merge, fromEvent, filter, tap, switchMap, startWith, of, defer, from, catchError, timer, takeUntil, pairwise, distinctUntilChanged, take, Observable, concatMap, EMPTY, concat, exhaustMap, share, shareReplay, isObservable, firstValueFrom, defaultIfEmpty } from 'rxjs';
5
5
  import { FormGroup, FormArray, FormControl } from '@angular/forms';
6
6
  import { SIGNAL, signalSetFn } from '@angular/core/primitives/signals';
7
- import { coerceElement } from '@angular/cdk/coercion';
8
7
  import { DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
9
- import { BreakpointObserver } from '@angular/cdk/layout';
10
8
  import { Router, NavigationEnd, NavigationSkipped, NavigationStart } from '@angular/router';
11
- import { Overlay } from '@angular/cdk/overlay';
12
- import { ComponentPortal } from '@angular/cdk/portal';
13
- import { autoUpdate, computePosition, offset, flip, size, shift, limitShift, arrow, hide } from '@floating-ui/dom';
9
+ import { autoUpdate, offset, flip, shift, limitShift, computePosition, hide, arrow, size } from '@floating-ui/dom';
14
10
  import { Title, Meta, DomSanitizer } from '@angular/platform-browser';
15
11
 
16
12
  const ANIMATION_DEBUG_STORAGE_KEY = 'et-overlay-debug';
@@ -86,7 +82,7 @@ class AnimatableDirective {
86
82
  }
87
83
  }
88
84
  else {
89
- animationDebugLog(`animatable ${el.tagName.toLowerCase()}`, `${eventType} ignored — count already 0 (start ${didEmitStart ? '' : 'not '}emitted)`);
85
+ animationDebugLog(`animatable ${el.tagName.toLowerCase()}`, `${eventType} ignored - count already 0 (start ${didEmitStart ? '' : 'not '}emitted)`);
90
86
  console.warn(`${el.tagName} received animation end/cancel event but activeAnimationCount is already 0. Start was ${didEmitStart ? '' : 'not '}emitted.`);
91
87
  }
92
88
  break;
@@ -126,7 +122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
126
122
  }]
127
123
  }], ctorParameters: () => [] });
128
124
 
129
- const componentIds = new Map();
125
+ const componentIds = /* @__PURE__ */ new Map();
130
126
  const createComponentId = (prefix) => {
131
127
  const id = componentIds.get(prefix) ?? 0;
132
128
  componentIds.set(prefix, id + 1);
@@ -135,7 +131,7 @@ const createComponentId = (prefix) => {
135
131
  let randomIdFallbackCounter = 0;
136
132
  /**
137
133
  * A unique id string. Uses `crypto.randomUUID()` when available, but falls back gracefully when it is
138
- * not — crucially in **insecure contexts** (a plain-HTTP page served from a LAN IP, not `localhost`),
134
+ * not - crucially in **insecure contexts** (a plain-HTTP page served from a LAN IP, not `localhost`),
139
135
  * where `crypto.randomUUID` is `undefined`. Calling it there throws, so anything using it during e.g.
140
136
  * an overlay/menu open would break on such hosts. Prefer this over `crypto.randomUUID()` directly.
141
137
  */
@@ -144,7 +140,7 @@ const randomId = () => {
144
140
  if (typeof cryptoObj?.randomUUID === 'function') {
145
141
  return cryptoObj.randomUUID();
146
142
  }
147
- // `getRandomValues` is available even in insecure contexts — build an RFC-4122-ish v4 id from it.
143
+ // `getRandomValues` is available even in insecure contexts - build an RFC-4122-ish v4 id from it.
148
144
  if (typeof cryptoObj?.getRandomValues === 'function') {
149
145
  const bytes = Array.from(cryptoObj.getRandomValues(new Uint8Array(16)));
150
146
  bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
@@ -152,7 +148,7 @@ const randomId = () => {
152
148
  const hex = bytes.map((byte) => (byte ?? 0).toString(16).padStart(2, '0'));
153
149
  return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;
154
150
  }
155
- // last resort: no Web Crypto at all — a session-unique (not cryptographic) id is enough for DOM ids
151
+ // last resort: no Web Crypto at all - a session-unique (not cryptographic) id is enough for DOM ids
156
152
  return `et-id-${(randomIdFallbackCounter++).toString(36)}-${Date.now().toString(36)}`;
157
153
  };
158
154
 
@@ -179,42 +175,67 @@ const createProviders = (token, factory, extraToken) => [
179
175
  { provide: token, useFactory: factory },
180
176
  ...(extraToken ? [{ provide: extraToken, useExisting: token }] : []),
181
177
  ];
182
- const createProvider = (factory, options) => {
178
+
179
+ const defineProvider = (factory, options) => {
183
180
  const token = new InjectionToken(options?.name ?? createComponentId('provider'));
184
- const provide = () => createProviders(token, factory, options?.extraInjectionToken);
185
- const injectFn = createInjectFunction(token);
186
- return [provide, injectFn, token];
181
+ return {
182
+ provide: () => createProviders(token, factory, options?.extraInjectionToken),
183
+ inject: createInjectFunction(token),
184
+ token,
185
+ };
187
186
  };
188
- const createRootProvider = (factory, options) => {
187
+ /**
188
+ * Like {@link defineProvider}, but the factory also runs in the root injector, so `injectX()` works
189
+ * without anyone calling `provideX()`. `provideX()` still exists, to give a subtree its own instance.
190
+ */
191
+ const defineRootProvider = (factory, options) => {
189
192
  const token = new InjectionToken(options?.name ?? createComponentId('provider'), {
190
193
  providedIn: 'root',
191
194
  factory,
192
195
  });
193
- const provide = () => createProviders(token, factory, options?.extraInjectionToken);
194
- const injectFn = createInjectFunction(token);
195
- return [provide, injectFn, token];
196
+ return {
197
+ provide: () => createProviders(token, factory, options?.extraInjectionToken),
198
+ inject: createInjectFunction(token),
199
+ token,
200
+ };
196
201
  };
197
- const createStaticProvider = (defaultValue, options) => {
202
+ /**
203
+ * Like {@link defineProvider} for a plain value rather than a factory: `provideX(override)` merges the
204
+ * partial override over `defaultValue`, which is how every config object in the library is shaped.
205
+ */
206
+ const defineStaticProvider = (defaultValue, options) => {
198
207
  const token = new InjectionToken(options?.name ?? createComponentId('static-provider'));
199
- const provide = (valueOverride) => [
200
- { provide: token, useValue: maybeMergeValues(defaultValue, valueOverride) },
201
- ...(options?.extraInjectionToken ? [{ provide: options.extraInjectionToken, useExisting: token }] : []),
202
- ];
203
- const injectFn = createInjectFunction(token);
204
- return [provide, injectFn, token];
208
+ return {
209
+ provide: (valueOverride) => createValueProviders(token, defaultValue, valueOverride, options),
210
+ inject: createInjectFunction(token),
211
+ token,
212
+ };
205
213
  };
206
- const createStaticRootProvider = (defaultValue, options) => {
214
+ /**
215
+ * {@link defineStaticProvider} whose default is also available in the root injector, so `injectX()`
216
+ * resolves to `defaultValue` in an app that never provides it.
217
+ */
218
+ const defineStaticRootProvider = (defaultValue, options) => {
207
219
  const token = new InjectionToken(options?.name ?? createComponentId('static-provider'), {
208
220
  providedIn: 'root',
209
221
  factory: () => defaultValue,
210
222
  });
211
- const provide = (valueOverride) => [
212
- { provide: token, useValue: maybeMergeValues(defaultValue, valueOverride) },
213
- ...(options?.extraInjectionToken ? [{ provide: options.extraInjectionToken, useExisting: token }] : []),
214
- ];
215
- const injectFn = createInjectFunction(token);
216
- return [provide, injectFn, token];
223
+ return {
224
+ provide: (valueOverride) => createValueProviders(token, defaultValue, valueOverride, options),
225
+ inject: createInjectFunction(token),
226
+ token,
227
+ };
217
228
  };
229
+
230
+ const toProvideFn = (definition) => definition.provide;
231
+ /** The `injectX` half of a provider definition. */
232
+ const toInjectFn = (definition) => definition.inject;
233
+ /** The token behind a provider definition. */
234
+ const toToken = (definition) => definition.token;
235
+ const createValueProviders = (token, defaultValue, valueOverride, options) => [
236
+ { provide: token, useValue: maybeMergeValues(defaultValue, valueOverride) },
237
+ ...(options?.extraInjectionToken ? [{ provide: options.extraInjectionToken, useExisting: token }] : []),
238
+ ];
218
239
  const maybeMergeValues = (defaultValue, valueOverride) => {
219
240
  if (valueOverride && defaultValue && typeof defaultValue === 'object') {
220
241
  return { ...defaultValue, ...valueOverride };
@@ -222,7 +243,6 @@ const maybeMergeValues = (defaultValue, valueOverride) => {
222
243
  return valueOverride ?? defaultValue;
223
244
  };
224
245
 
225
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
246
  const cloneFormGroup = (formGroup) => {
227
247
  const cloneLevel = (group, cloneGroup) => {
228
248
  Object.keys(group.controls).forEach((key) => {
@@ -233,7 +253,6 @@ const cloneFormGroup = (formGroup) => {
233
253
  cloneLevel(control, cloneControl);
234
254
  }
235
255
  else if (control instanceof FormArray) {
236
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
256
  const cloneArrayControl = new FormArray([], control.validator, control.asyncValidator);
238
257
  control.controls.forEach((control) => {
239
258
  if (control instanceof FormGroup) {
@@ -296,7 +315,6 @@ const applyHostListeners = (listeners, options) => {
296
315
  for (const eventName in listeners) {
297
316
  const handler = listeners[eventName];
298
317
  if (handler) {
299
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
318
  applyHostListener(eventName, handler, options);
301
319
  }
302
320
  }
@@ -389,7 +407,7 @@ const copyToClipboard = (text) => defer(() => {
389
407
  }
390
408
  if (navigator.clipboard) {
391
409
  return from(navigator.clipboard.writeText(text)).pipe(map(() => true),
392
- // The async Clipboard API can be blocked (missing permission, insecure context) — try the legacy path.
410
+ // The async Clipboard API can be blocked (missing permission, insecure context) - try the legacy path.
393
411
  catchError(() => of(copyToClipboardViaExecCommand(text))));
394
412
  }
395
413
  return of(copyToClipboardViaExecCommand(text));
@@ -426,7 +444,6 @@ const copyToClipboardViaExecCommand = (text) => {
426
444
  return didCopy;
427
445
  };
428
446
 
429
- /* eslint-disable @typescript-eslint/no-explicit-any */
430
447
  /* eslint-disable no-var */
431
448
  /**
432
449
  * Stolen from klona to avoid adding a dependency
@@ -455,7 +472,11 @@ const clone = (original) => {
455
472
  if (typeof original !== 'object')
456
473
  return original;
457
474
  var _og = original;
458
- var i = 0, k, list, tmp, str = Object.prototype.toString.call(_og);
475
+ var i = 0;
476
+ var k;
477
+ var list;
478
+ var tmp;
479
+ var str = Object.prototype.toString.call(_og);
459
480
  if (str === '[object Object]') {
460
481
  tmp = Object.create(_og.__proto__ || null);
461
482
  }
@@ -487,8 +508,6 @@ const clone = (original) => {
487
508
  tmp = _og.slice(0);
488
509
  }
489
510
  else if (str.slice(-6) === 'Array]') {
490
- // ArrayBuffer.isView(x)
491
- // ~> `new` bcuz `Buffer.slice` => ref
492
511
  tmp = new _og.constructor(_og);
493
512
  }
494
513
  if (tmp) {
@@ -537,7 +556,9 @@ function find(iter, tar, key) {
537
556
  }
538
557
  }
539
558
  const equal = (foo, bar) => {
540
- var ctor, len, tmp;
559
+ var ctor;
560
+ var len;
561
+ var tmp;
541
562
  if (foo === bar)
542
563
  return true;
543
564
  if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
@@ -707,7 +728,7 @@ class KeyPressManager {
707
728
  }
708
729
  }
709
730
 
710
- const [provideRenderer, injectRenderer] = createRootProvider(() => {
731
+ const RENDERER_DEF = /* @__PURE__ */ defineRootProvider(() => {
711
732
  const rendererFactory = inject(RendererFactory2);
712
733
  const renderer = rendererFactory.createRenderer(null, null);
713
734
  const addClass = (element, ...classes) => {
@@ -890,7 +911,10 @@ const [provideRenderer, injectRenderer] = createRootProvider(() => {
890
911
  renderer,
891
912
  };
892
913
  }, { name: 'Angular Renderer' });
914
+ const provideRenderer = /* @__PURE__ */ toProvideFn(RENDERER_DEF);
915
+ const injectRenderer = /* @__PURE__ */ toInjectFn(RENDERER_DEF);
893
916
 
917
+ const coerceElement = (el) => (el instanceof ElementRef ? el.nativeElement : el);
894
918
  const isElementSignal = (el) => {
895
919
  if (!isSignal(el))
896
920
  return false;
@@ -1229,17 +1253,17 @@ const memoizeSignal = (factory) => {
1229
1253
  };
1230
1254
 
1231
1255
  /** Inject a signal containing a boolean value indicating if the viewport is xs */
1232
- const injectIsXs = memoizeSignal(() => injectObserveBreakpoint({ max: 'xs' }));
1256
+ const injectIsXs = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ max: 'xs' }));
1233
1257
  /** Inject a signal containing a boolean value indicating if the viewport is sm */
1234
- const injectIsSm = memoizeSignal(() => injectObserveBreakpoint({ min: 'sm', max: 'sm' }));
1258
+ const injectIsSm = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ min: 'sm', max: 'sm' }));
1235
1259
  /** Inject a signal containing a boolean value indicating if the viewport is md */
1236
- const injectIsMd = memoizeSignal(() => injectObserveBreakpoint({ min: 'md', max: 'md' }));
1260
+ const injectIsMd = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ min: 'md', max: 'md' }));
1237
1261
  /** Inject a signal containing a boolean value indicating if the viewport is lg */
1238
- const injectIsLg = memoizeSignal(() => injectObserveBreakpoint({ min: 'lg', max: 'lg' }));
1262
+ const injectIsLg = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ min: 'lg', max: 'lg' }));
1239
1263
  /** Inject a signal containing a boolean value indicating if the viewport is xl */
1240
- const injectIsXl = memoizeSignal(() => injectObserveBreakpoint({ min: 'xl', max: 'xl' }));
1264
+ const injectIsXl = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ min: 'xl', max: 'xl' }));
1241
1265
  /** Inject a signal containing a boolean value indicating if the viewport is 2xl */
1242
- const injectIs2Xl = memoizeSignal(() => injectObserveBreakpoint({ min: '2xl' }));
1266
+ const injectIs2Xl = /* @__PURE__ */ memoizeSignal(() => injectObserveBreakpoint({ min: '2xl' }));
1243
1267
  /**
1244
1268
  * Inject a boolean value indicating if the viewport is matching the provided options.
1245
1269
  * This value is not reactive. If you want to react to changes, use the {@link injectObserveBreakpoint} function instead.
@@ -1259,7 +1283,7 @@ const injectObserveBreakpoint = (options) => injectBreakpointObserver().observeB
1259
1283
  */
1260
1284
  const injectObserveMediaQuery = (mediaQuery) => injectBreakpointObserver().observeMediaQuery(mediaQuery);
1261
1285
  /** Inject a signal containing the current breakpoint. */
1262
- const injectCurrentBreakpoint = memoizeSignal(() => {
1286
+ const injectCurrentBreakpoint = /* @__PURE__ */ memoizeSignal(() => {
1263
1287
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1264
1288
  const first = BREAKPOINT_ORDER[0];
1265
1289
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -1276,11 +1300,11 @@ const injectCurrentBreakpoint = memoizeSignal(() => {
1276
1300
  });
1277
1301
  });
1278
1302
  /** Inject a signal that indicates if the user is using a portrait display */
1279
- const injectIsPortrait = memoizeSignal(() => injectObserveMediaQuery('(orientation: portrait)'));
1303
+ const injectIsPortrait = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(orientation: portrait)'));
1280
1304
  /** Inject a signal that indicates if the user is using a landscape display */
1281
- const injectIsLandscape = memoizeSignal(() => injectObserveMediaQuery('(orientation: landscape)'));
1305
+ const injectIsLandscape = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(orientation: landscape)'));
1282
1306
  /** Inject a signal containing the current display orientation */
1283
- const injectDisplayOrientation = memoizeSignal(() => {
1307
+ const injectDisplayOrientation = /* @__PURE__ */ memoizeSignal(() => {
1284
1308
  const isPortrait = injectIsPortrait();
1285
1309
  return computed(() => {
1286
1310
  if (isPortrait())
@@ -1289,11 +1313,11 @@ const injectDisplayOrientation = memoizeSignal(() => {
1289
1313
  });
1290
1314
  });
1291
1315
  /** Inject a signal that indicates if the device has a touch input */
1292
- const injectHasTouchInput = memoizeSignal(() => injectObserveMediaQuery('(pointer: coarse)'));
1316
+ const injectHasTouchInput = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(pointer: coarse)'));
1293
1317
  /** Inject a signal that indicates if the device has a fine input (mouse or stylus) */
1294
- const injectHasPrecisionInput = memoizeSignal(() => injectObserveMediaQuery('(pointer: fine)'));
1318
+ const injectHasPrecisionInput = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(pointer: fine)'));
1295
1319
  /** Inject a signal containing the current device input type */
1296
- const injectDeviceInputType = memoizeSignal(() => {
1320
+ const injectDeviceInputType = /* @__PURE__ */ memoizeSignal(() => {
1297
1321
  const isTouch = injectHasTouchInput();
1298
1322
  return computed(() => {
1299
1323
  if (isTouch())
@@ -1302,13 +1326,13 @@ const injectDeviceInputType = memoizeSignal(() => {
1302
1326
  });
1303
1327
  });
1304
1328
  /** Inject a signal that indicates if the user prefers reduced motion */
1305
- const injectPrefersReducedMotion = memoizeSignal(() => injectObserveMediaQuery('(prefers-reduced-motion: reduce)'));
1329
+ const injectPrefersReducedMotion = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(prefers-reduced-motion: reduce)'));
1306
1330
  /** Inject a signal containing a boolean value indicating if the user can hover (eg. using a mouse) */
1307
- const injectCanHover = memoizeSignal(() => injectObserveMediaQuery('(hover: hover)'));
1331
+ const injectCanHover = /* @__PURE__ */ memoizeSignal(() => injectObserveMediaQuery('(hover: hover)'));
1308
1332
  /** Inject a signal containing the viewport dimensions */
1309
- const injectViewportDimensions = memoizeSignal(() => signalElementDimensions(createDocumentElementSignal()));
1333
+ const injectViewportDimensions = /* @__PURE__ */ memoizeSignal(() => signalElementDimensions(createDocumentElementSignal()));
1310
1334
  /** Inject a signal containing the scrollbar dimensions. Dimensions will be 0 if scrollbars overlap the page contents (like on mobile). */
1311
- const injectScrollbarDimensions = memoizeSignal(() => {
1335
+ const injectScrollbarDimensions = /* @__PURE__ */ memoizeSignal(() => {
1312
1336
  const document = inject(DOCUMENT);
1313
1337
  const renderer = injectRenderer();
1314
1338
  const scrollbarRuler = renderer.createElement('div');
@@ -1342,7 +1366,7 @@ const injectScrollbarDimensions = memoizeSignal(() => {
1342
1366
  * A change that interrupts an in-flight animation continues from the current animated size, and
1343
1367
  * zero/pre-layout measurements are ignored. Respects `prefers-reduced-motion`.
1344
1368
  *
1345
- * The animation starts synchronously inside the `ResizeObserver` callback — it runs after layout
1369
+ * The animation starts synchronously inside the `ResizeObserver` callback - it runs after layout
1346
1370
  * but **before paint**, so the host never paints a frame at its new natural size. Routing the
1347
1371
  * resize through a signal + effect instead would apply the animation one change-detection cycle
1348
1372
  * (= one painted frame) late: a growing panel would flash at its final size, snap back, and only
@@ -1409,7 +1433,7 @@ const injectAnimatedBlockSize = (config) => {
1409
1433
  const cleanup = () => {
1410
1434
  // Cancelling an animation to start the next one rejects its `finished` promise, firing this
1411
1435
  // cleanup after a newer animation has already taken over. That newer animation owns the
1412
- // resizing class, so only strip it when no other animation is mid-flight — otherwise an
1436
+ // resizing class, so only strip it when no other animation is mid-flight - otherwise an
1413
1437
  // interrupted resize (rapid content changes, e.g. async search typing) would un-clip the
1414
1438
  // scroller while it is still animating and flash a scrollbar.
1415
1439
  if (animation !== null && animation !== nextAnimation) {
@@ -1425,7 +1449,7 @@ const injectAnimatedBlockSize = (config) => {
1425
1449
  nextAnimation.finished.then(cleanup).catch(cleanup);
1426
1450
  animation = nextAnimation;
1427
1451
  };
1428
- // no zone/signal indirection — see the pre-paint timing note in the doc comment above
1452
+ // no zone/signal indirection - see the pre-paint timing note in the doc comment above
1429
1453
  const observer = new ResizeObserver(() => {
1430
1454
  if (!ready)
1431
1455
  return;
@@ -1568,15 +1592,41 @@ const DEFAULT_VIEWPORT_CONFIG = {
1568
1592
  '2xl': [1536, Infinity],
1569
1593
  },
1570
1594
  };
1571
- const [provideViewportConfig, injectViewportConfig] = createStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
1595
+ const VIEWPORT_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
1572
1596
  name: 'Viewport Config',
1573
1597
  });
1574
- const [provideBreakpointObserver, injectBreakpointObserver] = createRootProvider(() => {
1575
- const breakpointObserver = inject(BreakpointObserver);
1598
+ const provideViewportConfig = /* @__PURE__ */ toProvideFn(VIEWPORT_CONFIG_DEF);
1599
+ const injectViewportConfig = /* @__PURE__ */ toInjectFn(VIEWPORT_CONFIG_DEF);
1600
+ const BREAKPOINT_OBSERVER_DEF = /* @__PURE__ */ defineRootProvider(() => {
1576
1601
  const viewportConfig = injectViewportConfig();
1577
- const isMediaQueryMatched = (mediaQuery) => breakpointObserver.isMatched(mediaQuery);
1602
+ const defaultView = inject(DOCUMENT).defaultView;
1603
+ const matchMedia = defaultView?.matchMedia?.bind(defaultView) ?? null;
1604
+ const queryLists = new Map();
1605
+ const querySignals = new Map();
1606
+ const getQueryList = (mediaQuery) => {
1607
+ if (!matchMedia)
1608
+ return null;
1609
+ let queryList = queryLists.get(mediaQuery);
1610
+ if (!queryList) {
1611
+ queryList = matchMedia(mediaQuery);
1612
+ queryLists.set(mediaQuery, queryList);
1613
+ }
1614
+ return queryList;
1615
+ };
1616
+ const isMediaQueryMatched = (mediaQuery) => getQueryList(mediaQuery)?.matches ?? false;
1578
1617
  const observeMediaQuery = (mediaQuery) => {
1579
- return toSignal(breakpointObserver.observe(mediaQuery).pipe(map((x) => x.matches), startWith(isMediaQueryMatched(mediaQuery))), { requireSync: true });
1618
+ const existing = querySignals.get(mediaQuery);
1619
+ if (existing)
1620
+ return existing;
1621
+ const queryList = getQueryList(mediaQuery);
1622
+ const matches = signal(queryList?.matches ?? false, /* @ts-ignore */
1623
+ ...(ngDevMode ? [{ debugName: "matches" }] : /* istanbul ignore next */ []));
1624
+ // the root provider outlives every consumer, so the listener is intentionally never removed -
1625
+ // the same query list is shared by all of them
1626
+ queryList?.addEventListener('change', (event) => matches.set(event.matches));
1627
+ const result = matches.asReadonly();
1628
+ querySignals.set(mediaQuery, result);
1629
+ return result;
1580
1630
  };
1581
1631
  const observeBreakpoint = (options) => observeMediaQuery(buildMediaQueryString(options));
1582
1632
  const isBreakpointMatched = (options) => isMediaQueryMatched(buildMediaQueryString(options));
@@ -1629,13 +1679,27 @@ const [provideBreakpointObserver, injectBreakpointObserver] = createRootProvider
1629
1679
  isMediaQueryMatched,
1630
1680
  };
1631
1681
  }, { name: 'Breakpoint Observer' });
1682
+ const provideBreakpointObserver = /* @__PURE__ */ toProvideFn(BREAKPOINT_OBSERVER_DEF);
1683
+ const injectBreakpointObserver = /* @__PURE__ */ toInjectFn(BREAKPOINT_OBSERVER_DEF);
1632
1684
 
1633
- const BREAKPOINT_KEY_SET = new Set(BREAKPOINT_ORDER);
1685
+ const BREAKPOINT_KEY_SET = /* @__PURE__ */ new Set(BREAKPOINT_ORDER);
1634
1686
  const isBreakpointMap = (value) => {
1635
1687
  if (value === null || typeof value !== 'object' || Array.isArray(value))
1636
1688
  return false;
1637
1689
  const keys = Object.keys(value);
1638
- return keys.length > 0 && keys.every((k) => BREAKPOINT_KEY_SET.has(k));
1690
+ if (keys.length === 0)
1691
+ return false;
1692
+ const invalidKeys = keys.filter((key) => !BREAKPOINT_KEY_SET.has(key));
1693
+ if (!invalidKeys.length)
1694
+ return true;
1695
+ // One bad key makes the whole map inert - it stops being a breakpoint map and becomes a plain value, which
1696
+ // for an attribute binding means `[object Object]` and no effect at all. Silent, and expensive to find.
1697
+ if (ngDevMode && invalidKeys.length < keys.length) {
1698
+ console.warn(`[ethlete] A breakpoint map used the unknown key(s) ${invalidKeys.map((key) => `"${key}"`).join(', ')}, so ` +
1699
+ `the whole map was ignored. Valid keys are ${BREAKPOINT_ORDER.join(', ')} - there is no "default"; the ` +
1700
+ 'smallest breakpoint is the fallback.');
1701
+ }
1702
+ return false;
1639
1703
  };
1640
1704
  const resolveFromMap = (map, bp, defaultValue) => {
1641
1705
  const idx = BREAKPOINT_ORDER.indexOf(bp);
@@ -1654,7 +1718,6 @@ const breakpointTransformBase = (coerce) => {
1654
1718
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1655
1719
  let capturedDefault = undefined;
1656
1720
  let initialized = false;
1657
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1658
1721
  let cachedSig = null;
1659
1722
  const transformFn = (value) => {
1660
1723
  const coerced = isBreakpointMap(value) ? value : coerce(value);
@@ -1671,13 +1734,10 @@ const breakpointTransformBase = (coerce) => {
1671
1734
  const bp = currentBp();
1672
1735
  const r = raw();
1673
1736
  if (!cachedSig) {
1674
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1675
1737
  const instance = injector.get(BREAKPOINT_INSTANCE_TOKEN);
1676
1738
  for (const key of Object.keys(instance)) {
1677
1739
  const val = instance[key];
1678
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1679
1740
  if (val && typeof val === 'function' && val[SIGNAL]?.transformFn === transformFn) {
1680
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1681
1741
  cachedSig = val;
1682
1742
  break;
1683
1743
  }
@@ -1686,7 +1746,6 @@ const breakpointTransformBase = (coerce) => {
1686
1746
  if (!cachedSig || r === undefined || !isBreakpointMap(r))
1687
1747
  return;
1688
1748
  const resolved = resolveFromMap(r, bp, capturedDefault);
1689
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1690
1749
  untracked(() => setInputSignal(cachedSig, resolved));
1691
1750
  });
1692
1751
  return transformFn;
@@ -1812,11 +1871,12 @@ const signalElementMutations = (el, options) => {
1812
1871
  };
1813
1872
  const signalHostElementMutations = (options) => signalElementMutations(inject(ElementRef), options);
1814
1873
 
1815
- const signalElementChildren = (el) => {
1874
+ const DEFAULT_CHILDREN_MUTATIONS = { childList: true, subtree: true, attributes: true };
1875
+ const signalElementChildren = (el, options) => {
1816
1876
  const elements = buildElementSignal(el);
1817
1877
  const firstEl = firstElementSignal(elements);
1818
1878
  const isRendered = signalIsRendered();
1819
- const elementMutations = signalElementMutations(elements, { childList: true, subtree: true, attributes: true });
1879
+ const elementMutations = signalElementMutations(elements, options?.mutations ?? DEFAULT_CHILDREN_MUTATIONS);
1820
1880
  return computed(() => {
1821
1881
  if (!isRendered())
1822
1882
  return [];
@@ -1989,8 +2049,8 @@ const elementCanScroll = (element, direction) => {
1989
2049
  };
1990
2050
  const createViewportRect = () => {
1991
2051
  // Collapses to a zero rect where there is no window (server). `isElementVisible` never reaches the
1992
- // intersection math there anyway — `elementCanScroll` already reports the document as unscrollable
1993
- // — so the rect only has to be a valid shape.
2052
+ // intersection math there anyway - `elementCanScroll` already reports the document as unscrollable
2053
+ // - so the rect only has to be a valid shape.
1994
2054
  const hasWindow = typeof window !== 'undefined';
1995
2055
  const width = hasWindow ? window.innerWidth : 0;
1996
2056
  const height = hasWindow ? window.innerHeight : 0;
@@ -2219,7 +2279,7 @@ const getScrollItemTarget = (entries, container, direction, scrollOrigin, axisDi
2219
2279
  const firstIndex = entries.indexOf(firstVisible);
2220
2280
  const lastIndex = entries.indexOf(lastVisible);
2221
2281
  const containerRect = container.getBoundingClientRect();
2222
- // Only a single item is visible — it must be oversized (wider/taller than the container).
2282
+ // Only a single item is visible - it must be oversized (wider/taller than the container).
2223
2283
  if (firstVisible === lastVisible) {
2224
2284
  const itemRect = firstVisible.target.getBoundingClientRect();
2225
2285
  const isStartEdgeVisible = axisDirection === 'horizontal'
@@ -2458,20 +2518,21 @@ const signalElementLastScrollDirection = (el) => {
2458
2518
  const signalHostElementLastScrollDirection = () => signalElementLastScrollDirection(inject(ElementRef));
2459
2519
 
2460
2520
  /**
2461
- * A signal for whether this page is the one the reader is actually looking at — `false` for a backgrounded
2521
+ * A signal for whether this page is the one the reader is actually looking at - `false` for a backgrounded
2462
2522
  * tab or a minimised window.
2463
2523
  *
2464
2524
  * Not the same question as whether an element is on screen: an IntersectionObserver reports a fully visible
2465
2525
  * element in a background tab, because the tab's own visibility is no part of what it measures. Anything
2466
- * that moves on its own needs to ask both, and a timer is not a substitute — browsers throttle timers in a
2526
+ * that moves on its own needs to ask both, and a timer is not a substitute - browsers throttle timers in a
2467
2527
  * hidden tab rather than stopping them, so work keeps piling up out of sight.
2468
2528
  */
2469
- const injectIsDocumentVisible = memoizeSignal(() => {
2529
+ const injectIsDocumentVisible = /* @__PURE__ */ memoizeSignal(() => {
2470
2530
  const document = inject(DOCUMENT);
2471
2531
  const isVisible = () => document.visibilityState === 'visible';
2472
2532
  return toSignal(fromEvent(document, 'visibilitychange').pipe(map(isVisible)), { initialValue: isVisible() });
2473
2533
  });
2474
2534
 
2535
+ const DEFAULT_SCROLL_STATE_MUTATIONS = { childList: true, subtree: true, attributes: true };
2475
2536
  const areScrollStatesEqual = (a, b) => {
2476
2537
  return (a.canScroll === b.canScroll &&
2477
2538
  a.canScrollHorizontally === b.canScrollHorizontally &&
@@ -2482,7 +2543,7 @@ const signalElementScrollState = (el, options) => {
2482
2543
  const elements = buildElementSignal(el);
2483
2544
  const observedEl = firstElementSignal(elements);
2484
2545
  const elementDimensions = signalElementDimensions(elements);
2485
- const elementMutations = signalElementMutations(elements, { childList: true, subtree: true, attributes: true });
2546
+ const elementMutations = signalElementMutations(elements, options?.mutations ?? DEFAULT_SCROLL_STATE_MUTATIONS);
2486
2547
  const isRendered = signalIsRendered();
2487
2548
  const initialScrollPosition = options?.initialScrollPosition;
2488
2549
  if (initialScrollPosition) {
@@ -2596,7 +2657,6 @@ const useCursorDragScroll = (el, options) => {
2596
2657
  const element = firstElementSignal(elements);
2597
2658
  const destroyRef = inject(DestroyRef);
2598
2659
  const { enabled = signal(true), allowedDirection = 'both' } = options ?? {};
2599
- const scrollState = signalElementScrollState(elements);
2600
2660
  const renderer = injectRenderer();
2601
2661
  const isDragging = signal(false, /* @ts-ignore */
2602
2662
  ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
@@ -2609,8 +2669,13 @@ const useCursorDragScroll = (el, options) => {
2609
2669
  const dragAmount = signal({ x: 0, y: 0 }, /* @ts-ignore */
2610
2670
  ...(ngDevMode ? [{ debugName: "dragAmount" }] : /* istanbul ignore next */ []));
2611
2671
  const document = inject(DOCUMENT);
2612
- const canScroll = computed(() => {
2613
- const currentScrollState = scrollState();
2672
+ // Only measured when the caller hasn't already: this costs a MutationObserver and a ResizeObserver.
2673
+ const suppliedCanScroll = options?.canScroll;
2674
+ const measuredScrollState = suppliedCanScroll ? null : signalElementScrollState(elements);
2675
+ const measuredCanScroll = computed(() => {
2676
+ const currentScrollState = measuredScrollState?.();
2677
+ if (!currentScrollState)
2678
+ return false;
2614
2679
  const direction = maybeSignalValue(allowedDirection);
2615
2680
  switch (direction) {
2616
2681
  case 'both':
@@ -2621,7 +2686,8 @@ const useCursorDragScroll = (el, options) => {
2621
2686
  return currentScrollState.canScrollVertically;
2622
2687
  }
2623
2688
  }, /* @ts-ignore */
2624
- ...(ngDevMode ? [{ debugName: "canScroll" }] : /* istanbul ignore next */ []));
2689
+ ...(ngDevMode ? [{ debugName: "measuredCanScroll" }] : /* istanbul ignore next */ []));
2690
+ const canScroll = suppliedCanScroll ?? measuredCanScroll;
2625
2691
  // Cleanup if the element the cursor drag scroll is bound to gets changed
2626
2692
  effect(() => {
2627
2693
  const { previousElement } = element();
@@ -2722,7 +2788,7 @@ const useCursorDragScroll = (el, options) => {
2722
2788
  dragAmount.set({ x: 0, y: 0 });
2723
2789
  };
2724
2790
  const setupDragging = (e) => {
2725
- // A context menu takes the pointer away without ever delivering a `mouseup` — so without it in here, a
2791
+ // A context menu takes the pointer away without ever delivering a `mouseup` - so without it in here, a
2726
2792
  // right click (or a ctrl-click on a Mac, which is the primary button) leaves the drag latched on and
2727
2793
  // every later mouse move scrolls the container.
2728
2794
  const dragEnd = merge(fromEvent(document, 'mouseup'), fromEvent(document, 'contextmenu'));
@@ -2749,7 +2815,7 @@ const useCursorDragScroll = (el, options) => {
2749
2815
  };
2750
2816
  };
2751
2817
 
2752
- const ET_PROPERTY_REMOVED = Symbol('ET_PROPERTY_REMOVED');
2818
+ const ET_PROPERTY_REMOVED = /* @__PURE__ */ Symbol('ET_PROPERTY_REMOVED');
2753
2819
  const transformOrReturn = (src, config) => {
2754
2820
  const transformer = config?.transform;
2755
2821
  if (transformer) {
@@ -2766,7 +2832,7 @@ const createInitialRoute = () => {
2766
2832
  return window.location.pathname + window.location.search + window.location.hash;
2767
2833
  };
2768
2834
  /** Inject the current router event */
2769
- const injectRouterEvent = memoizeSignal(() => {
2835
+ const injectRouterEvent = /* @__PURE__ */ memoizeSignal(() => {
2770
2836
  const router = inject(Router);
2771
2837
  const initialRoute = createInitialRoute();
2772
2838
  return toSignal(router.events, {
@@ -2777,7 +2843,7 @@ const injectRouterEvent = memoizeSignal(() => {
2777
2843
  * Signal that indicates whether the router has been initialized.
2778
2844
  * The router is considered initialized once the first NavigationEnd event with an id different than -1 has been emitted.
2779
2845
  */
2780
- const injectIsRouterInitialized = memoizeSignal(() => {
2846
+ const injectIsRouterInitialized = /* @__PURE__ */ memoizeSignal(() => {
2781
2847
  const event = injectRouterEvent();
2782
2848
  return computed(() => {
2783
2849
  const e = event();
@@ -2789,7 +2855,7 @@ const injectIsRouterInitialized = memoizeSignal(() => {
2789
2855
  * The url includes query params as well as the fragment. Use `injectRoute` instead if you are not intrusted in those.
2790
2856
  * @example "/my-page?query=1&param=true#fragment"
2791
2857
  */
2792
- const injectUrl = memoizeSignal(() => {
2858
+ const injectUrl = /* @__PURE__ */ memoizeSignal(() => {
2793
2859
  const event = injectRouterEvent();
2794
2860
  const router = inject(Router);
2795
2861
  // Captured once for reads that happen before the router has committed any
@@ -2799,7 +2865,7 @@ const injectUrl = memoizeSignal(() => {
2799
2865
  // We depend on `event` and `currentNavigation` only to get re-evaluated
2800
2866
  // around each navigation, but we read the *committed* url straight from the
2801
2867
  // router. The router commits `currentUrlTree` before it activates components,
2802
- // so `router.url` is already correct inside a child component's constructor —
2868
+ // so `router.url` is already correct inside a child component's constructor -
2803
2869
  // unlike the NavigationEnd event, which only reaches this signal *after*
2804
2870
  // activation.
2805
2871
  event();
@@ -2819,7 +2885,7 @@ const injectUrl = memoizeSignal(() => {
2819
2885
  * Inject the current route
2820
2886
  * @example "/my-page"
2821
2887
  */
2822
- const injectRoute = memoizeSignal(() => {
2888
+ const injectRoute = /* @__PURE__ */ memoizeSignal(() => {
2823
2889
  const url = injectUrl();
2824
2890
  return computed(() => {
2825
2891
  const fullUrl = url();
@@ -2854,14 +2920,9 @@ const createRoute = (router) => {
2854
2920
  /**
2855
2921
  * Inject the complete router state. This includes the current route data, path params, query params, title and fragment.
2856
2922
  */
2857
- const injectRouterState = memoizeSignal(() => {
2923
+ const injectRouterState = /* @__PURE__ */ memoizeSignal(() => {
2858
2924
  const event = injectRouterEvent();
2859
2925
  const router = inject(Router);
2860
- // We only depend on `event` to get re-evaluated on every router event, but we
2861
- // read the *committed* snapshot straight from the router. The router commits
2862
- // `routerState` before it activates components (and emits ActivationStart
2863
- // before NavigationEnd), so this is already correct inside a child component's
2864
- // constructor — unlike an effect, which only flushes after construction.
2865
2926
  return computed(() => {
2866
2927
  event();
2867
2928
  return createRouterState(router);
@@ -2930,7 +2991,7 @@ const injectPathParam = (key, config) => {
2930
2991
  * Inject query params that changed during navigation. Unchanged query params will be ignored.
2931
2992
  * Removed query params will be represented by the symbol `ET_PROPERTY_REMOVED`.
2932
2993
  */
2933
- const injectQueryParamChanges = memoizeSignal(() => {
2994
+ const injectQueryParamChanges = /* @__PURE__ */ memoizeSignal(() => {
2934
2995
  const queryParams = injectQueryParams();
2935
2996
  const prevQueryParams = previousSignalValue(queryParams);
2936
2997
  return computed(() => {
@@ -2954,7 +3015,7 @@ const injectQueryParamChanges = memoizeSignal(() => {
2954
3015
  * Inject path params that changed during navigation. Unchanged path params will be ignored.
2955
3016
  * Removed path params will be represented by the symbol `ET_PROPERTY_REMOVED`.
2956
3017
  */
2957
- const injectPathParamChanges = memoizeSignal(() => {
3018
+ const injectPathParamChanges = /* @__PURE__ */ memoizeSignal(() => {
2958
3019
  const pathParams = injectPathParams();
2959
3020
  const prevPathParams = previousSignalValue(pathParams);
2960
3021
  return computed(() => {
@@ -2976,11 +3037,11 @@ const injectPathParamChanges = memoizeSignal(() => {
2976
3037
  });
2977
3038
  /**
2978
3039
  * The state handed to the navigation that is currently in progress, i.e. what
2979
- * `router.navigate(…, { state })` passed — typed to whatever you expect.
3040
+ * `router.navigate(…, { state })` passed - typed to whatever you expect.
2980
3041
  *
2981
3042
  * Read **synchronously**, in a constructor or a resolver, and deliberately not a signal: navigation state exists
2982
3043
  * only for the duration of the navigation that carries it. By the time an effect flushed, the navigation would be
2983
- * over and the answer always `null`. Returns `null` when there is no navigation in flight or it carried no state —
3044
+ * over and the answer always `null`. Returns `null` when there is no navigation in flight or it carried no state -
2984
3045
  * which is the normal case for a page arrived at by typing its URL, so always handle it.
2985
3046
  *
2986
3047
  * Use it for the things a URL should not carry: a "you were redirected because…" reason, a pre-fetched object the
@@ -3000,21 +3061,16 @@ const injectRouterNavigationState = () => {
3000
3061
  return router.getCurrentNavigation()?.extras.state ?? null;
3001
3062
  };
3002
3063
 
3003
- const ET_DISABLE_SCROLL_TOP = Symbol('ET_DISABLE_SCROLL_TOP');
3004
- const ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE = Symbol('ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE');
3005
- const ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE = Symbol('ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE');
3064
+ const ET_DISABLE_SCROLL_TOP = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP');
3065
+ const ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE');
3066
+ const ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE');
3006
3067
  const routerDisableScrollTop = (config = {}) => {
3007
3068
  return {
3008
3069
  ...(!config.asReturnRoute ? { [ET_DISABLE_SCROLL_TOP]: true } : { [ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE]: true }),
3009
3070
  ...(config.onPathParamChange ? { [ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE]: true } : {}),
3010
3071
  };
3011
3072
  };
3012
- /**
3013
- * Registry of "not settled yet" reporters. Lives in the root injector so a route component can
3014
- * reach the registration created by the app level `setupScrollRestoration` call.
3015
- * @internal
3016
- */
3017
- const [, injectScrollRestorationHolds] = createRootProvider(() => {
3073
+ const SCROLL_RESTORATION_HOLDS_DEF = /* @__PURE__ */ defineRootProvider(() => {
3018
3074
  const holds = new Set();
3019
3075
  return {
3020
3076
  add: (isPending) => {
@@ -3030,11 +3086,17 @@ const [, injectScrollRestorationHolds] = createRootProvider(() => {
3030
3086
  },
3031
3087
  };
3032
3088
  }, { name: 'ScrollRestorationHolds' });
3089
+ /**
3090
+ * Registry of "not settled yet" reporters. Lives in the root injector so a route component can
3091
+ * reach the registration created by the app level `setupScrollRestoration` call.
3092
+ * @internal
3093
+ */
3094
+ const injectScrollRestorationHolds = /* @__PURE__ */ toInjectFn(SCROLL_RESTORATION_HOLDS_DEF);
3033
3095
  /**
3034
3096
  * Suspend scroll restoration while `isPending` reads `true`.
3035
3097
  *
3036
3098
  * Restoration already waits for the scroll container to grow tall enough to reach the saved offset,
3037
- * so this is only needed when the content may take longer than `restore.timeout` to get there —
3099
+ * so this is only needed when the content may take longer than `restore.timeout` to get there -
3038
3100
  * a slow list request, for instance. Registers for the lifetime of the current injection context.
3039
3101
  *
3040
3102
  * @example
@@ -3068,7 +3130,7 @@ const setupScrollRestoration = (config = {}) => {
3068
3130
  return el ?? document.documentElement;
3069
3131
  };
3070
3132
  // The browser restores the offset against the still-loading (much shorter) document, which is the
3071
- // exact problem we are solving — take it over completely.
3133
+ // exact problem we are solving - take it over completely.
3072
3134
  if (restoreEnabled && 'scrollRestoration' in history) {
3073
3135
  history.scrollRestoration = 'manual';
3074
3136
  }
@@ -3107,7 +3169,7 @@ const setupScrollRestoration = (config = {}) => {
3107
3169
  }
3108
3170
  const now = Date.now();
3109
3171
  // A registered hold means the page knows more data is coming, so the content is expected to
3110
- // grow — suspend the clock instead of giving up on a still loading page.
3172
+ // grow - suspend the clock instead of giving up on a still loading page.
3111
3173
  if (holds.isHeld() && now < hardDeadline) {
3112
3174
  deadline = now + restoreTimeout;
3113
3175
  }
@@ -3120,7 +3182,7 @@ const setupScrollRestoration = (config = {}) => {
3120
3182
  }
3121
3183
  cancelPendingRestore();
3122
3184
  };
3123
- // Wait for the new route to actually render before reading any geometry — on `NavigationEnd` the
3185
+ // Wait for the new route to actually render before reading any geometry - on `NavigationEnd` the
3124
3186
  // components exist but the DOM still shows the previous page, whose height would be measured
3125
3187
  // instead. The timeout is a safety net in case no render pass follows.
3126
3188
  const start = () => {
@@ -3244,7 +3306,7 @@ const createLogger = (config) => {
3244
3306
  };
3245
3307
 
3246
3308
  const escapeHtml = (str) => str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3247
- /** Escapes HTML-special characters but leaves existing entity references intact — for input that
3309
+ /** Escapes HTML-special characters but leaves existing entity references intact - for input that
3248
3310
  * may already contain entities (e.g. Markdown serialized from the editor's DOM, where `&` is
3249
3311
  * stored as `&amp;`), where plain escaping would double-escape them. */
3250
3312
  const escapeHtmlPreservingEntities = (str) => str
@@ -3253,7 +3315,7 @@ const escapeHtmlPreservingEntities = (str) => str
3253
3315
  .replace(/>/g, '&gt;')
3254
3316
  .replace(/"/g, '&quot;');
3255
3317
  /** Rejects URL schemes that execute script when navigated. Whitespace/control characters are
3256
- * stripped before checking — browsers ignore them inside URLs, so `java\tscript:` would
3318
+ * stripped before checking - browsers ignore them inside URLs, so `java\tscript:` would
3257
3319
  * otherwise slip through. */
3258
3320
  const isSafeUrl = (url) =>
3259
3321
  // eslint-disable-next-line no-control-regex -- stripping control characters is the point: browsers ignore them inside URLs
@@ -3280,7 +3342,7 @@ const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
3280
3342
  /** The GFM separator token for a column alignment. */
3281
3343
  const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
3282
3344
  const alignStyle = (align) => (align ? ` style="text-align: ${align}"` : '');
3283
- /** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters —
3345
+ /** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters -
3284
3346
  * CommonMark emphasis must not face whitespace on the inside (`** fett**` doesn't parse), and
3285
3347
  * `&nbsp;` counts as whitespace there too. Whitespace-only content stays unwrapped. */
3286
3348
  const emphasize = (marker, content) => {
@@ -3294,9 +3356,9 @@ const makePlaceholder = (kind, idx) => `\u{E000}${kind}${idx}\u{E001}`;
3294
3356
  const placeholderRe = (kind) => new RegExp(`\u{E000}${kind}(\\d+)\u{E001}`, 'gu');
3295
3357
  const isPlaceholder = (kind, str) => new RegExp(`^\u{E000}${kind}\\d+\u{E001}$`, 'u').test(str);
3296
3358
  /** Tags allowed to survive inside an aligned block's raw-HTML passthrough. */
3297
- const SAFE_INLINE_TAGS = new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
3359
+ const SAFE_INLINE_TAGS = /* @__PURE__ */ new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
3298
3360
  /** Reduces raw inline HTML to the editor's own vocabulary: allowed tags lose all their attributes
3299
- * (`<a>` keeps a safe `href`), everything else — including any event-handler attribute — is
3361
+ * (`<a>` keeps a safe `href`), everything else - including any event-handler attribute - is
3300
3362
  * dropped, and the remaining text is escaped. */
3301
3363
  const sanitizeInlineHtml = (html) => {
3302
3364
  const kept = [];
@@ -3321,7 +3383,7 @@ const processInline = (text) => {
3321
3383
  });
3322
3384
  // "Open in new tab" links round-trip as raw HTML because Markdown has no `target` syntax. Extract
3323
3385
  // them before the raw-HTML escape below, keeping only a safe href, `target="_blank"` and a forced
3324
- // `rel="noopener noreferrer"` (any other attribute — including event handlers — is dropped); their
3386
+ // `rel="noopener noreferrer"` (any other attribute - including event handlers - is dropped); their
3325
3387
  // inner markup is processed recursively. Anything else that looks like raw HTML is still escaped.
3326
3388
  const newTabLinks = [];
3327
3389
  text = text.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (full, attrs, inner) => {
@@ -3332,11 +3394,11 @@ const processInline = (text) => {
3332
3394
  const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`;
3333
3395
  return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
3334
3396
  });
3335
- // Raw HTML in Markdown text is escaped, not rendered — the editor writes this HTML straight
3397
+ // Raw HTML in Markdown text is escaped, not rendered - the editor writes this HTML straight
3336
3398
  // into the DOM via innerHTML, so anything else would let a crafted value inject markup. `<u>`
3337
3399
  // is the one deliberate exception: underline has no Markdown form and round-trips as raw <u>.
3338
3400
  text = escapeHtmlPreservingEntities(text).replace(/&lt;(\/?)u&gt;/gi, '<$1u>');
3339
- // Bold + italic — *** before ** before *. A delimiter flanking whitespace doesn't open/close a
3401
+ // Bold + italic - *** before ** before *. A delimiter flanking whitespace doesn't open/close a
3340
3402
  // run (CommonMark's flanking rule), so literal asterisks as in `2 * 3 * 4` stay text.
3341
3403
  text = text.replace(/\*\*\*([^\s*](?:.*?[^\s*])?)\*\*\*/g, '<strong><em>$1</em></strong>');
3342
3404
  text = text.replace(/\*\*([^\s*](?:.*?[^\s*])?)\*\*/g, '<strong>$1</strong>');
@@ -3399,6 +3461,75 @@ const findListItems = (inner) => {
3399
3461
  }
3400
3462
  return items;
3401
3463
  };
3464
+ /** The first top-level `<blockquote>` in `html`, matched with balanced nesting. */
3465
+ const findBlockquote = (html) => {
3466
+ const open = /<blockquote\b[^>]*>/i.exec(html);
3467
+ if (!open)
3468
+ return null;
3469
+ const innerStart = open.index + open[0].length;
3470
+ const tag = /<(\/?)blockquote\b[^>]*>/gi;
3471
+ tag.lastIndex = innerStart;
3472
+ let depth = 1;
3473
+ let match;
3474
+ while ((match = tag.exec(html))) {
3475
+ depth += match[1] ? -1 : 1;
3476
+ if (depth === 0) {
3477
+ return { start: open.index, end: tag.lastIndex, inner: html.slice(innerStart, match.index) };
3478
+ }
3479
+ }
3480
+ return null;
3481
+ };
3482
+ /** The lines of quoted text in a blockquote's own (non-nested) HTML, each with its `> ` prefix.
3483
+ * `<br>` and paragraph boundaries are the line breaks - the tag-strip would swallow them. */
3484
+ const quotedLines = (html, prefix) => {
3485
+ const text = stripTags(html.replace(/<br\s*\/?>/gi, '\n').replace(/<\/p\s*>/gi, '\n')).trim();
3486
+ return text ? text.split('\n').map((line) => `${prefix}${line.trim()}`) : [];
3487
+ };
3488
+ /** Serializes a blockquote's inner HTML to Markdown, one `>` per nesting level. */
3489
+ const blockquoteToMarkdown = (inner, depth) => {
3490
+ const prefix = '> '.repeat(depth + 1);
3491
+ const lines = [];
3492
+ let rest = inner;
3493
+ for (let nested = findBlockquote(rest); nested; nested = findBlockquote(rest)) {
3494
+ lines.push(...quotedLines(rest.slice(0, nested.start), prefix));
3495
+ lines.push(blockquoteToMarkdown(nested.inner, depth + 1));
3496
+ rest = rest.slice(nested.end);
3497
+ }
3498
+ lines.push(...quotedLines(rest, prefix));
3499
+ // An empty quote still needs its marker, or it would vanish from the value entirely.
3500
+ return lines.length > 0 ? lines.join('\n') : prefix.trim();
3501
+ };
3502
+ /** Builds a `<blockquote>` from `> `-prefixed lines, recursing into runs of deeper (`>>`) lines. */
3503
+ const buildBlockquoteHtml = (lines) => {
3504
+ let html = '';
3505
+ let run = [];
3506
+ const flushRun = () => {
3507
+ if (run.length > 0)
3508
+ html += run.map(processInline).join('<br>');
3509
+ run = [];
3510
+ };
3511
+ for (let i = 0; i < lines.length;) {
3512
+ const stripped = (lines[i] ?? '').replace(/^>[ \t]?/, '');
3513
+ if (!stripped.startsWith('>')) {
3514
+ run.push(stripped);
3515
+ i++;
3516
+ continue;
3517
+ }
3518
+ flushRun();
3519
+ const nested = [];
3520
+ while (i < lines.length) {
3521
+ const line = (lines[i] ?? '').replace(/^>[ \t]?/, '');
3522
+ if (!line.startsWith('>'))
3523
+ break;
3524
+ nested.push(line);
3525
+ i++;
3526
+ }
3527
+ html += buildBlockquoteHtml(nested);
3528
+ }
3529
+ flushRun();
3530
+ // `<br>` so an empty quote has a line box the caret can sit in (the editor writes this HTML live)
3531
+ return `<blockquote>${html || '<br>'}</blockquote>`;
3532
+ };
3402
3533
  /** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
3403
3534
  const listToMarkdown = (inner, ordered, depth) => {
3404
3535
  const indent = ' '.repeat(depth);
@@ -3417,7 +3548,7 @@ const listToMarkdown = (inner, ordered, depth) => {
3417
3548
  })
3418
3549
  .join('\n');
3419
3550
  };
3420
- /** Parses list lines into (indent-level, ordered, text) — two leading spaces per nesting level. */
3551
+ /** Parses list lines into (indent-level, ordered, text) - two leading spaces per nesting level. */
3421
3552
  const parseListLines = (lines) => lines
3422
3553
  .map((line) => {
3423
3554
  const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
@@ -3491,7 +3622,7 @@ const markdownToHtml = (markdown) => {
3491
3622
  // Horizontal rule
3492
3623
  if (/^(---|\*\*\*|___)\s*$/.test(trimmed))
3493
3624
  return '<hr>';
3494
- // Table (GFM) — column alignment from the separator line lands as text-align on every cell
3625
+ // Table (GFM) - column alignment from the separator line lands as text-align on every cell
3495
3626
  const tableLines = trimmed.split('\n');
3496
3627
  if (tableLines.length >= 2 && /\|/.test(tableLines[0] ?? '') && isTableSeparatorLine(tableLines[1] ?? '')) {
3497
3628
  const aligns = parseSeparatorAligns(tableLines[1] ?? '');
@@ -3509,15 +3640,10 @@ const markdownToHtml = (markdown) => {
3509
3640
  : '';
3510
3641
  return `<table>${thead}${tbody}</table>`;
3511
3642
  }
3512
- // Blockquote — each line runs through the inline pass on its own (processInline escapes raw
3513
- // HTML, so joining first would escape the <br> separators too)
3514
- if (/^> /.test(trimmed)) {
3515
- const content = trimmed
3516
- .split('\n')
3517
- .map((l) => processInline(l.replace(/^>\s?/, '')))
3518
- .join('<br>');
3519
- return `<blockquote>${content}</blockquote>`;
3520
- }
3643
+ // Blockquote - each line runs through the inline pass on its own (processInline escapes raw
3644
+ // HTML, so joining first would escape the <br> separators too), and a `>>` run nests
3645
+ if (/^>/.test(trimmed))
3646
+ return buildBlockquoteHtml(trimmed.split('\n'));
3521
3647
  // List (unordered/ordered, with indentation-based nesting)
3522
3648
  if (/^([-*+]|\d+\.)\s/.test(trimmed)) {
3523
3649
  const lines = parseListLines(trimmed.split('\n'));
@@ -3525,7 +3651,7 @@ const markdownToHtml = (markdown) => {
3525
3651
  return buildListHtml(lines, 0, lines[0].indent).html;
3526
3652
  }
3527
3653
  }
3528
- // Paragraph — single newlines within a block become <br>
3654
+ // Paragraph - single newlines within a block become <br>
3529
3655
  return `<p>${trimmed
3530
3656
  .split('\n')
3531
3657
  .map((l) => processInline(l))
@@ -3545,21 +3671,21 @@ const htmlToMarkdown = (html) => {
3545
3671
  return '';
3546
3672
  let md = html;
3547
3673
  // Aligned blocks: text-align has no Markdown form, so preserve them verbatim as native HTML (their
3548
- // inner markup stays HTML) and round-trip via a placeholder — extracted before the block passes
3674
+ // inner markup stays HTML) and round-trip via a placeholder - extracted before the block passes
3549
3675
  // below rewrite them, restored after the final tag-strip.
3550
3676
  const alignedBlocks = [];
3551
3677
  md = md.replace(/<(p|h[1-6]|div)\b[^>]*\bstyle="[^"]*text-align[^"]*"[^>]*>[\s\S]*?<\/\1>/gi, (match) => makePlaceholder('ALIGN', alignedBlocks.push(match) - 1));
3552
- // Code blocks — process before inline code
3678
+ // Code blocks - process before inline code
3553
3679
  md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
3554
3680
  md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
3555
3681
  md = md.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
3556
- // Headings — keep the inner markup so the inline passes below turn any nested
3682
+ // Headings - keep the inner markup so the inline passes below turn any nested
3557
3683
  // bold/italic/link/code into markdown; leftover tags are stripped at the end.
3558
3684
  for (let i = 6; i >= 1; i--) {
3559
3685
  md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => `\n${'#'.repeat(i)} ${content.trim()}\n`);
3560
3686
  }
3561
3687
  // Bold + italic (combined before individual). Boundary whitespace is hoisted out of the
3562
- // delimiters (emphasize) — `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
3688
+ // delimiters (emphasize) - `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
3563
3689
  md = md.replace(/<strong[^>]*><em[^>]*>([\s\S]*?)<\/em><\/strong>/gi, (_, c) => emphasize('***', c));
3564
3690
  md = md.replace(/<em[^>]*><strong[^>]*>([\s\S]*?)<\/strong><\/em>/gi, (_, c) => emphasize('***', c));
3565
3691
  md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('**', c));
@@ -3568,7 +3694,7 @@ const htmlToMarkdown = (html) => {
3568
3694
  // Inline code
3569
3695
  md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, code) => `\`${unescapeHtml(code)}\``);
3570
3696
  // Links and images. A link with `target="_blank"` has no Markdown form, so it round-trips as raw
3571
- // HTML (keeping only href + target + rel) via a placeholder — like underline below; ordinary links
3697
+ // HTML (keeping only href + target + rel) via a placeholder - like underline below; ordinary links
3572
3698
  // become `[text](url)`.
3573
3699
  const newTabLinks = [];
3574
3700
  md = md.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, inner) => {
@@ -3582,20 +3708,17 @@ const htmlToMarkdown = (html) => {
3582
3708
  md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '![$2]($1)');
3583
3709
  md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '![$1]($2)');
3584
3710
  md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '![]($1)');
3585
- // Underline has no Markdown form — preserve it as raw <u> so it round-trips. Extract it now (its
3711
+ // Underline has no Markdown form - preserve it as raw <u> so it round-trips. Extract it now (its
3586
3712
  // inner markup is already converted above) so the block passes below don't strip the tag; the
3587
3713
  // placeholder is restored after the final tag-strip.
3588
3714
  const underlines = [];
3589
3715
  md = md.replace(/<u[^>]*>([\s\S]*?)<\/u>/gi, (_, inner) => makePlaceholder('U', underlines.push(inner) - 1));
3590
- // Block quotes — <br> line breaks become one quoted line each (stripTags would swallow them)
3591
- md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
3592
- return (stripTags(content.replace(/<br\s*\/?>/gi, '\n'))
3593
- .trim()
3594
- .split('\n')
3595
- .map((line) => `> ${line.trim()}`)
3596
- .join('\n') + '\n');
3597
- });
3598
- // Lists — replace each top-level list with its recursively-serialized Markdown (handles nesting).
3716
+ // Block quotes - replace each top-level <blockquote> with its recursively-serialized Markdown, so
3717
+ // a quote inside a quote becomes `>>` (regex alone can't match balanced nesting).
3718
+ for (let quote = findBlockquote(md); quote; quote = findBlockquote(md)) {
3719
+ md = `${md.slice(0, quote.start)}\n${blockquoteToMarkdown(quote.inner, 0)}\n${md.slice(quote.end)}`;
3720
+ }
3721
+ // Lists - replace each top-level list with its recursively-serialized Markdown (handles nesting).
3599
3722
  for (let list = findList(md); list; list = findList(md)) {
3600
3723
  md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
3601
3724
  }
@@ -3623,10 +3746,10 @@ const htmlToMarkdown = (html) => {
3623
3746
  const lines = [`| ${headerCells.join(' | ')} |`, `| ${separator} |`, ...bodyRows.map((r) => `| ${r} |`)];
3624
3747
  return `\n${lines.join('\n')}\n`;
3625
3748
  });
3626
- // Block elements — a <br> inside a paragraph is a soft line break; turn it into a newline before
3749
+ // Block elements - a <br> inside a paragraph is a soft line break; turn it into a newline before
3627
3750
  // the tag-strip removes it, so it round-trips (a single newline within a block renders as <br>)
3628
3751
  md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${stripTags(content.replace(/<br\s*\/?>/gi, '\n')).trim()}\n`);
3629
- // A <div> boundary acts as a paragraph boundary — clipboard HTML commonly uses divs as
3752
+ // A <div> boundary acts as a paragraph boundary - clipboard HTML commonly uses divs as
3630
3753
  // paragraphs, and silently stripping them would merge adjacent blocks into one.
3631
3754
  md = md.replace(/<\/div\s*>/gi, '\n\n').replace(/<div[^>]*>/gi, '\n\n');
3632
3755
  md = md.replace(/<br\s*\/?>/gi, '\n');
@@ -3685,17 +3808,11 @@ class RuntimeError extends Error {
3685
3808
  this.code = code;
3686
3809
  this.data = data;
3687
3810
  if (data !== RUNTIME_ERROR_NO_DATA) {
3688
- try {
3689
- const _data = clone(data);
3690
- setTimeout(() => {
3691
- console.error(_data);
3692
- }, 1);
3693
- }
3694
- catch {
3695
- setTimeout(() => {
3696
- console.error(data);
3697
- }, 1);
3698
- }
3811
+ // deferred so the error itself is logged first - consoles render the live object, so a
3812
+ // mutation after the throw is visible in the log
3813
+ setTimeout(() => {
3814
+ console.error(data);
3815
+ }, 1);
3699
3816
  }
3700
3817
  }
3701
3818
  }
@@ -3732,7 +3849,7 @@ const canUseSessionMemory = () => {
3732
3849
  if (typeof globalThis.document === 'undefined') {
3733
3850
  return false;
3734
3851
  }
3735
- return globalThis.sessionStorage != null;
3852
+ return globalThis.sessionStorage !== null && globalThis.sessionStorage !== undefined;
3736
3853
  }
3737
3854
  catch {
3738
3855
  return false;
@@ -3859,8 +3976,8 @@ const createSwipeTracker = (startEvent) => {
3859
3976
  if (firstInWindow === -1)
3860
3977
  return { x: 0, y: 0 };
3861
3978
  const newest = samples[samples.length - 1];
3862
- // Measure across the window when it holds a span of its own. With a single sample in it — a
3863
- // pointer emitting moves slower than the window — reach back one sample for a baseline instead
3979
+ // Measure across the window when it holds a span of its own. With a single sample in it - a
3980
+ // pointer emitting moves slower than the window - reach back one sample for a baseline instead
3864
3981
  // of reporting nothing.
3865
3982
  const hasSpanInWindow = samples.length - firstInWindow >= 2;
3866
3983
  const baseline = samples[hasSpanInWindow ? firstInWindow : Math.max(0, firstInWindow - 1)];
@@ -3897,7 +4014,7 @@ const createSwipeTracker = (startEvent) => {
3897
4014
  return { update, end, cancel };
3898
4015
  };
3899
4016
 
3900
- const [, injectAngularRootElement] = createRootProvider(() => {
4017
+ const ANGULAR_ROOT_ELEMENT_DEF = /* @__PURE__ */ defineRootProvider(() => {
3901
4018
  const appRef = inject(ApplicationRef);
3902
4019
  const rootElement = signal(null, /* @ts-ignore */
3903
4020
  ...(ngDevMode ? [{ debugName: "rootElement" }] : /* istanbul ignore next */ []));
@@ -3913,8 +4030,9 @@ const [, injectAngularRootElement] = createRootProvider(() => {
3913
4030
  setTimeout(poll);
3914
4031
  return rootElement;
3915
4032
  });
4033
+ const injectAngularRootElement = /* @__PURE__ */ toInjectFn(ANGULAR_ROOT_ELEMENT_DEF);
3916
4034
 
3917
- const [provideBoundaryElement, injectBoundaryElement, BOUNDARY_ELEMENT_TOKEN] = createProvider(() => {
4035
+ const BOUNDARY_ELEMENT_DEF = /* @__PURE__ */ defineProvider(() => {
3918
4036
  const hostElement = inject(ElementRef, { optional: true });
3919
4037
  const document = inject(DOCUMENT);
3920
4038
  const override = signal(null, /* @ts-ignore */
@@ -3926,8 +4044,11 @@ const [provideBoundaryElement, injectBoundaryElement, BOUNDARY_ELEMENT_TOKEN] =
3926
4044
  override,
3927
4045
  };
3928
4046
  }, { name: 'Boundary Element' });
4047
+ const provideBoundaryElement = /* @__PURE__ */ toProvideFn(BOUNDARY_ELEMENT_DEF);
4048
+ const injectBoundaryElement = /* @__PURE__ */ toInjectFn(BOUNDARY_ELEMENT_DEF);
4049
+ const BOUNDARY_ELEMENT_TOKEN = /* @__PURE__ */ toToken(BOUNDARY_ELEMENT_DEF);
3929
4050
 
3930
- const [provideFocusVisibleTracker, injectFocusVisibleTracker, FOCUS_VISIBLE_TRACKER_TOKEN] = createRootProvider(() => {
4051
+ const FOCUS_VISIBLE_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
3931
4052
  const document = inject(DOCUMENT);
3932
4053
  const destroyRef = inject(DestroyRef);
3933
4054
  const isFocusVisible = signal(false, /* @ts-ignore */
@@ -3969,8 +4090,11 @@ const [provideFocusVisibleTracker, injectFocusVisibleTracker, FOCUS_VISIBLE_TRAC
3969
4090
  isFocusVisible: isFocusVisible.asReadonly(),
3970
4091
  };
3971
4092
  }, { name: 'Focus Visible Tracker' });
4093
+ const provideFocusVisibleTracker = /* @__PURE__ */ toProvideFn(FOCUS_VISIBLE_TRACKER_DEF);
4094
+ const injectFocusVisibleTracker = /* @__PURE__ */ toInjectFn(FOCUS_VISIBLE_TRACKER_DEF);
4095
+ const FOCUS_VISIBLE_TRACKER_TOKEN = /* @__PURE__ */ toToken(FOCUS_VISIBLE_TRACKER_DEF);
3972
4096
 
3973
- const [provideLocale, injectLocale] = createRootProvider(() => {
4097
+ const LOCALE_DEF = /* @__PURE__ */ defineRootProvider(() => {
3974
4098
  const currentLocale = signal('en', /* @ts-ignore */
3975
4099
  ...(ngDevMode ? [{ debugName: "currentLocale" }] : /* istanbul ignore next */ []));
3976
4100
  return {
@@ -3979,12 +4103,14 @@ const [provideLocale, injectLocale] = createRootProvider(() => {
3979
4103
  }, {
3980
4104
  name: 'Locale',
3981
4105
  });
4106
+ const provideLocale = /* @__PURE__ */ toProvideFn(LOCALE_DEF);
4107
+ const injectLocale = /* @__PURE__ */ toInjectFn(LOCALE_DEF);
3982
4108
 
3983
4109
  const resolveSource = (source, locale) => typeof source === 'function' ? source(locale) : source;
3984
4110
  /**
3985
4111
  * The one mechanism for the strings a component renders or announces itself. Every domain in the UI
3986
- * library that has such strings exposes exactly this pair — `provide<Domain>Labels` to localize a
3987
- * subtree, `inject<Domain>Labels()` to read the result — so localizing an app is the same move
4112
+ * library that has such strings exposes exactly this pair - `provide<Domain>Labels` to localize a
4113
+ * subtree, `inject<Domain>Labels()` to read the result - so localizing an app is the same move
3988
4114
  * repeated per domain rather than a different one each time.
3989
4115
  *
3990
4116
  * `defaults` may itself be locale-derived, for a domain that ships more than one language (the
@@ -3992,7 +4118,7 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
3992
4118
  * resolved to, so translating into a third language while keeping a shipped one as the base is the
3993
4119
  * same call.
3994
4120
  *
3995
- * The injector returns a **signal**, because both halves can change at runtime — read it in a
4121
+ * The injector returns a **signal**, because both halves can change at runtime - read it in a
3996
4122
  * template or computed, never destructure it once.
3997
4123
  *
3998
4124
  * @example
@@ -4003,8 +4129,11 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
4003
4129
  * overflow: 'Show hidden levels',
4004
4130
  * };
4005
4131
  *
4006
- * export const [provideBreadcrumbLabels, injectBreadcrumbLabels, BREADCRUMB_LABELS] =
4007
- * createLabels<BreadcrumbLabels>('BREADCRUMB_LABELS', DEFAULT_BREADCRUMB_LABELS);
4132
+ * const BREADCRUMB_LABELS_DEF = defineLabels<BreadcrumbLabels>('BREADCRUMB_LABELS', DEFAULT_BREADCRUMB_LABELS);
4133
+ *
4134
+ * export const provideBreadcrumbLabels = toProvideFn(BREADCRUMB_LABELS_DEF);
4135
+ * export const injectBreadcrumbLabels = toInjectFn(BREADCRUMB_LABELS_DEF);
4136
+ * export const BREADCRUMB_LABELS = toToken(BREADCRUMB_LABELS_DEF);
4008
4137
  *
4009
4138
  * @example
4010
4139
  * // consumer, fixed wording
@@ -4013,24 +4142,26 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
4013
4142
  * // consumer, driven by the app's i18n and re-resolved on a locale change
4014
4143
  * provideBreadcrumbLabels((locale) => ({ navigation: translate('breadcrumb.nav', locale) }));
4015
4144
  */
4016
- const createLabels = (name, defaults) => {
4145
+ const defineLabels = (name, defaults) => {
4017
4146
  const token = new InjectionToken(name, {
4018
4147
  providedIn: 'root',
4019
4148
  factory: () => ({}),
4020
4149
  });
4021
- const provide = (labels) => ({ provide: token, useValue: labels });
4022
- const injectFn = () => {
4023
- const source = inject(token);
4024
- const { currentLocale } = injectLocale();
4025
- return computed(() => {
4026
- const locale = currentLocale();
4027
- return { ...resolveSource(defaults, locale), ...resolveSource(source, locale) };
4028
- });
4150
+ return {
4151
+ provide: (labels) => ({ provide: token, useValue: labels }),
4152
+ inject: () => {
4153
+ const source = inject(token);
4154
+ const { currentLocale } = injectLocale();
4155
+ return computed(() => {
4156
+ const locale = currentLocale();
4157
+ return { ...resolveSource(defaults, locale), ...resolveSource(source, locale) };
4158
+ });
4159
+ },
4160
+ token,
4029
4161
  };
4030
- return [provide, injectFn, token];
4031
4162
  };
4032
4163
 
4033
- const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
4164
+ const STYLE_MANAGER_DEF = /* @__PURE__ */ defineRootProvider(() => {
4034
4165
  const appRef = inject(ApplicationRef);
4035
4166
  const destroyRef = inject(DestroyRef);
4036
4167
  const document = inject(DOCUMENT);
@@ -4089,6 +4220,8 @@ const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
4089
4220
  });
4090
4221
  return { mount };
4091
4222
  }, { name: 'StyleManager' });
4223
+ const provideStyleManager = /* @__PURE__ */ toProvideFn(STYLE_MANAGER_DEF);
4224
+ const injectStyleManager = /* @__PURE__ */ toInjectFn(STYLE_MANAGER_DEF);
4092
4225
 
4093
4226
  const createUserConsentProvider = (options) => ({
4094
4227
  provide: options.for,
@@ -4123,7 +4256,7 @@ const forceReflow = (element) => {
4123
4256
  *
4124
4257
  * Inside a component, prefer the reactive `injectPrefersReducedMotion()`. This is for the animation
4125
4258
  * helpers that run from effects, event handlers and imperative APIs, where there is no injection
4126
- * context. Pass the element being animated so the query resolves against its own window — otherwise
4259
+ * context. Pass the element being animated so the query resolves against its own window - otherwise
4127
4260
  * an animation inside an iframe would read the top-level document's preference.
4128
4261
  */
4129
4262
  const matchesReducedMotion = (element) => {
@@ -4354,7 +4487,7 @@ class AnimatedLifecycleDirective {
4354
4487
  if (!e.cancelled && e.transitionId === transitionId) {
4355
4488
  return of(true);
4356
4489
  }
4357
- // The batch ended cancelled or under a different id — e.g. the browser cancelled and
4490
+ // The batch ended cancelled or under a different id - e.g. the browser cancelled and
4358
4491
  // restarted a running transition mid-flight (an anchored overlay whose ancestor pane got
4359
4492
  // destroyed) or a foreign transition on the same element finished. If nothing is
4360
4493
  // animating anymore the expected end event can never arrive, so treat the transition as
@@ -4501,321 +4634,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
4501
4634
  }]
4502
4635
  }], ctorParameters: () => [], propDecorators: { ifValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "etAnimatedIf", required: false }] }] } });
4503
4636
 
4504
- class AnimatedOverlayDirective {
4505
- constructor() {
4506
- this.overlayService = inject(Overlay);
4507
- this.injector = inject(Injector);
4508
- this.viewContainerRef = inject(ViewContainerRef);
4509
- this.zone = inject(NgZone);
4510
- this.elementRef = inject(ElementRef);
4511
- this.rootBoundary = injectBoundaryElement({ optional: true });
4512
- this.destroyRef = inject(DestroyRef);
4513
- this.floatingElCleanupFn = null;
4514
- this.unmountSubscription = null;
4515
- this.placement = input('bottom', /* @ts-ignore */
4516
- ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
4517
- this.fallbackPlacements = input(/* @ts-ignore */
4518
- ...(ngDevMode ? [undefined, { debugName: "fallbackPlacements" }] : /* istanbul ignore next */ []));
4519
- this.offset = input(null, /* @ts-ignore */
4520
- ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
4521
- this.arrowPadding = input(4, /* @ts-ignore */
4522
- ...(ngDevMode ? [{ debugName: "arrowPadding" }] : /* istanbul ignore next */ []));
4523
- this.viewportPadding = input(8, /* @ts-ignore */
4524
- ...(ngDevMode ? [{ debugName: "viewportPadding" }] : /* istanbul ignore next */ []));
4525
- this.autoResize = input(false, { ...(ngDevMode ? { debugName: "autoResize" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4526
- this.shift = input(true, { ...(ngDevMode ? { debugName: "shift" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4527
- this.autoHide = input(false, { ...(ngDevMode ? { debugName: "autoHide" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4528
- this.autoCloseIfReferenceHidden = input(false, { ...(ngDevMode ? { debugName: "autoCloseIfReferenceHidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4529
- this.referenceElement = input(this.elementRef.nativeElement, /* @ts-ignore */
4530
- ...(ngDevMode ? [{ debugName: "referenceElement" }] : /* istanbul ignore next */ []));
4531
- this.mirrorWidth = input(false, { ...(ngDevMode ? { debugName: "mirrorWidth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4532
- this.portal = null;
4533
- this.overlayRef = null;
4534
- this.componentRef = null;
4535
- this.beforeOpened$ = new Subject();
4536
- this.afterOpened$ = new Subject();
4537
- this.beforeClosed$ = new Subject();
4538
- this.afterClosed$ = new Subject();
4539
- this.state = signal('init', /* @ts-ignore */
4540
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
4541
- this.isMounted = computed(() => this.state() === 'mounted', /* @ts-ignore */
4542
- ...(ngDevMode ? [{ debugName: "isMounted" }] : /* istanbul ignore next */ []));
4543
- this.isMounting = computed(() => this.state() === 'mounting', /* @ts-ignore */
4544
- ...(ngDevMode ? [{ debugName: "isMounting" }] : /* istanbul ignore next */ []));
4545
- this.isUnmounted = computed(() => this.state() === 'unmounted', /* @ts-ignore */
4546
- ...(ngDevMode ? [{ debugName: "isUnmounted" }] : /* istanbul ignore next */ []));
4547
- this.isUnmounting = computed(() => this.state() === 'unmounting', /* @ts-ignore */
4548
- ...(ngDevMode ? [{ debugName: "isUnmounting" }] : /* istanbul ignore next */ []));
4549
- this.canMount = computed(() => !this.isMounted() && !this.isMounting(), /* @ts-ignore */
4550
- ...(ngDevMode ? [{ debugName: "canMount" }] : /* istanbul ignore next */ []));
4551
- this.canUnmount = computed(() => (this.isMounted() || this.isMounting()) && !this.isUnmounting(), /* @ts-ignore */
4552
- ...(ngDevMode ? [{ debugName: "canUnmount" }] : /* istanbul ignore next */ []));
4553
- this.isHidden = signal(false, /* @ts-ignore */
4554
- ...(ngDevMode ? [{ debugName: "isHidden" }] : /* istanbul ignore next */ []));
4555
- this.isMounted$ = toObservable(this.isMounted);
4556
- this.isMounting$ = toObservable(this.isMounting);
4557
- this.isUnmounted$ = toObservable(this.isUnmounted);
4558
- this.isUnmounting$ = toObservable(this.isUnmounting);
4559
- this.canMount$ = toObservable(this.canMount);
4560
- this.canUnmount$ = toObservable(this.canUnmount);
4561
- this.isHidden$ = toObservable(this.isHidden);
4562
- this.referenceElementDimensions = signalElementDimensions(computed(() => (this.mirrorWidth() ? this.referenceElement() : null)));
4563
- effect(() => {
4564
- const dimensions = this.referenceElementDimensions();
4565
- if (!dimensions || !this.mirrorWidth())
4566
- return;
4567
- this.overlayRef?.updateSize({
4568
- width: this.elementRef.nativeElement.offsetWidth,
4569
- });
4570
- });
4571
- this.destroyRef.onDestroy(() => this.destroy());
4572
- }
4573
- mount(config) {
4574
- if (this.isUnmounting()) {
4575
- if (!this.componentRef) {
4576
- if (isDevMode()) {
4577
- console.warn('AnimatedOverlayDirective: Cannot remount. Component ref is not available.');
4578
- }
4579
- return;
4580
- }
4581
- this.unmountSubscription?.unsubscribe();
4582
- this.unmountSubscription = null;
4583
- this.applyComponentData(config.data);
4584
- this.applyColorProvider(config.colorProvider);
4585
- const lifecycle = this.componentRef.instance.animatedLifecycle();
4586
- if (lifecycle) {
4587
- this.state.set('mounting');
4588
- this.beforeOpened$.next();
4589
- lifecycle.enter();
4590
- lifecycle.state$
4591
- .pipe(filter((s) => s === 'entered'), tap(() => {
4592
- if (this.state() === 'mounting') {
4593
- this.state.set('mounted');
4594
- this.afterOpened$.next();
4595
- }
4596
- }), take(1), takeUntilDestroyed(this.destroyRef))
4597
- .subscribe();
4598
- }
4599
- return this.componentRef.instance;
4600
- }
4601
- if (!this.canMount()) {
4602
- if (isDevMode()) {
4603
- console.warn('AnimatedOverlayDirective: Cannot mount. Component is already mounted or currently mounting.');
4604
- }
4605
- return;
4606
- }
4607
- const { component, providers, data, colorProvider } = config;
4608
- this.state.set('mounting');
4609
- this.beforeOpened$.next();
4610
- const injector = Injector.create({
4611
- parent: this.injector,
4612
- providers: providers ?? [],
4613
- });
4614
- this.overlayRef = this.overlayService.create();
4615
- this.portal = new ComponentPortal(component, this.viewContainerRef, injector);
4616
- this.componentRef = this.overlayRef.attach(this.portal);
4617
- this.applyComponentData(data);
4618
- this.applyColorProvider(colorProvider);
4619
- this.updateOverlaySize();
4620
- this.setupFloatingUI();
4621
- return this.componentRef.instance;
4622
- }
4623
- unmount() {
4624
- if (!this.isMounted() && !this.isMounting() && !this.isUnmounting()) {
4625
- if (isDevMode()) {
4626
- console.warn(`AnimatedOverlayDirective: Cannot unmount. Component is currently ${this.state()}`);
4627
- }
4628
- return;
4629
- }
4630
- if (this.isUnmounting()) {
4631
- return;
4632
- }
4633
- if (!this.componentRef) {
4634
- if (isDevMode()) {
4635
- console.warn('AnimatedOverlayDirective: Cannot unmount. Component ref is not available.');
4636
- }
4637
- return;
4638
- }
4639
- this.state.set('unmounting');
4640
- this.beforeClosed$.next();
4641
- if (this.isHidden()) {
4642
- this.destroy();
4643
- return;
4644
- }
4645
- const lifecycle = this.componentRef.instance.animatedLifecycle();
4646
- if (!lifecycle) {
4647
- this.destroy();
4648
- return;
4649
- }
4650
- lifecycle.leave();
4651
- this.unmountSubscription = lifecycle.state$
4652
- .pipe(filter((s) => s === 'left'), take(1), takeUntilDestroyed(this.destroyRef))
4653
- .subscribe(() => {
4654
- this.unmountSubscription = null;
4655
- this.destroy();
4656
- });
4657
- }
4658
- applyComponentData(data) {
4659
- if (!this.componentRef || !data)
4660
- return;
4661
- Object.assign(this.componentRef.instance, data);
4662
- this.componentRef.changeDetectorRef.markForCheck();
4663
- }
4664
- applyColorProvider(colorProvider) {
4665
- if (!this.componentRef || !colorProvider)
4666
- return;
4667
- this.componentRef.instance.setColorFromProvider(colorProvider);
4668
- }
4669
- updateOverlaySize() {
4670
- if (!this.overlayRef || !this.mirrorWidth())
4671
- return;
4672
- this.overlayRef.updateSize({
4673
- width: this.elementRef.nativeElement.offsetWidth,
4674
- });
4675
- }
4676
- setupFloatingUI() {
4677
- this.zone.runOutsideAngular(() => {
4678
- if (!this.componentRef)
4679
- return;
4680
- const floatingEl = this.componentRef.location.nativeElement;
4681
- const floatingElArrow = this.getFloatingArrow();
4682
- floatingEl.classList.add('et-floating-element');
4683
- const refEl = this.referenceElement();
4684
- const boundary = this.rootBoundary?.value();
4685
- this.floatingElCleanupFn = autoUpdate(refEl, floatingEl, () => {
4686
- this.updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow);
4687
- });
4688
- this.waitForRenderAndAnimate();
4689
- });
4690
- }
4691
- getFloatingArrow() {
4692
- if (!this.componentRef)
4693
- return null;
4694
- return this.componentRef.location.nativeElement.querySelector('[et-floating-arrow]');
4695
- }
4696
- updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow) {
4697
- if (!this.componentRef)
4698
- return;
4699
- const middleware = this.buildFloatingMiddleware(floatingEl, boundary, floatingElArrow);
4700
- computePosition(refEl, floatingEl, {
4701
- placement: this.placement(),
4702
- middleware,
4703
- }).then(({ x, y, placement, middlewareData }) => {
4704
- this.applyFloatingStyles(floatingEl, x, y, placement);
4705
- this.applyArrowStyles(floatingEl, middlewareData, floatingElArrow);
4706
- this.handleReferenceHidden(floatingEl, middlewareData);
4707
- });
4708
- }
4709
- buildFloatingMiddleware(floatingEl, boundary, floatingElArrow) {
4710
- const middleware = [];
4711
- const offsetValue = this.offset();
4712
- if (offsetValue !== null) {
4713
- middleware.push(offset(offsetValue));
4714
- }
4715
- middleware.push(flip({
4716
- fallbackPlacements: this.fallbackPlacements() ?? undefined,
4717
- fallbackAxisSideDirection: 'start',
4718
- boundary,
4719
- }));
4720
- if (this.autoResize()) {
4721
- middleware.push(size({
4722
- padding: this.viewportPadding() ?? undefined,
4723
- apply({ availableHeight, availableWidth }) {
4724
- floatingEl.style.setProperty('--et-floating-max-width', `${availableWidth}px`);
4725
- floatingEl.style.setProperty('--et-floating-max-height', `${availableHeight}px`);
4726
- },
4727
- }));
4728
- }
4729
- if (this.shift()) {
4730
- middleware.push(shift({
4731
- limiter: limitShift(),
4732
- padding: this.viewportPadding() ?? undefined,
4733
- boundary,
4734
- }));
4735
- }
4736
- if (floatingElArrow) {
4737
- middleware.push(arrow({ element: floatingElArrow, padding: this.arrowPadding() ?? undefined }));
4738
- }
4739
- if (this.autoHide() || this.autoCloseIfReferenceHidden()) {
4740
- middleware.push(hide({ strategy: 'referenceHidden', boundary }));
4741
- }
4742
- return middleware;
4743
- }
4744
- applyFloatingStyles(floatingEl, x, y, placement) {
4745
- floatingEl.style.setProperty('--et-floating-translate', `translate3d(${x}px, ${y}px, 0)`);
4746
- floatingEl.setAttribute('et-floating-placement', placement);
4747
- }
4748
- applyArrowStyles(floatingEl, middlewareData, floatingElArrow) {
4749
- if (!middlewareData.arrow || !floatingElArrow)
4750
- return;
4751
- const { x: arrowX, y: arrowY } = middlewareData.arrow;
4752
- floatingEl.style.setProperty('--et-floating-arrow-translate', `translate3d(${arrowX ?? 0}px, ${arrowY ?? 0}px, 0)`);
4753
- }
4754
- handleReferenceHidden(floatingEl, middlewareData) {
4755
- if (middlewareData.hide?.referenceHidden) {
4756
- if (this.autoCloseIfReferenceHidden()) {
4757
- this.unmount();
4758
- }
4759
- else {
4760
- floatingEl.classList.add('et-floating-element--hidden');
4761
- this.isHidden.set(true);
4762
- }
4763
- }
4764
- else if (this.autoHide()) {
4765
- floatingEl.classList.remove('et-floating-element--hidden');
4766
- this.isHidden.set(false);
4767
- }
4768
- }
4769
- waitForRenderAndAnimate() {
4770
- nextFrame(() => {
4771
- if (!this.componentRef || this.state() !== 'mounting') {
4772
- return;
4773
- }
4774
- const lifecycle = this.componentRef.instance.animatedLifecycle?.();
4775
- if (!lifecycle) {
4776
- console.error('AnimatedOverlayDirective: The component does not have an AnimatedLifecycleDirective. Please add one to the component.');
4777
- return;
4778
- }
4779
- lifecycle.enter();
4780
- lifecycle.state$
4781
- .pipe(filter((s) => s === 'entered'), tap(() => {
4782
- if (this.state() === 'mounting') {
4783
- this.state.set('mounted');
4784
- this.afterOpened$.next();
4785
- }
4786
- }), take(1), takeUntilDestroyed(this.destroyRef))
4787
- .subscribe();
4788
- });
4789
- }
4790
- destroy() {
4791
- this.zone.runOutsideAngular(() => {
4792
- this.floatingElCleanupFn?.();
4793
- });
4794
- if (this.overlayRef) {
4795
- this.overlayRef.dispose();
4796
- this.overlayRef = null;
4797
- }
4798
- if (this.componentRef) {
4799
- this.componentRef.destroy();
4800
- this.componentRef = null;
4801
- }
4802
- this.state.set('unmounted');
4803
- this.afterClosed$.next();
4804
- }
4805
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4806
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: AnimatedOverlayDirective, isStandalone: true, selector: "[etAnimatedOverlay]", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, fallbackPlacements: { classPropertyName: "fallbackPlacements", publicName: "fallbackPlacements", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, viewportPadding: { classPropertyName: "viewportPadding", publicName: "viewportPadding", isSignal: true, isRequired: false, transformFunction: null }, autoResize: { classPropertyName: "autoResize", publicName: "autoResize", isSignal: true, isRequired: false, transformFunction: null }, shift: { classPropertyName: "shift", publicName: "shift", isSignal: true, isRequired: false, transformFunction: null }, autoHide: { classPropertyName: "autoHide", publicName: "autoHide", isSignal: true, isRequired: false, transformFunction: null }, autoCloseIfReferenceHidden: { classPropertyName: "autoCloseIfReferenceHidden", publicName: "autoCloseIfReferenceHidden", isSignal: true, isRequired: false, transformFunction: null }, referenceElement: { classPropertyName: "referenceElement", publicName: "referenceElement", isSignal: true, isRequired: false, transformFunction: null }, mirrorWidth: { classPropertyName: "mirrorWidth", publicName: "mirrorWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-animated-overlay" }, exportAs: ["etAnimatedOverlay"], ngImport: i0 }); }
4807
- }
4808
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, decorators: [{
4809
- type: Directive,
4810
- args: [{
4811
- selector: '[etAnimatedOverlay]',
4812
- exportAs: 'etAnimatedOverlay',
4813
- host: {
4814
- class: 'et-animated-overlay',
4815
- },
4816
- }]
4817
- }], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], fallbackPlacements: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackPlacements", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], viewportPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportPadding", required: false }] }], autoResize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoResize", required: false }] }], shift: [{ type: i0.Input, args: [{ isSignal: true, alias: "shift", required: false }] }], autoHide: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHide", required: false }] }], autoCloseIfReferenceHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseIfReferenceHidden", required: false }] }], referenceElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "referenceElement", required: false }] }], mirrorWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mirrorWidth", required: false }] }] } });
4818
-
4819
4637
  const createFlipAnimationGroup = (config) => {
4820
4638
  const { elements, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)', ignoreReducedMotion } = config;
4821
4639
  const flips = elements.map((el) => {
@@ -5159,7 +4977,236 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
5159
4977
  }]
5160
4978
  }], ctorParameters: () => [], propDecorators: { commitThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitThreshold", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dragTapped: [{ type: i0.Output, args: ["dragTapped"] }], dragStarted: [{ type: i0.Output, args: ["dragStarted"] }], dragMoved: [{ type: i0.Output, args: ["dragMoved"] }], dragEnded: [{ type: i0.Output, args: ["dragEnded"] }] } });
5161
4979
 
5162
- const FOCUSABLE_SELECTOR = [
4980
+ /**
4981
+ * The `message` type a service worker must post for `onClick` to fire on the service-worker path,
4982
+ * where the click event is dispatched to the worker and never reaches the page.
4983
+ *
4984
+ * Nothing in the SDK can install this handler - a service worker's code is the app's. Add it to yours
4985
+ * to make `onClick` work on Android Chrome and installed iOS web apps:
4986
+ *
4987
+ * ```js
4988
+ * self.addEventListener('notificationclick', (event) => {
4989
+ * event.notification.close();
4990
+ *
4991
+ * event.waitUntil(
4992
+ * self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
4993
+ * for (const client of clients) {
4994
+ * client.postMessage({
4995
+ * type: 'et:notification-click',
4996
+ * tag: event.notification.tag,
4997
+ * data: event.notification.data,
4998
+ * action: event.action || undefined,
4999
+ * });
5000
+ * }
5001
+ * }),
5002
+ * );
5003
+ * });
5004
+ * ```
5005
+ */
5006
+ const NOTIFICATION_CLICK_MESSAGE_TYPE = 'et:notification-click';
5007
+
5008
+ const toNotificationOptions = (config, tag) => ({
5009
+ body: config.body,
5010
+ icon: config.icon,
5011
+ badge: config.badge,
5012
+ image: config.image,
5013
+ tag,
5014
+ data: config.data,
5015
+ lang: config.lang,
5016
+ dir: config.dir,
5017
+ silent: config.silent,
5018
+ requireInteraction: config.requireInteraction,
5019
+ renotify: config.renotify,
5020
+ vibrate: config.vibrate,
5021
+ timestamp: config.timestamp,
5022
+ actions: config.actions,
5023
+ });
5024
+ // The constructor rejects `actions` with a TypeError rather than ignoring them, so the page path gets
5025
+ // a copy without the options only a persistent notification understands.
5026
+ const toPageNotificationOptions = (options) => {
5027
+ const { actions, image, renotify, timestamp, vibrate, ...pageOptions } = options;
5028
+ return pageOptions;
5029
+ };
5030
+ const NOTIFICATIONS_DEF = /* @__PURE__ */ defineRootProvider(() => {
5031
+ const destroyRef = inject(DestroyRef);
5032
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
5033
+ const isSupported = isBrowser && typeof Notification !== 'undefined';
5034
+ // Streams here outlive the call that created them, and `close()` can be called after teardown -
5035
+ // where `takeUntilDestroyed()` would throw rather than unsubscribe.
5036
+ const destroyed$ = new Subject();
5037
+ destroyRef.onDestroy(() => {
5038
+ destroyed$.next();
5039
+ destroyed$.complete();
5040
+ });
5041
+ const permission = signal(isSupported ? Notification.permission : 'unsupported', /* @ts-ignore */
5042
+ ...(ngDevMode ? [{ debugName: "permission" }] : /* istanbul ignore next */ []));
5043
+ if (isSupported) {
5044
+ // Chromium and Firefox report permission changes here, including a revoke made in site settings
5045
+ // while the tab is open. WebKit has no `notifications` descriptor and rejects the query.
5046
+ const permissionStatus$ = navigator.permissions
5047
+ ? from(navigator.permissions.query({ name: 'notifications' })).pipe(catchError(() => EMPTY))
5048
+ : EMPTY;
5049
+ permissionStatus$
5050
+ .pipe(switchMap((status) => fromEvent(status, 'change').pipe(map(() => status.state), startWith(status.state))), map((state) => (state === 'prompt' ? 'default' : state)), tap((state) => permission.set(state)), takeUntil(destroyed$))
5051
+ .subscribe();
5052
+ const isDocumentVisible = injectIsDocumentVisible();
5053
+ // The fallback for engines without that descriptor: a permission changed in browser settings is
5054
+ // only observable by reading it again, and coming back to the tab is when that matters.
5055
+ effect(() => {
5056
+ if (!isDocumentVisible()) {
5057
+ return;
5058
+ }
5059
+ untracked(() => permission.set(Notification.permission));
5060
+ });
5061
+ }
5062
+ const request = () => defer(() => {
5063
+ if (!isSupported) {
5064
+ return of('unsupported');
5065
+ }
5066
+ // The prompt can only be shown once - after that only the user can change it in site settings.
5067
+ if (Notification.permission !== 'default') {
5068
+ return of(Notification.permission);
5069
+ }
5070
+ return from(new Promise((resolve) => {
5071
+ // WebKit only gained the promise-returning form in Safari 16 and hands the result to the
5072
+ // deprecated callback before that. Passing both is spec-legal; the later resolve is a no-op.
5073
+ const requested = Notification.requestPermission(resolve);
5074
+ requested?.then(resolve, () => resolve(Notification.permission));
5075
+ }));
5076
+ }).pipe(tap((state) => permission.set(state)));
5077
+ let nextTagId = 0;
5078
+ const clickHandlers = new Map();
5079
+ const openCloseFns = new Set();
5080
+ let isClickRelayAttached = false;
5081
+ const attachClickRelay = () => {
5082
+ const serviceWorker = navigator.serviceWorker;
5083
+ if (isClickRelayAttached || !serviceWorker) {
5084
+ return;
5085
+ }
5086
+ isClickRelayAttached = true;
5087
+ fromEvent(serviceWorker, 'message')
5088
+ .pipe(map((event) => event.data), filter((message) => message?.type === NOTIFICATION_CLICK_MESSAGE_TYPE), tap((message) => clickHandlers.get(message.tag)?.({ tag: message.tag, data: message.data, action: message.action })), takeUntil(destroyed$))
5089
+ .subscribe();
5090
+ };
5091
+ // `getRegistration()` rather than `ready`: `ready` never settles in an app without a service
5092
+ // worker, which would leave every `shown$` hanging instead of reporting 'unavailable'.
5093
+ const serviceWorkerRegistration = () => defer(() => navigator.serviceWorker
5094
+ ? from(navigator.serviceWorker.getRegistration()).pipe(catchError(() => of(undefined)))
5095
+ : of(undefined));
5096
+ const closePersistentNotifications = (tag) => serviceWorkerRegistration().pipe(switchMap((registration) => registration
5097
+ ? // Firefox rejects getNotifications() when the worker is not the one that showed them.
5098
+ from(registration.getNotifications(tag ? { tag } : undefined)).pipe(catchError(() => of([])))
5099
+ : of([])), tap((notifications) => notifications.forEach((notification) => notification.close())));
5100
+ const show = (config) => {
5101
+ const tag = config.tag ?? `et-notification-${++nextTagId}`;
5102
+ const options = toNotificationOptions(config, tag);
5103
+ const path = config.path ?? 'auto';
5104
+ // Actions only redirect a notification that has not been pinned to a path - `path: 'page'` is a
5105
+ // caller saying they would rather have it without the buttons than not at all.
5106
+ const needsPersistent = path === 'auto' && !!config.actions?.length;
5107
+ const closed$ = new Subject();
5108
+ let isClosed = false;
5109
+ let pageNotification = null;
5110
+ const close = () => {
5111
+ if (isClosed) {
5112
+ return;
5113
+ }
5114
+ isClosed = true;
5115
+ closed$.next();
5116
+ closed$.complete();
5117
+ clickHandlers.delete(tag);
5118
+ openCloseFns.delete(close);
5119
+ pageNotification?.close();
5120
+ // A no-op unless this notification took the persistent path.
5121
+ closePersistentNotifications(tag).pipe(takeUntil(destroyed$)).subscribe();
5122
+ };
5123
+ const showViaPage = () => {
5124
+ let notification;
5125
+ try {
5126
+ notification = new Notification(config.title, toPageNotificationOptions(options));
5127
+ }
5128
+ catch {
5129
+ // How Android Chrome says "persistent notifications only" - there is no feature flag to test.
5130
+ return 'unavailable';
5131
+ }
5132
+ pageNotification = notification;
5133
+ notification.onclick = (event) => config.onClick?.({ tag, data: config.data, event });
5134
+ notification.onclose = () => {
5135
+ // The user dismissed it - forget the ref so `closeAll()` has nothing stale to walk.
5136
+ openCloseFns.delete(close);
5137
+ config.onClose?.();
5138
+ };
5139
+ notification.onshow = () => config.onShow?.();
5140
+ notification.onerror = () => config.onError?.();
5141
+ if (isClosed) {
5142
+ notification.close();
5143
+ return 'unavailable';
5144
+ }
5145
+ return 'page';
5146
+ };
5147
+ const showViaServiceWorker = () => serviceWorkerRegistration().pipe(switchMap((registration) => {
5148
+ if (!registration || isClosed) {
5149
+ return of('unavailable');
5150
+ }
5151
+ if (config.onClick) {
5152
+ clickHandlers.set(tag, config.onClick);
5153
+ attachClickRelay();
5154
+ }
5155
+ return from(registration.showNotification(config.title, options)).pipe(switchMap(() => isClosed
5156
+ ? // Closed while the worker was still putting it up - take back what just appeared.
5157
+ closePersistentNotifications(tag).pipe(map(() => 'unavailable'))
5158
+ : of('service-worker')), catchError(() => {
5159
+ clickHandlers.delete(tag);
5160
+ return of('unavailable');
5161
+ }));
5162
+ }));
5163
+ const shown$ = defer(() => {
5164
+ // Permission is never requested here - an app that wants the prompt calls `request()`.
5165
+ if (!isSupported || Notification.permission !== 'granted' || isClosed) {
5166
+ return of('unavailable');
5167
+ }
5168
+ if (path === 'service-worker' || needsPersistent) {
5169
+ return showViaServiceWorker();
5170
+ }
5171
+ const pagePath = showViaPage();
5172
+ if (pagePath !== 'unavailable' || path === 'page') {
5173
+ return of(pagePath);
5174
+ }
5175
+ return showViaServiceWorker();
5176
+ }).pipe(tap((deliveryPath) => {
5177
+ if (deliveryPath === 'unavailable') {
5178
+ openCloseFns.delete(close);
5179
+ }
5180
+ }),
5181
+ // Shown once, replayed to every later subscriber - `refCount: false` keeps the result after
5182
+ // the eager subscription below has completed.
5183
+ shareReplay({ bufferSize: 1, refCount: false }));
5184
+ openCloseFns.add(close);
5185
+ // `show()` is imperative: the notification appears whether or not anyone subscribes.
5186
+ shown$.pipe(takeUntil(destroyed$)).subscribe();
5187
+ if (config.autoClose) {
5188
+ shown$
5189
+ .pipe(filter((deliveryPath) => deliveryPath !== 'unavailable'), switchMap(() => timer(config.autoClose ?? 0)), tap(() => close()), takeUntil(closed$), takeUntil(destroyed$))
5190
+ .subscribe();
5191
+ }
5192
+ return { tag, shown$, close };
5193
+ };
5194
+ const closeAll = () => {
5195
+ for (const close of Array.from(openCloseFns)) {
5196
+ close();
5197
+ }
5198
+ closePersistentNotifications().pipe(takeUntil(destroyed$)).subscribe();
5199
+ };
5200
+ destroyRef.onDestroy(() => {
5201
+ clickHandlers.clear();
5202
+ openCloseFns.clear();
5203
+ });
5204
+ return { isSupported, permission: permission.asReadonly(), request, show, closeAll };
5205
+ }, { name: 'Notifications' });
5206
+ const provideNotifications = /* @__PURE__ */ toProvideFn(NOTIFICATIONS_DEF);
5207
+ const injectNotifications = /* @__PURE__ */ toInjectFn(NOTIFICATIONS_DEF);
5208
+
5209
+ const FOCUSABLE_SELECTOR = /* @__PURE__ */ [
5163
5210
  'a[href]',
5164
5211
  'area[href]',
5165
5212
  'button:not([disabled])',
@@ -5320,6 +5367,56 @@ const resetPositioningStyles = (config, hostElement, paneElement, renderer) => {
5320
5367
  renderer.removeAttribute(paneElement, 'data-overlay-placement');
5321
5368
  setBaseElementStyles(config, hostElement, paneElement, renderer);
5322
5369
  };
5370
+ let anchoredPositionSetup = null;
5371
+ /**
5372
+ * Installs the anchored positioning implementation. Called by `anchoredOverlayPosition()`, which is
5373
+ * the only thing that pulls `@floating-ui/dom` into the bundle - apps that never anchor an overlay
5374
+ * do not ship it.
5375
+ *
5376
+ * @internal
5377
+ */
5378
+ const registerAnchoredPositionSetup = (setup) => {
5379
+ anchoredPositionSetup = setup;
5380
+ };
5381
+ const setupPositioning = (config, hostElement, paneElement, overlayRef, renderer) => {
5382
+ const strategy = config.positionStrategy ?? { kind: 'center' };
5383
+ if (strategy.kind === 'anchored') {
5384
+ renderer.setStyle(hostElement, {
5385
+ pointerEvents: config.hasBackdrop === false ? 'none' : 'auto',
5386
+ });
5387
+ if (!anchoredPositionSetup) {
5388
+ if (isDevMode()) {
5389
+ console.error('An overlay was mounted with an anchored position strategy, but anchored positioning is not installed. Build the strategy with `anchoredOverlayPosition()` from @ethlete/core so the positioning code is part of the bundle.', strategy);
5390
+ }
5391
+ applyCenteredPosition(hostElement, paneElement, renderer, { kind: 'center' });
5392
+ return () => undefined;
5393
+ }
5394
+ return anchoredPositionSetup(strategy, paneElement, overlayRef, renderer);
5395
+ }
5396
+ if (strategy.kind === 'global') {
5397
+ applyGlobalPosition(hostElement, paneElement, renderer, strategy);
5398
+ return () => undefined;
5399
+ }
5400
+ applyCenteredPosition(hostElement, paneElement, renderer, strategy);
5401
+ return () => undefined;
5402
+ };
5403
+
5404
+ let middlewareExtras = null;
5405
+ /**
5406
+ * Installs the `size` / `arrow` / `hide` middleware. Called by
5407
+ * `enableAnchoredOverlayPositionExtras()`.
5408
+ *
5409
+ * @internal
5410
+ */
5411
+ const registerAnchoredPositionMiddlewareExtras = (extras) => {
5412
+ middlewareExtras = extras;
5413
+ };
5414
+ const requireMiddlewareExtras = (feature) => {
5415
+ if (!middlewareExtras && isDevMode()) {
5416
+ console.error(`An anchored overlay uses "${feature}", but the matching floating-ui middleware is not installed. Call \`enableAnchoredOverlayPositionExtras()\` from @ethlete/core once during app setup.`);
5417
+ }
5418
+ return middlewareExtras;
5419
+ };
5323
5420
  const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, renderer) => {
5324
5421
  // mirrorWidth needs a real element to measure; virtual references fall back to max-content
5325
5422
  const mirrorWidthElement = strategy.mirrorWidth && isHTMLElement(strategy.referenceElement) ? strategy.referenceElement : null;
@@ -5346,32 +5443,41 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
5346
5443
  boundary: strategy.boundary,
5347
5444
  }));
5348
5445
  }
5349
- // size must run AFTER shift so the available space is measured from the shifted position —
5446
+ // size must run AFTER shift so the available space is measured from the shifted position -
5350
5447
  // otherwise a cross-axis-shifted pane gets its max size capped to the unshifted leftover space
5351
5448
  if (strategy.autoResize) {
5352
- middleware.push(size({
5353
- padding: strategy.viewportPadding ?? 8,
5354
- apply({ availableHeight, availableWidth }) {
5355
- renderer.setCssProperties(paneElement, {
5356
- '--et-overlay-max-width': `${availableWidth}px`,
5357
- '--et-overlay-max-height': `${availableHeight}px`,
5358
- });
5359
- },
5360
- }));
5449
+ const extras = requireMiddlewareExtras('autoResize');
5450
+ if (extras) {
5451
+ middleware.push(extras.size({
5452
+ padding: strategy.viewportPadding ?? 8,
5453
+ apply({ availableHeight, availableWidth }) {
5454
+ renderer.setCssProperties(paneElement, {
5455
+ '--et-overlay-max-width': `${availableWidth}px`,
5456
+ '--et-overlay-max-height': `${availableHeight}px`,
5457
+ });
5458
+ },
5459
+ }));
5460
+ }
5361
5461
  }
5362
5462
  if (arrowElement) {
5363
- middleware.push(arrow({
5364
- element: arrowElement,
5365
- // keeps the arrow off a rounded pane corner by default: 12px covers the radius of the
5366
- // built-in boxed panes (--et-overlay-radius). Panes with a larger radius have to pass their
5367
- // own — an arrow whose base sits on the corner arc looks detached from the pane.
5368
- padding: strategy.arrowPadding ?? 12,
5369
- }));
5463
+ const extras = requireMiddlewareExtras('arrow');
5464
+ if (extras) {
5465
+ middleware.push(extras.arrow({
5466
+ element: arrowElement,
5467
+ // keeps the arrow off a rounded pane corner by default: 12px covers the radius of the
5468
+ // built-in boxed panes (--et-overlay-radius). Panes with a larger radius have to pass their
5469
+ // own - an arrow whose base sits on the corner arc looks detached from the pane.
5470
+ padding: strategy.arrowPadding ?? 12,
5471
+ }));
5472
+ }
5370
5473
  }
5371
5474
  if (strategy.autoHide || strategy.autoCloseIfReferenceHidden) {
5372
- middleware.push(hide({
5373
- strategy: 'referenceHidden',
5374
- }));
5475
+ const extras = requireMiddlewareExtras('autoHide');
5476
+ if (extras) {
5477
+ middleware.push(extras.hide({
5478
+ strategy: 'referenceHidden',
5479
+ }));
5480
+ }
5375
5481
  }
5376
5482
  computePosition(strategy.referenceElement, paneElement, {
5377
5483
  placement: strategy.placement ?? 'bottom',
@@ -5412,20 +5518,29 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
5412
5518
  cleanup();
5413
5519
  };
5414
5520
  };
5415
- const setupPositioning = (config, hostElement, paneElement, overlayRef, renderer) => {
5416
- const strategy = config.positionStrategy ?? { kind: 'center' };
5417
- if (strategy.kind === 'anchored') {
5418
- renderer.setStyle(hostElement, {
5419
- pointerEvents: config.hasBackdrop === false ? 'none' : 'auto',
5420
- });
5421
- return createAnchoredPositionCleanup(strategy, paneElement, overlayRef, renderer);
5422
- }
5423
- if (strategy.kind === 'global') {
5424
- applyGlobalPosition(hostElement, paneElement, renderer, strategy);
5425
- return () => undefined;
5426
- }
5427
- applyCenteredPosition(hostElement, paneElement, renderer, strategy);
5428
- return () => undefined;
5521
+ /**
5522
+ * Builds an anchored position strategy and installs the anchored positioning implementation.
5523
+ *
5524
+ * Anchored positioning is the only part of the overlay runtime that needs `@floating-ui/dom`, and it
5525
+ * is reachable only through this function - an app that positions its overlays centered or globally
5526
+ * never bundles it. Always build anchored strategies with this helper instead of a plain
5527
+ * `{ kind: 'anchored', ... }` literal.
5528
+ *
5529
+ * Overlays that set `autoResize`, `autoHide`, `autoCloseIfReferenceHidden` or render an arrow
5530
+ * additionally need `enableAnchoredOverlayPositionExtras()`.
5531
+ */
5532
+ const anchoredOverlayPosition = (config) => {
5533
+ registerAnchoredPositionSetup(createAnchoredPositionCleanup);
5534
+ return { kind: 'anchored', ...config };
5535
+ };
5536
+
5537
+ /**
5538
+ * Installs the floating-ui middleware anchored overlays need for `autoResize`, `autoHide`,
5539
+ * `autoCloseIfReferenceHidden` and arrows. Call it once wherever such an overlay is built - an
5540
+ * anchored overlay that uses none of those features does not bundle this middleware.
5541
+ */
5542
+ const enableAnchoredOverlayPositionExtras = () => {
5543
+ registerAnchoredPositionMiddlewareExtras({ size, arrow, hide });
5429
5544
  };
5430
5545
 
5431
5546
  const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
@@ -5445,13 +5560,13 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
5445
5560
  elements,
5446
5561
  state: _state.asReadonly(),
5447
5562
  componentInstance: _componentInstance.asReadonly(),
5448
- // Internal — driven by overlay-runtime.ts
5563
+ // Internal - driven by overlay-runtime.ts
5449
5564
  beforeOpenedSubject,
5450
5565
  close(result, source = 'api') {
5451
5566
  if (_state() === 'closing' || _state() === 'closed') {
5452
5567
  return;
5453
5568
  }
5454
- // `reference-detached` is a forced teardown (the anchor is gone) — never vetoable.
5569
+ // `reference-detached` is a forced teardown (the anchor is gone) - never vetoable.
5455
5570
  if (source !== 'reference-detached') {
5456
5571
  for (const guard of closeGuards) {
5457
5572
  if (!guard({ result, source })) {
@@ -5461,7 +5576,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
5461
5576
  }
5462
5577
  requestClose(result, source);
5463
5578
  },
5464
- /** Close bypassing every registered close guard — used by a guard's owner to commit a
5579
+ /** Close bypassing every registered close guard - used by a guard's owner to commit a
5465
5580
  * close it previously vetoed (e.g. after an async confirm resolved). */
5466
5581
  forceClose(result, source = 'api') {
5467
5582
  if (_state() === 'closing' || _state() === 'closed') {
@@ -5528,7 +5643,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
5528
5643
  };
5529
5644
  };
5530
5645
 
5531
- const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
5646
+ const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
5532
5647
  const appRef = inject(ApplicationRef);
5533
5648
  const destroyRef = inject(DestroyRef);
5534
5649
  const document = inject(DOCUMENT);
@@ -5635,7 +5750,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
5635
5750
  hostElement: paneElement,
5636
5751
  bindings: config.bindings ?? [],
5637
5752
  });
5638
- // pane classes are applied after component creation — Angular replaces the host
5753
+ // pane classes are applied after component creation - Angular replaces the host
5639
5754
  // element's class attribute with the component's static host class on creation
5640
5755
  renderer.addClass(paneElement, 'et-overlay-runtime-pane', ...(config.paneClass ?? []));
5641
5756
  appRef.attachView(componentRef.hostView);
@@ -5682,7 +5797,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
5682
5797
  }
5683
5798
  const lifecycle = getAnimatedLifecycle(componentRef);
5684
5799
  animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state$.value ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
5685
- // The reference element is gone — there is nothing left to animate away from, so tear the
5800
+ // The reference element is gone - there is nothing left to animate away from, so tear the
5686
5801
  // overlay down synchronously instead of playing a leave transition from a stale position.
5687
5802
  if (!lifecycle || closeEvent.source === 'reference-detached') {
5688
5803
  destroyMountedOverlay(closeEvent);
@@ -5791,6 +5906,8 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
5791
5906
  openEntries,
5792
5907
  };
5793
5908
  }, { name: 'OverlayRuntime' });
5909
+ const provideOverlayRuntime = /* @__PURE__ */ toProvideFn(OVERLAY_RUNTIME_DEF);
5910
+ const injectOverlayRuntime = /* @__PURE__ */ toInjectFn(OVERLAY_RUNTIME_DEF);
5794
5911
 
5795
5912
  class NormalizeGameResultTypePipe {
5796
5913
  constructor() {
@@ -6831,7 +6948,7 @@ const toStringBinding = (binding) => {
6831
6948
  return value !== null && value !== undefined ? String(value) : null;
6832
6949
  });
6833
6950
  };
6834
- const createBulkPropertyBinding = (propertyPrefix, applyConfigFn) => {
6951
+ const createBulkPropertyBinding = (_propertyPrefix, applyConfigFn) => {
6835
6952
  return (config) => {
6836
6953
  Object.entries(config).forEach(([key, value]) => {
6837
6954
  if (value !== undefined) {
@@ -6841,11 +6958,202 @@ const createBulkPropertyBinding = (propertyPrefix, applyConfigFn) => {
6841
6958
  };
6842
6959
  };
6843
6960
 
6961
+ /** The rendered favicon is this many device-independent pixels square. */
6962
+ const FAVICON_SIZE = 64;
6963
+ /** Fallback when the color theming tokens aren't resolvable (no theme applied to the root element). */
6964
+ const FALLBACK_OVERLAY_COLOR = '#e11d48';
6965
+ const readThemeColor = (document) => {
6966
+ const value = getComputedStyle(document.documentElement).getPropertyValue('--et-theme-color-primary-solid').trim();
6967
+ return value || FALLBACK_OVERLAY_COLOR;
6968
+ };
6969
+ const drawDot = (ctx, color) => {
6970
+ const radius = FAVICON_SIZE * 0.19;
6971
+ const center = FAVICON_SIZE - radius - 3;
6972
+ // Punch a hole first so the badge reads on top of a busy icon, then fill it.
6973
+ ctx.save();
6974
+ ctx.globalCompositeOperation = 'destination-out';
6975
+ ctx.beginPath();
6976
+ ctx.arc(center, center, radius * 1.35, 0, Math.PI * 2);
6977
+ ctx.fill();
6978
+ ctx.restore();
6979
+ ctx.beginPath();
6980
+ ctx.arc(center, center, radius, 0, Math.PI * 2);
6981
+ ctx.fillStyle = color;
6982
+ ctx.fill();
6983
+ };
6984
+ const drawProgress = (ctx, value, color, trackColor) => {
6985
+ const lineWidth = FAVICON_SIZE * 0.14;
6986
+ const radius = (FAVICON_SIZE - lineWidth) / 2;
6987
+ const center = FAVICON_SIZE / 2;
6988
+ const clamped = Math.min(100, Math.max(0, value));
6989
+ const start = -Math.PI / 2;
6990
+ ctx.lineWidth = lineWidth;
6991
+ ctx.lineCap = 'butt';
6992
+ // Clear the ring's lane so the base icon doesn't show through a semi-transparent track.
6993
+ ctx.save();
6994
+ ctx.globalCompositeOperation = 'destination-out';
6995
+ ctx.strokeStyle = '#000';
6996
+ ctx.beginPath();
6997
+ ctx.arc(center, center, radius, 0, Math.PI * 2);
6998
+ ctx.stroke();
6999
+ ctx.restore();
7000
+ ctx.strokeStyle = trackColor;
7001
+ ctx.beginPath();
7002
+ ctx.arc(center, center, radius, 0, Math.PI * 2);
7003
+ ctx.stroke();
7004
+ if (clamped > 0) {
7005
+ ctx.strokeStyle = color;
7006
+ ctx.beginPath();
7007
+ ctx.arc(center, center, radius, start, start + (Math.PI * 2 * clamped) / 100);
7008
+ ctx.stroke();
7009
+ }
7010
+ };
7011
+ const FAVICON_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
7012
+ const document = inject(DOCUMENT);
7013
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
7014
+ const overlays = signal(new Map(), /* @ts-ignore */
7015
+ ...(ngDevMode ? [{ debugName: "overlays" }] : /* istanbul ignore next */ []));
7016
+ const activeOverlay = computed(() => {
7017
+ const all = Array.from(overlays().values());
7018
+ return all.find((overlay) => overlay.kind === 'progress') ?? all[all.length - 1] ?? null;
7019
+ }, /* @ts-ignore */
7020
+ ...(ngDevMode ? [{ debugName: "activeOverlay" }] : /* istanbul ignore next */ []));
7021
+ let iconLink = null;
7022
+ let createdIconLink = false;
7023
+ let originalHref = null;
7024
+ let baseImage = null;
7025
+ let baseImageFailed = false;
7026
+ const resolveIconLink = () => {
7027
+ if (iconLink) {
7028
+ return iconLink;
7029
+ }
7030
+ iconLink = document.querySelector('link[rel~="icon"]');
7031
+ if (!iconLink) {
7032
+ iconLink = document.createElement('link');
7033
+ iconLink.rel = 'icon';
7034
+ document.head.appendChild(iconLink);
7035
+ createdIconLink = true;
7036
+ }
7037
+ originalHref = createdIconLink ? null : iconLink.getAttribute('href');
7038
+ return iconLink;
7039
+ };
7040
+ const restore = () => {
7041
+ if (!iconLink)
7042
+ return;
7043
+ if (createdIconLink) {
7044
+ iconLink.remove();
7045
+ iconLink = null;
7046
+ createdIconLink = false;
7047
+ return;
7048
+ }
7049
+ if (originalHref === null) {
7050
+ iconLink.removeAttribute('href');
7051
+ }
7052
+ else {
7053
+ iconLink.setAttribute('href', originalHref);
7054
+ }
7055
+ };
7056
+ // The base icon is loaded once and reused; a favicon that can't be read (cross-origin without
7057
+ // CORS headers, an SVG without intrinsic size) draws on an empty canvas instead of failing.
7058
+ const loadBaseImage = () => {
7059
+ if (baseImage || baseImageFailed || !originalHref) {
7060
+ return Promise.resolve(baseImage);
7061
+ }
7062
+ return new Promise((resolve) => {
7063
+ const image = new Image();
7064
+ image.onload = () => {
7065
+ baseImage = image;
7066
+ resolve(image);
7067
+ };
7068
+ image.onerror = () => {
7069
+ baseImageFailed = true;
7070
+ resolve(null);
7071
+ };
7072
+ image.src = originalHref;
7073
+ });
7074
+ };
7075
+ const render = async (overlay) => {
7076
+ const canvas = document.createElement('canvas');
7077
+ canvas.width = FAVICON_SIZE;
7078
+ canvas.height = FAVICON_SIZE;
7079
+ // No 2d context (a non-browser DOM implementation) - leave the favicon untouched entirely.
7080
+ const ctx = canvas.getContext('2d');
7081
+ if (!ctx)
7082
+ return;
7083
+ const link = resolveIconLink();
7084
+ const image = await loadBaseImage();
7085
+ // A later state may have won the race while the base image loaded.
7086
+ if (activeOverlay() !== overlay)
7087
+ return;
7088
+ if (image) {
7089
+ ctx.drawImage(image, 0, 0, FAVICON_SIZE, FAVICON_SIZE);
7090
+ }
7091
+ const color = overlay.color ?? readThemeColor(document);
7092
+ if (overlay.kind === 'dot') {
7093
+ drawDot(ctx, color);
7094
+ }
7095
+ else {
7096
+ drawProgress(ctx, overlay.value, color, overlay.trackColor ?? 'rgba(127, 127, 127, 0.35)');
7097
+ }
7098
+ try {
7099
+ link.setAttribute('href', canvas.toDataURL('image/png'));
7100
+ }
7101
+ catch {
7102
+ // A tainted canvas (cross-origin base icon) can't be exported - leave the favicon as it was.
7103
+ restore();
7104
+ }
7105
+ };
7106
+ if (isBrowser) {
7107
+ effect(() => {
7108
+ const overlay = activeOverlay();
7109
+ if (!overlay) {
7110
+ restore();
7111
+ return;
7112
+ }
7113
+ void render(overlay);
7114
+ });
7115
+ }
7116
+ const addOverlay = (id, overlay) => {
7117
+ overlays.update((current) => new Map(current).set(id, overlay));
7118
+ };
7119
+ const removeOverlay = (id) => {
7120
+ overlays.update((current) => {
7121
+ const next = new Map(current);
7122
+ next.delete(id);
7123
+ return next;
7124
+ });
7125
+ };
7126
+ return { activeOverlay, addOverlay, removeOverlay };
7127
+ }, { name: 'Favicon Store' });
7128
+ /**
7129
+ * Owns the `<link rel="icon">` href so overlays can be drawn onto the site's favicon and taken back
7130
+ * off again. The original href is captured on first use and restored once the last overlay is gone.
7131
+ *
7132
+ * Registrations are keyed, and a `progress` overlay wins over a `dot` - a ring that also carries a
7133
+ * badge reads as neither.
7134
+ */
7135
+ const provideFaviconStore = /* @__PURE__ */ toProvideFn(FAVICON_STORE_DEF);
7136
+ const injectFaviconStore = /* @__PURE__ */ toInjectFn(FAVICON_STORE_DEF);
7137
+ /**
7138
+ * Draw an overlay on the site's favicon while the binding has a value - an unsaved-changes dot, an
7139
+ * upload's progress ring. The favicon is restored when the binding goes empty or the injector is
7140
+ * destroyed. No-op during SSR.
7141
+ *
7142
+ * ```ts
7143
+ * applyFaviconOverlay(computed(() => (this.saving() ? { kind: 'progress', value: this.percent() } : null)));
7144
+ * ```
7145
+ */
7146
+ const applyFaviconOverlay = (binding) => {
7147
+ const faviconStore = injectFaviconStore();
7148
+ const overlayId = Symbol('favicon-overlay');
7149
+ applyHeadBinding(binding, (value) => faviconStore.addOverlay(overlayId, value), () => faviconStore.removeOverlay(overlayId));
7150
+ };
7151
+
6844
7152
  var jsonLd = /*#__PURE__*/Object.freeze({
6845
7153
  __proto__: null
6846
7154
  });
6847
7155
 
6848
- const DEFAULT_MULTI_INSTANCE_RELS = new Set([
7156
+ const DEFAULT_MULTI_INSTANCE_RELS = /* @__PURE__ */ new Set([
6849
7157
  'alternate',
6850
7158
  'icon',
6851
7159
  'apple-touch-icon',
@@ -6858,10 +7166,12 @@ const DEFAULT_MULTI_INSTANCE_RELS = new Set([
6858
7166
  'stylesheet',
6859
7167
  'modulepreload',
6860
7168
  ]);
6861
- const [provideLinkStoreConfig, injectLinkStoreConfig] = createStaticRootProvider({
7169
+ const LINK_STORE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
6862
7170
  multiInstanceRels: DEFAULT_MULTI_INSTANCE_RELS,
6863
7171
  }, { name: 'Link Store Config' });
6864
- const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
7172
+ const provideLinkStoreConfig = /* @__PURE__ */ toProvideFn(LINK_STORE_CONFIG_DEF);
7173
+ const injectLinkStoreConfig = /* @__PURE__ */ toInjectFn(LINK_STORE_CONFIG_DEF);
7174
+ const LINK_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
6865
7175
  const document = inject(DOCUMENT$1);
6866
7176
  const config = injectLinkStoreConfig();
6867
7177
  const linksByKey = signal(new Map(), /* @ts-ignore */
@@ -6897,7 +7207,6 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
6897
7207
  };
6898
7208
  const createLinkElement = (linkConfig) => {
6899
7209
  const link = document.createElement('link');
6900
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6901
7210
  const { key, allowMultiple, ...attributes } = linkConfig;
6902
7211
  Object.entries(attributes).forEach(([attrKey, value]) => {
6903
7212
  if (value !== undefined && value !== null) {
@@ -6982,6 +7291,8 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
6982
7291
  };
6983
7292
  return { addLink, removeLink, getLinkKey };
6984
7293
  }, { name: 'Link Store' });
7294
+ const provideLinkStore = /* @__PURE__ */ toProvideFn(LINK_STORE_DEF);
7295
+ const injectLinkStore = /* @__PURE__ */ toInjectFn(LINK_STORE_DEF);
6985
7296
  const applyLinkBinding = (binding) => {
6986
7297
  const linkStore = injectLinkStore();
6987
7298
  const linkId = Symbol('link-tag');
@@ -6991,7 +7302,7 @@ const applyLinkBinding = (binding) => {
6991
7302
  linkStore.addLink(linkId, config);
6992
7303
  }, () => linkStore.removeLink(linkId, currentKey));
6993
7304
  };
6994
- const applyCanonicalBinding = createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
7305
+ const applyCanonicalBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
6995
7306
  const applyAlternateBinding = (hreflang, binding) => {
6996
7307
  applyLinkBinding(computed(() => {
6997
7308
  const url = untracked(() => (isSignal(binding) ? binding() : binding));
@@ -7003,8 +7314,8 @@ const applyAlternateLanguagesBindings = (config) => {
7003
7314
  applyAlternateBinding(hreflang, binding);
7004
7315
  });
7005
7316
  };
7006
- const applyPrevBinding = createPropertyBinding((href) => ({ rel: 'prev', href }), applyLinkBinding);
7007
- const applyNextBinding = createPropertyBinding((href) => ({ rel: 'next', href }), applyLinkBinding);
7317
+ const applyPrevBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'prev', href }), applyLinkBinding);
7318
+ const applyNextBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'next', href }), applyLinkBinding);
7008
7319
  const applyResourceHintsBindings = (config) => {
7009
7320
  if (config.preconnect) {
7010
7321
  createArrayPropertyBinding((href, index) => ({ rel: 'preconnect', href, key: `preconnect:${index}` }), applyLinkBinding)(config.preconnect);
@@ -7020,20 +7331,26 @@ const applyResourceHintsBindings = (config) => {
7020
7331
  }
7021
7332
  };
7022
7333
 
7023
- const [provideTitleConfig, injectTitleConfig] = createStaticRootProvider({
7334
+ const TITLE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
7024
7335
  divider: '|',
7025
7336
  defaultTitle: '',
7026
7337
  transformer: (title) => title,
7027
7338
  }, { name: 'Title Config' });
7028
- const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
7339
+ const provideTitleConfig = /* @__PURE__ */ toProvideFn(TITLE_CONFIG_DEF);
7340
+ const injectTitleConfig = /* @__PURE__ */ toInjectFn(TITLE_CONFIG_DEF);
7341
+ const TITLE_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
7029
7342
  const config = injectTitleConfig();
7030
7343
  const titleParts = signal(new Map(), /* @ts-ignore */
7031
7344
  ...(ngDevMode ? [{ debugName: "titleParts" }] : /* istanbul ignore next */ []));
7345
+ const titleMarkers = signal(new Map(), /* @ts-ignore */
7346
+ ...(ngDevMode ? [{ debugName: "titleMarkers" }] : /* istanbul ignore next */ []));
7032
7347
  const { currentLocale } = injectLocale();
7033
7348
  const titleService = inject(Title);
7034
7349
  const isRouterInitialized = injectIsRouterInitialized();
7035
- const defaultTitle = config.defaultTitle ?? titleService.getTitle();
7036
- const title = computed(() => {
7350
+ // Falls back on an empty configured default too, so a store created lazily (e.g. by a title
7351
+ // marker) doesn't wipe a title that came from `index.html`.
7352
+ const defaultTitle = config.defaultTitle || titleService.getTitle();
7353
+ const composedTitle = computed(() => {
7037
7354
  const partsMap = titleParts();
7038
7355
  if (!partsMap.size || !isRouterInitialized()) {
7039
7356
  return config.transformer(defaultTitle, currentLocale());
@@ -7057,6 +7374,20 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
7057
7374
  }
7058
7375
  return finalTextParts.join(` ${config.divider} `);
7059
7376
  }, /* @ts-ignore */
7377
+ ...(ngDevMode ? [{ debugName: "composedTitle" }] : /* istanbul ignore next */ []));
7378
+ /**
7379
+ * The composed title with any markers prefixed - what actually lands in `document.title`.
7380
+ * Markers sit outside the divider logic, so `● Page | App` rather than `● | Page | App`.
7381
+ */
7382
+ const title = computed(() => {
7383
+ // Deduplicated: two forms with unsaved changes should read as one marker, not two.
7384
+ const markers = Array.from(new Set(titleMarkers().values()));
7385
+ const composed = composedTitle();
7386
+ if (!markers.length) {
7387
+ return composed;
7388
+ }
7389
+ return `${markers.join(' ')} ${composed}`.trim();
7390
+ }, /* @ts-ignore */
7060
7391
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
7061
7392
  const addPart = (id, part) => {
7062
7393
  titleParts.update((parts) => new Map(parts).set(id, part));
@@ -7068,19 +7399,41 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
7068
7399
  return newParts;
7069
7400
  });
7070
7401
  };
7402
+ const addMarker = (id, marker) => {
7403
+ titleMarkers.update((markers) => new Map(markers).set(id, marker));
7404
+ };
7405
+ const removeMarker = (id) => {
7406
+ titleMarkers.update((markers) => {
7407
+ const newMarkers = new Map(markers);
7408
+ newMarkers.delete(id);
7409
+ return newMarkers;
7410
+ });
7411
+ };
7071
7412
  effect(() => {
7072
7413
  const titleText = title();
7073
7414
  titleService.setTitle(titleText);
7074
7415
  });
7075
- return { title, addPart, removePart };
7416
+ return { title, addPart, removePart, addMarker, removeMarker };
7076
7417
  }, { name: 'Title Store' });
7418
+ const provideTitleStore = /* @__PURE__ */ toProvideFn(TITLE_STORE_DEF);
7419
+ const injectTitleStore = /* @__PURE__ */ toInjectFn(TITLE_STORE_DEF);
7077
7420
  const applyHeadTitleBinding = (binding, options) => {
7078
7421
  const titleStore = injectTitleStore();
7079
7422
  const partId = Symbol('title-part');
7080
7423
  applyHeadBinding(binding, (value) => titleStore.addPart(partId, { text: `${value}`, ...options }), () => titleStore.removePart(partId), (value) => value !== null && value !== undefined && value !== '');
7081
7424
  };
7425
+ /**
7426
+ * Prefix the tab title with a short marker (`● Page | App`) while the binding has a value - the
7427
+ * unsaved-changes dot, a pending-count badge, and the like. The marker is removed when the binding
7428
+ * goes empty and on destroy. Unlike a title part, it is not joined by the divider.
7429
+ */
7430
+ const applyHeadTitleMarker = (binding) => {
7431
+ const titleStore = injectTitleStore();
7432
+ const markerId = Symbol('title-marker');
7433
+ applyHeadBinding(binding, (value) => titleStore.addMarker(markerId, value), () => titleStore.removeMarker(markerId), (value) => value !== null && value !== undefined && value !== '');
7434
+ };
7082
7435
 
7083
- const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
7436
+ const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
7084
7437
  // Open Graph - Media
7085
7438
  'property="og:image"',
7086
7439
  'property="og:image:url"',
@@ -7149,11 +7502,13 @@ const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
7149
7502
  'name="p:domain_verify"', // Pinterest
7150
7503
  'name="facebook-domain-verification"',
7151
7504
  ]);
7152
- const [provideMetaConfig, injectMetaConfig] = createStaticRootProvider({
7505
+ const META_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
7153
7506
  transformer: (content) => content,
7154
7507
  multiInstanceTags: DEFAULT_MULTI_INSTANCE_TAGS,
7155
7508
  }, { name: 'Meta Config' });
7156
- const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
7509
+ const provideMetaConfig = /* @__PURE__ */ toProvideFn(META_CONFIG_DEF);
7510
+ const injectMetaConfig = /* @__PURE__ */ toInjectFn(META_CONFIG_DEF);
7511
+ const META_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
7157
7512
  const metaService = inject(Meta);
7158
7513
  const config = injectMetaConfig();
7159
7514
  const { currentLocale } = injectLocale();
@@ -7179,7 +7534,6 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
7179
7534
  if (transformedConfig.content) {
7180
7535
  transformedConfig.content = config.transformer(transformedConfig.content, locale);
7181
7536
  }
7182
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7183
7537
  const { key, allowMultiple, ...metaConfig } = transformedConfig;
7184
7538
  return metaService.addTag(metaConfig);
7185
7539
  };
@@ -7266,6 +7620,8 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
7266
7620
  });
7267
7621
  return { addTag, removeTag, getSelector };
7268
7622
  }, { name: 'Meta Store' });
7623
+ const provideMetaStore = /* @__PURE__ */ toProvideFn(META_STORE_DEF);
7624
+ const injectMetaStore = /* @__PURE__ */ toInjectFn(META_STORE_DEF);
7269
7625
  const applyMetaBinding = (binding) => {
7270
7626
  const metaStore = injectMetaStore();
7271
7627
  const tagId = Symbol('meta-tag');
@@ -7275,14 +7631,14 @@ const applyMetaBinding = (binding) => {
7275
7631
  metaStore.addTag(tagId, config);
7276
7632
  }, () => metaStore.removeTag(tagId, currentSelector));
7277
7633
  };
7278
- const applyDescriptionBinding = createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
7634
+ const applyDescriptionBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
7279
7635
  const applyKeywordsBinding = (binding) => {
7280
7636
  applyMetaBinding(computed(() => {
7281
7637
  const keywords = untracked(() => (isSignal(binding) ? binding() : binding));
7282
7638
  return keywords && keywords.length > 0 ? { name: 'keywords', content: keywords.join(', ') } : null;
7283
7639
  }));
7284
7640
  };
7285
- const applyAuthorBinding = createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
7641
+ const applyAuthorBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
7286
7642
  const applyRobotsBinding = (binding) => {
7287
7643
  applyMetaBinding(computed(() => {
7288
7644
  const config = untracked(() => (isSignal(binding) ? binding() : binding));
@@ -7595,10 +7951,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7595
7951
  }]
7596
7952
  }] });
7597
7953
 
7598
- const [provideStructuredDataConfig, injectStructuredDataConfig] = createStaticRootProvider({
7954
+ const STRUCTURED_DATA_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
7599
7955
  placement: 'body',
7600
7956
  }, { name: 'Structured Data Config' });
7601
- const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvider(() => {
7957
+ const provideStructuredDataConfig = /* @__PURE__ */ toProvideFn(STRUCTURED_DATA_CONFIG_DEF);
7958
+ const injectStructuredDataConfig = /* @__PURE__ */ toInjectFn(STRUCTURED_DATA_CONFIG_DEF);
7959
+ const STRUCTURED_DATA_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
7602
7960
  const document = inject(DOCUMENT$1);
7603
7961
  const renderer = injectRenderer();
7604
7962
  const config = injectStructuredDataConfig();
@@ -7615,7 +7973,11 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
7615
7973
  renderer.removeChild(oldScript.parentNode, oldScript);
7616
7974
  }
7617
7975
  const script = renderer.createElement('script');
7618
- renderer.setAttributes(script, { type: 'application/ld+json', text: JSON.stringify(data) });
7976
+ renderer.setAttributes(script, { type: 'application/ld+json' });
7977
+ // The JSON is the script's *content*, not an attribute. Written as text rather than markup so a
7978
+ // `</script>` inside a string can't close the tag early - the escaping problem
7979
+ // `StructuredDataComponent` has to solve by hand because it goes through `innerHTML`.
7980
+ renderer.setTextContent(script, JSON.stringify(data));
7619
7981
  const target = getTargetElement();
7620
7982
  renderer.appendChild(target, script);
7621
7983
  newScripts.set(id, script);
@@ -7635,6 +7997,8 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
7635
7997
  };
7636
7998
  return { addStructuredData, removeStructuredData };
7637
7999
  }, { name: 'Structured Data Store' });
8000
+ const provideStructuredDataStore = /* @__PURE__ */ toProvideFn(STRUCTURED_DATA_STORE_DEF);
8001
+ const injectStructuredDataStore = /* @__PURE__ */ toInjectFn(STRUCTURED_DATA_STORE_DEF);
7638
8002
  const applyStructuredDataBinding = (binding) => {
7639
8003
  const store = injectStructuredDataStore();
7640
8004
  const scriptId = Symbol('structured-data');
@@ -7684,12 +8048,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7684
8048
 
7685
8049
  /**
7686
8050
  * Cascades hover/active-driven color DOWN to descendants explicitly marked with
7687
- * `ColorInteractiveDirective` — this element does not color itself. Use it when a wrapper should
8051
+ * `ColorInteractiveDirective` - this element does not color itself. Use it when a wrapper should
7688
8052
  * expand a nested control's hit/visual area (e.g. clicking a checkbox's label should light up the
7689
- * checkbox itself), never to give a wrapper its own interactive coloring — that's
8053
+ * checkbox itself), never to give a wrapper its own interactive coloring - that's
7690
8054
  * `ColorInteractiveDirective`.
7691
8055
  *
7692
- * Does nothing unless at least one descendant carries `ColorInteractiveDirective` — check for
8056
+ * Does nothing unless at least one descendant carries `ColorInteractiveDirective` - check for
7693
8057
  * that before adding this. It was mistakenly added to a component with no such descendant once
7694
8058
  * already, where it did nothing but was confusing to read.
7695
8059
  */
@@ -7715,9 +8079,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7715
8079
  * Marks a descendant as NOT counting toward an ancestor's `ColorInteractiveDirective`
7716
8080
  * `:hover`/`:active` reaction (via the `:has()` guard in color-interactive-styles.component.css).
7717
8081
  * Use it for content that's incidentally inside an interactive ancestor but isn't part of what
7718
- * makes that ancestor interactive — e.g. a hint/error block sitting inside a form field's frame.
8082
+ * makes that ancestor interactive - e.g. a hint/error block sitting inside a form field's frame.
7719
8083
  *
7720
- * This only suppresses the ancestor's OWN reaction to hovering/pressing the excluded element — it
8084
+ * This only suppresses the ancestor's OWN reaction to hovering/pressing the excluded element - it
7721
8085
  * has no effect on custom-property inheritance into descendants past the excluded element. If
7722
8086
  * what you actually need is "stop my own cosmetic styling from reacting to a parent's unrelated
7723
8087
  * interaction," this is the wrong tool; that's the class of bug `ColorInteractiveHasFocusDirective`
@@ -7739,7 +8103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7739
8103
 
7740
8104
  /**
7741
8105
  * Tints `--et-theme-color-primary-*` etc. only when a descendant genuinely has `:focus-visible`
7742
- * (`:has(:focus-visible)`) — never on mouse `:hover`/`:active`. Use this for a wrapper (like a
8106
+ * (`:has(:focus-visible)`) - never on mouse `:hover`/`:active`. Use this for a wrapper (like a
7743
8107
  * form field's frame) that should show the theme color while its control is focused, but must
7744
8108
  * NOT react to incidental mouse interaction with unrelated nested widgets (a toolbar button, a
7745
8109
  * rendered link) that happen to live inside the same wrapper.
@@ -7751,7 +8115,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7751
8115
  * Deliberately does NOT also carry `ColorInteractiveDirective`'s `et-color-interactive` class:
7752
8116
  * that class's plain `:hover`/`:active` rules locally override the same (inheriting)
7753
8117
  * `--et-theme-color-primary-rgb`, so anything bubbling up from ANYWHERE in the subtree would
7754
- * leak the transient hover/press color into every descendant reading it for unrelated cosmetics —
8118
+ * leak the transient hover/press color into every descendant reading it for unrelated cosmetics -
7755
8119
  * exactly the bug this directive's only consumer (form-field) shipped once, before the two
7756
8120
  * behaviors were split apart. If you need the plain hover/active reaction too, add
7757
8121
  * `ColorInteractiveDirective` explicitly and make sure you actually want both.
@@ -7777,13 +8141,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7777
8141
  /**
7778
8142
  * Makes an element react to ITS OWN `:hover`/`:active`/`:focus-visible`/`[disabled]` by tinting
7779
8143
  * `--et-theme-color-primary-*`/`-on-primary-*`/`-ink-*` with the current color theme. Use this on
7780
- * a single, self-contained interactive element — a button, a checkbox, a segmented button.
8144
+ * a single, self-contained interactive element - a button, a checkbox, a segmented button.
7781
8145
  *
7782
8146
  * Do NOT apply this to a wrapper that also contains OTHER, independently-interactive elements
7783
8147
  * (e.g. a toolbar with its own buttons, or a form-field's control slot). These are ordinary
7784
8148
  * inheriting CSS custom properties, so a descendant reading them for its own cosmetics (a caret
7785
8149
  * color, a link color) gets swept along whenever ANYTHING inside the wrapper is hovered or
7786
- * clicked — not just when the wrapper itself is the thing being interacted with. That exact bug
8150
+ * clicked - not just when the wrapper itself is the thing being interacted with. That exact bug
7787
8151
  * shipped once already; see ColorInteractiveContainerDirective/ColorInteractiveHasFocusDirective
7788
8152
  * for the shapes that avoid it.
7789
8153
  */
@@ -7811,19 +8175,23 @@ const SURFACE_TYPE = {
7811
8175
  };
7812
8176
  const createCssSurfaceName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
7813
8177
  const resolveSurfaceByElevation = (themes, type, elevation) => themes.find((t) => t.type === type && t.elevation === elevation) ?? null;
7814
- const [ɵProvideSurfaceThemes, injectSurfaceThemes] = createStaticProvider(undefined, {
8178
+ const SURFACE_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
7815
8179
  name: 'Surface Themes',
7816
8180
  });
7817
- const [ɵProvideSurfaceThemesPrefix, injectSurfaceThemesPrefix] = createStaticProvider('et', {
8181
+ const ɵProvideSurfaceThemes = /* @__PURE__ */ toProvideFn(SURFACE_THEMES_DEF);
8182
+ const injectSurfaceThemes = /* @__PURE__ */ toInjectFn(SURFACE_THEMES_DEF);
8183
+ const SURFACE_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
7818
8184
  name: 'Surface Themes Prefix',
7819
8185
  });
8186
+ const ɵProvideSurfaceThemesPrefix = /* @__PURE__ */ toProvideFn(SURFACE_THEMES_PREFIX_DEF);
8187
+ const injectSurfaceThemesPrefix = /* @__PURE__ */ toInjectFn(SURFACE_THEMES_PREFIX_DEF);
7820
8188
  const provideSurfaceThemesWithTailwind4 = (themes, prefix = 'et') => [
7821
8189
  ɵProvideSurfaceThemes(themes),
7822
8190
  ɵProvideSurfaceThemesPrefix(prefix),
7823
8191
  ];
7824
8192
 
7825
8193
  const SURFACE_PROVIDER = new InjectionToken('SurfaceProvider');
7826
- const FORCED_SURFACE_UNSET = Symbol('FORCED_SURFACE_UNSET');
8194
+ const FORCED_SURFACE_UNSET = /* @__PURE__ */ Symbol('FORCED_SURFACE_UNSET');
7827
8195
  class ProvideSurfaceDirective {
7828
8196
  constructor() {
7829
8197
  this.themes = injectSurfaceThemes({ optional: true });
@@ -7922,7 +8290,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
7922
8290
  }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "etProvideSurface", required: false }] }] } });
7923
8291
 
7924
8292
  let uniqueId = 0;
7925
- const [provideSurfaceContextTracker, injectSurfaceContextTracker] = createRootProvider(() => {
8293
+ const SURFACE_CONTEXT_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
7926
8294
  const entries = signal([], /* @ts-ignore */
7927
8295
  ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
7928
8296
  const topEntry = computed(() => {
@@ -7955,12 +8323,14 @@ const [provideSurfaceContextTracker, injectSurfaceContextTracker] = createRootPr
7955
8323
  };
7956
8324
  return { topType, topElevation, register, surfaceForElement };
7957
8325
  }, { name: 'SurfaceContextTracker' });
8326
+ const provideSurfaceContextTracker = /* @__PURE__ */ toProvideFn(SURFACE_CONTEXT_TRACKER_DEF);
8327
+ const injectSurfaceContextTracker = /* @__PURE__ */ toInjectFn(SURFACE_CONTEXT_TRACKER_DEF);
7958
8328
 
7959
8329
  class AutoSurfaceDirective {
7960
8330
  /**
7961
8331
  * Mark this auto-surface as its overlay's *own painted surface*. An overlay *panel*
7962
8332
  * (menu, select/date/cascader panel, tooltip, toggletip, rich-text-editor popups) is
7963
- * the overlay's surface, so it must paint the overlay's registered elevation exactly —
8333
+ * the overlay's surface, so it must paint the overlay's registered elevation exactly -
7964
8334
  * not stack a level above a parent surface. The elevation is read from the surface-context
7965
8335
  * tracker (the pane it renders into), which is authoritative across the portal boundary;
7966
8336
  * the panel's own declaration injector points back at the trigger location and cannot be
@@ -7988,7 +8358,7 @@ class AutoSurfaceDirective {
7988
8358
  ...(ngDevMode ? [{ debugName: "isOverlaySurface" }] : /* istanbul ignore next */ []));
7989
8359
  // Bumped by the afterEveryRender watcher in the constructor whenever this element's overlay
7990
8360
  // containment changes. surfaceForElement() below does a non-reactive element.contains() read,
7991
- // so the computed must be re-triggered when this element is (re)grafted into an overlay pane —
8361
+ // so the computed must be re-triggered when this element is (re)grafted into an overlay pane -
7992
8362
  // otherwise a value cached from before the graft (one elevation too low) sticks forever.
7993
8363
  this.domSettleTick = signal(0, /* @ts-ignore */
7994
8364
  ...(ngDevMode ? [{ debugName: "domSettleTick" }] : /* istanbul ignore next */ []));
@@ -8004,7 +8374,7 @@ class AutoSurfaceDirective {
8004
8374
  const contextElevation = contextProvider?.elevation() ?? null;
8005
8375
  const contextType = contextProvider?.surfaceType() ?? null;
8006
8376
  // An overlay's projected/portaled content keeps the injector of where it was
8007
- // *declared* (the trigger location), not the pane it renders into — so the
8377
+ // *declared* (the trigger location), not the pane it renders into - so the
8008
8378
  // injector-derived parent surface is one elevation too low. The surface-context
8009
8379
  // tracker records each open overlay's surface *and* its pane element, so we consult
8010
8380
  // only the overlay whose pane actually contains this element in the DOM (portaling
@@ -8015,7 +8385,7 @@ class AutoSurfaceDirective {
8015
8385
  const overlayType = overlaySurface?.type ?? null;
8016
8386
  // A panel that IS its overlay's own surface (see matchOverlaySurface) must paint exactly the
8017
8387
  // overlay's elevation: the overlay container already resolved it (one above the trigger) and
8018
- // registered it in the tracker. It must NOT re-derive from its declaration injector — that
8388
+ // registered it in the tracker. It must NOT re-derive from its declaration injector - that
8019
8389
  // points back at the trigger location and disagrees with the pane whenever the trigger itself
8020
8390
  // sits on a surface, which would double-elevate the panel relative to its own pane.
8021
8391
  if (this.isOverlaySurface()) {
@@ -8048,7 +8418,7 @@ class AutoSurfaceDirective {
8048
8418
  ...(ngDevMode ? [{ debugName: "resolvedSurface" }] : /* istanbul ignore next */ []));
8049
8419
  // Projected/portaled auto-surface content (e.g. a select option's avatar) can have its
8050
8420
  // resolvedSurface computed run *before* it is grafted into the overlay pane it visually lives
8051
- // in — or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
8421
+ // in - or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
8052
8422
  // surfaceForElement()'s element.contains() check then returns the wrong (or no) overlay, so it
8053
8423
  // resolves off the declaration injector (the trigger location) one elevation too low. That check
8054
8424
  // is a plain DOM read, not a signal, so nothing re-runs the computed once the element reaches its
@@ -8065,14 +8435,13 @@ class AutoSurfaceDirective {
8065
8435
  this.domSettleTick.update((v) => v + 1);
8066
8436
  }
8067
8437
  else if (el.isConnected && ++stableRenders >= 2) {
8068
- // containment has held for a couple of renders and the element is in the DOM — done moving
8438
+ // containment has held for a couple of renders and the element is in the DOM - done moving
8069
8439
  settleWatcher.destroy();
8070
8440
  }
8071
8441
  });
8072
8442
  effect(() => {
8073
8443
  const surface = this.resolvedSurface();
8074
8444
  untracked(() => {
8075
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8076
8445
  setInputSignal(this.ownSurfaceProvider.surface, surface);
8077
8446
  });
8078
8447
  });
@@ -8089,12 +8458,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
8089
8458
  }], ctorParameters: () => [], propDecorators: { surfaceProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceProvider", required: false }] }] } });
8090
8459
 
8091
8460
  const createCssColorThemeName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
8092
- const [ɵProvideColorThemes, injectColorThemes] = createStaticProvider(undefined, {
8461
+ const COLOR_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
8093
8462
  name: 'Color Themes',
8094
8463
  });
8095
- const [ɵProvideColorThemesPrefix, injectColorThemesPrefix] = createStaticProvider('et', {
8464
+ const ɵProvideColorThemes = /* @__PURE__ */ toProvideFn(COLOR_THEMES_DEF);
8465
+ const injectColorThemes = /* @__PURE__ */ toInjectFn(COLOR_THEMES_DEF);
8466
+ const COLOR_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
8096
8467
  name: 'Themes Prefix',
8097
8468
  });
8469
+ const ɵProvideColorThemesPrefix = /* @__PURE__ */ toProvideFn(COLOR_THEMES_PREFIX_DEF);
8470
+ const injectColorThemesPrefix = /* @__PURE__ */ toInjectFn(COLOR_THEMES_PREFIX_DEF);
8098
8471
  const provideColorThemesWithTailwind4 = (themes, prefix = 'et') => [
8099
8472
  ɵProvideColorThemes(themes),
8100
8473
  ɵProvideColorThemesPrefix(prefix),
@@ -8313,7 +8686,7 @@ const COLOR_PROVIDER = new InjectionToken('ColorProvider');
8313
8686
  * The color provider attached to a bootstrapped root component (e.g. `ProvideColorDirective` in
8314
8687
  * the app component's `hostDirectives`), if any. Detached DOM such as overlay panes can't reach
8315
8688
  * it through element DI, so overlay containers fall back to this when resolving the provider to
8316
- * `syncWithProvider()` — resolve at sync time, not at injection time, since root components only
8689
+ * `syncWithProvider()` - resolve at sync time, not at injection time, since root components only
8317
8690
  * register with `ApplicationRef` once bootstrap completes.
8318
8691
  *
8319
8692
  * @internal
@@ -8327,7 +8700,7 @@ const resolveAppRootColorProvider = (appRef) => {
8327
8700
  }
8328
8701
  return null;
8329
8702
  };
8330
- const FORCED_COLOR_UNSET = Symbol('FORCED_COLOR_UNSET');
8703
+ const FORCED_COLOR_UNSET = /* @__PURE__ */ Symbol('FORCED_COLOR_UNSET');
8331
8704
  class ProvideColorDirective {
8332
8705
  constructor() {
8333
8706
  this.themes = injectColorThemes({ optional: true });
@@ -8347,8 +8720,8 @@ class ProvideColorDirective {
8347
8720
  }, /* @ts-ignore */
8348
8721
  ...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
8349
8722
  /**
8350
- * The color that actually applies at this provider's location: its own effective color, or —
8351
- * when this provider is passive (no input, nothing forced) — the nearest ancestor provider's.
8723
+ * The color that actually applies at this provider's location: its own effective color, or -
8724
+ * when this provider is passive (no input, nothing forced) - the nearest ancestor provider's.
8352
8725
  * Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
8353
8726
  * consumers that re-apply context across detached DOM (overlay panes) must use this instead
8354
8727
  * of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
@@ -8441,14 +8814,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
8441
8814
  }] });
8442
8815
 
8443
8816
  /**
8444
- * Same shape as `ColorInteractiveDirective` — reacts to its own `:hover`/`:focus-visible`/
8445
- * `:active`/`[disabled]` — but drives `--et-surface-interaction-*`, a neutral/muted tint, instead
8817
+ * Same shape as `ColorInteractiveDirective` - reacts to its own `:hover`/`:focus-visible`/
8818
+ * `:active`/`[disabled]` - but drives `--et-surface-interaction-*`, a neutral/muted tint, instead
8446
8819
  * of the ambient color theme. Use it for chrome-level interaction feedback that shouldn't borrow
8447
8820
  * the page's brand color (a tab trigger's hover state, a toggle button that should look neutral
8448
- * until it's actually active — see `ButtonDirective`'s `mutedUntilPressed`).
8821
+ * until it's actually active - see `ButtonDirective`'s `mutedUntilPressed`).
8449
8822
  *
8450
8823
  * Has no container/exclude/has-focus variants: it has no descendant-cascading CSS at all, so
8451
- * applying it to a wrapper with independently-interactive children is safe by construction —
8824
+ * applying it to a wrapper with independently-interactive children is safe by construction -
8452
8825
  * unlike `ColorInteractiveDirective`, there's no bubbling-leak failure mode to worry about here.
8453
8826
  */
8454
8827
  class SurfaceInteractiveDirective {
@@ -8469,6 +8842,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
8469
8842
  }]
8470
8843
  }], ctorParameters: () => [] });
8471
8844
 
8845
+ const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() => {
8846
+ const registrations = new Set();
8847
+ const _isCheckPending = signal(false, /* @ts-ignore */
8848
+ ...(ngDevMode ? [{ debugName: "_isCheckPending" }] : /* istanbul ignore next */ []));
8849
+ let pending = null;
8850
+ /** @internal Runs a tracker's confirm under the single-flight + abandon regime. */
8851
+ const runCheck = (runner) => {
8852
+ // A confirm is already on screen - adopt its answer rather than opening a second dialog.
8853
+ if (pending) {
8854
+ return pending.promise;
8855
+ }
8856
+ const controller = new AbortController();
8857
+ let settle;
8858
+ const abandoned = new Promise((resolve) => (settle = resolve));
8859
+ // Run the confirm synchronously - a dialog that opens a microtask later can miss a `signal` that
8860
+ // already aborted, and callers reasonably expect the dialog to be up when `runCheck()` returns.
8861
+ let confirmed;
8862
+ try {
8863
+ confirmed = runner({ signal: controller.signal }).catch(() => false);
8864
+ }
8865
+ catch {
8866
+ confirmed = Promise.resolve(false);
8867
+ }
8868
+ // Whichever settles first wins: a late answer from an abandoned confirm is ignored.
8869
+ const promise = Promise.race([confirmed, abandoned]).then((result) => {
8870
+ pending = null;
8871
+ _isCheckPending.set(false);
8872
+ return result;
8873
+ });
8874
+ pending = { promise, controller, settle };
8875
+ _isCheckPending.set(true);
8876
+ return promise;
8877
+ };
8878
+ /** @internal */
8879
+ const register = (registration) => {
8880
+ registrations.add(registration);
8881
+ return () => registrations.delete(registration);
8882
+ };
8883
+ const abandonAll = (reason = 'session-end') => {
8884
+ if (pending) {
8885
+ pending.controller.abort(reason);
8886
+ // The changes are unrecoverable at this point, so the discard is allowed: whatever waited on
8887
+ // this check (an overlay close, a `canDeactivate`) proceeds instead of hanging.
8888
+ pending.settle(true);
8889
+ }
8890
+ for (const registration of Array.from(registrations)) {
8891
+ registration.abandon(reason);
8892
+ }
8893
+ };
8894
+ return {
8895
+ /** Whether a confirm is currently on screen. */
8896
+ isCheckPending: _isCheckPending.asReadonly(),
8897
+ abandonAll,
8898
+ runCheck,
8899
+ register,
8900
+ };
8901
+ }, { name: 'Unsaved Changes Coordinator' });
8902
+ /**
8903
+ * App-wide coordination for the unsaved-changes family. Two jobs:
8904
+ *
8905
+ * - **One confirm at a time.** A page form, an overlay form and a route guard can all want a decision
8906
+ * at once; stacking three "discard your changes?" dialogs is never the right answer. A check that
8907
+ * starts while another is pending adopts the pending decision instead of asking again.
8908
+ * - **Abandoning guards when the session ends.** `abandonAll()` resolves the pending confirm, tells
8909
+ * its dialog to close (via {@link UnsavedChangesConfirmContext.signal}), and switches every live
8910
+ * guard off: further checks pass, and the tab locks release. Called automatically by
8911
+ * `@ethlete/query`'s auth provider on logout - the edits cannot be saved anymore, so guarding them
8912
+ * only strands dialogs over the login page and blocks the tab.
8913
+ *
8914
+ * Root-provided; every tracker registers itself. Inject it to abandon guards from app code (a session
8915
+ * timeout of your own, a hard reset) or to read whether a confirm is currently on screen.
8916
+ */
8917
+ const provideUnsavedChangesCoordinator = /* @__PURE__ */ toProvideFn(UNSAVED_CHANGES_COORDINATOR_DEF);
8918
+ const injectUnsavedChangesCoordinator = /* @__PURE__ */ toInjectFn(UNSAVED_CHANGES_COORDINATOR_DEF);
8919
+
8472
8920
  const isAbstractControl = (value) => !!value &&
8473
8921
  typeof value === 'object' &&
8474
8922
  typeof value.getRawValue === 'function' &&
@@ -8484,13 +8932,13 @@ const normalizeUnsavedChangesSource = (source) => {
8484
8932
  if (isFieldTree(source)) {
8485
8933
  return {
8486
8934
  value: computed(() => source().value()),
8487
- // FieldTree's value type is a generic conditional that TS can't narrow here — cast the writer.
8935
+ // FieldTree's value type is a generic conditional that TS can't narrow here - cast the writer.
8488
8936
  setValue: (next) => source().value.set(next),
8489
8937
  };
8490
8938
  }
8491
8939
  if (isSignal(source)) {
8492
8940
  const peek = untracked(source);
8493
- // A signal that currently holds a FieldTree — or nothing yet (the late/async form case).
8941
+ // A signal that currently holds a FieldTree - or nothing yet (the late/async form case).
8494
8942
  if (peek === null || peek === undefined || isFieldTree(peek)) {
8495
8943
  const fieldSignal = source;
8496
8944
  return {
@@ -8515,6 +8963,179 @@ const normalizeUnsavedChangesSource = (source) => {
8515
8963
  'an AbstractControl, or a WritableSignal.');
8516
8964
  };
8517
8965
 
8966
+ /** The default tab title marker - a filled dot, prefixed while there are unsaved changes. */
8967
+ const UNSAVED_CHANGES_TITLE_MARKER = '●';
8968
+ // The badge is a single app-wide surface, so every lock that wants one registers here and the badge
8969
+ // is derived from all of them - otherwise the first tracker to go clean would clear a badge that
8970
+ // another one still needs.
8971
+ const BADGE_HOLDERS = /* @__PURE__ */ new Map();
8972
+ const syncAppBadge = () => {
8973
+ const nav = typeof navigator === 'undefined' ? null : navigator;
8974
+ if (!nav?.setAppBadge || !nav.clearAppBadge) {
8975
+ return;
8976
+ }
8977
+ // Unsupported, or not installed - the promise rejects instead of throwing synchronously.
8978
+ const ignore = () => undefined;
8979
+ if (!BADGE_HOLDERS.size) {
8980
+ nav.clearAppBadge().catch(ignore);
8981
+ return;
8982
+ }
8983
+ const count = Array.from(BADGE_HOLDERS.values()).reduce((total, contents) => (typeof contents === 'number' ? total + contents : total), 0);
8984
+ // `setAppBadge()` without a count renders a plain dot.
8985
+ nav.setAppBadge(count || undefined).catch(ignore);
8986
+ };
8987
+ /**
8988
+ * The browser-tab flavor of the unsaved-changes family: while `hasChanges` reads `true`, the tab
8989
+ * cannot be closed or reloaded without the browser's confirm prompt, and - opt in - the tab title
8990
+ * carries a marker (optionally blinking), the favicon carries a dot, and the installed app carries a
8991
+ * badge.
8992
+ *
8993
+ * Every {@link createUnsavedChangesTracker} sets this up automatically (lock only); reach for it
8994
+ * directly when you track "unsaved" state that isn't a form, or want to lock the tab from a signal
8995
+ * you already have. Call from an injection context.
8996
+ *
8997
+ * ```ts
8998
+ * createUnsavedChangesTabLock({
8999
+ * hasChanges: computed(() => this.uploads().some((u) => u.pending)),
9000
+ * titleMarker: true,
9001
+ * flash: true,
9002
+ * });
9003
+ * ```
9004
+ */
9005
+ const createUnsavedChangesTabLock = (config) => {
9006
+ assertInInjectionContext(createUnsavedChangesTabLock);
9007
+ const { hasChanges } = config;
9008
+ const lock = config.lock ?? true;
9009
+ const flash = config.flash === true ? {} : config.flash || null;
9010
+ const favicon = config.favicon === true ? {} : config.favicon || null;
9011
+ const badge = config.badge ?? false;
9012
+ // Blinking needs something to blink, so it implies the default marker.
9013
+ const titleMarker = config.titleMarker === true
9014
+ ? UNSAVED_CHANGES_TITLE_MARKER
9015
+ : (config.titleMarker ?? (flash && !favicon ? UNSAVED_CHANGES_TITLE_MARKER : null));
9016
+ const destroyRef = inject(DestroyRef);
9017
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
9018
+ const releaseFns = [];
9019
+ // The blink phase. Always `true` unless flashing is on, so the marker/favicon effects below can read
9020
+ // one signal either way.
9021
+ const flashPhase = signal(true, /* @ts-ignore */
9022
+ ...(ngDevMode ? [{ debugName: "flashPhase" }] : /* istanbul ignore next */ []));
9023
+ if (isBrowser && flash) {
9024
+ const interval = flash.interval ?? 1000;
9025
+ const whenHidden = flash.whenHidden ?? true;
9026
+ const isDocumentVisible = injectIsDocumentVisible();
9027
+ const shouldFlash = computed(() => hasChanges() && (!whenHidden || !isDocumentVisible()), /* @ts-ignore */
9028
+ ...(ngDevMode ? [{ debugName: "shouldFlash" }] : /* istanbul ignore next */ []));
9029
+ const flashEffect = effect((onCleanup) => {
9030
+ if (!shouldFlash()) {
9031
+ return;
9032
+ }
9033
+ const handle = setInterval(() => flashPhase.update((on) => !on), interval);
9034
+ onCleanup(() => {
9035
+ clearInterval(handle);
9036
+ flashPhase.set(true);
9037
+ });
9038
+ }, /* @ts-ignore */
9039
+ ...(ngDevMode ? [{ debugName: "flashEffect" }] : /* istanbul ignore next */ []));
9040
+ releaseFns.push(() => {
9041
+ flashEffect.destroy();
9042
+ flashPhase.set(true);
9043
+ });
9044
+ }
9045
+ /** Dirty, and in the "shown" half of a blink. */
9046
+ const isMarked = computed(() => hasChanges() && flashPhase(), /* @ts-ignore */
9047
+ ...(ngDevMode ? [{ debugName: "isMarked" }] : /* istanbul ignore next */ []));
9048
+ if (titleMarker) {
9049
+ const titleStore = injectTitleStore();
9050
+ const markerId = Symbol('unsaved-changes-title-marker');
9051
+ const markerEffect = effect(() => {
9052
+ const marked = isMarked();
9053
+ untracked(() => (marked ? titleStore.addMarker(markerId, titleMarker) : titleStore.removeMarker(markerId)));
9054
+ }, /* @ts-ignore */
9055
+ ...(ngDevMode ? [{ debugName: "markerEffect" }] : /* istanbul ignore next */ []));
9056
+ releaseFns.push(() => {
9057
+ markerEffect.destroy();
9058
+ titleStore.removeMarker(markerId);
9059
+ });
9060
+ }
9061
+ if (isBrowser && favicon) {
9062
+ const faviconStore = injectFaviconStore();
9063
+ const overlayId = Symbol('unsaved-changes-favicon');
9064
+ const faviconEffect = effect(() => {
9065
+ const marked = isMarked();
9066
+ untracked(() => marked
9067
+ ? faviconStore.addOverlay(overlayId, { kind: 'dot', color: favicon.color })
9068
+ : faviconStore.removeOverlay(overlayId));
9069
+ }, /* @ts-ignore */
9070
+ ...(ngDevMode ? [{ debugName: "faviconEffect" }] : /* istanbul ignore next */ []));
9071
+ releaseFns.push(() => {
9072
+ faviconEffect.destroy();
9073
+ faviconStore.removeOverlay(overlayId);
9074
+ });
9075
+ }
9076
+ if (isBrowser && lock) {
9077
+ const onBeforeUnload = (event) => {
9078
+ event.preventDefault();
9079
+ // Legacy Chrome / Safari gate the prompt on a truthy `returnValue` rather than on
9080
+ // `preventDefault()`. Deprecated, but still the only way to reach those engines.
9081
+ event.returnValue = true;
9082
+ };
9083
+ let attached = false;
9084
+ const attach = () => {
9085
+ if (attached)
9086
+ return;
9087
+ attached = true;
9088
+ window.addEventListener('beforeunload', onBeforeUnload);
9089
+ };
9090
+ const detach = () => {
9091
+ if (!attached)
9092
+ return;
9093
+ attached = false;
9094
+ window.removeEventListener('beforeunload', onBeforeUnload);
9095
+ };
9096
+ // Attached only while there are changes: a permanently registered `beforeunload` listener makes
9097
+ // the page ineligible for the back/forward cache, which would slow every back navigation.
9098
+ const lockEffect = effect(() => {
9099
+ const dirty = hasChanges();
9100
+ untracked(() => (dirty ? attach() : detach()));
9101
+ }, /* @ts-ignore */
9102
+ ...(ngDevMode ? [{ debugName: "lockEffect" }] : /* istanbul ignore next */ []));
9103
+ releaseFns.push(() => {
9104
+ lockEffect.destroy();
9105
+ detach();
9106
+ });
9107
+ }
9108
+ if (isBrowser && badge !== false) {
9109
+ const badgeId = Symbol('unsaved-changes-badge');
9110
+ const contents = badge === true ? true : badge;
9111
+ const badgeEffect = effect(() => {
9112
+ const dirty = hasChanges();
9113
+ untracked(() => {
9114
+ if (dirty) {
9115
+ BADGE_HOLDERS.set(badgeId, contents);
9116
+ }
9117
+ else {
9118
+ BADGE_HOLDERS.delete(badgeId);
9119
+ }
9120
+ syncAppBadge();
9121
+ });
9122
+ }, /* @ts-ignore */
9123
+ ...(ngDevMode ? [{ debugName: "badgeEffect" }] : /* istanbul ignore next */ []));
9124
+ releaseFns.push(() => {
9125
+ badgeEffect.destroy();
9126
+ BADGE_HOLDERS.delete(badgeId);
9127
+ syncAppBadge();
9128
+ });
9129
+ }
9130
+ const destroy = () => {
9131
+ while (releaseFns.length) {
9132
+ releaseFns.pop()?.();
9133
+ }
9134
+ };
9135
+ destroyRef.onDestroy(destroy);
9136
+ return { destroy };
9137
+ };
9138
+
8518
9139
  const toBooleanPromise = (result) => {
8519
9140
  if (result instanceof Promise) {
8520
9141
  return result.then(Boolean);
@@ -8529,8 +9150,15 @@ const toBooleanPromise = (result) => {
8529
9150
  * whether the watched form/value differs from it, and runs an async confirm before a discard.
8530
9151
  * Call from an injection context (a field initializer or constructor).
8531
9152
  *
9153
+ * While there are changes the browser tab is locked too (`beforeunload`), so closing or reloading the
9154
+ * tab needs a confirmation - see the `tab` config for the title-marker / app-badge extras and for
9155
+ * opting out.
9156
+ *
9157
+ * Every tracker registers with the app-wide {@link injectUnsavedChangesCoordinator}, which keeps a
9158
+ * single confirm on screen at a time and switches all guards off when the session ends (logout).
9159
+ *
8532
9160
  * Overlay and router flavors (`createOverlayUnsavedChangesGuard`, `createUnsavedChangesGuard`) build
8533
- * on this — use them when you want the guard wired to a close/navigation event automatically.
9161
+ * on this - use them when you want the guard wired to a close/navigation event automatically.
8534
9162
  */
8535
9163
  const createUnsavedChangesTracker = (config) => {
8536
9164
  assertInInjectionContext(createUnsavedChangesTracker);
@@ -8565,11 +9193,23 @@ const createUnsavedChangesTracker = (config) => {
8565
9193
  return !(compareFn ? compareFn(current, defaultValue) : equal(current, defaultValue));
8566
9194
  }, /* @ts-ignore */
8567
9195
  ...(ngDevMode ? [{ debugName: "hasChanges" }] : /* istanbul ignore next */ []));
9196
+ const tab = config.tab === false ? null : createUnsavedChangesTabLock({ ...config.tab, hasChanges });
9197
+ const coordinator = injectUnsavedChangesCoordinator();
9198
+ const _isAbandoned = signal(false, /* @ts-ignore */
9199
+ ...(ngDevMode ? [{ debugName: "_isAbandoned" }] : /* istanbul ignore next */ []));
9200
+ const unregister = coordinator.register({
9201
+ abandon: () => {
9202
+ _isAbandoned.set(true);
9203
+ // The session is gone: stop holding the tab hostage over edits that can no longer be saved.
9204
+ tab?.destroy();
9205
+ },
9206
+ });
9207
+ inject(DestroyRef).onDestroy(unregister);
8568
9208
  const runCheck = () => {
8569
- if (!untracked(hasChanges)) {
9209
+ if (untracked(_isAbandoned) || !untracked(hasChanges)) {
8570
9210
  return Promise.resolve(true);
8571
9211
  }
8572
- return toBooleanPromise(confirm(untracked(normalized.value)));
9212
+ return coordinator.runCheck((context) => toBooleanPromise(confirm(untracked(normalized.value), context)));
8573
9213
  };
8574
9214
  const refreshDefaultValue = () => _defaultValue.set(untracked(normalized.value));
8575
9215
  const restoreDefaultValue = () => {
@@ -8580,10 +9220,12 @@ const createUnsavedChangesTracker = (config) => {
8580
9220
  };
8581
9221
  return {
8582
9222
  hasChanges,
9223
+ isAbandoned: _isAbandoned.asReadonly(),
8583
9224
  defaultValue: _defaultValue.asReadonly(),
8584
9225
  runCheck,
8585
9226
  refreshDefaultValue,
8586
9227
  restoreDefaultValue,
9228
+ tab,
8587
9229
  };
8588
9230
  };
8589
9231
 
@@ -8606,5 +9248,5 @@ const createUnsavedChangesGuard = (config) => {
8606
9248
  * Generated bundle index. Do not edit.
8607
9249
  */
8608
9250
 
8609
- export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective, AnimatedOverlayDirective, AutoSurfaceDirective, BOUNDARY_ELEMENT_TOKEN, BREAKPOINT_INSTANCE_TOKEN, BREAKPOINT_ORDER, COLOR_PROVIDER, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, 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, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MUST_MATCH, MarkdownToHtmlPipe, MustMatch, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, PropsDirective, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RuntimeError, SEO_DIRECTIVE_TOKEN, SURFACE_PROVIDER, SURFACE_TYPE, SWIPE_VELOCITY_WINDOW_MS, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SeoDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, ValidateAtLeastOneRequired, Validators, animationDebugLog, applyAlternateBinding, applyAlternateLanguagesBindings, applyArticleBindings, applyAuthorBinding, applyCanonicalBinding, applyCenteredPosition, applyDescriptionBinding, applyGlobalPosition, applyHeadBinding, applyHeadTitleBinding, applyHostListener, applyHostListeners, applyInitialFocus, applyKeywordsBinding, applyLinkBinding, applyMetaBinding, applyNextBinding, applyOgBinding, applyOpenGraphBindings, applyPrevBinding, applyResourceHintsBindings, applyRobotsBinding, applySocialMediaBindings, applyStructuredDataBinding, applyTwitterCardBindings, areScrollStatesEqual, bindProps, boolBreakpointTransform, booleanBreakpointAttribute, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, copyToClipboard, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createBulkPropertyBinding, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDependencyStash, createDestroy, createDocumentElementSignal, createElementDictionary, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createHostProps, createIsRenderedSignal, createLabels, createLogger, createOverlayRuntimeRef, createPropHandlers, createPropertyBinding, createProps, createProvider, createRootProvider, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSetup, createStaticProvider, createStaticRootProvider, createSwatchCss, createSwipeTracker, createTailwindColorThemes, createTailwindCssVar, createTailwindRgbVar, createThemeStyle, createUnsavedChangesGuard, createUnsavedChangesTracker, createUserConsentProvider, deferredSignal, deleteCookie, dragGestureFrom, easeElastic, easeIn, easeInOut, easeLinear, easeOut, easeOutBack, easeOutBackStrong, elementCanScroll, equal, 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, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFocusVisibleTracker, injectFragment, injectHasPrecisionInput, injectHasTouchInput, injectHostElement, injectIs2Xl, injectIsDocumentVisible, injectIsLandscape, injectIsLg, injectIsMd, injectIsPortrait, injectIsRouterInitialized, injectIsSm, injectIsXl, injectIsXs, injectLinkStore, injectLinkStoreConfig, injectLocale, injectMediaQueryIsMatched, injectMetaConfig, injectMetaStore, injectObserveBreakpoint, injectObserveMediaQuery, injectOverlayRuntime, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterNavigationState, injectRouterState, injectScrollbarDimensions, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isKnockoutMatch, isObject, markdownToHtml, matchesReducedMotion, maybeSignalValue, memoizeSignal, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, normalizeUnsavedChangesSource, numberBreakpointAttribute, numberBreakpointTransform, previousSignalValue, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorThemes, provideColorThemesWithTailwind4, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideViewportConfig, randomId, readFromClipboard, resetPositioningStyles, resolveAppRootColorProvider, resolveSurfaceByElevation, round, routerDisableScrollTop, scrollToElement, setBackdropStyles, setBaseElementStyles, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, switchQueryListChanges, syncSignal, templateComputed, toArray, toArrayTrackByFn, toStringBinding, transformOrReturn, typedBreakpointTransform, unbindProps, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
9251
+ 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_PROVIDER, ClickOutsideDirective, ColorInteractiveContainerDirective, ColorInteractiveDirective, ColorInteractiveExcludeDirective, ColorInteractiveHasFocusDirective, DEFAULT_MULTI_INSTANCE_RELS, DEFAULT_MULTI_INSTANCE_TAGS, 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, FOCUSABLE_SELECTOR, FOCUS_VISIBLE_TRACKER_TOKEN, HtmlToMarkdownPipe, IS_ARRAY_NOT_EMPTY, IS_EMAIL, InferMimeTypePipe, InitialsPipe, IsArrayNotEmpty, IsEmail, jsonLd as JsonLD, KeyPressManager, MUST_MATCH, MarkdownToHtmlPipe, MustMatch, NOTIFICATION_CLICK_MESSAGE_TYPE, NormalizeGameResultTypePipe, NormalizeMatchParticipantsPipe, NormalizeMatchScorePipe, NormalizeMatchStatePipe, NormalizeMatchTypePipe, PropsDirective, ProvideColorDirective, ProvideSurfaceDirective, RUNTIME_ERROR_NO_DATA, RepeatDirective, ResizeHandlesComponent, RuntimeError, SEO_DIRECTIVE_TOKEN, SURFACE_PROVIDER, SURFACE_TYPE, SWIPE_VELOCITY_WINDOW_MS, ScrollObserverDirective, ScrollObserverEndDirective, ScrollObserverStartDirective, SeoDirective, SlugifyPipe, StructuredDataComponent, SurfaceInteractiveDirective, ToArrayPipe, TypedQueryList, UNSAVED_CHANGES_TITLE_MARKER, ValidateAtLeastOneRequired, Validators, 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, areScrollStatesEqual, bindProps, boolBreakpointTransform, booleanBreakpointAttribute, boundingClientRectToElementRect, breakpointTransformBase, buildElementSignal, buildSignalEffects, canUseSessionMemory, clamp, clone, cloneFormGroup, computedTillFalsy, computedTillTruthy, controlValueSignal, controlValueSignalWithPrevious, copyToClipboard, createAnchoredPositionCleanup, createArrayPropertyBinding, createAutoSessionMemoryKey, createBulkPropertyBinding, createCanAnimateSignal, createComponentId, createCssColorThemeName, createCssSurfaceName, createDependencyStash, createDestroy, createDocumentElementSignal, createElementDictionary, createElementDimensions, createEmptyElementSignal, createFlipAnimation, createFlipAnimationGroup, createHostProps, createIsRenderedSignal, createLogger, createOverlayRuntimeRef, createPropHandlers, createPropertyBinding, createProps, createRootThemeCss, createRoute, createRouterState, createRxHostListener, createSessionMemory, createSetup, 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, 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, injectBoundaryElement, injectBreakpointInput, injectBreakpointIsMatched, injectBreakpointObserver, injectCanHover, injectColorThemes, injectColorThemesPrefix, injectCurrentBreakpoint, injectDeviceInputType, injectDisplayOrientation, injectErrorTheme, injectFaviconStore, 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, injectPathParam, injectPathParamChanges, injectPathParams, injectPrefersReducedMotion, injectQueryParam, injectQueryParamChanges, injectQueryParams, injectRenderer, injectRoute, injectRouteData, injectRouteDataItem, injectRouteTitle, injectRouterEvent, injectRouterNavigationState, injectRouterState, injectScrollbarDimensions, injectStructuredDataConfig, injectStructuredDataStore, injectStyleManager, injectSuccessTheme, injectSurfaceContextTracker, injectSurfaceThemes, injectSurfaceThemesPrefix, injectTemplateRef, injectTitleConfig, injectTitleStore, injectUnsavedChangesCoordinator, injectUrl, injectViewportConfig, injectViewportDimensions, injectViewportSize, injectWarningTheme, isAnimationDebugEnabled, isArray, isElementSignal, isElementVisible, isFocusable, isGroupMatch, isHTMLElement, isKnockoutMatch, isObject, markdownToHtml, matchesReducedMotion, maybeSignalValue, memoizeSignal, nextFrame, normalizeGameResultType, normalizeMatchParticipant, normalizeMatchParticipants, normalizeMatchScore, normalizeMatchState, normalizeMatchType, normalizeUnsavedChangesSource, numberBreakpointAttribute, numberBreakpointTransform, previousSignalValue, provideBoundaryElement, provideBreakpointInstance, provideBreakpointObserver, provideColorThemes, provideColorThemesWithTailwind4, provideFaviconStore, provideFocusVisibleTracker, provideLinkStore, provideLinkStoreConfig, provideLocale, provideMetaConfig, provideMetaStore, provideNotifications, provideOverlayRuntime, provideRenderer, provideStructuredDataConfig, provideStructuredDataStore, provideStyleManager, provideSurfaceContextTracker, provideSurfaceThemesWithTailwind4, provideTitleConfig, provideTitleStore, provideUnsavedChangesCoordinator, provideViewportConfig, randomId, readFromClipboard, registerAnchoredPositionMiddlewareExtras, registerAnchoredPositionSetup, resetPositioningStyles, resolveAppRootColorProvider, resolveSurfaceByElevation, round, routerDisableScrollTop, scrollToElement, setBackdropStyles, setBaseElementStyles, setCookie, setInputSignal, setupFocusTrap, setupPositioning, setupScrollRestoration, signalAnimatedNumber, signalAttributes, signalClasses, signalElementChildren, signalElementDimensions, signalElementIntersection, signalElementLastScrollDirection, signalElementMutations, signalElementScrollState, signalHostAttributes, signalHostClasses, signalHostElementDimensions, signalHostElementIntersection, signalHostElementLastScrollDirection, signalHostElementMutations, signalHostElementScrollState, signalHostStyles, signalIsRendered, signalStyles, slugify, switchQueryListChanges, syncSignal, templateComputed, toArray, toArrayTrackByFn, toInjectFn, toProvideFn, toStringBinding, toToken, transformOrReturn, typedBreakpointTransform, unbindProps, useCursorDragScroll, writeScrollbarSizeToCssVariables, writeViewportSizeToCssVariables, ɵProvideColorThemes, ɵProvideColorThemesPrefix, ɵProvideSurfaceThemes, ɵProvideSurfaceThemesPrefix };
8610
9252
  //# sourceMappingURL=ethlete-core.mjs.map