@tilde-nlp/ngx-common 8.1.39 → 8.1.41

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,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Component, Input, ViewChild, Output, NgModule, Optional, Inject, Pipe, inject, Directive, HostListener, HostBinding, ViewChildren, input, effect, ElementRef, ContentChild, ContentChildren, InjectionToken, signal, output, computed, makeEnvironmentProviders } from '@angular/core';
2
+ import { Injectable, EventEmitter, Component, Input, ViewChild, Output, NgModule, Optional, Inject, Pipe, inject, Directive, HostListener, HostBinding, ViewChildren, input, effect, ElementRef, ContentChild, ContentChildren, InjectionToken, signal, output, computed, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
3
3
  import * as i2 from '@angular/material/icon';
4
4
  import { MatIconModule, MatIcon } from '@angular/material/icon';
5
5
  import * as i2$1 from '@angular/platform-browser';
6
6
  import { Title } from '@angular/platform-browser';
7
- import { Subject, map, Observable, of, take, BehaviorSubject, tap, forkJoin, takeUntil, finalize, catchError, fromEvent, debounceTime } from 'rxjs';
7
+ import { Subject, map, Observable, of, take, BehaviorSubject, tap, forkJoin, takeUntil, finalize, catchError, fromEvent, debounceTime, ReplaySubject, filter, first } from 'rxjs';
8
8
  import * as i1$1 from '@angular/common';
9
9
  import { CommonModule, TitleCasePipe, AsyncPipe, DatePipe } from '@angular/common';
10
10
  import { FlexLayoutModule } from '@ngbracket/ngx-layout';
@@ -4416,7 +4416,7 @@ class MultiFunctionalTableComponent {
4416
4416
  }], tableElementRef: [{
4417
4417
  type: ViewChild,
4418
4418
  args: [MatTable, { read: ElementRef }]
4419
- }] }); })();
4419
+ }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] }); })();
4420
4420
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MultiFunctionalTableComponent, { className: "MultiFunctionalTableComponent", filePath: "lib/multi-functional-table/multi-functional-table.component.ts", lineNumber: 27 }); })();
4421
4421
 
