@dereekb/dbx-core 14.9.0 → 14.10.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.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, makeEnvironmentProviders, DestroyRef, input, Directive, computed, signal, forwardRef, effect, TemplateRef, ViewContainerRef, Injector, runInInjectionContext, NgModule, Service, Input, output, model, booleanAttribute, Pipe, LOCALE_ID, viewChild, Component, Inject, Optional } from '@angular/core';
3
3
  import { HttpClient } from '@angular/common/http';
4
- import { defer, switchMap, first, distinctUntilChanged, filter, EMPTY, debounce, interval, throttle, exhaustMap, mergeMap, map, shareReplay, combineLatest, startWith, of, delay, BehaviorSubject, tap, combineLatestWith, catchError, withLatestFrom, firstValueFrom, takeUntil, Subject, Observable } from 'rxjs';
5
- import { AssetLoader, SubscriptionObject, LockSet, loadingStateContext, listLoadingStateContext, filterMaybe, LoadingStateType, beginLoading, idleLoadingState, errorResult, successResult, scanCount, workFactory, asObservable, combineLatestFromMapValuesObsFn, emitDelayObs, makeIsModifiedFunctionObservable, returnIfIs, timeoutStartWith, onFalseToTrue, onTrueToFalse, setContainsAllValuesFrom, setContainsNoValueFrom, maybeValueFromObservableOrValueGetter, switchMapToDefault, asObservableFromGetter, maybeValueFromObservableOrValue, distinctUntilKeysChange, FilterSource, FilterSourceConnector, FilterSourceInstance, FilterMap, skipAllInitialMaybe, switchMapObject } from '@dereekb/rxjs';
6
- import { getValueFromGetter, DestroyFunctionObject, isNotFalse, isDefinedAndNotFalse, BooleanStringKeyArrayUtility, reduceBooleansWithOrFn, asGetter, toReadableError, hasValueOrNotEmpty, isGetter, asArray, setIncludes, isAllowed, maybeSet, iterableToSet, IMPERSONATION_URL_QUERY_PARAM, expandTreeFunction, flattenTreeToArrayFunction, expandFlattenTreeFunction, mailToUrlString, telUrlString, mergeObjects, KeyValueTypleValueFilter, filterMaybeArrayValues, defaultForwardFunctionFactory, MINUTES_IN_DAY, MINUTES_IN_HOUR, millisecondsToMinutes, cutString, dollarAmountString, objectHasKey, objectHasKeys, safeEqualityComparatorFunction, mergeArrays, flattenArrayOrValueArray, promiseReference, DataIsExpiredError, DataDoesNotExistError, hasNonNullValue, unixDateTimeSecondsNumberForNow, isThrottled, splitJoinRemainder, StorageObjectUtility, SHARED_MEMORY_STORAGE } from '@dereekb/util';
4
+ import { defer, switchMap, first, distinctUntilChanged, filter, EMPTY, debounce, interval, throttle, exhaustMap, mergeMap, map, shareReplay, combineLatest, startWith, of, delay, BehaviorSubject, tap, combineLatestWith, catchError, withLatestFrom, firstValueFrom, takeUntil, Subject, Observable, skip } from 'rxjs';
5
+ import { AssetLoader, SubscriptionObject, LockSet, loadingStateContext, listLoadingStateContext, filterMaybe, LoadingStateType, beginLoading, idleLoadingState, errorResult, successResult, scanCount, workFactory, asObservable, combineLatestFromMapValuesObsFn, emitDelayObs, makeIsModifiedFunctionObservable, returnIfIs, timeoutStartWith, onFalseToTrue, onTrueToFalse, setContainsAllValuesFrom, setContainsNoValueFrom, maybeValueFromObservableOrValueGetter, switchMapToDefault, asObservableFromGetter, maybeValueFromObservableOrValue, distinctUntilKeysChange, FilterSource, FilterSourceConnector, FilterSourceInstance, FilterMap, distinctUntilObjectValuesChanged, skipAllInitialMaybe, switchMapObject } from '@dereekb/rxjs';
6
+ import { getValueFromGetter, DestroyFunctionObject, isNotFalse, isDefinedAndNotFalse, BooleanStringKeyArrayUtility, reduceBooleansWithOrFn, asGetter, toReadableError, hasValueOrNotEmpty, isGetter, asArray, setIncludes, isAllowed, maybeSet, iterableToSet, IMPERSONATION_URL_QUERY_PARAM, expandTreeFunction, flattenTreeToArrayFunction, expandFlattenTreeFunction, mailToUrlString, telUrlString, mergeObjects, KeyValueTypleValueFilter, filterMaybeArrayValues, defaultForwardFunctionFactory, MINUTES_IN_DAY, MINUTES_IN_HOUR, millisecondsToMinutes, cutString, dollarAmountString, DataIsExpiredError, DataDoesNotExistError, hasNonNullValue, unixDateTimeSecondsNumberForNow, isThrottled, splitJoinRemainder, StorageObjectUtility, objectHasKey, objectHasKeys, safeEqualityComparatorFunction, mergeArrays, flattenArrayOrValueArray, promiseReference, SHARED_MEMORY_STORAGE } from '@dereekb/util';
7
7
  import { toObservable, toSignal, outputToObservable } from '@angular/core/rxjs-interop';
8
8
  import { ComponentStore } from '@ngrx/component-store';
9
9
  import { Actions, ofType, createEffect, provideEffects } from '@ngrx/effects';
@@ -8225,1196 +8225,1410 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
8225
8225
  }] });
8226
8226
 
8227
8227
  /**
8228
- * Type guard that checks if an object is a {@link ClickableFilterPreset}.
8229
- *
8230
- * @param preset - The object to check.
8231
- * @returns `true` if the object has a `presetValue` property.
8232
- */
8233
- function isClickableFilterPreset(preset) {
8234
- return objectHasKey(preset, 'presetValue');
8235
- }
8236
- /**
8237
- * Type guard that checks if an object is a {@link ClickablePartialFilterPreset}.
8228
+ * Provides a read-only {@link FilterSource} that merges the filters of several keys in an ancestor {@link FilterMap} into one filter. Lets separate filter controls (e.g. a date popover and an attributes popover) each own a key while children consume a single combined filter.
8238
8229
  *
8239
- * @param preset - The object to check.
8240
- * @returns `true` if the object has `partialPresetValue` and `isActive` properties.
8241
- */
8242
- function isClickablePartialFilterPreset(preset) {
8243
- return objectHasKeys(preset, ['partialPresetValue', 'isActive']);
8244
- }
8245
-
8246
- /**
8247
- * Provides a {@link FilterSource} in DI so child components can inject and consume the current filter value. Use this on a wrapper element when the child is the canonical owner of the filter (a filter form, a chip group).
8230
+ * Keys later in the array win when two keys set the same field, and the merged filter has no `preset`. Every key needs a value (e.g. a default filter) before the merged filter emits.
8248
8231
  *
8249
8232
  * @dbxFilter
8250
- * @dbxFilterSlug source
8251
- * @dbxFilterRelated connect-source, source-connector, map-source
8233
+ * @dbxFilterSlug map-merge-source
8234
+ * @dbxFilterRelated map, map-source, map-source-connector
8252
8235
  * @dbxFilterSkillRefs dbx__ref__dbx-component-patterns
8253
8236
  *
8254
8237
  * @example
8255
8238
  * ```html
8256
- * <div dbxFilterSource>
8257
- * <my-filter-form></my-filter-form>
8239
+ * <div dbxFilterMap>
8240
+ * <my-date-filter-button [dbxFilterMapSourceConnector]="'date'"></my-date-filter-button>
8241
+ * <my-attributes-filter-button [dbxFilterMapSourceConnector]="'attributes'"></my-attributes-filter-button>
8242
+ * <div [dbxFilterMapMergeSource]="['date', 'attributes']">
8243
+ * <my-filtered-list></my-filtered-list>
8244
+ * </div>
8258
8245
  * </div>
8259
8246
  * ```
8260
8247
  */
8261
- class DbxFilterSourceDirective extends AbstractFilterSourceDirective {
8262
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterSourceDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
8263
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: DbxFilterSourceDirective, isStandalone: true, selector: "[dbxFilterSource]", providers: provideFilterSourceDirective(DbxFilterSourceDirective), usesInheritance: true, ngImport: i0 });
8248
+ class DbxFilterMapMergeSourceDirective {
8249
+ dbxFilterMap = inject((FilterMap));
8250
+ /**
8251
+ * The map keys to merge, in priority order.
8252
+ */
8253
+ dbxFilterMapMergeSource = input(/* @ts-ignore */
8254
+ ...(ngDevMode ? [undefined, { debugName: "dbxFilterMapMergeSource" }] : /* istanbul ignore next */ []));
8255
+ filterMapKeys$ = toObservable(this.dbxFilterMapMergeSource).pipe(filterMaybe(), distinctUntilObjectValuesChanged());
8256
+ filter$ = this.filterMapKeys$.pipe(switchMap((keys) => this.dbxFilterMap.mergedFilterForKeys(keys)), shareReplay(1));
8257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterMapMergeSourceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8258
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: DbxFilterMapMergeSourceDirective, isStandalone: true, selector: "[dbxFilterMapMergeSource]", inputs: { dbxFilterMapMergeSource: { classPropertyName: "dbxFilterMapMergeSource", publicName: "dbxFilterMapMergeSource", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideFilterSource(DbxFilterMapMergeSourceDirective)], exportAs: ["dbxFilterMapMergeSource"], ngImport: i0 });
8264
8259
  }
