ngx-lift 1.10.2 → 19.0.0
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/README.md +5 -1
- package/fesm2022/ngx-lift.mjs +738 -194
- package/fesm2022/ngx-lift.mjs.map +1 -1
- package/index.d.ts +1638 -3
- package/package.json +6 -6
- package/esm2022/lib/const.mjs +0 -10
- package/esm2022/lib/models/async-state.model.mjs +0 -2
- package/esm2022/lib/models/index.mjs +0 -6
- package/esm2022/lib/models/kubernetes-list.model.mjs +0 -2
- package/esm2022/lib/models/kubernetes-object-condition.model.mjs +0 -2
- package/esm2022/lib/models/kubernetes-object-meta.model.mjs +0 -2
- package/esm2022/lib/models/kubernetes-object.model.mjs +0 -2
- package/esm2022/lib/operators/combine-latest-eager.operator.mjs +0 -48
- package/esm2022/lib/operators/create-async-state.operator.mjs +0 -61
- package/esm2022/lib/operators/distinct-on-change.operator.mjs +0 -80
- package/esm2022/lib/operators/index.mjs +0 -9
- package/esm2022/lib/operators/kubernetes-pagination.operator.mjs +0 -71
- package/esm2022/lib/operators/logger.operator.mjs +0 -21
- package/esm2022/lib/operators/poll.operator.mjs +0 -39
- package/esm2022/lib/operators/start-with-tap.operator.mjs +0 -15
- package/esm2022/lib/operators/switch-map-with-async-state.operator.mjs +0 -33
- package/esm2022/lib/pipes/array-join.pipe.mjs +0 -21
- package/esm2022/lib/pipes/byte-converter.pipe.mjs +0 -150
- package/esm2022/lib/pipes/index.mjs +0 -6
- package/esm2022/lib/pipes/is-https.pipe.mjs +0 -18
- package/esm2022/lib/pipes/mask.pipe.mjs +0 -36
- package/esm2022/lib/pipes/range.pipe.mjs +0 -19
- package/esm2022/lib/signals/combine-from.mjs +0 -81
- package/esm2022/lib/signals/computed-async.mjs +0 -63
- package/esm2022/lib/signals/create-trigger.mjs +0 -11
- package/esm2022/lib/signals/index.mjs +0 -7
- package/esm2022/lib/signals/inject-params.mjs +0 -35
- package/esm2022/lib/signals/inject-query-params.mjs +0 -53
- package/esm2022/lib/signals/merge-from.mjs +0 -71
- package/esm2022/lib/utils/difference-in-days.util.mjs +0 -25
- package/esm2022/lib/utils/form.util.mjs +0 -56
- package/esm2022/lib/utils/idle-detection/idle-detection.config.mjs +0 -7
- package/esm2022/lib/utils/idle-detection/idle-detection.module.mjs +0 -25
- package/esm2022/lib/utils/idle-detection/idle-detection.service.mjs +0 -190
- package/esm2022/lib/utils/idle-detection/index.mjs +0 -4
- package/esm2022/lib/utils/index.mjs +0 -10
- package/esm2022/lib/utils/internal.util.mjs +0 -13
- package/esm2022/lib/utils/is-empty.util.mjs +0 -49
- package/esm2022/lib/utils/is-equal.util.mjs +0 -24
- package/esm2022/lib/utils/is-promise.util.mjs +0 -5
- package/esm2022/lib/utils/omit-by.util.mjs +0 -12
- package/esm2022/lib/utils/pick-by.util.mjs +0 -16
- package/esm2022/lib/utils/range.util.mjs +0 -28
- package/esm2022/lib/utils/url.util.mjs +0 -34
- package/esm2022/lib/validators/date-range.validator.mjs +0 -58
- package/esm2022/lib/validators/index.mjs +0 -5
- package/esm2022/lib/validators/intersection.validator.mjs +0 -34
- package/esm2022/lib/validators/unique.validator.mjs +0 -64
- package/esm2022/lib/validators/url.validator.mjs +0 -14
- package/esm2022/ngx-lift.mjs +0 -5
- package/esm2022/public-api.mjs +0 -10
- package/lib/const.d.ts +0 -5
- package/lib/models/async-state.model.d.ts +0 -22
- package/lib/models/index.d.ts +0 -5
- package/lib/models/kubernetes-list.model.d.ts +0 -10
- package/lib/models/kubernetes-object-condition.model.d.ts +0 -28
- package/lib/models/kubernetes-object-meta.model.d.ts +0 -38
- package/lib/models/kubernetes-object.model.d.ts +0 -8
- package/lib/operators/combine-latest-eager.operator.d.ts +0 -7
- package/lib/operators/create-async-state.operator.d.ts +0 -64
- package/lib/operators/distinct-on-change.operator.d.ts +0 -55
- package/lib/operators/index.d.ts +0 -8
- package/lib/operators/kubernetes-pagination.operator.d.ts +0 -30
- package/lib/operators/logger.operator.d.ts +0 -11
- package/lib/operators/poll.operator.d.ts +0 -39
- package/lib/operators/start-with-tap.operator.d.ts +0 -10
- package/lib/operators/switch-map-with-async-state.operator.d.ts +0 -31
- package/lib/pipes/array-join.pipe.d.ts +0 -7
- package/lib/pipes/byte-converter.pipe.d.ts +0 -40
- package/lib/pipes/index.d.ts +0 -5
- package/lib/pipes/is-https.pipe.d.ts +0 -7
- package/lib/pipes/mask.pipe.d.ts +0 -19
- package/lib/pipes/range.pipe.d.ts +0 -10
- package/lib/signals/combine-from.d.ts +0 -25
- package/lib/signals/computed-async.d.ts +0 -29
- package/lib/signals/create-trigger.d.ts +0 -4
- package/lib/signals/index.d.ts +0 -6
- package/lib/signals/inject-params.d.ts +0 -63
- package/lib/signals/inject-query-params.d.ts +0 -71
- package/lib/signals/merge-from.d.ts +0 -13
- package/lib/utils/difference-in-days.util.d.ts +0 -19
- package/lib/utils/form.util.d.ts +0 -18
- package/lib/utils/idle-detection/idle-detection.config.d.ts +0 -5
- package/lib/utils/idle-detection/idle-detection.module.d.ts +0 -13
- package/lib/utils/idle-detection/idle-detection.service.d.ts +0 -119
- package/lib/utils/idle-detection/index.d.ts +0 -3
- package/lib/utils/index.d.ts +0 -9
- package/lib/utils/internal.util.d.ts +0 -4
- package/lib/utils/is-empty.util.d.ts +0 -10
- package/lib/utils/is-equal.util.d.ts +0 -7
- package/lib/utils/is-promise.util.d.ts +0 -1
- package/lib/utils/omit-by.util.d.ts +0 -7
- package/lib/utils/pick-by.util.d.ts +0 -7
- package/lib/utils/range.util.d.ts +0 -12
- package/lib/utils/url.util.d.ts +0 -24
- package/lib/validators/date-range.validator.d.ts +0 -19
- package/lib/validators/index.d.ts +0 -4
- package/lib/validators/intersection.validator.d.ts +0 -9
- package/lib/validators/unique.validator.d.ts +0 -18
- package/lib/validators/url.validator.d.ts +0 -3
- package/public-api.d.ts +0 -6
package/fesm2022/ngx-lift.mjs
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
import { startWith, Subject, combineLatest, pipe, tap, map, catchError, of, Observable, expand, EMPTY, reduce, timer, isObservable, merge, exhaustMap, from, share, switchMap, fromEvent, throttleTime, identity, distinctUntilChanged, exhaustAll, concatAll, mergeAll, switchAll } from 'rxjs';
|
|
2
2
|
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Pipe, inject, LOCALE_ID, makeEnvironmentProviders, NgModule,
|
|
4
|
+
import { Pipe, inject, LOCALE_ID, makeEnvironmentProviders, NgModule, Injectable, assertInInjectionContext, isSignal, untracked, computed, DestroyRef, signal, effect } from '@angular/core';
|
|
5
5
|
import { Validators, FormArray } from '@angular/forms';
|
|
6
6
|
import { ActivatedRoute } from '@angular/router';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Combines multiple observables into a single observable emitting an array or dictionary
|
|
10
10
|
* of the latest values from each source observable.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* This operator is similar to RxJS `combineLatest`, but with additional behavior:
|
|
13
|
+
* - When `startWithNullForAll` is `false` (default), only `Subject` instances get `startWith(null)`
|
|
14
|
+
* - When `startWithNullForAll` is `true`, all observables get `startWith(null)`
|
|
15
|
+
*
|
|
16
|
+
* This ensures that `combineLatest` emits immediately with initial values, even if some
|
|
17
|
+
* observables haven't emitted yet.
|
|
13
18
|
*
|
|
14
19
|
* @template T - The type of the data in the observables.
|
|
15
20
|
*
|
|
16
|
-
* @param
|
|
17
|
-
*
|
|
21
|
+
* @param sources - An array of observables or a dictionary (object) of observables to be combined.
|
|
22
|
+
* @param startWithNullForAll - When `true`, all observables will start with `null`.
|
|
23
|
+
* When `false` (default), only `Subject` instances will start with `null`.
|
|
24
|
+
* @returns An observable emitting an array or dictionary of the latest values from each source observable.
|
|
25
|
+
* Values may be `null` initially if `startWithNullForAll` is `true` or for `Subject` instances.
|
|
18
26
|
*
|
|
19
|
-
* @
|
|
20
|
-
*
|
|
27
|
+
* @throws {Error} Throws an error if the provided argument is not an array of observables or a dictionary of observables.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Array of observables
|
|
32
|
+
* const combined$ = combineLatestEager([obs1$, obs2$, obs3$]);
|
|
21
33
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
34
|
+
* // Dictionary of observables
|
|
35
|
+
* const combined$ = combineLatestEager({
|
|
36
|
+
* users: users$,
|
|
37
|
+
* posts: posts$,
|
|
38
|
+
* comments: comments$
|
|
39
|
+
* });
|
|
24
40
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
41
|
+
* // Start all with null
|
|
42
|
+
* const combined$ = combineLatestEager([obs1$, obs2$], true);
|
|
43
|
+
* ```
|
|
27
44
|
*/
|
|
28
45
|
function combineLatestEager(sources, startWithNullForAll = false) {
|
|
29
46
|
function observableMapper(observable) {
|
|
@@ -199,17 +216,35 @@ function distinctOnChange(onChangeCallback, comparator = (prev, curr) => prev ==
|
|
|
199
216
|
* ******************************************************************
|
|
200
217
|
*/
|
|
201
218
|
/**
|
|
202
|
-
*
|
|
203
|
-
* until all pages have been retrieved, and aggregates the items from all pages
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* @
|
|
219
|
+
* Creates an RxJS operator that fetches paginated Kubernetes resources by continually making requests
|
|
220
|
+
* until all pages have been retrieved, and aggregates the items from all pages into a single KubernetesList.
|
|
221
|
+
*
|
|
222
|
+
* This operator uses Kubernetes' pagination mechanism with the `continue` token. It:
|
|
223
|
+
* - Starts with the initial request
|
|
224
|
+
* - Checks for a `continue` token in the response metadata
|
|
225
|
+
* - Makes subsequent requests with the `continue` token until no more pages exist
|
|
226
|
+
* - Aggregates all items from all pages into a single KubernetesList
|
|
227
|
+
*
|
|
228
|
+
* @template T - The type of Kubernetes objects in the list. Must extend `KubernetesObject`.
|
|
229
|
+
* @param http - The HttpClient instance used to make the HTTP requests.
|
|
230
|
+
* @param endpoint - The API endpoint to fetch the resources from.
|
|
231
|
+
* @param initialParams - Optional initial parameters to include in the request.
|
|
232
|
+
* Can include query parameters like filters and pagination settings.
|
|
233
|
+
* Note: `limit` and `continue` are Kubernetes-specific pagination parameters.
|
|
234
|
+
* @returns An RxJS operator function that transforms a source Observable into an Observable
|
|
235
|
+
* that emits a single KubernetesList containing all aggregated items from all pages.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* // Use as an operator
|
|
240
|
+
* this.http.get<KubernetesList<Pod>>('/api/v1/pods')
|
|
241
|
+
* .pipe(
|
|
242
|
+
* aggregatePaginatedKubernetesResources(this.http, '/api/v1/pods', { limit: 100 })
|
|
243
|
+
* )
|
|
244
|
+
* .subscribe(list => {
|
|
245
|
+
* console.log(`Total pods: ${list.items.length}`);
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
213
248
|
*/
|
|
214
249
|
function aggregatePaginatedKubernetesResources(http, endpoint, initialParams = {}) {
|
|
215
250
|
return (source$) => {
|
|
@@ -231,16 +266,33 @@ function aggregatePaginatedKubernetesResources(http, endpoint, initialParams = {
|
|
|
231
266
|
};
|
|
232
267
|
}
|
|
233
268
|
/**
|
|
234
|
-
* Fetches paginated Kubernetes resources by continually making requests
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* @
|
|
269
|
+
* Fetches paginated Kubernetes resources by continually making requests until all pages have been retrieved.
|
|
270
|
+
*
|
|
271
|
+
* This function is a convenience wrapper that combines the initial HTTP request with pagination handling.
|
|
272
|
+
* It automatically handles Kubernetes pagination using the `continue` token mechanism.
|
|
273
|
+
*
|
|
274
|
+
* Unlike `aggregatePaginatedKubernetesResources`, this function makes the initial request itself
|
|
275
|
+
* rather than being used as an operator on an existing Observable.
|
|
276
|
+
*
|
|
277
|
+
* @template T - The type of Kubernetes objects in the list. Must extend `KubernetesObject`.
|
|
278
|
+
* @param http - The HttpClient instance used to make the HTTP requests.
|
|
279
|
+
* @param endpoint - The API endpoint to fetch the resources from.
|
|
280
|
+
* @param initialParams - Optional initial parameters to include in the request.
|
|
281
|
+
* Can include query parameters like filters and pagination settings.
|
|
282
|
+
* Note: `limit` and `continue` are Kubernetes-specific pagination parameters.
|
|
283
|
+
* @returns An Observable that emits a single KubernetesList containing all aggregated items from all pages.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* // Fetch all pods across all pages
|
|
288
|
+
* fetchPaginatedKubernetesResources<Pod>(
|
|
289
|
+
* this.http,
|
|
290
|
+
* '/api/v1/pods',
|
|
291
|
+
* { limit: 100 }
|
|
292
|
+
* ).subscribe(list => {
|
|
293
|
+
* console.log(`Total pods: ${list.items.length}`);
|
|
294
|
+
* });
|
|
295
|
+
* ```
|
|
244
296
|
*/
|
|
245
297
|
function fetchPaginatedKubernetesResources(http, endpoint, initialParams = {}) {
|
|
246
298
|
const initialRequest$ = http.get(endpoint, { params: initialParams });
|
|
@@ -270,17 +322,50 @@ const loggerFunctions = {
|
|
|
270
322
|
table: console.table.bind(console),
|
|
271
323
|
};
|
|
272
324
|
/**
|
|
273
|
-
*
|
|
325
|
+
* RxJS operator that logs values emitted by an observable using various console methods.
|
|
326
|
+
* Useful for debugging and monitoring observable streams during development.
|
|
274
327
|
*
|
|
275
|
-
* @
|
|
276
|
-
*
|
|
328
|
+
* @template T - The type of values emitted by the observable.
|
|
329
|
+
* @param loggerType - The type of logger to be used. Options:
|
|
330
|
+
* - `'log'`: Standard console.log (default)
|
|
331
|
+
* - `'debug'`: Console.debug for debug messages
|
|
332
|
+
* - `'dir'`: Console.dir for object inspection
|
|
333
|
+
* - `'count'`: Console.count for counting emissions
|
|
334
|
+
* - `'table'`: Console.table for tabular data display
|
|
277
335
|
* @returns An RxJS operator function that logs values using the specified console function.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* // Log all values
|
|
340
|
+
* source$.pipe(logger()).subscribe();
|
|
341
|
+
*
|
|
342
|
+
* // Use debug logger
|
|
343
|
+
* source$.pipe(logger('debug')).subscribe();
|
|
344
|
+
*
|
|
345
|
+
* // Display objects in table format
|
|
346
|
+
* users$.pipe(logger('table')).subscribe();
|
|
347
|
+
* ```
|
|
278
348
|
*/
|
|
279
349
|
const logger = (loggerType = 'log') => pipe(tap((value) => {
|
|
280
350
|
const logFunction = loggerFunctions[loggerType] || console.log.bind(console);
|
|
281
351
|
logFunction(value);
|
|
282
352
|
}));
|
|
283
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Type guard that checks if a value is a Promise.
|
|
356
|
+
*
|
|
357
|
+
* @param obj - The value to check.
|
|
358
|
+
* @returns `true` if the value is a Promise (has a `then` method), `false` otherwise.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* const promise = Promise.resolve(42);
|
|
363
|
+
* isPromise(promise); // true
|
|
364
|
+
*
|
|
365
|
+
* const notPromise = 42;
|
|
366
|
+
* isPromise(notPromise); // false
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
284
369
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
370
|
function isPromise$1(obj) {
|
|
286
371
|
return !!obj && typeof obj.then === 'function';
|
|
@@ -323,12 +408,28 @@ function poll(options) {
|
|
|
323
408
|
}
|
|
324
409
|
|
|
325
410
|
/**
|
|
326
|
-
*
|
|
411
|
+
* RxJS operator that executes a callback function before the source Observable starts emitting values.
|
|
412
|
+
* This operator is useful for triggering side effects (like logging, state updates, or initialization)
|
|
413
|
+
* before the main Observable begins emitting.
|
|
327
414
|
*
|
|
328
|
-
*
|
|
415
|
+
* @template T - The type of values emitted by the observable.
|
|
416
|
+
* @param callback - A function to be executed synchronously before the source Observable emits its first value.
|
|
417
|
+
* @returns An RxJS operator function that executes the callback and then returns the source Observable unchanged.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* // Log before starting
|
|
422
|
+
* data$.pipe(
|
|
423
|
+
* startWithTap(() => console.log('Starting data fetch...')),
|
|
424
|
+
* switchMap(() => this.http.get('/api/data'))
|
|
425
|
+
* ).subscribe();
|
|
329
426
|
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
427
|
+
* // Update loading state
|
|
428
|
+
* data$.pipe(
|
|
429
|
+
* startWithTap(() => this.loading.set(true)),
|
|
430
|
+
* finalize(() => this.loading.set(false))
|
|
431
|
+
* ).subscribe();
|
|
432
|
+
* ```
|
|
332
433
|
*/
|
|
333
434
|
function startWithTap(callback) {
|
|
334
435
|
return (source) => {
|
|
@@ -368,7 +469,28 @@ function switchMapWithAsyncState(project) {
|
|
|
368
469
|
return (source) => source.pipe(switchMap((value, index) => project(value, index).pipe(createAsyncState())));
|
|
369
470
|
}
|
|
370
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Angular pipe that joins array elements into a string using a specified separator.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```html
|
|
477
|
+
* <!-- Join array with default comma separator -->
|
|
478
|
+
* <div>{{ ['apple', 'banana', 'cherry'] | arrayJoin }}</div>
|
|
479
|
+
* <!-- Output: "apple,banana,cherry" -->
|
|
480
|
+
*
|
|
481
|
+
* <!-- Join array with custom separator -->
|
|
482
|
+
* <div>{{ ['apple', 'banana', 'cherry'] | arrayJoin: ' - ' }}</div>
|
|
483
|
+
* <!-- Output: "apple - banana - cherry" -->
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
371
486
|
class ArrayJoinPipe {
|
|
487
|
+
/**
|
|
488
|
+
* Transforms an array into a string by joining its elements with a separator.
|
|
489
|
+
*
|
|
490
|
+
* @param value - The array to join. If not an array, returns the value as-is.
|
|
491
|
+
* @param separator - The separator string to use between array elements. Defaults to ','.
|
|
492
|
+
* @returns A string containing the joined array elements, or the original value if not an array
|
|
493
|
+
*/
|
|
372
494
|
transform(value, separator = ',') {
|
|
373
495
|
if (Array.isArray(value)) {
|
|
374
496
|
return value.join(separator);
|
|
@@ -376,50 +498,57 @@ class ArrayJoinPipe {
|
|
|
376
498
|
// For non-array cases or unexpected types, return the value as is
|
|
377
499
|
return value;
|
|
378
500
|
}
|
|
379
|
-
static
|
|
380
|
-
static
|
|
501
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ArrayJoinPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
502
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: ArrayJoinPipe, isStandalone: true, name: "arrayJoin" });
|
|
381
503
|
}
|
|
382
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
504
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ArrayJoinPipe, decorators: [{
|
|
383
505
|
type: Pipe,
|
|
384
506
|
args: [{
|
|
385
507
|
name: 'arrayJoin',
|
|
386
|
-
standalone: true,
|
|
387
508
|
}]
|
|
388
509
|
}] });
|
|
389
510
|
|
|
390
511
|
/**
|
|
512
|
+
* Angular pipe that converts a number of bytes into a human-readable string format
|
|
513
|
+
* (e.g., "1.5 MB", "2.3 GB") with locale-aware formatting.
|
|
514
|
+
*
|
|
515
|
+
* Supports multiple locales including English, French, Chinese, Japanese, and more.
|
|
516
|
+
* The pipe uses the application's LOCALE_ID to determine the appropriate unit translations.
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```html
|
|
520
|
+
* <!-- Convert bytes to human-readable format -->
|
|
521
|
+
* <div>{{ 1024 | byteConverter }}</div>
|
|
522
|
+
* <!-- Output: "1 KB" (English) or "1 Ko" (French) -->
|
|
523
|
+
*
|
|
524
|
+
* <div>{{ 1048576 | byteConverter }}</div>
|
|
525
|
+
* <!-- Output: "1 MB" (English) or "1 Mo" (French) -->
|
|
526
|
+
* ```
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* To use locale-specific formatting, configure LOCALE_ID in your app:
|
|
530
|
+
* ```typescript
|
|
391
531
|
* import { LOCALE_ID, NgModule } from '@angular/core';
|
|
392
|
-
* import { BrowserModule } from '@angular/platform-browser';
|
|
393
|
-
* import { AppComponent } from './app.component';
|
|
394
532
|
* import { registerLocaleData } from '@angular/common';
|
|
395
|
-
*
|
|
396
533
|
* import localeEn from '@angular/common/locales/en';
|
|
397
534
|
* import localeFr from '@angular/common/locales/fr';
|
|
398
535
|
*
|
|
399
|
-
* // Register locales
|
|
400
536
|
* registerLocaleData(localeEn);
|
|
401
537
|
* registerLocaleData(localeFr);
|
|
402
538
|
*
|
|
403
539
|
* @NgModule({
|
|
404
|
-
* declarations: [AppComponent],
|
|
405
|
-
* imports: [BrowserModule],
|
|
406
540
|
* providers: [
|
|
407
541
|
* {
|
|
408
542
|
* provide: LOCALE_ID,
|
|
409
|
-
* useFactory: () =>
|
|
410
|
-
* // Use the browser's language or a default language
|
|
411
|
-
* return navigator.language || 'en';
|
|
412
|
-
* },
|
|
543
|
+
* useFactory: () => navigator.language || 'en',
|
|
413
544
|
* },
|
|
414
545
|
* ],
|
|
415
|
-
* bootstrap: [AppComponent],
|
|
416
546
|
* })
|
|
417
547
|
* export class AppModule {}
|
|
548
|
+
* ```
|
|
418
549
|
*/
|
|
419
550
|
class ByteConverterPipe {
|
|
420
|
-
|
|
421
|
-
this.locale = inject(LOCALE_ID);
|
|
422
|
-
}
|
|
551
|
+
locale = inject(LOCALE_ID);
|
|
423
552
|
transform(value) {
|
|
424
553
|
if (value === null || value === undefined || isNaN(value)) {
|
|
425
554
|
return null;
|
|
@@ -435,16 +564,17 @@ class ByteConverterPipe {
|
|
|
435
564
|
return this.formatNumber(value) + ' ' + translationObject[key];
|
|
436
565
|
}
|
|
437
566
|
formatNumber(value) {
|
|
438
|
-
return new Intl.NumberFormat(this.locale, {
|
|
567
|
+
return new Intl.NumberFormat(this.locale, {
|
|
568
|
+
maximumFractionDigits: 2,
|
|
569
|
+
}).format(value);
|
|
439
570
|
}
|
|
440
|
-
static
|
|
441
|
-
static
|
|
571
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ByteConverterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
572
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: ByteConverterPipe, isStandalone: true, name: "byteConverter" });
|
|
442
573
|
}
|
|
443
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ByteConverterPipe, decorators: [{
|
|
444
575
|
type: Pipe,
|
|
445
576
|
args: [{
|
|
446
577
|
name: 'byteConverter',
|
|
447
|
-
standalone: true,
|
|
448
578
|
}]
|
|
449
579
|
}] });
|
|
450
580
|
const translations = {
|
|
@@ -614,8 +744,44 @@ function composeValidators(control, validatorFn) {
|
|
|
614
744
|
return Validators.compose(validatorFns)?.(control) || null;
|
|
615
745
|
}
|
|
616
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Configuration options for the idle detection service.
|
|
749
|
+
* Used to customize the idle duration and timeout duration for user inactivity detection.
|
|
750
|
+
*/
|
|
617
751
|
class IdleDetectionConfig {
|
|
752
|
+
/**
|
|
753
|
+
* The duration in seconds before the user is considered idle.
|
|
754
|
+
* After this duration, the idle detection phase ends and countdown begins.
|
|
755
|
+
* Defaults to 19 minutes (1140 seconds) if not provided.
|
|
756
|
+
*/
|
|
757
|
+
idleDurationInSeconds;
|
|
758
|
+
/**
|
|
759
|
+
* The duration in seconds for the countdown phase after idle detection.
|
|
760
|
+
* During this phase, the user can still interact to reset the timer.
|
|
761
|
+
* After this duration, the timeout event is emitted.
|
|
762
|
+
* Defaults to 1 minute (60 seconds) if not provided.
|
|
763
|
+
*/
|
|
764
|
+
timeoutDurationInSeconds;
|
|
618
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Provides configuration for the idle detection service.
|
|
768
|
+
* This function should be used in the application's providers array to configure idle detection.
|
|
769
|
+
*
|
|
770
|
+
* @param config - The idle detection configuration object.
|
|
771
|
+
* @returns Environment providers for the idle detection configuration.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```typescript
|
|
775
|
+
* export const appConfig: ApplicationConfig = {
|
|
776
|
+
* providers: [
|
|
777
|
+
* provideIdleDetectionConfig({
|
|
778
|
+
* idleDurationInSeconds: 15 * 60, // 15 minutes
|
|
779
|
+
* timeoutDurationInSeconds: 60 // 1 minute
|
|
780
|
+
* })
|
|
781
|
+
* ]
|
|
782
|
+
* };
|
|
783
|
+
* ```
|
|
784
|
+
*/
|
|
619
785
|
function provideIdleDetectionConfig(config) {
|
|
620
786
|
return makeEnvironmentProviders([{ provide: IdleDetectionConfig, useValue: config }]);
|
|
621
787
|
}
|
|
@@ -631,69 +797,77 @@ class IdleDetectionModule {
|
|
|
631
797
|
providers: [provideIdleDetectionConfig(config)],
|
|
632
798
|
};
|
|
633
799
|
}
|
|
634
|
-
static
|
|
635
|
-
static
|
|
636
|
-
static
|
|
800
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
801
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionModule });
|
|
802
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionModule });
|
|
637
803
|
}
|
|
638
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
804
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionModule, decorators: [{
|
|
639
805
|
type: NgModule,
|
|
640
|
-
args: [{
|
|
641
|
-
imports: [],
|
|
642
|
-
}]
|
|
806
|
+
args: [{}]
|
|
643
807
|
}] });
|
|
644
808
|
|
|
645
809
|
/**
|
|
646
810
|
* Service for detecting user idle time and implementing a countdown.
|
|
647
811
|
*/
|
|
648
812
|
class IdleDetectionService {
|
|
813
|
+
/**
|
|
814
|
+
* The list of interruption events that will end the idle detection.
|
|
815
|
+
*/
|
|
816
|
+
interruptionEvents = [
|
|
817
|
+
'click',
|
|
818
|
+
'keydown',
|
|
819
|
+
'keypress',
|
|
820
|
+
'mousemove',
|
|
821
|
+
'mousedown',
|
|
822
|
+
'scroll',
|
|
823
|
+
'wheel',
|
|
824
|
+
'touchmove',
|
|
825
|
+
'pointermove',
|
|
826
|
+
'resize',
|
|
827
|
+
];
|
|
828
|
+
interruptionSubscription;
|
|
829
|
+
/**
|
|
830
|
+
* The default idle duration in seconds (19 minutes).
|
|
831
|
+
*/
|
|
832
|
+
idleDuration = 19 * 60;
|
|
833
|
+
/**
|
|
834
|
+
* The default timeout duration in seconds (1 minute).
|
|
835
|
+
*/
|
|
836
|
+
timeoutDuration = 60;
|
|
837
|
+
/**
|
|
838
|
+
* Timer for idle detection.
|
|
839
|
+
*/
|
|
840
|
+
idleTimer;
|
|
841
|
+
/**
|
|
842
|
+
* Timer for countdown.
|
|
843
|
+
*/
|
|
844
|
+
countdownTimer;
|
|
845
|
+
/**
|
|
846
|
+
* Flag to indicate if countdown is in progress.
|
|
847
|
+
*/
|
|
848
|
+
isCountingDown = false;
|
|
849
|
+
/**
|
|
850
|
+
* The current countdown value.
|
|
851
|
+
*/
|
|
852
|
+
countdown = this.timeoutDuration;
|
|
853
|
+
/**
|
|
854
|
+
* Subject to emit when idle period ends.
|
|
855
|
+
*/
|
|
856
|
+
idleEndSubject = new Subject();
|
|
857
|
+
/**
|
|
858
|
+
* Subject to emit the countdown value.
|
|
859
|
+
*/
|
|
860
|
+
countdownSubject = new Subject();
|
|
861
|
+
/**
|
|
862
|
+
* Subject to emit when countdown ends.
|
|
863
|
+
*/
|
|
864
|
+
countdownEndSubject = new Subject();
|
|
649
865
|
/**
|
|
650
866
|
* Constructs the IdleDetectionService.
|
|
651
867
|
* @param config - Optional configuration for idle and timeout durations.
|
|
652
868
|
*/
|
|
653
|
-
constructor(
|
|
654
|
-
|
|
655
|
-
* The list of interruption events that will end the idle detection.
|
|
656
|
-
*/
|
|
657
|
-
this.interruptionEvents = [
|
|
658
|
-
'click',
|
|
659
|
-
'keydown',
|
|
660
|
-
'keypress',
|
|
661
|
-
'mousemove',
|
|
662
|
-
'mousedown',
|
|
663
|
-
'scroll',
|
|
664
|
-
'wheel',
|
|
665
|
-
'touchmove',
|
|
666
|
-
'pointermove',
|
|
667
|
-
'resize',
|
|
668
|
-
];
|
|
669
|
-
/**
|
|
670
|
-
* The default idle duration in seconds (19 minutes).
|
|
671
|
-
*/
|
|
672
|
-
this.idleDuration = 19 * 60;
|
|
673
|
-
/**
|
|
674
|
-
* The default timeout duration in seconds (1 minute).
|
|
675
|
-
*/
|
|
676
|
-
this.timeoutDuration = 60;
|
|
677
|
-
/**
|
|
678
|
-
* Flag to indicate if countdown is in progress.
|
|
679
|
-
*/
|
|
680
|
-
this.isCountingDown = false;
|
|
681
|
-
/**
|
|
682
|
-
* The current countdown value.
|
|
683
|
-
*/
|
|
684
|
-
this.countdown = this.timeoutDuration;
|
|
685
|
-
/**
|
|
686
|
-
* Subject to emit when idle period ends.
|
|
687
|
-
*/
|
|
688
|
-
this.idleEndSubject = new Subject();
|
|
689
|
-
/**
|
|
690
|
-
* Subject to emit the countdown value.
|
|
691
|
-
*/
|
|
692
|
-
this.countdownSubject = new Subject();
|
|
693
|
-
/**
|
|
694
|
-
* Subject to emit when countdown ends.
|
|
695
|
-
*/
|
|
696
|
-
this.countdownEndSubject = new Subject();
|
|
869
|
+
constructor() {
|
|
870
|
+
const config = inject(IdleDetectionConfig, { optional: true });
|
|
697
871
|
if (config) {
|
|
698
872
|
this.setConfig(config);
|
|
699
873
|
}
|
|
@@ -728,7 +902,7 @@ class IdleDetectionService {
|
|
|
728
902
|
setupInterruptionEvents() {
|
|
729
903
|
if (!this.interruptionSubscription) {
|
|
730
904
|
const throttledInterruptionEvents = this.interruptionEvents.map((eventName) => fromEvent(document, eventName).pipe(throttleTime(1000)));
|
|
731
|
-
this.interruptionSubscription = merge(...throttledInterruptionEvents).subscribe(() => this.resetTimer());
|
|
905
|
+
this.interruptionSubscription = merge(...throttledInterruptionEvents).subscribe(() => this.resetTimer(true));
|
|
732
906
|
}
|
|
733
907
|
}
|
|
734
908
|
/**
|
|
@@ -801,6 +975,8 @@ class IdleDetectionService {
|
|
|
801
975
|
clearTimers() {
|
|
802
976
|
clearTimeout(this.idleTimer);
|
|
803
977
|
clearInterval(this.countdownTimer);
|
|
978
|
+
this.idleTimer = undefined;
|
|
979
|
+
this.countdownTimer = undefined;
|
|
804
980
|
this.interruptionSubscription?.unsubscribe();
|
|
805
981
|
this.interruptionSubscription = undefined;
|
|
806
982
|
}
|
|
@@ -816,19 +992,33 @@ class IdleDetectionService {
|
|
|
816
992
|
this.countdown = this.timeoutDuration = config.timeoutDurationInSeconds;
|
|
817
993
|
}
|
|
818
994
|
}
|
|
819
|
-
static
|
|
820
|
-
static
|
|
995
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
996
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionService, providedIn: 'root' });
|
|
821
997
|
}
|
|
822
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
998
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IdleDetectionService, decorators: [{
|
|
823
999
|
type: Injectable,
|
|
824
1000
|
args: [{
|
|
825
1001
|
providedIn: 'root',
|
|
826
1002
|
}]
|
|
827
|
-
}], ctorParameters: () => [
|
|
828
|
-
type: Optional
|
|
829
|
-
}] }] });
|
|
1003
|
+
}], ctorParameters: () => [] });
|
|
830
1004
|
|
|
831
1005
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1006
|
+
/**
|
|
1007
|
+
* Type guard that checks if a value is array-like (has a numeric length property).
|
|
1008
|
+
* Array-like objects include arrays, strings, NodeLists, and similar structures.
|
|
1009
|
+
*
|
|
1010
|
+
* @param value - The value to check.
|
|
1011
|
+
* @returns `true` if the value is array-like, `false` otherwise.
|
|
1012
|
+
*
|
|
1013
|
+
* @example
|
|
1014
|
+
* ```typescript
|
|
1015
|
+
* isArrayLike([1, 2, 3]); // true
|
|
1016
|
+
* isArrayLike('string'); // true
|
|
1017
|
+
* isArrayLike({length: 5}); // true
|
|
1018
|
+
* isArrayLike(null); // false
|
|
1019
|
+
* isArrayLike(() => {}); // false
|
|
1020
|
+
* ```
|
|
1021
|
+
*/
|
|
832
1022
|
function isArrayLike(value) {
|
|
833
1023
|
return (value != null &&
|
|
834
1024
|
typeof value !== 'function' &&
|
|
@@ -836,6 +1026,20 @@ function isArrayLike(value) {
|
|
|
836
1026
|
value.length >= 0 &&
|
|
837
1027
|
value.length <= Number.MAX_SAFE_INTEGER);
|
|
838
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Checks if a value is a prototype object.
|
|
1031
|
+
* A prototype object is an object that is the prototype property of a constructor function.
|
|
1032
|
+
*
|
|
1033
|
+
* @param value - The value to check.
|
|
1034
|
+
* @returns `true` if the value is a prototype object, `false` otherwise.
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```typescript
|
|
1038
|
+
* isPrototype(Array.prototype); // true
|
|
1039
|
+
* isPrototype(Object.prototype); // true
|
|
1040
|
+
* isPrototype({}); // false
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
839
1043
|
function isPrototype(value) {
|
|
840
1044
|
const Ctor = value?.constructor;
|
|
841
1045
|
return Ctor && Ctor.prototype === value;
|
|
@@ -968,73 +1172,197 @@ function range(start, end, step, fromRight = false) {
|
|
|
968
1172
|
return result;
|
|
969
1173
|
}
|
|
970
1174
|
|
|
971
|
-
|
|
1175
|
+
/**
|
|
1176
|
+
* Regular expression pattern for validating email addresses.
|
|
1177
|
+
* Supports standard email formats including subdomains and various TLDs.
|
|
1178
|
+
*
|
|
1179
|
+
* @see {@link https://regex101.com/library/mX1xW0 | Regex Pattern Reference}
|
|
1180
|
+
*
|
|
1181
|
+
* @example
|
|
1182
|
+
* ```typescript
|
|
1183
|
+
* emailPattern.test('user@example.com'); // true
|
|
1184
|
+
* emailPattern.test('invalid-email'); // false
|
|
1185
|
+
* ```
|
|
1186
|
+
*/
|
|
972
1187
|
const emailPattern = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/;
|
|
1188
|
+
/**
|
|
1189
|
+
* Regular expression pattern for validating HTTP and HTTPS URLs.
|
|
1190
|
+
* Supports URLs with or without www subdomain, various protocols, and query parameters.
|
|
1191
|
+
*
|
|
1192
|
+
* @example
|
|
1193
|
+
* ```typescript
|
|
1194
|
+
* urlPattern.test('https://example.com'); // true
|
|
1195
|
+
* urlPattern.test('http://www.example.com/path?query=1'); // true
|
|
1196
|
+
* urlPattern.test('invalid-url'); // false
|
|
1197
|
+
* ```
|
|
1198
|
+
*/
|
|
973
1199
|
const urlPattern = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
|
|
1200
|
+
/**
|
|
1201
|
+
* Regular expression pattern for validating HTTPS URLs only.
|
|
1202
|
+
* Similar to `urlPattern` but requires the HTTPS protocol.
|
|
1203
|
+
*
|
|
1204
|
+
* @example
|
|
1205
|
+
* ```typescript
|
|
1206
|
+
* httpsPattern.test('https://example.com'); // true
|
|
1207
|
+
* httpsPattern.test('http://example.com'); // false
|
|
1208
|
+
* ```
|
|
1209
|
+
*/
|
|
974
1210
|
const httpsPattern = /^https:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
|
|
975
|
-
|
|
1211
|
+
/**
|
|
1212
|
+
* Regular expression pattern for validating IPv4 addresses.
|
|
1213
|
+
* Supports all valid IPv4 address ranges (0.0.0.0 to 255.255.255.255).
|
|
1214
|
+
*
|
|
1215
|
+
* @see {@link https://regex101.com/library/dT0vT3?orderBy=RELEVANCE&search=ip | Regex Pattern Reference}
|
|
1216
|
+
*
|
|
1217
|
+
* @example
|
|
1218
|
+
* ```typescript
|
|
1219
|
+
* ipRegex.test('192.168.1.1'); // true
|
|
1220
|
+
* ipRegex.test('256.1.1.1'); // false
|
|
1221
|
+
* ipRegex.test('not-an-ip'); // false
|
|
1222
|
+
* ```
|
|
1223
|
+
*/
|
|
976
1224
|
const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
977
|
-
|
|
978
|
-
|
|
1225
|
+
/**
|
|
1226
|
+
* Regular expression pattern for validating Fully Qualified Domain Names (FQDN).
|
|
1227
|
+
* A FQDN is a domain name that specifies its exact location in the tree hierarchy of the Domain Name System (DNS).
|
|
1228
|
+
* The pattern validates domain names between 4 and 253 characters with proper formatting.
|
|
1229
|
+
*
|
|
1230
|
+
* @see {@link https://www.regextester.com/103452 | Regex Pattern Reference}
|
|
1231
|
+
*
|
|
1232
|
+
* @example
|
|
1233
|
+
* ```typescript
|
|
1234
|
+
* fqdnRegex.test('example.com'); // true
|
|
1235
|
+
* fqdnRegex.test('subdomain.example.com'); // true
|
|
1236
|
+
* fqdnRegex.test('invalid..domain'); // false
|
|
1237
|
+
* ```
|
|
1238
|
+
*/
|
|
979
1239
|
const fqdnRegex = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/;
|
|
980
1240
|
|
|
981
1241
|
/**
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
* @
|
|
1242
|
+
* Checks if a hostname is a valid IP address (IPv4 or IPv6).
|
|
1243
|
+
*
|
|
1244
|
+
* @param hostname - The hostname to check. Should be extracted from `new URL(url).hostname`
|
|
1245
|
+
* @returns `true` if the hostname is a valid IP address, `false` otherwise
|
|
1246
|
+
*
|
|
1247
|
+
* @example
|
|
1248
|
+
* ```typescript
|
|
1249
|
+
* const url = new URL('http://192.168.1.1');
|
|
1250
|
+
* isIP(url.hostname); // true
|
|
1251
|
+
*
|
|
1252
|
+
* const url2 = new URL('http://example.com');
|
|
1253
|
+
* isIP(url2.hostname); // false
|
|
1254
|
+
* ```
|
|
985
1255
|
*/
|
|
986
1256
|
function isIP(hostname) {
|
|
987
1257
|
return ipRegex.test(hostname);
|
|
988
1258
|
}
|
|
989
1259
|
/**
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
* @
|
|
1260
|
+
* Checks if a hostname is a valid Fully Qualified Domain Name (FQDN).
|
|
1261
|
+
*
|
|
1262
|
+
* @param hostname - The hostname to check. Should be extracted from `new URL(url).hostname`
|
|
1263
|
+
* @returns `true` if the hostname is a valid FQDN, `false` otherwise
|
|
1264
|
+
*
|
|
1265
|
+
* @example
|
|
1266
|
+
* ```typescript
|
|
1267
|
+
* const url = new URL('http://example.com');
|
|
1268
|
+
* isFQDN(url.hostname); // true
|
|
1269
|
+
*
|
|
1270
|
+
* const url2 = new URL('http://192.168.1.1');
|
|
1271
|
+
* isFQDN(url2.hostname); // false
|
|
1272
|
+
* ```
|
|
993
1273
|
*/
|
|
994
1274
|
function isFQDN(hostname) {
|
|
995
1275
|
return fqdnRegex.test(hostname);
|
|
996
1276
|
}
|
|
997
1277
|
/**
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
* @
|
|
1278
|
+
* Checks if a string is a valid URL.
|
|
1279
|
+
*
|
|
1280
|
+
* @param url - The URL string to validate
|
|
1281
|
+
* @returns `true` if the string is a valid URL, `false` otherwise
|
|
1282
|
+
*
|
|
1283
|
+
* @example
|
|
1284
|
+
* ```typescript
|
|
1285
|
+
* isURL('https://example.com'); // true
|
|
1286
|
+
* isURL('not-a-url'); // false
|
|
1287
|
+
* ```
|
|
1001
1288
|
*/
|
|
1002
1289
|
function isURL(url) {
|
|
1003
1290
|
return urlPattern.test(url);
|
|
1004
1291
|
}
|
|
1005
1292
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1293
|
+
* Checks if a URL string uses the HTTPS protocol.
|
|
1294
|
+
* The URL must be a valid URL format.
|
|
1295
|
+
*
|
|
1296
|
+
* @param url - The URL string to check
|
|
1297
|
+
* @returns `true` if the URL is valid and uses HTTPS protocol, `false` otherwise
|
|
1298
|
+
*
|
|
1299
|
+
* @example
|
|
1300
|
+
* ```typescript
|
|
1301
|
+
* isHttps('https://example.com'); // true
|
|
1302
|
+
* isHttps('http://example.com'); // false
|
|
1303
|
+
* ```
|
|
1009
1304
|
*/
|
|
1010
1305
|
function isHttps(url) {
|
|
1011
1306
|
return httpsPattern.test(url);
|
|
1012
1307
|
}
|
|
1013
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* Angular pipe that checks if a URL string uses the HTTPS protocol.
|
|
1311
|
+
*
|
|
1312
|
+
* @example
|
|
1313
|
+
* ```html
|
|
1314
|
+
* <!-- Check if URL is HTTPS -->
|
|
1315
|
+
* <div>{{ 'https://example.com' | isHttps }}</div>
|
|
1316
|
+
* <!-- Output: true -->
|
|
1317
|
+
*
|
|
1318
|
+
* <div>{{ 'http://example.com' | isHttps }}</div>
|
|
1319
|
+
* <!-- Output: false -->
|
|
1320
|
+
* ```
|
|
1321
|
+
*/
|
|
1014
1322
|
class IsHttpsPipe {
|
|
1323
|
+
/**
|
|
1324
|
+
* Transforms a URL string into a boolean indicating whether it uses HTTPS.
|
|
1325
|
+
*
|
|
1326
|
+
* @param value - The URL string to check
|
|
1327
|
+
* @returns `true` if the URL uses HTTPS protocol, `false` otherwise
|
|
1328
|
+
*/
|
|
1015
1329
|
transform(value) {
|
|
1016
1330
|
return isHttps(value);
|
|
1017
1331
|
}
|
|
1018
|
-
static
|
|
1019
|
-
static
|
|
1332
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IsHttpsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1333
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: IsHttpsPipe, isStandalone: true, name: "isHttps" });
|
|
1020
1334
|
}
|
|
1021
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1335
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: IsHttpsPipe, decorators: [{
|
|
1022
1336
|
type: Pipe,
|
|
1023
1337
|
args: [{
|
|
1024
1338
|
name: 'isHttps',
|
|
1025
|
-
standalone: true,
|
|
1026
1339
|
}]
|
|
1027
1340
|
}] });
|
|
1028
1341
|
|
|
1029
1342
|
const unmaskNumber = 6;
|
|
1030
1343
|
const maskChar = '*';
|
|
1344
|
+
/**
|
|
1345
|
+
* Angular pipe that masks sensitive string data by replacing characters with asterisks,
|
|
1346
|
+
* while preserving a configurable number of characters at the beginning and end.
|
|
1347
|
+
*
|
|
1348
|
+
* @example
|
|
1349
|
+
* ```html
|
|
1350
|
+
* <!-- Mask a credit card number -->
|
|
1351
|
+
* <div>{{ '1234567890123456' | mask }}</div>
|
|
1352
|
+
* <!-- Output: "123456******3456" -->
|
|
1353
|
+
*
|
|
1354
|
+
* <!-- Custom masking options -->
|
|
1355
|
+
* <div>{{ '1234567890123456' | mask: { unmaskedPrefixLength: 4, unmaskedSuffixLength: 4 } }}</div>
|
|
1356
|
+
* <!-- Output: "1234********3456" -->
|
|
1357
|
+
* ```
|
|
1358
|
+
*/
|
|
1031
1359
|
class MaskPipe {
|
|
1032
1360
|
/**
|
|
1033
1361
|
* Transforms the input string by masking characters based on the provided options.
|
|
1034
1362
|
*
|
|
1035
|
-
* @param
|
|
1036
|
-
* @param
|
|
1037
|
-
* @returns
|
|
1363
|
+
* @param value - The input string to be masked
|
|
1364
|
+
* @param options - Options for customizing the masking behavior
|
|
1365
|
+
* @returns The masked string, or the original string if masking is disabled or the string is too short
|
|
1038
1366
|
*/
|
|
1039
1367
|
transform(value, options = {}) {
|
|
1040
1368
|
const { unmaskedPrefixLength = unmaskNumber, unmaskedSuffixLength = unmaskNumber, masked = true } = options;
|
|
@@ -1049,62 +1377,102 @@ class MaskPipe {
|
|
|
1049
1377
|
.map((char, i) => (i < unmaskedPrefixLength || i > value.length - unmaskedSuffixLength - 1 ? char : maskChar))
|
|
1050
1378
|
.join('');
|
|
1051
1379
|
}
|
|
1052
|
-
static
|
|
1053
|
-
static
|
|
1380
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MaskPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1381
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: MaskPipe, isStandalone: true, name: "mask" });
|
|
1054
1382
|
}
|
|
1055
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MaskPipe, decorators: [{
|
|
1056
1384
|
type: Pipe,
|
|
1057
1385
|
args: [{
|
|
1058
1386
|
name: 'mask',
|
|
1059
|
-
standalone: true,
|
|
1060
1387
|
}]
|
|
1061
1388
|
}] });
|
|
1062
1389
|
|
|
1390
|
+
/**
|
|
1391
|
+
* Angular pipe that generates an array of numbers within a specified range.
|
|
1392
|
+
*
|
|
1393
|
+
* @example
|
|
1394
|
+
* ```html
|
|
1395
|
+
* <!-- Generate array from 0 to 4 -->
|
|
1396
|
+
* <div *ngFor="let i of [5] | range">{{ i }}</div>
|
|
1397
|
+
*
|
|
1398
|
+
* <!-- Generate array from 1 to 5 -->
|
|
1399
|
+
* <div *ngFor="let i of [1, 5] | range">{{ i }}</div>
|
|
1400
|
+
*
|
|
1401
|
+
* <!-- Generate array from 0 to 10 with step 2 -->
|
|
1402
|
+
* <div *ngFor="let i of [0, 10, 2] | range">{{ i }}</div>
|
|
1403
|
+
* ```
|
|
1404
|
+
*/
|
|
1063
1405
|
class RangePipe {
|
|
1064
1406
|
transform(value) {
|
|
1065
1407
|
const input = value;
|
|
1066
1408
|
return range(...input);
|
|
1067
1409
|
}
|
|
1068
|
-
static
|
|
1069
|
-
static
|
|
1410
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RangePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1411
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: RangePipe, isStandalone: true, name: "range" });
|
|
1070
1412
|
}
|
|
1071
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1413
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RangePipe, decorators: [{
|
|
1072
1414
|
type: Pipe,
|
|
1073
1415
|
args: [{
|
|
1074
1416
|
name: 'range',
|
|
1075
|
-
standalone: true,
|
|
1076
1417
|
}]
|
|
1077
1418
|
}] });
|
|
1078
1419
|
|
|
1079
1420
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1080
1421
|
/**
|
|
1081
|
-
* Combines multiple `Observable` or `Signal` sources into a `Signal` that emits their values.
|
|
1422
|
+
* Combines multiple `Observable` or `Signal` sources into a `Signal` that emits their combined values.
|
|
1423
|
+
* This function is similar to RxJS `combineLatest`, but works with both Observables and Signals,
|
|
1424
|
+
* and returns a Signal instead of an Observable.
|
|
1082
1425
|
*
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1426
|
+
* The function supports:
|
|
1427
|
+
* - Array of sources: Returns a Signal of an array
|
|
1428
|
+
* - Object of sources: Returns a Signal of an object with the same keys
|
|
1429
|
+
* - Optional RxJS operator: Apply transformations to the combined values
|
|
1430
|
+
* - Optional initial value: Provide an initial value for the Signal
|
|
1431
|
+
*
|
|
1432
|
+
* @template Input - The type of the input sources (array or object).
|
|
1433
|
+
* @template Output - The type of the output Signal (defaults to Input).
|
|
1434
|
+
*
|
|
1435
|
+
* @param sources - Array or object of `Observable` or `Signal` values to combine.
|
|
1436
|
+
* @param operator - Optional RxJS operator function to transform the combined values.
|
|
1437
|
+
* @param options - Optional configuration object:
|
|
1438
|
+
* - `initialValue`: Initial value for the Signal (required if sources don't emit synchronously)
|
|
1439
|
+
* - `injector`: Angular injector to use for signal conversion
|
|
1440
|
+
* @returns A Signal that emits the combined values from all sources.
|
|
1087
1441
|
*
|
|
1088
1442
|
* @example
|
|
1089
|
-
* ```
|
|
1443
|
+
* ```typescript
|
|
1444
|
+
* // Array of sources
|
|
1090
1445
|
* export class Component {
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1446
|
+
* private readonly userService = inject(UserService);
|
|
1447
|
+
* page = signal(2);
|
|
1448
|
+
*
|
|
1449
|
+
* data = combineFrom(
|
|
1450
|
+
* [this.page, this.userService.users$],
|
|
1451
|
+
* pipe(
|
|
1452
|
+
* switchMap(([page, users]) => this.dataService.getData(page, users)),
|
|
1453
|
+
* startWith([])
|
|
1454
|
+
* )
|
|
1455
|
+
* );
|
|
1101
1456
|
* }
|
|
1457
|
+
*
|
|
1458
|
+
* // Object of sources
|
|
1459
|
+
* const vm = combineFrom({
|
|
1460
|
+
* users: users$,
|
|
1461
|
+
* filters: filtersSignal,
|
|
1462
|
+
* page: pageSignal
|
|
1463
|
+
* });
|
|
1464
|
+
*
|
|
1465
|
+
* // With initial value
|
|
1466
|
+
* const data = combineFrom(
|
|
1467
|
+
* [source1$, source2$],
|
|
1468
|
+
* { initialValue: [null, null] }
|
|
1469
|
+
* );
|
|
1102
1470
|
* ```
|
|
1103
1471
|
*/
|
|
1104
1472
|
function combineFrom(...args) {
|
|
1105
1473
|
assertInInjectionContext(combineFrom);
|
|
1106
1474
|
const { normalizedSources, hasInitValue, operator, options } = normalizeArgs(args);
|
|
1107
|
-
const ret = hasInitValue
|
|
1475
|
+
const ret = hasInitValue && options?.initialValue !== undefined
|
|
1108
1476
|
? toSignal(combineLatest(normalizedSources).pipe(operator), {
|
|
1109
1477
|
initialValue: options.initialValue,
|
|
1110
1478
|
injector: options?.injector,
|
|
@@ -1133,17 +1501,30 @@ function normalizeArgs(args) {
|
|
|
1133
1501
|
const hasInitValue = options?.initialValue !== undefined;
|
|
1134
1502
|
const normalizedSources = Object.entries(sources).reduce((acc, [keyOrIndex, source]) => {
|
|
1135
1503
|
if (isSignal(source)) {
|
|
1136
|
-
|
|
1504
|
+
// fix angular NG0950: Input is required but no value is available yet.
|
|
1505
|
+
// when input.required is used as combineFrom's input, its value is undefined, untracked(source) will throw error
|
|
1506
|
+
let initialValue;
|
|
1507
|
+
try {
|
|
1508
|
+
initialValue = untracked(source);
|
|
1509
|
+
}
|
|
1510
|
+
catch {
|
|
1511
|
+
// If the input is not set, skip startWith or provide a fallback
|
|
1512
|
+
initialValue = undefined;
|
|
1513
|
+
}
|
|
1137
1514
|
// toObservable doesn't immediately emit initialValue of the signal
|
|
1138
|
-
|
|
1515
|
+
acc[keyOrIndex] = toObservable(source, {
|
|
1516
|
+
injector: options?.injector,
|
|
1517
|
+
}).pipe(startWith(initialValue));
|
|
1139
1518
|
}
|
|
1140
1519
|
else if (isObservable(source)) {
|
|
1141
1520
|
acc[keyOrIndex] = source.pipe(distinctUntilChanged());
|
|
1142
1521
|
}
|
|
1143
1522
|
else if (typeof source === 'function') {
|
|
1144
1523
|
// seldom use: pass function like () => 5
|
|
1145
|
-
const computedRes = computed(source);
|
|
1146
|
-
acc[keyOrIndex] = toObservable(computedRes, {
|
|
1524
|
+
const computedRes = computed(source, ...(ngDevMode ? [{ debugName: "computedRes" }] : []));
|
|
1525
|
+
acc[keyOrIndex] = toObservable(computedRes, {
|
|
1526
|
+
injector: options?.injector,
|
|
1527
|
+
}).pipe(startWith(source()));
|
|
1147
1528
|
}
|
|
1148
1529
|
else {
|
|
1149
1530
|
// seldom use: pass promise, Map, array, etc that from accepts
|
|
@@ -1159,7 +1540,7 @@ function computedAsync(computeFn, options = {}) {
|
|
|
1159
1540
|
const destroyRef = inject(DestroyRef);
|
|
1160
1541
|
const sourceSubject = new Subject();
|
|
1161
1542
|
const source$ = flattenObservable(sourceSubject, options.behavior || 'switch');
|
|
1162
|
-
const sourceValue = signal(options.initialValue);
|
|
1543
|
+
const sourceValue = signal(options.initialValue, ...(ngDevMode ? [{ debugName: "sourceValue" }] : []));
|
|
1163
1544
|
const sourceResult = source$.subscribe({
|
|
1164
1545
|
next: (value) => sourceValue.set(value),
|
|
1165
1546
|
error: (error) => {
|
|
@@ -1215,12 +1596,43 @@ function isPromise(value) {
|
|
|
1215
1596
|
return value && typeof value.then === 'function';
|
|
1216
1597
|
}
|
|
1217
1598
|
|
|
1599
|
+
/**
|
|
1600
|
+
* Creates a trigger signal that can be used to manually trigger updates or side effects.
|
|
1601
|
+
* The trigger maintains an internal counter that increments each time `next()` is called.
|
|
1602
|
+
*
|
|
1603
|
+
* @returns An object containing:
|
|
1604
|
+
* - `next()`: A function to trigger an update (increments the internal counter)
|
|
1605
|
+
* - `value`: A readonly signal that emits the current counter value
|
|
1606
|
+
*
|
|
1607
|
+
* @example
|
|
1608
|
+
* ```typescript
|
|
1609
|
+
* export class MyComponent {
|
|
1610
|
+
* private refreshTrigger = createTrigger();
|
|
1611
|
+
*
|
|
1612
|
+
* // Use the trigger to refresh data
|
|
1613
|
+
* refreshData() {
|
|
1614
|
+
* this.refreshTrigger.next();
|
|
1615
|
+
* }
|
|
1616
|
+
*
|
|
1617
|
+
* // React to trigger changes
|
|
1618
|
+
* data$ = toObservable(this.refreshTrigger.value).pipe(
|
|
1619
|
+
* switchMap(() => this.dataService.getData())
|
|
1620
|
+
* );
|
|
1621
|
+
* }
|
|
1622
|
+
* ```
|
|
1623
|
+
*/
|
|
1218
1624
|
function createTrigger() {
|
|
1219
|
-
const sourceSignal = signal(0);
|
|
1625
|
+
const sourceSignal = signal(0, ...(ngDevMode ? [{ debugName: "sourceSignal" }] : []));
|
|
1220
1626
|
return {
|
|
1627
|
+
/**
|
|
1628
|
+
* Triggers an update by incrementing the internal counter.
|
|
1629
|
+
*/
|
|
1221
1630
|
next: () => {
|
|
1222
1631
|
sourceSignal.update((v) => v + 1);
|
|
1223
1632
|
},
|
|
1633
|
+
/**
|
|
1634
|
+
* A readonly signal that emits the current counter value.
|
|
1635
|
+
*/
|
|
1224
1636
|
value: sourceSignal.asReadonly(),
|
|
1225
1637
|
};
|
|
1226
1638
|
}
|
|
@@ -1307,26 +1719,46 @@ function injectQueryParams(keyOrParamsTransform, options = {}) {
|
|
|
1307
1719
|
|
|
1308
1720
|
// No object inputs
|
|
1309
1721
|
/**
|
|
1310
|
-
*
|
|
1722
|
+
* Merges multiple `Observable` or `Signal` sources into a `Signal` that emits values from any source.
|
|
1723
|
+
* This function is similar to RxJS `merge`, but works with both Observables and Signals,
|
|
1724
|
+
* and returns a Signal instead of an Observable.
|
|
1725
|
+
*
|
|
1726
|
+
* When any source emits a value, the Signal will emit that value. This is useful for
|
|
1727
|
+
* combining multiple event streams or reactive sources.
|
|
1311
1728
|
*
|
|
1312
|
-
* @
|
|
1313
|
-
* @
|
|
1314
|
-
*
|
|
1315
|
-
* @
|
|
1729
|
+
* @template Input - The type of values in the input sources array.
|
|
1730
|
+
* @template Output - The type of the output Signal (defaults to Input[number]).
|
|
1731
|
+
*
|
|
1732
|
+
* @param sources - Array of `Observable` or `Signal` values to merge.
|
|
1733
|
+
* @param operator - Optional RxJS operator function to transform the merged values.
|
|
1734
|
+
* @param options - Optional configuration object:
|
|
1735
|
+
* - `initialValue`: Initial value for the Signal
|
|
1736
|
+
* - `injector`: Angular injector to use for signal conversion
|
|
1737
|
+
* @returns A Signal that emits values from any of the merged sources.
|
|
1316
1738
|
*
|
|
1317
1739
|
* @example
|
|
1318
|
-
* ```
|
|
1740
|
+
* ```typescript
|
|
1319
1741
|
* export class Component {
|
|
1320
1742
|
* e$ = of(1).pipe(delay(1000));
|
|
1321
1743
|
* f = signal(2);
|
|
1322
1744
|
*
|
|
1745
|
+
* // Merge with operator
|
|
1323
1746
|
* data = mergeFrom(
|
|
1324
1747
|
* [this.e$, this.f],
|
|
1325
1748
|
* pipe(
|
|
1326
|
-
*
|
|
1749
|
+
* map((res) => `${res} is coming~`),
|
|
1327
1750
|
* startWith(0),
|
|
1328
1751
|
* ),
|
|
1329
1752
|
* );
|
|
1753
|
+
*
|
|
1754
|
+
* // Simple merge
|
|
1755
|
+
* merged = mergeFrom([source1$, source2$, sourceSignal]);
|
|
1756
|
+
*
|
|
1757
|
+
* // With initial value
|
|
1758
|
+
* merged = mergeFrom(
|
|
1759
|
+
* [source1$, source2$],
|
|
1760
|
+
* { initialValue: null }
|
|
1761
|
+
* );
|
|
1330
1762
|
* }
|
|
1331
1763
|
* ```
|
|
1332
1764
|
*/
|
|
@@ -1374,10 +1806,45 @@ function parseArgs(args) {
|
|
|
1374
1806
|
}
|
|
1375
1807
|
|
|
1376
1808
|
/**
|
|
1377
|
-
*
|
|
1809
|
+
* Creates a validator function that validates a date against a specified date range.
|
|
1810
|
+
*
|
|
1811
|
+
* The validator checks if the form control's date value falls within the specified range.
|
|
1812
|
+
* It supports:
|
|
1813
|
+
* - Minimum and maximum date constraints
|
|
1814
|
+
* - Inclusive or exclusive boundary comparisons
|
|
1815
|
+
* - Time-aware or date-only comparisons
|
|
1816
|
+
*
|
|
1817
|
+
* @param options - The options for the date range validation.
|
|
1818
|
+
* @returns A validator function that validates a FormControl and returns an error object if the date is out of range,
|
|
1819
|
+
* or `null` if the date is valid. Error objects contain:
|
|
1820
|
+
* - `minDate`: ISO string of the minimum date (if value is too early)
|
|
1821
|
+
* - `maxDate`: ISO string of the maximum date (if value is too late)
|
|
1822
|
+
* - `invalidDate`: `true` (if the value cannot be parsed as a date)
|
|
1378
1823
|
*
|
|
1379
|
-
* @
|
|
1380
|
-
*
|
|
1824
|
+
* @example
|
|
1825
|
+
* ```typescript
|
|
1826
|
+
* // Date range with inclusive boundaries
|
|
1827
|
+
* const form = new FormGroup({
|
|
1828
|
+
* startDate: new FormControl('', [
|
|
1829
|
+
* dateRangeValidator({
|
|
1830
|
+
* minDate: new Date('2024-01-01'),
|
|
1831
|
+
* maxDate: new Date('2024-12-31'),
|
|
1832
|
+
* minInclusive: true,
|
|
1833
|
+
* maxInclusive: true
|
|
1834
|
+
* })
|
|
1835
|
+
* ])
|
|
1836
|
+
* });
|
|
1837
|
+
*
|
|
1838
|
+
* // Date-only comparison (ignores time)
|
|
1839
|
+
* const form = new FormGroup({
|
|
1840
|
+
* appointment: new FormControl('', [
|
|
1841
|
+
* dateRangeValidator({
|
|
1842
|
+
* minDate: '2024-01-01',
|
|
1843
|
+
* compareTime: false
|
|
1844
|
+
* })
|
|
1845
|
+
* ])
|
|
1846
|
+
* });
|
|
1847
|
+
* ```
|
|
1381
1848
|
*/
|
|
1382
1849
|
function dateRangeValidator(options) {
|
|
1383
1850
|
return (control) => {
|
|
@@ -1431,11 +1898,30 @@ function dateRangeValidator(options) {
|
|
|
1431
1898
|
}
|
|
1432
1899
|
|
|
1433
1900
|
/**
|
|
1434
|
-
*
|
|
1901
|
+
* Creates a validator function that checks for intersection between two form controls within a FormGroup.
|
|
1902
|
+
* Both controls' values must be arrays. The validator sets an error on both controls if they have any common values.
|
|
1903
|
+
*
|
|
1904
|
+
* This is useful for scenarios where you need to ensure two arrays don't share any elements,
|
|
1905
|
+
* such as preventing duplicate selections in multi-select scenarios.
|
|
1906
|
+
*
|
|
1907
|
+
* @template T - The type of elements in the arrays (defaults to `string`).
|
|
1908
|
+
* @param controlName1 - The name of the first form control in the FormGroup.
|
|
1909
|
+
* @param controlName2 - The name of the second form control in the FormGroup.
|
|
1910
|
+
* @returns A validator function that validates the FormGroup and returns an error object with `intersection: true`
|
|
1911
|
+
* if there is an intersection between the two arrays, or `null` if there is no intersection.
|
|
1912
|
+
*
|
|
1913
|
+
* @example
|
|
1914
|
+
* ```typescript
|
|
1915
|
+
* const form = new FormGroup({
|
|
1916
|
+
* selectedUsers: new FormControl(['user1', 'user2']),
|
|
1917
|
+
* excludedUsers: new FormControl(['user3', 'user4']),
|
|
1918
|
+
* }, {
|
|
1919
|
+
* validators: [intersectionValidator('selectedUsers', 'excludedUsers')]
|
|
1920
|
+
* });
|
|
1435
1921
|
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1922
|
+
* // If selectedUsers contains 'user1' and excludedUsers also contains 'user1',
|
|
1923
|
+
* // both controls will have an error: { intersection: true }
|
|
1924
|
+
* ```
|
|
1439
1925
|
*/
|
|
1440
1926
|
function intersectionValidator(controlName1, controlName2) {
|
|
1441
1927
|
return (formGroup) => {
|
|
@@ -1469,15 +1955,47 @@ function intersectionValidator(controlName1, controlName2) {
|
|
|
1469
1955
|
*
|
|
1470
1956
|
* This validator can be applied to a FormArray or FormGroup containing the controls to be validated.
|
|
1471
1957
|
* It ensures that each control's value is unique among all other controls within the array or group.
|
|
1958
|
+
*
|
|
1959
|
+
* When duplicate values are found, the validator sets a `notUnique` error on all affected controls.
|
|
1960
|
+
* The error is automatically removed when the value becomes unique again.
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* ```typescript
|
|
1964
|
+
* // FormArray with unique values
|
|
1965
|
+
* const formArray = new FormArray([
|
|
1966
|
+
* new FormControl('value1'),
|
|
1967
|
+
* new FormControl('value2'),
|
|
1968
|
+
* new FormControl('value1') // This will have notUnique error
|
|
1969
|
+
* ], [UniqueValidator.unique()]);
|
|
1970
|
+
*
|
|
1971
|
+
* // FormArray with custom key selector
|
|
1972
|
+
* const formArray = new FormArray([
|
|
1973
|
+
* new FormGroup({
|
|
1974
|
+
* id: new FormControl(1),
|
|
1975
|
+
* name: new FormControl('Item 1')
|
|
1976
|
+
* }),
|
|
1977
|
+
* new FormGroup({
|
|
1978
|
+
* id: new FormControl(2),
|
|
1979
|
+
* name: new FormControl('Item 2')
|
|
1980
|
+
* })
|
|
1981
|
+
* ], [UniqueValidator.unique(control => control.get('id'))]);
|
|
1982
|
+
* ```
|
|
1472
1983
|
*/
|
|
1473
1984
|
class UniqueValidator {
|
|
1474
1985
|
/**
|
|
1475
|
-
*
|
|
1986
|
+
* Creates a validator function that checks for uniqueness of values across controls in a FormArray or FormGroup.
|
|
1476
1987
|
*
|
|
1477
|
-
*
|
|
1988
|
+
* The validator:
|
|
1989
|
+
* - Compares values using the provided key selector function
|
|
1990
|
+
* - Sets `notUnique` error on controls with duplicate values
|
|
1991
|
+
* - Automatically removes errors when values become unique
|
|
1992
|
+
* - Ignores null, undefined, empty strings, and 'NaN' values
|
|
1478
1993
|
*
|
|
1479
|
-
* @
|
|
1480
|
-
* @
|
|
1994
|
+
* @template T - The type of the control value.
|
|
1995
|
+
* @param keySelector - A function to select the key control for comparison.
|
|
1996
|
+
* Defaults to the control itself if not provided.
|
|
1997
|
+
* This is useful when validating FormGroups where you want to check uniqueness of a specific field.
|
|
1998
|
+
* @returns A validator function that can be attached to a FormArray or FormGroup.
|
|
1481
1999
|
*/
|
|
1482
2000
|
static unique(keySelector = (control) => control) {
|
|
1483
2001
|
return (formArray) => {
|
|
@@ -1527,14 +2045,40 @@ class UniqueValidator {
|
|
|
1527
2045
|
}
|
|
1528
2046
|
}
|
|
1529
2047
|
|
|
2048
|
+
/**
|
|
2049
|
+
* Validator function that checks if a form control value is a valid URL.
|
|
2050
|
+
*
|
|
2051
|
+
* @param control - The form control to validate
|
|
2052
|
+
* @returns `null` if the value is a valid URL, or an error object with `invalidUrl: true` if invalid
|
|
2053
|
+
*
|
|
2054
|
+
* @example
|
|
2055
|
+
* ```typescript
|
|
2056
|
+
* const form = new FormGroup({
|
|
2057
|
+
* website: new FormControl('', urlValidator)
|
|
2058
|
+
* });
|
|
2059
|
+
* ```
|
|
2060
|
+
*/
|
|
1530
2061
|
function urlValidator(control) {
|
|
1531
|
-
if (!urlPattern.test(control.value)) {
|
|
2062
|
+
if (!control.value || !urlPattern.test(control.value)) {
|
|
1532
2063
|
return { invalidUrl: true };
|
|
1533
2064
|
}
|
|
1534
2065
|
return null;
|
|
1535
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Validator function that checks if a form control value is a valid HTTPS URL.
|
|
2069
|
+
*
|
|
2070
|
+
* @param control - The form control to validate
|
|
2071
|
+
* @returns `null` if the value is a valid HTTPS URL, or an error object with `invalidUrl: true` if invalid
|
|
2072
|
+
*
|
|
2073
|
+
* @example
|
|
2074
|
+
* ```typescript
|
|
2075
|
+
* const form = new FormGroup({
|
|
2076
|
+
* secureUrl: new FormControl('', httpsValidator)
|
|
2077
|
+
* });
|
|
2078
|
+
* ```
|
|
2079
|
+
*/
|
|
1536
2080
|
function httpsValidator(control) {
|
|
1537
|
-
if (!httpsPattern.test(control.value)) {
|
|
2081
|
+
if (!control.value || !httpsPattern.test(control.value)) {
|
|
1538
2082
|
return { invalidUrl: true };
|
|
1539
2083
|
}
|
|
1540
2084
|
return null;
|