4422
4422
  class StatusDisplayModule {
@@ -8519,7 +8519,7 @@ class SubscriptionComponent {
8519
8519
  CommonModule,
8520
8520
  MatButtonModule
8521
8521
  ], template: "@if(token()){\r\n <!-- narrow type to non null property -->\r\n @let userToken = token()!;\r\n\r\n <h3 class=\"subscription-section-title text-l-semi-bold\">{{ \"SUBSCRIPTION.USER_INFO\" | translate }}</h3>\r\n\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_NAME\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <span>{{ userToken.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_EMAIL\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <span>{{ userToken.email }}</span>\r\n </div>\r\n </div>\r\n}\r\n\r\n@if(subscription()){\r\n @let currentSubscription = subscription()!;\r\n\r\n <div fxLayout=\"row\" class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_SUBSCRIPTION_PLAN_ID\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <ng-container>\r\n <span>\r\n {{ currentSubscription.Label ? currentSubscription.Label : currentSubscription.PlanId }}\r\n\r\n @switch (subscriptionState()) {\r\n @case(subscriptionStates.TRIAL){\r\n <span> ({{ \"SUBSCRIPTION.TRIAL_END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }}) </span>\r\n }\r\n @case (subscriptionStates.ACTIVE) {\r\n @if(currentSubscription.EndDate){\r\n <span> ({{ \"SUBSCRIPTION.END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }}) </span>\r\n }\r\n }\r\n @case (subscriptionStates.ENDED) {\r\n ({{ \"SUBSCRIPTION.END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }})\r\n }\r\n }\r\n </span>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n @if(currentSubscription.EndDate){\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_SUBSCRIPTION_END_DATE\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <ng-container>\r\n <span>{{currentSubscription.EndDate | date: \"dd.MM.yyyy\"}}</span>\r\n </ng-container>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if(currentSubscription.Addons?.length){\r\n <div fxLayout=\"row\" class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.ADDITIONS_PLUGINS\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n @for (item of currentSubscription.Addons; track $index; let i = $index) {\r\n <span>{{ item.id }}</span>\r\n @if(item.qt>1){\r\n <span> ({{item.qt}})</span>\r\n }\r\n <span> {{ currentSubscription.Addons!.length - 1 > i ? \",\" : \"\" }} </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n}\r\n\r\n@if(visibleQuotas().length){\r\n <h3 class=\"subscription-section-title text-l-semi-bold\">{{ \"SUBSCRIPTION.LIMITS\" | translate }}</h3>\r\n\r\n @for (currentQuota of visibleQuotas(); track $index) {\r\n <div class=\"subscription-limit-wrapper\">\r\n <mat-progress-bar [value]=\"currentQuota.percentUsed\"></mat-progress-bar>\r\n <div class=\"subscription-limit\">\r\n <span class=\"text-l-semi-bold\">\r\n {{ (\"QUOTA.\" + (currentQuota.quotaType.toString() | uppercase) + (currentQuota.isUnlimited? \"_UNLIMITED\": \"\")) | translate: { used: currentQuota.convertedUsed, limit: currentQuota.convertedLimit, percent: currentQuota.percentUsed} }}\r\n </span>\r\n </div>\r\n </div>\r\n }\r\n}\r\n\r\n<!-- Add some extra info, such as disclaimer etc -->\r\n <ng-content></ng-content>\r\n\r\n<div class=\"subscription-actions-wrapper\">\r\n @if(chargebeeSettings()){\r\n <a mat-flat-button color=\"accent\" class=\"manage-sub-btn button-row-element\" href=\"javascript:void(0)\" data-cb-type=\"portal\" (click)=\"manageClicked()\">\r\n {{ \"SUBSCRIPTION.MANAGE\" | translate }}\r\n </a>\r\n }\r\n @if(contactUsUrl()){\r\n <a mat-stroked-button [attr.href]=\"contactUsUrl()\" target=\"_blank\" color=\"accent\" (click)=\"contactUsClicked()\">{{\"SUBSCRIPTION.CONTACT\" | translate}}</a>\r\n }\r\n</div>\r\n", styles: [":host{display:block}mat-card{margin:1.5rem}.subscription-container:not(.mobile){margin:16px 32px}.subscription-section-title{margin:32px 0 18px}.subscription-data-row{border-bottom:2px solid var(--base-70);margin-bottom:18px;flex-direction:row;display:flex}.subscription-data{color:var(--base-30);word-wrap:break-word;margin-left:.5rem}.subscription-limit-wrapper,.subscription-limit{margin-top:16px}.subscription-actions-wrapper{padding:32px 0 18px;display:flex;flex-direction:row;gap:24px}.subscription-manage-link{text-decoration:none}.subscription-container:not(.mobile,.empty){width:75%}.subscription-label:not(.mobile,.empty){width:35%}.subscription-data:not(.mobile,.empty){width:65%}.untranslated-files-disclaimer{margin-top:16px;margin-bottom:10px;color:var(--base-40)}.no-subscription-message{margin-top:32px;color:var(--base-40)}.contact-sales-btn{text-align:center}.contact-sales-btn:hover{text-decoration:none}\n"] }]
8522
- }], null, null); })();
8522
+ }], null, { token: [{ type: i0.Input, args: [{ isSignal: true, alias: "token", required: true }] }], subscription: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscription", required: true }] }], quota: [{ type: i0.Input, args: [{ isSignal: true, alias: "quota", required: true }] }], visibleQuotaTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibleQuotaTypes", required: false }] }], contactUsUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "contactUsUrl", required: false }] }], chargebeeSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "chargebeeSettings", required: false }] }], manageClick: [{ type: i0.Output, args: ["manageClick"] }], contactUsClick: [{ type: i0.Output, args: ["contactUsClick"] }] }); })();
8523
8523
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SubscriptionComponent, { className: "SubscriptionComponent", filePath: "lib/subscription/subscription.component.ts", lineNumber: 26 }); })();
8524
8524
 