8265
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterSourceDirective, decorators: [{
8260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterMapMergeSourceDirective, decorators: [{
8266
8261
  type: Directive,
8267
8262
  args: [{
8268
- selector: '[dbxFilterSource]',
8269
- providers: provideFilterSourceDirective(DbxFilterSourceDirective)
8270
- }]
8271
- }] });
8272
-
8273
- const importsAndExports = [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective];
8274
- class DbxCoreFilterModule {
8275
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
8276
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, imports: [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective], exports: [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective] });
8277
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule });
8278
- }
8279
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, decorators: [{
8280
- type: NgModule,
8281
- args: [{
8282
- imports: importsAndExports,
8283
- exports: importsAndExports
8263
+ selector: '[dbxFilterMapMergeSource]',
8264
+ exportAs: 'dbxFilterMapMergeSource',
8265
+ providers: [provideFilterSource(DbxFilterMapMergeSourceDirective)]
8284
8266
  }]
8285
- }] });
8267
+ }], propDecorators: { dbxFilterMapMergeSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxFilterMapMergeSource", required: false }] }] } });
8286
8268
 
8287
8269
  /**
8288
- * Injection token used to provide arbitrary data to dynamically created components.
8289
- *
8290
- * Components created via {@link DbxInjectionComponentConfig} can inject this token
8291
- * to access the `data` property from their configuration.
8270
+ * Default {@link SimpleStorageAccessorConverter} that uses `JSON.stringify`/`JSON.parse` for conversion.
8292
8271
  *
8293
- * @example
8294
- * ```typescript
8295
- * // In a dynamically injected component:
8296
- * readonly data = inject(DBX_INJECTION_COMPONENT_DATA);
8297
- * ```
8272
+ * @typeParam T - The type of value being converted.
8298
8273
  */
8299
- const DBX_INJECTION_COMPONENT_DATA = new InjectionToken('DbxInjectionComponentConfigData');
8274
+ class StringifySimpleStorageAccessorConverter {
8275
+ stringifyValue(value) {
8276
+ return JSON.stringify(value);
8277
+ }
8278
+ parseValue(data) {
8279
+ return JSON.parse(data);
8280
+ }
8281
+ }
8300
8282
  /**
8301
- * Compares two {@link DbxInjectionComponentConfig} values for structural equality, safely handling nullish values.
8302
- *
8303
- * Compares `componentClass`, `data`, `init`, and `injector` by reference. Does NOT compare
8304
- * `providers` or `ngModuleRef`, as provider arrays typically change on every list data update
8305
- * (e.g., with new `DBX_VALUE_LIST_VIEW_ITEM` values) without requiring component recreation.
8283
+ * Composes a {@link StorageAccessor} and a {@link SimpleStorageAccessorConverter} into a single
8284
+ * {@link SimpleStorageAccessorDelegate} implementation.
8306
8285
  *
8307
- * When both values are nullish, uses strict equality (`null === null` is `true`,
8308
- * `null === undefined` is `false`). The comparator is only invoked when both are non-nullish.
8286
+ * @typeParam T - The type of value being stored.
8309
8287
  */
8310
- const dbxInjectionComponentConfigIsEqual = safeEqualityComparatorFunction((a, b) => a.componentClass === b.componentClass && a.data === b.data && a.init === b.init && a.injector === b.injector);
8288
+ class WrapperSimpleStorageAccessorDelegate {
8289
+ _delegate;
8290
+ _converter;
8291
+ constructor(delegate, converter) {
8292
+ this._delegate = delegate;
8293
+ this._converter = converter;
8294
+ }
8295
+ get(key) {
8296
+ return this._delegate.get(key);
8297
+ }
8298
+ set(key, value) {
8299
+ return this._delegate.set(key, value);
8300
+ }
8301
+ remove(key) {
8302
+ return this._delegate.remove(key);
8303
+ }
8304
+ clear() {
8305
+ return this._delegate.clear();
8306
+ }
8307
+ all(prefix) {
8308
+ return this._delegate.all(prefix);
8309
+ }
8310
+ allKeys(prefix) {
8311
+ return this._delegate.allKeys(prefix);
8312
+ }
8313
+ stringifyValue(value) {
8314
+ return this._converter.stringifyValue(value);
8315
+ }
8316
+ parseValue(data) {
8317
+ return this._converter.parseValue(data);
8318
+ }
8319
+ }
8311
8320
  /**
8312
- * Merges multiple partial {@link DbxInjectionComponentConfig} objects into a single configuration.
8313
- *
8314
- * Provider arrays are concatenated so that all providers from all configs are preserved.
8315
- * All other properties are merged with later values taking precedence.
8321
+ * Validates that a storage key prefix is non-empty and does not contain the prefix splitter character.
8316
8322
  *
8317
- * @param configs - The partial configs to merge. May contain `undefined`/`null` entries which are filtered out.
8318
- * @returns A single merged partial configuration.
8323
+ * @param prefix - The prefix string to validate.
8324
+ * @param prefixSplitter - The splitter string that separates the prefix from the key.
8325
+ * @throws {Error} If the prefix is invalid or the splitter is empty.
8319
8326
  */
8320
- function mergeDbxInjectionComponentConfigs(configs) {
8321
- const providers = mergeArrays(filterMaybeArrayValues(configs).map((x) => x.providers));
8322
- const result = mergeObjects(configs);
8323
- result.providers = providers;
8324
- return result;
8327
+ function assertValidStorageKeyPrefix(prefix, prefixSplitter) {
8328
+ if (!prefixSplitter) {
8329
+ throw new Error('Invalid storage key prefix splitter. Must be defined and not empty.'); // TODO(FUTURE): Consider changing to a concrete error type
8330
+ }
8331
+ if (!isValidStorageKeyPrefix(prefix, prefixSplitter)) {
8332
+ throw new Error('Invalid storage key prefix.');
8333
+ }
8325
8334
  }
8326
-
8327
8335
  /**
8328
- * Flattens and merges multiple provider sources into a single `StaticProvider[]` array.
8329
- *
8330
- * Each argument can be a single `StaticProvider`, an array of providers, or `undefined`/`null`.
8331
- * All values are flattened into a single array with nullish entries removed.
8332
- *
8333
- * @param providers - Any number of provider values or arrays to merge.
8334
- * @returns A flat array of all non-nullish static providers.
8336
+ * Checks whether a storage key prefix is valid (non-empty and does not contain the splitter).
8335
8337
  *
8336
- * @example
8337
- * ```typescript
8338
- * const providers = mergeStaticProviders(
8339
- * { provide: TOKEN_A, useValue: 'a' },
8340
- * undefined,
8341
- * [{ provide: TOKEN_B, useValue: 'b' }]
8342
- * );
8343
- * // Result: [{ provide: TOKEN_A, useValue: 'a' }, { provide: TOKEN_B, useValue: 'b' }]
8344
- * ```
8338
+ * @param prefix - The prefix string to validate.
8339
+ * @param prefixSpltter - The splitter string that separates the prefix from the key.
8340
+ * @returns `true` if the prefix is valid.
8345
8341
  */
8346
- function mergeStaticProviders(...providers) {
8347
- return flattenArrayOrValueArray(providers);
8342
+ function isValidStorageKeyPrefix(prefix, prefixSpltter) {
8343
+ return Boolean(prefix?.indexOf(prefixSpltter) === -1);
8348
8344
  }
8349
8345
  /**
8350
- * Builds an element injector from a {@link DbxInjectionComponentConfig},
8351
- * merging the config's providers and {@link DBX_INJECTION_COMPONENT_DATA} into a child injector.
8346
+ * Full-featured {@link StorageAccessor} that adds key namespacing, JSON serialization,
8347
+ * and optional time-based expiration on top of a raw string storage backend.
8352
8348
  *
8353
- * Returns the config's injector (or parent) unchanged if no providers or data are specified.
8349
+ * Values are stored as JSON with metadata (timestamp) and automatically checked
8350
+ * for expiration on read.
8354
8351
  *
8355
- * @param params - The config and parent injector to build from.
8356
- * @returns A child {@link Injector} with the config's providers and data, or the parent injector if none are specified.
8352
+ * @typeParam T - The type of values stored.
8353
+ *
8354
+ * @throws {DataDoesNotExistError} When reading a key that does not exist.
8355
+ * @throws {DataIsExpiredError} When reading a key whose stored data has expired.
8357
8356
  *
8358
8357
  * @example
8359
8358
  * ```typescript
8360
- * const injector = createInjectorForInjectionComponentConfig({
8361
- * config: { componentClass: MyComponent, data: { id: 1 }, providers: [myProvider] },
8362
- * parentInjector: this._injector
8359
+ * const accessor = factory.createStorageAccessor<UserSettings>({
8360
+ * prefix: 'user_prefs',
8361
+ * expiresIn: 3600000,
8363
8362
  * });
8363
+ *
8364
+ * accessor.set('theme', { dark: true }).subscribe();
8365
+ * accessor.get('theme').subscribe(settings => console.log(settings));
8364
8366
  * ```
8365
- * @__NO_SIDE_EFFECTS__
8366
8367
  */
8367
- function createInjectorForInjectionComponentConfig(params) {
8368
- const { config, parentInjector } = params;
8369
- const { injector: inputInjector, providers, data } = config;
8370
- const parent = inputInjector ?? parentInjector;
8371
- let result;
8372
- if (!providers && data == null) {
8373
- result = parent;
8368
+ class SimpleStorageAccessor {
8369
+ static PREFIX_SPLITTER = '::';
8370
+ _delegate;
8371
+ _config;
8372
+ constructor(delegate, config) {
8373
+ const prefix = config.prefix;
8374
+ const prefixSplitter = config.prefixSplitter ?? SimpleStorageAccessor.PREFIX_SPLITTER;
8375
+ assertValidStorageKeyPrefix(prefix, prefixSplitter);
8376
+ const fullPrefix = `${prefix}${prefixSplitter}`;
8377
+ this._delegate = delegate;
8378
+ this._config = {
8379
+ ...config,
8380
+ prefixSplitter,
8381
+ fullPrefix
8382
+ };
8374
8383
  }
8375
- else {
8376
- result = Injector.create({
8377
- parent,
8378
- providers: mergeStaticProviders({ provide: DBX_INJECTION_COMPONENT_DATA, useValue: data }, providers)
8379
- });
8384
+ get delegate() {
8385
+ return this._delegate;
8380
8386
  }
8381
- return result;
8382
- }
8383
- /**
8384
- * Runs the init callback from a {@link DbxInjectionComponentConfig} on the given component ref,
8385
- * executing within the component's injection context so that `inject()` is available inside the callback.
8386
- *
8387
- * @param componentRef - The component reference whose instance will be passed to the init callback.
8388
- * @param config - The injection component config containing the optional init callback.
8389
- *
8390
- * @example
8391
- * ```typescript
8392
- * const ref = viewContainerRef.createComponent(config.componentClass, { injector });
8393
- * initInjectionComponent(ref, config);
8394
- * ```
8395
- */
8396
- function initInjectionComponent(componentRef, config) {
8397
- const { init } = config;
8398
- if (init) {
8399
- runInInjectionContext(componentRef.injector, () => init(componentRef.instance));
8387
+ get config() {
8388
+ return this._config;
8400
8389
  }
8401
- }
8402
-
8403
- /**
8404
- * Core runtime engine for the dbx-injection system. Manages the lifecycle of dynamically injected
8405
- * components and templates within an Angular `ViewContainerRef`.
8406
- *
8407
- * This class reactively listens to configuration and template observables. When a new
8408
- * {@link DbxInjectionComponentConfig} or {@link DbxInjectionTemplateConfig} is emitted, it tears
8409
- * down the previous content and creates the new component or template in the target view container.
8410
- *
8411
- * Component configs take precedence over template configs when both are provided.
8412
- *
8413
- * Typically used internally by {@link AbstractDbxInjectionDirective} and {@link DbxInjectionComponent}
8414
- * rather than consumed directly.
8415
- *
8416
- * @typeParam T - The type of the dynamically created component.
8417
- *
8418
- * @see {@link DbxInjectionComponentConfig}
8419
- * @see {@link DbxInjectionTemplateConfig}
8420
- * @see {@link AbstractDbxInjectionDirective}
8421
- */
8422
- class DbxInjectionInstance {
8423
- _subscriptionObject = new SubscriptionObject();
8424
- _config = new BehaviorSubject(undefined);
8425
- _template = new BehaviorSubject(undefined);
8426
- _content = new BehaviorSubject(undefined);
8427
- _componentRef = new BehaviorSubject(undefined);
8428
- _injector;
8429
- config$ = this._config.pipe(maybeValueFromObservableOrValueGetter(), distinctUntilChanged(dbxInjectionComponentConfigIsEqual), shareReplay(1));
8430
- template$ = this._template.pipe(maybeValueFromObservableOrValueGetter(), distinctUntilChanged(), shareReplay(1));
8431
- content$ = this._content.pipe(filterMaybe(), distinctUntilChanged(), shareReplay(1));
8432
- get config() {
8433
- return this._config.value;
8434
- }
8435
- set config(config) {
8436
- this._config.next(config);
8390
+ // MARK: LimitedStorageAccessor
8391
+ get(inputKey) {
8392
+ const storeKey = this.makeStorageKey(inputKey);
8393
+ return this._delegate.get(storeKey).pipe(map((storedData) => {
8394
+ let result;
8395
+ if (storedData) {
8396
+ const readStoredData = this.readStoredData(storedData);
8397
+ if (readStoredData.expired) {
8398
+ throw new DataIsExpiredError(readStoredData);
8399
+ }
8400
+ else {
8401
+ result = readStoredData.convertedData;
8402
+ }
8403
+ }
8404
+ else {
8405
+ throw new DataDoesNotExistError();
8406
+ }
8407
+ return result;
8408
+ }));
8437
8409
  }
8438
- get template() {
8439
- return this._template.value;
8410
+ set(inputKey, inputValue) {
8411
+ const storeKey = this.makeStorageKey(inputKey);
8412
+ const storeData = this.buildStoredData(inputValue);
8413
+ const data = JSON.stringify(storeData);
8414
+ return this._delegate.set(storeKey, data);
8440
8415
  }
8441
- set template(template) {
8442
- this._template.next(template);
8416
+ remove(key) {
8417
+ const storeKey = this.makeStorageKey(key);
8418
+ return this._delegate.remove(storeKey);
8443
8419
  }
8444
- get content() {
8445
- return this._content.value;
8420
+ all() {
8421
+ return this._delegate.all(this._config.fullPrefix).pipe(map((allStoredData) => {
8422
+ return allStoredData
8423
+ .map((storedData) => {
8424
+ const readStoredData = this.readStoredData(storedData);
8425
+ return readStoredData.expired ? null : readStoredData.convertedData;
8426
+ })
8427
+ .filter(hasNonNullValue);
8428
+ }));
8446
8429
  }
8447
- set content(content) {
8448
- this._content.next(content);
8430
+ allKeys() {
8431
+ return this._delegate.allKeys(this._config.fullPrefix).pipe(map((keys) => keys.map((x) => this.decodeStorageKey(x))));
8449
8432
  }
8450
- get componentRef() {
8451
- return this._componentRef.value;
8433
+ clear() {
8434
+ return this._delegate.clear();
8452
8435
  }
8453
- set componentRef(componentRef) {
8454
- this._componentRef.next(componentRef);
8436
+ // MARK: Stored Values
8437
+ readStoredData(storedDataString) {
8438
+ const storedData = JSON.parse(storedDataString);
8439
+ const expired = this.isExpiredStoredData(storedData);
8440
+ const convertedData = this._delegate.parseValue(storedData.data);
8441
+ return {
8442
+ ...storedData,
8443
+ expired,
8444
+ convertedData
8445
+ };
8455
8446
  }
8456
- constructor(injector) {
8457
- this._injector = injector;
8447
+ buildStoredData(value) {
8448
+ return {
8449
+ storedAt: unixDateTimeSecondsNumberForNow(),
8450
+ data: this.stringifyValue(value)
8451
+ };
8458
8452
  }
8459
- init() {
8460
- // Wait until the first of either of the two inputs comes in as not defined, and then emit.
8461
- // We filter the first maybe here between the two items.
8462
- const configTemplateObs = combineLatest([this.config$, this.template$]).pipe(map(([config, template]) => {
8463
- return config || template ? { config, template } : undefined;
8464
- }), skipAllInitialMaybe());
8465
- this._subscriptionObject.subscription = combineLatest([configTemplateObs, this.content$]).subscribe(([inputConfig, content]) => {
8466
- const { config, template } = inputConfig ?? {};
8467
- this._reset(content);
8468
- if (config) {
8469
- this._initComponent(config, content);
8453
+ isExpiredStoredData(storeData) {
8454
+ const expiresIn = this._config.expiresIn;
8455
+ let result;
8456
+ if (expiresIn) {
8457
+ if (storeData.storedAt) {
8458
+ result = isThrottled(expiresIn, storeData.storedAt);
8470
8459
  }
8471
- else if (template) {
8472
- this._initTemplate(template, content);
8460
+ else {
8461
+ result = true;
8473
8462
  }
8474
- });
8463
+ }
8464
+ else {
8465
+ result = false;
8466
+ }
8467
+ return result;
8475
8468
  }
8476
- destroy() {
8477
- this._subscriptionObject.destroy();
8478
- this._config.complete();
8479
- this._template.complete();
8480
- this._content.complete();
8481
- this._componentRef.complete();
8469
+ // MARK: Internal
8470
+ makeStorageKey(key) {
8471
+ return `${this._config.prefix}${this._config.prefixSplitter}${String(key)}`;
8482
8472
  }
8483
- _initComponent(config, content) {
8484
- content.clear();
8485
- const { componentClass, ngModuleRef } = config;
8486
- if (!componentClass) {
8487
- throw new Error('DbxInjectionInstance: componentClass was expected in the config but it was unavailable.');
8488
- }
8489
- const injector = createInjectorForInjectionComponentConfig({ config, parentInjector: this._injector });
8490
- const componentRef = content.createComponent(componentClass, { injector, ngModuleRef });
8491
- initInjectionComponent(componentRef, config);
8492
- this.componentRef = componentRef;
8473
+ isKeyOfAccessor(storageKey) {
8474
+ return storageKey.startsWith(this._config.fullPrefix);
8493
8475
  }
8494
- _initTemplate(config, content) {
8495
- content.clear();
8496
- const { templateRef, viewRef } = config;
8497
- if (templateRef) {
8498
- content.createEmbeddedView(templateRef);
8499
- // TODO: Figure out if these items need to be destroyed or not when this item is destroyed. If so, we need a reference to destroy.
8500
- }
8501
- else if (viewRef) {
8502
- content.insert(viewRef);
8503
- }
8476
+ decodeStorageKey(storageKey) {
8477
+ const split = splitJoinRemainder(storageKey, this._config.prefixSplitter, 2);
8478
+ return split[1];
8504
8479
  }
8505
- _reset(content) {
8506
- if (this.componentRef) {
8507
- content.clear();
8508
- this.componentRef = undefined;
8509
- }
8480
+ stringifyValue(value) {
8481
+ return this._delegate.stringifyValue(value);
8510
8482
  }
8511
8483
  }
8512
8484
 
8513
8485
  /**
8514
- * Abstract base directive for dynamically injecting components or templates into a view.
8515
- *
8516
- * Manages a {@link DbxInjectionInstance} lifecycle, initializing it on `ngOnInit` and
8517
- * destroying it on `ngOnDestroy`. Subclasses are responsible for wiring their inputs
8518
- * (config, template, content) to the corresponding setter methods.
8486
+ * {@link StorageAccessor} implementation that wraps a {@link FullStorageObject} and stores raw strings.
8519
8487
  *
8520
- * @typeParam T - The type of the dynamically created component.
8488
+ * Each operation completes synchronously within an observable wrapper.
8521
8489
  *
8522
- * @see {@link DbxInjectionComponent} - The concrete standalone component implementation.
8523
- * @see {@link DbxInjectionInstance}
8490
+ * @example
8491
+ * ```typescript
8492
+ * const accessor = new StringStorageAccessor(localStorage);
8493
+ * accessor.set('key', 'value').subscribe();
8494
+ * accessor.get('key').subscribe(value => console.log(value)); // 'value'
8495
+ * ```
8524
8496
  */
8525
- class AbstractDbxInjectionDirective {
8526
- _instance = new DbxInjectionInstance(inject(Injector));
8527
- ngOnInit() {
8528
- this._instance.init();
8497
+ class StringStorageAccessor {
8498
+ _storage;
8499
+ constructor(storage) {
8500
+ this._storage = storage;
8529
8501
  }
8530
- ngOnDestroy() {
8531
- this._instance.destroy();
8502
+ get(key) {
8503
+ return new Observable((x) => {
8504
+ const value = this._storage.getItem(key);
8505
+ x.next(value);
8506
+ x.complete();
8507
+ });
8532
8508
  }
8533
- /**
8534
- * Sets the component injection configuration on the underlying {@link DbxInjectionInstance}.
8535
- *
8536
- * @param config - The component config, observable, or getter to use.
8537
- */
8538
- setConfig(config) {
8539
- this._instance.config = config;
8509
+ set(key, value) {
8510
+ return new Observable((x) => {
8511
+ this._storage.setItem(key, value);
8512
+ x.next();
8513
+ x.complete();
8514
+ });
8540
8515
  }
8541
- /**
8542
- * Sets the template injection configuration on the underlying {@link DbxInjectionInstance}.
8543
- *
8544
- * @param template - The template config, observable, or getter to use.
8545
- */
8546
- setTemplate(template) {
8547
- this._instance.template = template;
8516
+ remove(key) {
8517
+ return new Observable((x) => {
8518
+ this._storage.removeItem(key);
8519
+ x.next();
8520
+ x.complete();
8521
+ });
8548
8522
  }
8549
- /**
8550
- * Sets the target `ViewContainerRef` where dynamic content will be inserted.
8551
- *
8552
- * @param content - The view container reference for content projection.
8553
- */
8554
- setContent(content) {
8555
- this._instance.content = content;
8523
+ clear() {
8524
+ const removed = this._storage.removeAll();
8525
+ return new Observable((x) => {
8526
+ x.next(removed);
8527
+ x.complete();
8528
+ });
8529
+ }
8530
+ all() {
8531
+ return this.allKeys().pipe(map((x) => filterMaybeArrayValues(x.map((y) => this._storage.getItem(y)))), shareReplay(1));
8532
+ }
8533
+ allKeys() {
8534
+ return new Observable((x) => {
8535
+ const result = StorageObjectUtility.allKeysFromStorageObject(this._storage);
8536
+ x.next(result);
8537
+ x.complete();
8538
+ });
8556
8539
  }
8557
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractDbxInjectionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8558
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: AbstractDbxInjectionDirective, isStandalone: true, ngImport: i0 });
8559
8540
  }
8560
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractDbxInjectionDirective, decorators: [{
8561
- type: Directive
8562
- }] });
8563
8541
 
8564
8542
  /**
8565
- * Standalone component for dynamically injecting a component or template into the DOM.
8566
- *
8567
- * Accepts a {@link DbxInjectionComponentConfig} (to create a component) or a
8568
- * {@link DbxInjectionTemplateConfig} (to embed a template/view). When the config changes,
8569
- * the previous content is destroyed and the new content is created in its place.
8543
+ * DI token for the application's default {@link FullStorageObject} instance.
8570
8544
  *
8571
- * Can be used as an element (`<dbx-injection>`) or as an attribute directive
8572
- * (`[dbxInjection]` / `[dbx-injection]`).
8545
+ * Provided by {@link provideDbxStorage}.
8546
+ */
8547
+ const DEFAULT_STORAGE_OBJECT_TOKEN = new InjectionToken('DBX_CORE_DEFAULT_STORAGE_OBJECT');
8548
+ /**
8549
+ * DI token for the application's default {@link SimpleStorageAccessorFactory} instance.
8573
8550
  *
8574
- * @typeParam T - The type of the dynamically created component.
8551
+ * Provided by {@link provideDbxStorage}.
8552
+ */
8553
+ const DEFAULT_STORAGE_ACCESSOR_FACTORY_TOKEN = new InjectionToken('DBX_CORE_DEFAULT_STORAGE_ACCESSOR_FACTORY');
8554
+
8555
+ /**
8556
+ * Injectable factory for creating namespaced {@link SimpleStorageAccessor} instances
8557
+ * backed by the application's default storage object.
8575
8558
  *
8576
8559
  * @example
8577
- * ```html
8578
- * <!-- Element usage with a component config -->
8579
- * <dbx-injection [config]="myComponentConfig"></dbx-injection>
8580
- *
8581
- * <!-- Element usage with a template config -->
8582
- * <dbx-injection [template]="myTemplateConfig"></dbx-injection>
8560
+ * ```typescript
8561
+ * @Injectable()
8562
+ * export class UserPrefsService {
8563
+ * private readonly storage = inject(SimpleStorageAccessorFactory)
8564
+ * .createStorageAccessor<UserPrefs>({ prefix: 'user_prefs' });
8583
8565
  *
8584
- * <!-- Attribute usage -->
8585
- * <div dbxInjection [config]="myComponentConfig"></div>
8566
+ * save(prefs: UserPrefs) { return this.storage.set('current', prefs); }
8567
+ * load() { return this.storage.get('current'); }
8568
+ * }
8586
8569
  * ```
8570
+ */
8571
+ class SimpleStorageAccessorFactory {
8572
+ storageObject = inject(DEFAULT_STORAGE_OBJECT_TOKEN);
8573
+ createStorageAccessor(config) {
8574
+ const storage = config.storage ?? new StringStorageAccessor(this.storageObject);
8575
+ const converter = config.converter ?? new StringifySimpleStorageAccessorConverter();
8576
+ const delegate = new WrapperSimpleStorageAccessorDelegate(storage, converter);
8577
+ const accessorConfig = {
8578
+ prefix: config.prefix
8579
+ };
8580
+ return new SimpleStorageAccessor(delegate, accessorConfig);
8581
+ }
8582
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8583
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory });
8584
+ }
8585
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory, decorators: [{
8586
+ type: Injectable
8587
+ }] });
8588
+
8589
+ /**
8590
+ * Default storage key prefix used by {@link DbxFilterStorageService}.
8591
+ */
8592
+ const DEFAULT_DBX_FILTER_STORAGE_PREFIX = 'dbxf';
8593
+ /**
8594
+ * Optional injection token for the {@link DbxFilterStorageServiceConfig}.
8595
+ */
8596
+ const DBX_FILTER_STORAGE_SERVICE_CONFIG_TOKEN = new InjectionToken('DbxFilterStorageServiceConfig');
8597
+ /**
8598
+ * Saves and loads filters to storage (localStorage by default, via {@link SimpleStorageAccessorFactory}).
8587
8599
  *
8588
- * @see {@link DbxInjectionComponentConfig}
8589
- * @see {@link DbxInjectionTemplateConfig}
8590
- * @see {@link AbstractDbxInjectionDirective}
8600
+ * Filters are saved as JSON. Filters with fields that JSON cannot represent, such as Date or Set fields, pass a {@link FilterJsonConverter}.
8601
+ *
8602
+ * To save and load the filter of a {@link FilterMap} key, use the {@link DbxFilterMapStorageDirective} in the template instead of calling this service directly.
8603
+ *
8604
+ * Provided by {@link provideDbxFilterStorage}, which requires {@link provideDbxStorage}.
8591
8605
  */
8592
- class DbxInjectionComponent extends AbstractDbxInjectionDirective {
8606
+ class DbxFilterStorageService {
8607
+ storageAccessorFactory = inject(SimpleStorageAccessorFactory);
8608
+ config = inject(DBX_FILTER_STORAGE_SERVICE_CONFIG_TOKEN, { optional: true });
8609
+ prefix = this.config?.prefix ?? DEFAULT_DBX_FILTER_STORAGE_PREFIX;
8610
+ storageAccessor = this.storageAccessorFactory.createStorageAccessor({
8611
+ prefix: this.prefix
8612
+ });
8593
8613
  /**
8594
- * Reference to the internal view container where dynamic content is projected.
8614
+ * Loads the saved filter for the given storage key.
8615
+ *
8616
+ * @param storageKey - Key the filter was saved under.
8617
+ * @param jsonConverter - Converts the saved JSON value back to the filter. Without one, the saved value is returned as-is.
8618
+ * @returns Observable of the saved filter, or undefined if nothing is saved or the saved value cannot be read.
8595
8619
  */
8596
- content = viewChild('content', { ...(ngDevMode ? { debugName: "content" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
8620
+ loadFilter(storageKey, jsonConverter) {
8621
+ return this.storageAccessor.get(storageKey).pipe(map((json) => {
8622
+ let result;
8623
+ if (json != null) {
8624
+ result = jsonConverter ? jsonConverter.fromJson(json) : json;
8625
+ }
8626
+ return result;
8627
+ }), catchError(() => of(undefined)));
8628
+ }
8597
8629
  /**
8598
- * The component injection configuration. Accepts an observable, getter, or static value.
8630
+ * Saves the filter under the given storage key.
8631
+ *
8632
+ * @param storageKey - Key to save the filter under.
8633
+ * @param filter - Filter to save.
8634
+ * @param jsonConverter - Converts the filter to the JSON value that is saved. Without one, the filter is saved as-is.
8635
+ * @returns Observable that emits once the filter is saved.
8599
8636
  */
8600
- config = input(/* @ts-ignore */
8601
- ...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
8637
+ saveFilter(storageKey, filter, jsonConverter) {
8638
+ return this.storageAccessor.set(storageKey, jsonConverter ? jsonConverter.toJson(filter) : filter);
8639
+ }
8602
8640
  /**
8603
- * The template injection configuration. Accepts an observable, getter, or static value.
8604
- * Only used when `config` is not provided.
8641
+ * Removes the saved filter for the given storage key.
8642
+ *
8643
+ * @param storageKey - Key the filter was saved under.
8644
+ * @returns Observable that emits once the filter is removed.
8605
8645
  */
8606
- template = input(/* @ts-ignore */
8607
- ...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ []));
8608
- // allow signal writes for each as during their initialization they may write to a signal in some cases when initializing
8609
- _contentEffect = effect(() => this.setContent(this.content()), /* @ts-ignore */
8610
- ...(ngDevMode ? [{ debugName: "_contentEffect" }] : /* istanbul ignore next */ []));
8611
- _configEffect = effect(() => this.setConfig(this.config()), /* @ts-ignore */
8612
- ...(ngDevMode ? [{ debugName: "_configEffect" }] : /* istanbul ignore next */ []));
8613
- _templateEffect = effect(() => this.setTemplate(this.template()), /* @ts-ignore */
8614
- ...(ngDevMode ? [{ debugName: "_templateEffect" }] : /* istanbul ignore next */ []));
8615
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8616
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.4", type: DbxInjectionComponent, isStandalone: true, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, read: ViewContainerRef, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
8617
- <ng-template #content></ng-template>
8618
- `, isInline: true });
8646
+ clearFilter(storageKey) {
8647
+ return this.storageAccessor.remove(storageKey);
8648
+ }
8649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8650
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterStorageService });
8619
8651
  }
8620
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionComponent, decorators: [{
8621
- type: Component,
8622
- args: [{
8623
- selector: 'dbx-injection, [dbxInjection], [dbx-injection]',
8624
- template: `
8625
- <ng-template #content></ng-template>
8626
- `,
8627
- imports: []
8628
- }]
8629
- }], propDecorators: { content: [{ type: i0.ViewChild, args: ['content', { ...{ read: ViewContainerRef }, isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }] } });
8652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterStorageService, decorators: [{
8653
+ type: Injectable
8654
+ }] });
8630
8655
 
8631
8656
  /**
8632
- * Renders a list of dynamically injected components from an array of {@link DbxInjectionArrayEntry} items.
8657
+ * Saves the filter of a keyed entry in an ancestor {@link FilterMap} to storage (localStorage by default), and loads it again when the page loads. The saved filter becomes the key's default filter, and every later change to the key is saved.
8633
8658
  *
8634
- * Each entry is tracked by its `key` property for efficient change detection, and rendered
8635
- * using a nested {@link DbxInjectionComponent}.
8659
+ * Use {@link reset} to set the key back to its default filter and remove the saved filter.
8636
8660
  *
8637
- * @example
8638
- * ```html
8639
- * <dbx-injection-array [entries]="myEntries" />
8640
- * ```
8661
+ * Requires {@link provideDbxFilterStorage}.
8662
+ *
8663
+ * @dbxFilter
8664
+ * @dbxFilterSlug map-storage
8665
+ * @dbxFilterRelated map, map-source-connector, map-merge-source
8666
+ * @dbxFilterSkillRefs dbx__ref__dbx-component-patterns
8641
8667
  *
8642
8668
  * @example
8643
- * ```typescript
8644
- * // In the host component:
8645
- * myEntries: DbxInjectionArrayEntry[] = [
8646
- * { key: 'chart', injectionConfig: { componentClass: ChartComponent } },
8647
- * { key: 'table', injectionConfig: { componentClass: TableComponent } }
8648
- * ];
8669
+ * ```html
8670
+ * <div dbxFilterMap>
8671
+ * <my-filter-button [dbxFilterMapSourceConnector]="'list'" [dbxFilterMapStorage]="{ key: 'list', storageKey: 'my.list.filter', defaultFilter: {} }"></my-filter-button>
8672
+ * </div>
8649
8673
  * ```
8650
- *
8651
- * @see {@link DbxInjectionArrayEntry}
8652
- * @see {@link DbxInjectionComponent}
8653
8674
  */
8654
- class DbxInjectionArrayComponent {
8675
+ class DbxFilterMapStorageDirective {
8676
+ dbxFilterMap = inject((FilterMap));
8677
+ dbxFilterStorageService = inject(DbxFilterStorageService);
8655
8678
  /**
8656
- * The array of keyed injection entries to render. Each entry produces a `<dbx-injection>` component.
8679
+ * The key to save and how to save it.
8657
8680
  */
8658
- entries = input(undefined, /* @ts-ignore */
8659
- ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
8660
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionArrayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8661
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: DbxInjectionArrayComponent, isStandalone: true, selector: "dbx-injection-array", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
8662
- @for (entry of entries(); track entry.key) {
8663
- <dbx-injection [config]="entry.injectionConfig"></dbx-injection>
8681
+ dbxFilterMapStorage = input(/* @ts-ignore */
8682
+ ...(ngDevMode ? [undefined, { debugName: "dbxFilterMapStorage" }] : /* istanbul ignore next */ []));
8683
+ config$ = toObservable(this.dbxFilterMapStorage).pipe(filterMaybe(), distinctUntilChanged(), shareReplay(1));
8684
+ /**
8685
+ * Set while reset() sets the key back to the default filter, so the default filter is not saved.
8686
+ */
8687
+ _resetting = false;
8688
+ constructor() {
8689
+ cleanSubscription(this.config$.pipe(switchMap((config) => this._loadAndSaveFilter(config))).subscribe());
8664
8690
  }
8665
- `, isInline: true, dependencies: [{ kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }] });
8691
+ /**
8692
+ * Sets the key back to the default filter and removes the saved filter.
8693
+ *
8694
+ * The default filter is not saved, so the next page load also starts from the default filter.
8695
+ *
8696
+ * @returns Observable that emits once the key is reset and the saved filter is removed.
8697
+ */
8698
+ reset() {
8699
+ return this.config$.pipe(first(), switchMap((config) => asObservable(config.defaultFilter).pipe(first(), tap((defaultFilter) => {
8700
+ if (defaultFilter != null) {
8701
+ this._resetting = true;
8702
+ this.dbxFilterMap.setFilterForKey(config.key, defaultFilter);
8703
+ this._resetting = false;
8704
+ }
8705
+ }), switchMap(() => this.dbxFilterStorageService.clearFilter(storageKeyForConfig(config))))));
8706
+ }
8707
+ /**
8708
+ * Loads the saved filter as the key's default filter, then returns an observable that saves every later change to the key.
8709
+ *
8710
+ * @param config - The key to save and how to save it.
8711
+ * @returns Observable that saves the key's filter while subscribed.
8712
+ */
8713
+ _loadAndSaveFilter(config) {
8714
+ const { key, defaultFilter, mapLoadedFilter, jsonConverter } = config;
8715
+ const storageKey = storageKeyForConfig(config);
8716
+ const loadedFilter$ = this.dbxFilterStorageService.loadFilter(storageKey, jsonConverter).pipe(map((savedFilter) => (savedFilter != null && mapLoadedFilter ? mapLoadedFilter(savedFilter) : savedFilter)), switchMap((savedFilter) => (savedFilter == null ? asObservable(defaultFilter) : of(savedFilter))), filterMaybe());
8717
+ this.dbxFilterMap.addDefaultFilterObs(key, loadedFilter$);
8718
+ return this.dbxFilterMap.filterForKey(key).pipe(distinctUntilObjectValuesChanged(), skip(1), // the first value is the loaded/default filter
8719
+ filter(() => !this._resetting), switchMap((nextFilter) => this.dbxFilterStorageService.saveFilter(storageKey, nextFilter, jsonConverter)));
8720
+ }
8721
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterMapStorageDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8722
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: DbxFilterMapStorageDirective, isStandalone: true, selector: "[dbxFilterMapStorage]", inputs: { dbxFilterMapStorage: { classPropertyName: "dbxFilterMapStorage", publicName: "dbxFilterMapStorage", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["dbxFilterMapStorage"], ngImport: i0 });
8666
8723
  }
8667
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionArrayComponent, decorators: [{
8668
- type: Component,
8724
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterMapStorageDirective, decorators: [{
8725
+ type: Directive,
8669
8726
  args: [{
8670
- selector: 'dbx-injection-array',
8671
- template: `
8672
- @for (entry of entries(); track entry.key) {
8673
- <dbx-injection [config]="entry.injectionConfig"></dbx-injection>
8674
- }
8675
- `,
8676
- imports: [DbxInjectionComponent]
8727
+ selector: '[dbxFilterMapStorage]',
8728
+ exportAs: 'dbxFilterMapStorage'
8677
8729
  }]
8678
- }], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }] } });
8730
+ }], ctorParameters: () => [], propDecorators: { dbxFilterMapStorage: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxFilterMapStorage", required: false }] }] } });
8731
+ function storageKeyForConfig(config) {
8732
+ return config.storageKey ?? config.key;
8733
+ }
8679
8734
 
8680
8735
  /**
8681
- * Abstract service for temporarily replacing a view's content with a dynamically injected component,
8682
- * then restoring the original content when done.
8683
- *
8684
- * Unlike `*ngIf` or `*ngSwitch`, the original child content is **hidden** rather than destroyed,
8685
- * preserving component state. Once the injected context's promise resolves (or is reset), the
8686
- * original content is re-displayed.
8687
- *
8688
- * This is useful for overlay-like workflows such as inline editors, confirmation dialogs,
8689
- * or multi-step flows where destroying and recreating the underlying view would lose state.
8736
+ * Type guard that checks if an object is a {@link ClickableFilterPreset}.
8690
8737
  *
8691
- * @see {@link DbxInjectionContextConfig}
8692
- * @see {@link DbxInjectionContextDirective} - The concrete structural directive implementation.
8738
+ * @param preset - The object to check.
8739
+ * @returns `true` if the object has a `presetValue` property.
8693
8740
  */
8694
- class DbxInjectionContext {
8741
+ function isClickableFilterPreset(preset) {
8742
+ return objectHasKey(preset, 'presetValue');
8695
8743
  }
8696
8744
  /**
8697
- * Creates Angular providers that register a concrete {@link DbxInjectionContext} implementation
8698
- * under the abstract `DbxInjectionContext` token via `useExisting`.
8699
- *
8700
- * This enables dependency injection consumers to request `DbxInjectionContext` and receive
8701
- * the specific directive or service implementation.
8702
- *
8703
- * @param type - The concrete class to register as the existing provider.
8704
- * @returns Array of Angular providers.
8745
+ * Type guard that checks if an object is a {@link ClickablePartialFilterPreset}.
8705
8746
  *
8706
- * @typeParam T - The concrete type that extends {@link DbxInjectionContext}.
8747
+ * @param preset - The object to check.
8748
+ * @returns `true` if the object has `partialPresetValue` and `isActive` properties.
8707
8749
  */
8708
- function provideDbxInjectionContext(type) {
8709
- return [
8710
- {
8711
- provide: DbxInjectionContext,
8712
- useExisting: type
8713
- }
8714
- ];
8750
+ function isClickablePartialFilterPreset(preset) {
8751
+ return objectHasKeys(preset, ['partialPresetValue', 'isActive']);
8715
8752
  }
8716
8753
 
8717
8754
  /**
8718
- * Structural directive that implements {@link DbxInjectionContext}, allowing its host content
8719
- * to be temporarily replaced with a dynamically injected component and then restored.
8720
- *
8721
- * Unlike `*ngIf` or `*ngSwitch`, the original child content is **detached** (hidden) rather than
8722
- * destroyed, so component state is preserved while the injected context is active. When the
8723
- * context's promise resolves or {@link resetContext} is called, the original view is re-attached.
8724
- *
8725
- * The directive also accepts an optional `[dbxInjectionContext]` input to set a static component
8726
- * config directly, which replaces the default content until cleared.
8755
+ * Provides a {@link FilterSource} in DI so child components can inject and consume the current filter value. Use this on a wrapper element when the child is the canonical owner of the filter (a filter form, a chip group).
8727
8756
  *
8728
- * @typeParam O - The output type produced by {@link showContext}.
8757
+ * @dbxFilter
8758
+ * @dbxFilterSlug source
8759
+ * @dbxFilterRelated connect-source, source-connector, map-source
8760
+ * @dbxFilterSkillRefs dbx__ref__dbx-component-patterns
8729
8761
  *
8730
8762
  * @example
8731
8763
  * ```html
8732
- * <!-- Wrap content that should be temporarily replaceable -->
8733
- * <div *dbxInjectionContext>
8734
- * <p>Original content preserved while context is active</p>
8764
+ * <div dbxFilterSource>
8765
+ * <my-filter-form></my-filter-form>
8735
8766
  * </div>
8736
8767
  * ```
8768
+ */
8769
+ class DbxFilterSourceDirective extends AbstractFilterSourceDirective {
8770
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterSourceDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
8771
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: DbxFilterSourceDirective, isStandalone: true, selector: "[dbxFilterSource]", providers: provideFilterSourceDirective(DbxFilterSourceDirective), usesInheritance: true, ngImport: i0 });
8772
+ }
8773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxFilterSourceDirective, decorators: [{
8774
+ type: Directive,
8775
+ args: [{
8776
+ selector: '[dbxFilterSource]',
8777
+ providers: provideFilterSourceDirective(DbxFilterSourceDirective)
8778
+ }]
8779
+ }] });
8780
+
8781
+ const importsAndExports = [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective, DbxFilterMapMergeSourceDirective, DbxFilterMapStorageDirective];
8782
+ class DbxCoreFilterModule {
8783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
8784
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, imports: [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective, DbxFilterMapMergeSourceDirective, DbxFilterMapStorageDirective], exports: [DbxFilterSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterConnectSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterMapDirective, DbxFilterMapSourceDirective, DbxFilterMapMergeSourceDirective, DbxFilterMapStorageDirective] });
8785
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule });
8786
+ }
8787
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxCoreFilterModule, decorators: [{
8788
+ type: NgModule,
8789
+ args: [{
8790
+ imports: importsAndExports,
8791
+ exports: importsAndExports
8792
+ }]
8793
+ }] });
8794
+
8795
+ /**
8796
+ * Registers {@link DbxFilterStorageService} as an environment-level provider.
8797
+ *
8798
+ * Requires {@link provideDbxStorage} to also be provided.
8799
+ *
8800
+ * @param config - Optional service configuration.
8801
+ * @returns The environment providers for filter storage.
8737
8802
  *
8738
8803
  * @example
8739
8804
  * ```typescript
8740
- * // Programmatically show a temporary editor overlay:
8741
- * const result = await injectionContext.showContext({
8742
- * config: { componentClass: InlineEditorComponent },
8743
- * use: (editor) => editor.waitForSave()
8744
- * });
8805
+ * export const appConfig: ApplicationConfig = {
8806
+ * providers: [provideDbxStorage(), provideDbxFilterStorage()],
8807
+ * };
8745
8808
  * ```
8746
- *
8747
- * @see {@link DbxInjectionContext}
8748
- * @see {@link DbxInjectionContextConfig}
8749
8809
  */
8750
- class DbxInjectionContextDirective {
8751
- _injector = inject(Injector);
8752
- _templateRef = inject((TemplateRef));
8753
- _viewContainer = inject(ViewContainerRef);
8754
- _instance = new DbxInjectionInstance(this._injector);
8755
- _currentPromise;
8756
- _embeddedView;
8757
- _isDetached = false;
8758
- /**
8759
- * Optional static component config input. When set, the directive replaces its content
8760
- * with the specified component. When cleared (`undefined`), the original content is restored.
8761
- */
8762
- config = input(/* @ts-ignore */
8763
- ...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
8764
- _configEffect = effect(() => {
8765
- this.setConfig(this.config());
8766
- // NOTE: we have/call setConfig() because the effect() may not respond to all value changes,
8767
- // especially when setConfig() ends up being called twice quickly in quick succession.
8768
- }, /* @ts-ignore */
8769
- ...(ngDevMode ? [{ debugName: "_configEffect" }] : /* istanbul ignore next */ []));
8770
- ngOnInit() {
8771
- this._instance.content = this._viewContainer;
8772
- this._instance.init();
8773
- this._embeddedView = this._viewContainer.createEmbeddedView(this._templateRef);
8774
- // detach the embedded view
8775
- this._viewContainer.detach();
8776
- // reattach it through the template configuration.
8777
- // the template configuration acts as the default.
8778
- this._instance.template = {
8779
- viewRef: this._embeddedView
8780
- };
8781
- }
8782
- ngOnDestroy() {
8783
- this._instance.destroy();
8784
- this._embeddedView?.destroy(); // destroy our embedded view too if it is set.
8785
- }
8786
- /**
8787
- * {@inheritDoc DbxInjectionContext.showContext}
8788
- *
8789
- * @param config - The injection context configuration describing the component and its usage.
8790
- * @returns Resolves to the output of the injected component's usage.
8791
- */
8792
- async showContext(config) {
8793
- // clear the current context before showing something new.
8794
- this.resetContext();
8795
- let promiseRef;
8796
- let result;
8797
- let error;
8798
- // wait for the promise to resolve and use to finish using that instance.
8799
- try {
8800
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
8801
- promiseRef = promiseReference(async (resolve, reject) => {
8802
- const injectionConfig = {
8803
- ...config.config,
8804
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
8805
- init: async (instance) => {
8806
- // init if available in the base config.
8807
- if (config.config.init) {
8808
- config.config.init(instance);
8809
- }
8810
- try {
8811
- const result = (await config.use(instance));
8812
- resolve(result);
8813
- }
8814
- catch (e) {
8815
- reject(e);
8816
- }
8817
- }
8818
- };
8819
- this.setConfig(injectionConfig);
8820
- });
8821
- this._currentPromise = promiseRef;
8822
- // await the promise
8823
- result = await promiseRef.promise;
8824
- }
8825
- catch (e) {
8826
- error = e;
8827
- }
8828
- // if we're still using the same promiseRef
8829
- if (promiseRef && promiseRef === this._currentPromise) {
8830
- // clear the config to reshow the view
8831
- this.setConfig(undefined);
8832
- // clear the current promise
8833
- this._currentPromise = undefined;
8834
- }
8835
- if (error != null) {
8836
- if (error instanceof Error) {
8837
- throw error;
8838
- }
8839
- let message;
8840
- if (error == null) {
8841
- message = String(error);
8842
- }
8843
- else if (typeof error === 'object') {
8844
- message = JSON.stringify(error);
8845
- }
8846
- else {
8847
- message = String(error);
8848
- }
8849
- throw new Error(message);
8850
- }
8851
- return result;
8852
- }
8853
- /**
8854
- * {@inheritDoc DbxInjectionContext.resetContext}
8855
- *
8856
- * @returns `true` if an active context was cleared, `false` otherwise.
8857
- */
8858
- resetContext() {
8859
- let clearedValue = false;
8860
- if (this._currentPromise) {
8861
- const promise = this._currentPromise;
8862
- // clear the current promise too
8863
- this._currentPromise = undefined;
8864
- // clear the config.
8865
- this.setConfig(undefined);
8866
- // send a rejection signal to bail out.
8867
- promise.reject(new Error('dbxInjectionContext bailout'));
8868
- clearedValue = true;
8869
- }
8870
- return clearedValue;
8871
- }
8872
- /**
8873
- * Sets or clears the active component configuration.
8874
- *
8875
- * When a config is provided, the original embedded view is detached and the component is injected.
8876
- * When `undefined` is provided, the injected component is removed and the original view is re-attached.
8877
- *
8878
- * @param config - The component config to display, or `undefined` to restore the original content.
8879
- */
8880
- setConfig(config) {
8881
- let reattach = false;
8882
- if (config) {
8883
- if (!this._isDetached) {
8884
- // detach the original view before setting config.
8885
- this._viewContainer.detach();
8886
- this._isDetached = true;
8887
- }
8888
- }
8889
- else if (this._isDetached) {
8890
- reattach = true;
8891
- }
8892
- this._instance.config = config;
8893
- if (reattach) {
8894
- this._viewContainer.insert(this._embeddedView);
8895
- this._isDetached = false;
8896
- }
8810
+ function provideDbxFilterStorage(config) {
8811
+ const providers = [DbxFilterStorageService];
8812
+ if (config) {
8813
+ providers.push({
8814
+ provide: DBX_FILTER_STORAGE_SERVICE_CONFIG_TOKEN,
8815
+ useValue: config
8816
+ });
8897
8817
  }
8898
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8899
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: DbxInjectionContextDirective, isStandalone: true, selector: "[dbxInjectionContext]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, providers: provideDbxInjectionContext(DbxInjectionContextDirective), ngImport: i0 });
8818
+ return makeEnvironmentProviders(providers);
8900
8819
  }
8901
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionContextDirective, decorators: [{
8902
- type: Directive,
8903
- args: [{
8904
- selector: '[dbxInjectionContext]',
8905
- providers: provideDbxInjectionContext(DbxInjectionContextDirective)
8906
- }]
8907
- }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
8908
8820
 
8909
8821
  /**
8910
- * Abstract directive that delegates {@link DbxInjectionContext} operations to a host-level
8911
- * `DbxInjectionContext` obtained via Angular's dependency injection (`{ host: true }`).
8822
+ * Injection token used to provide arbitrary data to dynamically created components.
8912
8823
  *
8913
- * This is useful for creating specialized injection context subtypes that can be injected
8914
- * by their own token while still forwarding the actual show/reset behavior to a parent
8915
- * {@link DbxInjectionContextDirective}.
8824
+ * Components created via {@link DbxInjectionComponentConfig} can inject this token
8825
+ * to access the `data` property from their configuration.
8916
8826
  *
8917
8827
  * @example
8918
8828
  * ```typescript
8919
- * @Directive({ providers: [{ provide: MySpecialContext, useExisting: MyForwardDirective }] })
8920
- * class MyForwardDirective extends AbstractForwardDbxInjectionContextDirective {}
8829
+ * // In a dynamically injected component:
8830
+ * readonly data = inject(DBX_INJECTION_COMPONENT_DATA);
8921
8831
  * ```
8922
- *
8923
- * @see {@link DbxInjectionContext}
8924
- * @see {@link DbxInjectionContextDirective}
8925
8832
  */
8926
- class AbstractForwardDbxInjectionContextDirective {
8927
- /**
8928
- * The host-level {@link DbxInjectionContext} that all operations are forwarded to.
8929
- */
8930
- dbxInjectionContext = inject(DbxInjectionContext, { host: true });
8931
- // MARK: DbxInjectionContext
8932
- /**
8933
- * {@inheritDoc DbxInjectionContext.showContext}
8934
- *
8935
- * @param config - The injection context configuration to forward to the host context.
8936
- * @returns Resolves to the output of the injected component's usage.
8937
- */
8938
- showContext(config) {
8939
- return this.dbxInjectionContext.showContext(config);
8940
- }
8941
- /**
8942
- * {@inheritDoc DbxInjectionContext.resetContext}
8943
- *
8944
- * @returns `true` if an active context was cleared, `false` otherwise.
8945
- */
8946
- resetContext() {
8947
- return this.dbxInjectionContext.resetContext();
8948
- }
8949
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractForwardDbxInjectionContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8950
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: AbstractForwardDbxInjectionContextDirective, isStandalone: true, ngImport: i0 });
8951
- }
8952
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractForwardDbxInjectionContextDirective, decorators: [{
8953
- type: Directive
8954
- }] });
8955
-
8833
+ const DBX_INJECTION_COMPONENT_DATA = new InjectionToken('DbxInjectionComponentConfigData');
8956
8834
  /**
8957
- * Creates an RxJS operator that switches between injection component configs, falling back to a
8958
- * default config when the source emits `null`, `undefined`, or `true`.
8959
- *
8960
- * If the `defaultConfig` resolves to a class (function), it is automatically wrapped into a
8961
- * `{ componentClass }` config object.
8835
+ * Compares two {@link DbxInjectionComponentConfig} values for structural equality, safely handling nullish values.
8962
8836
  *
8963
- * This is useful for reactive streams where an upstream value may indicate "use the default component"
8964
- * rather than providing an explicit configuration.
8837
+ * Compares `componentClass`, `data`, `init`, and `injector` by reference. Does NOT compare
8838
+ * `providers` or `ngModuleRef`, as provider arrays typically change on every list data update
8839
+ * (e.g., with new `DBX_VALUE_LIST_VIEW_ITEM` values) without requiring component recreation.
8965
8840
  *
8966
- * @param defaultConfig - A static value, getter, or component class to use as the fallback config.
8967
- * @returns An RxJS operator compatible with `pipe()`.
8841
+ * When both values are nullish, uses strict equality (`null === null` is `true`,
8842
+ * `null === undefined` is `false`). The comparator is only invoked when both are non-nullish.
8843
+ */
8844
+ const dbxInjectionComponentConfigIsEqual = safeEqualityComparatorFunction((a, b) => a.componentClass === b.componentClass && a.data === b.data && a.init === b.init && a.injector === b.injector);
8845
+ /**
8846
+ * Merges multiple partial {@link DbxInjectionComponentConfig} objects into a single configuration.
8968
8847
  *
8969
- * @see {@link DbxInjectionComponentConfig}
8970
- * @typeParam T - The specific {@link DbxInjectionComponentConfig} subtype.
8971
- * @typeParam X - The component type.
8848
+ * Provider arrays are concatenated so that all providers from all configs are preserved.
8849
+ * All other properties are merged with later values taking precedence.
8972
8850
  *
8973
- * @example
8974
- * ```typescript
8975
- * config$.pipe(
8976
- * switchMapDbxInjectionComponentConfig(MyDefaultComponent)
8977
- * ).subscribe(config => {
8978
- * // config is always a DbxInjectionComponentConfig or undefined
8979
- * });
8980
- * ```
8851
+ * @param configs - The partial configs to merge. May contain `undefined`/`null` entries which are filtered out.
8852
+ * @returns A single merged partial configuration.
8981
8853
  */
8982
- function switchMapDbxInjectionComponentConfig(defaultConfig) {
8983
- const defaultAsGetter = asGetter(defaultConfig);
8984
- return switchMapObject({
8985
- defaultGetter: () => {
8986
- const value = defaultAsGetter();
8987
- // if value is a function, it is a component class
8988
- return typeof value === 'function' ? { componentClass: value } : value;
8989
- }
8990
- });
8854
+ function mergeDbxInjectionComponentConfigs(configs) {
8855
+ const providers = mergeArrays(filterMaybeArrayValues(configs).map((x) => x.providers));
8856
+ const result = mergeObjects(configs);
8857
+ result.providers = providers;
8858
+ return result;
8991
8859
  }
8992
8860
 
8993
8861
  /**
8994
- * Creates a child injector with the given `type` as its sole provider and immediately
8995
- * resolves an instance of that type.
8996
- *
8997
- * This is a convenience for one-off instantiation of an injectable class using a specific
8998
- * parent injector, without needing to manually configure `Injector.create()`.
8862
+ * Flattens and merges multiple provider sources into a single `StaticProvider[]` array.
8999
8863
  *
9000
- * @param type - The injectable class to provide and resolve.
9001
- * @param parent - The parent injector that supplies the type's dependencies.
9002
- * @returns A new instance of `T`.
8864
+ * Each argument can be a single `StaticProvider`, an array of providers, or `undefined`/`null`.
8865
+ * All values are flattened into a single array with nullish entries removed.
9003
8866
  *
9004
- * @typeParam T - The type to instantiate.
8867
+ * @param providers - Any number of provider values or arrays to merge.
8868
+ * @returns A flat array of all non-nullish static providers.
9005
8869
  *
9006
8870
  * @example
9007
8871
  * ```typescript
9008
- * const service = newWithInjector(MyService, parentInjector);
9009
- * ```
9010
- */
9011
- function newWithInjector(type, parent) {
9012
- const injector = Injector.create({ providers: [type], parent });
9013
- return injector.get(type);
8872
+ * const providers = mergeStaticProviders(
8873
+ * { provide: TOKEN_A, useValue: 'a' },
8874
+ * undefined,
8875
+ * [{ provide: TOKEN_B, useValue: 'b' }]
8876
+ * );
8877
+ * // Result: [{ provide: TOKEN_A, useValue: 'a' }, { provide: TOKEN_B, useValue: 'b' }]
8878
+ * ```
8879
+ */
8880
+ function mergeStaticProviders(...providers) {
8881
+ return flattenArrayOrValueArray(providers);
9014
8882
  }
9015
-
9016
8883
  /**
9017
- * Abstract NgRx `ComponentStore` extension that integrates a {@link LockSet} for coordinating
9018
- * async operations and delays destruction until all locks are released.
8884
+ * Builds an element injector from a {@link DbxInjectionComponentConfig},
8885
+ * merging the config's providers and {@link DBX_INJECTION_COMPONENT_DATA} into a child injector.
9019
8886
  *
9020
- * Subclasses call {@link setupLockSet} to register parent lock sets and named locks.
9021
- * On destroy, the store waits for all locks to unlock before completing cleanup,
9022
- * preventing premature teardown of in-flight operations.
8887
+ * Returns the config's injector (or parent) unchanged if no providers or data are specified.
9023
8888
  *
9024
- * @typeParam S - The shape of the component store's state object.
8889
+ * @param params - The config and parent injector to build from.
8890
+ * @returns A child {@link Injector} with the config's providers and data, or the parent injector if none are specified.
9025
8891
  *
9026
8892
  * @example
9027
8893
  * ```typescript
9028
- * interface MyState { items: string[]; }
9029
- *
9030
- * @Injectable()
9031
- * export class MyStore extends LockSetComponentStore<MyState> {
9032
- * constructor() {
9033
- * super({ items: [] });
9034
- * }
9035
- * }
8894
+ * const injector = createInjectorForInjectionComponentConfig({
8895
+ * config: { componentClass: MyComponent, data: { id: 1 }, providers: [myProvider] },
8896
+ * parentInjector: this._injector
8897
+ * });
9036
8898
  * ```
8899
+ * @__NO_SIDE_EFFECTS__
9037
8900
  */
9038
- class LockSetComponentStore extends ComponentStore {
9039
- initialState;
9040
- lockSet = new LockSet();
9041
- lockSetDestroyDelayMs = 2000;
9042
- lockSetDestroyTimeoutMs;
9043
- // NOTE: Injection does not occur here, but we need @Injectable to compile properly for Angular usage
9044
- // eslint-disable-next-line @angular-eslint/prefer-inject
9045
- constructor(initialState) {
9046
- super(initialState);
9047
- this.initialState = initialState;
9048
- }
9049
- // MARK: State Changes
9050
- resetStore = this.updater(() => ({ ...this.initialState }));
9051
- // MARK: Locks
9052
- setupLockSet({ parent, locks }) {
9053
- if (parent) {
9054
- this.setParentLockSet(parent);
9055
- }
9056
- if (locks) {
9057
- for (const key in locks) {
9058
- if (locks[key]) {
9059
- this.addLock(key, locks[key]);
9060
- }
9061
- }
9062
- }
9063
- }
9064
- setParentLockSet(obs) {
9065
- this.lockSet.setParentLockSet(asObservable(obs).pipe(map((x) => x?.lockSet)));
9066
- }
9067
- addLock(key, obs) {
9068
- this.lockSet.addLock(key, obs);
9069
- }
9070
- // MARK: Cleanup
9071
- ngOnDestroy() {
9072
- // Wait for any actions to complete before destroying.
9073
- this.lockSet.destroyOnNextUnlock({
9074
- fn: () => {
9075
- this._destroyNow();
9076
- },
9077
- timeout: this.lockSetDestroyTimeoutMs
9078
- }, this.lockSetDestroyDelayMs);
8901
+ function createInjectorForInjectionComponentConfig(params) {
8902
+ const { config, parentInjector } = params;
8903
+ const { injector: inputInjector, providers, data } = config;
8904
+ const parent = inputInjector ?? parentInjector;
8905
+ let result;
8906
+ if (!providers && data == null) {
8907
+ result = parent;
9079
8908
  }
9080
- /**
9081
- * Immediately completes cleanup by destroying the lock set.
9082
- * Called after all locks are released during the deferred destruction process.
9083
- */
9084
- _destroyNow() {
9085
- this.lockSet.destroy();
8909
+ else {
8910
+ result = Injector.create({
8911
+ parent,
8912
+ providers: mergeStaticProviders({ provide: DBX_INJECTION_COMPONENT_DATA, useValue: data }, providers)
8913
+ });
9086
8914
  }
9087
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore, deps: [{ token: null, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
9088
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore });
8915
+ return result;
9089
8916
  }
9090
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore, decorators: [{
9091
- type: Injectable
9092
- }], ctorParameters: () => [{ type: undefined, decorators: [{
9093
- type: Inject,
9094
- args: [null]
9095
- }, {
9096
- type: Optional
9097
- }] }] });
9098
-
9099
8917
  /**
9100
- * Default {@link SimpleStorageAccessorConverter} that uses `JSON.stringify`/`JSON.parse` for conversion.
8918
+ * Runs the init callback from a {@link DbxInjectionComponentConfig} on the given component ref,
8919
+ * executing within the component's injection context so that `inject()` is available inside the callback.
9101
8920
  *
9102
- * @typeParam T - The type of value being converted.
8921
+ * @param componentRef - The component reference whose instance will be passed to the init callback.
8922
+ * @param config - The injection component config containing the optional init callback.
8923
+ *
8924
+ * @example
8925
+ * ```typescript
8926
+ * const ref = viewContainerRef.createComponent(config.componentClass, { injector });
8927
+ * initInjectionComponent(ref, config);
8928
+ * ```
9103
8929
  */
9104
- class StringifySimpleStorageAccessorConverter {
9105
- stringifyValue(value) {
9106
- return JSON.stringify(value);
9107
- }
9108
- parseValue(data) {
9109
- return JSON.parse(data);
8930
+ function initInjectionComponent(componentRef, config) {
8931
+ const { init } = config;
8932
+ if (init) {
8933
+ runInInjectionContext(componentRef.injector, () => init(componentRef.instance));
9110
8934
  }
9111
8935
  }
8936
+
9112
8937
  /**
9113
- * Composes a {@link StorageAccessor} and a {@link SimpleStorageAccessorConverter} into a single
9114
- * {@link SimpleStorageAccessorDelegate} implementation.
8938
+ * Core runtime engine for the dbx-injection system. Manages the lifecycle of dynamically injected
8939
+ * components and templates within an Angular `ViewContainerRef`.
9115
8940
  *
9116
- * @typeParam T - The type of value being stored.
8941
+ * This class reactively listens to configuration and template observables. When a new
8942
+ * {@link DbxInjectionComponentConfig} or {@link DbxInjectionTemplateConfig} is emitted, it tears
8943
+ * down the previous content and creates the new component or template in the target view container.
8944
+ *
8945
+ * Component configs take precedence over template configs when both are provided.
8946
+ *
8947
+ * Typically used internally by {@link AbstractDbxInjectionDirective} and {@link DbxInjectionComponent}
8948
+ * rather than consumed directly.
8949
+ *
8950
+ * @typeParam T - The type of the dynamically created component.
8951
+ *
8952
+ * @see {@link DbxInjectionComponentConfig}
8953
+ * @see {@link DbxInjectionTemplateConfig}
8954
+ * @see {@link AbstractDbxInjectionDirective}
9117
8955
  */
9118
- class WrapperSimpleStorageAccessorDelegate {
9119
- _delegate;
9120
- _converter;
9121
- constructor(delegate, converter) {
9122
- this._delegate = delegate;
9123
- this._converter = converter;
8956
+ class DbxInjectionInstance {
8957
+ _subscriptionObject = new SubscriptionObject();
8958
+ _config = new BehaviorSubject(undefined);
8959
+ _template = new BehaviorSubject(undefined);
8960
+ _content = new BehaviorSubject(undefined);
8961
+ _componentRef = new BehaviorSubject(undefined);
8962
+ _injector;
8963
+ config$ = this._config.pipe(maybeValueFromObservableOrValueGetter(), distinctUntilChanged(dbxInjectionComponentConfigIsEqual), shareReplay(1));
8964
+ template$ = this._template.pipe(maybeValueFromObservableOrValueGetter(), distinctUntilChanged(), shareReplay(1));
8965
+ content$ = this._content.pipe(filterMaybe(), distinctUntilChanged(), shareReplay(1));
8966
+ get config() {
8967
+ return this._config.value;
9124
8968
  }
9125
- get(key) {
9126
- return this._delegate.get(key);
8969
+ set config(config) {
8970
+ this._config.next(config);
9127
8971
  }
9128
- set(key, value) {
9129
- return this._delegate.set(key, value);
8972
+ get template() {
8973
+ return this._template.value;
9130
8974
  }
9131
- remove(key) {
9132
- return this._delegate.remove(key);
8975
+ set template(template) {
8976
+ this._template.next(template);
9133
8977
  }
9134
- clear() {
9135
- return this._delegate.clear();
8978
+ get content() {
8979
+ return this._content.value;
9136
8980
  }
9137
- all(prefix) {
9138
- return this._delegate.all(prefix);
8981
+ set content(content) {
8982
+ this._content.next(content);
9139
8983
  }
9140
- allKeys(prefix) {
9141
- return this._delegate.allKeys(prefix);
8984
+ get componentRef() {
8985
+ return this._componentRef.value;
9142
8986
  }
9143
- stringifyValue(value) {
9144
- return this._converter.stringifyValue(value);
8987
+ set componentRef(componentRef) {
8988
+ this._componentRef.next(componentRef);
9145
8989
  }
9146
- parseValue(data) {
9147
- return this._converter.parseValue(data);
8990
+ constructor(injector) {
8991
+ this._injector = injector;
8992
+ }
8993
+ init() {
8994
+ // Wait until the first of either of the two inputs comes in as not defined, and then emit.
8995
+ // We filter the first maybe here between the two items.
8996
+ const configTemplateObs = combineLatest([this.config$, this.template$]).pipe(map(([config, template]) => {
8997
+ return config || template ? { config, template } : undefined;
8998
+ }), skipAllInitialMaybe());
8999
+ this._subscriptionObject.subscription = combineLatest([configTemplateObs, this.content$]).subscribe(([inputConfig, content]) => {
9000
+ const { config, template } = inputConfig ?? {};
9001
+ this._reset(content);
9002
+ if (config) {
9003
+ this._initComponent(config, content);
9004
+ }
9005
+ else if (template) {
9006
+ this._initTemplate(template, content);
9007
+ }
9008
+ });
9009
+ }
9010
+ destroy() {
9011
+ this._subscriptionObject.destroy();
9012
+ this._config.complete();
9013
+ this._template.complete();
9014
+ this._content.complete();
9015
+ this._componentRef.complete();
9016
+ }
9017
+ _initComponent(config, content) {
9018
+ content.clear();
9019
+ const { componentClass, ngModuleRef } = config;
9020
+ if (!componentClass) {
9021
+ throw new Error('DbxInjectionInstance: componentClass was expected in the config but it was unavailable.');
9022
+ }
9023
+ const injector = createInjectorForInjectionComponentConfig({ config, parentInjector: this._injector });
9024
+ const componentRef = content.createComponent(componentClass, { injector, ngModuleRef });
9025
+ initInjectionComponent(componentRef, config);
9026
+ this.componentRef = componentRef;
9027
+ }
9028
+ _initTemplate(config, content) {
9029
+ content.clear();
9030
+ const { templateRef, viewRef } = config;
9031
+ if (templateRef) {
9032
+ content.createEmbeddedView(templateRef);
9033
+ // TODO: Figure out if these items need to be destroyed or not when this item is destroyed. If so, we need a reference to destroy.
9034
+ }
9035
+ else if (viewRef) {
9036
+ content.insert(viewRef);
9037
+ }
9038
+ }
9039
+ _reset(content) {
9040
+ if (this.componentRef) {
9041
+ content.clear();
9042
+ this.componentRef = undefined;
9043
+ }
9148
9044
  }
9149
9045
  }
9046
+
9150
9047
  /**
9151
- * Validates that a storage key prefix is non-empty and does not contain the prefix splitter character.
9048
+ * Abstract base directive for dynamically injecting components or templates into a view.
9152
9049
  *
9153
- * @param prefix - The prefix string to validate.
9154
- * @param prefixSplitter - The splitter string that separates the prefix from the key.
9155
- * @throws {Error} If the prefix is invalid or the splitter is empty.
9050
+ * Manages a {@link DbxInjectionInstance} lifecycle, initializing it on `ngOnInit` and
9051
+ * destroying it on `ngOnDestroy`. Subclasses are responsible for wiring their inputs
9052
+ * (config, template, content) to the corresponding setter methods.
9053
+ *
9054
+ * @typeParam T - The type of the dynamically created component.
9055
+ *
9056
+ * @see {@link DbxInjectionComponent} - The concrete standalone component implementation.
9057
+ * @see {@link DbxInjectionInstance}
9156
9058
  */
9157
- function assertValidStorageKeyPrefix(prefix, prefixSplitter) {
9158
- if (!prefixSplitter) {
9159
- throw new Error('Invalid storage key prefix splitter. Must be defined and not empty.'); // TODO(FUTURE): Consider changing to a concrete error type
9059
+ class AbstractDbxInjectionDirective {
9060
+ _instance = new DbxInjectionInstance(inject(Injector));
9061
+ ngOnInit() {
9062
+ this._instance.init();
9160
9063
  }
9161
- if (!isValidStorageKeyPrefix(prefix, prefixSplitter)) {
9162
- throw new Error('Invalid storage key prefix.');
9064
+ ngOnDestroy() {
9065
+ this._instance.destroy();
9163
9066
  }
9067
+ /**
9068
+ * Sets the component injection configuration on the underlying {@link DbxInjectionInstance}.
9069
+ *
9070
+ * @param config - The component config, observable, or getter to use.
9071
+ */
9072
+ setConfig(config) {
9073
+ this._instance.config = config;
9074
+ }
9075
+ /**
9076
+ * Sets the template injection configuration on the underlying {@link DbxInjectionInstance}.
9077
+ *
9078
+ * @param template - The template config, observable, or getter to use.
9079
+ */
9080
+ setTemplate(template) {
9081
+ this._instance.template = template;
9082
+ }
9083
+ /**
9084
+ * Sets the target `ViewContainerRef` where dynamic content will be inserted.
9085
+ *
9086
+ * @param content - The view container reference for content projection.
9087
+ */
9088
+ setContent(content) {
9089
+ this._instance.content = content;
9090
+ }
9091
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractDbxInjectionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9092
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: AbstractDbxInjectionDirective, isStandalone: true, ngImport: i0 });
9164
9093
  }
9094
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractDbxInjectionDirective, decorators: [{
9095
+ type: Directive
9096
+ }] });
9097
+
9165
9098
  /**
9166
- * Checks whether a storage key prefix is valid (non-empty and does not contain the splitter).
9099
+ * Standalone component for dynamically injecting a component or template into the DOM.
9167
9100
  *
9168
- * @param prefix - The prefix string to validate.
9169
- * @param prefixSpltter - The splitter string that separates the prefix from the key.
9170
- * @returns `true` if the prefix is valid.
9101
+ * Accepts a {@link DbxInjectionComponentConfig} (to create a component) or a
9102
+ * {@link DbxInjectionTemplateConfig} (to embed a template/view). When the config changes,
9103
+ * the previous content is destroyed and the new content is created in its place.
9104
+ *
9105
+ * Can be used as an element (`<dbx-injection>`) or as an attribute directive
9106
+ * (`[dbxInjection]` / `[dbx-injection]`).
9107
+ *
9108
+ * @typeParam T - The type of the dynamically created component.
9109
+ *
9110
+ * @example
9111
+ * ```html
9112
+ * <!-- Element usage with a component config -->
9113
+ * <dbx-injection [config]="myComponentConfig"></dbx-injection>
9114
+ *
9115
+ * <!-- Element usage with a template config -->
9116
+ * <dbx-injection [template]="myTemplateConfig"></dbx-injection>
9117
+ *
9118
+ * <!-- Attribute usage -->
9119
+ * <div dbxInjection [config]="myComponentConfig"></div>
9120
+ * ```
9121
+ *
9122
+ * @see {@link DbxInjectionComponentConfig}
9123
+ * @see {@link DbxInjectionTemplateConfig}
9124
+ * @see {@link AbstractDbxInjectionDirective}
9171
9125
  */
9172
- function isValidStorageKeyPrefix(prefix, prefixSpltter) {
9173
- return Boolean(prefix?.indexOf(prefixSpltter) === -1);
9126
+ class DbxInjectionComponent extends AbstractDbxInjectionDirective {
9127
+ /**
9128
+ * Reference to the internal view container where dynamic content is projected.
9129
+ */
9130
+ content = viewChild('content', { ...(ngDevMode ? { debugName: "content" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
9131
+ /**
9132
+ * The component injection configuration. Accepts an observable, getter, or static value.
9133
+ */
9134
+ config = input(/* @ts-ignore */
9135
+ ...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
9136
+ /**
9137
+ * The template injection configuration. Accepts an observable, getter, or static value.
9138
+ * Only used when `config` is not provided.
9139
+ */
9140
+ template = input(/* @ts-ignore */
9141
+ ...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ []));
9142
+ // allow signal writes for each as during their initialization they may write to a signal in some cases when initializing
9143
+ _contentEffect = effect(() => this.setContent(this.content()), /* @ts-ignore */
9144
+ ...(ngDevMode ? [{ debugName: "_contentEffect" }] : /* istanbul ignore next */ []));
9145
+ _configEffect = effect(() => this.setConfig(this.config()), /* @ts-ignore */
9146
+ ...(ngDevMode ? [{ debugName: "_configEffect" }] : /* istanbul ignore next */ []));
9147
+ _templateEffect = effect(() => this.setTemplate(this.template()), /* @ts-ignore */
9148
+ ...(ngDevMode ? [{ debugName: "_templateEffect" }] : /* istanbul ignore next */ []));
9149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
9150
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.4", type: DbxInjectionComponent, isStandalone: true, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, read: ViewContainerRef, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
9151
+ <ng-template #content></ng-template>
9152
+ `, isInline: true });
9174
9153
  }
9154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionComponent, decorators: [{
9155
+ type: Component,
9156
+ args: [{
9157
+ selector: 'dbx-injection, [dbxInjection], [dbx-injection]',
9158
+ template: `
9159
+ <ng-template #content></ng-template>
9160
+ `,
9161
+ imports: []
9162
+ }]
9163
+ }], propDecorators: { content: [{ type: i0.ViewChild, args: ['content', { ...{ read: ViewContainerRef }, isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }] } });
9164
+
9175
9165
  /**
9176
- * Full-featured {@link StorageAccessor} that adds key namespacing, JSON serialization,
9177
- * and optional time-based expiration on top of a raw string storage backend.
9166
+ * Renders a list of dynamically injected components from an array of {@link DbxInjectionArrayEntry} items.
9178
9167
  *
9179
- * Values are stored as JSON with metadata (timestamp) and automatically checked
9180
- * for expiration on read.
9168
+ * Each entry is tracked by its `key` property for efficient change detection, and rendered
9169
+ * using a nested {@link DbxInjectionComponent}.
9181
9170
  *
9182
- * @typeParam T - The type of values stored.
9171
+ * @example
9172
+ * ```html
9173
+ * <dbx-injection-array [entries]="myEntries" />
9174
+ * ```
9183
9175
  *
9184
- * @throws {DataDoesNotExistError} When reading a key that does not exist.
9185
- * @throws {DataIsExpiredError} When reading a key whose stored data has expired.
9176
+ * @example
9177
+ * ```typescript
9178
+ * // In the host component:
9179
+ * myEntries: DbxInjectionArrayEntry[] = [
9180
+ * { key: 'chart', injectionConfig: { componentClass: ChartComponent } },
9181
+ * { key: 'table', injectionConfig: { componentClass: TableComponent } }
9182
+ * ];
9183
+ * ```
9184
+ *
9185
+ * @see {@link DbxInjectionArrayEntry}
9186
+ * @see {@link DbxInjectionComponent}
9187
+ */
9188
+ class DbxInjectionArrayComponent {
9189
+ /**
9190
+ * The array of keyed injection entries to render. Each entry produces a `<dbx-injection>` component.
9191
+ */
9192
+ entries = input(undefined, /* @ts-ignore */
9193
+ ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
9194
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionArrayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9195
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: DbxInjectionArrayComponent, isStandalone: true, selector: "dbx-injection-array", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
9196
+ @for (entry of entries(); track entry.key) {
9197
+ <dbx-injection [config]="entry.injectionConfig"></dbx-injection>
9198
+ }
9199
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }] });
9200
+ }
9201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionArrayComponent, decorators: [{
9202
+ type: Component,
9203
+ args: [{
9204
+ selector: 'dbx-injection-array',
9205
+ template: `
9206
+ @for (entry of entries(); track entry.key) {
9207
+ <dbx-injection [config]="entry.injectionConfig"></dbx-injection>
9208
+ }
9209
+ `,
9210
+ imports: [DbxInjectionComponent]
9211
+ }]
9212
+ }], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }] } });
9213
+
9214
+ /**
9215
+ * Abstract service for temporarily replacing a view's content with a dynamically injected component,
9216
+ * then restoring the original content when done.
9217
+ *
9218
+ * Unlike `*ngIf` or `*ngSwitch`, the original child content is **hidden** rather than destroyed,
9219
+ * preserving component state. Once the injected context's promise resolves (or is reset), the
9220
+ * original content is re-displayed.
9221
+ *
9222
+ * This is useful for overlay-like workflows such as inline editors, confirmation dialogs,
9223
+ * or multi-step flows where destroying and recreating the underlying view would lose state.
9224
+ *
9225
+ * @see {@link DbxInjectionContextConfig}
9226
+ * @see {@link DbxInjectionContextDirective} - The concrete structural directive implementation.
9227
+ */
9228
+ class DbxInjectionContext {
9229
+ }
9230
+ /**
9231
+ * Creates Angular providers that register a concrete {@link DbxInjectionContext} implementation
9232
+ * under the abstract `DbxInjectionContext` token via `useExisting`.
9233
+ *
9234
+ * This enables dependency injection consumers to request `DbxInjectionContext` and receive
9235
+ * the specific directive or service implementation.
9236
+ *
9237
+ * @param type - The concrete class to register as the existing provider.
9238
+ * @returns Array of Angular providers.
9239
+ *
9240
+ * @typeParam T - The concrete type that extends {@link DbxInjectionContext}.
9241
+ */
9242
+ function provideDbxInjectionContext(type) {
9243
+ return [
9244
+ {
9245
+ provide: DbxInjectionContext,
9246
+ useExisting: type
9247
+ }
9248
+ ];
9249
+ }
9250
+
9251
+ /**
9252
+ * Structural directive that implements {@link DbxInjectionContext}, allowing its host content
9253
+ * to be temporarily replaced with a dynamically injected component and then restored.
9254
+ *
9255
+ * Unlike `*ngIf` or `*ngSwitch`, the original child content is **detached** (hidden) rather than
9256
+ * destroyed, so component state is preserved while the injected context is active. When the
9257
+ * context's promise resolves or {@link resetContext} is called, the original view is re-attached.
9258
+ *
9259
+ * The directive also accepts an optional `[dbxInjectionContext]` input to set a static component
9260
+ * config directly, which replaces the default content until cleared.
9261
+ *
9262
+ * @typeParam O - The output type produced by {@link showContext}.
9263
+ *
9264
+ * @example
9265
+ * ```html
9266
+ * <!-- Wrap content that should be temporarily replaceable -->
9267
+ * <div *dbxInjectionContext>
9268
+ * <p>Original content preserved while context is active</p>
9269
+ * </div>
9270
+ * ```
9186
9271
  *
9187
9272
  * @example
9188
9273
  * ```typescript
9189
- * const accessor = factory.createStorageAccessor<UserSettings>({
9190
- * prefix: 'user_prefs',
9191
- * expiresIn: 3600000,
9274
+ * // Programmatically show a temporary editor overlay:
9275
+ * const result = await injectionContext.showContext({
9276
+ * config: { componentClass: InlineEditorComponent },
9277
+ * use: (editor) => editor.waitForSave()
9192
9278
  * });
9193
- *
9194
- * accessor.set('theme', { dark: true }).subscribe();
9195
- * accessor.get('theme').subscribe(settings => console.log(settings));
9196
9279
  * ```
9280
+ *
9281
+ * @see {@link DbxInjectionContext}
9282
+ * @see {@link DbxInjectionContextConfig}
9197
9283
  */
9198
- class SimpleStorageAccessor {
9199
- static PREFIX_SPLITTER = '::';
9200
- _delegate;
9201
- _config;
9202
- constructor(delegate, config) {
9203
- const prefix = config.prefix;
9204
- const prefixSplitter = config.prefixSplitter ?? SimpleStorageAccessor.PREFIX_SPLITTER;
9205
- assertValidStorageKeyPrefix(prefix, prefixSplitter);
9206
- const fullPrefix = `${prefix}${prefixSplitter}`;
9207
- this._delegate = delegate;
9208
- this._config = {
9209
- ...config,
9210
- prefixSplitter,
9211
- fullPrefix
9284
+ class DbxInjectionContextDirective {
9285
+ _injector = inject(Injector);
9286
+ _templateRef = inject((TemplateRef));
9287
+ _viewContainer = inject(ViewContainerRef);
9288
+ _instance = new DbxInjectionInstance(this._injector);
9289
+ _currentPromise;
9290
+ _embeddedView;
9291
+ _isDetached = false;
9292
+ /**
9293
+ * Optional static component config input. When set, the directive replaces its content
9294
+ * with the specified component. When cleared (`undefined`), the original content is restored.
9295
+ */
9296
+ config = input(/* @ts-ignore */
9297
+ ...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
9298
+ _configEffect = effect(() => {
9299
+ this.setConfig(this.config());
9300
+ // NOTE: we have/call setConfig() because the effect() may not respond to all value changes,
9301
+ // especially when setConfig() ends up being called twice quickly in quick succession.
9302
+ }, /* @ts-ignore */
9303
+ ...(ngDevMode ? [{ debugName: "_configEffect" }] : /* istanbul ignore next */ []));
9304
+ ngOnInit() {
9305
+ this._instance.content = this._viewContainer;
9306
+ this._instance.init();
9307
+ this._embeddedView = this._viewContainer.createEmbeddedView(this._templateRef);
9308
+ // detach the embedded view
9309
+ this._viewContainer.detach();
9310
+ // reattach it through the template configuration.
9311
+ // the template configuration acts as the default.
9312
+ this._instance.template = {
9313
+ viewRef: this._embeddedView
9212
9314
  };
9213
9315
  }
9214
- get delegate() {
9215
- return this._delegate;
9216
- }
9217
- get config() {
9218
- return this._config;
9316
+ ngOnDestroy() {
9317
+ this._instance.destroy();
9318
+ this._embeddedView?.destroy(); // destroy our embedded view too if it is set.
9219
9319
  }
9220
- // MARK: LimitedStorageAccessor
9221
- get(inputKey) {
9222
- const storeKey = this.makeStorageKey(inputKey);
9223
- return this._delegate.get(storeKey).pipe(map((storedData) => {
9224
- let result;
9225
- if (storedData) {
9226
- const readStoredData = this.readStoredData(storedData);
9227
- if (readStoredData.expired) {
9228
- throw new DataIsExpiredError(readStoredData);
9229
- }
9230
- else {
9231
- result = readStoredData.convertedData;
9232
- }
9320
+ /**
9321
+ * {@inheritDoc DbxInjectionContext.showContext}
9322
+ *
9323
+ * @param config - The injection context configuration describing the component and its usage.
9324
+ * @returns Resolves to the output of the injected component's usage.
9325
+ */
9326
+ async showContext(config) {
9327
+ // clear the current context before showing something new.
9328
+ this.resetContext();
9329
+ let promiseRef;
9330
+ let result;
9331
+ let error;
9332
+ // wait for the promise to resolve and use to finish using that instance.
9333
+ try {
9334
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
9335
+ promiseRef = promiseReference(async (resolve, reject) => {
9336
+ const injectionConfig = {
9337
+ ...config.config,
9338
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
9339
+ init: async (instance) => {
9340
+ // init if available in the base config.
9341
+ if (config.config.init) {
9342
+ config.config.init(instance);
9343
+ }
9344
+ try {
9345
+ const result = (await config.use(instance));
9346
+ resolve(result);
9347
+ }
9348
+ catch (e) {
9349
+ reject(e);
9350
+ }
9351
+ }
9352
+ };
9353
+ this.setConfig(injectionConfig);
9354
+ });
9355
+ this._currentPromise = promiseRef;
9356
+ // await the promise
9357
+ result = await promiseRef.promise;
9358
+ }
9359
+ catch (e) {
9360
+ error = e;
9361
+ }
9362
+ // if we're still using the same promiseRef
9363
+ if (promiseRef && promiseRef === this._currentPromise) {
9364
+ // clear the config to reshow the view
9365
+ this.setConfig(undefined);
9366
+ // clear the current promise
9367
+ this._currentPromise = undefined;
9368
+ }
9369
+ if (error != null) {
9370
+ if (error instanceof Error) {
9371
+ throw error;
9233
9372
  }
9234
- else {
9235
- throw new DataDoesNotExistError();
9373
+ let message;
9374
+ if (error == null) {
9375
+ message = String(error);
9236
9376
  }
9237
- return result;
9238
- }));
9239
- }
9240
- set(inputKey, inputValue) {
9241
- const storeKey = this.makeStorageKey(inputKey);
9242
- const storeData = this.buildStoredData(inputValue);
9243
- const data = JSON.stringify(storeData);
9244
- return this._delegate.set(storeKey, data);
9245
- }
9246
- remove(key) {
9247
- const storeKey = this.makeStorageKey(key);
9248
- return this._delegate.remove(storeKey);
9249
- }
9250
- all() {
9251
- return this._delegate.all(this._config.fullPrefix).pipe(map((allStoredData) => {
9252
- return allStoredData
9253
- .map((storedData) => {
9254
- const readStoredData = this.readStoredData(storedData);
9255
- return readStoredData.expired ? null : readStoredData.convertedData;
9256
- })
9257
- .filter(hasNonNullValue);
9258
- }));
9259
- }
9260
- allKeys() {
9261
- return this._delegate.allKeys(this._config.fullPrefix).pipe(map((keys) => keys.map((x) => this.decodeStorageKey(x))));
9262
- }
9263
- clear() {
9264
- return this._delegate.clear();
9265
- }
9266
- // MARK: Stored Values
9267
- readStoredData(storedDataString) {
9268
- const storedData = JSON.parse(storedDataString);
9269
- const expired = this.isExpiredStoredData(storedData);
9270
- const convertedData = this._delegate.parseValue(storedData.data);
9271
- return {
9272
- ...storedData,
9273
- expired,
9274
- convertedData
9275
- };
9276
- }
9277
- buildStoredData(value) {
9278
- return {
9279
- storedAt: unixDateTimeSecondsNumberForNow(),
9280
- data: this.stringifyValue(value)
9281
- };
9282
- }
9283
- isExpiredStoredData(storeData) {
9284
- const expiresIn = this._config.expiresIn;
9285
- let result;
9286
- if (expiresIn) {
9287
- if (storeData.storedAt) {
9288
- result = isThrottled(expiresIn, storeData.storedAt);
9377
+ else if (typeof error === 'object') {
9378
+ message = JSON.stringify(error);
9289
9379
  }
9290
9380
  else {
9291
- result = true;
9381
+ message = String(error);
9292
9382
  }
9293
- }
9294
- else {
9295
- result = false;
9383
+ throw new Error(message);
9296
9384
  }
9297
9385
  return result;
9298
9386
  }
9299
- // MARK: Internal
9300
- makeStorageKey(key) {
9301
- return `${this._config.prefix}${this._config.prefixSplitter}${String(key)}`;
9302
- }
9303
- isKeyOfAccessor(storageKey) {
9304
- return storageKey.startsWith(this._config.fullPrefix);
9305
- }
9306
- decodeStorageKey(storageKey) {
9307
- const split = splitJoinRemainder(storageKey, this._config.prefixSplitter, 2);
9308
- return split[1];
9387
+ /**
9388
+ * {@inheritDoc DbxInjectionContext.resetContext}
9389
+ *
9390
+ * @returns `true` if an active context was cleared, `false` otherwise.
9391
+ */
9392
+ resetContext() {
9393
+ let clearedValue = false;
9394
+ if (this._currentPromise) {
9395
+ const promise = this._currentPromise;
9396
+ // clear the current promise too
9397
+ this._currentPromise = undefined;
9398
+ // clear the config.
9399
+ this.setConfig(undefined);
9400
+ // send a rejection signal to bail out.
9401
+ promise.reject(new Error('dbxInjectionContext bailout'));
9402
+ clearedValue = true;
9403
+ }
9404
+ return clearedValue;
9309
9405
  }
9310
- stringifyValue(value) {
9311
- return this._delegate.stringifyValue(value);
9406
+ /**
9407
+ * Sets or clears the active component configuration.
9408
+ *
9409
+ * When a config is provided, the original embedded view is detached and the component is injected.
9410
+ * When `undefined` is provided, the injected component is removed and the original view is re-attached.
9411
+ *
9412
+ * @param config - The component config to display, or `undefined` to restore the original content.
9413
+ */
9414
+ setConfig(config) {
9415
+ let reattach = false;
9416
+ if (config) {
9417
+ if (!this._isDetached) {
9418
+ // detach the original view before setting config.
9419
+ this._viewContainer.detach();
9420
+ this._isDetached = true;
9421
+ }
9422
+ }
9423
+ else if (this._isDetached) {
9424
+ reattach = true;
9425
+ }
9426
+ this._instance.config = config;
9427
+ if (reattach) {
9428
+ this._viewContainer.insert(this._embeddedView);
9429
+ this._isDetached = false;
9430
+ }
9312
9431
  }
9432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9433
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: DbxInjectionContextDirective, isStandalone: true, selector: "[dbxInjectionContext]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, providers: provideDbxInjectionContext(DbxInjectionContextDirective), ngImport: i0 });
9313
9434
  }
9435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: DbxInjectionContextDirective, decorators: [{
9436
+ type: Directive,
9437
+ args: [{
9438
+ selector: '[dbxInjectionContext]',
9439
+ providers: provideDbxInjectionContext(DbxInjectionContextDirective)
9440
+ }]
9441
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
9314
9442
 
9315
9443
  /**
9316
- * {@link StorageAccessor} implementation that wraps a {@link FullStorageObject} and stores raw strings.
9444
+ * Abstract directive that delegates {@link DbxInjectionContext} operations to a host-level
9445
+ * `DbxInjectionContext` obtained via Angular's dependency injection (`{ host: true }`).
9317
9446
  *
9318
- * Each operation completes synchronously within an observable wrapper.
9447
+ * This is useful for creating specialized injection context subtypes that can be injected
9448
+ * by their own token while still forwarding the actual show/reset behavior to a parent
9449
+ * {@link DbxInjectionContextDirective}.
9319
9450
  *
9320
9451
  * @example
9321
9452
  * ```typescript
9322
- * const accessor = new StringStorageAccessor(localStorage);
9323
- * accessor.set('key', 'value').subscribe();
9324
- * accessor.get('key').subscribe(value => console.log(value)); // 'value'
9453
+ * @Directive({ providers: [{ provide: MySpecialContext, useExisting: MyForwardDirective }] })
9454
+ * class MyForwardDirective extends AbstractForwardDbxInjectionContextDirective {}
9325
9455
  * ```
9456
+ *
9457
+ * @see {@link DbxInjectionContext}
9458
+ * @see {@link DbxInjectionContextDirective}
9326
9459
  */
9327
- class StringStorageAccessor {
9328
- _storage;
9329
- constructor(storage) {
9330
- this._storage = storage;
9331
- }
9332
- get(key) {
9333
- return new Observable((x) => {
9334
- const value = this._storage.getItem(key);
9335
- x.next(value);
9336
- x.complete();
9337
- });
9338
- }
9339
- set(key, value) {
9340
- return new Observable((x) => {
9341
- this._storage.setItem(key, value);
9342
- x.next();
9343
- x.complete();
9344
- });
9345
- }
9346
- remove(key) {
9347
- return new Observable((x) => {
9348
- this._storage.removeItem(key);
9349
- x.next();
9350
- x.complete();
9351
- });
9352
- }
9353
- clear() {
9354
- const removed = this._storage.removeAll();
9355
- return new Observable((x) => {
9356
- x.next(removed);
9357
- x.complete();
9358
- });
9359
- }
9360
- all() {
9361
- return this.allKeys().pipe(map((x) => filterMaybeArrayValues(x.map((y) => this._storage.getItem(y)))), shareReplay(1));
9460
+ class AbstractForwardDbxInjectionContextDirective {
9461
+ /**
9462
+ * The host-level {@link DbxInjectionContext} that all operations are forwarded to.
9463
+ */
9464
+ dbxInjectionContext = inject(DbxInjectionContext, { host: true });
9465
+ // MARK: DbxInjectionContext
9466
+ /**
9467
+ * {@inheritDoc DbxInjectionContext.showContext}
9468
+ *
9469
+ * @param config - The injection context configuration to forward to the host context.
9470
+ * @returns Resolves to the output of the injected component's usage.
9471
+ */
9472
+ showContext(config) {
9473
+ return this.dbxInjectionContext.showContext(config);
9362
9474
  }
9363
- allKeys() {
9364
- return new Observable((x) => {
9365
- const result = StorageObjectUtility.allKeysFromStorageObject(this._storage);
9366
- x.next(result);
9367
- x.complete();
9368
- });
9475
+ /**
9476
+ * {@inheritDoc DbxInjectionContext.resetContext}
9477
+ *
9478
+ * @returns `true` if an active context was cleared, `false` otherwise.
9479
+ */
9480
+ resetContext() {
9481
+ return this.dbxInjectionContext.resetContext();
9369
9482
  }
9483
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractForwardDbxInjectionContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9484
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: AbstractForwardDbxInjectionContextDirective, isStandalone: true, ngImport: i0 });
9370
9485
  }
9486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: AbstractForwardDbxInjectionContextDirective, decorators: [{
9487
+ type: Directive
9488
+ }] });
9371
9489
 
9372
9490
  /**
9373
- * DI token for the application's default {@link FullStorageObject} instance.
9491
+ * Creates an RxJS operator that switches between injection component configs, falling back to a
9492
+ * default config when the source emits `null`, `undefined`, or `true`.
9374
9493
  *
9375
- * Provided by {@link provideDbxStorage}.
9494
+ * If the `defaultConfig` resolves to a class (function), it is automatically wrapped into a
9495
+ * `{ componentClass }` config object.
9496
+ *
9497
+ * This is useful for reactive streams where an upstream value may indicate "use the default component"
9498
+ * rather than providing an explicit configuration.
9499
+ *
9500
+ * @param defaultConfig - A static value, getter, or component class to use as the fallback config.
9501
+ * @returns An RxJS operator compatible with `pipe()`.
9502
+ *
9503
+ * @see {@link DbxInjectionComponentConfig}
9504
+ * @typeParam T - The specific {@link DbxInjectionComponentConfig} subtype.
9505
+ * @typeParam X - The component type.
9506
+ *
9507
+ * @example
9508
+ * ```typescript
9509
+ * config$.pipe(
9510
+ * switchMapDbxInjectionComponentConfig(MyDefaultComponent)
9511
+ * ).subscribe(config => {
9512
+ * // config is always a DbxInjectionComponentConfig or undefined
9513
+ * });
9514
+ * ```
9376
9515
  */
9377
- const DEFAULT_STORAGE_OBJECT_TOKEN = new InjectionToken('DBX_CORE_DEFAULT_STORAGE_OBJECT');
9516
+ function switchMapDbxInjectionComponentConfig(defaultConfig) {
9517
+ const defaultAsGetter = asGetter(defaultConfig);
9518
+ return switchMapObject({
9519
+ defaultGetter: () => {
9520
+ const value = defaultAsGetter();
9521
+ // if value is a function, it is a component class
9522
+ return typeof value === 'function' ? { componentClass: value } : value;
9523
+ }
9524
+ });
9525
+ }
9526
+
9378
9527
  /**
9379
- * DI token for the application's default {@link SimpleStorageAccessorFactory} instance.
9528
+ * Creates a child injector with the given `type` as its sole provider and immediately
9529
+ * resolves an instance of that type.
9380
9530
  *
9381
- * Provided by {@link provideDbxStorage}.
9531
+ * This is a convenience for one-off instantiation of an injectable class using a specific
9532
+ * parent injector, without needing to manually configure `Injector.create()`.
9533
+ *
9534
+ * @param type - The injectable class to provide and resolve.
9535
+ * @param parent - The parent injector that supplies the type's dependencies.
9536
+ * @returns A new instance of `T`.
9537
+ *
9538
+ * @typeParam T - The type to instantiate.
9539
+ *
9540
+ * @example
9541
+ * ```typescript
9542
+ * const service = newWithInjector(MyService, parentInjector);
9543
+ * ```
9382
9544
  */
9383
- const DEFAULT_STORAGE_ACCESSOR_FACTORY_TOKEN = new InjectionToken('DBX_CORE_DEFAULT_STORAGE_ACCESSOR_FACTORY');
9545
+ function newWithInjector(type, parent) {
9546
+ const injector = Injector.create({ providers: [type], parent });
9547
+ return injector.get(type);
9548
+ }
9384
9549
 
9385
9550
  /**
9386
- * Injectable factory for creating namespaced {@link SimpleStorageAccessor} instances
9387
- * backed by the application's default storage object.
9551
+ * Abstract NgRx `ComponentStore` extension that integrates a {@link LockSet} for coordinating
9552
+ * async operations and delays destruction until all locks are released.
9553
+ *
9554
+ * Subclasses call {@link setupLockSet} to register parent lock sets and named locks.
9555
+ * On destroy, the store waits for all locks to unlock before completing cleanup,
9556
+ * preventing premature teardown of in-flight operations.
9557
+ *
9558
+ * @typeParam S - The shape of the component store's state object.
9388
9559
  *
9389
9560
  * @example
9390
9561
  * ```typescript
9391
- * @Injectable()
9392
- * export class UserPrefsService {
9393
- * private readonly storage = inject(SimpleStorageAccessorFactory)
9394
- * .createStorageAccessor<UserPrefs>({ prefix: 'user_prefs' });
9562
+ * interface MyState { items: string[]; }
9395
9563
  *
9396
- * save(prefs: UserPrefs) { return this.storage.set('current', prefs); }
9397
- * load() { return this.storage.get('current'); }
9564
+ * @Injectable()
9565
+ * export class MyStore extends LockSetComponentStore<MyState> {
9566
+ * constructor() {
9567
+ * super({ items: [] });
9568
+ * }
9398
9569
  * }
9399
9570
  * ```
9400
9571
  */
9401
- class SimpleStorageAccessorFactory {
9402
- storageObject = inject(DEFAULT_STORAGE_OBJECT_TOKEN);
9403
- createStorageAccessor(config) {
9404
- const storage = config.storage ?? new StringStorageAccessor(this.storageObject);
9405
- const converter = config.converter ?? new StringifySimpleStorageAccessorConverter();
9406
- const delegate = new WrapperSimpleStorageAccessorDelegate(storage, converter);
9407
- const accessorConfig = {
9408
- prefix: config.prefix
9409
- };
9410
- return new SimpleStorageAccessor(delegate, accessorConfig);
9572
+ class LockSetComponentStore extends ComponentStore {
9573
+ initialState;
9574
+ lockSet = new LockSet();
9575
+ lockSetDestroyDelayMs = 2000;
9576
+ lockSetDestroyTimeoutMs;
9577
+ // NOTE: Injection does not occur here, but we need @Injectable to compile properly for Angular usage
9578
+ // eslint-disable-next-line @angular-eslint/prefer-inject
9579
+ constructor(initialState) {
9580
+ super(initialState);
9581
+ this.initialState = initialState;
9411
9582
  }
9412
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
9413
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory });
9583
+ // MARK: State Changes
9584
+ resetStore = this.updater(() => ({ ...this.initialState }));
9585
+ // MARK: Locks
9586
+ setupLockSet({ parent, locks }) {
9587
+ if (parent) {
9588
+ this.setParentLockSet(parent);
9589
+ }
9590
+ if (locks) {
9591
+ for (const key in locks) {
9592
+ if (locks[key]) {
9593
+ this.addLock(key, locks[key]);
9594
+ }
9595
+ }
9596
+ }
9597
+ }
9598
+ setParentLockSet(obs) {
9599
+ this.lockSet.setParentLockSet(asObservable(obs).pipe(map((x) => x?.lockSet)));
9600
+ }
9601
+ addLock(key, obs) {
9602
+ this.lockSet.addLock(key, obs);
9603
+ }
9604
+ // MARK: Cleanup
9605
+ ngOnDestroy() {
9606
+ // Wait for any actions to complete before destroying.
9607
+ this.lockSet.destroyOnNextUnlock({
9608
+ fn: () => {
9609
+ this._destroyNow();
9610
+ },
9611
+ timeout: this.lockSetDestroyTimeoutMs
9612
+ }, this.lockSetDestroyDelayMs);
9613
+ }
9614
+ /**
9615
+ * Immediately completes cleanup by destroying the lock set.
9616
+ * Called after all locks are released during the deferred destruction process.
9617
+ */
9618
+ _destroyNow() {
9619
+ this.lockSet.destroy();
9620
+ }
9621
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore, deps: [{ token: null, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
9622
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore });
9414
9623
  }
9415
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SimpleStorageAccessorFactory, decorators: [{
9624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LockSetComponentStore, decorators: [{
9416
9625
  type: Injectable
9417
- }] });
9626
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
9627
+ type: Inject,
9628
+ args: [null]
9629
+ }, {
9630
+ type: Optional
9631
+ }] }] });
9418
9632
 
9419
9633
  /**
9420
9634
  * Abstract storage accessor providing key-value get/set/remove operations and a clear-all method.
@@ -9652,5 +9866,5 @@ function hasNonTrivialChildNodes(element) {
9652
9866
  * Generated bundle index. Do not edit.
9653
9867
  */
9654
9868
 
9655
- export { ACTION_CONTEXT_STORE_LOCKSET_DESTROY_DELAY_TIME, APP_ACTION_DISABLED_DIRECTIVE_KEY, APP_ACTION_DISABLED_ON_SUCCESS_DIRECTIVE_KEY, APP_ACTION_ENFORCE_MODIFIED_DIRECTIVE_KEY, AbstractDbxActionHandlerDirective, AbstractDbxActionValueGetterDirective, AbstractDbxAnchorDirective, AbstractDbxButtonDirective, AbstractDbxFilterMapInstanceDirective, AbstractDbxFilterMapSourceDirective, AbstractDbxInjectionDirective, AbstractFilterSourceConnectorDirective, AbstractFilterSourceDirective, AbstractForwardDbxInjectionContextDirective, AbstractIfDirective, AbstractTransitionDirective, AbstractTransitionWatcherDirective, ActionContextStore, ActionContextStoreSource, ActionContextStoreSourceMap, AsObservablePipe, CutTextPipe, DBX_ACTION_BUTTON_ECHO_CONFIG, DBX_ACTION_HANDLER_LOCK_KEY, DBX_APP_APP_CONTEXT_STATE, DBX_APP_AUTH_ROUTER_EFFECTS_TOKEN, DBX_ASSET_LOADER_CONFIG_TOKEN, DBX_AUTH_APP_CONTEXT_STATE, DBX_INIT_APP_CONTEXT_STATE, DBX_INJECTION_COMPONENT_DATA, DBX_KNOWN_APP_CONTEXT_STATES, DBX_OAUTH_APP_CONTEXT_STATE, DBX_ONBOARD_APP_CONTEXT_STATE, DBX_PUBLIC_APP_CONTEXT_STATE, DEFAULT_ACTION_DISABLED_KEY, DEFAULT_ACTION_MAP_WORKING_DISABLED_KEY, DEFAULT_DBX_ACTION_BUTTON_ECHO_CONFIG, DEFAULT_DBX_ACTION_BUTTON_ERROR_ECHO, DEFAULT_DBX_ACTION_BUTTON_SUCCESS_ECHO, DEFAULT_DBX_BUTTON_ECHO_DURATION, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_ID_PARAM_KEY, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_KEY_PARAM_KEY, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_USE_PARAM_VALUE, DEFAULT_FILTER_SOURCE_DIRECTIVE_FILTER_TOKEN, DEFAULT_LOCAL_ASSET_BASE_URL, DEFAULT_REDIRECT_FOR_IDENTIFIER_PARAM_KEY, DEFAULT_REDIRECT_FOR_IDENTIFIER_PARAM_VALUE, DEFAULT_REDIRECT_FOR_USER_IDENTIFIER_PARAM_KEY, DEFAULT_REDIRECT_FOR_USER_IDENTIFIER_PARAM_VALUE, DEFAULT_STORAGE_ACCESSOR_FACTORY_TOKEN, DEFAULT_STORAGE_OBJECT_TOKEN, DateDayRangePipe, DateDayTimeRangePipe, DateDistancePipe, DateFormatDistancePipe, DateFormatFromToPipe, DateRangeDistancePipe, DateTimeRangeOnlyDistancePipe, DateTimeRangeOnlyPipe, DateTimeRangePipe, DbxActionAutoModifyDirective, DbxActionAutoTriggerDirective, DbxActionButtonDirective, DbxActionButtonTriggerDirective, DbxActionContextBaseSource, DbxActionContextLoggerDirective, DbxActionContextMachine, DbxActionContextMachineAsService, DbxActionContextMapDirective, DbxActionContextStoreSourceInstance, DbxActionDirective, DbxActionDisabledDirective, DbxActionDisabledOnSuccessDirective, DbxActionEnforceModifiedDirective, DbxActionErrorHandlerDirective, DbxActionFromMapDirective, DbxActionHandlerDirective, DbxActionHandlerInstance, DbxActionHandlerValueDirective, DbxActionHasSuccessDirective, DbxActionIdleDirective, DbxActionIsModifiedDirective, DbxActionIsWorkingDirective, DbxActionMapSourceDirective, DbxActionMapWorkingDisableDirective, DbxActionPreSuccessDirective, DbxActionSourceDirective, DbxActionState, DbxActionSuccessHandlerDirective, DbxActionTriggeredDirective, DbxActionValueDirective, DbxActionValueGetterInstance, DbxActionValueStreamDirective, DbxActionValueTriggerDirective, DbxActionWorkInstanceDelegate, DbxActionWorkable, DbxAnchor, DbxAngularRouterService, DbxAppAuthRouterEffects, DbxAppAuthRouterService, DbxAppAuthRoutes, DbxAppAuthStateService, DbxAppContextService, DbxAppContextStateDirective, DbxAppEnvironment, DbxAppEnvironmentService, DbxAuthHasAnyRoleDirective, DbxAuthHasRolesDirective, DbxAuthImpersonationDelegate, DbxAuthImpersonationQuerySyncDirective, DbxAuthImpersonationService, DbxAuthImpersonationTriggerDirective, DbxAuthNotAnyRoleDirective, DbxAuthService, DbxButton, DbxButtonDirective, DbxButtonSegueDirective, DbxCoreActionModule, DbxCoreAngularRouterSegueModule, DbxCoreAssetLoader, DbxCoreButtonModule, DbxCoreFilterModule, DbxFilterConnectSourceDirective, DbxFilterMapDirective, DbxFilterMapSourceConnectorDirective, DbxFilterMapSourceDirective, DbxFilterSourceConnectorDirective, DbxFilterSourceDirective, DbxInjectionArrayComponent, DbxInjectionComponent, DbxInjectionContext, DbxInjectionContextDirective, DbxInjectionInstance, DbxLoadingButtonDirective, DbxRouteModelIdDirective, DbxRouteModelIdDirectiveDelegate, DbxRouteModelIdFromAuthUserIdDirective, DbxRouteModelKeyDirective, DbxRouteModelKeyDirectiveDelegate, DbxRouteParamDefaultRedirectInstance, DbxRouterService, DbxRouterTransitionEventType, DbxRouterTransitionService, DbxUIRouterService, DollarAmountPipe, FilterSourceDirective, FullLocalStorageObject, GetValueOncePipe, GetValuePipe, InstantStorageAccessor, LimitedStorageAccessor, LockSetComponentStore, MemoryStorageObject, MinutesStringPipe, NO_AUTH_USER_IDENTIFIER, PrettyJsonPipe, SecondaryActionContextStoreSource, SimpleStorageAccessor, SimpleStorageAccessorFactory, StorageAccessor, StringStorageAccessor, StringifySimpleStorageAccessorConverter, SystemDateToTargetDatePipe, TargetDateToSystemDatePipe, TimeDistanceCountdownPipe, TimeDistancePipe, TimezoneAbbreviationPipe, ToJsDatePipe, ToMinutesPipe, WrapperSimpleStorageAccessorDelegate, actionContextHasNoErrorAndIsModifiedAndCanTrigger, actionContextIsModifiedAndCanTrigger, actionContextStoreSourceMap, actionContextStoreSourceMapReader, actionContextStoreSourcePipe, anchorTypeForAnchor, asSegueRef, asSegueRefString, assertValidStorageKeyPrefix, authRolesSetContainsAllRolesFrom, authRolesSetContainsAnyRoleFrom, authRolesSetContainsNoRolesFrom, authUserIdentifier, canReadyValue, canTriggerAction, canTriggerActionState, checkNgContentWrapperHasContent, clean, cleanDestroy, cleanListLoadingContext, cleanLoadingContext, cleanLockSet, cleanSubscription, cleanSubscriptionWithLockSet, cleanWithLockSet, clickableUrlInNewTab, clickableUrlMailTo, clickableUrlTel, completeOnDestroy, createInjectorForInjectionComponentConfig, dbxActionWorkProgress, dbxButtonDisplayType, dbxInjectionComponentConfigIsEqual, dbxRouteModelIdParamRedirect, dbxRouteModelKeyParamRedirect, dbxRouteParamReaderInstance, defaultStorageObjectFactory, enableHasAuthRoleHook, enableHasAuthStateHook, enableIsLoggedInHook, expandClickableAnchorLinkTree, expandClickableAnchorLinkTreeNode, expandClickableAnchorLinkTrees, filterTransitionEvent, filterTransitionSuccess, flattenExpandedClickableAnchorLinkTree, flattenExpandedClickableAnchorLinkTreeToLinks, fromAllActionContextStoreSourceMapSources, index as fromDbxAppAuth, index$2 as fromDbxAppContext, goWithRouter, hasAuthRoleDecisionPipe, hasNonTrivialChildNodes, initInjectionComponent, isActionContextDisabled, isActionContextEnabled, isClickableFilterPreset, isClickablePartialFilterPreset, isDisabledActionContextState, isIdleActionState, isLatestSuccessfulRoute, isSegueRef, isSegueRefActive, isSegueRefActiveFunction, isSegueRefActiveOnTransitionSuccess, isValidStorageKeyPrefix, isWorkingActionState, latestSuccessfulRoutes, loadingStateForActionContextState, loadingStateTypeForActionContextState, loadingStateTypeForActionState, loggedInObsFromIsLoggedIn, loggedOutObsFromIsLoggedIn, makeAuthTransitionHook, makeDbxActionContextSourceReference, mapRefStringObsToSegueRefObs, mergeDbxInjectionComponentConfigs, mergeStaticProviders, newWithInjector, index$1 as onDbxAppAuth, index$3 as onDbxAppContext, onRouterTransitionEventType, onRouterTransitionSuccessEvent, pipeActionStore, provideActionStoreSource, provideDbxActionButtonEchoConfig, provideDbxActionWorkable, provideDbxAnchor, provideDbxAppAuth, provideDbxAppAuthRouter, provideDbxAppAuthRouterState, provideDbxAppAuthState, provideDbxAppContextState, provideDbxAppEnvironment, provideDbxAssetLoader, provideDbxAuthImpersonation, provideDbxAuthImpersonationDelegate, provideDbxButton, provideDbxInjectionContext, provideDbxRouteModelIdDirectiveDelegate, provideDbxRouteModelKeyDirectiveDelegate, provideDbxStorage, provideDbxUIRouterService, provideFilterSource, provideFilterSourceConnector, provideFilterSourceDirective, provideSecondaryActionStoreSource, redirectBasedOnAuthUserState, redirectForIdentifierParamHook, redirectForUserIdentifierParamHook, refStringToSegueRef, successTransition, switchMapDbxInjectionComponentConfig, transformEmptyStringInputToUndefined, useActionStore };
9869
+ export { ACTION_CONTEXT_STORE_LOCKSET_DESTROY_DELAY_TIME, APP_ACTION_DISABLED_DIRECTIVE_KEY, APP_ACTION_DISABLED_ON_SUCCESS_DIRECTIVE_KEY, APP_ACTION_ENFORCE_MODIFIED_DIRECTIVE_KEY, AbstractDbxActionHandlerDirective, AbstractDbxActionValueGetterDirective, AbstractDbxAnchorDirective, AbstractDbxButtonDirective, AbstractDbxFilterMapInstanceDirective, AbstractDbxFilterMapSourceDirective, AbstractDbxInjectionDirective, AbstractFilterSourceConnectorDirective, AbstractFilterSourceDirective, AbstractForwardDbxInjectionContextDirective, AbstractIfDirective, AbstractTransitionDirective, AbstractTransitionWatcherDirective, ActionContextStore, ActionContextStoreSource, ActionContextStoreSourceMap, AsObservablePipe, CutTextPipe, DBX_ACTION_BUTTON_ECHO_CONFIG, DBX_ACTION_HANDLER_LOCK_KEY, DBX_APP_APP_CONTEXT_STATE, DBX_APP_AUTH_ROUTER_EFFECTS_TOKEN, DBX_ASSET_LOADER_CONFIG_TOKEN, DBX_AUTH_APP_CONTEXT_STATE, DBX_FILTER_STORAGE_SERVICE_CONFIG_TOKEN, DBX_INIT_APP_CONTEXT_STATE, DBX_INJECTION_COMPONENT_DATA, DBX_KNOWN_APP_CONTEXT_STATES, DBX_OAUTH_APP_CONTEXT_STATE, DBX_ONBOARD_APP_CONTEXT_STATE, DBX_PUBLIC_APP_CONTEXT_STATE, DEFAULT_ACTION_DISABLED_KEY, DEFAULT_ACTION_MAP_WORKING_DISABLED_KEY, DEFAULT_DBX_ACTION_BUTTON_ECHO_CONFIG, DEFAULT_DBX_ACTION_BUTTON_ERROR_ECHO, DEFAULT_DBX_ACTION_BUTTON_SUCCESS_ECHO, DEFAULT_DBX_BUTTON_ECHO_DURATION, DEFAULT_DBX_FILTER_STORAGE_PREFIX, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_ID_PARAM_KEY, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_KEY_PARAM_KEY, DEFAULT_DBX_ROUTE_MODEL_ID_PARAM_USE_PARAM_VALUE, DEFAULT_FILTER_SOURCE_DIRECTIVE_FILTER_TOKEN, DEFAULT_LOCAL_ASSET_BASE_URL, DEFAULT_REDIRECT_FOR_IDENTIFIER_PARAM_KEY, DEFAULT_REDIRECT_FOR_IDENTIFIER_PARAM_VALUE, DEFAULT_REDIRECT_FOR_USER_IDENTIFIER_PARAM_KEY, DEFAULT_REDIRECT_FOR_USER_IDENTIFIER_PARAM_VALUE, DEFAULT_STORAGE_ACCESSOR_FACTORY_TOKEN, DEFAULT_STORAGE_OBJECT_TOKEN, DateDayRangePipe, DateDayTimeRangePipe, DateDistancePipe, DateFormatDistancePipe, DateFormatFromToPipe, DateRangeDistancePipe, DateTimeRangeOnlyDistancePipe, DateTimeRangeOnlyPipe, DateTimeRangePipe, DbxActionAutoModifyDirective, DbxActionAutoTriggerDirective, DbxActionButtonDirective, DbxActionButtonTriggerDirective, DbxActionContextBaseSource, DbxActionContextLoggerDirective, DbxActionContextMachine, DbxActionContextMachineAsService, DbxActionContextMapDirective, DbxActionContextStoreSourceInstance, DbxActionDirective, DbxActionDisabledDirective, DbxActionDisabledOnSuccessDirective, DbxActionEnforceModifiedDirective, DbxActionErrorHandlerDirective, DbxActionFromMapDirective, DbxActionHandlerDirective, DbxActionHandlerInstance, DbxActionHandlerValueDirective, DbxActionHasSuccessDirective, DbxActionIdleDirective, DbxActionIsModifiedDirective, DbxActionIsWorkingDirective, DbxActionMapSourceDirective, DbxActionMapWorkingDisableDirective, DbxActionPreSuccessDirective, DbxActionSourceDirective, DbxActionState, DbxActionSuccessHandlerDirective, DbxActionTriggeredDirective, DbxActionValueDirective, DbxActionValueGetterInstance, DbxActionValueStreamDirective, DbxActionValueTriggerDirective, DbxActionWorkInstanceDelegate, DbxActionWorkable, DbxAnchor, DbxAngularRouterService, DbxAppAuthRouterEffects, DbxAppAuthRouterService, DbxAppAuthRoutes, DbxAppAuthStateService, DbxAppContextService, DbxAppContextStateDirective, DbxAppEnvironment, DbxAppEnvironmentService, DbxAuthHasAnyRoleDirective, DbxAuthHasRolesDirective, DbxAuthImpersonationDelegate, DbxAuthImpersonationQuerySyncDirective, DbxAuthImpersonationService, DbxAuthImpersonationTriggerDirective, DbxAuthNotAnyRoleDirective, DbxAuthService, DbxButton, DbxButtonDirective, DbxButtonSegueDirective, DbxCoreActionModule, DbxCoreAngularRouterSegueModule, DbxCoreAssetLoader, DbxCoreButtonModule, DbxCoreFilterModule, DbxFilterConnectSourceDirective, DbxFilterMapDirective, DbxFilterMapMergeSourceDirective, DbxFilterMapSourceConnectorDirective, DbxFilterMapSourceDirective, DbxFilterMapStorageDirective, DbxFilterSourceConnectorDirective, DbxFilterSourceDirective, DbxFilterStorageService, DbxInjectionArrayComponent, DbxInjectionComponent, DbxInjectionContext, DbxInjectionContextDirective, DbxInjectionInstance, DbxLoadingButtonDirective, DbxRouteModelIdDirective, DbxRouteModelIdDirectiveDelegate, DbxRouteModelIdFromAuthUserIdDirective, DbxRouteModelKeyDirective, DbxRouteModelKeyDirectiveDelegate, DbxRouteParamDefaultRedirectInstance, DbxRouterService, DbxRouterTransitionEventType, DbxRouterTransitionService, DbxUIRouterService, DollarAmountPipe, FilterSourceDirective, FullLocalStorageObject, GetValueOncePipe, GetValuePipe, InstantStorageAccessor, LimitedStorageAccessor, LockSetComponentStore, MemoryStorageObject, MinutesStringPipe, NO_AUTH_USER_IDENTIFIER, PrettyJsonPipe, SecondaryActionContextStoreSource, SimpleStorageAccessor, SimpleStorageAccessorFactory, StorageAccessor, StringStorageAccessor, StringifySimpleStorageAccessorConverter, SystemDateToTargetDatePipe, TargetDateToSystemDatePipe, TimeDistanceCountdownPipe, TimeDistancePipe, TimezoneAbbreviationPipe, ToJsDatePipe, ToMinutesPipe, WrapperSimpleStorageAccessorDelegate, actionContextHasNoErrorAndIsModifiedAndCanTrigger, actionContextIsModifiedAndCanTrigger, actionContextStoreSourceMap, actionContextStoreSourceMapReader, actionContextStoreSourcePipe, anchorTypeForAnchor, asSegueRef, asSegueRefString, assertValidStorageKeyPrefix, authRolesSetContainsAllRolesFrom, authRolesSetContainsAnyRoleFrom, authRolesSetContainsNoRolesFrom, authUserIdentifier, canReadyValue, canTriggerAction, canTriggerActionState, checkNgContentWrapperHasContent, clean, cleanDestroy, cleanListLoadingContext, cleanLoadingContext, cleanLockSet, cleanSubscription, cleanSubscriptionWithLockSet, cleanWithLockSet, clickableUrlInNewTab, clickableUrlMailTo, clickableUrlTel, completeOnDestroy, createInjectorForInjectionComponentConfig, dbxActionWorkProgress, dbxButtonDisplayType, dbxInjectionComponentConfigIsEqual, dbxRouteModelIdParamRedirect, dbxRouteModelKeyParamRedirect, dbxRouteParamReaderInstance, defaultStorageObjectFactory, enableHasAuthRoleHook, enableHasAuthStateHook, enableIsLoggedInHook, expandClickableAnchorLinkTree, expandClickableAnchorLinkTreeNode, expandClickableAnchorLinkTrees, filterTransitionEvent, filterTransitionSuccess, flattenExpandedClickableAnchorLinkTree, flattenExpandedClickableAnchorLinkTreeToLinks, fromAllActionContextStoreSourceMapSources, index as fromDbxAppAuth, index$2 as fromDbxAppContext, goWithRouter, hasAuthRoleDecisionPipe, hasNonTrivialChildNodes, initInjectionComponent, isActionContextDisabled, isActionContextEnabled, isClickableFilterPreset, isClickablePartialFilterPreset, isDisabledActionContextState, isIdleActionState, isLatestSuccessfulRoute, isSegueRef, isSegueRefActive, isSegueRefActiveFunction, isSegueRefActiveOnTransitionSuccess, isValidStorageKeyPrefix, isWorkingActionState, latestSuccessfulRoutes, loadingStateForActionContextState, loadingStateTypeForActionContextState, loadingStateTypeForActionState, loggedInObsFromIsLoggedIn, loggedOutObsFromIsLoggedIn, makeAuthTransitionHook, makeDbxActionContextSourceReference, mapRefStringObsToSegueRefObs, mergeDbxInjectionComponentConfigs, mergeStaticProviders, newWithInjector, index$1 as onDbxAppAuth, index$3 as onDbxAppContext, onRouterTransitionEventType, onRouterTransitionSuccessEvent, pipeActionStore, provideActionStoreSource, provideDbxActionButtonEchoConfig, provideDbxActionWorkable, provideDbxAnchor, provideDbxAppAuth, provideDbxAppAuthRouter, provideDbxAppAuthRouterState, provideDbxAppAuthState, provideDbxAppContextState, provideDbxAppEnvironment, provideDbxAssetLoader, provideDbxAuthImpersonation, provideDbxAuthImpersonationDelegate, provideDbxButton, provideDbxFilterStorage, provideDbxInjectionContext, provideDbxRouteModelIdDirectiveDelegate, provideDbxRouteModelKeyDirectiveDelegate, provideDbxStorage, provideDbxUIRouterService, provideFilterSource, provideFilterSourceConnector, provideFilterSourceDirective, provideSecondaryActionStoreSource, redirectBasedOnAuthUserState, redirectForIdentifierParamHook, redirectForUserIdentifierParamHook, refStringToSegueRef, successTransition, switchMapDbxInjectionComponentConfig, transformEmptyStringInputToUndefined, useActionStore };
9656
9870
  //# sourceMappingURL=dereekb-dbx-core.mjs.map