@ethlete/core 5.0.0-next.36 → 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 +94 -26
- package/README.md +1 -1
- package/fesm2022/ethlete-core.mjs +1017 -604
- 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 +2598 -2599
|
@@ -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 [];
|
|
@@ -1989,8 +2049,8 @@ const elementCanScroll = (element, direction) => {
|
|
|
1989
2049
|
};
|
|
1990
2050
|
const createViewportRect = () => {
|
|
1991
2051
|
// Collapses to a zero rect where there is no window (server). `isElementVisible` never reaches the
|
|
1992
|
-
// intersection math there anyway
|
|
1993
|
-
//
|
|
2052
|
+
// intersection math there anyway - `elementCanScroll` already reports the document as unscrollable
|
|
2053
|
+
// - so the rect only has to be a valid shape.
|
|
1994
2054
|
const hasWindow = typeof window !== 'undefined';
|
|
1995
2055
|
const width = hasWindow ? window.innerWidth : 0;
|
|
1996
2056
|
const height = hasWindow ? window.innerHeight : 0;
|
|
@@ -2219,7 +2279,7 @@ const getScrollItemTarget = (entries, container, direction, scrollOrigin, axisDi
|
|
|
2219
2279
|
const firstIndex = entries.indexOf(firstVisible);
|
|
2220
2280
|
const lastIndex = entries.indexOf(lastVisible);
|
|
2221
2281
|
const containerRect = container.getBoundingClientRect();
|
|
2222
|
-
// Only a single item is visible
|
|
2282
|
+
// Only a single item is visible - it must be oversized (wider/taller than the container).
|
|
2223
2283
|
if (firstVisible === lastVisible) {
|
|
2224
2284
|
const itemRect = firstVisible.target.getBoundingClientRect();
|
|
2225
2285
|
const isStartEdgeVisible = axisDirection === 'horizontal'
|
|
@@ -2458,20 +2518,21 @@ const signalElementLastScrollDirection = (el) => {
|
|
|
2458
2518
|
const signalHostElementLastScrollDirection = () => signalElementLastScrollDirection(inject(ElementRef));
|
|
2459
2519
|
|
|
2460
2520
|
/**
|
|
2461
|
-
* A signal for whether this page is the one the reader is actually looking at
|
|
2521
|
+
* A signal for whether this page is the one the reader is actually looking at - `false` for a backgrounded
|
|
2462
2522
|
* tab or a minimised window.
|
|
2463
2523
|
*
|
|
2464
2524
|
* Not the same question as whether an element is on screen: an IntersectionObserver reports a fully visible
|
|
2465
2525
|
* element in a background tab, because the tab's own visibility is no part of what it measures. Anything
|
|
2466
|
-
* that moves on its own needs to ask both, and a timer is not a substitute
|
|
2526
|
+
* that moves on its own needs to ask both, and a timer is not a substitute - browsers throttle timers in a
|
|
2467
2527
|
* hidden tab rather than stopping them, so work keeps piling up out of sight.
|
|
2468
2528
|
*/
|
|
2469
|
-
const injectIsDocumentVisible = memoizeSignal(() => {
|
|
2529
|
+
const injectIsDocumentVisible = /* @__PURE__ */ memoizeSignal(() => {
|
|
2470
2530
|
const document = inject(DOCUMENT);
|
|
2471
2531
|
const isVisible = () => document.visibilityState === 'visible';
|
|
2472
2532
|
return toSignal(fromEvent(document, 'visibilitychange').pipe(map(isVisible)), { initialValue: isVisible() });
|
|
2473
2533
|
});
|
|
2474
2534
|
|
|
2535
|
+
const DEFAULT_SCROLL_STATE_MUTATIONS = { childList: true, subtree: true, attributes: true };
|
|
2475
2536
|
const areScrollStatesEqual = (a, b) => {
|
|
2476
2537
|
return (a.canScroll === b.canScroll &&
|
|
2477
2538
|
a.canScrollHorizontally === b.canScrollHorizontally &&
|
|
@@ -2482,7 +2543,7 @@ const signalElementScrollState = (el, options) => {
|
|
|
2482
2543
|
const elements = buildElementSignal(el);
|
|
2483
2544
|
const observedEl = firstElementSignal(elements);
|
|
2484
2545
|
const elementDimensions = signalElementDimensions(elements);
|
|
2485
|
-
const elementMutations = signalElementMutations(elements,
|
|
2546
|
+
const elementMutations = signalElementMutations(elements, options?.mutations ?? DEFAULT_SCROLL_STATE_MUTATIONS);
|
|
2486
2547
|
const isRendered = signalIsRendered();
|
|
2487
2548
|
const initialScrollPosition = options?.initialScrollPosition;
|
|
2488
2549
|
if (initialScrollPosition) {
|
|
@@ -2596,7 +2657,6 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2596
2657
|
const element = firstElementSignal(elements);
|
|
2597
2658
|
const destroyRef = inject(DestroyRef);
|
|
2598
2659
|
const { enabled = signal(true), allowedDirection = 'both' } = options ?? {};
|
|
2599
|
-
const scrollState = signalElementScrollState(elements);
|
|
2600
2660
|
const renderer = injectRenderer();
|
|
2601
2661
|
const isDragging = signal(false, /* @ts-ignore */
|
|
2602
2662
|
...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
@@ -2609,8 +2669,13 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2609
2669
|
const dragAmount = signal({ x: 0, y: 0 }, /* @ts-ignore */
|
|
2610
2670
|
...(ngDevMode ? [{ debugName: "dragAmount" }] : /* istanbul ignore next */ []));
|
|
2611
2671
|
const document = inject(DOCUMENT);
|
|
2612
|
-
|
|
2613
|
-
|
|
2672
|
+
// Only measured when the caller hasn't already: this costs a MutationObserver and a ResizeObserver.
|
|
2673
|
+
const suppliedCanScroll = options?.canScroll;
|
|
2674
|
+
const measuredScrollState = suppliedCanScroll ? null : signalElementScrollState(elements);
|
|
2675
|
+
const measuredCanScroll = computed(() => {
|
|
2676
|
+
const currentScrollState = measuredScrollState?.();
|
|
2677
|
+
if (!currentScrollState)
|
|
2678
|
+
return false;
|
|
2614
2679
|
const direction = maybeSignalValue(allowedDirection);
|
|
2615
2680
|
switch (direction) {
|
|
2616
2681
|
case 'both':
|
|
@@ -2621,7 +2686,8 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2621
2686
|
return currentScrollState.canScrollVertically;
|
|
2622
2687
|
}
|
|
2623
2688
|
}, /* @ts-ignore */
|
|
2624
|
-
...(ngDevMode ? [{ debugName: "
|
|
2689
|
+
...(ngDevMode ? [{ debugName: "measuredCanScroll" }] : /* istanbul ignore next */ []));
|
|
2690
|
+
const canScroll = suppliedCanScroll ?? measuredCanScroll;
|
|
2625
2691
|
// Cleanup if the element the cursor drag scroll is bound to gets changed
|
|
2626
2692
|
effect(() => {
|
|
2627
2693
|
const { previousElement } = element();
|
|
@@ -2722,7 +2788,7 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2722
2788
|
dragAmount.set({ x: 0, y: 0 });
|
|
2723
2789
|
};
|
|
2724
2790
|
const setupDragging = (e) => {
|
|
2725
|
-
// A context menu takes the pointer away without ever delivering a `mouseup`
|
|
2791
|
+
// A context menu takes the pointer away without ever delivering a `mouseup` - so without it in here, a
|
|
2726
2792
|
// right click (or a ctrl-click on a Mac, which is the primary button) leaves the drag latched on and
|
|
2727
2793
|
// every later mouse move scrolls the container.
|
|
2728
2794
|
const dragEnd = merge(fromEvent(document, 'mouseup'), fromEvent(document, 'contextmenu'));
|
|
@@ -2749,7 +2815,7 @@ const useCursorDragScroll = (el, options) => {
|
|
|
2749
2815
|
};
|
|
2750
2816
|
};
|
|
2751
2817
|
|
|
2752
|
-
const ET_PROPERTY_REMOVED = Symbol('ET_PROPERTY_REMOVED');
|
|
2818
|
+
const ET_PROPERTY_REMOVED = /* @__PURE__ */ Symbol('ET_PROPERTY_REMOVED');
|
|
2753
2819
|
const transformOrReturn = (src, config) => {
|
|
2754
2820
|
const transformer = config?.transform;
|
|
2755
2821
|
if (transformer) {
|
|
@@ -2766,7 +2832,7 @@ const createInitialRoute = () => {
|
|
|
2766
2832
|
return window.location.pathname + window.location.search + window.location.hash;
|
|
2767
2833
|
};
|
|
2768
2834
|
/** Inject the current router event */
|
|
2769
|
-
const injectRouterEvent = memoizeSignal(() => {
|
|
2835
|
+
const injectRouterEvent = /* @__PURE__ */ memoizeSignal(() => {
|
|
2770
2836
|
const router = inject(Router);
|
|
2771
2837
|
const initialRoute = createInitialRoute();
|
|
2772
2838
|
return toSignal(router.events, {
|
|
@@ -2777,7 +2843,7 @@ const injectRouterEvent = memoizeSignal(() => {
|
|
|
2777
2843
|
* Signal that indicates whether the router has been initialized.
|
|
2778
2844
|
* The router is considered initialized once the first NavigationEnd event with an id different than -1 has been emitted.
|
|
2779
2845
|
*/
|
|
2780
|
-
const injectIsRouterInitialized = memoizeSignal(() => {
|
|
2846
|
+
const injectIsRouterInitialized = /* @__PURE__ */ memoizeSignal(() => {
|
|
2781
2847
|
const event = injectRouterEvent();
|
|
2782
2848
|
return computed(() => {
|
|
2783
2849
|
const e = event();
|
|
@@ -2789,7 +2855,7 @@ const injectIsRouterInitialized = memoizeSignal(() => {
|
|
|
2789
2855
|
* The url includes query params as well as the fragment. Use `injectRoute` instead if you are not intrusted in those.
|
|
2790
2856
|
* @example "/my-page?query=1¶m=true#fragment"
|
|
2791
2857
|
*/
|
|
2792
|
-
const injectUrl = memoizeSignal(() => {
|
|
2858
|
+
const injectUrl = /* @__PURE__ */ memoizeSignal(() => {
|
|
2793
2859
|
const event = injectRouterEvent();
|
|
2794
2860
|
const router = inject(Router);
|
|
2795
2861
|
// Captured once for reads that happen before the router has committed any
|
|
@@ -2799,7 +2865,7 @@ const injectUrl = memoizeSignal(() => {
|
|
|
2799
2865
|
// We depend on `event` and `currentNavigation` only to get re-evaluated
|
|
2800
2866
|
// around each navigation, but we read the *committed* url straight from the
|
|
2801
2867
|
// router. The router commits `currentUrlTree` before it activates components,
|
|
2802
|
-
// so `router.url` is already correct inside a child component's constructor
|
|
2868
|
+
// so `router.url` is already correct inside a child component's constructor -
|
|
2803
2869
|
// unlike the NavigationEnd event, which only reaches this signal *after*
|
|
2804
2870
|
// activation.
|
|
2805
2871
|
event();
|
|
@@ -2819,7 +2885,7 @@ const injectUrl = memoizeSignal(() => {
|
|
|
2819
2885
|
* Inject the current route
|
|
2820
2886
|
* @example "/my-page"
|
|
2821
2887
|
*/
|
|
2822
|
-
const injectRoute = memoizeSignal(() => {
|
|
2888
|
+
const injectRoute = /* @__PURE__ */ memoizeSignal(() => {
|
|
2823
2889
|
const url = injectUrl();
|
|
2824
2890
|
return computed(() => {
|
|
2825
2891
|
const fullUrl = url();
|
|
@@ -2854,14 +2920,9 @@ const createRoute = (router) => {
|
|
|
2854
2920
|
/**
|
|
2855
2921
|
* Inject the complete router state. This includes the current route data, path params, query params, title and fragment.
|
|
2856
2922
|
*/
|
|
2857
|
-
const injectRouterState = memoizeSignal(() => {
|
|
2923
|
+
const injectRouterState = /* @__PURE__ */ memoizeSignal(() => {
|
|
2858
2924
|
const event = injectRouterEvent();
|
|
2859
2925
|
const router = inject(Router);
|
|
2860
|
-
// We only depend on `event` to get re-evaluated on every router event, but we
|
|
2861
|
-
// read the *committed* snapshot straight from the router. The router commits
|
|
2862
|
-
// `routerState` before it activates components (and emits ActivationStart
|
|
2863
|
-
// before NavigationEnd), so this is already correct inside a child component's
|
|
2864
|
-
// constructor — unlike an effect, which only flushes after construction.
|
|
2865
2926
|
return computed(() => {
|
|
2866
2927
|
event();
|
|
2867
2928
|
return createRouterState(router);
|
|
@@ -2930,7 +2991,7 @@ const injectPathParam = (key, config) => {
|
|
|
2930
2991
|
* Inject query params that changed during navigation. Unchanged query params will be ignored.
|
|
2931
2992
|
* Removed query params will be represented by the symbol `ET_PROPERTY_REMOVED`.
|
|
2932
2993
|
*/
|
|
2933
|
-
const injectQueryParamChanges = memoizeSignal(() => {
|
|
2994
|
+
const injectQueryParamChanges = /* @__PURE__ */ memoizeSignal(() => {
|
|
2934
2995
|
const queryParams = injectQueryParams();
|
|
2935
2996
|
const prevQueryParams = previousSignalValue(queryParams);
|
|
2936
2997
|
return computed(() => {
|
|
@@ -2954,7 +3015,7 @@ const injectQueryParamChanges = memoizeSignal(() => {
|
|
|
2954
3015
|
* Inject path params that changed during navigation. Unchanged path params will be ignored.
|
|
2955
3016
|
* Removed path params will be represented by the symbol `ET_PROPERTY_REMOVED`.
|
|
2956
3017
|
*/
|
|
2957
|
-
const injectPathParamChanges = memoizeSignal(() => {
|
|
3018
|
+
const injectPathParamChanges = /* @__PURE__ */ memoizeSignal(() => {
|
|
2958
3019
|
const pathParams = injectPathParams();
|
|
2959
3020
|
const prevPathParams = previousSignalValue(pathParams);
|
|
2960
3021
|
return computed(() => {
|
|
@@ -2976,11 +3037,11 @@ const injectPathParamChanges = memoizeSignal(() => {
|
|
|
2976
3037
|
});
|
|
2977
3038
|
/**
|
|
2978
3039
|
* The state handed to the navigation that is currently in progress, i.e. what
|
|
2979
|
-
* `router.navigate(…, { state })` passed
|
|
3040
|
+
* `router.navigate(…, { state })` passed - typed to whatever you expect.
|
|
2980
3041
|
*
|
|
2981
3042
|
* Read **synchronously**, in a constructor or a resolver, and deliberately not a signal: navigation state exists
|
|
2982
3043
|
* only for the duration of the navigation that carries it. By the time an effect flushed, the navigation would be
|
|
2983
|
-
* over and the answer always `null`. Returns `null` when there is no navigation in flight or it carried no state
|
|
3044
|
+
* over and the answer always `null`. Returns `null` when there is no navigation in flight or it carried no state -
|
|
2984
3045
|
* which is the normal case for a page arrived at by typing its URL, so always handle it.
|
|
2985
3046
|
*
|
|
2986
3047
|
* Use it for the things a URL should not carry: a "you were redirected because…" reason, a pre-fetched object the
|
|
@@ -3000,21 +3061,16 @@ const injectRouterNavigationState = () => {
|
|
|
3000
3061
|
return router.getCurrentNavigation()?.extras.state ?? null;
|
|
3001
3062
|
};
|
|
3002
3063
|
|
|
3003
|
-
const ET_DISABLE_SCROLL_TOP = Symbol('ET_DISABLE_SCROLL_TOP');
|
|
3004
|
-
const ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE = Symbol('ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE');
|
|
3005
|
-
const ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE = Symbol('ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE');
|
|
3064
|
+
const ET_DISABLE_SCROLL_TOP = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP');
|
|
3065
|
+
const ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE');
|
|
3066
|
+
const ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE = /* @__PURE__ */ Symbol('ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE');
|
|
3006
3067
|
const routerDisableScrollTop = (config = {}) => {
|
|
3007
3068
|
return {
|
|
3008
3069
|
...(!config.asReturnRoute ? { [ET_DISABLE_SCROLL_TOP]: true } : { [ET_DISABLE_SCROLL_TOP_AS_RETURN_ROUTE]: true }),
|
|
3009
3070
|
...(config.onPathParamChange ? { [ET_DISABLE_SCROLL_TOP_ON_PATH_PARAM_CHANGE]: true } : {}),
|
|
3010
3071
|
};
|
|
3011
3072
|
};
|
|
3012
|
-
|
|
3013
|
-
* Registry of "not settled yet" reporters. Lives in the root injector so a route component can
|
|
3014
|
-
* reach the registration created by the app level `setupScrollRestoration` call.
|
|
3015
|
-
* @internal
|
|
3016
|
-
*/
|
|
3017
|
-
const [, injectScrollRestorationHolds] = createRootProvider(() => {
|
|
3073
|
+
const SCROLL_RESTORATION_HOLDS_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3018
3074
|
const holds = new Set();
|
|
3019
3075
|
return {
|
|
3020
3076
|
add: (isPending) => {
|
|
@@ -3030,11 +3086,17 @@ const [, injectScrollRestorationHolds] = createRootProvider(() => {
|
|
|
3030
3086
|
},
|
|
3031
3087
|
};
|
|
3032
3088
|
}, { name: 'ScrollRestorationHolds' });
|
|
3089
|
+
/**
|
|
3090
|
+
* Registry of "not settled yet" reporters. Lives in the root injector so a route component can
|
|
3091
|
+
* reach the registration created by the app level `setupScrollRestoration` call.
|
|
3092
|
+
* @internal
|
|
3093
|
+
*/
|
|
3094
|
+
const injectScrollRestorationHolds = /* @__PURE__ */ toInjectFn(SCROLL_RESTORATION_HOLDS_DEF);
|
|
3033
3095
|
/**
|
|
3034
3096
|
* Suspend scroll restoration while `isPending` reads `true`.
|
|
3035
3097
|
*
|
|
3036
3098
|
* Restoration already waits for the scroll container to grow tall enough to reach the saved offset,
|
|
3037
|
-
* so this is only needed when the content may take longer than `restore.timeout` to get there
|
|
3099
|
+
* so this is only needed when the content may take longer than `restore.timeout` to get there -
|
|
3038
3100
|
* a slow list request, for instance. Registers for the lifetime of the current injection context.
|
|
3039
3101
|
*
|
|
3040
3102
|
* @example
|
|
@@ -3068,7 +3130,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3068
3130
|
return el ?? document.documentElement;
|
|
3069
3131
|
};
|
|
3070
3132
|
// The browser restores the offset against the still-loading (much shorter) document, which is the
|
|
3071
|
-
// exact problem we are solving
|
|
3133
|
+
// exact problem we are solving - take it over completely.
|
|
3072
3134
|
if (restoreEnabled && 'scrollRestoration' in history) {
|
|
3073
3135
|
history.scrollRestoration = 'manual';
|
|
3074
3136
|
}
|
|
@@ -3107,7 +3169,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3107
3169
|
}
|
|
3108
3170
|
const now = Date.now();
|
|
3109
3171
|
// A registered hold means the page knows more data is coming, so the content is expected to
|
|
3110
|
-
// grow
|
|
3172
|
+
// grow - suspend the clock instead of giving up on a still loading page.
|
|
3111
3173
|
if (holds.isHeld() && now < hardDeadline) {
|
|
3112
3174
|
deadline = now + restoreTimeout;
|
|
3113
3175
|
}
|
|
@@ -3120,7 +3182,7 @@ const setupScrollRestoration = (config = {}) => {
|
|
|
3120
3182
|
}
|
|
3121
3183
|
cancelPendingRestore();
|
|
3122
3184
|
};
|
|
3123
|
-
// Wait for the new route to actually render before reading any geometry
|
|
3185
|
+
// Wait for the new route to actually render before reading any geometry - on `NavigationEnd` the
|
|
3124
3186
|
// components exist but the DOM still shows the previous page, whose height would be measured
|
|
3125
3187
|
// instead. The timeout is a safety net in case no render pass follows.
|
|
3126
3188
|
const start = () => {
|
|
@@ -3244,7 +3306,7 @@ const createLogger = (config) => {
|
|
|
3244
3306
|
};
|
|
3245
3307
|
|
|
3246
3308
|
const escapeHtml = (str) => str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
3247
|
-
/** Escapes HTML-special characters but leaves existing entity references intact
|
|
3309
|
+
/** Escapes HTML-special characters but leaves existing entity references intact - for input that
|
|
3248
3310
|
* may already contain entities (e.g. Markdown serialized from the editor's DOM, where `&` is
|
|
3249
3311
|
* stored as `&`), where plain escaping would double-escape them. */
|
|
3250
3312
|
const escapeHtmlPreservingEntities = (str) => str
|
|
@@ -3253,7 +3315,7 @@ const escapeHtmlPreservingEntities = (str) => str
|
|
|
3253
3315
|
.replace(/>/g, '>')
|
|
3254
3316
|
.replace(/"/g, '"');
|
|
3255
3317
|
/** Rejects URL schemes that execute script when navigated. Whitespace/control characters are
|
|
3256
|
-
* stripped before checking
|
|
3318
|
+
* stripped before checking - browsers ignore them inside URLs, so `java\tscript:` would
|
|
3257
3319
|
* otherwise slip through. */
|
|
3258
3320
|
const isSafeUrl = (url) =>
|
|
3259
3321
|
// eslint-disable-next-line no-control-regex -- stripping control characters is the point: browsers ignore them inside URLs
|
|
@@ -3280,7 +3342,7 @@ const parseSeparatorAligns = (line) => parseTableRow(line).map((cell) => {
|
|
|
3280
3342
|
/** The GFM separator token for a column alignment. */
|
|
3281
3343
|
const separatorFor = (align) => align === 'center' ? ':---:' : align === 'right' ? '---:' : align === 'left' ? ':---' : '---';
|
|
3282
3344
|
const alignStyle = (align) => (align ? ` style="text-align: ${align}"` : '');
|
|
3283
|
-
/** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters
|
|
3345
|
+
/** Wraps `content` in an emphasis `marker`, hoisting boundary whitespace outside the delimiters -
|
|
3284
3346
|
* CommonMark emphasis must not face whitespace on the inside (`** fett**` doesn't parse), and
|
|
3285
3347
|
* ` ` counts as whitespace there too. Whitespace-only content stays unwrapped. */
|
|
3286
3348
|
const emphasize = (marker, content) => {
|
|
@@ -3294,9 +3356,9 @@ const makePlaceholder = (kind, idx) => `\u{E000}${kind}${idx}\u{E001}`;
|
|
|
3294
3356
|
const placeholderRe = (kind) => new RegExp(`\u{E000}${kind}(\\d+)\u{E001}`, 'gu');
|
|
3295
3357
|
const isPlaceholder = (kind, str) => new RegExp(`^\u{E000}${kind}\\d+\u{E001}$`, 'u').test(str);
|
|
3296
3358
|
/** Tags allowed to survive inside an aligned block's raw-HTML passthrough. */
|
|
3297
|
-
const SAFE_INLINE_TAGS = new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
|
|
3359
|
+
const SAFE_INLINE_TAGS = /* @__PURE__ */ new Set(['strong', 'em', 'b', 'i', 'del', 's', 'u', 'code', 'br']);
|
|
3298
3360
|
/** Reduces raw inline HTML to the editor's own vocabulary: allowed tags lose all their attributes
|
|
3299
|
-
* (`<a>` keeps a safe `href`), everything else
|
|
3361
|
+
* (`<a>` keeps a safe `href`), everything else - including any event-handler attribute - is
|
|
3300
3362
|
* dropped, and the remaining text is escaped. */
|
|
3301
3363
|
const sanitizeInlineHtml = (html) => {
|
|
3302
3364
|
const kept = [];
|
|
@@ -3321,7 +3383,7 @@ const processInline = (text) => {
|
|
|
3321
3383
|
});
|
|
3322
3384
|
// "Open in new tab" links round-trip as raw HTML because Markdown has no `target` syntax. Extract
|
|
3323
3385
|
// them before the raw-HTML escape below, keeping only a safe href, `target="_blank"` and a forced
|
|
3324
|
-
// `rel="noopener noreferrer"` (any other attribute
|
|
3386
|
+
// `rel="noopener noreferrer"` (any other attribute - including event handlers - is dropped); their
|
|
3325
3387
|
// inner markup is processed recursively. Anything else that looks like raw HTML is still escaped.
|
|
3326
3388
|
const newTabLinks = [];
|
|
3327
3389
|
text = text.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (full, attrs, inner) => {
|
|
@@ -3332,11 +3394,11 @@ const processInline = (text) => {
|
|
|
3332
3394
|
const anchor = `<a href="${href}" target="_blank" rel="noopener noreferrer">${processInline(inner)}</a>`;
|
|
3333
3395
|
return makePlaceholder('NTLINK', newTabLinks.push(anchor) - 1);
|
|
3334
3396
|
});
|
|
3335
|
-
// Raw HTML in Markdown text is escaped, not rendered
|
|
3397
|
+
// Raw HTML in Markdown text is escaped, not rendered - the editor writes this HTML straight
|
|
3336
3398
|
// into the DOM via innerHTML, so anything else would let a crafted value inject markup. `<u>`
|
|
3337
3399
|
// is the one deliberate exception: underline has no Markdown form and round-trips as raw <u>.
|
|
3338
3400
|
text = escapeHtmlPreservingEntities(text).replace(/<(\/?)u>/gi, '<$1u>');
|
|
3339
|
-
// Bold + italic
|
|
3401
|
+
// Bold + italic - *** before ** before *. A delimiter flanking whitespace doesn't open/close a
|
|
3340
3402
|
// run (CommonMark's flanking rule), so literal asterisks as in `2 * 3 * 4` stay text.
|
|
3341
3403
|
text = text.replace(/\*\*\*([^\s*](?:.*?[^\s*])?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
3342
3404
|
text = text.replace(/\*\*([^\s*](?:.*?[^\s*])?)\*\*/g, '<strong>$1</strong>');
|
|
@@ -3399,6 +3461,75 @@ const findListItems = (inner) => {
|
|
|
3399
3461
|
}
|
|
3400
3462
|
return items;
|
|
3401
3463
|
};
|
|
3464
|
+
/** The first top-level `<blockquote>` in `html`, matched with balanced nesting. */
|
|
3465
|
+
const findBlockquote = (html) => {
|
|
3466
|
+
const open = /<blockquote\b[^>]*>/i.exec(html);
|
|
3467
|
+
if (!open)
|
|
3468
|
+
return null;
|
|
3469
|
+
const innerStart = open.index + open[0].length;
|
|
3470
|
+
const tag = /<(\/?)blockquote\b[^>]*>/gi;
|
|
3471
|
+
tag.lastIndex = innerStart;
|
|
3472
|
+
let depth = 1;
|
|
3473
|
+
let match;
|
|
3474
|
+
while ((match = tag.exec(html))) {
|
|
3475
|
+
depth += match[1] ? -1 : 1;
|
|
3476
|
+
if (depth === 0) {
|
|
3477
|
+
return { start: open.index, end: tag.lastIndex, inner: html.slice(innerStart, match.index) };
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
return null;
|
|
3481
|
+
};
|
|
3482
|
+
/** The lines of quoted text in a blockquote's own (non-nested) HTML, each with its `> ` prefix.
|
|
3483
|
+
* `<br>` and paragraph boundaries are the line breaks - the tag-strip would swallow them. */
|
|
3484
|
+
const quotedLines = (html, prefix) => {
|
|
3485
|
+
const text = stripTags(html.replace(/<br\s*\/?>/gi, '\n').replace(/<\/p\s*>/gi, '\n')).trim();
|
|
3486
|
+
return text ? text.split('\n').map((line) => `${prefix}${line.trim()}`) : [];
|
|
3487
|
+
};
|
|
3488
|
+
/** Serializes a blockquote's inner HTML to Markdown, one `>` per nesting level. */
|
|
3489
|
+
const blockquoteToMarkdown = (inner, depth) => {
|
|
3490
|
+
const prefix = '> '.repeat(depth + 1);
|
|
3491
|
+
const lines = [];
|
|
3492
|
+
let rest = inner;
|
|
3493
|
+
for (let nested = findBlockquote(rest); nested; nested = findBlockquote(rest)) {
|
|
3494
|
+
lines.push(...quotedLines(rest.slice(0, nested.start), prefix));
|
|
3495
|
+
lines.push(blockquoteToMarkdown(nested.inner, depth + 1));
|
|
3496
|
+
rest = rest.slice(nested.end);
|
|
3497
|
+
}
|
|
3498
|
+
lines.push(...quotedLines(rest, prefix));
|
|
3499
|
+
// An empty quote still needs its marker, or it would vanish from the value entirely.
|
|
3500
|
+
return lines.length > 0 ? lines.join('\n') : prefix.trim();
|
|
3501
|
+
};
|
|
3502
|
+
/** Builds a `<blockquote>` from `> `-prefixed lines, recursing into runs of deeper (`>>`) lines. */
|
|
3503
|
+
const buildBlockquoteHtml = (lines) => {
|
|
3504
|
+
let html = '';
|
|
3505
|
+
let run = [];
|
|
3506
|
+
const flushRun = () => {
|
|
3507
|
+
if (run.length > 0)
|
|
3508
|
+
html += run.map(processInline).join('<br>');
|
|
3509
|
+
run = [];
|
|
3510
|
+
};
|
|
3511
|
+
for (let i = 0; i < lines.length;) {
|
|
3512
|
+
const stripped = (lines[i] ?? '').replace(/^>[ \t]?/, '');
|
|
3513
|
+
if (!stripped.startsWith('>')) {
|
|
3514
|
+
run.push(stripped);
|
|
3515
|
+
i++;
|
|
3516
|
+
continue;
|
|
3517
|
+
}
|
|
3518
|
+
flushRun();
|
|
3519
|
+
const nested = [];
|
|
3520
|
+
while (i < lines.length) {
|
|
3521
|
+
const line = (lines[i] ?? '').replace(/^>[ \t]?/, '');
|
|
3522
|
+
if (!line.startsWith('>'))
|
|
3523
|
+
break;
|
|
3524
|
+
nested.push(line);
|
|
3525
|
+
i++;
|
|
3526
|
+
}
|
|
3527
|
+
html += buildBlockquoteHtml(nested);
|
|
3528
|
+
}
|
|
3529
|
+
flushRun();
|
|
3530
|
+
// `<br>` so an empty quote has a line box the caret can sit in (the editor writes this HTML live)
|
|
3531
|
+
return `<blockquote>${html || '<br>'}</blockquote>`;
|
|
3532
|
+
};
|
|
3402
3533
|
/** Serializes a list's inner HTML to Markdown, indenting nested lists two spaces per level. */
|
|
3403
3534
|
const listToMarkdown = (inner, ordered, depth) => {
|
|
3404
3535
|
const indent = ' '.repeat(depth);
|
|
@@ -3417,7 +3548,7 @@ const listToMarkdown = (inner, ordered, depth) => {
|
|
|
3417
3548
|
})
|
|
3418
3549
|
.join('\n');
|
|
3419
3550
|
};
|
|
3420
|
-
/** Parses list lines into (indent-level, ordered, text)
|
|
3551
|
+
/** Parses list lines into (indent-level, ordered, text) - two leading spaces per nesting level. */
|
|
3421
3552
|
const parseListLines = (lines) => lines
|
|
3422
3553
|
.map((line) => {
|
|
3423
3554
|
const match = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
@@ -3491,7 +3622,7 @@ const markdownToHtml = (markdown) => {
|
|
|
3491
3622
|
// Horizontal rule
|
|
3492
3623
|
if (/^(---|\*\*\*|___)\s*$/.test(trimmed))
|
|
3493
3624
|
return '<hr>';
|
|
3494
|
-
// Table (GFM)
|
|
3625
|
+
// Table (GFM) - column alignment from the separator line lands as text-align on every cell
|
|
3495
3626
|
const tableLines = trimmed.split('\n');
|
|
3496
3627
|
if (tableLines.length >= 2 && /\|/.test(tableLines[0] ?? '') && isTableSeparatorLine(tableLines[1] ?? '')) {
|
|
3497
3628
|
const aligns = parseSeparatorAligns(tableLines[1] ?? '');
|
|
@@ -3509,15 +3640,10 @@ const markdownToHtml = (markdown) => {
|
|
|
3509
3640
|
: '';
|
|
3510
3641
|
return `<table>${thead}${tbody}</table>`;
|
|
3511
3642
|
}
|
|
3512
|
-
// Blockquote
|
|
3513
|
-
// HTML, so joining first would escape the <br> separators too)
|
|
3514
|
-
if (
|
|
3515
|
-
|
|
3516
|
-
.split('\n')
|
|
3517
|
-
.map((l) => processInline(l.replace(/^>\s?/, '')))
|
|
3518
|
-
.join('<br>');
|
|
3519
|
-
return `<blockquote>${content}</blockquote>`;
|
|
3520
|
-
}
|
|
3643
|
+
// Blockquote - each line runs through the inline pass on its own (processInline escapes raw
|
|
3644
|
+
// HTML, so joining first would escape the <br> separators too), and a `>>` run nests
|
|
3645
|
+
if (/^>/.test(trimmed))
|
|
3646
|
+
return buildBlockquoteHtml(trimmed.split('\n'));
|
|
3521
3647
|
// List (unordered/ordered, with indentation-based nesting)
|
|
3522
3648
|
if (/^([-*+]|\d+\.)\s/.test(trimmed)) {
|
|
3523
3649
|
const lines = parseListLines(trimmed.split('\n'));
|
|
@@ -3525,7 +3651,7 @@ const markdownToHtml = (markdown) => {
|
|
|
3525
3651
|
return buildListHtml(lines, 0, lines[0].indent).html;
|
|
3526
3652
|
}
|
|
3527
3653
|
}
|
|
3528
|
-
// Paragraph
|
|
3654
|
+
// Paragraph - single newlines within a block become <br>
|
|
3529
3655
|
return `<p>${trimmed
|
|
3530
3656
|
.split('\n')
|
|
3531
3657
|
.map((l) => processInline(l))
|
|
@@ -3545,21 +3671,21 @@ const htmlToMarkdown = (html) => {
|
|
|
3545
3671
|
return '';
|
|
3546
3672
|
let md = html;
|
|
3547
3673
|
// Aligned blocks: text-align has no Markdown form, so preserve them verbatim as native HTML (their
|
|
3548
|
-
// inner markup stays HTML) and round-trip via a placeholder
|
|
3674
|
+
// inner markup stays HTML) and round-trip via a placeholder - extracted before the block passes
|
|
3549
3675
|
// below rewrite them, restored after the final tag-strip.
|
|
3550
3676
|
const alignedBlocks = [];
|
|
3551
3677
|
md = md.replace(/<(p|h[1-6]|div)\b[^>]*\bstyle="[^"]*text-align[^"]*"[^>]*>[\s\S]*?<\/\1>/gi, (match) => makePlaceholder('ALIGN', alignedBlocks.push(match) - 1));
|
|
3552
|
-
// Code blocks
|
|
3678
|
+
// Code blocks - process before inline code
|
|
3553
3679
|
md = md.replace(/<pre[^>]*><code[^>]*class="language-([^"]+)"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, lang, code) => `\n\`\`\`${lang}\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3554
3680
|
md = md.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3555
3681
|
md = md.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_, code) => `\n\`\`\`\n${unescapeHtml(code.trim())}\n\`\`\`\n`);
|
|
3556
|
-
// Headings
|
|
3682
|
+
// Headings - keep the inner markup so the inline passes below turn any nested
|
|
3557
3683
|
// bold/italic/link/code into markdown; leftover tags are stripped at the end.
|
|
3558
3684
|
for (let i = 6; i >= 1; i--) {
|
|
3559
3685
|
md = md.replace(new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, 'gi'), (_, content) => `\n${'#'.repeat(i)} ${content.trim()}\n`);
|
|
3560
3686
|
}
|
|
3561
3687
|
// Bold + italic (combined before individual). Boundary whitespace is hoisted out of the
|
|
3562
|
-
// delimiters (emphasize)
|
|
3688
|
+
// delimiters (emphasize) - `<strong> x</strong>` must become ` **x**`, not the invalid `** x**`.
|
|
3563
3689
|
md = md.replace(/<strong[^>]*><em[^>]*>([\s\S]*?)<\/em><\/strong>/gi, (_, c) => emphasize('***', c));
|
|
3564
3690
|
md = md.replace(/<em[^>]*><strong[^>]*>([\s\S]*?)<\/strong><\/em>/gi, (_, c) => emphasize('***', c));
|
|
3565
3691
|
md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_, _tag, c) => emphasize('**', c));
|
|
@@ -3568,7 +3694,7 @@ const htmlToMarkdown = (html) => {
|
|
|
3568
3694
|
// Inline code
|
|
3569
3695
|
md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, code) => `\`${unescapeHtml(code)}\``);
|
|
3570
3696
|
// Links and images. A link with `target="_blank"` has no Markdown form, so it round-trips as raw
|
|
3571
|
-
// HTML (keeping only href + target + rel) via a placeholder
|
|
3697
|
+
// HTML (keeping only href + target + rel) via a placeholder - like underline below; ordinary links
|
|
3572
3698
|
// become `[text](url)`.
|
|
3573
3699
|
const newTabLinks = [];
|
|
3574
3700
|
md = md.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, inner) => {
|
|
@@ -3582,20 +3708,17 @@ const htmlToMarkdown = (html) => {
|
|
|
3582
3708
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, '');
|
|
3583
3709
|
md = md.replace(/<img[^>]+alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, '');
|
|
3584
3710
|
md = md.replace(/<img[^>]+src="([^"]*)"[^>]*\/?>/gi, '');
|
|
3585
|
-
// Underline has no Markdown form
|
|
3711
|
+
// Underline has no Markdown form - preserve it as raw <u> so it round-trips. Extract it now (its
|
|
3586
3712
|
// inner markup is already converted above) so the block passes below don't strip the tag; the
|
|
3587
3713
|
// placeholder is restored after the final tag-strip.
|
|
3588
3714
|
const underlines = [];
|
|
3589
3715
|
md = md.replace(/<u[^>]*>([\s\S]*?)<\/u>/gi, (_, inner) => makePlaceholder('U', underlines.push(inner) - 1));
|
|
3590
|
-
// Block quotes
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
.join('\n') + '\n');
|
|
3597
|
-
});
|
|
3598
|
-
// Lists — replace each top-level list with its recursively-serialized Markdown (handles nesting).
|
|
3716
|
+
// Block quotes - replace each top-level <blockquote> with its recursively-serialized Markdown, so
|
|
3717
|
+
// a quote inside a quote becomes `>>` (regex alone can't match balanced nesting).
|
|
3718
|
+
for (let quote = findBlockquote(md); quote; quote = findBlockquote(md)) {
|
|
3719
|
+
md = `${md.slice(0, quote.start)}\n${blockquoteToMarkdown(quote.inner, 0)}\n${md.slice(quote.end)}`;
|
|
3720
|
+
}
|
|
3721
|
+
// Lists - replace each top-level list with its recursively-serialized Markdown (handles nesting).
|
|
3599
3722
|
for (let list = findList(md); list; list = findList(md)) {
|
|
3600
3723
|
md = `${md.slice(0, list.start)}\n${listToMarkdown(list.inner, list.ordered, 0)}\n${md.slice(list.end)}`;
|
|
3601
3724
|
}
|
|
@@ -3623,10 +3746,10 @@ const htmlToMarkdown = (html) => {
|
|
|
3623
3746
|
const lines = [`| ${headerCells.join(' | ')} |`, `| ${separator} |`, ...bodyRows.map((r) => `| ${r} |`)];
|
|
3624
3747
|
return `\n${lines.join('\n')}\n`;
|
|
3625
3748
|
});
|
|
3626
|
-
// Block elements
|
|
3749
|
+
// Block elements - a <br> inside a paragraph is a soft line break; turn it into a newline before
|
|
3627
3750
|
// the tag-strip removes it, so it round-trips (a single newline within a block renders as <br>)
|
|
3628
3751
|
md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => `\n${stripTags(content.replace(/<br\s*\/?>/gi, '\n')).trim()}\n`);
|
|
3629
|
-
// A <div> boundary acts as a paragraph boundary
|
|
3752
|
+
// A <div> boundary acts as a paragraph boundary - clipboard HTML commonly uses divs as
|
|
3630
3753
|
// paragraphs, and silently stripping them would merge adjacent blocks into one.
|
|
3631
3754
|
md = md.replace(/<\/div\s*>/gi, '\n\n').replace(/<div[^>]*>/gi, '\n\n');
|
|
3632
3755
|
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
@@ -3685,17 +3808,11 @@ class RuntimeError extends Error {
|
|
|
3685
3808
|
this.code = code;
|
|
3686
3809
|
this.data = data;
|
|
3687
3810
|
if (data !== RUNTIME_ERROR_NO_DATA) {
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
}
|
|
3694
|
-
catch {
|
|
3695
|
-
setTimeout(() => {
|
|
3696
|
-
console.error(data);
|
|
3697
|
-
}, 1);
|
|
3698
|
-
}
|
|
3811
|
+
// deferred so the error itself is logged first - consoles render the live object, so a
|
|
3812
|
+
// mutation after the throw is visible in the log
|
|
3813
|
+
setTimeout(() => {
|
|
3814
|
+
console.error(data);
|
|
3815
|
+
}, 1);
|
|
3699
3816
|
}
|
|
3700
3817
|
}
|
|
3701
3818
|
}
|
|
@@ -3732,7 +3849,7 @@ const canUseSessionMemory = () => {
|
|
|
3732
3849
|
if (typeof globalThis.document === 'undefined') {
|
|
3733
3850
|
return false;
|
|
3734
3851
|
}
|
|
3735
|
-
return globalThis.sessionStorage
|
|
3852
|
+
return globalThis.sessionStorage !== null && globalThis.sessionStorage !== undefined;
|
|
3736
3853
|
}
|
|
3737
3854
|
catch {
|
|
3738
3855
|
return false;
|
|
@@ -3859,8 +3976,8 @@ const createSwipeTracker = (startEvent) => {
|
|
|
3859
3976
|
if (firstInWindow === -1)
|
|
3860
3977
|
return { x: 0, y: 0 };
|
|
3861
3978
|
const newest = samples[samples.length - 1];
|
|
3862
|
-
// Measure across the window when it holds a span of its own. With a single sample in it
|
|
3863
|
-
// pointer emitting moves slower than the window
|
|
3979
|
+
// Measure across the window when it holds a span of its own. With a single sample in it - a
|
|
3980
|
+
// pointer emitting moves slower than the window - reach back one sample for a baseline instead
|
|
3864
3981
|
// of reporting nothing.
|
|
3865
3982
|
const hasSpanInWindow = samples.length - firstInWindow >= 2;
|
|
3866
3983
|
const baseline = samples[hasSpanInWindow ? firstInWindow : Math.max(0, firstInWindow - 1)];
|
|
@@ -3897,7 +4014,7 @@ const createSwipeTracker = (startEvent) => {
|
|
|
3897
4014
|
return { update, end, cancel };
|
|
3898
4015
|
};
|
|
3899
4016
|
|
|
3900
|
-
const
|
|
4017
|
+
const ANGULAR_ROOT_ELEMENT_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3901
4018
|
const appRef = inject(ApplicationRef);
|
|
3902
4019
|
const rootElement = signal(null, /* @ts-ignore */
|
|
3903
4020
|
...(ngDevMode ? [{ debugName: "rootElement" }] : /* istanbul ignore next */ []));
|
|
@@ -3913,8 +4030,9 @@ const [, injectAngularRootElement] = createRootProvider(() => {
|
|
|
3913
4030
|
setTimeout(poll);
|
|
3914
4031
|
return rootElement;
|
|
3915
4032
|
});
|
|
4033
|
+
const injectAngularRootElement = /* @__PURE__ */ toInjectFn(ANGULAR_ROOT_ELEMENT_DEF);
|
|
3916
4034
|
|
|
3917
|
-
const
|
|
4035
|
+
const BOUNDARY_ELEMENT_DEF = /* @__PURE__ */ defineProvider(() => {
|
|
3918
4036
|
const hostElement = inject(ElementRef, { optional: true });
|
|
3919
4037
|
const document = inject(DOCUMENT);
|
|
3920
4038
|
const override = signal(null, /* @ts-ignore */
|
|
@@ -3926,8 +4044,11 @@ const [provideBoundaryElement, injectBoundaryElement, BOUNDARY_ELEMENT_TOKEN] =
|
|
|
3926
4044
|
override,
|
|
3927
4045
|
};
|
|
3928
4046
|
}, { name: 'Boundary Element' });
|
|
4047
|
+
const provideBoundaryElement = /* @__PURE__ */ toProvideFn(BOUNDARY_ELEMENT_DEF);
|
|
4048
|
+
const injectBoundaryElement = /* @__PURE__ */ toInjectFn(BOUNDARY_ELEMENT_DEF);
|
|
4049
|
+
const BOUNDARY_ELEMENT_TOKEN = /* @__PURE__ */ toToken(BOUNDARY_ELEMENT_DEF);
|
|
3929
4050
|
|
|
3930
|
-
const
|
|
4051
|
+
const FOCUS_VISIBLE_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3931
4052
|
const document = inject(DOCUMENT);
|
|
3932
4053
|
const destroyRef = inject(DestroyRef);
|
|
3933
4054
|
const isFocusVisible = signal(false, /* @ts-ignore */
|
|
@@ -3969,8 +4090,11 @@ const [provideFocusVisibleTracker, injectFocusVisibleTracker, FOCUS_VISIBLE_TRAC
|
|
|
3969
4090
|
isFocusVisible: isFocusVisible.asReadonly(),
|
|
3970
4091
|
};
|
|
3971
4092
|
}, { name: 'Focus Visible Tracker' });
|
|
4093
|
+
const provideFocusVisibleTracker = /* @__PURE__ */ toProvideFn(FOCUS_VISIBLE_TRACKER_DEF);
|
|
4094
|
+
const injectFocusVisibleTracker = /* @__PURE__ */ toInjectFn(FOCUS_VISIBLE_TRACKER_DEF);
|
|
4095
|
+
const FOCUS_VISIBLE_TRACKER_TOKEN = /* @__PURE__ */ toToken(FOCUS_VISIBLE_TRACKER_DEF);
|
|
3972
4096
|
|
|
3973
|
-
const
|
|
4097
|
+
const LOCALE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
3974
4098
|
const currentLocale = signal('en', /* @ts-ignore */
|
|
3975
4099
|
...(ngDevMode ? [{ debugName: "currentLocale" }] : /* istanbul ignore next */ []));
|
|
3976
4100
|
return {
|
|
@@ -3979,12 +4103,14 @@ const [provideLocale, injectLocale] = createRootProvider(() => {
|
|
|
3979
4103
|
}, {
|
|
3980
4104
|
name: 'Locale',
|
|
3981
4105
|
});
|
|
4106
|
+
const provideLocale = /* @__PURE__ */ toProvideFn(LOCALE_DEF);
|
|
4107
|
+
const injectLocale = /* @__PURE__ */ toInjectFn(LOCALE_DEF);
|
|
3982
4108
|
|
|
3983
4109
|
const resolveSource = (source, locale) => typeof source === 'function' ? source(locale) : source;
|
|
3984
4110
|
/**
|
|
3985
4111
|
* The one mechanism for the strings a component renders or announces itself. Every domain in the UI
|
|
3986
|
-
* library that has such strings exposes exactly this pair
|
|
3987
|
-
* subtree, `inject<Domain>Labels()` to read the result
|
|
4112
|
+
* library that has such strings exposes exactly this pair - `provide<Domain>Labels` to localize a
|
|
4113
|
+
* subtree, `inject<Domain>Labels()` to read the result - so localizing an app is the same move
|
|
3988
4114
|
* repeated per domain rather than a different one each time.
|
|
3989
4115
|
*
|
|
3990
4116
|
* `defaults` may itself be locale-derived, for a domain that ships more than one language (the
|
|
@@ -3992,7 +4118,7 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
|
|
|
3992
4118
|
* resolved to, so translating into a third language while keeping a shipped one as the base is the
|
|
3993
4119
|
* same call.
|
|
3994
4120
|
*
|
|
3995
|
-
* The injector returns a **signal**, because both halves can change at runtime
|
|
4121
|
+
* The injector returns a **signal**, because both halves can change at runtime - read it in a
|
|
3996
4122
|
* template or computed, never destructure it once.
|
|
3997
4123
|
*
|
|
3998
4124
|
* @example
|
|
@@ -4003,8 +4129,11 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
|
|
|
4003
4129
|
* overflow: 'Show hidden levels',
|
|
4004
4130
|
* };
|
|
4005
4131
|
*
|
|
4006
|
-
*
|
|
4007
|
-
*
|
|
4132
|
+
* const BREADCRUMB_LABELS_DEF = defineLabels<BreadcrumbLabels>('BREADCRUMB_LABELS', DEFAULT_BREADCRUMB_LABELS);
|
|
4133
|
+
*
|
|
4134
|
+
* export const provideBreadcrumbLabels = toProvideFn(BREADCRUMB_LABELS_DEF);
|
|
4135
|
+
* export const injectBreadcrumbLabels = toInjectFn(BREADCRUMB_LABELS_DEF);
|
|
4136
|
+
* export const BREADCRUMB_LABELS = toToken(BREADCRUMB_LABELS_DEF);
|
|
4008
4137
|
*
|
|
4009
4138
|
* @example
|
|
4010
4139
|
* // consumer, fixed wording
|
|
@@ -4013,24 +4142,26 @@ const resolveSource = (source, locale) => typeof source === 'function' ? source(
|
|
|
4013
4142
|
* // consumer, driven by the app's i18n and re-resolved on a locale change
|
|
4014
4143
|
* provideBreadcrumbLabels((locale) => ({ navigation: translate('breadcrumb.nav', locale) }));
|
|
4015
4144
|
*/
|
|
4016
|
-
const
|
|
4145
|
+
const defineLabels = (name, defaults) => {
|
|
4017
4146
|
const token = new InjectionToken(name, {
|
|
4018
4147
|
providedIn: 'root',
|
|
4019
4148
|
factory: () => ({}),
|
|
4020
4149
|
});
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4150
|
+
return {
|
|
4151
|
+
provide: (labels) => ({ provide: token, useValue: labels }),
|
|
4152
|
+
inject: () => {
|
|
4153
|
+
const source = inject(token);
|
|
4154
|
+
const { currentLocale } = injectLocale();
|
|
4155
|
+
return computed(() => {
|
|
4156
|
+
const locale = currentLocale();
|
|
4157
|
+
return { ...resolveSource(defaults, locale), ...resolveSource(source, locale) };
|
|
4158
|
+
});
|
|
4159
|
+
},
|
|
4160
|
+
token,
|
|
4029
4161
|
};
|
|
4030
|
-
return [provide, injectFn, token];
|
|
4031
4162
|
};
|
|
4032
4163
|
|
|
4033
|
-
const
|
|
4164
|
+
const STYLE_MANAGER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
4034
4165
|
const appRef = inject(ApplicationRef);
|
|
4035
4166
|
const destroyRef = inject(DestroyRef);
|
|
4036
4167
|
const document = inject(DOCUMENT);
|
|
@@ -4089,6 +4220,8 @@ const [provideStyleManager, injectStyleManager] = createRootProvider(() => {
|
|
|
4089
4220
|
});
|
|
4090
4221
|
return { mount };
|
|
4091
4222
|
}, { name: 'StyleManager' });
|
|
4223
|
+
const provideStyleManager = /* @__PURE__ */ toProvideFn(STYLE_MANAGER_DEF);
|
|
4224
|
+
const injectStyleManager = /* @__PURE__ */ toInjectFn(STYLE_MANAGER_DEF);
|
|
4092
4225
|
|
|
4093
4226
|
const createUserConsentProvider = (options) => ({
|
|
4094
4227
|
provide: options.for,
|
|
@@ -4123,7 +4256,7 @@ const forceReflow = (element) => {
|
|
|
4123
4256
|
*
|
|
4124
4257
|
* Inside a component, prefer the reactive `injectPrefersReducedMotion()`. This is for the animation
|
|
4125
4258
|
* helpers that run from effects, event handlers and imperative APIs, where there is no injection
|
|
4126
|
-
* context. Pass the element being animated so the query resolves against its own window
|
|
4259
|
+
* context. Pass the element being animated so the query resolves against its own window - otherwise
|
|
4127
4260
|
* an animation inside an iframe would read the top-level document's preference.
|
|
4128
4261
|
*/
|
|
4129
4262
|
const matchesReducedMotion = (element) => {
|
|
@@ -4354,7 +4487,7 @@ class AnimatedLifecycleDirective {
|
|
|
4354
4487
|
if (!e.cancelled && e.transitionId === transitionId) {
|
|
4355
4488
|
return of(true);
|
|
4356
4489
|
}
|
|
4357
|
-
// The batch ended cancelled or under a different id
|
|
4490
|
+
// The batch ended cancelled or under a different id - e.g. the browser cancelled and
|
|
4358
4491
|
// restarted a running transition mid-flight (an anchored overlay whose ancestor pane got
|
|
4359
4492
|
// destroyed) or a foreign transition on the same element finished. If nothing is
|
|
4360
4493
|
// animating anymore the expected end event can never arrive, so treat the transition as
|
|
@@ -4501,321 +4634,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
4501
4634
|
}]
|
|
4502
4635
|
}], ctorParameters: () => [], propDecorators: { ifValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "etAnimatedIf", required: false }] }] } });
|
|
4503
4636
|
|
|
4504
|
-
class AnimatedOverlayDirective {
|
|
4505
|
-
constructor() {
|
|
4506
|
-
this.overlayService = inject(Overlay);
|
|
4507
|
-
this.injector = inject(Injector);
|
|
4508
|
-
this.viewContainerRef = inject(ViewContainerRef);
|
|
4509
|
-
this.zone = inject(NgZone);
|
|
4510
|
-
this.elementRef = inject(ElementRef);
|
|
4511
|
-
this.rootBoundary = injectBoundaryElement({ optional: true });
|
|
4512
|
-
this.destroyRef = inject(DestroyRef);
|
|
4513
|
-
this.floatingElCleanupFn = null;
|
|
4514
|
-
this.unmountSubscription = null;
|
|
4515
|
-
this.placement = input('bottom', /* @ts-ignore */
|
|
4516
|
-
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
4517
|
-
this.fallbackPlacements = input(/* @ts-ignore */
|
|
4518
|
-
...(ngDevMode ? [undefined, { debugName: "fallbackPlacements" }] : /* istanbul ignore next */ []));
|
|
4519
|
-
this.offset = input(null, /* @ts-ignore */
|
|
4520
|
-
...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
4521
|
-
this.arrowPadding = input(4, /* @ts-ignore */
|
|
4522
|
-
...(ngDevMode ? [{ debugName: "arrowPadding" }] : /* istanbul ignore next */ []));
|
|
4523
|
-
this.viewportPadding = input(8, /* @ts-ignore */
|
|
4524
|
-
...(ngDevMode ? [{ debugName: "viewportPadding" }] : /* istanbul ignore next */ []));
|
|
4525
|
-
this.autoResize = input(false, { ...(ngDevMode ? { debugName: "autoResize" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4526
|
-
this.shift = input(true, { ...(ngDevMode ? { debugName: "shift" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4527
|
-
this.autoHide = input(false, { ...(ngDevMode ? { debugName: "autoHide" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4528
|
-
this.autoCloseIfReferenceHidden = input(false, { ...(ngDevMode ? { debugName: "autoCloseIfReferenceHidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4529
|
-
this.referenceElement = input(this.elementRef.nativeElement, /* @ts-ignore */
|
|
4530
|
-
...(ngDevMode ? [{ debugName: "referenceElement" }] : /* istanbul ignore next */ []));
|
|
4531
|
-
this.mirrorWidth = input(false, { ...(ngDevMode ? { debugName: "mirrorWidth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4532
|
-
this.portal = null;
|
|
4533
|
-
this.overlayRef = null;
|
|
4534
|
-
this.componentRef = null;
|
|
4535
|
-
this.beforeOpened$ = new Subject();
|
|
4536
|
-
this.afterOpened$ = new Subject();
|
|
4537
|
-
this.beforeClosed$ = new Subject();
|
|
4538
|
-
this.afterClosed$ = new Subject();
|
|
4539
|
-
this.state = signal('init', /* @ts-ignore */
|
|
4540
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
4541
|
-
this.isMounted = computed(() => this.state() === 'mounted', /* @ts-ignore */
|
|
4542
|
-
...(ngDevMode ? [{ debugName: "isMounted" }] : /* istanbul ignore next */ []));
|
|
4543
|
-
this.isMounting = computed(() => this.state() === 'mounting', /* @ts-ignore */
|
|
4544
|
-
...(ngDevMode ? [{ debugName: "isMounting" }] : /* istanbul ignore next */ []));
|
|
4545
|
-
this.isUnmounted = computed(() => this.state() === 'unmounted', /* @ts-ignore */
|
|
4546
|
-
...(ngDevMode ? [{ debugName: "isUnmounted" }] : /* istanbul ignore next */ []));
|
|
4547
|
-
this.isUnmounting = computed(() => this.state() === 'unmounting', /* @ts-ignore */
|
|
4548
|
-
...(ngDevMode ? [{ debugName: "isUnmounting" }] : /* istanbul ignore next */ []));
|
|
4549
|
-
this.canMount = computed(() => !this.isMounted() && !this.isMounting(), /* @ts-ignore */
|
|
4550
|
-
...(ngDevMode ? [{ debugName: "canMount" }] : /* istanbul ignore next */ []));
|
|
4551
|
-
this.canUnmount = computed(() => (this.isMounted() || this.isMounting()) && !this.isUnmounting(), /* @ts-ignore */
|
|
4552
|
-
...(ngDevMode ? [{ debugName: "canUnmount" }] : /* istanbul ignore next */ []));
|
|
4553
|
-
this.isHidden = signal(false, /* @ts-ignore */
|
|
4554
|
-
...(ngDevMode ? [{ debugName: "isHidden" }] : /* istanbul ignore next */ []));
|
|
4555
|
-
this.isMounted$ = toObservable(this.isMounted);
|
|
4556
|
-
this.isMounting$ = toObservable(this.isMounting);
|
|
4557
|
-
this.isUnmounted$ = toObservable(this.isUnmounted);
|
|
4558
|
-
this.isUnmounting$ = toObservable(this.isUnmounting);
|
|
4559
|
-
this.canMount$ = toObservable(this.canMount);
|
|
4560
|
-
this.canUnmount$ = toObservable(this.canUnmount);
|
|
4561
|
-
this.isHidden$ = toObservable(this.isHidden);
|
|
4562
|
-
this.referenceElementDimensions = signalElementDimensions(computed(() => (this.mirrorWidth() ? this.referenceElement() : null)));
|
|
4563
|
-
effect(() => {
|
|
4564
|
-
const dimensions = this.referenceElementDimensions();
|
|
4565
|
-
if (!dimensions || !this.mirrorWidth())
|
|
4566
|
-
return;
|
|
4567
|
-
this.overlayRef?.updateSize({
|
|
4568
|
-
width: this.elementRef.nativeElement.offsetWidth,
|
|
4569
|
-
});
|
|
4570
|
-
});
|
|
4571
|
-
this.destroyRef.onDestroy(() => this.destroy());
|
|
4572
|
-
}
|
|
4573
|
-
mount(config) {
|
|
4574
|
-
if (this.isUnmounting()) {
|
|
4575
|
-
if (!this.componentRef) {
|
|
4576
|
-
if (isDevMode()) {
|
|
4577
|
-
console.warn('AnimatedOverlayDirective: Cannot remount. Component ref is not available.');
|
|
4578
|
-
}
|
|
4579
|
-
return;
|
|
4580
|
-
}
|
|
4581
|
-
this.unmountSubscription?.unsubscribe();
|
|
4582
|
-
this.unmountSubscription = null;
|
|
4583
|
-
this.applyComponentData(config.data);
|
|
4584
|
-
this.applyColorProvider(config.colorProvider);
|
|
4585
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle();
|
|
4586
|
-
if (lifecycle) {
|
|
4587
|
-
this.state.set('mounting');
|
|
4588
|
-
this.beforeOpened$.next();
|
|
4589
|
-
lifecycle.enter();
|
|
4590
|
-
lifecycle.state$
|
|
4591
|
-
.pipe(filter((s) => s === 'entered'), tap(() => {
|
|
4592
|
-
if (this.state() === 'mounting') {
|
|
4593
|
-
this.state.set('mounted');
|
|
4594
|
-
this.afterOpened$.next();
|
|
4595
|
-
}
|
|
4596
|
-
}), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4597
|
-
.subscribe();
|
|
4598
|
-
}
|
|
4599
|
-
return this.componentRef.instance;
|
|
4600
|
-
}
|
|
4601
|
-
if (!this.canMount()) {
|
|
4602
|
-
if (isDevMode()) {
|
|
4603
|
-
console.warn('AnimatedOverlayDirective: Cannot mount. Component is already mounted or currently mounting.');
|
|
4604
|
-
}
|
|
4605
|
-
return;
|
|
4606
|
-
}
|
|
4607
|
-
const { component, providers, data, colorProvider } = config;
|
|
4608
|
-
this.state.set('mounting');
|
|
4609
|
-
this.beforeOpened$.next();
|
|
4610
|
-
const injector = Injector.create({
|
|
4611
|
-
parent: this.injector,
|
|
4612
|
-
providers: providers ?? [],
|
|
4613
|
-
});
|
|
4614
|
-
this.overlayRef = this.overlayService.create();
|
|
4615
|
-
this.portal = new ComponentPortal(component, this.viewContainerRef, injector);
|
|
4616
|
-
this.componentRef = this.overlayRef.attach(this.portal);
|
|
4617
|
-
this.applyComponentData(data);
|
|
4618
|
-
this.applyColorProvider(colorProvider);
|
|
4619
|
-
this.updateOverlaySize();
|
|
4620
|
-
this.setupFloatingUI();
|
|
4621
|
-
return this.componentRef.instance;
|
|
4622
|
-
}
|
|
4623
|
-
unmount() {
|
|
4624
|
-
if (!this.isMounted() && !this.isMounting() && !this.isUnmounting()) {
|
|
4625
|
-
if (isDevMode()) {
|
|
4626
|
-
console.warn(`AnimatedOverlayDirective: Cannot unmount. Component is currently ${this.state()}`);
|
|
4627
|
-
}
|
|
4628
|
-
return;
|
|
4629
|
-
}
|
|
4630
|
-
if (this.isUnmounting()) {
|
|
4631
|
-
return;
|
|
4632
|
-
}
|
|
4633
|
-
if (!this.componentRef) {
|
|
4634
|
-
if (isDevMode()) {
|
|
4635
|
-
console.warn('AnimatedOverlayDirective: Cannot unmount. Component ref is not available.');
|
|
4636
|
-
}
|
|
4637
|
-
return;
|
|
4638
|
-
}
|
|
4639
|
-
this.state.set('unmounting');
|
|
4640
|
-
this.beforeClosed$.next();
|
|
4641
|
-
if (this.isHidden()) {
|
|
4642
|
-
this.destroy();
|
|
4643
|
-
return;
|
|
4644
|
-
}
|
|
4645
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle();
|
|
4646
|
-
if (!lifecycle) {
|
|
4647
|
-
this.destroy();
|
|
4648
|
-
return;
|
|
4649
|
-
}
|
|
4650
|
-
lifecycle.leave();
|
|
4651
|
-
this.unmountSubscription = lifecycle.state$
|
|
4652
|
-
.pipe(filter((s) => s === 'left'), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4653
|
-
.subscribe(() => {
|
|
4654
|
-
this.unmountSubscription = null;
|
|
4655
|
-
this.destroy();
|
|
4656
|
-
});
|
|
4657
|
-
}
|
|
4658
|
-
applyComponentData(data) {
|
|
4659
|
-
if (!this.componentRef || !data)
|
|
4660
|
-
return;
|
|
4661
|
-
Object.assign(this.componentRef.instance, data);
|
|
4662
|
-
this.componentRef.changeDetectorRef.markForCheck();
|
|
4663
|
-
}
|
|
4664
|
-
applyColorProvider(colorProvider) {
|
|
4665
|
-
if (!this.componentRef || !colorProvider)
|
|
4666
|
-
return;
|
|
4667
|
-
this.componentRef.instance.setColorFromProvider(colorProvider);
|
|
4668
|
-
}
|
|
4669
|
-
updateOverlaySize() {
|
|
4670
|
-
if (!this.overlayRef || !this.mirrorWidth())
|
|
4671
|
-
return;
|
|
4672
|
-
this.overlayRef.updateSize({
|
|
4673
|
-
width: this.elementRef.nativeElement.offsetWidth,
|
|
4674
|
-
});
|
|
4675
|
-
}
|
|
4676
|
-
setupFloatingUI() {
|
|
4677
|
-
this.zone.runOutsideAngular(() => {
|
|
4678
|
-
if (!this.componentRef)
|
|
4679
|
-
return;
|
|
4680
|
-
const floatingEl = this.componentRef.location.nativeElement;
|
|
4681
|
-
const floatingElArrow = this.getFloatingArrow();
|
|
4682
|
-
floatingEl.classList.add('et-floating-element');
|
|
4683
|
-
const refEl = this.referenceElement();
|
|
4684
|
-
const boundary = this.rootBoundary?.value();
|
|
4685
|
-
this.floatingElCleanupFn = autoUpdate(refEl, floatingEl, () => {
|
|
4686
|
-
this.updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow);
|
|
4687
|
-
});
|
|
4688
|
-
this.waitForRenderAndAnimate();
|
|
4689
|
-
});
|
|
4690
|
-
}
|
|
4691
|
-
getFloatingArrow() {
|
|
4692
|
-
if (!this.componentRef)
|
|
4693
|
-
return null;
|
|
4694
|
-
return this.componentRef.location.nativeElement.querySelector('[et-floating-arrow]');
|
|
4695
|
-
}
|
|
4696
|
-
updateFloatingPosition(floatingEl, refEl, boundary, floatingElArrow) {
|
|
4697
|
-
if (!this.componentRef)
|
|
4698
|
-
return;
|
|
4699
|
-
const middleware = this.buildFloatingMiddleware(floatingEl, boundary, floatingElArrow);
|
|
4700
|
-
computePosition(refEl, floatingEl, {
|
|
4701
|
-
placement: this.placement(),
|
|
4702
|
-
middleware,
|
|
4703
|
-
}).then(({ x, y, placement, middlewareData }) => {
|
|
4704
|
-
this.applyFloatingStyles(floatingEl, x, y, placement);
|
|
4705
|
-
this.applyArrowStyles(floatingEl, middlewareData, floatingElArrow);
|
|
4706
|
-
this.handleReferenceHidden(floatingEl, middlewareData);
|
|
4707
|
-
});
|
|
4708
|
-
}
|
|
4709
|
-
buildFloatingMiddleware(floatingEl, boundary, floatingElArrow) {
|
|
4710
|
-
const middleware = [];
|
|
4711
|
-
const offsetValue = this.offset();
|
|
4712
|
-
if (offsetValue !== null) {
|
|
4713
|
-
middleware.push(offset(offsetValue));
|
|
4714
|
-
}
|
|
4715
|
-
middleware.push(flip({
|
|
4716
|
-
fallbackPlacements: this.fallbackPlacements() ?? undefined,
|
|
4717
|
-
fallbackAxisSideDirection: 'start',
|
|
4718
|
-
boundary,
|
|
4719
|
-
}));
|
|
4720
|
-
if (this.autoResize()) {
|
|
4721
|
-
middleware.push(size({
|
|
4722
|
-
padding: this.viewportPadding() ?? undefined,
|
|
4723
|
-
apply({ availableHeight, availableWidth }) {
|
|
4724
|
-
floatingEl.style.setProperty('--et-floating-max-width', `${availableWidth}px`);
|
|
4725
|
-
floatingEl.style.setProperty('--et-floating-max-height', `${availableHeight}px`);
|
|
4726
|
-
},
|
|
4727
|
-
}));
|
|
4728
|
-
}
|
|
4729
|
-
if (this.shift()) {
|
|
4730
|
-
middleware.push(shift({
|
|
4731
|
-
limiter: limitShift(),
|
|
4732
|
-
padding: this.viewportPadding() ?? undefined,
|
|
4733
|
-
boundary,
|
|
4734
|
-
}));
|
|
4735
|
-
}
|
|
4736
|
-
if (floatingElArrow) {
|
|
4737
|
-
middleware.push(arrow({ element: floatingElArrow, padding: this.arrowPadding() ?? undefined }));
|
|
4738
|
-
}
|
|
4739
|
-
if (this.autoHide() || this.autoCloseIfReferenceHidden()) {
|
|
4740
|
-
middleware.push(hide({ strategy: 'referenceHidden', boundary }));
|
|
4741
|
-
}
|
|
4742
|
-
return middleware;
|
|
4743
|
-
}
|
|
4744
|
-
applyFloatingStyles(floatingEl, x, y, placement) {
|
|
4745
|
-
floatingEl.style.setProperty('--et-floating-translate', `translate3d(${x}px, ${y}px, 0)`);
|
|
4746
|
-
floatingEl.setAttribute('et-floating-placement', placement);
|
|
4747
|
-
}
|
|
4748
|
-
applyArrowStyles(floatingEl, middlewareData, floatingElArrow) {
|
|
4749
|
-
if (!middlewareData.arrow || !floatingElArrow)
|
|
4750
|
-
return;
|
|
4751
|
-
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
|
4752
|
-
floatingEl.style.setProperty('--et-floating-arrow-translate', `translate3d(${arrowX ?? 0}px, ${arrowY ?? 0}px, 0)`);
|
|
4753
|
-
}
|
|
4754
|
-
handleReferenceHidden(floatingEl, middlewareData) {
|
|
4755
|
-
if (middlewareData.hide?.referenceHidden) {
|
|
4756
|
-
if (this.autoCloseIfReferenceHidden()) {
|
|
4757
|
-
this.unmount();
|
|
4758
|
-
}
|
|
4759
|
-
else {
|
|
4760
|
-
floatingEl.classList.add('et-floating-element--hidden');
|
|
4761
|
-
this.isHidden.set(true);
|
|
4762
|
-
}
|
|
4763
|
-
}
|
|
4764
|
-
else if (this.autoHide()) {
|
|
4765
|
-
floatingEl.classList.remove('et-floating-element--hidden');
|
|
4766
|
-
this.isHidden.set(false);
|
|
4767
|
-
}
|
|
4768
|
-
}
|
|
4769
|
-
waitForRenderAndAnimate() {
|
|
4770
|
-
nextFrame(() => {
|
|
4771
|
-
if (!this.componentRef || this.state() !== 'mounting') {
|
|
4772
|
-
return;
|
|
4773
|
-
}
|
|
4774
|
-
const lifecycle = this.componentRef.instance.animatedLifecycle?.();
|
|
4775
|
-
if (!lifecycle) {
|
|
4776
|
-
console.error('AnimatedOverlayDirective: The component does not have an AnimatedLifecycleDirective. Please add one to the component.');
|
|
4777
|
-
return;
|
|
4778
|
-
}
|
|
4779
|
-
lifecycle.enter();
|
|
4780
|
-
lifecycle.state$
|
|
4781
|
-
.pipe(filter((s) => s === 'entered'), tap(() => {
|
|
4782
|
-
if (this.state() === 'mounting') {
|
|
4783
|
-
this.state.set('mounted');
|
|
4784
|
-
this.afterOpened$.next();
|
|
4785
|
-
}
|
|
4786
|
-
}), take(1), takeUntilDestroyed(this.destroyRef))
|
|
4787
|
-
.subscribe();
|
|
4788
|
-
});
|
|
4789
|
-
}
|
|
4790
|
-
destroy() {
|
|
4791
|
-
this.zone.runOutsideAngular(() => {
|
|
4792
|
-
this.floatingElCleanupFn?.();
|
|
4793
|
-
});
|
|
4794
|
-
if (this.overlayRef) {
|
|
4795
|
-
this.overlayRef.dispose();
|
|
4796
|
-
this.overlayRef = null;
|
|
4797
|
-
}
|
|
4798
|
-
if (this.componentRef) {
|
|
4799
|
-
this.componentRef.destroy();
|
|
4800
|
-
this.componentRef = null;
|
|
4801
|
-
}
|
|
4802
|
-
this.state.set('unmounted');
|
|
4803
|
-
this.afterClosed$.next();
|
|
4804
|
-
}
|
|
4805
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4806
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: AnimatedOverlayDirective, isStandalone: true, selector: "[etAnimatedOverlay]", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, fallbackPlacements: { classPropertyName: "fallbackPlacements", publicName: "fallbackPlacements", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, viewportPadding: { classPropertyName: "viewportPadding", publicName: "viewportPadding", isSignal: true, isRequired: false, transformFunction: null }, autoResize: { classPropertyName: "autoResize", publicName: "autoResize", isSignal: true, isRequired: false, transformFunction: null }, shift: { classPropertyName: "shift", publicName: "shift", isSignal: true, isRequired: false, transformFunction: null }, autoHide: { classPropertyName: "autoHide", publicName: "autoHide", isSignal: true, isRequired: false, transformFunction: null }, autoCloseIfReferenceHidden: { classPropertyName: "autoCloseIfReferenceHidden", publicName: "autoCloseIfReferenceHidden", isSignal: true, isRequired: false, transformFunction: null }, referenceElement: { classPropertyName: "referenceElement", publicName: "referenceElement", isSignal: true, isRequired: false, transformFunction: null }, mirrorWidth: { classPropertyName: "mirrorWidth", publicName: "mirrorWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-animated-overlay" }, exportAs: ["etAnimatedOverlay"], ngImport: i0 }); }
|
|
4807
|
-
}
|
|
4808
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: AnimatedOverlayDirective, decorators: [{
|
|
4809
|
-
type: Directive,
|
|
4810
|
-
args: [{
|
|
4811
|
-
selector: '[etAnimatedOverlay]',
|
|
4812
|
-
exportAs: 'etAnimatedOverlay',
|
|
4813
|
-
host: {
|
|
4814
|
-
class: 'et-animated-overlay',
|
|
4815
|
-
},
|
|
4816
|
-
}]
|
|
4817
|
-
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], fallbackPlacements: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackPlacements", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], viewportPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportPadding", required: false }] }], autoResize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoResize", required: false }] }], shift: [{ type: i0.Input, args: [{ isSignal: true, alias: "shift", required: false }] }], autoHide: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHide", required: false }] }], autoCloseIfReferenceHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseIfReferenceHidden", required: false }] }], referenceElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "referenceElement", required: false }] }], mirrorWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mirrorWidth", required: false }] }] } });
|
|
4818
|
-
|
|
4819
4637
|
const createFlipAnimationGroup = (config) => {
|
|
4820
4638
|
const { elements, duration = 250, easing = 'cubic-bezier(0.4, 0, 0.2, 1)', ignoreReducedMotion } = config;
|
|
4821
4639
|
const flips = elements.map((el) => {
|
|
@@ -5159,7 +4977,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
5159
4977
|
}]
|
|
5160
4978
|
}], ctorParameters: () => [], propDecorators: { commitThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitThreshold", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dragTapped: [{ type: i0.Output, args: ["dragTapped"] }], dragStarted: [{ type: i0.Output, args: ["dragStarted"] }], dragMoved: [{ type: i0.Output, args: ["dragMoved"] }], dragEnded: [{ type: i0.Output, args: ["dragEnded"] }] } });
|
|
5161
4979
|
|
|
5162
|
-
const FOCUSABLE_SELECTOR = [
|
|
4980
|
+
const FOCUSABLE_SELECTOR = /* @__PURE__ */ [
|
|
5163
4981
|
'a[href]',
|
|
5164
4982
|
'area[href]',
|
|
5165
4983
|
'button:not([disabled])',
|
|
@@ -5320,6 +5138,56 @@ const resetPositioningStyles = (config, hostElement, paneElement, renderer) => {
|
|
|
5320
5138
|
renderer.removeAttribute(paneElement, 'data-overlay-placement');
|
|
5321
5139
|
setBaseElementStyles(config, hostElement, paneElement, renderer);
|
|
5322
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
|
+
};
|
|
5323
5191
|
const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, renderer) => {
|
|
5324
5192
|
// mirrorWidth needs a real element to measure; virtual references fall back to max-content
|
|
5325
5193
|
const mirrorWidthElement = strategy.mirrorWidth && isHTMLElement(strategy.referenceElement) ? strategy.referenceElement : null;
|
|
@@ -5346,32 +5214,41 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
5346
5214
|
boundary: strategy.boundary,
|
|
5347
5215
|
}));
|
|
5348
5216
|
}
|
|
5349
|
-
// 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 -
|
|
5350
5218
|
// otherwise a cross-axis-shifted pane gets its max size capped to the unshifted leftover space
|
|
5351
5219
|
if (strategy.autoResize) {
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
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
|
+
}
|
|
5361
5232
|
}
|
|
5362
5233
|
if (arrowElement) {
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
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
|
+
}
|
|
5370
5244
|
}
|
|
5371
5245
|
if (strategy.autoHide || strategy.autoCloseIfReferenceHidden) {
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5246
|
+
const extras = requireMiddlewareExtras('autoHide');
|
|
5247
|
+
if (extras) {
|
|
5248
|
+
middleware.push(extras.hide({
|
|
5249
|
+
strategy: 'referenceHidden',
|
|
5250
|
+
}));
|
|
5251
|
+
}
|
|
5375
5252
|
}
|
|
5376
5253
|
computePosition(strategy.referenceElement, paneElement, {
|
|
5377
5254
|
placement: strategy.placement ?? 'bottom',
|
|
@@ -5412,20 +5289,29 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
5412
5289
|
cleanup();
|
|
5413
5290
|
};
|
|
5414
5291
|
};
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
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 });
|
|
5429
5315
|
};
|
|
5430
5316
|
|
|
5431
5317
|
const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
@@ -5445,13 +5331,13 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5445
5331
|
elements,
|
|
5446
5332
|
state: _state.asReadonly(),
|
|
5447
5333
|
componentInstance: _componentInstance.asReadonly(),
|
|
5448
|
-
// Internal
|
|
5334
|
+
// Internal - driven by overlay-runtime.ts
|
|
5449
5335
|
beforeOpenedSubject,
|
|
5450
5336
|
close(result, source = 'api') {
|
|
5451
5337
|
if (_state() === 'closing' || _state() === 'closed') {
|
|
5452
5338
|
return;
|
|
5453
5339
|
}
|
|
5454
|
-
// `reference-detached` is a forced teardown (the anchor is gone)
|
|
5340
|
+
// `reference-detached` is a forced teardown (the anchor is gone) - never vetoable.
|
|
5455
5341
|
if (source !== 'reference-detached') {
|
|
5456
5342
|
for (const guard of closeGuards) {
|
|
5457
5343
|
if (!guard({ result, source })) {
|
|
@@ -5461,7 +5347,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5461
5347
|
}
|
|
5462
5348
|
requestClose(result, source);
|
|
5463
5349
|
},
|
|
5464
|
-
/** Close bypassing every registered close guard
|
|
5350
|
+
/** Close bypassing every registered close guard - used by a guard's owner to commit a
|
|
5465
5351
|
* close it previously vetoed (e.g. after an async confirm resolved). */
|
|
5466
5352
|
forceClose(result, source = 'api') {
|
|
5467
5353
|
if (_state() === 'closing' || _state() === 'closed') {
|
|
@@ -5528,7 +5414,7 @@ const createOverlayRuntimeRef = (id, config, elements, requestClose) => {
|
|
|
5528
5414
|
};
|
|
5529
5415
|
};
|
|
5530
5416
|
|
|
5531
|
-
const
|
|
5417
|
+
const OVERLAY_RUNTIME_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
5532
5418
|
const appRef = inject(ApplicationRef);
|
|
5533
5419
|
const destroyRef = inject(DestroyRef);
|
|
5534
5420
|
const document = inject(DOCUMENT);
|
|
@@ -5635,7 +5521,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5635
5521
|
hostElement: paneElement,
|
|
5636
5522
|
bindings: config.bindings ?? [],
|
|
5637
5523
|
});
|
|
5638
|
-
// pane classes are applied after component creation
|
|
5524
|
+
// pane classes are applied after component creation - Angular replaces the host
|
|
5639
5525
|
// element's class attribute with the component's static host class on creation
|
|
5640
5526
|
renderer.addClass(paneElement, 'et-overlay-runtime-pane', ...(config.paneClass ?? []));
|
|
5641
5527
|
appRef.attachView(componentRef.hostView);
|
|
@@ -5682,7 +5568,7 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5682
5568
|
}
|
|
5683
5569
|
const lifecycle = getAnimatedLifecycle(componentRef);
|
|
5684
5570
|
animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state$.value ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
|
|
5685
|
-
// The reference element is gone
|
|
5571
|
+
// The reference element is gone - there is nothing left to animate away from, so tear the
|
|
5686
5572
|
// overlay down synchronously instead of playing a leave transition from a stale position.
|
|
5687
5573
|
if (!lifecycle || closeEvent.source === 'reference-detached') {
|
|
5688
5574
|
destroyMountedOverlay(closeEvent);
|
|
@@ -5791,6 +5677,8 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
5791
5677
|
openEntries,
|
|
5792
5678
|
};
|
|
5793
5679
|
}, { name: 'OverlayRuntime' });
|
|
5680
|
+
const provideOverlayRuntime = /* @__PURE__ */ toProvideFn(OVERLAY_RUNTIME_DEF);
|
|
5681
|
+
const injectOverlayRuntime = /* @__PURE__ */ toInjectFn(OVERLAY_RUNTIME_DEF);
|
|
5794
5682
|
|
|
5795
5683
|
class NormalizeGameResultTypePipe {
|
|
5796
5684
|
constructor() {
|
|
@@ -6831,7 +6719,7 @@ const toStringBinding = (binding) => {
|
|
|
6831
6719
|
return value !== null && value !== undefined ? String(value) : null;
|
|
6832
6720
|
});
|
|
6833
6721
|
};
|
|
6834
|
-
const createBulkPropertyBinding = (
|
|
6722
|
+
const createBulkPropertyBinding = (_propertyPrefix, applyConfigFn) => {
|
|
6835
6723
|
return (config) => {
|
|
6836
6724
|
Object.entries(config).forEach(([key, value]) => {
|
|
6837
6725
|
if (value !== undefined) {
|
|
@@ -6841,11 +6729,202 @@ const createBulkPropertyBinding = (propertyPrefix, applyConfigFn) => {
|
|
|
6841
6729
|
};
|
|
6842
6730
|
};
|
|
6843
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
|
+
|
|
6844
6923
|
var jsonLd = /*#__PURE__*/Object.freeze({
|
|
6845
6924
|
__proto__: null
|
|
6846
6925
|
});
|
|
6847
6926
|
|
|
6848
|
-
const DEFAULT_MULTI_INSTANCE_RELS = new Set([
|
|
6927
|
+
const DEFAULT_MULTI_INSTANCE_RELS = /* @__PURE__ */ new Set([
|
|
6849
6928
|
'alternate',
|
|
6850
6929
|
'icon',
|
|
6851
6930
|
'apple-touch-icon',
|
|
@@ -6858,10 +6937,12 @@ const DEFAULT_MULTI_INSTANCE_RELS = new Set([
|
|
|
6858
6937
|
'stylesheet',
|
|
6859
6938
|
'modulepreload',
|
|
6860
6939
|
]);
|
|
6861
|
-
const
|
|
6940
|
+
const LINK_STORE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
6862
6941
|
multiInstanceRels: DEFAULT_MULTI_INSTANCE_RELS,
|
|
6863
6942
|
}, { name: 'Link Store Config' });
|
|
6864
|
-
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(() => {
|
|
6865
6946
|
const document = inject(DOCUMENT$1);
|
|
6866
6947
|
const config = injectLinkStoreConfig();
|
|
6867
6948
|
const linksByKey = signal(new Map(), /* @ts-ignore */
|
|
@@ -6897,7 +6978,6 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
|
|
|
6897
6978
|
};
|
|
6898
6979
|
const createLinkElement = (linkConfig) => {
|
|
6899
6980
|
const link = document.createElement('link');
|
|
6900
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6901
6981
|
const { key, allowMultiple, ...attributes } = linkConfig;
|
|
6902
6982
|
Object.entries(attributes).forEach(([attrKey, value]) => {
|
|
6903
6983
|
if (value !== undefined && value !== null) {
|
|
@@ -6982,6 +7062,8 @@ const [provideLinkStore, injectLinkStore] = createRootProvider(() => {
|
|
|
6982
7062
|
};
|
|
6983
7063
|
return { addLink, removeLink, getLinkKey };
|
|
6984
7064
|
}, { name: 'Link Store' });
|
|
7065
|
+
const provideLinkStore = /* @__PURE__ */ toProvideFn(LINK_STORE_DEF);
|
|
7066
|
+
const injectLinkStore = /* @__PURE__ */ toInjectFn(LINK_STORE_DEF);
|
|
6985
7067
|
const applyLinkBinding = (binding) => {
|
|
6986
7068
|
const linkStore = injectLinkStore();
|
|
6987
7069
|
const linkId = Symbol('link-tag');
|
|
@@ -6991,7 +7073,7 @@ const applyLinkBinding = (binding) => {
|
|
|
6991
7073
|
linkStore.addLink(linkId, config);
|
|
6992
7074
|
}, () => linkStore.removeLink(linkId, currentKey));
|
|
6993
7075
|
};
|
|
6994
|
-
const applyCanonicalBinding = createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
|
|
7076
|
+
const applyCanonicalBinding = /* @__PURE__ */ createPropertyBinding((href) => ({ rel: 'canonical', href }), applyLinkBinding);
|
|
6995
7077
|
const applyAlternateBinding = (hreflang, binding) => {
|
|
6996
7078
|
applyLinkBinding(computed(() => {
|
|
6997
7079
|
const url = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
@@ -7003,8 +7085,8 @@ const applyAlternateLanguagesBindings = (config) => {
|
|
|
7003
7085
|
applyAlternateBinding(hreflang, binding);
|
|
7004
7086
|
});
|
|
7005
7087
|
};
|
|
7006
|
-
const applyPrevBinding = createPropertyBinding((href) => ({ rel: 'prev', href }), applyLinkBinding);
|
|
7007
|
-
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);
|
|
7008
7090
|
const applyResourceHintsBindings = (config) => {
|
|
7009
7091
|
if (config.preconnect) {
|
|
7010
7092
|
createArrayPropertyBinding((href, index) => ({ rel: 'preconnect', href, key: `preconnect:${index}` }), applyLinkBinding)(config.preconnect);
|
|
@@ -7020,20 +7102,26 @@ const applyResourceHintsBindings = (config) => {
|
|
|
7020
7102
|
}
|
|
7021
7103
|
};
|
|
7022
7104
|
|
|
7023
|
-
const
|
|
7105
|
+
const TITLE_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
7024
7106
|
divider: '|',
|
|
7025
7107
|
defaultTitle: '',
|
|
7026
7108
|
transformer: (title) => title,
|
|
7027
7109
|
}, { name: 'Title Config' });
|
|
7028
|
-
const
|
|
7110
|
+
const provideTitleConfig = /* @__PURE__ */ toProvideFn(TITLE_CONFIG_DEF);
|
|
7111
|
+
const injectTitleConfig = /* @__PURE__ */ toInjectFn(TITLE_CONFIG_DEF);
|
|
7112
|
+
const TITLE_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7029
7113
|
const config = injectTitleConfig();
|
|
7030
7114
|
const titleParts = signal(new Map(), /* @ts-ignore */
|
|
7031
7115
|
...(ngDevMode ? [{ debugName: "titleParts" }] : /* istanbul ignore next */ []));
|
|
7116
|
+
const titleMarkers = signal(new Map(), /* @ts-ignore */
|
|
7117
|
+
...(ngDevMode ? [{ debugName: "titleMarkers" }] : /* istanbul ignore next */ []));
|
|
7032
7118
|
const { currentLocale } = injectLocale();
|
|
7033
7119
|
const titleService = inject(Title);
|
|
7034
7120
|
const isRouterInitialized = injectIsRouterInitialized();
|
|
7035
|
-
|
|
7036
|
-
|
|
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(() => {
|
|
7037
7125
|
const partsMap = titleParts();
|
|
7038
7126
|
if (!partsMap.size || !isRouterInitialized()) {
|
|
7039
7127
|
return config.transformer(defaultTitle, currentLocale());
|
|
@@ -7057,6 +7145,20 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
|
|
|
7057
7145
|
}
|
|
7058
7146
|
return finalTextParts.join(` ${config.divider} `);
|
|
7059
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 */
|
|
7060
7162
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
7061
7163
|
const addPart = (id, part) => {
|
|
7062
7164
|
titleParts.update((parts) => new Map(parts).set(id, part));
|
|
@@ -7068,19 +7170,41 @@ const [provideTitleStore, injectTitleStore] = createRootProvider(() => {
|
|
|
7068
7170
|
return newParts;
|
|
7069
7171
|
});
|
|
7070
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
|
+
};
|
|
7071
7183
|
effect(() => {
|
|
7072
7184
|
const titleText = title();
|
|
7073
7185
|
titleService.setTitle(titleText);
|
|
7074
7186
|
});
|
|
7075
|
-
return { title, addPart, removePart };
|
|
7187
|
+
return { title, addPart, removePart, addMarker, removeMarker };
|
|
7076
7188
|
}, { name: 'Title Store' });
|
|
7189
|
+
const provideTitleStore = /* @__PURE__ */ toProvideFn(TITLE_STORE_DEF);
|
|
7190
|
+
const injectTitleStore = /* @__PURE__ */ toInjectFn(TITLE_STORE_DEF);
|
|
7077
7191
|
const applyHeadTitleBinding = (binding, options) => {
|
|
7078
7192
|
const titleStore = injectTitleStore();
|
|
7079
7193
|
const partId = Symbol('title-part');
|
|
7080
7194
|
applyHeadBinding(binding, (value) => titleStore.addPart(partId, { text: `${value}`, ...options }), () => titleStore.removePart(partId), (value) => value !== null && value !== undefined && value !== '');
|
|
7081
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
|
+
};
|
|
7082
7206
|
|
|
7083
|
-
const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
|
|
7207
|
+
const DEFAULT_MULTI_INSTANCE_TAGS = /* @__PURE__ */ new Set([
|
|
7084
7208
|
// Open Graph - Media
|
|
7085
7209
|
'property="og:image"',
|
|
7086
7210
|
'property="og:image:url"',
|
|
@@ -7149,11 +7273,13 @@ const DEFAULT_MULTI_INSTANCE_TAGS = new Set([
|
|
|
7149
7273
|
'name="p:domain_verify"', // Pinterest
|
|
7150
7274
|
'name="facebook-domain-verification"',
|
|
7151
7275
|
]);
|
|
7152
|
-
const
|
|
7276
|
+
const META_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
7153
7277
|
transformer: (content) => content,
|
|
7154
7278
|
multiInstanceTags: DEFAULT_MULTI_INSTANCE_TAGS,
|
|
7155
7279
|
}, { name: 'Meta Config' });
|
|
7156
|
-
const
|
|
7280
|
+
const provideMetaConfig = /* @__PURE__ */ toProvideFn(META_CONFIG_DEF);
|
|
7281
|
+
const injectMetaConfig = /* @__PURE__ */ toInjectFn(META_CONFIG_DEF);
|
|
7282
|
+
const META_STORE_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7157
7283
|
const metaService = inject(Meta);
|
|
7158
7284
|
const config = injectMetaConfig();
|
|
7159
7285
|
const { currentLocale } = injectLocale();
|
|
@@ -7179,7 +7305,6 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
|
|
|
7179
7305
|
if (transformedConfig.content) {
|
|
7180
7306
|
transformedConfig.content = config.transformer(transformedConfig.content, locale);
|
|
7181
7307
|
}
|
|
7182
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7183
7308
|
const { key, allowMultiple, ...metaConfig } = transformedConfig;
|
|
7184
7309
|
return metaService.addTag(metaConfig);
|
|
7185
7310
|
};
|
|
@@ -7266,6 +7391,8 @@ const [provideMetaStore, injectMetaStore] = createRootProvider(() => {
|
|
|
7266
7391
|
});
|
|
7267
7392
|
return { addTag, removeTag, getSelector };
|
|
7268
7393
|
}, { name: 'Meta Store' });
|
|
7394
|
+
const provideMetaStore = /* @__PURE__ */ toProvideFn(META_STORE_DEF);
|
|
7395
|
+
const injectMetaStore = /* @__PURE__ */ toInjectFn(META_STORE_DEF);
|
|
7269
7396
|
const applyMetaBinding = (binding) => {
|
|
7270
7397
|
const metaStore = injectMetaStore();
|
|
7271
7398
|
const tagId = Symbol('meta-tag');
|
|
@@ -7275,14 +7402,14 @@ const applyMetaBinding = (binding) => {
|
|
|
7275
7402
|
metaStore.addTag(tagId, config);
|
|
7276
7403
|
}, () => metaStore.removeTag(tagId, currentSelector));
|
|
7277
7404
|
};
|
|
7278
|
-
const applyDescriptionBinding = createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
|
|
7405
|
+
const applyDescriptionBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'description', content }), applyMetaBinding);
|
|
7279
7406
|
const applyKeywordsBinding = (binding) => {
|
|
7280
7407
|
applyMetaBinding(computed(() => {
|
|
7281
7408
|
const keywords = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
7282
7409
|
return keywords && keywords.length > 0 ? { name: 'keywords', content: keywords.join(', ') } : null;
|
|
7283
7410
|
}));
|
|
7284
7411
|
};
|
|
7285
|
-
const applyAuthorBinding = createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
|
|
7412
|
+
const applyAuthorBinding = /* @__PURE__ */ createPropertyBinding((content) => ({ name: 'author', content }), applyMetaBinding);
|
|
7286
7413
|
const applyRobotsBinding = (binding) => {
|
|
7287
7414
|
applyMetaBinding(computed(() => {
|
|
7288
7415
|
const config = untracked(() => (isSignal(binding) ? binding() : binding));
|
|
@@ -7595,10 +7722,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7595
7722
|
}]
|
|
7596
7723
|
}] });
|
|
7597
7724
|
|
|
7598
|
-
const
|
|
7725
|
+
const STRUCTURED_DATA_CONFIG_DEF = /* @__PURE__ */ defineStaticRootProvider({
|
|
7599
7726
|
placement: 'body',
|
|
7600
7727
|
}, { name: 'Structured Data Config' });
|
|
7601
|
-
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(() => {
|
|
7602
7731
|
const document = inject(DOCUMENT$1);
|
|
7603
7732
|
const renderer = injectRenderer();
|
|
7604
7733
|
const config = injectStructuredDataConfig();
|
|
@@ -7615,7 +7744,11 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
|
|
|
7615
7744
|
renderer.removeChild(oldScript.parentNode, oldScript);
|
|
7616
7745
|
}
|
|
7617
7746
|
const script = renderer.createElement('script');
|
|
7618
|
-
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));
|
|
7619
7752
|
const target = getTargetElement();
|
|
7620
7753
|
renderer.appendChild(target, script);
|
|
7621
7754
|
newScripts.set(id, script);
|
|
@@ -7635,6 +7768,8 @@ const [provideStructuredDataStore, injectStructuredDataStore] = createRootProvid
|
|
|
7635
7768
|
};
|
|
7636
7769
|
return { addStructuredData, removeStructuredData };
|
|
7637
7770
|
}, { name: 'Structured Data Store' });
|
|
7771
|
+
const provideStructuredDataStore = /* @__PURE__ */ toProvideFn(STRUCTURED_DATA_STORE_DEF);
|
|
7772
|
+
const injectStructuredDataStore = /* @__PURE__ */ toInjectFn(STRUCTURED_DATA_STORE_DEF);
|
|
7638
7773
|
const applyStructuredDataBinding = (binding) => {
|
|
7639
7774
|
const store = injectStructuredDataStore();
|
|
7640
7775
|
const scriptId = Symbol('structured-data');
|
|
@@ -7684,12 +7819,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7684
7819
|
|
|
7685
7820
|
/**
|
|
7686
7821
|
* Cascades hover/active-driven color DOWN to descendants explicitly marked with
|
|
7687
|
-
* `ColorInteractiveDirective`
|
|
7822
|
+
* `ColorInteractiveDirective` - this element does not color itself. Use it when a wrapper should
|
|
7688
7823
|
* expand a nested control's hit/visual area (e.g. clicking a checkbox's label should light up the
|
|
7689
|
-
* checkbox itself), never to give a wrapper its own interactive coloring
|
|
7824
|
+
* checkbox itself), never to give a wrapper its own interactive coloring - that's
|
|
7690
7825
|
* `ColorInteractiveDirective`.
|
|
7691
7826
|
*
|
|
7692
|
-
* Does nothing unless at least one descendant carries `ColorInteractiveDirective`
|
|
7827
|
+
* Does nothing unless at least one descendant carries `ColorInteractiveDirective` - check for
|
|
7693
7828
|
* that before adding this. It was mistakenly added to a component with no such descendant once
|
|
7694
7829
|
* already, where it did nothing but was confusing to read.
|
|
7695
7830
|
*/
|
|
@@ -7715,9 +7850,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7715
7850
|
* Marks a descendant as NOT counting toward an ancestor's `ColorInteractiveDirective`
|
|
7716
7851
|
* `:hover`/`:active` reaction (via the `:has()` guard in color-interactive-styles.component.css).
|
|
7717
7852
|
* Use it for content that's incidentally inside an interactive ancestor but isn't part of what
|
|
7718
|
-
* makes that ancestor interactive
|
|
7853
|
+
* makes that ancestor interactive - e.g. a hint/error block sitting inside a form field's frame.
|
|
7719
7854
|
*
|
|
7720
|
-
* 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
|
|
7721
7856
|
* has no effect on custom-property inheritance into descendants past the excluded element. If
|
|
7722
7857
|
* what you actually need is "stop my own cosmetic styling from reacting to a parent's unrelated
|
|
7723
7858
|
* interaction," this is the wrong tool; that's the class of bug `ColorInteractiveHasFocusDirective`
|
|
@@ -7739,7 +7874,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7739
7874
|
|
|
7740
7875
|
/**
|
|
7741
7876
|
* Tints `--et-theme-color-primary-*` etc. only when a descendant genuinely has `:focus-visible`
|
|
7742
|
-
* (`:has(:focus-visible)`)
|
|
7877
|
+
* (`:has(:focus-visible)`) - never on mouse `:hover`/`:active`. Use this for a wrapper (like a
|
|
7743
7878
|
* form field's frame) that should show the theme color while its control is focused, but must
|
|
7744
7879
|
* NOT react to incidental mouse interaction with unrelated nested widgets (a toolbar button, a
|
|
7745
7880
|
* rendered link) that happen to live inside the same wrapper.
|
|
@@ -7751,7 +7886,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7751
7886
|
* Deliberately does NOT also carry `ColorInteractiveDirective`'s `et-color-interactive` class:
|
|
7752
7887
|
* that class's plain `:hover`/`:active` rules locally override the same (inheriting)
|
|
7753
7888
|
* `--et-theme-color-primary-rgb`, so anything bubbling up from ANYWHERE in the subtree would
|
|
7754
|
-
* leak the transient hover/press color into every descendant reading it for unrelated cosmetics
|
|
7889
|
+
* leak the transient hover/press color into every descendant reading it for unrelated cosmetics -
|
|
7755
7890
|
* exactly the bug this directive's only consumer (form-field) shipped once, before the two
|
|
7756
7891
|
* behaviors were split apart. If you need the plain hover/active reaction too, add
|
|
7757
7892
|
* `ColorInteractiveDirective` explicitly and make sure you actually want both.
|
|
@@ -7777,13 +7912,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7777
7912
|
/**
|
|
7778
7913
|
* Makes an element react to ITS OWN `:hover`/`:active`/`:focus-visible`/`[disabled]` by tinting
|
|
7779
7914
|
* `--et-theme-color-primary-*`/`-on-primary-*`/`-ink-*` with the current color theme. Use this on
|
|
7780
|
-
* a single, self-contained interactive element
|
|
7915
|
+
* a single, self-contained interactive element - a button, a checkbox, a segmented button.
|
|
7781
7916
|
*
|
|
7782
7917
|
* Do NOT apply this to a wrapper that also contains OTHER, independently-interactive elements
|
|
7783
7918
|
* (e.g. a toolbar with its own buttons, or a form-field's control slot). These are ordinary
|
|
7784
7919
|
* inheriting CSS custom properties, so a descendant reading them for its own cosmetics (a caret
|
|
7785
7920
|
* color, a link color) gets swept along whenever ANYTHING inside the wrapper is hovered or
|
|
7786
|
-
* clicked
|
|
7921
|
+
* clicked - not just when the wrapper itself is the thing being interacted with. That exact bug
|
|
7787
7922
|
* shipped once already; see ColorInteractiveContainerDirective/ColorInteractiveHasFocusDirective
|
|
7788
7923
|
* for the shapes that avoid it.
|
|
7789
7924
|
*/
|
|
@@ -7811,19 +7946,23 @@ const SURFACE_TYPE = {
|
|
|
7811
7946
|
};
|
|
7812
7947
|
const createCssSurfaceName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
|
|
7813
7948
|
const resolveSurfaceByElevation = (themes, type, elevation) => themes.find((t) => t.type === type && t.elevation === elevation) ?? null;
|
|
7814
|
-
const
|
|
7949
|
+
const SURFACE_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
|
|
7815
7950
|
name: 'Surface Themes',
|
|
7816
7951
|
});
|
|
7817
|
-
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', {
|
|
7818
7955
|
name: 'Surface Themes Prefix',
|
|
7819
7956
|
});
|
|
7957
|
+
const ɵProvideSurfaceThemesPrefix = /* @__PURE__ */ toProvideFn(SURFACE_THEMES_PREFIX_DEF);
|
|
7958
|
+
const injectSurfaceThemesPrefix = /* @__PURE__ */ toInjectFn(SURFACE_THEMES_PREFIX_DEF);
|
|
7820
7959
|
const provideSurfaceThemesWithTailwind4 = (themes, prefix = 'et') => [
|
|
7821
7960
|
ɵProvideSurfaceThemes(themes),
|
|
7822
7961
|
ɵProvideSurfaceThemesPrefix(prefix),
|
|
7823
7962
|
];
|
|
7824
7963
|
|
|
7825
7964
|
const SURFACE_PROVIDER = new InjectionToken('SurfaceProvider');
|
|
7826
|
-
const FORCED_SURFACE_UNSET = Symbol('FORCED_SURFACE_UNSET');
|
|
7965
|
+
const FORCED_SURFACE_UNSET = /* @__PURE__ */ Symbol('FORCED_SURFACE_UNSET');
|
|
7827
7966
|
class ProvideSurfaceDirective {
|
|
7828
7967
|
constructor() {
|
|
7829
7968
|
this.themes = injectSurfaceThemes({ optional: true });
|
|
@@ -7922,7 +8061,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
7922
8061
|
}], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "etProvideSurface", required: false }] }] } });
|
|
7923
8062
|
|
|
7924
8063
|
let uniqueId = 0;
|
|
7925
|
-
const
|
|
8064
|
+
const SURFACE_CONTEXT_TRACKER_DEF = /* @__PURE__ */ defineRootProvider(() => {
|
|
7926
8065
|
const entries = signal([], /* @ts-ignore */
|
|
7927
8066
|
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
7928
8067
|
const topEntry = computed(() => {
|
|
@@ -7955,12 +8094,14 @@ const [provideSurfaceContextTracker, injectSurfaceContextTracker] = createRootPr
|
|
|
7955
8094
|
};
|
|
7956
8095
|
return { topType, topElevation, register, surfaceForElement };
|
|
7957
8096
|
}, { name: 'SurfaceContextTracker' });
|
|
8097
|
+
const provideSurfaceContextTracker = /* @__PURE__ */ toProvideFn(SURFACE_CONTEXT_TRACKER_DEF);
|
|
8098
|
+
const injectSurfaceContextTracker = /* @__PURE__ */ toInjectFn(SURFACE_CONTEXT_TRACKER_DEF);
|
|
7958
8099
|
|
|
7959
8100
|
class AutoSurfaceDirective {
|
|
7960
8101
|
/**
|
|
7961
8102
|
* Mark this auto-surface as its overlay's *own painted surface*. An overlay *panel*
|
|
7962
8103
|
* (menu, select/date/cascader panel, tooltip, toggletip, rich-text-editor popups) is
|
|
7963
|
-
* the overlay's surface, so it must paint the overlay's registered elevation exactly
|
|
8104
|
+
* the overlay's surface, so it must paint the overlay's registered elevation exactly -
|
|
7964
8105
|
* not stack a level above a parent surface. The elevation is read from the surface-context
|
|
7965
8106
|
* tracker (the pane it renders into), which is authoritative across the portal boundary;
|
|
7966
8107
|
* the panel's own declaration injector points back at the trigger location and cannot be
|
|
@@ -7988,7 +8129,7 @@ class AutoSurfaceDirective {
|
|
|
7988
8129
|
...(ngDevMode ? [{ debugName: "isOverlaySurface" }] : /* istanbul ignore next */ []));
|
|
7989
8130
|
// Bumped by the afterEveryRender watcher in the constructor whenever this element's overlay
|
|
7990
8131
|
// containment changes. surfaceForElement() below does a non-reactive element.contains() read,
|
|
7991
|
-
// so the computed must be re-triggered when this element is (re)grafted into an overlay pane
|
|
8132
|
+
// so the computed must be re-triggered when this element is (re)grafted into an overlay pane -
|
|
7992
8133
|
// otherwise a value cached from before the graft (one elevation too low) sticks forever.
|
|
7993
8134
|
this.domSettleTick = signal(0, /* @ts-ignore */
|
|
7994
8135
|
...(ngDevMode ? [{ debugName: "domSettleTick" }] : /* istanbul ignore next */ []));
|
|
@@ -8004,7 +8145,7 @@ class AutoSurfaceDirective {
|
|
|
8004
8145
|
const contextElevation = contextProvider?.elevation() ?? null;
|
|
8005
8146
|
const contextType = contextProvider?.surfaceType() ?? null;
|
|
8006
8147
|
// An overlay's projected/portaled content keeps the injector of where it was
|
|
8007
|
-
// *declared* (the trigger location), not the pane it renders into
|
|
8148
|
+
// *declared* (the trigger location), not the pane it renders into - so the
|
|
8008
8149
|
// injector-derived parent surface is one elevation too low. The surface-context
|
|
8009
8150
|
// tracker records each open overlay's surface *and* its pane element, so we consult
|
|
8010
8151
|
// only the overlay whose pane actually contains this element in the DOM (portaling
|
|
@@ -8015,7 +8156,7 @@ class AutoSurfaceDirective {
|
|
|
8015
8156
|
const overlayType = overlaySurface?.type ?? null;
|
|
8016
8157
|
// A panel that IS its overlay's own surface (see matchOverlaySurface) must paint exactly the
|
|
8017
8158
|
// overlay's elevation: the overlay container already resolved it (one above the trigger) and
|
|
8018
|
-
// registered it in the tracker. It must NOT re-derive from its declaration injector
|
|
8159
|
+
// registered it in the tracker. It must NOT re-derive from its declaration injector - that
|
|
8019
8160
|
// points back at the trigger location and disagrees with the pane whenever the trigger itself
|
|
8020
8161
|
// sits on a surface, which would double-elevate the panel relative to its own pane.
|
|
8021
8162
|
if (this.isOverlaySurface()) {
|
|
@@ -8048,7 +8189,7 @@ class AutoSurfaceDirective {
|
|
|
8048
8189
|
...(ngDevMode ? [{ debugName: "resolvedSurface" }] : /* istanbul ignore next */ []));
|
|
8049
8190
|
// Projected/portaled auto-surface content (e.g. a select option's avatar) can have its
|
|
8050
8191
|
// resolvedSurface computed run *before* it is grafted into the overlay pane it visually lives
|
|
8051
|
-
// in
|
|
8192
|
+
// in - or while it is briefly mounted in an off-pane measuring container, as windowed lists do.
|
|
8052
8193
|
// surfaceForElement()'s element.contains() check then returns the wrong (or no) overlay, so it
|
|
8053
8194
|
// resolves off the declaration injector (the trigger location) one elevation too low. That check
|
|
8054
8195
|
// is a plain DOM read, not a signal, so nothing re-runs the computed once the element reaches its
|
|
@@ -8065,14 +8206,13 @@ class AutoSurfaceDirective {
|
|
|
8065
8206
|
this.domSettleTick.update((v) => v + 1);
|
|
8066
8207
|
}
|
|
8067
8208
|
else if (el.isConnected && ++stableRenders >= 2) {
|
|
8068
|
-
// 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
|
|
8069
8210
|
settleWatcher.destroy();
|
|
8070
8211
|
}
|
|
8071
8212
|
});
|
|
8072
8213
|
effect(() => {
|
|
8073
8214
|
const surface = this.resolvedSurface();
|
|
8074
8215
|
untracked(() => {
|
|
8075
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8076
8216
|
setInputSignal(this.ownSurfaceProvider.surface, surface);
|
|
8077
8217
|
});
|
|
8078
8218
|
});
|
|
@@ -8089,12 +8229,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
8089
8229
|
}], ctorParameters: () => [], propDecorators: { surfaceProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceProvider", required: false }] }] } });
|
|
8090
8230
|
|
|
8091
8231
|
const createCssColorThemeName = (name) => name.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
|
|
8092
|
-
const
|
|
8232
|
+
const COLOR_THEMES_DEF = /* @__PURE__ */ defineStaticProvider(undefined, {
|
|
8093
8233
|
name: 'Color Themes',
|
|
8094
8234
|
});
|
|
8095
|
-
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', {
|
|
8096
8238
|
name: 'Themes Prefix',
|
|
8097
8239
|
});
|
|
8240
|
+
const ɵProvideColorThemesPrefix = /* @__PURE__ */ toProvideFn(COLOR_THEMES_PREFIX_DEF);
|
|
8241
|
+
const injectColorThemesPrefix = /* @__PURE__ */ toInjectFn(COLOR_THEMES_PREFIX_DEF);
|
|
8098
8242
|
const provideColorThemesWithTailwind4 = (themes, prefix = 'et') => [
|
|
8099
8243
|
ɵProvideColorThemes(themes),
|
|
8100
8244
|
ɵProvideColorThemesPrefix(prefix),
|
|
@@ -8313,7 +8457,7 @@ const COLOR_PROVIDER = new InjectionToken('ColorProvider');
|
|
|
8313
8457
|
* The color provider attached to a bootstrapped root component (e.g. `ProvideColorDirective` in
|
|
8314
8458
|
* the app component's `hostDirectives`), if any. Detached DOM such as overlay panes can't reach
|
|
8315
8459
|
* it through element DI, so overlay containers fall back to this when resolving the provider to
|
|
8316
|
-
* `syncWithProvider()`
|
|
8460
|
+
* `syncWithProvider()` - resolve at sync time, not at injection time, since root components only
|
|
8317
8461
|
* register with `ApplicationRef` once bootstrap completes.
|
|
8318
8462
|
*
|
|
8319
8463
|
* @internal
|
|
@@ -8327,7 +8471,7 @@ const resolveAppRootColorProvider = (appRef) => {
|
|
|
8327
8471
|
}
|
|
8328
8472
|
return null;
|
|
8329
8473
|
};
|
|
8330
|
-
const FORCED_COLOR_UNSET = Symbol('FORCED_COLOR_UNSET');
|
|
8474
|
+
const FORCED_COLOR_UNSET = /* @__PURE__ */ Symbol('FORCED_COLOR_UNSET');
|
|
8331
8475
|
class ProvideColorDirective {
|
|
8332
8476
|
constructor() {
|
|
8333
8477
|
this.themes = injectColorThemes({ optional: true });
|
|
@@ -8347,8 +8491,8 @@ class ProvideColorDirective {
|
|
|
8347
8491
|
}, /* @ts-ignore */
|
|
8348
8492
|
...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
|
|
8349
8493
|
/**
|
|
8350
|
-
* The color that actually applies at this provider's location: its own effective color, or
|
|
8351
|
-
* when this provider is passive (no input, nothing forced)
|
|
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.
|
|
8352
8496
|
* Mirrors the CSS cascade, which falls through a provider's `-color--inherited` scope class;
|
|
8353
8497
|
* consumers that re-apply context across detached DOM (overlay panes) must use this instead
|
|
8354
8498
|
* of `effectiveColor` or a passive in-between provider (e.g. a form field's) erases the theme.
|
|
@@ -8441,14 +8585,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
8441
8585
|
}] });
|
|
8442
8586
|
|
|
8443
8587
|
/**
|
|
8444
|
-
* Same shape as `ColorInteractiveDirective`
|
|
8445
|
-
* `: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
|
|
8446
8590
|
* of the ambient color theme. Use it for chrome-level interaction feedback that shouldn't borrow
|
|
8447
8591
|
* the page's brand color (a tab trigger's hover state, a toggle button that should look neutral
|
|
8448
|
-
* until it's actually active
|
|
8592
|
+
* until it's actually active - see `ButtonDirective`'s `mutedUntilPressed`).
|
|
8449
8593
|
*
|
|
8450
8594
|
* Has no container/exclude/has-focus variants: it has no descendant-cascading CSS at all, so
|
|
8451
|
-
* applying it to a wrapper with independently-interactive children is safe by construction
|
|
8595
|
+
* applying it to a wrapper with independently-interactive children is safe by construction -
|
|
8452
8596
|
* unlike `ColorInteractiveDirective`, there's no bubbling-leak failure mode to worry about here.
|
|
8453
8597
|
*/
|
|
8454
8598
|
class SurfaceInteractiveDirective {
|
|
@@ -8469,6 +8613,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
8469
8613
|
}]
|
|
8470
8614
|
}], ctorParameters: () => [] });
|
|
8471
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
|
+
|
|
8472
8691
|
const isAbstractControl = (value) => !!value &&
|
|
8473
8692
|
typeof value === 'object' &&
|
|
8474
8693
|
typeof value.getRawValue === 'function' &&
|
|
@@ -8484,13 +8703,13 @@ const normalizeUnsavedChangesSource = (source) => {
|
|
|
8484
8703
|
if (isFieldTree(source)) {
|
|
8485
8704
|
return {
|
|
8486
8705
|
value: computed(() => source().value()),
|
|
8487
|
-
// 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.
|
|
8488
8707
|
setValue: (next) => source().value.set(next),
|
|
8489
8708
|
};
|
|
8490
8709
|
}
|
|
8491
8710
|
if (isSignal(source)) {
|
|
8492
8711
|
const peek = untracked(source);
|
|
8493
|
-
// A signal that currently holds a FieldTree
|
|
8712
|
+
// A signal that currently holds a FieldTree - or nothing yet (the late/async form case).
|
|
8494
8713
|
if (peek === null || peek === undefined || isFieldTree(peek)) {
|
|
8495
8714
|
const fieldSignal = source;
|
|
8496
8715
|
return {
|
|
@@ -8515,6 +8734,179 @@ const normalizeUnsavedChangesSource = (source) => {
|
|
|
8515
8734
|
'an AbstractControl, or a WritableSignal.');
|
|
8516
8735
|
};
|
|
8517
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
|
+
|
|
8518
8910
|
const toBooleanPromise = (result) => {
|
|
8519
8911
|
if (result instanceof Promise) {
|
|
8520
8912
|
return result.then(Boolean);
|
|
@@ -8529,8 +8921,15 @@ const toBooleanPromise = (result) => {
|
|
|
8529
8921
|
* whether the watched form/value differs from it, and runs an async confirm before a discard.
|
|
8530
8922
|
* Call from an injection context (a field initializer or constructor).
|
|
8531
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
|
+
*
|
|
8532
8931
|
* Overlay and router flavors (`createOverlayUnsavedChangesGuard`, `createUnsavedChangesGuard`) build
|
|
8533
|
-
* on this
|
|
8932
|
+
* on this - use them when you want the guard wired to a close/navigation event automatically.
|
|
8534
8933
|
*/
|
|
8535
8934
|
const createUnsavedChangesTracker = (config) => {
|
|
8536
8935
|
assertInInjectionContext(createUnsavedChangesTracker);
|
|
@@ -8565,11 +8964,23 @@ const createUnsavedChangesTracker = (config) => {
|
|
|
8565
8964
|
return !(compareFn ? compareFn(current, defaultValue) : equal(current, defaultValue));
|
|
8566
8965
|
}, /* @ts-ignore */
|
|
8567
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);
|
|
8568
8979
|
const runCheck = () => {
|
|
8569
|
-
if (!untracked(hasChanges)) {
|
|
8980
|
+
if (untracked(_isAbandoned) || !untracked(hasChanges)) {
|
|
8570
8981
|
return Promise.resolve(true);
|
|
8571
8982
|
}
|
|
8572
|
-
return toBooleanPromise(confirm(untracked(normalized.value)));
|
|
8983
|
+
return coordinator.runCheck((context) => toBooleanPromise(confirm(untracked(normalized.value), context)));
|
|
8573
8984
|
};
|
|
8574
8985
|
const refreshDefaultValue = () => _defaultValue.set(untracked(normalized.value));
|
|
8575
8986
|
const restoreDefaultValue = () => {
|
|
@@ -8580,10 +8991,12 @@ const createUnsavedChangesTracker = (config) => {
|
|
|
8580
8991
|
};
|
|
8581
8992
|
return {
|
|
8582
8993
|
hasChanges,
|
|
8994
|
+
isAbandoned: _isAbandoned.asReadonly(),
|
|
8583
8995
|
defaultValue: _defaultValue.asReadonly(),
|
|
8584
8996
|
runCheck,
|
|
8585
8997
|
refreshDefaultValue,
|
|
8586
8998
|
restoreDefaultValue,
|
|
8999
|
+
tab,
|
|
8587
9000
|
};
|
|
8588
9001
|
};
|
|
8589
9002
|
|
|
@@ -8606,5 +9019,5 @@ const createUnsavedChangesGuard = (config) => {
|
|
|
8606
9019
|
* Generated bundle index. Do not edit.
|
|
8607
9020
|
*/
|
|
8608
9021
|
|
|
8609
|
-
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 };
|
|
8610
9023
|
//# sourceMappingURL=ethlete-core.mjs.map
|