8525
8525
  var AccessibilityFontSizes;
@@ -9459,22 +9459,14 @@ class CookieConsentComponent {
9459
9459
  this.destroy.complete();
9460
9460
  }
9461
9461
  resolveInitialConsentState() {
9462
- const configured = this.isCookieConsentConfigured();
9463
- if (this.isPromise(configured)) {
9464
- configured.then((isConfigured) => {
9465
- if (isConfigured) {
9466
- this.isClosed = true;
9467
- return;
9468
- }
9469
- this.setupTranslations();
9470
- });
9471
- return;
9472
- }
9473
- if (configured) {
9474
- this.isClosed = true;
9475
- return;
9476
- }
9477
- this.setupTranslations();
9462
+ this.isCookieConsentConfigured()
9463
+ .pipe(tap((isConfigured) => {
9464
+ if (isConfigured) {
9465
+ this.isClosed = true;
9466
+ return;
9467
+ }
9468
+ this.setupTranslations();
9469
+ })).subscribe();
9478
9470
  }
9479
9471
  injectConsentScript() {
9480
9472
  if (!this.script || typeof document === 'undefined') {
@@ -9501,24 +9493,19 @@ class CookieConsentComponent {
9501
9493
  return translations[key] ? key : 'en';
9502
9494
  }
9503
9495
  isCookieConsentConfigured() {
9504
- const snapshot = this.#storage?.snapshot();
9505
- if (snapshot) {
9506
- return this.applyStoredConsent(snapshot);
9496
+ if (!this.#storage) {
9497
+ return of(false);
9507
9498
  }
9508
- const stored = this.#storage?.read();
9509
- if (this.isPromise(stored)) {
9510
- return stored.then((config) => this.applyStoredConsent(config));
9511
- }
9512
- return this.applyStoredConsent(stored ?? null);
9499
+ return this.#storage?.read().pipe(map((config) => {
9500
+ return this.hasUserHandledConsent(config ?? null);
9501
+ }));
9513
9502
  }
9514
- applyStoredConsent(config) {
9503
+ hasUserHandledConsent(config) {
9515
9504
  const consent = config?.cookieConsent;
9516
9505
  if (!consent?.updatedAt) {
9517
9506
  return false;
9518
9507
  }
9519
- if (consent.accepted) {
9520
- this.#analytics.cookieConsentGiven(true);
9521
- }
9508
+ this.#analytics.cookieConsentGiven(true);
9522
9509
  return true;
9523
9510
  }
9524
9511
  persistConsent(accepted) {
@@ -9531,10 +9518,7 @@ class CookieConsentComponent {
9531
9518
  accepted,
9532
9519
  updatedAt: now,
9533
9520
  },
9534
- });
9535
- }
9536
- isPromise(value) {
9537
- return !!value && typeof value.then === 'function';
9521
+ }).subscribe();
9538
9522
  }
9539
9523
  static { this.ɵfac = function CookieConsentComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CookieConsentComponent)(); }; }
