@ethlete/core 5.0.0-next.35 → 5.0.0-next.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -21
- package/README.md +1 -1
- package/fesm2022/ethlete-core.mjs +1160 -600
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/generators.json +5 -0
- package/generators/migrate-provider-shape/migration-scope.js +35 -0
- package/generators/migrate-provider-shape/migration.js +51 -0
- package/generators/migrate-provider-shape/provider-shape.js +217 -0
- package/generators/migrate-provider-shape/schema.json +26 -0
- package/generators/tailwind-4-color-theme/generator.js +4 -4
- package/generators/tailwind-4-surface-theme/generator.js +7 -7
- package/package.json +18 -2
- package/types/ethlete-core.d.ts +2847 -2746
|
@@ -4,13 +4,9 @@ import { takeUntilDestroyed, toSignal, toObservable, outputFromObservable } from
|
|
|
4
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';
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
178
|
+
|
|
179
|
+
const defineProvider = (factory, options) => {
|
|
183
180
|
const token = new InjectionToken(options?.name ?? createComponentId('provider'));
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
return {
|
|
182
|
+
provide: () => createProviders(token, factory, options?.extraInjectionToken),
|
|
183
|
+
inject: createInjectFunction(token),
|
|
184
|
+
token,
|
|
185
|
+
};
|
|
187
186
|
};
|
|
188
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
return {
|
|
197
|
+
provide: () => createProviders(token, factory, options?.extraInjectionToken),
|
|
198
|
+
inject: createInjectFunction(token),
|
|
199
|
+
token,
|
|
200
|
+
};
|
|
196
201
|
};
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1595
|
+
const VIEWPORT_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider(DEFAULT_VIEWPORT_CONFIG, {
|
|
1572
1596
|
name: 'Viewport Config',
|
|
1573
1597
|
});
|
|
1574
|
-
const
|
|
1575
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
1879
|
+
const elementMutations = signalElementMutations(elements, options?.mutations ?? DEFAULT_CHILDREN_MUTATIONS);
|
|
1820
1880
|
return computed(() => {
|
|
1821
1881
|
if (!isRendered())
|
|
1822
1882
|
return [];
|
|
@@ -1972,7 +2032,12 @@ const signalStyles = (el, styleMap) => {
|
|
|
1972
2032
|
const signalHostStyles = (styleMap) => signalStyles(inject(ElementRef), styleMap);
|
|
1973
2033
|
|
|
1974
2034
|
const elementCanScroll = (element, direction) => {
|
|
1975
|
-
|
|
2035
|
+
// Omitting the element means "the document scroller". Resolved lazily and guarded so a call on the
|
|
2036
|
+
// server answers "cannot scroll" instead of throwing on a missing `document`.
|
|
2037
|
+
const el = element || (typeof document === 'undefined' ? null : document.documentElement);
|
|
2038
|
+
if (!el) {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
1976
2041
|
const { scrollHeight, clientHeight, scrollWidth, clientWidth } = el;
|
|
1977
2042
|
if (direction === 'x') {
|
|
1978
2043
|
return scrollWidth > clientWidth;
|
|
@@ -1982,17 +2047,25 @@ const elementCanScroll = (element, direction) => {
|
|
|
1982
2047
|
}
|
|
1983
2048
|
return scrollHeight > clientHeight || scrollWidth > clientWidth;
|
|
1984
2049
|
};
|
|
1985
|
-
const createViewportRect = () =>
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
width
|
|
1991
|
-
height
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2050
|
+
const createViewportRect = () => {
|
|
2051
|
+
// Collapses to a zero rect where there is no window (server). `isElementVisible` never reaches the
|
|
2052
|
+
// intersection math there anyway - `elementCanScroll` already reports the document as unscrollable
|
|
2053
|
+
// - so the rect only has to be a valid shape.
|
|
2054
|
+
const hasWindow = typeof window !== 'undefined';
|
|
2055
|
+
const width = hasWindow ? window.innerWidth : 0;
|
|
2056
|
+
const height = hasWindow ? window.innerHeight : 0;
|
|
2057
|
+
return {
|
|
2058
|
+
left: 0,
|
|
2059
|
+
top: 0,
|
|
2060
|
+
right: width,
|
|
2061
|
+
bottom: height,
|
|
2062
|
+
width,
|
|
2063
|
+
height,
|
|
2064
|
+
x: 0,
|
|
2065
|
+
y: 0,
|
|
2066
|
+
toJSON: () => ({}),
|
|
2067
|
+
};
|
|
2068
|
+
};
|
|
1996
2069
|
const isElementVisible = (options) => {
|
|
1997
2070
|
const { container, element } = options;
|
|
1998
2071
|
if (!element) {
|
|
@@ -2206,7 +2279,7 @@ const getScrollItemTarget = (entries, container, direction, scrollOrigin, axisDi
|
|
|
2206
2279
|
const firstIndex = entries.indexOf(firstVisible);
|
|
2207
2280
|
const lastIndex = entries.indexOf(lastVisible);
|
|
2208
2281
|
const containerRect = container.getBoundingClientRect();
|
|
2209
|
-
// Only a single item is visible
|
|
2282
|
+
// Only a single item is visible - it must be oversized (wider/taller than the container).
|
|
2210
2283
|
if (firstVisible === lastVisible) {
|
|
2211
2284
|
const itemRect = firstVisible.target.getBoundingClientRect();
|
|
2212
2285
|
const isStartEdgeVisible = axisDirection === 'horizontal'
|
|
@@ -2445,20 +2518,21 @@ const signalElementLastScrollDirection = (el) => {
|
|
|
2445
2518
|
const signalHostElementLastScrollDirection = () => signalElementLastScrollDirection(inject(ElementRef));
|
|
2446
2519
|
|
|
2447
2520
|
/**
|
|
2448
|
-
* A signal for whether this page is the one the reader is actually looking at
|
|
2521
|
+
* A signal for whether this page is the one the reader is actually looking at - `false` for a backgrounded
|
|
2449
2522
|
* tab or a minimised window.
|
|
2450
2523
|
*
|
|
2451
2524
|
* Not the same question as whether an element is on screen: an IntersectionObserver reports a fully visible
|
|
2452
2525
|
* element in a background tab, because the tab's own visibility is no part of what it measures. Anything
|
|
2453
|
-
* that moves on its own needs to ask both, and a timer is not a substitute
|
|
2526
|
+
* that moves on its own needs to ask both, and a timer is not a substitute - browsers throttle timers in a
|
|
2454
2527
|
* hidden tab rather than stopping them, so work keeps piling up out of sight.
|
|
2455
2528
|
*/
|
|
2456
|
-
const injectIsDocumentVisible = memoizeSignal(() => {
|
|
2529
|
+
const injectIsDocumentVisible = /* @__PURE__ */ memoizeSignal(() => {
|
|
2457
2530
|
const document = inject(DOCUMENT);
|
|
2458
2531
|
const isVisible = () => document.visibilityState === 'visible';
|
|
2459
2532
|
return toSignal(fromEvent(document, 'visibilitychange').pipe(map(isVisible)), { initialValue: isVisible() });
|
|
2460
2533
|
});
|
|
2461
2534
|
|
|
2535
|
+
const DEFAULT_SCROLL_STATE_MUTATIONS = { childList: true, subtree: true, attributes: true };
|
|
2462
2536
|
const areScrollStatesEqual = (a, b) => {
|
|
2463
2537
|
return (a.canScroll === b.canScroll &&
|
|
2464
2538
|
a.canScrollHorizontally === b.canScrollHorizontally &&
|
|
@@ -2469,7 +2543,7 @@ const signalElementScrollState = (el, options) => {
|
|
|
2469
2543
|
const elements = buildElementSignal(el);
|
|
2470
2544
|
const observedEl = firstElementSignal(elements);
|
|
2471
2545
|
const elementDimensions = signalElementDimensions(elements);
|
|
2472
|
-
const elementMutations = signalElementMutations(elements,
|
|
2546
|
+
const elementMutations = signalElementMutations(elements, options?.mutations ?? DEFAULT_SCROLL_STATE_MUTATIONS);
|
|
2473
2547
|
const isRendered = signalIsRendered();
|
|
2474
2548
|
const initialScrollPosition = options?.initialScrollPosition;
|
|
2475
2549
|
if (initialScrollPosition) {
|
|
@@ -2583,7 +2657,6 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2583
2657
|
const element = firstElementSignal(elements);
|
|
2584
2658
|
const destroyRef = inject(DestroyRef);
|
|
2585
2659
|
const { enabled = signal(true), allowedDirection = 'both' } = options ?? {};
|
|
2586
|
-
const scrollState = signalElementScrollState(elements);
|
|
2587
2660
|
const renderer = injectRenderer();
|
|
2588
2661
|
const isDragging = signal(false, /* @ts-ignore */
|
|
2589
2662
|
...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
@@ -2596,8 +2669,13 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2596
2669
|
const dragAmount = signal({ x: 0, y: 0 }, /* @ts-ignore */
|
|
2597
2670
|
...(ngDevMode ? [{ debugName: "dragAmount" }] : /* istanbul ignore next */ []));
|
|
2598
2671
|
const document = inject(DOCUMENT);
|
|
2599
|
-
|
|
2600
|
-
|
|
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;
|
|
2601
2679
|
const direction = maybeSignalValue(allowedDirection);
|
|
2602
2680
|
switch (direction) {
|
|
2603
2681
|
case 'both':
|
|
@@ -2608,7 +2686,8 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2608
2686
|
return currentScrollState.canScrollVertically;
|
|
2609
2687
|
}
|
|
2610
2688
|
}, /* @ts-ignore */
|
|
2611
|
-
...(ngDevMode ? [{ debugName: "
|
|
2689
|
+
...(ngDevMode ? [{ debugName: "measuredCanScroll" }] : /* istanbul ignore next */ []));
|
|
2690
|
+
const canScroll = suppliedCanScroll ?? measuredCanScroll;
|
|
2612
2691
|
// Cleanup if the element the cursor drag scroll is bound to gets changed
|
|
2613
2692
|
effect(() => {
|
|
2614
2693
|
const { previousElement } = element();
|
|
@@ -2709,7 +2788,7 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2709
2788
|
dragAmount.set({ x: 0, y: 0 });
|
|
2710
2789
|
};
|
|
2711
2790
|
const setupDragging = (e) => {
|
|
2712
|
-
// A context menu takes the pointer away without ever delivering a `mouseup`
|
|
2791
|
+
// A context menu takes the pointer away without ever delivering a `mouseup` - so without it in here, a
|
|
2713
2792
|
// right click (or a ctrl-click on a Mac, which is the primary button) leaves the drag latched on and
|
|
2714
2793
|
// every later mouse move scrolls the container.
|
|
2715
2794
|
const dragEnd = merge(fromEvent(document, 'mouseup'), fromEvent(document, 'contextmenu'));
|
|
@@ -2736,7 +2815,7 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2736
2815
|
};
|
|
2737
2816
|
};
|
|
2738
2817
|
|
|
2739
|
-
const ET_PROPERTY_REMOVED = Symbol('ET_PROPERTY_REMOVED');
|
|
2818
|
+
const ET_PROPERTY_REMOVED = /* @__PURE__ */ Symbol('ET_PROPERTY_REMOVED');
|
|
2740
2819
|
const transformOrReturn = (src, config) => {
|
|
2741
2820
|
const transformer = config?.transform;
|
|
2742
2821
|
if (transformer) {
|
|
@@ -2753,7 +2832,7 @@ const createInitialRoute = () => {
|
|
|
2753
2832
|
return window.location.pathname + window.location.search + window.location.hash;
|
|
2754
2833
|
};
|
|
2755
2834
|
/** Inject the current router event */
|
|
2756
|
-
const injectRouterEvent = memoizeSignal(() => {
|
|
2835
|
+
const injectRouterEvent = /* @__PURE__ */ memoizeSignal(() => {
|
|
2757
2836
|
const router = inject(Router);
|
|
2758
2837
|
const initialRoute = createInitialRoute();
|
|
2759
2838
|
return toSignal(router.events, {
|
|
@@ -2764,7 +2843,7 @@ const injectRouterEvent = memoizeSignal(() => {
|
|
|
2764
2843
|
* Signal that indicates whether the router has been initialized.
|
|
2765
2844
|
* The router is considered initialized once the first NavigationEnd event with an id different than -1 has been emitted.
|
|
2766
2845
|
*/
|
|
2767
|
-
const injectIsRouterInitialized = memoizeSignal(() => {
|
|
2846
|
+
const injectIsRouterInitialized = /* @__PURE__ */ memoizeSignal(() => {
|
|
2768
2847
|
const event = injectRouterEvent();
|
|
2769
2848
|
return computed(() => {
|
|
2770
2849
|
const e = event();
|
|
@@ -2776,7 +2855,7 @@ const injectIsRouterInitialized = memoizeSignal(() => {
|
|
|
2776
2855
|
* The url includes query params as well as the fragment. Use `injectRoute` instead if you are not intrusted in those.
|
|
2777
2856
|
* @example "/my-page?query=1¶m=true#fragment"
|
|
2778
2857
|
*/
|
|
2779
|
-
const injectUrl = memoizeSignal(() => {
|
|
2858
|
+
const injectUrl = /* @__PURE__ */ memoizeSignal(() => {
|
|
2780
2859
|
const event = injectRouterEvent();
|
|
2781
2860
|
const router = inject(Router);
|
|
2782
2861
|
// Captured once for reads that happen before the router has committed any
|
|
@@ -2786,7 +2865,7 @@ const injectUrl = memoizeSignal(() => {
|
|
|
2786
2865
|
// We depend on `event` and `currentNavigation` only to get re-evaluated
|
|
2787
2866
|
// around each navigation, but we read the *committed* url straight from the
|
|
2788
2867
|
// router. The router commits `currentUrlTree` before it activates components,
|
|
2789
|
-
// 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 -
|
|
2790
2869
|
// unlike the NavigationEnd event, which only reaches this signal *after*
|
|
2791
2870
|
// activation.
|
|
2792
2871
|
event();
|
|
@@ -2806,7 +2885,7 @@ const injectUrl = memoizeSignal(() => {
|
|
|
2806
2885
|
* Inject the current route
|
|
2807
2886
|
* @example "/my-page"
|
|
2808
2887
|
*/
|
|
2809
|
-
const injectRoute = memoizeSignal(() => {
|
|
2888
|
+
const injectRoute = /* @__PURE__ */ memoizeSignal(() => {
|
|
2810
2889
|
const url = injectUrl();
|
|
2811
2890
|
return computed(() => {
|
|
2812
2891
|
const fullUrl = url();
|
|
@@ -2841,14 +2920,9 @@ const createRoute = (router) => {
|
|
|
2841
2920
|
/**
|
|
2842
2921
|
* Inject the complete router state. This includes the current route data, path params, query params, title and fragment.
|
|
2843
2922
|
*/
|
|
2844
|
-
const injectRouterState = memoizeSignal(() => {
|
|
2923
|
+
const injectRouterState = /* @__PURE__ */ memoizeSignal(() => {
|
|
2845
2924
|
const event = injectRouterEvent();
|
|
2846
2925
|
const router = inject(Router);
|
|
2847
|
-
// We only depend on `event` to get re-evaluated on every router event, but we
|
|
2848
|
-
// read the *committed* snapshot straight from the router. The router commits
|
|
2849
|
-
// `routerState` before it activates components (and emits ActivationStart
|
|
2850
|
-
// before NavigationEnd), so this is already correct inside a child component's
|
|
2851
|
-
// constructor — unlike an effect, which only flushes after construction.
|
|
2852
2926
|
return computed(() => {
|
|
2853
2927
|
event();
|
|
2854
2928
|
return createRouterState(router);
|
|
@@ -2917,7 +2991,7 @@ const injectPathParam = (key, config) => {
|
|
|
2917
2991
|
* Inject query params that changed during navigation. Unchanged query params will be ignored.
|
|
2918
2992
|
* Removed query params will be represented by the symbol `ET_PROPERTY_REMOVED`.
|
|
2919
2993
|
*/
|
|
2920
|
-
const injectQueryParamChanges = memoizeSignal(() => {
|
|
2994
|
+
const injectQueryParamChanges = /* @__PURE__ */ memoizeSignal(() => {
|
|
2921
2995
|
const queryParams = injectQueryParams();
|
|
2922
2996
|
const prevQueryParams = previousSignalValue(queryParams);
|
|
2923
2997
|
return computed(() => {
|
|
@@ -2941,7 +3015,7 @@ const injectQueryParamChanges = memoizeSignal(() => {
|
|
|
2941
3015
|
* Inject path params that changed during navigation. Unchanged path params will be ignored.
|
|
2942
3016
|
* Removed path params will be represented by the symbol `ET_PROPERTY_REMOVED`.
|
|
2943
3017
|
*/
|
|
2944
|
-
const injectPathParamChanges = memoizeSignal(() => {
|
|
3018
|
+
const injectPathParamChanges = /* @__PURE__ */ memoizeSignal(() => {
|
|
2945
3019
|
const pathParams = injectPathParams();
|
|
2946
3020
|
const prevPathParams = previousSignalValue(pathParams);
|
|
2947
3021
|
return computed(() => {
|
|
@@ -2961,22 +3035,42 @@ const injectPathParamChanges = memoizeSignal(() => {
|
|
|
2961
3035
|
return changes;
|
|
2962
3036
|
});
|
|
2963
3037
|
});
|
|
3038
|
+
/**
|
|
3039
|
+
* The state handed to the navigation that is currently in progress, i.e. what
|
|
3040
|
+
* `router.navigate(…, { state })` passed - typed to whatever you expect.
|
|
3041
|
+
*
|
|
3042
|
+
* Read **synchronously**, in a constructor or a resolver, and deliberately not a signal: navigation state exists
|
|
3043
|
+
* only for the duration of the navigation that carries it. By the time an effect flushed, the navigation would be
|
|
3044
|
+
* over and the answer always `null`. Returns `null` when there is no navigation in flight or it carried no state -
|
|
3045
|
+
* which is the normal case for a page arrived at by typing its URL, so always handle it.
|
|
3046
|
+
*
|
|
3047
|
+
* Use it for the things a URL should not carry: a "you were redirected because…" reason, a pre-fetched object the
|
|
3048
|
+
* previous page already had, a scroll intent. Anything the page needs to survive a reload belongs in the URL.
|
|
3049
|
+
*
|
|
3050
|
+
* @example
|
|
3051
|
+
* type CheckoutNavState = { fromCart: boolean };
|
|
3052
|
+
*
|
|
3053
|
+
* export class CheckoutComponent {
|
|
3054
|
+
* private navState = injectRouterNavigationState<CheckoutNavState>();
|
|
3055
|
+
*
|
|
3056
|
+
* protected cameFromCart = this.navState?.fromCart ?? false;
|
|
3057
|
+
* }
|
|
3058
|
+
*/
|
|
3059
|
+
const injectRouterNavigationState = () => {
|
|
3060
|
+
const router = inject(Router);
|
|
3061
|
+
return router.getCurrentNavigation()?.extras.state ?? null;
|
|
3062
|
+
};
|
|
2964
3063
|
|
|
2965
|
-
const ET_DISABLE_SCROLL_TOP = Symbol('ET_DISABLE_SCROLL_TOP');
|
|
2966
|
-
const ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE = Symbol('ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE');
|
|
2967
|
-
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');
|
|
2968
3067
|
const routerDisableScrollTop = (config = {}) => {
|
|
2969
3068
|
return {
|
|
2970
3069
|
...(!config.asReturnRoute ? { [ET_DISABLE_SCROLL_TOP]: true } : { [ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE]: true }),
|
|
2971
3070
|
...(config.onPathParamChange ? { [ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE]: true } : {}),
|
|
2972
3071
|
};
|
|
2973
3072
|
};
|
|
2974
|
-
|
|
2975
|
-
* Registry of "not settled yet" reporters. Lives in the root injector so a route component can
|
|
2976
|
-
* reach the registration created by the app level `setupScrollRestoration` call.
|
|
2977
|
-
* @internal
|
|
2978
|
-
*/
|
|
2979
|
-
const [, injectScrollRestorationHolds] = createRootProvider(() => {
|
|
3073
|
+
const SCROLL_RESTORATION_HOLDS_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
2980
3074
|
const holds = new Set();
|
|
2981
3075
|
return {
|
|
2982
3076
|
add: (isPending) => {
|
|
@@ -2992,11 +3086,17 @@ const [, injectScrollRestorationHolds] = createRootProvider(() => {
|
|
|
2992
3086
|
},
|
|
2993
3087
|
};
|
|
2994
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);
|
|
2995
3095
|
/**
|
|
2996
3096
|
* Suspend scroll restoration while `isPending` reads `true`.
|
|
2997
3097
|
*
|
|
2998
3098
|
* Restoration already waits for the scroll container to grow tall enough to reach the saved offset,
|
|
2999
|
-
* 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 -
|
|
3000
3100
|
* a slow list request, for instance. Registers for the lifetime of the current injection context.
|
|
3001
3101
|
*
|
|
3002
3102
|
* @example
|
|
@@ -3030,7 +3130,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3030
3130
|
return el ?? document.documentElement;
|
|
3031
3131
|
};
|
|
3032
3132
|
// The browser restores the offset against the still-loading (much shorter) document, which is the
|
|
3033
|
-
// exact problem we are solving
|
|
3133
|
+
// exact problem we are solving - take it over completely.
|
|
3034
3134
|
if (restoreEnabled && 'scrollRestoration' in history) {
|
|
3035
3135
|
history.scrollRestoration = 'manual';
|
|
3036
3136
|
}
|
|
@@ -3069,7 +3169,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3069
3169
|
}
|
|
3070
3170
|
const now = Date.now();
|
|
3071
3171
|
// A registered hold means the page knows more data is coming, so the content is expected to
|
|
3072
|
-
// grow
|
|
3172
|
+
// grow - suspend the clock instead of giving up on a still loading page.
|
|
3073
3173
|
if (holds.isHeld() && now < hardDeadline) {
|
|
3074
3174
|
deadline = now + restoreTimeout;
|
|
3075
3175
|
}
|
|
@@ -3082,7 +3182,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3082
3182
|
}
|
|
3083
3183
|
cancelPendingRestore();
|
|
3084
3184
|
};
|
|
3085
|
-
// Wait for the new route to actually render before reading any geometry
|
|
3185
|
+
// Wait for the new route to actually render before reading any geometry - on `NavigationEnd` the
|
|
3086
3186
|
// components exist but the DOM still shows the previous page, whose height would be measured
|
|
3087
3187
|
// instead. The timeout is a safety net in case no render pass follows.
|
|
3088
3188
|
const start = () => {
|
|
@@ -3206,7 +3306,7 @@ const createLogger = (config) => {
|
|
|
3206
3306
|
};
|
|
3207
3307
|
|
|
3208
3308
|
const escapeHtml = (str) => str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
3209
|
-
/** Escapes HTML-special characters but leaves existing entity references intact
|
|
3309
|
+
/** Escapes HTML-special characters but leaves existing entity references intact - for input that
|
|
3210
3310
|
* may already contain entities (e.g. Markdown serialized from the editor's DOM, where `&` is
|
|
3211
3311
|
* stored as `&`), where plain escaping would double-escape them. */
|
|
3212
3312
|
const escapeHtmlPreservingEntities = (str) => str
|
|
@@ -3215,7 +3315,7 @@ const escapeHtmlPreservingEntities = (str) => str
|
|
|
3215
3315
|
.replace(/>/g, '>')
|
|
3216
3316
|
.replace(/"/g, '"');
|
|
3217
3317
|
/** Rejects URL schemes that execute script when navigated. Whitespace/control characters are
|
|
3218
|
-
* stripped before checking
|
|
3318
|
+
* stripped before checking - browsers ignore them inside URLs, so `java\tscript:` would
|
|
3219
3319
|
* otherwise slip through. */
|
|
3220
3320
|
const isSafeUrl = (url) =>
|
|
3221
3321
|
// eslint-disable-next-line no-control-regex -- stripping control characters is the point: browsers ignore them inside URLs
|
|
@@ -3242,7 +3342,7 @@ const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
|
|
|
3242
3342
|
/** The GFM separator token for a column alignment. */
|
|
3243
3343
|
const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
|
|
3244
3344
|
const alignStyle = (align) => (align ? ` style="text-align: ${align}"` : '');
|
|
3245
|
-
/** 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 -
|
|
3246
3346
|
* CommonMark emphasis must not face whitespace on the inside (`** fett**` doesn't parse), and
|
|
3247
3347
|
* ` ` counts as whitespace there too. Whitespace-only content stays unwrapped. */
|
|
3248
3348
|
const emphasize = (marker, content) => {
|
|
@@ -3256,9 +3356,9 @@ const makePlaceholder = (kind, idx) => `\u{E000}${kind}${idx}\u{E001}`;
|
|
|
3256
3356
|
const placeholderRe = (kind) => new RegExp(`\u{E000}${kind}(\\d+)\u{E001}`, 'gu');
|
|
3257
3357
|
const isPlaceholder = (kind, str) => new RegExp(`^\u{E000}${kind}\\d+\u{E001}$`, 'u').test(str);
|
|
3258
3358
|
/** Tags allowed to survive inside an aligned block's raw-HTML passthrough. */
|
|
3259
|
-
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']);
|
|
3260
3360
|
/** Reduces raw inline HTML to the editor's own vocabulary: allowed tags lose all their attributes
|
|
3261
|
-
* (`<a>` keeps a safe `href`), everything else
|
|
3361
|
+
* (`<a>` keeps a safe `href`), everything else - including any event-handler attribute - is
|
|
3262
3362
|
* dropped, and the remaining text is escaped. */
|
|
3263
3363
|
const sanitizeInlineHtml = (html) => {
|
|
3264
3364
|
const kept = [];
|
|
@@ -3283,7 +3383,7 @@ const processInline = (text) => {
|
|
|
3283
3383
|
});
|
|
3284
3384
|
// "Open in new tab" links round-trip as raw HTML because Markdown has no `target` syntax. Extract
|
|
3285
3385
|
// them before the raw-HTML escape below, keeping only a safe href, `target="_blank"` and a forced
|
|
3286
|
-
// `rel="noopener noreferrer"` (any other attribute
|
|
3386
|
+
// `rel="noopener noreferrer"` (any other attribute - including event handlers - is dropped); their
|
|
3287
3387
|
// inner markup is processed recursively. Anything else that looks like raw HTML is still escaped.
|
|
3288
3388
|
const newTabLinks = [];
|
|
3289
3389
|
text = text.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (full, attrs, inner) => {
|
|
@@ -3294,11 +3394,11 @@ const processInline = (text) => {
|
|
|
3294
3394
|
const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`;
|
|
3295
3395
|
return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
|
|
3296
3396
|
});
|
|
3297
|
-
// Raw HTML in Markdown text is escaped, not rendered
|
|
3397
|
+
// Raw HTML in Markdown text is escaped, not rendered - the editor writes this HTML straight
|
|
3298
3398
|
// into the DOM via innerHTML, so anything else would let a crafted value inject markup. `<u>`
|
|
3299
3399
|
// is the one deliberate exception: underline has no Markdown form and round-trips as raw <u>.
|
|
3300
3400
|
text = escapeHtmlPreservingEntities(text).replace(/<(\/?)u>/gi, '<$1u>');
|
|
3301
|
-
// Bold + italic
|
|
3401
|
+
// Bold + italic - *** before ** before *. A delimiter flanking whitespace doesn't open/close a
|
|
3302
3402
|
// run (CommonMark's flanking rule), so literal asterisks as in `2 * 3 * 4` stay text.
|
|
3303
3403
|
text = text.replace(/\*\*\*([^\s*](?:.*?[^\s*])?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
3304
3404
|
text = text.replace(/\*\*([^\s*](?:.*?[^\s*])?)\*\*/g, '<strong>$1</strong>');
|
|
@@ -3361,6 +3461,75 @@ const findListItems = (inner) => {
|
|
|
3361
3461
|
}
|
|
3362
3462
|
return items;
|
|
3363
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
|
+
};
|
|
3364
3533
|
/** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
|
|
3365
3534
|
const listToMarkdown = (inner, ordered, depth) => {
|
|
3366
3535
|
const indent = ' '.repeat(depth);
|
|
@@ -3379,7 +3548,7 @@ const listToMarkdown = (inner, ordered, depth) => {
|
|
|
3379
3548
|
})
|
|
3380
3549
|
.join('\n');
|
|
3381
3550
|
};
|
|
3382
|
-
/** Parses list lines into (indent-level, ordered, text)
|
|
3551
|
+
/** Parses list lines into (indent-level, ordered, text) - two leading spaces per nesting level. */
|
|
3383
3552
|
const parseListLines = (lines) => lines
|
|
3384
3553
|
.map((line) => {
|
|
3385
3554
|
const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
@@ -3453,7 +3622,7 @@ const markdownToHtml = (markdown) => {
|
|
|
3453
3622
|
// Horizontal rule
|
|
3454
3623
|
if (/^(---|\*\*\*|___)\s*$/.test(trimmed))
|
|
3455
3624
|
return '<hr>';
|
|
3456
|
-
// Table (GFM)
|
|
3625
|
+
// Table (GFM) - column alignment from the separator line lands as text-align on every cell
|
|
3457
3626
|
const tableLines = trimmed.split('\n');
|
|
3458
3627
|
if (tableLines.length >= 2 && /\|/.test(tableLines[0] ?? '') && isTableSeparatorLine(tableLines[1] ?? '')) {
|
|
3459
3628
|
const aligns = parseSeparatorAligns(tableLines[1] ?? '');
|
|
@@ -3471,15 +3640,10 @@ const markdownToHtml = (markdown) => {
|
|
|
3471
3640
|
: '';
|
|
3472
3641
|
return `<table>${thead}${tbody}</table>`;
|
|
3473
3642
|
}
|
|
3474
|
-
// Blockquote
|
|
3475
|
-
// HTML, so joining first would escape the <br> separators too)
|
|
3476
|
-
if (
|
|
3477
|
-
|
|
3478
|
-
.split('\n')
|
|
3479
|
-
.map((l) => processInline(l.replace(/^>\s?/, '')))
|
|
3480
|
-
.join('<br>');
|
|
3481
|
-
return `<blockquote>${content}</blockquote>`;
|
|
3482
|
-
}
|
|
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'));
|
|
3483
3647
|
// List (unordered/ordered, with indentation-based nesting)
|
|
3484
3648
|
if (/^([-*+]|\d+\.)\s/.test(trimmed)) {
|
|
3485
3649
|
const lines = parseListLines(trimmed.split('\n'));
|
|
@@ -3487,7 +3651,7 @@ const markdownToHtml = (markdown) => {
|
|
|
3487
3651
|
return buildListHtml(lines, 0, lines[0].indent).html;
|
|
3488
3652
|
}
|
|
3489
3653
|
}
|
|
3490
|
-
// Paragraph
|
|
3654
|
+
// Paragraph - single newlines within a block become <br>
|
|
3491
3655
|
return `<p>${trimmed
|
|
3492
3656
|
.split('\n')
|
|
3493
3657
|
.map((l) => processInline(l))
|
|
@@ -3507,21 +3671,21 @@ const htmlToMarkdown = (html) => {
|
|
|
3507
3671
|
return '';
|
|
3508
3672
|
let md = html;
|
|
3509
3673
|
// Aligned blocks: text-align has no Markdown form, so preserve them verbatim as native HTML (their
|
|
3510
|
-
// inner markup stays HTML) and round-trip via a placeholder
|
|
3674
|
+
// inner markup stays HTML) and round-trip via a placeholder - extracted before the block passes
|
|
3511
3675
|
// below rewrite them, restored after the final tag-strip.
|
|
3512
3676
|
const alignedBlocks = [];
|
|
3513
3677
|
md = md.replace(/<(p|h[1-6]|div)\b[^>]*\bstyle="[^"]*text-align[^"]*"[^>]*>[\s\S]*?<\/\1>/gi, (match) => makePlaceholder('ALIGN', alignedBlocks.push(match) - 1));
|
|
3514
|
-
// Code blocks
|
|
3678
|
+
// Code blocks - process before inline code
|
|
3515
3679
|
md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3516
3680
|
md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3517
3681
|
md = md.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3518
|
-
// Headings
|
|
3682
|
+
// Headings - keep the inner markup so the inline passes below turn any nested
|
|
3519
3683
|
// bold/italic/link/code into markdown; leftover tags are stripped at the end.
|
|
3520
3684
|
for (let i = 6; i >= 1; i--) {
|
|
3521
3685
|
md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => `\n${'#'.repeat(i)} ${content.trim()}\n`);
|
|
3522
3686
|
}
|
|
3523
3687
|
// Bold + italic (combined before individual). Boundary whitespace is hoisted out of the
|
|
3524
|
-
// delimiters (emphasize)
|
|
3688
|
+
// delimiters (emphasize) - `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
|
|
3525
3689
|
md = md.replace(/<strong[^>]*><em[^>]*>([\s\S]*?)<\/em><\/strong>/gi, (_, c) => emphasize('***', c));
|
|
3526
3690
|
md = md.replace(/<em[^>]*><strong[^>]*>([\s\S]*?)<\/strong><\/em>/gi, (_, c) => emphasize('***', c));
|
|
3527
3691
|
md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('**', c));
|
|
@@ -3530,7 +3694,7 @@ const htmlToMarkdown = (html) => {
|
|
|
3530
3694
|
// Inline code
|
|
3531
3695
|
md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, code) => `\`${unescapeHtml(code)}\``);
|
|
3532
3696
|
// Links and images. A link with `target="_blank"` has no Markdown form, so it round-trips as raw
|
|
3533
|
-
// HTML (keeping only href + target + rel) via a placeholder
|
|
3697
|
+
// HTML (keeping only href + target + rel) via a placeholder - like underline below; ordinary links
|
|
3534
3698
|
// become `[text](url)`.
|
|
3535
3699
|
const newTabLinks = [];
|
|
3536
3700
|
md = md.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, inner) => {
|
|
@@ -3544,20 +3708,17 @@ const htmlToMarkdown = (html) => {
|
|
|
3544
3708
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '');
|
|
3545
3709
|
md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '');
|
|
3546
3710
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '');
|
|
3547
|
-
// Underline has no Markdown form
|
|
3711
|
+
// Underline has no Markdown form - preserve it as raw <u> so it round-trips. Extract it now (its
|
|
3548
3712
|
// inner markup is already converted above) so the block passes below don't strip the tag; the
|
|
3549
3713
|
// placeholder is restored after the final tag-strip.
|
|
3550
3714
|
const underlines = [];
|
|
3551
3715
|
md = md.replace(/<u[^>]*>([\s\S]*?)<\/u>/gi, (_, inner) => makePlaceholder('U', underlines.push(inner) - 1));
|
|
3552
|
-
// Block quotes
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
.join('\n') + '\n');
|
|
3559
|
-
});
|
|
3560
|
-
// 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).
|
|
3561
3722
|
for (let list = findList(md); list; list = findList(md)) {
|
|
3562
3723
|
md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
|
|
3563
3724
|
}
|
|
@@ -3585,10 +3746,10 @@ const htmlToMarkdown = (html) => {
|
|
|
3585
3746
|
const lines = [`| ${headerCells.join(' | ')} |`, `| ${separator} |`, ...bodyRows.map((r) => `| ${r} |`)];
|
|
3586
3747
|
return `\n${lines.join('\n')}\n`;
|
|
3587
3748
|
});
|
|
3588
|
-
// Block elements
|
|
3749
|
+
// Block elements - a <br> inside a paragraph is a soft line break; turn it into a newline before
|
|
3589
3750
|
// the tag-strip removes it, so it round-trips (a single newline within a block renders as <br>)
|
|
3590
3751
|
md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${stripTags(content.replace(/<br\s*\/?>/gi, '\n')).trim()}\n`);
|
|
3591
|
-
// A <div> boundary acts as a paragraph boundary
|
|
3752
|
+
// A <div> boundary acts as a paragraph boundary - clipboard HTML commonly uses divs as
|
|
3592
3753
|
// paragraphs, and silently stripping them would merge adjacent blocks into one.
|
|
3593
3754
|
md = md.replace(/<\/div\s*>/gi, '\n\n').replace(/<div[^>]*>/gi, '\n\n');
|
|
3594
3755
|
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
@@ -3647,17 +3808,11 @@ class RuntimeError extends Error {
|
|
|
3647
3808
|
this.code = code;
|
|
3648
3809
|
this.data = data;
|
|
3649
3810
|
if (data !== RUNTIME_ERROR_NO_DATA) {
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
}
|
|
3656
|
-
catch {
|
|
3657
|
-
setTimeout(() => {
|
|
3658
|
-
console.error(data);
|
|
3659
|
-
}, 1);
|
|
3660
|
-
}
|
|
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);
|
|
3661
3816
|
}
|
|
3662
3817
|
}
|
|
3663
3818
|
}
|
|
@@ -3694,7 +3849,7 @@ const canUseSessionMemory = () => {
|
|
|
3694
3849
|
if (typeof globalThis.document === 'undefined') {
|
|
3695
3850
|
return false;
|
|
3696
3851
|
}
|
|
3697
|
-
return globalThis.sessionStorage
|
|
3852
|
+
return globalThis.sessionStorage !== null && globalThis.sessionStorage !== undefined;
|
|
3698
3853
|
}
|
|
3699
3854
|
catch {
|
|
3700
3855
|
return false;
|
|
@@ -3763,6 +3918,8 @@ const getClientXY = (event) => {
|
|
|
3763
3918
|
}
|
|
3764
3919
|
return { clientX: event.clientX, clientY: event.clientY };
|
|
3765
3920
|
};
|
|
3921
|
+
/** Trailing window over which {@link SwipeTracker.end} measures the release velocity. */
|
|
3922
|
+
const SWIPE_VELOCITY_WINDOW_MS = 100;
|
|
3766
3923
|
const createSwipeTracker = (startEvent) => {
|
|
3767
3924
|
const { clientX: originClientX, clientY: originClientY } = getClientXY(startEvent);
|
|
3768
3925
|
const startTime = Date.now();
|
|
@@ -3770,10 +3927,25 @@ const createSwipeTracker = (startEvent) => {
|
|
|
3770
3927
|
let movementY = 0;
|
|
3771
3928
|
let isSwiping = false;
|
|
3772
3929
|
let isScrolling = false;
|
|
3930
|
+
/**
|
|
3931
|
+
* Recent positions, oldest first, used to derive the release velocity. Trimmed to the trailing
|
|
3932
|
+
* window plus the one sample just before it, which serves as that window's baseline.
|
|
3933
|
+
*/
|
|
3934
|
+
let samples = [{ t: startTime, movementX: 0, movementY: 0 }];
|
|
3773
3935
|
const update = (event) => {
|
|
3774
3936
|
const { clientX, clientY } = getClientXY(event);
|
|
3775
3937
|
movementX = clientX - originClientX;
|
|
3776
3938
|
movementY = clientY - originClientY;
|
|
3939
|
+
const now = Date.now();
|
|
3940
|
+
samples.push({ t: now, movementX, movementY });
|
|
3941
|
+
// Trim against the newest sample rather than `now + window`: `end()` measures from its own
|
|
3942
|
+
// timestamp, which is never earlier, so this always keeps a superset of what it needs.
|
|
3943
|
+
const cutoff = now - SWIPE_VELOCITY_WINDOW_MS;
|
|
3944
|
+
let dropCount = 0;
|
|
3945
|
+
while (dropCount + 1 < samples.length && samples[dropCount + 1].t < cutoff)
|
|
3946
|
+
dropCount++;
|
|
3947
|
+
if (dropCount)
|
|
3948
|
+
samples = samples.slice(dropCount);
|
|
3777
3949
|
const positiveMovementX = Math.abs(movementX);
|
|
3778
3950
|
const positiveMovementY = Math.abs(movementY);
|
|
3779
3951
|
if (!isSwiping && !isScrolling) {
|
|
@@ -3796,12 +3968,31 @@ const createSwipeTracker = (startEvent) => {
|
|
|
3796
3968
|
isSwiping,
|
|
3797
3969
|
};
|
|
3798
3970
|
};
|
|
3971
|
+
const measureReleaseVelocity = (endTime) => {
|
|
3972
|
+
const windowStart = endTime - SWIPE_VELOCITY_WINDOW_MS;
|
|
3973
|
+
const firstInWindow = samples.findIndex((sample) => sample.t >= windowStart);
|
|
3974
|
+
// Nothing moved during the window: the pointer was held still before release, so the gesture
|
|
3975
|
+
// carries no momentum no matter how fast it started out.
|
|
3976
|
+
if (firstInWindow === -1)
|
|
3977
|
+
return { x: 0, y: 0 };
|
|
3978
|
+
const newest = samples[samples.length - 1];
|
|
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
|
|
3981
|
+
// of reporting nothing.
|
|
3982
|
+
const hasSpanInWindow = samples.length - firstInWindow >= 2;
|
|
3983
|
+
const baseline = samples[hasSpanInWindow ? firstInWindow : Math.max(0, firstInWindow - 1)];
|
|
3984
|
+
const elapsed = newest.t - baseline.t;
|
|
3985
|
+
if (elapsed <= 0)
|
|
3986
|
+
return { x: 0, y: 0 };
|
|
3987
|
+
return {
|
|
3988
|
+
x: ((newest.movementX - baseline.movementX) / elapsed) * 1000,
|
|
3989
|
+
y: ((newest.movementY - baseline.movementY) / elapsed) * 1000,
|
|
3990
|
+
};
|
|
3991
|
+
};
|
|
3799
3992
|
const end = () => {
|
|
3800
|
-
const swipeTime = Math.max(1, Date.now() - startTime);
|
|
3801
3993
|
const positiveMovementX = Math.abs(movementX);
|
|
3802
3994
|
const positiveMovementY = Math.abs(movementY);
|
|
3803
|
-
const pixelPerSecondX = (
|
|
3804
|
-
const pixelPerSecondY = (movementY / swipeTime) * 1000;
|
|
3995
|
+
const { x: pixelPerSecondX, y: pixelPerSecondY } = measureReleaseVelocity(Date.now());
|
|
3805
3996
|
return {
|
|
3806
3997
|
movementX,
|
|
3807
3998
|
movementY,
|
|
@@ -3818,11 +4009,12 @@ const createSwipeTracker = (startEvent) => {
|
|
|
3818
4009
|
const cancel = () => {
|
|
3819
4010
|
movementX = 0;
|
|
3820
4011
|
movementY = 0;
|
|
4012
|
+
samples = [{ t: Date.now(), movementX: 0, movementY: 0 }];
|
|
3821
4013
|
};
|
|
3822
4014
|
return { update, end, cancel };
|
|
3823
4015
|
};
|
|
3824
4016
|
|
|
3825
|
-
const
|
|
4017
|
+
const ANGULAR_ROOT_ELEMENT_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3826
4018
|
const appRef = inject(ApplicationRef);
|
|
3827
4019
|
const rootElement = signal(null, /* @ts-ignore */
|
|
3828
4020
|
...(ngDevMode ? [{ debugName: "rootElement" }] : /* istanbul ignore next */ []));
|
|
@@ -3838,8 +4030,9 @@ const [, injectAngularRootElement] = createRootProvider(() => {
|
|
|
3838
4030
|
setTimeout(poll);
|
|
3839
4031
|
return rootElement;
|
|
3840
4032
|
});
|
|
4033
|
+
const injectAngularRootElement = /* @__PURE__ */ toInjectFn(ANGULAR_ROOT_ELEMENT_DEF);
|
|
3841
4034
|
|
|
3842
|
-
const
|
|
4035
|
+
const BOUNDARY_ELEMENT_DEF = /* @__PURE__ */ defineProvider(() => {
|
|
3843
4036
|
const hostElement = inject(ElementRef, { optional: true });
|
|
3844
4037
|
const document = inject(DOCUMENT);
|
|
3845
4038
|
const override = signal(null, /* @ts-ignore */
|
|
@@ -3851,8 +4044,11 @@ const [provideBoundaryElement, injectBoundaryElement, BOUNDARY_ELEMENT_TOKEN] =
|
|
|
3851
4044
|
override,
|
|
3852
4045
|
};
|
|
3853
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);
|
|
3854
4050
|
|
|
3855
|
-
const
|
|
4051
|
+
const FOCUS_VISIBLE_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3856
4052
|
const document = inject(DOCUMENT);
|
|
3857
4053
|
const destroyRef = inject(DestroyRef);
|
|
3858
4054
|
const isFocusVisible = signal(false, /* @ts-ignore */
|
|
@@ -3894,8 +4090,11 @@ const [provideFocusVisibleTracker, injectFocusVisibleTracker, FOCUS_VISIBLE_TRAC
|
|
|
3894
4090
|
isFocusVisible: isFocusVisible.asReadonly(),
|
|
3895
4091
|
};
|
|
3896
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);
|
|
3897
4096
|
|
|
3898
|
-
const
|
|
4097
|
+
const LOCALE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3899
4098
|
const currentLocale = signal('en', /* @ts-ignore */
|
|
3900
4099
|
...(ngDevMode ? [{ debugName: "currentLocale" }] : /* istanbul ignore next */ []));
|
|
3901
4100
|
return {
|
|
@@ -3904,8 +4103,65 @@ const [provideLocale, injectLocale] = createRootProvider(() => {
|
|
|
3904
4103
|
}, {
|
|
3905
4104
|
name: 'Locale',
|
|
3906
4105
|
});
|
|
4106
|
+
const provideLocale = /* @__PURE__ */ toProvideFn(LOCALE_DEF);
|
|
4107
|
+
const injectLocale = /* @__PURE__ */ toInjectFn(LOCALE_DEF);
|
|
4108
|
+
|
|
4109
|
+
const resolveSource = (source, locale) => typeof source === 'function' ? source(locale) : source;
|
|
4110
|
+
/**
|
|
4111
|
+
* The one mechanism for the strings a component renders or announces itself. Every domain in the UI
|
|
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
|
|
4114
|
+
* repeated per domain rather than a different one each time.
|
|
4115
|
+
*
|
|
4116
|
+
* `defaults` may itself be locale-derived, for a domain that ships more than one language (the
|
|
4117
|
+
* `@ethlete/query` error tables, say). Overrides are partial and layer on top of whatever the locale
|
|
4118
|
+
* resolved to, so translating into a third language while keeping a shipped one as the base is the
|
|
4119
|
+
* same call.
|
|
4120
|
+
*
|
|
4121
|
+
* The injector returns a **signal**, because both halves can change at runtime - read it in a
|
|
4122
|
+
* template or computed, never destructure it once.
|
|
4123
|
+
*
|
|
4124
|
+
* @example
|
|
4125
|
+
* export type BreadcrumbLabels = { navigation: string; overflow: string };
|
|
4126
|
+
*
|
|
4127
|
+
* export const DEFAULT_BREADCRUMB_LABELS: BreadcrumbLabels = {
|
|
4128
|
+
* navigation: 'Breadcrumb',
|
|
4129
|
+
* overflow: 'Show hidden levels',
|
|
4130
|
+
* };
|
|
4131
|
+
*
|
|
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);
|
|
4137
|
+
*
|
|
4138
|
+
* @example
|
|
4139
|
+
* // consumer, fixed wording
|
|
4140
|
+
* provideBreadcrumbLabels({ navigation: 'Brotkrumen' });
|
|
4141
|
+
*
|
|
4142
|
+
* // consumer, driven by the app's i18n and re-resolved on a locale change
|
|
4143
|
+
* provideBreadcrumbLabels((locale) => ({ navigation: translate('breadcrumb.nav', locale) }));
|
|
4144
|
+
*/
|
|
4145
|
+
const defineLabels = (name, defaults) => {
|
|
4146
|
+
const token = new InjectionToken(name, {
|
|
4147
|
+
providedIn: 'root',
|
|
4148
|
+
factory: () => ({}),
|
|
4149
|
+
});
|
|
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,
|
|
4161
|
+
};
|
|
4162
|
+
};
|
|
3907
4163
|
|
|
3908
|
-
const
|
|
4164
|
+
const STYLE_MANAGER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3909
4165
|
const appRef = inject(ApplicationRef);
|
|
3910
4166
|
const destroyRef = inject(DestroyRef);
|
|
3911
4167
|
const document = inject(DOCUMENT);
|
|
@@ -3964,6 +4220,8 @@ const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
|
|
|
3964
4220
|
});
|
|
3965
4221
|
return { mount };
|
|
3966
4222
|
}, { name: 'StyleManager' });
|
|
4223
|
+
const provideStyleManager = /* @__PURE__ */ toProvideFn(STYLE_MANAGER_DEF);
|
|
4224
|
+
const injectStyleManager = /* @__PURE__ */ toInjectFn(STYLE_MANAGER_DEF);
|
|
3967
4225
|
|
|
3968
4226
|
const createUserConsentProvider = (options) => ({
|
|
3969
4227
|
provide: options.for,
|
|
@@ -3987,8 +4245,23 @@ const fromNextFrame = () => {
|
|
|
3987
4245
|
});
|
|
3988
4246
|
});
|
|
3989
4247
|
};
|
|
3990
|
-
const forceReflow = (element
|
|
3991
|
-
|
|
4248
|
+
const forceReflow = (element) => {
|
|
4249
|
+
// The `document.body` fallback is resolved lazily and guarded: there is nothing to reflow on the
|
|
4250
|
+
// server, so this becomes a no-op instead of throwing on a missing `document`.
|
|
4251
|
+
const el = element ?? (typeof document === 'undefined' ? null : document.body);
|
|
4252
|
+
return el?.offsetHeight ?? 0;
|
|
4253
|
+
};
|
|
4254
|
+
/**
|
|
4255
|
+
* Whether the user asked for reduced motion, read straight from `matchMedia`.
|
|
4256
|
+
*
|
|
4257
|
+
* Inside a component, prefer the reactive `injectPrefersReducedMotion()`. This is for the animation
|
|
4258
|
+
* helpers that run from effects, event handlers and imperative APIs, where there is no injection
|
|
4259
|
+
* context. Pass the element being animated so the query resolves against its own window - otherwise
|
|
4260
|
+
* an animation inside an iframe would read the top-level document's preference.
|
|
4261
|
+
*/
|
|
4262
|
+
const matchesReducedMotion = (element) => {
|
|
4263
|
+
const view = element?.ownerDocument.defaultView ?? (typeof window === 'undefined' ? null : window);
|
|
4264
|
+
return view?.matchMedia('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
3992
4265
|
};
|
|
3993
4266
|
|
|
3994
4267
|
const ANIMATED_LIFECYCLE_TOKEN = new InjectionToken('ANIMATED_LIFECYCLE_DIRECTIVE_TOKEN');
|
|
@@ -4214,7 +4487,7 @@ class AnimatedLifecycleDirective {
|
|
|
4214
4487
|
if (!e.cancelled && e.transitionId === transitionId) {
|
|
4215
4488
|
return of(true);
|
|
4216
4489
|
}
|
|
4217
|
-
// The batch ended cancelled or under a different id
|
|
4490
|
+
// The batch ended cancelled or under a different id - e.g. the browser cancelled and
|
|
4218
4491
|
// restarted a running transition mid-flight (an anchored overlay whose ancestor pane got
|
|
4219
4492
|
// destroyed) or a foreign transition on the same element finished. If nothing is
|
|
4220
4493
|
// animating anymore the expected end event can never arrive, so treat the transition as
|
|
@@ -4361,327 +4634,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
4361
4634
|
}]
|
|
4362
4635
|
}], ctorParameters: () => [], propDecorators: { ifValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "etAnimatedIf", required: false }] }] } });
|
|
4363
4636
|
|
|
4364
|
-
class AnimatedOverlayDirective {
|
|
4365
|
-
constructor() {
|
|
4366
|
-
this.overlayService = inject(Overlay);
|
|
4367
|
-
this.injector = inject(Injector);
|
|
4368
|
-
this.viewContainerRef = inject(ViewContainerRef);
|
|
4369
|
-
this.zone = inject(NgZone);
|
|
4370
|
-
this.elementRef = inject(ElementRef);
|
|
4371
|
-
this.rootBoundary = injectBoundaryElement({ optional: true });
|
|
4372
|
-
this.destroyRef = inject(DestroyRef);
|
|
4373
|
-
this.floatingElCleanupFn = null;
|
|
4374
|
-
this.unmountSubscription = null;
|
|
4375
|
-
this.placement = input('bottom', /* @ts-ignore */
|
|
4376
|
-
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
4377
|
-
this.fallbackPlacements = input(/* @ts-ignore */
|
|
4378
|
-
...(ngDevMode ? [undefined, { debugName: "fallbackPlacements" }] : /* istanbul ignore next */ []));
|
|
4379
|
-
this.offset = input(null, /* @ts-ignore */
|
|
4380
|
-
...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
4381
|
-
this.arrowPadding = input(4, /* @ts-ignore */
|
|
4382
|
-
...(ngDevMode ? [{ debugName: "arrowPadding" }] : /* istanbul ignore next */ []));
|
|
4383
|
-
this.viewportPadding = input(8, /* @ts-ignore */
|
|
4384
|
-
...(ngDevMode ? [{ debugName: "viewportPadding" }] : /* istanbul ignore next */ []));
|
|
4385
|
-
this.autoResize = input(false, { ...(ngDevMode ? { debugName: "autoResize" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4386
|
-
this.shift = input(true, { ...(ngDevMode ? { debugName: "shift" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4387
|
-
this.autoHide = input(false, { ...(ngDevMode ? { debugName: "autoHide" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4388
|
-
this.autoCloseIfReferenceHidden = input(false, { ...(ngDevMode ? { debugName: "autoCloseIfReferenceHidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4389
|
-
this.referenceElement = input(this.elementRef.nativeElement, /* @ts-ignore */
|
|
4390
|
-
...(ngDevMode ? [{ debugName: "referenceElement" }] : /* istanbul ignore next */ []));
|
|
4391
|
-
this.mirrorWidth = input(false, { ...(ngDevMode ? { debugName: "mirrorWidth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4392
|
-
this.portal = null;
|
|
4393
|
-
this.overlayRef = null;
|
|
4394
|
-
this.componentRef = null;
|
|
4395
|
-
this.beforeOpened$ = new Subject();
|
|
4396
|
-
this.afterOpened$ = new Subject();
|
|
4397
|
-
this.beforeClosed$ = new Subject();
|
|
4398
|
-
this.afterClosed$ = new Subject();
|
|
4399
|
-
this.state = signal('init', /* @ts-ignore */
|
|
4400
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
4401
|
-
this.isMounted = computed(() => this.state() === 'mounted', /* @ts-ignore */
|
|
4402
|
-
...(ngDevMode ? [{ debugName: "isMounted" }] : /* istanbul ignore next */ []));
|
|
4403
|
-
this.isMounting = computed(() => this.state() === 'mounting', /* @ts-ignore */
|
|
4404
|
-
...(ngDevMode ? [{ debugName: "isMounting" }] : /* istanbul ignore next */ []));
|
|
4405
|
-
this.isUnmounted = computed(() => this.state() === 'unmounted', /* @ts-ignore */
|
|
4406
|
-
...(ngDevMode ? [{ debugName: "isUnmounted" }] : /* istanbul ignore next */ []));
|
|
4407
|
-
this.isUnmounting = computed(() => this.state() === 'unmounting', /* @ts-ignore */
|
|
4408
|
-
...(ngDevMode ? [{ debugName: "isUnmounting" }] : /* istanbul ignore next */ []));
|
|
4409
|
-
this.canMount = computed(() => !this.isMounted() && !this.isMounting(), /* @ts-ignore */
|
|
4410
|
-
...(ngDevMode ? [{ debugName: "canMount" }] : /* istanbul ignore next */ []));
|
|
4411
|
-
this.canUnmount = computed(() => (this.isMounted() || this.isMounting()) && !this.isUnmounting(), /* @ts-ignore */
|
|
4412
|
-
...(ngDevMode ? [{ debugName: "canUnmount" }] : /* istanbul ignore next */ []));
|
|
4413
|
-
this.isHidden = signal(false, /* @ts-ignore */
|
|
4414
|
-
...(ngDevMode ? [{ debugName: "isHidden" }] : /* istanbul ignore next */ []));
|
|
4415
|
-
this.isMounted$ = toObservable(this.isMounted);
|
|
4416
|
-
this.isMounting$ = toObservable(this.isMounting);
|
|
4417
|
-
this.isUnmounted$ = toObservable(this.isUnmounted);
|
|
4418
|
-
this.isUnmounting$ = toObservable(this.isUnmounting);
|
|
4419
|
-
this.canMount$ = toObservable(this.canMount);
|
|
4420
|
-
this.canUnmount$ = toObservable(this.canUnmount);
|
|
4421
|
-
this.isHidden$ = toObservable(this.isHidden);
|
|
4422
|
-
this.referenceElementDimensions = signalElementDimensions(computed(() => (this.mirrorWidth() ? this.referenceElement() : null)));
|
|
4423
|
-
effect(() => {
|
|
4424
|
-
const dimensions = this.referenceElementDimensions();
|
|
4425
|
-
if (!dimensions || !this.mirrorWidth())
|
|
4426
|
-
return;
|
|
4427
|
-
this.overlayRef?.updateSize({
|
|
4428
|
-
width: this.elementRef.nativeElement.offsetWidth,
|
|
4429
|
-
});
|
|
4430
|
-
});
|
|
4431
|
-
this.destroyRef.onDestroy(() => this.destroy());
|
|
4432
|
-
}
|
|
4433
|
-
mount(config) {
|
|
4434
|
-
if (this.isUnmounting()) {
|
|
4435
|
-
if (!this.componentRef) {
|
|
4436
|
-
if (isDevMode()) {
|
|
4437
|
-
console.warn('AnimatedOverlayDirective: Cannot remount. Component ref is not available.');
|
|
4438
|
-
}
|
|
4439
|
-
return;
|
|
4440
|
-
}
|
|
4441
|
-
this.unmountSubscription?.unsubscribe();
|
|
4442
|
-
this.unmountSubscription = null;
|
|
4443
|
-
this.applyComponentData(config.data);
|
|
4444
|
-
this.applyColorProvider(config.colorProvider);
|
|
4445
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle();
|
|
4446
|
-
if (lifecycle) {
|
|
4447
|
-
this.state.set('mounting');
|
|
4448
|
-
this.beforeOpened$.next();
|
|
4449
|
-
lifecycle.enter();
|
|
4450
|
-
lifecycle.state$
|
|
4451
|
-
.pipe(filter((s) => s === 'entered'), tap(() => {
|
|
4452
|
-
if (this.state() === 'mounting') {
|
|
4453
|
-
this.state.set('mounted');
|
|
4454
|
-
this.afterOpened$.next();
|
|
4455
|
-
}
|
|
4456
|
-
}), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4457
|
-
.subscribe();
|
|
4458
|
-
}
|
|
4459
|
-
return this.componentRef.instance;
|
|
4460
|
-
}
|
|
4461
|
-
if (!this.canMount()) {
|
|
4462
|
-
if (isDevMode()) {
|
|
4463
|
-
console.warn('AnimatedOverlayDirective: Cannot mount. Component is already mounted or currently mounting.');
|
|
4464
|
-
}
|
|
4465
|
-
return;
|
|
4466
|
-
}
|
|
4467
|
-
const { component, providers, data, colorProvider } = config;
|
|
4468
|
-
this.state.set('mounting');
|
|
4469
|
-
this.beforeOpened$.next();
|
|
4470
|
-
const injector = Injector.create({
|
|
4471
|
-
parent: this.injector,
|
|
4472
|
-
providers: providers ?? [],
|
|
4473
|
-
});
|
|
4474
|
-
this.overlayRef = this.overlayService.create();
|
|
4475
|
-
this.portal = new ComponentPortal(component, this.viewContainerRef, injector);
|
|
4476
|
-
this.componentRef = this.overlayRef.attach(this.portal);
|
|
4477
|
-
this.applyComponentData(data);
|
|
4478
|
-
this.applyColorProvider(colorProvider);
|
|
4479
|
-
this.updateOverlaySize();
|
|
4480
|
-
this.setupFloatingUI();
|
|
4481
|
-
return this.componentRef.instance;
|
|
4482
|
-
}
|
|
4483
|
-
unmount() {
|
|
4484
|
-
if (!this.isMounted() && !this.isMounting() && !this.isUnmounting()) {
|
|
4485
|
-
if (isDevMode()) {
|
|
4486
|
-
console.warn(`AnimatedOverlayDirective: Cannot unmount. Component is currently ${this.state()}`);
|
|
4487
|
-
}
|
|
4488
|
-
return;
|
|
4489
|
-
}
|
|
4490
|
-
if (this.isUnmounting()) {
|
|
4491
|
-
return;
|
|
4492
|
-
}
|
|
4493
|
-
if (!this.componentRef) {
|
|
4494
|
-
if (isDevMode()) {
|
|
4495
|
-
console.warn('AnimatedOverlayDirective: Cannot unmount. Component ref is not available.');
|
|
4496
|
-
}
|
|
4497
|
-
return;
|
|
4498
|
-
}
|
|
4499
|
-
this.state.set('unmounting');
|
|
4500
|
-
this.beforeClosed$.next();
|
|
4501
|
-
if (this.isHidden()) {
|
|
4502
|
-
this.destroy();
|
|
4503
|
-
return;
|
|
4504
|
-
}
|
|
4505
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle();
|
|
4506
|
-
if (!lifecycle) {
|
|
4507
|
-
this.destroy();
|
|
4508
|
-
return;
|
|
4509
|
-
}
|
|
4510
|
-
lifecycle.leave();
|
|
4511
|
-
this.unmountSubscription = lifecycle.state$
|
|
4512
|
-
.pipe(filter((s) => s === 'left'), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4513
|
-
.subscribe(() => {
|
|
4514
|
-
this.unmountSubscription = null;
|
|
4515
|
-
this.destroy();
|
|
4516
|
-
});
|
|
4517
|
-
}
|
|
4518
|
-
applyComponentData(data) {
|
|
4519
|
-
if (!this.componentRef || !data)
|
|
4520
|
-
return;
|
|
4521
|
-
Object.assign(this.componentRef.instance, data);
|
|
4522
|
-
this.componentRef.changeDetectorRef.markForCheck();
|
|
4523
|
-
}
|
|
4524
|
-
applyColorProvider(colorProvider) {
|
|
4525
|
-
if (!this.componentRef || !colorProvider)
|
|
4526
|
-
return;
|
|
4527
|
-
this.componentRef.instance.setColorFromProvider(colorProvider);
|
|
4528
|
-
}
|
|
4529
|
-
updateOverlaySize() {
|
|
4530
|
-
if (!this.overlayRef || !this.mirrorWidth())
|
|
4531
|
-
return;
|
|
4532
|
-
this.overlayRef.updateSize({
|
|
4533
|
-
width: this.elementRef.nativeElement.offsetWidth,
|
|
4534
|
-
});
|
|
4535
|
-
}
|
|
4536
|
-
setupFloatingUI() {
|
|
4537
|
-
this.zone.runOutsideAngular(() => {
|
|
4538
|
-
if (!this.componentRef)
|
|
4539
|
-
return;
|
|
4540
|
-
const floatingEl = this.componentRef.location.nativeElement;
|
|
4541
|
-
const floatingElArrow = this.getFloatingArrow();
|
|
4542
|
-
floatingEl.classList.add('et-floating-element');
|
|
4543
|
-
const refEl = this.referenceElement();
|
|
4544
|
-
const boundary = this.rootBoundary?.value();
|
|
4545
|
-
this.floatingElCleanupFn = autoUpdate(refEl, floatingEl, () => {
|
|
4546
|
-
this.updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow);
|
|
4547
|
-
});
|
|
4548
|
-
this.waitForRenderAndAnimate();
|
|
4549
|
-
});
|
|
4550
|
-
}
|
|
4551
|
-
getFloatingArrow() {
|
|
4552
|
-
if (!this.componentRef)
|
|
4553
|
-
return null;
|
|
4554
|
-
return this.componentRef.location.nativeElement.querySelector('[et-floating-arrow]');
|
|
4555
|
-
}
|
|
4556
|
-
updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow) {
|
|
4557
|
-
if (!this.componentRef)
|
|
4558
|
-
return;
|
|
4559
|
-
const middleware = this.buildFloatingMiddleware(floatingEl, boundary, floatingElArrow);
|
|
4560
|
-
computePosition(refEl, floatingEl, {
|
|
4561
|
-
placement: this.placement(),
|
|
4562
|
-
middleware,
|
|
4563
|
-
}).then(({ x, y, placement, middlewareData }) => {
|
|
4564
|
-
this.applyFloatingStyles(floatingEl, x, y, placement);
|
|
4565
|
-
this.applyArrowStyles(floatingEl, middlewareData, floatingElArrow);
|
|
4566
|
-
this.handleReferenceHidden(floatingEl, middlewareData);
|
|
4567
|
-
});
|
|
4568
|
-
}
|
|
4569
|
-
buildFloatingMiddleware(floatingEl, boundary, floatingElArrow) {
|
|
4570
|
-
const middleware = [];
|
|
4571
|
-
const offsetValue = this.offset();
|
|
4572
|
-
if (offsetValue !== null) {
|
|
4573
|
-
middleware.push(offset(offsetValue));
|
|
4574
|
-
}
|
|
4575
|
-
middleware.push(flip({
|
|
4576
|
-
fallbackPlacements: this.fallbackPlacements() ?? undefined,
|
|
4577
|
-
fallbackAxisSideDirection: 'start',
|
|
4578
|
-
boundary,
|
|
4579
|
-
}));
|
|
4580
|
-
if (this.autoResize()) {
|
|
4581
|
-
middleware.push(size({
|
|
4582
|
-
padding: this.viewportPadding() ?? undefined,
|
|
4583
|
-
apply({ availableHeight, availableWidth }) {
|
|
4584
|
-
floatingEl.style.setProperty('--et-floating-max-width', `${availableWidth}px`);
|
|
4585
|
-
floatingEl.style.setProperty('--et-floating-max-height', `${availableHeight}px`);
|
|
4586
|
-
},
|
|
4587
|
-
}));
|
|
4588
|
-
}
|
|
4589
|
-
if (this.shift()) {
|
|
4590
|
-
middleware.push(shift({
|
|
4591
|
-
limiter: limitShift(),
|
|
4592
|
-
padding: this.viewportPadding() ?? undefined,
|
|
4593
|
-
boundary,
|
|
4594
|
-
}));
|
|
4595
|
-
}
|
|
4596
|
-
if (floatingElArrow) {
|
|
4597
|
-
middleware.push(arrow({ element: floatingElArrow, padding: this.arrowPadding() ?? undefined }));
|
|
4598
|
-
}
|
|
4599
|
-
if (this.autoHide() || this.autoCloseIfReferenceHidden()) {
|
|
4600
|
-
middleware.push(hide({ strategy: 'referenceHidden', boundary }));
|
|
4601
|
-
}
|
|
4602
|
-
return middleware;
|
|
4603
|
-
}
|
|
4604
|
-
applyFloatingStyles(floatingEl, x, y, placement) {
|
|
4605
|
-
floatingEl.style.setProperty('--et-floating-translate', `translate3d(${x}px, ${y}px, 0)`);
|
|
4606
|
-
floatingEl.setAttribute('et-floating-placement', placement);
|
|
4607
|
-
}
|
|
4608
|
-
applyArrowStyles(floatingEl, middlewareData, floatingElArrow) {
|
|
4609
|
-
if (!middlewareData.arrow || !floatingElArrow)
|
|
4610
|
-
return;
|
|
4611
|
-
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
|
4612
|
-
floatingEl.style.setProperty('--et-floating-arrow-translate', `translate3d(${arrowX ?? 0}px, ${arrowY ?? 0}px, 0)`);
|
|
4613
|
-
}
|
|
4614
|
-
handleReferenceHidden(floatingEl, middlewareData) {
|
|
4615
|
-
if (middlewareData.hide?.referenceHidden) {
|
|
4616
|
-
if (this.autoCloseIfReferenceHidden()) {
|
|
4617
|
-
this.unmount();
|
|
4618
|
-
}
|
|
4619
|
-
else {
|
|
4620
|
-
floatingEl.classList.add('et-floating-element--hidden');
|
|
4621
|
-
this.isHidden.set(true);
|
|
4622
|
-
}
|
|
4623
|
-
}
|
|
4624
|
-
else if (this.autoHide()) {
|
|
4625
|
-
floatingEl.classList.remove('et-floating-element--hidden');
|
|
4626
|
-
this.isHidden.set(false);
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
waitForRenderAndAnimate() {
|
|
4630
|
-
nextFrame(() => {
|
|
4631
|
-
if (!this.componentRef || this.state() !== 'mounting') {
|
|
4632
|
-
return;
|
|
4633
|
-
}
|
|
4634
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle?.();
|
|
4635
|
-
if (!lifecycle) {
|
|
4636
|
-
console.error('AnimatedOverlayDirective: The component does not have an AnimatedLifecycleDirective. Please add one to the component.');
|
|
4637
|
-
return;
|
|
4638
|
-
}
|
|
4639
|
-
lifecycle.enter();
|
|
4640
|
-
lifecycle.state$
|
|
4641
|
-
.pipe(filter((s) => s === 'entered'), tap(() => {
|
|
4642
|
-
if (this.state() === 'mounting') {
|
|
4643
|
-
this.state.set('mounted');
|
|
4644
|
-
this.afterOpened$.next();
|
|
4645
|
-
}
|
|
4646
|
-
}), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4647
|
-
.subscribe();
|
|
4648
|
-
});
|
|
4649
|
-
}
|
|
4650
|
-
destroy() {
|
|
4651
|
-
this.zone.runOutsideAngular(() => {
|
|
4652
|
-
this.floatingElCleanupFn?.();
|
|
4653
|
-
});
|
|
4654
|
-
if (this.overlayRef) {
|
|
4655
|
-
this.overlayRef.dispose();
|
|
4656
|
-
this.overlayRef = null;
|
|
4657
|
-
}
|
|
4658
|
-
if (this.componentRef) {
|
|
4659
|
-
this.componentRef.destroy();
|
|
4660
|
-
this.componentRef = null;
|
|
4661
|
-
}
|
|
4662
|
-
this.state.set('unmounted');
|
|
4663
|
-
this.afterClosed$.next();
|
|
4664
|
-
}
|
|
4665
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4666
|
-
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 }); }
|
|
4667
|
-
}
|
|
4668
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, decorators: [{
|
|
4669
|
-
type: Directive,
|
|
4670
|
-
args: [{
|
|
4671
|
-
selector: '[etAnimatedOverlay]',
|
|
4672
|
-
exportAs: 'etAnimatedOverlay',
|
|
4673
|
-
host: {
|
|
4674
|
-
class: 'et-animated-overlay',
|
|
4675
|
-
},
|
|
4676
|
-
}]
|
|
4677
|
-
}], 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 }] }] } });
|
|
4678
|
-
|
|
4679
4637
|
const createFlipAnimationGroup = (config) => {
|
|
4680
|
-
const { elements, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)' } = config;
|
|
4638
|
+
const { elements, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)', ignoreReducedMotion } = config;
|
|
4681
4639
|
const flips = elements.map((el) => {
|
|
4682
4640
|
const element = 'element' in el ? el.element : el;
|
|
4683
4641
|
const originElement = 'originElement' in el ? el.originElement : undefined;
|
|
4684
|
-
return createFlipAnimation({ element, originElement, duration, easing });
|
|
4642
|
+
return createFlipAnimation({ element, originElement, duration, easing, ignoreReducedMotion });
|
|
4685
4643
|
});
|
|
4686
4644
|
const onStart$ = combineLatest(flips.map((animation) => animation.onStart$));
|
|
4687
4645
|
const onFinish$ = combineLatest(flips.map((animation) => animation.onFinish$));
|
|
@@ -4705,7 +4663,7 @@ const createFlipAnimationGroup = (config) => {
|
|
|
4705
4663
|
};
|
|
4706
4664
|
};
|
|
4707
4665
|
const createFlipAnimation = (config) => {
|
|
4708
|
-
const { element: el, originElement = el, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)' } = config;
|
|
4666
|
+
const { element: el, originElement = el, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)', ignoreReducedMotion = false, } = config;
|
|
4709
4667
|
let initialRect = originElement.getBoundingClientRect();
|
|
4710
4668
|
let animation = null;
|
|
4711
4669
|
const onStart$ = new Subject();
|
|
@@ -4737,6 +4695,10 @@ const createFlipAnimation = (config) => {
|
|
|
4737
4695
|
scaleX: initialRect.width / lastRect.width,
|
|
4738
4696
|
scaleY: initialRect.height / lastRect.height,
|
|
4739
4697
|
};
|
|
4698
|
+
// Under reduced motion the element jumps straight to its final position. Collapsing the duration
|
|
4699
|
+
// instead of bailing out keeps the `Animation` lifecycle intact, so callers that wait on
|
|
4700
|
+
// `onFinish$` to clean up still get their event.
|
|
4701
|
+
const effectiveDuration = !ignoreReducedMotion && matchesReducedMotion(el) ? 0 : duration;
|
|
4740
4702
|
animation = el.animate([
|
|
4741
4703
|
{
|
|
4742
4704
|
transformOrigin: 'top left',
|
|
@@ -4750,7 +4712,7 @@ const createFlipAnimation = (config) => {
|
|
|
4750
4712
|
transform: 'none',
|
|
4751
4713
|
},
|
|
4752
4714
|
], {
|
|
4753
|
-
duration,
|
|
4715
|
+
duration: effectiveDuration,
|
|
4754
4716
|
easing,
|
|
4755
4717
|
fill: 'both',
|
|
4756
4718
|
});
|
|
@@ -5015,7 +4977,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
5015
4977
|
}]
|
|
5016
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"] }] } });
|
|
5017
4979
|
|
|
5018
|
-
const FOCUSABLE_SELECTOR = [
|
|
4980
|
+
const FOCUSABLE_SELECTOR = /* @__PURE__ */ [
|
|
5019
4981
|
'a[href]',
|
|
5020
4982
|
'area[href]',
|
|
5021
4983
|
'button:not([disabled])',
|
|
@@ -5176,6 +5138,56 @@ const resetPositioningStyles = (config, hostElement, paneElement, renderer) => {
|
|
|
5176
5138
|
renderer.removeAttribute(paneElement, 'data-overlay-placement');
|
|
5177
5139
|
setBaseElementStyles(config, hostElement, paneElement, renderer);
|
|
5178
5140
|
};
|
|
5141
|
+
let anchoredPositionSetup = null;
|
|
5142
|
+
/**
|
|
5143
|
+
* Installs the anchored positioning implementation. Called by `anchoredOverlayPosition()`, which is
|
|
5144
|
+
* the only thing that pulls `@floating-ui/dom` into the bundle - apps that never anchor an overlay
|
|
5145
|
+
* do not ship it.
|
|
5146
|
+
*
|
|
5147
|
+
* @internal
|
|
5148
|
+
*/
|
|
5149
|
+
const registerAnchoredPositionSetup = (setup) => {
|
|
5150
|
+
anchoredPositionSetup = setup;
|
|
5151
|
+
};
|
|
5152
|
+
const setupPositioning = (config, hostElement, paneElement, overlayRef, renderer) => {
|
|
5153
|
+
const strategy = config.positionStrategy ?? { kind: 'center' };
|
|
5154
|
+
if (strategy.kind === 'anchored') {
|
|
5155
|
+
renderer.setStyle(hostElement, {
|
|
5156
|
+
pointerEvents: config.hasBackdrop === false ? 'none' : 'auto',
|
|
5157
|
+
});
|
|
5158
|
+
if (!anchoredPositionSetup) {
|
|
5159
|
+
if (isDevMode()) {
|
|
5160
|
+
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);
|
|
5161
|
+
}
|
|
5162
|
+
applyCenteredPosition(hostElement, paneElement, renderer, { kind: 'center' });
|
|
5163
|
+
return () => undefined;
|
|
5164
|
+
}
|
|
5165
|
+
return anchoredPositionSetup(strategy, paneElement, overlayRef, renderer);
|
|
5166
|
+
}
|
|
5167
|
+
if (strategy.kind === 'global') {
|
|
5168
|
+
applyGlobalPosition(hostElement, paneElement, renderer, strategy);
|
|
5169
|
+
return () => undefined;
|
|
5170
|
+
}
|
|
5171
|
+
applyCenteredPosition(hostElement, paneElement, renderer, strategy);
|
|
5172
|
+
return () => undefined;
|
|
5173
|
+
};
|
|
5174
|
+
|
|
5175
|
+
let middlewareExtras = null;
|
|
5176
|
+
/**
|
|
5177
|
+
* Installs the `size` / `arrow` / `hide` middleware. Called by
|
|
5178
|
+
* `enableAnchoredOverlayPositionExtras()`.
|
|
5179
|
+
*
|
|
5180
|
+
* @internal
|
|
5181
|
+
*/
|
|
5182
|
+
const registerAnchoredPositionMiddlewareExtras = (extras) => {
|
|
5183
|
+
middlewareExtras = extras;
|
|
5184
|
+
};
|
|
5185
|
+
const requireMiddlewareExtras = (feature) => {
|
|
5186
|
+
if (!middlewareExtras && isDevMode()) {
|
|
5187
|
+
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.`);
|
|
5188
|
+
}
|
|
5189
|
+
return middlewareExtras;
|
|
5190
|
+
};
|
|
5179
5191
|
const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, renderer) => {
|
|
5180
5192
|
// mirrorWidth needs a real element to measure; virtual references fall back to max-content
|
|
5181
5193
|
const mirrorWidthElement = strategy.mirrorWidth && isHTMLElement(strategy.referenceElement) ? strategy.referenceElement : null;
|
|
@@ -5202,29 +5214,41 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
5202
5214
|
boundary: strategy.boundary,
|
|
5203
5215
|
}));
|
|
5204
5216
|
}
|
|
5205
|
-
// size must run AFTER shift so the available space is measured from the shifted position
|
|
5217
|
+
// size must run AFTER shift so the available space is measured from the shifted position -
|
|
5206
5218
|
// otherwise a cross-axis-shifted pane gets its max size capped to the unshifted leftover space
|
|
5207
5219
|
if (strategy.autoResize) {
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5220
|
+
const extras = requireMiddlewareExtras('autoResize');
|
|
5221
|
+
if (extras) {
|
|
5222
|
+
middleware.push(extras.size({
|
|
5223
|
+
padding: strategy.viewportPadding ?? 8,
|
|
5224
|
+
apply({ availableHeight, availableWidth }) {
|
|
5225
|
+
renderer.setCssProperties(paneElement, {
|
|
5226
|
+
'--et-overlay-max-width': `${availableWidth}px`,
|
|
5227
|
+
'--et-overlay-max-height': `${availableHeight}px`,
|
|
5228
|
+
});
|
|
5229
|
+
},
|
|
5230
|
+
}));
|
|
5231
|
+
}
|
|
5217
5232
|
}
|
|
5218
5233
|
if (arrowElement) {
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5234
|
+
const extras = requireMiddlewareExtras('arrow');
|
|
5235
|
+
if (extras) {
|
|
5236
|
+
middleware.push(extras.arrow({
|
|
5237
|
+
element: arrowElement,
|
|
5238
|
+
// keeps the arrow off a rounded pane corner by default: 12px covers the radius of the
|
|
5239
|
+
// built-in boxed panes (--et-overlay-radius). Panes with a larger radius have to pass their
|
|
5240
|
+
// own - an arrow whose base sits on the corner arc looks detached from the pane.
|
|
5241
|
+
padding: strategy.arrowPadding ?? 12,
|
|
5242
|
+
}));
|
|
5243
|
+
}
|
|
5223
5244
|
}
|
|
5224
5245
|
if (strategy.autoHide || strategy.autoCloseIfReferenceHidden) {
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5246
|
+
const extras = requireMiddlewareExtras('autoHide');
|
|
5247
|
+
if (extras) {
|
|
5248
|
+
middleware.push(extras.hide({
|
|
5249
|
+
strategy: 'referenceHidden',
|
|
5250
|
+
}));
|
|
5251
|
+
}
|
|
5228
5252
|
}
|
|
5229
5253
|
computePosition(strategy.referenceElement, paneElement, {
|
|
5230
5254
|
placement: strategy.placement ?? 'bottom',
|
|
@@ -5265,20 +5289,29 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
5265
5289
|
cleanup();
|
|
5266
5290
|
};
|
|
5267
5291
|
};
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
return
|
|
5292
|
+
/**
|
|
5293
|
+
* Builds an anchored position strategy and installs the anchored positioning implementation.
|
|
5294
|
+
*
|
|
5295
|
+
* Anchored positioning is the only part of the overlay runtime that needs `@floating-ui/dom`, and it
|
|
5296
|
+
* is reachable only through this function - an app that positions its overlays centered or globally
|
|
5297
|
+
* never bundles it. Always build anchored strategies with this helper instead of a plain
|
|
5298
|
+
* `{ kind: 'anchored', ... }` literal.
|
|
5299
|
+
*
|
|
5300
|
+
* Overlays that set `autoResize`, `autoHide`, `autoCloseIfReferenceHidden` or render an arrow
|
|
5301
|
+
* additionally need `enableAnchoredOverlayPositionExtras()`.
|
|
5302
|
+
*/
|
|
5303
|
+
const anchoredOverlayPosition = (config) => {
|
|
5304
|
+
registerAnchoredPositionSetup(createAnchoredPositionCleanup);
|
|
5305
|
+
return { kind: 'anchored', ...config };
|
|
5306
|
+
};
|
|
5307
|
+
|
|
5308
|
+
/**
|
|
5309
|
+
* Installs the floating-ui middleware anchored overlays need for `autoResize`, `autoHide`,
|
|
5310
|
+
* `autoCloseIfReferenceHidden` and arrows. Call it once wherever such an overlay is built - an
|
|
5311
|
+
* anchored overlay that uses none of those features does not bundle this middleware.
|
|
5312
|
+
*/
|
|
5313
|
+
const enableAnchoredOverlayPositionExtras = () => {
|
|
5314
|
+
registerAnchoredPositionMiddlewareExtras({ size, arrow, hide });
|
|
5282
5315
|
};
|
|
5283
5316
|
|
|
5284
5317
|
const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
@@ -5298,13 +5331,13 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5298
5331
|
elements,
|
|
5299
5332
|
state: _state.asReadonly(),
|
|
5300
5333
|
componentInstance: _componentInstance.asReadonly(),
|
|
5301
|
-
// Internal
|
|
5334
|
+
// Internal - driven by overlay-runtime.ts
|
|
5302
5335
|
beforeOpenedSubject,
|
|
5303
5336
|
close(result, source = 'api') {
|
|
5304
5337
|
if (_state() === 'closing' || _state() === 'closed') {
|
|
5305
5338
|
return;
|
|
5306
5339
|
}
|
|
5307
|
-
// `reference-detached` is a forced teardown (the anchor is gone)
|
|
5340
|
+
// `reference-detached` is a forced teardown (the anchor is gone) - never vetoable.
|
|
5308
5341
|
if (source !== 'reference-detached') {
|
|
5309
5342
|
for (const guard of closeGuards) {
|
|
5310
5343
|
if (!guard({ result, source })) {
|
|
@@ -5314,7 +5347,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5314
5347
|
}
|
|
5315
5348
|
requestClose(result, source);
|
|
5316
5349
|
},
|
|
5317
|
-
/** Close bypassing every registered close guard
|
|
5350
|
+
/** Close bypassing every registered close guard - used by a guard's owner to commit a
|
|
5318
5351
|
* close it previously vetoed (e.g. after an async confirm resolved). */
|
|
5319
5352
|
forceClose(result, source = 'api') {
|
|
5320
5353
|
if (_state() === 'closing' || _state() === 'closed') {
|
|
@@ -5381,7 +5414,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5381
5414
|
};
|
|
5382
5415
|
};
|
|
5383
5416
|
|
|
5384
|
-
const
|
|
5417
|
+
const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
5385
5418
|
const appRef = inject(ApplicationRef);
|
|
5386
5419
|
const destroyRef = inject(DestroyRef);
|
|
5387
5420
|
const document = inject(DOCUMENT);
|
|
@@ -5488,7 +5521,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5488
5521
|
hostElement: paneElement,
|
|
5489
5522
|
bindings: config.bindings ?? [],
|
|
5490
5523
|
});
|
|
5491
|
-
// pane classes are applied after component creation
|
|
5524
|
+
// pane classes are applied after component creation - Angular replaces the host
|
|
5492
5525
|
// element's class attribute with the component's static host class on creation
|
|
5493
5526
|
renderer.addClass(paneElement, 'et-overlay-runtime-pane', ...(config.paneClass ?? []));
|
|
5494
5527
|
appRef.attachView(componentRef.hostView);
|
|
@@ -5535,7 +5568,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5535
5568
|
}
|
|
5536
5569
|
const lifecycle = getAnimatedLifecycle(componentRef);
|
|
5537
5570
|
animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state$.value ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
|
|
5538
|
-
// The reference element is gone
|
|
5571
|
+
// The reference element is gone - there is nothing left to animate away from, so tear the
|
|
5539
5572
|
// overlay down synchronously instead of playing a leave transition from a stale position.
|
|
5540
5573
|
if (!lifecycle || closeEvent.source === 'reference-detached') {
|
|
5541
5574
|
destroyMountedOverlay(closeEvent);
|
|
@@ -5644,6 +5677,8 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5644
5677
|
openEntries,
|
|
5645
5678
|
};
|
|
5646
5679
|
}, { name: 'OverlayRuntime' });
|
|
5680
|
+
const provideOverlayRuntime = /* @__PURE__ */ toProvideFn(OVERLAY_RUNTIME_DEF);
|
|
5681
|
+
const injectOverlayRuntime = /* @__PURE__ */ toInjectFn(OVERLAY_RUNTIME_DEF);
|
|
5647
5682
|
|
|
5648
5683
|
class NormalizeGameResultTypePipe {
|
|
5649
5684
|
constructor() {
|
|
@@ -6684,7 +6719,7 @@ const toStringBinding = (binding) => {
|
|
|
6684
6719
|
return value !== null && value !== undefined ? String(value) : null;
|
|
6685
6720
|
});
|
|
6686
6721
|
};
|
|
6687
|
-
const createBulkPropertyBinding = (
|
|
6722
|
+
const createBulkPropertyBinding = (_propertyPrefix, applyConfigFn) => {
|
|
6688
6723
|
return (config) => {
|
|
6689
6724
|
Object.entries(config).forEach(([key, value]) => {
|
|
6690
6725
|
if (value !== undefined) {
|
|
@@ -6694,11 +6729,202 @@ const createBulkPropertyBinding = (propertyPrefix, applyConfigFn) => {
|
|
|
6694
6729
|
};
|
|
6695
6730
|
};
|
|
6696
6731
|
|
|
6732
|
+
/** The rendered favicon is this many device-independent pixels square. */
|
|
6733
|
+
const FAVICON_SIZE = 64;
|
|
6734
|
+
/** Fallback when the color theming tokens aren't resolvable (no theme applied to the root element). */
|
|
6735
|
+
const FALLBACK_OVERLAY_COLOR = '#e11d48';
|
|
6736
|
+
const readThemeColor = (document) => {
|
|
6737
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue('--et-theme-color-primary-solid').trim();
|
|
6738
|
+
return value || FALLBACK_OVERLAY_COLOR;
|
|
6739
|
+
};
|
|
6740
|
+
const drawDot = (ctx, color) => {
|
|
6741
|
+
const radius = FAVICON_SIZE * 0.19;
|
|
6742
|
+
const center = FAVICON_SIZE - radius - 3;
|
|
6743
|
+
// Punch a hole first so the badge reads on top of a busy icon, then fill it.
|
|
6744
|
+
ctx.save();
|
|
6745
|
+
ctx.globalCompositeOperation = 'destination-out';
|
|
6746
|
+
ctx.beginPath();
|
|
6747
|
+
ctx.arc(center, center, radius * 1.35, 0, Math.PI * 2);
|
|
6748
|
+
ctx.fill();
|
|
6749
|
+
ctx.restore();
|
|
6750
|
+
ctx.beginPath();
|
|
6751
|
+
ctx.arc(center, center, radius, 0, Math.PI * 2);
|
|
6752
|
+
ctx.fillStyle = color;
|
|
6753
|
+
ctx.fill();
|
|
6754
|
+
};
|
|
6755
|
+
const drawProgress = (ctx, value, color, trackColor) => {
|
|
6756
|
+
const lineWidth = FAVICON_SIZE * 0.14;
|
|
6757
|
+
const radius = (FAVICON_SIZE - lineWidth) / 2;
|
|
6758
|
+
const center = FAVICON_SIZE / 2;
|
|
6759
|
+
const clamped = Math.min(100, Math.max(0, value));
|
|
6760
|
+
const start = -Math.PI / 2;
|
|
6761
|
+
ctx.lineWidth = lineWidth;
|
|
6762
|
+
ctx.lineCap = 'butt';
|
|
6763
|
+
// Clear the ring's lane so the base icon doesn't show through a semi-transparent track.
|
|
6764
|
+
ctx.save();
|
|
6765
|
+
ctx.globalCompositeOperation = 'destination-out';
|
|
6766
|
+
ctx.strokeStyle = '#000';
|
|
6767
|
+
ctx.beginPath();
|
|
6768
|
+
ctx.arc(center, center, radius, 0, Math.PI * 2);
|
|
6769
|
+
ctx.stroke();
|
|
6770
|
+
ctx.restore();
|
|
6771
|
+
ctx.strokeStyle = trackColor;
|
|
6772
|
+
ctx.beginPath();
|
|
6773
|
+
ctx.arc(center, center, radius, 0, Math.PI * 2);
|
|
6774
|
+
ctx.stroke();
|
|
6775
|
+
if (clamped > 0) {
|
|
6776
|
+
ctx.strokeStyle = color;
|
|
6777
|
+
ctx.beginPath();
|
|
6778
|
+
ctx.arc(center, center, radius, start, start + (Math.PI * 2 * clamped) / 100);
|
|
6779
|
+
ctx.stroke();
|
|
6780
|
+
}
|
|
6781
|
+
};
|
|
6782
|
+
const FAVICON_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
6783
|
+
const document = inject(DOCUMENT);
|
|
6784
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
6785
|
+
const overlays = signal(new Map(), /* @ts-ignore */
|
|
6786
|
+
...(ngDevMode ? [{ debugName: "overlays" }] : /* istanbul ignore next */ []));
|
|
6787
|
+
const activeOverlay = computed(() => {
|
|
6788
|
+
const all = Array.from(overlays().values());
|
|
6789
|
+
return all.find((overlay) => overlay.kind === 'progress') ?? all[all.length - 1] ?? null;
|
|
6790
|
+
}, /* @ts-ignore */
|
|
6791
|
+
...(ngDevMode ? [{ debugName: "activeOverlay" }] : /* istanbul ignore next */ []));
|
|
6792
|
+
let iconLink = null;
|
|
6793
|
+
let createdIconLink = false;
|
|
6794
|
+
let originalHref = null;
|
|
6795
|
+
let baseImage = null;
|
|
6796
|
+
let baseImageFailed = false;
|
|
6797
|
+
const resolveIconLink = () => {
|
|
6798
|
+
if (iconLink) {
|
|
6799
|
+
return iconLink;
|
|
6800
|
+
}
|
|
6801
|
+
iconLink = document.querySelector('link[rel~="icon"]');
|
|
6802
|
+
if (!iconLink) {
|
|
6803
|
+
iconLink = document.createElement('link');
|
|
6804
|
+
iconLink.rel = 'icon';
|
|
6805
|
+
document.head.appendChild(iconLink);
|
|
6806
|
+
createdIconLink = true;
|
|
6807
|
+
}
|
|
6808
|
+
originalHref = createdIconLink ? null : iconLink.getAttribute('href');
|
|
6809
|
+
return iconLink;
|
|
6810
|
+
};
|
|
6811
|
+
const restore = () => {
|
|
6812
|
+
if (!iconLink)
|
|
6813
|
+
return;
|
|
6814
|
+
if (createdIconLink) {
|
|
6815
|
+
iconLink.remove();
|
|
6816
|
+
iconLink = null;
|
|
6817
|
+
createdIconLink = false;
|
|
6818
|
+
return;
|
|
6819
|
+
}
|
|
6820
|
+
if (originalHref === null) {
|
|
6821
|
+
iconLink.removeAttribute('href');
|
|
6822
|
+
}
|
|
6823
|
+
else {
|
|
6824
|
+
iconLink.setAttribute('href', originalHref);
|
|
6825
|
+
}
|
|
6826
|
+
};
|
|
6827
|
+
// The base icon is loaded once and reused; a favicon that can't be read (cross-origin without
|
|
6828
|
+
// CORS headers, an SVG without intrinsic size) draws on an empty canvas instead of failing.
|
|
6829
|
+
const loadBaseImage = () => {
|
|
6830
|
+
if (baseImage || baseImageFailed || !originalHref) {
|
|
6831
|
+
return Promise.resolve(baseImage);
|
|
6832
|
+
}
|
|
6833
|
+
return new Promise((resolve) => {
|
|
6834
|
+
const image = new Image();
|
|
6835
|
+
image.onload = () => {
|
|
6836
|
+
baseImage = image;
|
|
6837
|
+
resolve(image);
|
|
6838
|
+
};
|
|
6839
|
+
image.onerror = () => {
|
|
6840
|
+
baseImageFailed = true;
|
|
6841
|
+
resolve(null);
|
|
6842
|
+
};
|
|
6843
|
+
image.src = originalHref;
|
|
6844
|
+
});
|
|
6845
|
+
};
|
|
6846
|
+
const render = async (overlay) => {
|
|
6847
|
+
const canvas = document.createElement('canvas');
|
|
6848
|
+
canvas.width = FAVICON_SIZE;
|
|
6849
|
+
canvas.height = FAVICON_SIZE;
|
|
6850
|
+
// No 2d context (a non-browser DOM implementation) - leave the favicon untouched entirely.
|
|
6851
|
+
const ctx = canvas.getContext('2d');
|
|
6852
|
+
if (!ctx)
|
|
6853
|
+
return;
|
|
6854
|
+
const link = resolveIconLink();
|
|
6855
|
+
const image = await loadBaseImage();
|
|
6856
|
+
// A later state may have won the race while the base image loaded.
|
|
6857
|
+
if (activeOverlay() !== overlay)
|
|
6858
|
+
return;
|
|
6859
|
+
if (image) {
|
|
6860
|
+
ctx.drawImage(image, 0, 0, FAVICON_SIZE, FAVICON_SIZE);
|
|
6861
|
+
}
|
|
6862
|
+
const color = overlay.color ?? readThemeColor(document);
|
|
6863
|
+
if (overlay.kind === 'dot') {
|
|
6864
|
+
drawDot(ctx, color);
|
|
6865
|
+
}
|
|
6866
|
+
else {
|
|
6867
|
+
drawProgress(ctx, overlay.value, color, overlay.trackColor ?? 'rgba(127, 127, 127, 0.35)');
|
|
6868
|
+
}
|
|
6869
|
+
try {
|
|
6870
|
+
link.setAttribute('href', canvas.toDataURL('image/png'));
|
|
6871
|
+
}
|
|
6872
|
+
catch {
|
|
6873
|
+
// A tainted canvas (cross-origin base icon) can't be exported - leave the favicon as it was.
|
|
6874
|
+
restore();
|
|
6875
|
+
}
|
|
6876
|
+
};
|
|
6877
|
+
if (isBrowser) {
|
|
6878
|
+
effect(() => {
|
|
6879
|
+
const overlay = activeOverlay();
|
|
6880
|
+
if (!overlay) {
|
|
6881
|
+
restore();
|
|
6882
|
+
return;
|
|
6883
|
+
}
|
|
6884
|
+
void render(overlay);
|
|
6885
|
+
});
|
|
6886
|
+
}
|
|
6887
|
+
const addOverlay = (id, overlay) => {
|
|
6888
|
+
overlays.update((current) => new Map(current).set(id, overlay));
|
|
6889
|
+
};
|
|
6890
|
+
const removeOverlay = (id) => {
|
|
6891
|
+
overlays.update((current) => {
|
|
6892
|
+
const next = new Map(current);
|
|
6893
|
+
next.delete(id);
|
|
6894
|
+
return next;
|
|
6895
|
+
});
|
|
6896
|
+
};
|
|
6897
|
+
return { activeOverlay, addOverlay, removeOverlay };
|
|
6898
|
+
}, { name: 'Favicon Store' });
|
|
6899
|
+
/**
|
|
6900
|
+
* Owns the `<link rel="icon">` href so overlays can be drawn onto the site's favicon and taken back
|
|
6901
|
+
* off again. The original href is captured on first use and restored once the last overlay is gone.
|
|
6902
|
+
*
|
|
6903
|
+
* Registrations are keyed, and a `progress` overlay wins over a `dot` - a ring that also carries a
|
|
6904
|
+
* badge reads as neither.
|
|
6905
|
+
*/
|
|
6906
|
+
const provideFaviconStore = /* @__PURE__ */ toProvideFn(FAVICON_STORE_DEF);
|
|
6907
|
+
const injectFaviconStore = /* @__PURE__ */ toInjectFn(FAVICON_STORE_DEF);
|
|
6908
|
+
/**
|
|
6909
|
+
* Draw an overlay on the site's favicon while the binding has a value - an unsaved-changes dot, an
|
|
6910
|
+
* upload's progress ring. The favicon is restored when the binding goes empty or the injector is
|
|
6911
|
+
* destroyed. No-op during SSR.
|
|
6912
|
+
*
|
|
6913
|
+
* ```ts
|
|
6914
|
+
* applyFaviconOverlay(computed(() => (this.saving() ? { kind: 'progress', value: this.percent() } : null)));
|
|
6915
|
+
* ```
|
|
6916
|
+
*/
|
|
6917
|
+
const applyFaviconOverlay = (binding) => {
|
|
6918
|
+
const faviconStore = injectFaviconStore();
|
|
6919
|
+
const overlayId = Symbol('favicon-overlay');
|
|
6920
|
+
applyHeadBinding(binding, (value) => faviconStore.addOverlay(overlayId, value), () => faviconStore.removeOverlay(overlayId));
|
|
6921
|
+
};
|
|
6922
|
+
|
|
6697
6923
|
var jsonLd = /*#__PURE__*/Object.freeze({
|
|
6698
6924
|
__proto__: null
|
|
6699
6925
|
});
|
|
6700
6926
|
|
|
6701
|
-
const DEFAULT_MULTI_INSTANCE_RELS = new Set([
|
|
6927
|
+
const DEFAULT_MULTI_INSTANCE_RELS = /* @__PURE__ */ new Set([
|
|
6702
6928
|
'alternate',
|
|
6703
6929
|
'icon',
|
|
6704
6930
|
'apple-touch-icon',
|
|
@@ -6711,10 +6937,12 @@ const DEFAULT_MULTI_INSTANCE_RELS = new Set([
|
|
|
6711
6937
|
'stylesheet',
|
|
6712
6938
|
'modulepreload',
|
|
6713
6939
|
]);
|
|
6714
|
-
const
|
|
6940
|
+
const LINK_STORE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
6715
6941
|
multiInstanceRels: DEFAULT_MULTI_INSTANCE_RELS,
|
|
6716
6942
|
}, { name: 'Link Store Config' });
|
|
6717
|
-
const
|
|
6943
|
+
const provideLinkStoreConfig = /* @__PURE__ */ toProvideFn(LINK_STORE_CONFIG_DEF);
|
|
6944
|
+
const injectLinkStoreConfig = /* @__PURE__ */ toInjectFn(LINK_STORE_CONFIG_DEF);
|
|
6945
|
+
const LINK_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
6718
6946
|
const document = inject(DOCUMENT$1);
|
|
6719
6947
|
const config = injectLinkStoreConfig();
|
|
6720
6948
|
const linksByKey = signal(new Map(), /* @ts-ignore */
|
|
@@ -6750,7 +6978,6 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
|
|
|
6750
6978
|
};
|
|
6751
6979
|
const createLinkElement = (linkConfig) => {
|
|
6752
6980
|
const link = document.createElement('link');
|
|
6753
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6754
6981
|
const { key, allowMultiple, ...attributes } = linkConfig;
|
|
6755
6982
|
Object.entries(attributes).forEach(([attrKey, value]) => {
|
|
6756
6983
|
if (value !== undefined && value !== null) {
|
|
@@ -6835,6 +7062,8 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
|
|
|
6835
7062
|
};
|
|
6836
7063
|
return { addLink, removeLink, getLinkKey };
|
|
6837
7064
|
}, { name: 'Link Store' });
|
|
7065
|
+
const provideLinkStore = /* @__PURE__ */ toProvideFn(LINK_STORE_DEF);
|
|
7066
|
+
const injectLinkStore = /* @__PURE__ */ toInjectFn(LINK_STORE_DEF);
|
|
6838
7067
|
const applyLinkBinding = (binding) => {
|
|
6839
7068
|
const linkStore = injectLinkStore();
|
|
6840
7069
|
const linkId = Symbol('link-tag');
|
|
@@ -6844,7 +7073,7 @@ const applyLinkBinding = (binding) => {
|
|
|
6844
7073
|
linkStore.addLink(linkId, config);
|
|
6845
7074
|
}, () => linkStore.removeLink(linkId, currentKey));
|
|
6846
7075
|
};
|
|
6847
|
-
const applyCanonicalBinding = createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
|
|
7076
|
+
const applyCanonicalBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
|
|
6848
7077
|
const applyAlternateBinding = (hreflang, binding) => {
|
|
6849
7078
|
applyLinkBinding(computed(() => {
|
|
6850
7079
|
const url = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
@@ -6856,8 +7085,8 @@ const applyAlternateLanguagesBindings = (config) => {
|
|
|
6856
7085
|
applyAlternateBinding(hreflang, binding);
|
|
6857
7086
|
});
|
|
6858
7087
|
};
|
|
6859
|
-
const applyPrevBinding = createPropertyBinding((href) => ({ rel: 'prev', href }), applyLinkBinding);
|
|
6860
|
-
const applyNextBinding = createPropertyBinding((href) => ({ rel: 'next', href }), applyLinkBinding);
|
|
7088
|
+
const applyPrevBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'prev', href }), applyLinkBinding);
|
|
7089
|
+
const applyNextBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'next', href }), applyLinkBinding);
|
|
6861
7090
|
const applyResourceHintsBindings = (config) => {
|
|
6862
7091
|
if (config.preconnect) {
|
|
6863
7092
|
createArrayPropertyBinding((href, index) => ({ rel: 'preconnect', href, key: `preconnect:${index}` }), applyLinkBinding)(config.preconnect);
|
|
@@ -6873,20 +7102,26 @@ const applyResourceHintsBindings = (config) => {
|
|
|
6873
7102
|
}
|
|
6874
7103
|
};
|
|
6875
7104
|
|
|
6876
|
-
const
|
|
7105
|
+
const TITLE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
6877
7106
|
divider: '|',
|
|
6878
7107
|
defaultTitle: '',
|
|
6879
7108
|
transformer: (title) => title,
|
|
6880
7109
|
}, { name: 'Title Config' });
|
|
6881
|
-
const
|
|
7110
|
+
const provideTitleConfig = /* @__PURE__ */ toProvideFn(TITLE_CONFIG_DEF);
|
|
7111
|
+
const injectTitleConfig = /* @__PURE__ */ toInjectFn(TITLE_CONFIG_DEF);
|
|
7112
|
+
const TITLE_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
6882
7113
|
const config = injectTitleConfig();
|
|
6883
7114
|
const titleParts = signal(new Map(), /* @ts-ignore */
|
|
6884
7115
|
...(ngDevMode ? [{ debugName: "titleParts" }] : /* istanbul ignore next */ []));
|
|
7116
|
+
const titleMarkers = signal(new Map(), /* @ts-ignore */
|
|
7117
|
+
...(ngDevMode ? [{ debugName: "titleMarkers" }] : /* istanbul ignore next */ []));
|
|
6885
7118
|
const { currentLocale } = injectLocale();
|
|
6886
7119
|
const titleService = inject(Title);
|
|
6887
7120
|
const isRouterInitialized = injectIsRouterInitialized();
|
|
6888
|
-
|
|
6889
|
-
|
|
7121
|
+
// Falls back on an empty configured default too, so a store created lazily (e.g. by a title
|
|
7122
|
+
// marker) doesn't wipe a title that came from `index.html`.
|
|
7123
|
+
const defaultTitle = config.defaultTitle || titleService.getTitle();
|
|
7124
|
+
const composedTitle = computed(() => {
|
|
6890
7125
|
const partsMap = titleParts();
|
|
6891
7126
|
if (!partsMap.size || !isRouterInitialized()) {
|
|
6892
7127
|
return config.transformer(defaultTitle, currentLocale());
|
|
@@ -6910,6 +7145,20 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
|
|
|
6910
7145
|
}
|
|
6911
7146
|
return finalTextParts.join(` ${config.divider} `);
|
|
6912
7147
|
}, /* @ts-ignore */
|
|
7148
|
+
...(ngDevMode ? [{ debugName: "composedTitle" }] : /* istanbul ignore next */ []));
|
|
7149
|
+
/**
|
|
7150
|
+
* The composed title with any markers prefixed - what actually lands in `document.title`.
|
|
7151
|
+
* Markers sit outside the divider logic, so `● Page | App` rather than `● | Page | App`.
|
|
7152
|
+
*/
|
|
7153
|
+
const title = computed(() => {
|
|
7154
|
+
// Deduplicated: two forms with unsaved changes should read as one marker, not two.
|
|
7155
|
+
const markers = Array.from(new Set(titleMarkers().values()));
|
|
7156
|
+
const composed = composedTitle();
|
|
7157
|
+
if (!markers.length) {
|
|
7158
|
+
return composed;
|
|
7159
|
+
}
|
|
7160
|
+
return `${markers.join(' ')} ${composed}`.trim();
|
|
7161
|
+
}, /* @ts-ignore */
|
|
6913
7162
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
6914
7163
|
const addPart = (id, part) => {
|
|
6915
7164
|
titleParts.update((parts) => new Map(parts).set(id, part));
|
|
@@ -6921,19 +7170,41 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
|
|
|
6921
7170
|
return newParts;
|
|
6922
7171
|
});
|
|
6923
7172
|
};
|
|
7173
|
+
const addMarker = (id, marker) => {
|
|
7174
|
+
titleMarkers.update((markers) => new Map(markers).set(id, marker));
|
|
7175
|
+
};
|
|
7176
|
+
const removeMarker = (id) => {
|
|
7177
|
+
titleMarkers.update((markers) => {
|
|
7178
|
+
const newMarkers = new Map(markers);
|
|
7179
|
+
newMarkers.delete(id);
|
|
7180
|
+
return newMarkers;
|
|
7181
|
+
});
|
|
7182
|
+
};
|
|
6924
7183
|
effect(() => {
|
|
6925
7184
|
const titleText = title();
|
|
6926
7185
|
titleService.setTitle(titleText);
|
|
6927
7186
|
});
|
|
6928
|
-
return { title, addPart, removePart };
|
|
7187
|
+
return { title, addPart, removePart, addMarker, removeMarker };
|
|
6929
7188
|
}, { name: 'Title Store' });
|
|
7189
|
+
const provideTitleStore = /* @__PURE__ */ toProvideFn(TITLE_STORE_DEF);
|
|
7190
|
+
const injectTitleStore = /* @__PURE__ */ toInjectFn(TITLE_STORE_DEF);
|
|
6930
7191
|
const applyHeadTitleBinding = (binding, options) => {
|
|
6931
7192
|
const titleStore = injectTitleStore();
|
|
6932
7193
|
const partId = Symbol('title-part');
|
|
6933
7194
|
applyHeadBinding(binding, (value) => titleStore.addPart(partId, { text: `${value}`, ...options }), () => titleStore.removePart(partId), (value) => value !== null && value !== undefined && value !== '');
|
|
6934
7195
|
};
|
|
7196
|
+
/**
|
|
7197
|
+
* Prefix the tab title with a short marker (`● Page | App`) while the binding has a value - the
|
|
7198
|
+
* unsaved-changes dot, a pending-count badge, and the like. The marker is removed when the binding
|
|
7199
|
+
* goes empty and on destroy. Unlike a title part, it is not joined by the divider.
|
|
7200
|
+
*/
|
|
7201
|
+
const applyHeadTitleMarker = (binding) => {
|
|
7202
|
+
const titleStore = injectTitleStore();
|
|
7203
|
+
const markerId = Symbol('title-marker');
|
|
7204
|
+
applyHeadBinding(binding, (value) => titleStore.addMarker(markerId, value), () => titleStore.removeMarker(markerId), (value) => value !== null && value !== undefined && value !== '');
|
|
7205
|
+
};
|
|
6935
7206
|
|
|
6936
|
-
const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
|
|
7207
|
+
const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
|
|
6937
7208
|
// Open Graph - Media
|
|
6938
7209
|
'property="og:image"',
|
|
6939
7210
|
'property="og:image:url"',
|
|
@@ -7002,11 +7273,13 @@ const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
|
|
|
7002
7273
|
'name="p:domain_verify"', // Pinterest
|
|
7003
7274
|
'name="facebook-domain-verification"',
|
|
7004
7275
|
]);
|
|
7005
|
-
const
|
|
7276
|
+
const META_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
7006
7277
|
transformer: (content) => content,
|
|
7007
7278
|
multiInstanceTags: DEFAULT_MULTI_INSTANCE_TAGS,
|
|
7008
7279
|
}, { name: 'Meta Config' });
|
|
7009
|
-
const
|
|
7280
|
+
const provideMetaConfig = /* @__PURE__ */ toProvideFn(META_CONFIG_DEF);
|
|
7281
|
+
const injectMetaConfig = /* @__PURE__ */ toInjectFn(META_CONFIG_DEF);
|
|
7282
|
+
const META_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7010
7283
|
const metaService = inject(Meta);
|
|
7011
7284
|
const config = injectMetaConfig();
|
|
7012
7285
|
const { currentLocale } = injectLocale();
|
|
@@ -7032,7 +7305,6 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
|
|
|
7032
7305
|
if (transformedConfig.content) {
|
|
7033
7306
|
transformedConfig.content = config.transformer(transformedConfig.content, locale);
|
|
7034
7307
|
}
|
|
7035
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7036
7308
|
const { key, allowMultiple, ...metaConfig } = transformedConfig;
|
|
7037
7309
|
return metaService.addTag(metaConfig);
|
|
7038
7310
|
};
|
|
@@ -7119,6 +7391,8 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
|
|
|
7119
7391
|
});
|
|
7120
7392
|
return { addTag, removeTag, getSelector };
|
|
7121
7393
|
}, { name: 'Meta Store' });
|
|
7394
|
+
const provideMetaStore = /* @__PURE__ */ toProvideFn(META_STORE_DEF);
|
|
7395
|
+
const injectMetaStore = /* @__PURE__ */ toInjectFn(META_STORE_DEF);
|
|
7122
7396
|
const applyMetaBinding = (binding) => {
|
|
7123
7397
|
const metaStore = injectMetaStore();
|
|
7124
7398
|
const tagId = Symbol('meta-tag');
|
|
@@ -7128,14 +7402,14 @@ const applyMetaBinding = (binding) => {
|
|
|
7128
7402
|
metaStore.addTag(tagId, config);
|
|
7129
7403
|
}, () => metaStore.removeTag(tagId, currentSelector));
|
|
7130
7404
|
};
|
|
7131
|
-
const applyDescriptionBinding = createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
|
|
7405
|
+
const applyDescriptionBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
|
|
7132
7406
|
const applyKeywordsBinding = (binding) => {
|
|
7133
7407
|
applyMetaBinding(computed(() => {
|
|
7134
7408
|
const keywords = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
7135
7409
|
return keywords && keywords.length > 0 ? { name: 'keywords', content: keywords.join(', ') } : null;
|
|
7136
7410
|
}));
|
|
7137
7411
|
};
|
|
7138
|
-
const applyAuthorBinding = createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
|
|
7412
|
+
const applyAuthorBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
|
|
7139
7413
|
const applyRobotsBinding = (binding) => {
|
|
7140
7414
|
applyMetaBinding(computed(() => {
|
|
7141
7415
|
const config = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
@@ -7448,10 +7722,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7448
7722
|
}]
|
|
7449
7723
|
}] });
|
|
7450
7724
|
|
|
7451
|
-
const
|
|
7725
|
+
const STRUCTURED_DATA_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
7452
7726
|
placement: 'body',
|
|
7453
7727
|
}, { name: 'Structured Data Config' });
|
|
7454
|
-
const
|
|
7728
|
+
const provideStructuredDataConfig = /* @__PURE__ */ toProvideFn(STRUCTURED_DATA_CONFIG_DEF);
|
|
7729
|
+
const injectStructuredDataConfig = /* @__PURE__ */ toInjectFn(STRUCTURED_DATA_CONFIG_DEF);
|
|
7730
|
+
const STRUCTURED_DATA_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7455
7731
|
const document = inject(DOCUMENT$1);
|
|
7456
7732
|
const renderer = injectRenderer();
|
|
7457
7733
|
const config = injectStructuredDataConfig();
|
|
@@ -7468,7 +7744,11 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
|
|
|
7468
7744
|
renderer.removeChild(oldScript.parentNode, oldScript);
|
|
7469
7745
|
}
|
|
7470
7746
|
const script = renderer.createElement('script');
|
|
7471
|
-
renderer.setAttributes(script, { type: 'application/ld+json'
|
|
7747
|
+
renderer.setAttributes(script, { type: 'application/ld+json' });
|
|
7748
|
+
// The JSON is the script's *content*, not an attribute. Written as text rather than markup so a
|
|
7749
|
+
// `</script>` inside a string can't close the tag early - the escaping problem
|
|
7750
|
+
// `StructuredDataComponent` has to solve by hand because it goes through `innerHTML`.
|
|
7751
|
+
renderer.setTextContent(script, JSON.stringify(data));
|
|
7472
7752
|
const target = getTargetElement();
|
|
7473
7753
|
renderer.appendChild(target, script);
|
|
7474
7754
|
newScripts.set(id, script);
|
|
@@ -7488,6 +7768,8 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
|
|
|
7488
7768
|
};
|
|
7489
7769
|
return { addStructuredData, removeStructuredData };
|
|
7490
7770
|
}, { name: 'Structured Data Store' });
|
|
7771
|
+
const provideStructuredDataStore = /* @__PURE__ */ toProvideFn(STRUCTURED_DATA_STORE_DEF);
|
|
7772
|
+
const injectStructuredDataStore = /* @__PURE__ */ toInjectFn(STRUCTURED_DATA_STORE_DEF);
|
|
7491
7773
|
const applyStructuredDataBinding = (binding) => {
|
|
7492
7774
|
const store = injectStructuredDataStore();
|
|
7493
7775
|
const scriptId = Symbol('structured-data');
|
|
@@ -7537,12 +7819,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7537
7819
|
|
|
7538
7820
|
/**
|
|
7539
7821
|
* Cascades hover/active-driven color DOWN to descendants explicitly marked with
|
|
7540
|
-
* `ColorInteractiveDirective`
|
|
7822
|
+
* `ColorInteractiveDirective` - this element does not color itself. Use it when a wrapper should
|
|
7541
7823
|
* expand a nested control's hit/visual area (e.g. clicking a checkbox's label should light up the
|
|
7542
|
-
* checkbox itself), never to give a wrapper its own interactive coloring
|
|
7824
|
+
* checkbox itself), never to give a wrapper its own interactive coloring - that's
|
|
7543
7825
|
* `ColorInteractiveDirective`.
|
|
7544
7826
|
*
|
|
7545
|
-
* Does nothing unless at least one descendant carries `ColorInteractiveDirective`
|
|
7827
|
+
* Does nothing unless at least one descendant carries `ColorInteractiveDirective` - check for
|
|
7546
7828
|
* that before adding this. It was mistakenly added to a component with no such descendant once
|
|
7547
7829
|
* already, where it did nothing but was confusing to read.
|
|
7548
7830
|
*/
|
|
@@ -7568,9 +7850,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7568
7850
|
* Marks a descendant as NOT counting toward an ancestor's `ColorInteractiveDirective`
|
|
7569
7851
|
* `:hover`/`:active` reaction (via the `:has()` guard in color-interactive-styles.component.css).
|
|
7570
7852
|
* Use it for content that's incidentally inside an interactive ancestor but isn't part of what
|
|
7571
|
-
* makes that ancestor interactive
|
|
7853
|
+
* makes that ancestor interactive - e.g. a hint/error block sitting inside a form field's frame.
|
|
7572
7854
|
*
|
|
7573
|
-
* This only suppresses the ancestor's OWN reaction to hovering/pressing the excluded element
|
|
7855
|
+
* This only suppresses the ancestor's OWN reaction to hovering/pressing the excluded element - it
|
|
7574
7856
|
* has no effect on custom-property inheritance into descendants past the excluded element. If
|
|
7575
7857
|
* what you actually need is "stop my own cosmetic styling from reacting to a parent's unrelated
|
|
7576
7858
|
* interaction," this is the wrong tool; that's the class of bug `ColorInteractiveHasFocusDirective`
|
|
@@ -7592,7 +7874,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7592
7874
|
|
|
7593
7875
|
/**
|
|
7594
7876
|
* Tints `--et-theme-color-primary-*` etc. only when a descendant genuinely has `:focus-visible`
|
|
7595
|
-
* (`:has(:focus-visible)`)
|
|
7877
|
+
* (`:has(:focus-visible)`) - never on mouse `:hover`/`:active`. Use this for a wrapper (like a
|
|
7596
7878
|
* form field's frame) that should show the theme color while its control is focused, but must
|
|
7597
7879
|
* NOT react to incidental mouse interaction with unrelated nested widgets (a toolbar button, a
|
|
7598
7880
|
* rendered link) that happen to live inside the same wrapper.
|
|
@@ -7604,7 +7886,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7604
7886
|
* Deliberately does NOT also carry `ColorInteractiveDirective`'s `et-color-interactive` class:
|
|
7605
7887
|
* that class's plain `:hover`/`:active` rules locally override the same (inheriting)
|
|
7606
7888
|
* `--et-theme-color-primary-rgb`, so anything bubbling up from ANYWHERE in the subtree would
|
|
7607
|
-
* leak the transient hover/press color into every descendant reading it for unrelated cosmetics
|
|
7889
|
+
* leak the transient hover/press color into every descendant reading it for unrelated cosmetics -
|
|
7608
7890
|
* exactly the bug this directive's only consumer (form-field) shipped once, before the two
|
|
7609
7891
|
* behaviors were split apart. If you need the plain hover/active reaction too, add
|
|
7610
7892
|
* `ColorInteractiveDirective` explicitly and make sure you actually want both.
|
|
@@ -7630,13 +7912,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7630
7912
|
/**
|
|
7631
7913
|
* Makes an element react to ITS OWN `:hover`/`:active`/`:focus-visible`/`[disabled]` by tinting
|
|
7632
7914
|
* `--et-theme-color-primary-*`/`-on-primary-*`/`-ink-*` with the current color theme. Use this on
|
|
7633
|
-
* a single, self-contained interactive element
|
|
7915
|
+
* a single, self-contained interactive element - a button, a checkbox, a segmented button.
|
|
7634
7916
|
*
|
|
7635
7917
|
* Do NOT apply this to a wrapper that also contains OTHER, independently-interactive elements
|
|
7636
7918
|
* (e.g. a toolbar with its own buttons, or a form-field's control slot). These are ordinary
|
|
7637
7919
|
* inheriting CSS custom properties, so a descendant reading them for its own cosmetics (a caret
|
|
7638
7920
|
* color, a link color) gets swept along whenever ANYTHING inside the wrapper is hovered or
|
|
7639
|
-
* clicked
|
|
7921
|
+
* clicked - not just when the wrapper itself is the thing being interacted with. That exact bug
|
|
7640
7922
|
* shipped once already; see ColorInteractiveContainerDirective/ColorInteractiveHasFocusDirective
|
|
7641
7923
|
* for the shapes that avoid it.
|
|
7642
7924
|
*/
|
|
@@ -7664,19 +7946,23 @@ const SURFACE_TYPE = {
|
|
|
7664
7946
|
};
|
|
7665
7947
|
const createCssSurfaceName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
|
|
7666
7948
|
const resolveSurfaceByElevation = (themes, type, elevation) => themes.find((t) => t.type === type && t.elevation === elevation) ?? null;
|
|
7667
|
-
const
|
|
7949
|
+
const SURFACE_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
|
|
7668
7950
|
name: 'Surface Themes',
|
|
7669
7951
|
});
|
|
7670
|
-
const
|
|
7952
|
+
const ɵProvideSurfaceThemes = /* @__PURE__ */ toProvideFn(SURFACE_THEMES_DEF);
|
|
7953
|
+
const injectSurfaceThemes = /* @__PURE__ */ toInjectFn(SURFACE_THEMES_DEF);
|
|
7954
|
+
const SURFACE_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
|
|
7671
7955
|
name: 'Surface Themes Prefix',
|
|
7672
7956
|
});
|
|
7957
|
+
const ɵProvideSurfaceThemesPrefix = /* @__PURE__ */ toProvideFn(SURFACE_THEMES_PREFIX_DEF);
|
|
7958
|
+
const injectSurfaceThemesPrefix = /* @__PURE__ */ toInjectFn(SURFACE_THEMES_PREFIX_DEF);
|
|
7673
7959
|
const provideSurfaceThemesWithTailwind4 = (themes, prefix = 'et') => [
|
|
7674
7960
|
ɵProvideSurfaceThemes(themes),
|
|
7675
7961
|
ɵProvideSurfaceThemesPrefix(prefix),
|
|
7676
7962
|
];
|
|
7677
7963
|
|
|
7678
7964
|
const SURFACE_PROVIDER = new InjectionToken('SurfaceProvider');
|
|
7679
|
-
const FORCED_SURFACE_UNSET = Symbol('FORCED_SURFACE_UNSET');
|
|
7965
|
+
const FORCED_SURFACE_UNSET = /* @__PURE__ */ Symbol('FORCED_SURFACE_UNSET');
|
|
7680
7966
|
class ProvideSurfaceDirective {
|
|
7681
7967
|
constructor() {
|
|
7682
7968
|
this.themes = injectSurfaceThemes({ optional: true });
|
|
@@ -7775,7 +8061,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7775
8061
|
}], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "etProvideSurface", required: false }] }] } });
|
|
7776
8062
|
|
|
7777
8063
|
let uniqueId = 0;
|
|
7778
|
-
const
|
|
8064
|
+
const SURFACE_CONTEXT_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7779
8065
|
const entries = signal([], /* @ts-ignore */
|
|
7780
8066
|
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
7781
8067
|
const topEntry = computed(() => {
|
|
@@ -7808,12 +8094,14 @@ const [provideSurfaceContextTracker, injectSurfaceContextTracker] = createRootPr
|
|
|
7808
8094
|
};
|
|
7809
8095
|
return { topType, topElevation, register, surfaceForElement };
|
|
7810
8096
|
}, { name: 'SurfaceContextTracker' });
|
|
8097
|
+
const provideSurfaceContextTracker = /* @__PURE__ */ toProvideFn(SURFACE_CONTEXT_TRACKER_DEF);
|
|
8098
|
+
const injectSurfaceContextTracker = /* @__PURE__ */ toInjectFn(SURFACE_CONTEXT_TRACKER_DEF);
|
|
7811
8099
|
|
|
7812
8100
|
class AutoSurfaceDirective {
|
|
7813
8101
|
/**
|
|
7814
8102
|
* Mark this auto-surface as its overlay's *own painted surface*. An overlay *panel*
|
|
7815
8103
|
* (menu, select/date/cascader panel, tooltip, toggletip, rich-text-editor popups) is
|
|
7816
|
-
* the overlay's surface, so it must paint the overlay's registered elevation exactly
|
|
8104
|
+
* the overlay's surface, so it must paint the overlay's registered elevation exactly -
|
|
7817
8105
|
* not stack a level above a parent surface. The elevation is read from the surface-context
|
|
7818
8106
|
* tracker (the pane it renders into), which is authoritative across the portal boundary;
|
|
7819
8107
|
* the panel's own declaration injector points back at the trigger location and cannot be
|
|
@@ -7841,7 +8129,7 @@ class AutoSurfaceDirective {
|
|
|
7841
8129
|
...(ngDevMode ? [{ debugName: "isOverlaySurface" }] : /* istanbul ignore next */ []));
|
|
7842
8130
|
// Bumped by the afterEveryRender watcher in the constructor whenever this element's overlay
|
|
7843
8131
|
// containment changes. surfaceForElement() below does a non-reactive element.contains() read,
|
|
7844
|
-
// so the computed must be re-triggered when this element is (re)grafted into an overlay pane
|
|
8132
|
+
// so the computed must be re-triggered when this element is (re)grafted into an overlay pane -
|
|
7845
8133
|
// otherwise a value cached from before the graft (one elevation too low) sticks forever.
|
|
7846
8134
|
this.domSettleTick = signal(0, /* @ts-ignore */
|
|
7847
8135
|
...(ngDevMode ? [{ debugName: "domSettleTick" }] : /* istanbul ignore next */ []));
|
|
@@ -7857,7 +8145,7 @@ class AutoSurfaceDirective {
|
|
|
7857
8145
|
const contextElevation = contextProvider?.elevation() ?? null;
|
|
7858
8146
|
const contextType = contextProvider?.surfaceType() ?? null;
|
|
7859
8147
|
// An overlay's projected/portaled content keeps the injector of where it was
|
|
7860
|
-
// *declared* (the trigger location), not the pane it renders into
|
|
8148
|
+
// *declared* (the trigger location), not the pane it renders into - so the
|
|
7861
8149
|
// injector-derived parent surface is one elevation too low. The surface-context
|
|
7862
8150
|
// tracker records each open overlay's surface *and* its pane element, so we consult
|
|
7863
8151
|
// only the overlay whose pane actually contains this element in the DOM (portaling
|
|
@@ -7868,7 +8156,7 @@ class AutoSurfaceDirective {
|
|
|
7868
8156
|
const overlayType = overlaySurface?.type ?? null;
|
|
7869
8157
|
// A panel that IS its overlay's own surface (see matchOverlaySurface) must paint exactly the
|
|
7870
8158
|
// overlay's elevation: the overlay container already resolved it (one above the trigger) and
|
|
7871
|
-
// registered it in the tracker. It must NOT re-derive from its declaration injector
|
|
8159
|
+
// registered it in the tracker. It must NOT re-derive from its declaration injector - that
|
|
7872
8160
|
// points back at the trigger location and disagrees with the pane whenever the trigger itself
|
|
7873
8161
|
// sits on a surface, which would double-elevate the panel relative to its own pane.
|
|
7874
8162
|
if (this.isOverlaySurface()) {
|
|
@@ -7901,7 +8189,7 @@ class AutoSurfaceDirective {
|
|
|
7901
8189
|
...(ngDevMode ? [{ debugName: "resolvedSurface" }] : /* istanbul ignore next */ []));
|
|
7902
8190
|
// Projected/portaled auto-surface content (e.g. a select option's avatar) can have its
|
|
7903
8191
|
// resolvedSurface computed run *before* it is grafted into the overlay pane it visually lives
|
|
7904
|
-
// in
|
|
8192
|
+
// in - or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
|
|
7905
8193
|
// surfaceForElement()'s element.contains() check then returns the wrong (or no) overlay, so it
|
|
7906
8194
|
// resolves off the declaration injector (the trigger location) one elevation too low. That check
|
|
7907
8195
|
// is a plain DOM read, not a signal, so nothing re-runs the computed once the element reaches its
|
|
@@ -7918,14 +8206,13 @@ class AutoSurfaceDirective {
|
|
|
7918
8206
|
this.domSettleTick.update((v) => v + 1);
|
|
7919
8207
|
}
|
|
7920
8208
|
else if (el.isConnected && ++stableRenders >= 2) {
|
|
7921
|
-
// containment has held for a couple of renders and the element is in the DOM
|
|
8209
|
+
// containment has held for a couple of renders and the element is in the DOM - done moving
|
|
7922
8210
|
settleWatcher.destroy();
|
|
7923
8211
|
}
|
|
7924
8212
|
});
|
|
7925
8213
|
effect(() => {
|
|
7926
8214
|
const surface = this.resolvedSurface();
|
|
7927
8215
|
untracked(() => {
|
|
7928
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7929
8216
|
setInputSignal(this.ownSurfaceProvider.surface, surface);
|
|
7930
8217
|
});
|
|
7931
8218
|
});
|
|
@@ -7942,12 +8229,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7942
8229
|
}], ctorParameters: () => [], propDecorators: { surfaceProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceProvider", required: false }] }] } });
|
|
7943
8230
|
|
|
7944
8231
|
const createCssColorThemeName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
|
|
7945
|
-
const
|
|
8232
|
+
const COLOR_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
|
|
7946
8233
|
name: 'Color Themes',
|
|
7947
8234
|
});
|
|
7948
|
-
const
|
|
8235
|
+
const ɵProvideColorThemes = /* @__PURE__ */ toProvideFn(COLOR_THEMES_DEF);
|
|
8236
|
+
const injectColorThemes = /* @__PURE__ */ toInjectFn(COLOR_THEMES_DEF);
|
|
8237
|
+
const COLOR_THEMES_PREFIX_DEF = /* @__PURE__ */ defineStaticProvider('et', {
|
|
7949
8238
|
name: 'Themes Prefix',
|
|
7950
8239
|
});
|
|
8240
|
+
const ɵProvideColorThemesPrefix = /* @__PURE__ */ toProvideFn(COLOR_THEMES_PREFIX_DEF);
|
|
8241
|
+
const injectColorThemesPrefix = /* @__PURE__ */ toInjectFn(COLOR_THEMES_PREFIX_DEF);
|
|
7951
8242
|
const provideColorThemesWithTailwind4 = (themes, prefix = 'et') => [
|
|
7952
8243
|
ɵProvideColorThemes(themes),
|
|
7953
8244
|
ɵProvideColorThemesPrefix(prefix),
|
|
@@ -8166,7 +8457,7 @@ const COLOR_PROVIDER = new InjectionToken('ColorProvider');
|
|
|
8166
8457
|
* The color provider attached to a bootstrapped root component (e.g. `ProvideColorDirective` in
|
|
8167
8458
|
* the app component's `hostDirectives`), if any. Detached DOM such as overlay panes can't reach
|
|
8168
8459
|
* it through element DI, so overlay containers fall back to this when resolving the provider to
|
|
8169
|
-
* `syncWithProvider()`
|
|
8460
|
+
* `syncWithProvider()` - resolve at sync time, not at injection time, since root components only
|
|
8170
8461
|
* register with `ApplicationRef` once bootstrap completes.
|
|
8171
8462
|
*
|
|
8172
8463
|
* @internal
|
|
@@ -8180,7 +8471,7 @@ const resolveAppRootColorProvider = (appRef) => {
|
|
|
8180
8471
|
}
|
|
8181
8472
|
return null;
|
|
8182
8473
|
};
|
|
8183
|
-
const FORCED_COLOR_UNSET = Symbol('FORCED_COLOR_UNSET');
|
|
8474
|
+
const FORCED_COLOR_UNSET = /* @__PURE__ */ Symbol('FORCED_COLOR_UNSET');
|
|
8184
8475
|
class ProvideColorDirective {
|
|
8185
8476
|
constructor() {
|
|
8186
8477
|
this.themes = injectColorThemes({ optional: true });
|
|
@@ -8200,8 +8491,8 @@ class ProvideColorDirective {
|
|
|
8200
8491
|
}, /* @ts-ignore */
|
|
8201
8492
|
...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
|
|
8202
8493
|
/**
|
|
8203
|
-
* The color that actually applies at this provider's location: its own effective color, or
|
|
8204
|
-
* when this provider is passive (no input, nothing forced)
|
|
8494
|
+
* The color that actually applies at this provider's location: its own effective color, or -
|
|
8495
|
+
* when this provider is passive (no input, nothing forced) - the nearest ancestor provider's.
|
|
8205
8496
|
* Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
|
|
8206
8497
|
* consumers that re-apply context across detached DOM (overlay panes) must use this instead
|
|
8207
8498
|
* of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
|
|
@@ -8294,14 +8585,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
8294
8585
|
}] });
|
|
8295
8586
|
|
|
8296
8587
|
/**
|
|
8297
|
-
* Same shape as `ColorInteractiveDirective`
|
|
8298
|
-
* `:active`/`[disabled]`
|
|
8588
|
+
* Same shape as `ColorInteractiveDirective` - reacts to its own `:hover`/`:focus-visible`/
|
|
8589
|
+
* `:active`/`[disabled]` - but drives `--et-surface-interaction-*`, a neutral/muted tint, instead
|
|
8299
8590
|
* of the ambient color theme. Use it for chrome-level interaction feedback that shouldn't borrow
|
|
8300
8591
|
* the page's brand color (a tab trigger's hover state, a toggle button that should look neutral
|
|
8301
|
-
* until it's actually active
|
|
8592
|
+
* until it's actually active - see `ButtonDirective`'s `mutedUntilPressed`).
|
|
8302
8593
|
*
|
|
8303
8594
|
* Has no container/exclude/has-focus variants: it has no descendant-cascading CSS at all, so
|
|
8304
|
-
* applying it to a wrapper with independently-interactive children is safe by construction
|
|
8595
|
+
* applying it to a wrapper with independently-interactive children is safe by construction -
|
|
8305
8596
|
* unlike `ColorInteractiveDirective`, there's no bubbling-leak failure mode to worry about here.
|
|
8306
8597
|
*/
|
|
8307
8598
|
class SurfaceInteractiveDirective {
|
|
@@ -8322,6 +8613,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
8322
8613
|
}]
|
|
8323
8614
|
}], ctorParameters: () => [] });
|
|
8324
8615
|
|
|
8616
|
+
const UNSAVED_CHANGES_COORDINATOR_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
8617
|
+
const registrations = new Set();
|
|
8618
|
+
const _isCheckPending = signal(false, /* @ts-ignore */
|
|
8619
|
+
...(ngDevMode ? [{ debugName: "_isCheckPending" }] : /* istanbul ignore next */ []));
|
|
8620
|
+
let pending = null;
|
|
8621
|
+
/** @internal Runs a tracker's confirm under the single-flight + abandon regime. */
|
|
8622
|
+
const runCheck = (runner) => {
|
|
8623
|
+
// A confirm is already on screen - adopt its answer rather than opening a second dialog.
|
|
8624
|
+
if (pending) {
|
|
8625
|
+
return pending.promise;
|
|
8626
|
+
}
|
|
8627
|
+
const controller = new AbortController();
|
|
8628
|
+
let settle;
|
|
8629
|
+
const abandoned = new Promise((resolve) => (settle = resolve));
|
|
8630
|
+
// Run the confirm synchronously - a dialog that opens a microtask later can miss a `signal` that
|
|
8631
|
+
// already aborted, and callers reasonably expect the dialog to be up when `runCheck()` returns.
|
|
8632
|
+
let confirmed;
|
|
8633
|
+
try {
|
|
8634
|
+
confirmed = runner({ signal: controller.signal }).catch(() => false);
|
|
8635
|
+
}
|
|
8636
|
+
catch {
|
|
8637
|
+
confirmed = Promise.resolve(false);
|
|
8638
|
+
}
|
|
8639
|
+
// Whichever settles first wins: a late answer from an abandoned confirm is ignored.
|
|
8640
|
+
const promise = Promise.race([confirmed, abandoned]).then((result) => {
|
|
8641
|
+
pending = null;
|
|
8642
|
+
_isCheckPending.set(false);
|
|
8643
|
+
return result;
|
|
8644
|
+
});
|
|
8645
|
+
pending = { promise, controller, settle };
|
|
8646
|
+
_isCheckPending.set(true);
|
|
8647
|
+
return promise;
|
|
8648
|
+
};
|
|
8649
|
+
/** @internal */
|
|
8650
|
+
const register = (registration) => {
|
|
8651
|
+
registrations.add(registration);
|
|
8652
|
+
return () => registrations.delete(registration);
|
|
8653
|
+
};
|
|
8654
|
+
const abandonAll = (reason = 'session-end') => {
|
|
8655
|
+
if (pending) {
|
|
8656
|
+
pending.controller.abort(reason);
|
|
8657
|
+
// The changes are unrecoverable at this point, so the discard is allowed: whatever waited on
|
|
8658
|
+
// this check (an overlay close, a `canDeactivate`) proceeds instead of hanging.
|
|
8659
|
+
pending.settle(true);
|
|
8660
|
+
}
|
|
8661
|
+
for (const registration of Array.from(registrations)) {
|
|
8662
|
+
registration.abandon(reason);
|
|
8663
|
+
}
|
|
8664
|
+
};
|
|
8665
|
+
return {
|
|
8666
|
+
/** Whether a confirm is currently on screen. */
|
|
8667
|
+
isCheckPending: _isCheckPending.asReadonly(),
|
|
8668
|
+
abandonAll,
|
|
8669
|
+
runCheck,
|
|
8670
|
+
register,
|
|
8671
|
+
};
|
|
8672
|
+
}, { name: 'Unsaved Changes Coordinator' });
|
|
8673
|
+
/**
|
|
8674
|
+
* App-wide coordination for the unsaved-changes family. Two jobs:
|
|
8675
|
+
*
|
|
8676
|
+
* - **One confirm at a time.** A page form, an overlay form and a route guard can all want a decision
|
|
8677
|
+
* at once; stacking three "discard your changes?" dialogs is never the right answer. A check that
|
|
8678
|
+
* starts while another is pending adopts the pending decision instead of asking again.
|
|
8679
|
+
* - **Abandoning guards when the session ends.** `abandonAll()` resolves the pending confirm, tells
|
|
8680
|
+
* its dialog to close (via {@link UnsavedChangesConfirmContext.signal}), and switches every live
|
|
8681
|
+
* guard off: further checks pass, and the tab locks release. Called automatically by
|
|
8682
|
+
* `@ethlete/query`'s auth provider on logout - the edits cannot be saved anymore, so guarding them
|
|
8683
|
+
* only strands dialogs over the login page and blocks the tab.
|
|
8684
|
+
*
|
|
8685
|
+
* Root-provided; every tracker registers itself. Inject it to abandon guards from app code (a session
|
|
8686
|
+
* timeout of your own, a hard reset) or to read whether a confirm is currently on screen.
|
|
8687
|
+
*/
|
|
8688
|
+
const provideUnsavedChangesCoordinator = /* @__PURE__ */ toProvideFn(UNSAVED_CHANGES_COORDINATOR_DEF);
|
|
8689
|
+
const injectUnsavedChangesCoordinator = /* @__PURE__ */ toInjectFn(UNSAVED_CHANGES_COORDINATOR_DEF);
|
|
8690
|
+
|
|
8325
8691
|
const isAbstractControl = (value) => !!value &&
|
|
8326
8692
|
typeof value === 'object' &&
|
|
8327
8693
|
typeof value.getRawValue === 'function' &&
|
|
@@ -8337,13 +8703,13 @@ const normalizeUnsavedChangesSource = (source) => {
|
|
|
8337
8703
|
if (isFieldTree(source)) {
|
|
8338
8704
|
return {
|
|
8339
8705
|
value: computed(() => source().value()),
|
|
8340
|
-
// FieldTree's value type is a generic conditional that TS can't narrow here
|
|
8706
|
+
// FieldTree's value type is a generic conditional that TS can't narrow here - cast the writer.
|
|
8341
8707
|
setValue: (next) => source().value.set(next),
|
|
8342
8708
|
};
|
|
8343
8709
|
}
|
|
8344
8710
|
if (isSignal(source)) {
|
|
8345
8711
|
const peek = untracked(source);
|
|
8346
|
-
// A signal that currently holds a FieldTree
|
|
8712
|
+
// A signal that currently holds a FieldTree - or nothing yet (the late/async form case).
|
|
8347
8713
|
if (peek === null || peek === undefined || isFieldTree(peek)) {
|
|
8348
8714
|
const fieldSignal = source;
|
|
8349
8715
|
return {
|
|
@@ -8368,6 +8734,179 @@ const normalizeUnsavedChangesSource = (source) => {
|
|
|
8368
8734
|
'an AbstractControl, or a WritableSignal.');
|
|
8369
8735
|
};
|
|
8370
8736
|
|
|
8737
|
+
/** The default tab title marker - a filled dot, prefixed while there are unsaved changes. */
|
|
8738
|
+
const UNSAVED_CHANGES_TITLE_MARKER = '●';
|
|
8739
|
+
// The badge is a single app-wide surface, so every lock that wants one registers here and the badge
|
|
8740
|
+
// is derived from all of them - otherwise the first tracker to go clean would clear a badge that
|
|
8741
|
+
// another one still needs.
|
|
8742
|
+
const BADGE_HOLDERS = /* @__PURE__ */ new Map();
|
|
8743
|
+
const syncAppBadge = () => {
|
|
8744
|
+
const nav = typeof navigator === 'undefined' ? null : navigator;
|
|
8745
|
+
if (!nav?.setAppBadge || !nav.clearAppBadge) {
|
|
8746
|
+
return;
|
|
8747
|
+
}
|
|
8748
|
+
// Unsupported, or not installed - the promise rejects instead of throwing synchronously.
|
|
8749
|
+
const ignore = () => undefined;
|
|
8750
|
+
if (!BADGE_HOLDERS.size) {
|
|
8751
|
+
nav.clearAppBadge().catch(ignore);
|
|
8752
|
+
return;
|
|
8753
|
+
}
|
|
8754
|
+
const count = Array.from(BADGE_HOLDERS.values()).reduce((total, contents) => (typeof contents === 'number' ? total + contents : total), 0);
|
|
8755
|
+
// `setAppBadge()` without a count renders a plain dot.
|
|
8756
|
+
nav.setAppBadge(count || undefined).catch(ignore);
|
|
8757
|
+
};
|
|
8758
|
+
/**
|
|
8759
|
+
* The browser-tab flavor of the unsaved-changes family: while `hasChanges` reads `true`, the tab
|
|
8760
|
+
* cannot be closed or reloaded without the browser's confirm prompt, and - opt in - the tab title
|
|
8761
|
+
* carries a marker (optionally blinking), the favicon carries a dot, and the installed app carries a
|
|
8762
|
+
* badge.
|
|
8763
|
+
*
|
|
8764
|
+
* Every {@link createUnsavedChangesTracker} sets this up automatically (lock only); reach for it
|
|
8765
|
+
* directly when you track "unsaved" state that isn't a form, or want to lock the tab from a signal
|
|
8766
|
+
* you already have. Call from an injection context.
|
|
8767
|
+
*
|
|
8768
|
+
* ```ts
|
|
8769
|
+
* createUnsavedChangesTabLock({
|
|
8770
|
+
* hasChanges: computed(() => this.uploads().some((u) => u.pending)),
|
|
8771
|
+
* titleMarker: true,
|
|
8772
|
+
* flash: true,
|
|
8773
|
+
* });
|
|
8774
|
+
* ```
|
|
8775
|
+
*/
|
|
8776
|
+
const createUnsavedChangesTabLock = (config) => {
|
|
8777
|
+
assertInInjectionContext(createUnsavedChangesTabLock);
|
|
8778
|
+
const { hasChanges } = config;
|
|
8779
|
+
const lock = config.lock ?? true;
|
|
8780
|
+
const flash = config.flash === true ? {} : config.flash || null;
|
|
8781
|
+
const favicon = config.favicon === true ? {} : config.favicon || null;
|
|
8782
|
+
const badge = config.badge ?? false;
|
|
8783
|
+
// Blinking needs something to blink, so it implies the default marker.
|
|
8784
|
+
const titleMarker = config.titleMarker === true
|
|
8785
|
+
? UNSAVED_CHANGES_TITLE_MARKER
|
|
8786
|
+
: (config.titleMarker ?? (flash && !favicon ? UNSAVED_CHANGES_TITLE_MARKER : null));
|
|
8787
|
+
const destroyRef = inject(DestroyRef);
|
|
8788
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
8789
|
+
const releaseFns = [];
|
|
8790
|
+
// The blink phase. Always `true` unless flashing is on, so the marker/favicon effects below can read
|
|
8791
|
+
// one signal either way.
|
|
8792
|
+
const flashPhase = signal(true, /* @ts-ignore */
|
|
8793
|
+
...(ngDevMode ? [{ debugName: "flashPhase" }] : /* istanbul ignore next */ []));
|
|
8794
|
+
if (isBrowser && flash) {
|
|
8795
|
+
const interval = flash.interval ?? 1000;
|
|
8796
|
+
const whenHidden = flash.whenHidden ?? true;
|
|
8797
|
+
const isDocumentVisible = injectIsDocumentVisible();
|
|
8798
|
+
const shouldFlash = computed(() => hasChanges() && (!whenHidden || !isDocumentVisible()), /* @ts-ignore */
|
|
8799
|
+
...(ngDevMode ? [{ debugName: "shouldFlash" }] : /* istanbul ignore next */ []));
|
|
8800
|
+
const flashEffect = effect((onCleanup) => {
|
|
8801
|
+
if (!shouldFlash()) {
|
|
8802
|
+
return;
|
|
8803
|
+
}
|
|
8804
|
+
const handle = setInterval(() => flashPhase.update((on) => !on), interval);
|
|
8805
|
+
onCleanup(() => {
|
|
8806
|
+
clearInterval(handle);
|
|
8807
|
+
flashPhase.set(true);
|
|
8808
|
+
});
|
|
8809
|
+
}, /* @ts-ignore */
|
|
8810
|
+
...(ngDevMode ? [{ debugName: "flashEffect" }] : /* istanbul ignore next */ []));
|
|
8811
|
+
releaseFns.push(() => {
|
|
8812
|
+
flashEffect.destroy();
|
|
8813
|
+
flashPhase.set(true);
|
|
8814
|
+
});
|
|
8815
|
+
}
|
|
8816
|
+
/** Dirty, and in the "shown" half of a blink. */
|
|
8817
|
+
const isMarked = computed(() => hasChanges() && flashPhase(), /* @ts-ignore */
|
|
8818
|
+
...(ngDevMode ? [{ debugName: "isMarked" }] : /* istanbul ignore next */ []));
|
|
8819
|
+
if (titleMarker) {
|
|
8820
|
+
const titleStore = injectTitleStore();
|
|
8821
|
+
const markerId = Symbol('unsaved-changes-title-marker');
|
|
8822
|
+
const markerEffect = effect(() => {
|
|
8823
|
+
const marked = isMarked();
|
|
8824
|
+
untracked(() => (marked ? titleStore.addMarker(markerId, titleMarker) : titleStore.removeMarker(markerId)));
|
|
8825
|
+
}, /* @ts-ignore */
|
|
8826
|
+
...(ngDevMode ? [{ debugName: "markerEffect" }] : /* istanbul ignore next */ []));
|
|
8827
|
+
releaseFns.push(() => {
|
|
8828
|
+
markerEffect.destroy();
|
|
8829
|
+
titleStore.removeMarker(markerId);
|
|
8830
|
+
});
|
|
8831
|
+
}
|
|
8832
|
+
if (isBrowser && favicon) {
|
|
8833
|
+
const faviconStore = injectFaviconStore();
|
|
8834
|
+
const overlayId = Symbol('unsaved-changes-favicon');
|
|
8835
|
+
const faviconEffect = effect(() => {
|
|
8836
|
+
const marked = isMarked();
|
|
8837
|
+
untracked(() => marked
|
|
8838
|
+
? faviconStore.addOverlay(overlayId, { kind: 'dot', color: favicon.color })
|
|
8839
|
+
: faviconStore.removeOverlay(overlayId));
|
|
8840
|
+
}, /* @ts-ignore */
|
|
8841
|
+
...(ngDevMode ? [{ debugName: "faviconEffect" }] : /* istanbul ignore next */ []));
|
|
8842
|
+
releaseFns.push(() => {
|
|
8843
|
+
faviconEffect.destroy();
|
|
8844
|
+
faviconStore.removeOverlay(overlayId);
|
|
8845
|
+
});
|
|
8846
|
+
}
|
|
8847
|
+
if (isBrowser && lock) {
|
|
8848
|
+
const onBeforeUnload = (event) => {
|
|
8849
|
+
event.preventDefault();
|
|
8850
|
+
// Legacy Chrome / Safari gate the prompt on a truthy `returnValue` rather than on
|
|
8851
|
+
// `preventDefault()`. Deprecated, but still the only way to reach those engines.
|
|
8852
|
+
event.returnValue = true;
|
|
8853
|
+
};
|
|
8854
|
+
let attached = false;
|
|
8855
|
+
const attach = () => {
|
|
8856
|
+
if (attached)
|
|
8857
|
+
return;
|
|
8858
|
+
attached = true;
|
|
8859
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
8860
|
+
};
|
|
8861
|
+
const detach = () => {
|
|
8862
|
+
if (!attached)
|
|
8863
|
+
return;
|
|
8864
|
+
attached = false;
|
|
8865
|
+
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
8866
|
+
};
|
|
8867
|
+
// Attached only while there are changes: a permanently registered `beforeunload` listener makes
|
|
8868
|
+
// the page ineligible for the back/forward cache, which would slow every back navigation.
|
|
8869
|
+
const lockEffect = effect(() => {
|
|
8870
|
+
const dirty = hasChanges();
|
|
8871
|
+
untracked(() => (dirty ? attach() : detach()));
|
|
8872
|
+
}, /* @ts-ignore */
|
|
8873
|
+
...(ngDevMode ? [{ debugName: "lockEffect" }] : /* istanbul ignore next */ []));
|
|
8874
|
+
releaseFns.push(() => {
|
|
8875
|
+
lockEffect.destroy();
|
|
8876
|
+
detach();
|
|
8877
|
+
});
|
|
8878
|
+
}
|
|
8879
|
+
if (isBrowser && badge !== false) {
|
|
8880
|
+
const badgeId = Symbol('unsaved-changes-badge');
|
|
8881
|
+
const contents = badge === true ? true : badge;
|
|
8882
|
+
const badgeEffect = effect(() => {
|
|
8883
|
+
const dirty = hasChanges();
|
|
8884
|
+
untracked(() => {
|
|
8885
|
+
if (dirty) {
|
|
8886
|
+
BADGE_HOLDERS.set(badgeId, contents);
|
|
8887
|
+
}
|
|
8888
|
+
else {
|
|
8889
|
+
BADGE_HOLDERS.delete(badgeId);
|
|
8890
|
+
}
|
|
8891
|
+
syncAppBadge();
|
|
8892
|
+
});
|
|
8893
|
+
}, /* @ts-ignore */
|
|
8894
|
+
...(ngDevMode ? [{ debugName: "badgeEffect" }] : /* istanbul ignore next */ []));
|
|
8895
|
+
releaseFns.push(() => {
|
|
8896
|
+
badgeEffect.destroy();
|
|
8897
|
+
BADGE_HOLDERS.delete(badgeId);
|
|
8898
|
+
syncAppBadge();
|
|
8899
|
+
});
|
|
8900
|
+
}
|
|
8901
|
+
const destroy = () => {
|
|
8902
|
+
while (releaseFns.length) {
|
|
8903
|
+
releaseFns.pop()?.();
|
|
8904
|
+
}
|
|
8905
|
+
};
|
|
8906
|
+
destroyRef.onDestroy(destroy);
|
|
8907
|
+
return { destroy };
|
|
8908
|
+
};
|
|
8909
|
+
|
|
8371
8910
|
const toBooleanPromise = (result) => {
|
|
8372
8911
|
if (result instanceof Promise) {
|
|
8373
8912
|
return result.then(Boolean);
|
|
@@ -8382,8 +8921,15 @@ const toBooleanPromise = (result) => {
|
|
|
8382
8921
|
* whether the watched form/value differs from it, and runs an async confirm before a discard.
|
|
8383
8922
|
* Call from an injection context (a field initializer or constructor).
|
|
8384
8923
|
*
|
|
8924
|
+
* While there are changes the browser tab is locked too (`beforeunload`), so closing or reloading the
|
|
8925
|
+
* tab needs a confirmation - see the `tab` config for the title-marker / app-badge extras and for
|
|
8926
|
+
* opting out.
|
|
8927
|
+
*
|
|
8928
|
+
* Every tracker registers with the app-wide {@link injectUnsavedChangesCoordinator}, which keeps a
|
|
8929
|
+
* single confirm on screen at a time and switches all guards off when the session ends (logout).
|
|
8930
|
+
*
|
|
8385
8931
|
* Overlay and router flavors (`createOverlayUnsavedChangesGuard`, `createUnsavedChangesGuard`) build
|
|
8386
|
-
* on this
|
|
8932
|
+
* on this - use them when you want the guard wired to a close/navigation event automatically.
|
|
8387
8933
|
*/
|
|
8388
8934
|
const createUnsavedChangesTracker = (config) => {
|
|
8389
8935
|
assertInInjectionContext(createUnsavedChangesTracker);
|
|
@@ -8418,11 +8964,23 @@ const createUnsavedChangesTracker = (config) => {
|
|
|
8418
8964
|
return !(compareFn ? compareFn(current, defaultValue) : equal(current, defaultValue));
|
|
8419
8965
|
}, /* @ts-ignore */
|
|
8420
8966
|
...(ngDevMode ? [{ debugName: "hasChanges" }] : /* istanbul ignore next */ []));
|
|
8967
|
+
const tab = config.tab === false ? null : createUnsavedChangesTabLock({ ...config.tab, hasChanges });
|
|
8968
|
+
const coordinator = injectUnsavedChangesCoordinator();
|
|
8969
|
+
const _isAbandoned = signal(false, /* @ts-ignore */
|
|
8970
|
+
...(ngDevMode ? [{ debugName: "_isAbandoned" }] : /* istanbul ignore next */ []));
|
|
8971
|
+
const unregister = coordinator.register({
|
|
8972
|
+
abandon: () => {
|
|
8973
|
+
_isAbandoned.set(true);
|
|
8974
|
+
// The session is gone: stop holding the tab hostage over edits that can no longer be saved.
|
|
8975
|
+
tab?.destroy();
|
|
8976
|
+
},
|
|
8977
|
+
});
|
|
8978
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
8421
8979
|
const runCheck = () => {
|
|
8422
|
-
if (!untracked(hasChanges)) {
|
|
8980
|
+
if (untracked(_isAbandoned) || !untracked(hasChanges)) {
|
|
8423
8981
|
return Promise.resolve(true);
|
|
8424
8982
|
}
|
|
8425
|
-
return toBooleanPromise(confirm(untracked(normalized.value)));
|
|
8983
|
+
return coordinator.runCheck((context) => toBooleanPromise(confirm(untracked(normalized.value), context)));
|
|
8426
8984
|
};
|
|
8427
8985
|
const refreshDefaultValue = () => _defaultValue.set(untracked(normalized.value));
|
|
8428
8986
|
const restoreDefaultValue = () => {
|
|
@@ -8433,10 +8991,12 @@ const createUnsavedChangesTracker = (config) => {
|
|
|
8433
8991
|
};
|
|
8434
8992
|
return {
|
|
8435
8993
|
hasChanges,
|
|
8994
|
+
isAbandoned: _isAbandoned.asReadonly(),
|
|
8436
8995
|
defaultValue: _defaultValue.asReadonly(),
|
|
8437
8996
|
runCheck,
|
|
8438
8997
|
refreshDefaultValue,
|
|
8439
8998
|
restoreDefaultValue,
|
|
8999
|
+
tab,
|
|
8440
9000
|
};
|
|
8441
9001
|
};
|
|
8442
9002
|
|
|
@@ -8459,5 +9019,5 @@ const createUnsavedChangesGuard = (config) => {
|
|
|
8459
9019
|
* Generated bundle index. Do not edit.
|
|
8460
9020
|
*/
|
|
8461
9021
|
|
|
8462
|
-
export { ANIMATABLE_TOKEN, ANIMATED_IF_TOKEN, ANIMATED_LIFECYCLE_TOKEN, ANIMATION_DEBUG_STORAGE_KEY, AT_LEAST_ONE_REQUIRED, AnimatableDirective, AnimatedIfDirective, AnimatedLifecycleDirective,
|
|
9022
|
+
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, 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, 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, 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 };
|
|
8463
9023
|
//# sourceMappingURL=ethlete-core.mjs.map
|