9540
9524
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CookieConsentComponent, selectors: [["lib-cookie-consent"]], inputs: { script: "script", privacyPolicyUrl: "privacyPolicyUrl" }, decls: 1, vars: 1, consts: [[1, "cookie-consent-container"], [1, "cookie-consent-content"], [1, "cookie-consent-description", 3, "innerHTML"], [1, "cookie-consent-actions"], ["mat-flat-button", "", 3, "click"], ["mat-stroked-button", "", 3, "click"], [1, "cookie-consent-info"], [1, "cookie-consent-info--privacy", 3, "href"], [1, "cookie-consent-info--details", 3, "click"], [1, "spin"], [1, "cookie-consent--details"]], template: function CookieConsentComponent_Template(rf, ctx) { if (rf & 1) {
@@ -9598,134 +9582,132 @@ const USER_CONFIG_MESSAGE_UPDATE = 'ngx-user-config:update';
9598
9582
  const DEFAULT_USER_CONFIG_OPTIONS = {
9599
9583
  strategy: UserConfigStrategy.Local,
9600
9584
  iframeUrl: DEFAULT_USER_CONFIG_IFRAME_URL,
9585
+ defaultConfig: DEFAULT_USER_CONFIG
9601
9586
  };
9602
9587
 
9603
- const USER_CONFIG_STORAGE_KEY = 'ngx_common_user_config';
9604
-
9605
9588
  const USER_CONFIG_OPTIONS = new InjectionToken('USER_CONFIG_OPTIONS');
9606
9589
 
9607
- class IframeStorageStrategy {
9608
- #dom;
9609
- #options;
9610
- #iframe;
9611
- #messageHandler;
9612
- constructor() {
9613
- this.#dom = inject(DOMService);
9614
- this.#options = inject(USER_CONFIG_OPTIONS);
9590
+ const mergeSection = (defaults, override) => ({
9591
+ ...defaults,
9592
+ ...(override ?? {}),
9593
+ });
9594
+ const mergeConfigSections = (base, override = {}) => ({
9595
+ accessibility: mergeSection(base.accessibility, override.accessibility),
9596
+ cookieConsent: mergeSection(base.cookieConsent, override.cookieConsent),
9597
+ language: mergeSection(base.language, override.language),
9598
+ metadata: mergeSection(base.metadata, override.metadata),
9599
+ });
9600
+ function createDefaultUserConfig(options) {
9601
+ const override = options.defaultConfig ?? {};
9602
+ return mergeConfigSections(DEFAULT_USER_CONFIG, override);
9603
+ }
9604
+ function mergeUserConfig(current, update, options) {
9605
+ const base = current ?? createDefaultUserConfig(options);
9606
+ const merged = mergeConfigSections(base, update);
9607
+ return {
9608
+ ...merged,
9609
+ metadata: {
9610
+ ...merged.metadata,
9611
+ updatedAt: new Date().toISOString(),
9612
+ },
9613
+ };
9614
+ }
9615
+
9616
+ class BaseStorageStrategy {
9617
+ constructor(options) {
9618
+ this.options = options;
9615
9619
  this.snapshot = signal(null, ...(ngDevMode ? [{ debugName: "snapshot" }] : []));
9616
- this.#ensureIframe();
9617
- const win = this.#dom.window;
9618
- if (!win) {
9619
- return;
9620
- }
9621
- this.#messageHandler = (event) => this.#handleMessage(event);
9622
- win.addEventListener('message', this.#messageHandler);
9623
- const cached = this.#readCache();
9624
- if (cached) {
9625
- this.snapshot.set(cached);
9626
- }
9620
+ /** Tracks when the initial data has been fetched from the source */
9621
+ this.isReady$ = new ReplaySubject(1);
9627
9622
  }
9623
+ /**
9624
+ * Returns an Observable that waits for the source to be ready,
9625
+ * then emits the current snapshot.
9626
+ */
9628
9627
  read() {
9629
- return this.#readCache();
9628
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => {
9629
+ return this.snapshot();
9630
+ }));
9630
9631
  }
9632
+ /**
9633
+ * Waits for readiness, performs a deep merge, updates the local signal,
9634
+ * and triggers the implementation-specific persistence.
9635
+ */
9631
9636
  write(config) {
9632
- const merged = this.#mergeWithBase(config);
9633
- this.#writeCache(merged);
9634
- this.#postToBridge({ type: USER_CONFIG_MESSAGE_UPDATE, payload: merged });
9635
- this.snapshot.set(merged);
9637
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => {
9638
+ const merged = this.mergeWithBase(config);
9639
+ this.snapshot.set(merged);
9640
+ this.saveToSource(merged);
9641
+ return merged;
9642
+ }));
9643
+ }
9644
+ initialize() {
9645
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => null));
9646
+ }
9647
+ mergeWithBase(update) {
9648
+ return mergeUserConfig(this.snapshot(), update, this.options);
9649
+ }
9650
+ createDefaultConfig() {
9651
+ return createDefaultUserConfig(this.options);
9652
+ }
9653
+ static { this.ɵfac = function BaseStorageStrategy_Factory(__ngFactoryType__) { i0.ɵɵinvalidFactory(); }; }
9654
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: BaseStorageStrategy, factory: BaseStorageStrategy.ɵfac }); }
9655
+ }
9656
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseStorageStrategy, [{
9657
+ type: Injectable
9658
+ }], () => [{ type: undefined }], null); })();
9659
+
9660
+ class IframeStorageStrategy extends BaseStorageStrategy {
9661
+ #dom = inject(DOMService);
9662
+ #options = inject(USER_CONFIG_OPTIONS);
9663
+ #iframe;
9664
+ #messageHandler;
9665
+ constructor() {
9666
+ super(inject(USER_CONFIG_OPTIONS));
9667
+ this.#init();
9668
+ }
9669
+ saveToSource(config) {
9670
+ this.#postToBridge({ type: USER_CONFIG_MESSAGE_UPDATE, payload: config });
9636
9671
  }
9637
9672
  ngOnDestroy() {
9638
9673
  if (this.#messageHandler && this.#dom.window) {
9639
9674
  this.#dom.window.removeEventListener('message', this.#messageHandler);
9640
9675
  }
9641
9676
  }
9642
- #ensureIframe() {
9643
- if (this.#iframe) {
9644
- return;
9645
- }
9646
- const win = this.#dom.window;
9647
- const doc = win?.document;
9648
- if (!doc) {
9677
+ #init() {
9678
+ const window = this.#dom.window;
9679
+ if (!window) {
9649
9680
  return;
9650
9681
  }
9682
+ ;
9683
+ this.#messageHandler = (event) => this.#handleMessage(event);
9684
+ window.addEventListener('message', this.#messageHandler);
9685
+ const doc = window.document;
9651
9686
  const iframe = doc.createElement('iframe');
9652
9687
  iframe.style.display = 'none';
9653
9688
  iframe.name = USER_CONFIG_IFRAME_NAME;
9654
9689
  iframe.src = this.#options?.iframeUrl ?? DEFAULT_USER_CONFIG_IFRAME_URL;
9655
9690
  iframe.setAttribute('aria-hidden', 'true');
9656
- iframe.addEventListener('load', () => this.#postToBridge({ type: USER_CONFIG_MESSAGE_REQUEST }));
9691
+ iframe.addEventListener('load', () => {
9692
+ this.#postToBridge({ type: USER_CONFIG_MESSAGE_REQUEST });
9693
+ });
9657
9694
  doc.body?.appendChild(iframe);
9658
9695
  this.#iframe = iframe;
9659
9696
  }
9660
9697
  #handleMessage(event) {
9661
- if (!this.#iframe || event.source !== this.#iframe.contentWindow) {
9662
- return;
9663
- }
9664
- const data = event.data;
9665
- if (data?.type === USER_CONFIG_MESSAGE_SNAPSHOT && data.payload) {
9666
- const snapshot = data.payload;
9667
- this.#writeCache(snapshot);
9668
- this.snapshot.set(snapshot);
9669
- }
9670
- }
9671
- #postToBridge(message) {
9672
- if (!this.#iframe?.contentWindow) {
9698
+ if (event.source !== this.#iframe?.contentWindow) {
9673
9699
  return;
9674
9700
  }
9675
- this.#iframe.contentWindow.postMessage(message, USER_CONFIG_IFRAME_TARGET_ORIGIN);
9676
- }
9677
- #readCache() {
9678
- const store = this.#dom.localStorage;
9679
- if (!store) {
9680
- return null;
9681
- }
9682
- return this.#parse(store.getItem(USER_CONFIG_STORAGE_KEY));
9683
- }
9684
- #writeCache(config) {
9685
- const store = this.#dom.localStorage;
9686
- if (!store) {
9701
+ const messageData = event.data;
9702
+ if (messageData?.type !== USER_CONFIG_MESSAGE_SNAPSHOT) {
9687
9703
  return;
9688
9704
  }
9689
- try {
9690
- store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(config));
9691
- }
9692
- catch {
9693
- /* noop */
9694
- }
9705
+ const data = messageData?.payload ?? this.createDefaultConfig();
9706
+ this.snapshot.set(data);
9707
+ this.isReady$.next(true);
9695
9708
  }
9696
- #parse(raw) {
9697
- if (!raw) {
9698
- return null;
9699
- }
9700
- try {
9701
- return JSON.parse(raw);
9702
- }
9703
- catch {
9704
- return DEFAULT_USER_CONFIG;
9705
- }
9706
- }
9707
- #mergeWithBase(update) {
9708
- const base = this.snapshot() ?? this.read() ?? DEFAULT_USER_CONFIG;
9709
- const mergedMetadata = {
9710
- ...base.metadata,
9711
- ...update.metadata,
9712
- updatedAt: new Date().toISOString(),
9713
- };
9714
- return {
9715
- accessibility: {
9716
- ...base.accessibility,
9717
- ...update.accessibility,
9718
- },
9719
- cookieConsent: {
9720
- ...base.cookieConsent,
9721
- ...update.cookieConsent,
9722
- },
9723
- language: {
9724
- ...base.language,
9725
- ...update.language,
9726
- },
9727
- metadata: mergedMetadata,
9728
- };
9709
+ #postToBridge(message) {
9710
+ this.#iframe?.contentWindow?.postMessage(message, USER_CONFIG_IFRAME_TARGET_ORIGIN);
9729
9711
  }
9730
9712
  static { this.ɵfac = function IframeStorageStrategy_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IframeStorageStrategy)(); }; }
9731
9713
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: IframeStorageStrategy, factory: IframeStorageStrategy.ɵfac, providedIn: 'root' }); }
@@ -9735,91 +9717,60 @@ class IframeStorageStrategy {
9735
9717
  args: [{ providedIn: 'root' }]
9736
9718
  }], () => [], null); })();
9737
9719
 
9738
- class LocalStorageStrategy {
9739
- #dom;
9720
+ const USER_CONFIG_STORAGE_KEY = 'ngx_common_user_config';
9721
+
9722
+ class LocalStorageStrategy extends BaseStorageStrategy {
9723
+ #dom = inject(DOMService);
9740
9724
  #storageListener;
9741
9725
  constructor() {
9742
- this.#dom = inject(DOMService);
9743
- this.snapshot = signal(null, ...(ngDevMode ? [{ debugName: "snapshot" }] : []));
9744
- const win = this.#dom.window;
9745
- if (!win) {
9746
- return;
9747
- }
9748
- this.#storageListener = (event) => {
9749
- if (event.key !== USER_CONFIG_STORAGE_KEY || !event.newValue) {
9750
- return;
9726
+ super(inject(USER_CONFIG_OPTIONS));
9727
+ this.#init();
9728
+ }
9729
+ saveToSource(config) {
9730
+ const store = this.#dom.localStorage;
9731
+ if (store) {
9732
+ try {
9733
+ store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(config));
9751
9734
  }
9752
- const parsed = this.#parse(event.newValue);
9753
- if (parsed) {
9754
- this.snapshot.set(parsed);
9735
+ catch (exception) {
9736
+ console.error('Failed to save user config to localStorage', exception);
9755
9737
  }
9756
- };
9757
- win.addEventListener('storage', this.#storageListener);
9758
- const current = this.read();
9759
- if (current) {
9760
- this.snapshot.set(current);
9761
9738
  }
9762
9739
  }
9763
- read() {
9764
- const store = this.#dom.localStorage;
9765
- if (!store) {
9766
- return null;
9740
+ ngOnDestroy() {
9741
+ if (this.#storageListener && this.#dom.window) {
9742
+ this.#dom.window.removeEventListener('storage', this.#storageListener);
9767
9743
  }
9768
- return this.#parse(store.getItem(USER_CONFIG_STORAGE_KEY));
9769
9744
  }
9770
- write(config) {
9745
+ #init() {
9746
+ const win = this.#dom.window;
9771
9747
  const store = this.#dom.localStorage;
9772
- if (!store) {
9773
- return;
9774
- }
9775
- const merged = this.#mergeWithBase(config);
9776
- try {
9777
- store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(merged));
9778
- this.snapshot.set(merged);
9779
- }
9780
- catch {
9781
- /* noop */
9748
+ if (win) {
9749
+ this.#storageListener = (event) => {
9750
+ if (event.key === USER_CONFIG_STORAGE_KEY && event.newValue) {
9751
+ this.snapshot.set(this.#parse(event.newValue));
9752
+ }
9753
+ };
9754
+ win.addEventListener('storage', this.#storageListener);
9782
9755
  }
9783
- }
9784
- ngOnDestroy() {
9785
- if (this.#storageListener && this.#dom.window) {
9786
- this.#dom.window.removeEventListener('storage', this.#storageListener);
9756
+ if (store) {
9757
+ const raw = store.getItem(USER_CONFIG_STORAGE_KEY);
9758
+ this.snapshot.set(this.#parse(raw));
9787
9759
  }
9760
+ // LocalStorage is available immediately
9761
+ this.isReady$.next(true);
9788
9762
  }
9789
9763
  #parse(raw) {
9790
9764
  if (!raw) {
9791
- return null;
9765
+ return this.createDefaultConfig();
9792
9766
  }
9793
9767
  try {
9794
9768
  return JSON.parse(raw);
9795
9769
  }
9796
9770
  catch {
9797
- return DEFAULT_USER_CONFIG;
9771
+ return this.createDefaultConfig();
9798
9772
  }
9799
9773
  }
9800
- #mergeWithBase(update) {
9801
- const base = this.snapshot() ?? this.read() ?? DEFAULT_USER_CONFIG;
9802
- const mergedMetadata = {
9803
- ...base.metadata,
9804
- ...update.metadata,
9805
- updatedAt: new Date().toISOString(),
9806
- };
9807
- return {
9808
- accessibility: {
9809
- ...base.accessibility,
9810
- ...update.accessibility,
9811
- },
9812
- cookieConsent: {
9813
- ...base.cookieConsent,
9814
- ...update.cookieConsent,
9815
- },
9816
- language: {
9817
- ...base.language,
9818
- ...update.language,
9819
- },
9820
- metadata: mergedMetadata,
9821
- };
9822
- }
9823
9774
  static { this.ɵfac = function LocalStorageStrategy_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || LocalStorageStrategy)(); }; }
9824
9775
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: LocalStorageStrategy, factory: LocalStorageStrategy.ɵfac, providedIn: 'root' }); }
9825
9776
  }
@@ -9862,6 +9813,10 @@ function provideUserConfig(options = {}) {
9862
9813
  useFactory: userConfigStorageFactory,
9863
9814
  deps: [USER_CONFIG_OPTIONS],
9864
9815
  },
9816
+ provideAppInitializer(() => {
9817
+ const storage = inject(USER_CONFIG_STORAGE);
9818
+ return storage.initialize();
9819
+ })
9865
9820
  ]);
9866
9821
  }
9867
9822