@flusys/ng-layout 1.0.0-beta → 1.0.0-rc

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,8 +1,7 @@
1
- import * as i1 from '@angular/common';
2
- import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
1
+ import { isPlatformBrowser, NgClass, DOCUMENT as DOCUMENT$1 } from '@angular/common';
3
2
  import * as i0 from '@angular/core';
4
- import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, Component, ChangeDetectionStrategy, InjectionToken, DestroyRef, ElementRef, viewChild, input, Renderer2, ViewChild } from '@angular/core';
5
- import * as i2 from '@angular/forms';
3
+ import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, afterNextRender, ChangeDetectionStrategy, Component, InjectionToken, DestroyRef, ElementRef, viewChild, input, Renderer2 } from '@angular/core';
4
+ import * as i1 from '@angular/forms';
6
5
  import { FormsModule } from '@angular/forms';
7
6
  import * as i1$2 from '@angular/router';
8
7
  import { Router, RouterModule, NavigationEnd } from '@angular/router';
@@ -10,12 +9,11 @@ import { updatePreset, updateSurfacePalette, $t, definePreset } from '@primeuix/
10
9
  import Aura from '@primeuix/themes/aura';
11
10
  import Lara from '@primeuix/themes/lara';
12
11
  import Nora from '@primeuix/themes/nora';
13
- import { PrimeNG } from 'primeng/config';
14
- import * as i3 from 'primeng/selectbutton';
12
+ import * as i2 from 'primeng/selectbutton';
15
13
  import { SelectButtonModule } from 'primeng/selectbutton';
16
14
  import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
17
15
  import * as i2$2 from '@flusys/ng-shared';
18
- import { evaluateLogicNode, PermissionValidatorService, PlatformService, AngularModule, IconComponent } from '@flusys/ng-shared';
16
+ import { evaluateLogicNode, PermissionValidatorService, AngularModule, IconComponent } from '@flusys/ng-shared';
19
17
  import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
20
18
  import * as i1$1 from 'primeng/button';
21
19
  import { ButtonModule } from 'primeng/button';
@@ -235,17 +233,25 @@ class LayoutService {
235
233
  staticMenuMobileActive: false,
236
234
  menuHoverActive: false,
237
235
  };
238
- // Signals
239
- layoutConfig = signal(this.initialConfig, ...(ngDevMode ? [{ debugName: "layoutConfig" }] : []));
240
- layoutState = signal(this.defaultState, ...(ngDevMode ? [{ debugName: "layoutState" }] : []));
241
- transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "transitionComplete" }] : []));
242
- // User Profile Signals
243
- userProfile = signal(null, ...(ngDevMode ? [{ debugName: "userProfile" }] : []));
244
- companyProfile = signal(null, ...(ngDevMode ? [{ debugName: "companyProfile" }] : []));
245
- appName = signal(this.appConfig?.appName ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "appName" }] : []));
246
- // Author/Brand Signals
247
- authorName = signal(this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name, ...(ngDevMode ? [{ debugName: "authorName" }] : []));
248
- authorUrl = signal(this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url, ...(ngDevMode ? [{ debugName: "authorUrl" }] : []));
236
+ // Private writable signals with public readonly accessors
237
+ _layoutConfig = signal(this.initialConfig, ...(ngDevMode ? [{ debugName: "_layoutConfig" }] : []));
238
+ _layoutState = signal(this.defaultState, ...(ngDevMode ? [{ debugName: "_layoutState" }] : []));
239
+ _transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "_transitionComplete" }] : []));
240
+ layoutConfig = this._layoutConfig.asReadonly();
241
+ layoutState = this._layoutState.asReadonly();
242
+ transitionComplete = this._transitionComplete.asReadonly();
243
+ // User Profile Signals (private writable, public readonly)
244
+ _userProfile = signal(null, ...(ngDevMode ? [{ debugName: "_userProfile" }] : []));
245
+ _companyProfile = signal(null, ...(ngDevMode ? [{ debugName: "_companyProfile" }] : []));
246
+ _appName = signal(this.appConfig?.appName ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "_appName" }] : []));
247
+ userProfile = this._userProfile.asReadonly();
248
+ companyProfile = this._companyProfile.asReadonly();
249
+ appName = this._appName.asReadonly();
250
+ // Author/Brand Signals (private writable, public readonly)
251
+ _authorName = signal(this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name, ...(ngDevMode ? [{ debugName: "_authorName" }] : []));
252
+ _authorUrl = signal(this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url, ...(ngDevMode ? [{ debugName: "_authorUrl" }] : []));
253
+ authorName = this._authorName.asReadonly();
254
+ authorUrl = this._authorUrl.asReadonly();
249
255
  // App Launcher Signals
250
256
  _rawApps = signal([], ...(ngDevMode ? [{ debugName: "_rawApps" }] : []));
251
257
  /**
@@ -271,25 +277,25 @@ class LayoutService {
271
277
  return filterMenuByPermissions(raw, permission);
272
278
  }, ...(ngDevMode ? [{ debugName: "menu" }] : []));
273
279
  // Computed signals - Layout
274
- isSidebarActive = computed(() => this.layoutState().overlayMenuActive ||
275
- this.layoutState().staticMenuMobileActive, ...(ngDevMode ? [{ debugName: "isSidebarActive" }] : []));
276
- isDarkTheme = computed(() => this.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
277
- getPrimary = computed(() => this.layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
278
- getSurface = computed(() => this.layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
279
- isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
280
+ isSidebarActive = computed(() => this._layoutState().overlayMenuActive ||
281
+ this._layoutState().staticMenuMobileActive, ...(ngDevMode ? [{ debugName: "isSidebarActive" }] : []));
282
+ isDarkTheme = computed(() => this._layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
283
+ getPrimary = computed(() => this._layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
284
+ getSurface = computed(() => this._layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
285
+ isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
280
286
  // Computed signals - User Profile
281
- userName = computed(() => this.userProfile()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
282
- userEmail = computed(() => this.userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
283
- userProfilePictureUrl = computed(() => this.userProfile()?.profilePictureUrl ?? null, ...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : []));
287
+ userName = computed(() => this._userProfile()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
288
+ userEmail = computed(() => this._userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
289
+ userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl ?? null, ...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : []));
284
290
  companyName = computed(() => {
285
291
  // If company feature is disabled, always show app name
286
292
  if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig)) {
287
- return this.appName();
293
+ return this._appName();
288
294
  }
289
- return this.companyProfile()?.name ?? this.appName();
295
+ return this._companyProfile()?.name ?? this._appName();
290
296
  }, ...(ngDevMode ? [{ debugName: "companyName" }] : []));
291
- companyLogoUrl = computed(() => this.companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
292
- isAuthenticated = computed(() => !!this.userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
297
+ companyLogoUrl = computed(() => this._companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
298
+ isAuthenticated = computed(() => !!this._userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
293
299
  // Computed signals - App Launcher
294
300
  hasApps = computed(() => this.apps().length > 0, ...(ngDevMode ? [{ debugName: "hasApps" }] : []));
295
301
  // RxJS Subjects for event communication
@@ -304,13 +310,13 @@ class LayoutService {
304
310
  initialized = false;
305
311
  constructor() {
306
312
  effect(() => {
307
- const config = this.layoutConfig();
313
+ const config = this._layoutConfig();
308
314
  if (config) {
309
315
  this.onConfigUpdate();
310
316
  }
311
317
  });
312
318
  effect(() => {
313
- const config = this.layoutConfig();
319
+ const config = this._layoutConfig();
314
320
  if (!this.initialized || !config) {
315
321
  this.initialized = true;
316
322
  return;
@@ -319,29 +325,31 @@ class LayoutService {
319
325
  });
320
326
  // Auto-save configuration changes to localStorage
321
327
  effect(() => {
322
- const config = this.layoutConfig();
328
+ const config = this._layoutConfig();
323
329
  if (config && this.initialized) {
324
330
  this.persistence.save(config);
325
331
  }
326
332
  });
327
333
  }
328
334
  handleDarkModeTransition(config) {
329
- if (this.document.startViewTransition) {
330
- this.startViewTransition(config);
335
+ const doc = this.document;
336
+ // Check for View Transitions API support (not all browsers support it)
337
+ if ('startViewTransition' in doc && typeof doc.startViewTransition === 'function') {
338
+ this.startViewTransition(config, doc);
331
339
  }
332
340
  else {
333
341
  this.toggleDarkMode(config);
334
342
  this.onTransitionEnd();
335
343
  }
336
344
  }
337
- startViewTransition(config) {
338
- const transition = this.document.startViewTransition(() => {
345
+ startViewTransition(config, doc) {
346
+ const transition = doc.startViewTransition(() => {
339
347
  this.toggleDarkMode(config);
340
348
  });
341
349
  transition.ready.then(() => this.onTransitionEnd()).catch(() => { });
342
350
  }
343
351
  toggleDarkMode(config) {
344
- const _config = config || this.layoutConfig();
352
+ const _config = config || this._layoutConfig();
345
353
  if (_config.darkTheme) {
346
354
  this.document.documentElement.classList.add('app-dark');
347
355
  }
@@ -350,31 +358,31 @@ class LayoutService {
350
358
  }
351
359
  }
352
360
  onTransitionEnd() {
353
- this.transitionComplete.set(true);
354
- setTimeout(() => this.transitionComplete.set(false));
361
+ this._transitionComplete.set(true);
362
+ setTimeout(() => this._transitionComplete.set(false));
355
363
  }
356
364
  onMenuToggle() {
357
365
  if (this.isOverlay()) {
358
- this.layoutState.update((prev) => ({
366
+ this._layoutState.update((prev) => ({
359
367
  ...prev,
360
- overlayMenuActive: !this.layoutState().overlayMenuActive,
368
+ overlayMenuActive: !this._layoutState().overlayMenuActive,
361
369
  }));
362
- if (this.layoutState().overlayMenuActive) {
370
+ if (this._layoutState().overlayMenuActive) {
363
371
  this.overlayOpen.next();
364
372
  }
365
373
  }
366
374
  if (this.isDesktop()) {
367
- this.layoutState.update((prev) => ({
375
+ this._layoutState.update((prev) => ({
368
376
  ...prev,
369
- staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive,
377
+ staticMenuDesktopInactive: !this._layoutState().staticMenuDesktopInactive,
370
378
  }));
371
379
  }
372
380
  else {
373
- this.layoutState.update((prev) => ({
381
+ this._layoutState.update((prev) => ({
374
382
  ...prev,
375
- staticMenuMobileActive: !this.layoutState().staticMenuMobileActive,
383
+ staticMenuMobileActive: !this._layoutState().staticMenuMobileActive,
376
384
  }));
377
- if (this.layoutState().staticMenuMobileActive) {
385
+ if (this._layoutState().staticMenuMobileActive) {
378
386
  this.overlayOpen.next();
379
387
  }
380
388
  }
@@ -386,7 +394,7 @@ class LayoutService {
386
394
  return !this.isDesktop();
387
395
  }
388
396
  onConfigUpdate() {
389
- this.configUpdate.next(this.layoutConfig());
397
+ this.configUpdate.next(this._layoutConfig());
390
398
  }
391
399
  onMenuStateChange(event) {
392
400
  this.menuSource.next(event);
@@ -395,6 +403,23 @@ class LayoutService {
395
403
  this.resetSource.next(true);
396
404
  }
397
405
  // ==========================================================================
406
+ // Layout Config Methods
407
+ // ==========================================================================
408
+ /**
409
+ * Update layout configuration.
410
+ * Called by configurator component.
411
+ */
412
+ updateLayoutConfig(config) {
413
+ this._layoutConfig.update((prev) => ({ ...prev, ...config }));
414
+ }
415
+ /**
416
+ * Update layout state.
417
+ * Called internally and by layout components.
418
+ */
419
+ updateLayoutState(state) {
420
+ this._layoutState.update((prev) => ({ ...prev, ...state }));
421
+ }
422
+ // ==========================================================================
398
423
  // User Profile Methods
399
424
  // ==========================================================================
400
425
  /**
@@ -402,14 +427,14 @@ class LayoutService {
402
427
  * Called by auth integration to sync user data.
403
428
  */
404
429
  setUserProfile(profile) {
405
- this.userProfile.set(profile);
430
+ this._userProfile.set(profile);
406
431
  }
407
432
  /**
408
433
  * Set the current company profile for display in layout.
409
434
  * Called by auth integration to sync company data.
410
435
  */
411
436
  setCompanyProfile(profile) {
412
- this.companyProfile.set(profile);
437
+ this._companyProfile.set(profile);
413
438
  }
414
439
  // ==========================================================================
415
440
  // Menu Methods
@@ -464,19 +489,19 @@ const presets = {
464
489
  };
465
490
  class AppConfigurator {
466
491
  router = inject(Router);
467
- primeng = inject(PrimeNG);
468
492
  layoutService = inject(LayoutService);
469
- platformService = inject(PlatformService);
470
493
  presets = Object.keys(presets);
471
494
  showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
472
495
  menuModeOptions = [
473
496
  { label: 'Static', value: 'static' },
474
497
  { label: 'Overlay', value: 'overlay' },
475
498
  ];
476
- ngOnInit() {
477
- if (!this.platformService.isServer) {
478
- this.onPresetChange(this.layoutService.layoutConfig().preset);
479
- }
499
+ constructor() {
500
+ // Use afterNextRender for browser-only initialization (replaces isServer check)
501
+ afterNextRender(() => {
502
+ const preset = this.layoutService.layoutConfig().preset ?? 'Aura';
503
+ this.onPresetChange(preset);
504
+ });
480
505
  }
481
506
  surfaces = [
482
507
  {
@@ -782,16 +807,10 @@ class AppConfigurator {
782
807
  }
783
808
  updateColors(event, type, color) {
784
809
  if (type === 'primary') {
785
- this.layoutService.layoutConfig.update((state) => ({
786
- ...state,
787
- primary: color.name,
788
- }));
810
+ this.layoutService.updateLayoutConfig({ primary: color.name });
789
811
  }
790
812
  else if (type === 'surface') {
791
- this.layoutService.layoutConfig.update((state) => ({
792
- ...state,
793
- surface: color.name,
794
- }));
813
+ this.layoutService.updateLayoutConfig({ surface: color.name });
795
814
  }
796
815
  this.applyTheme(type, color);
797
816
  event.stopPropagation();
@@ -805,12 +824,12 @@ class AppConfigurator {
805
824
  }
806
825
  }
807
826
  onPresetChange(event) {
808
- this.layoutService.layoutConfig.update((state) => ({
809
- ...state,
810
- preset: event,
811
- }));
827
+ this.layoutService.updateLayoutConfig({ preset: event });
812
828
  const preset = presets[event];
813
- const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
829
+ // Use selected surface or default (slate for light, zinc for dark)
830
+ const selectedSurface = this.selectedSurfaceColor();
831
+ const defaultSurface = this.layoutService.layoutConfig().darkTheme ? 'zinc' : 'slate';
832
+ const surfacePalette = this.surfaces.find((s) => s.name === (selectedSurface || defaultSurface))?.palette;
814
833
  $t()
815
834
  .preset(preset)
816
835
  .preset(this.getPresetExt())
@@ -818,13 +837,10 @@ class AppConfigurator {
818
837
  .use({ useDefaultOptions: true });
819
838
  }
820
839
  onMenuModeChange(event) {
821
- this.layoutService.layoutConfig.update((prev) => ({
822
- ...prev,
823
- menuMode: event,
824
- }));
840
+ this.layoutService.updateLayoutConfig({ menuMode: event });
825
841
  }
826
842
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
827
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay)", classAttribute: "hidden absolute top-[3.25rem] right-0 w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]" }, ngImport: i0, template: `
843
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay)", classAttribute: "hidden absolute top-[3.25rem] right-0 w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]" }, ngImport: i0, template: `
828
844
  <div class="flex flex-col gap-4">
829
845
  <div>
830
846
  <span class="text-sm text-muted-color font-semibold">Primary</span>
@@ -891,14 +907,15 @@ class AppConfigurator {
891
907
  </div>
892
908
  }
893
909
  </div>
894
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i3.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }] });
910
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i2.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
895
911
  }
896
912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppConfigurator, decorators: [{
897
913
  type: Component,
898
914
  args: [{
899
915
  selector: 'app-configurator',
900
916
  standalone: true,
901
- imports: [CommonModule, FormsModule, SelectButtonModule],
917
+ changeDetection: ChangeDetectionStrategy.OnPush,
918
+ imports: [NgClass, FormsModule, SelectButtonModule],
902
919
  template: `
903
920
  <div class="flex flex-col gap-4">
904
921
  <div>
@@ -968,36 +985,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
968
985
  </div>
969
986
  `,
970
987
  host: {
971
- class: 'hidden absolute top-[3.25rem] right-0 w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]',
988
+ class: 'hidden absolute top-[3.25rem] right-0 w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]',
972
989
  style: 'background-color: var(--surface-overlay)',
973
990
  },
974
991
  }]
975
- }] });
992
+ }], ctorParameters: () => [] });
976
993
 
977
994
  class AppFloatingConfigurator {
978
- LayoutService = inject(LayoutService);
979
- isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
995
+ layoutService = inject(LayoutService);
996
+ isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
980
997
  toggleDarkMode() {
981
- this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
998
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
999
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
982
1000
  }
983
1001
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
984
1002
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
985
- <div class="fixed flex flex-col md:flex-row gap-4 top-8 right-0 md:right-8 z-99">
1003
+ <div class="fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 right-2 md:right-8 z-50">
986
1004
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
987
1005
  <div class="relative">
988
1006
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
989
1007
  <app-configurator />
990
1008
  </div>
991
1009
  </div>
992
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }] });
1010
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
993
1011
  }
994
1012
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
995
1013
  type: Component,
996
1014
  args: [{
997
1015
  selector: 'app-floating-configurator',
1016
+ standalone: true,
1017
+ changeDetection: ChangeDetectionStrategy.OnPush,
998
1018
  imports: [ButtonModule, StyleClassModule, AppConfigurator],
999
1019
  template: `
1000
- <div class="fixed flex flex-col md:flex-row gap-4 top-8 right-0 md:right-8 z-99">
1020
+ <div class="fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 right-2 md:right-8 z-50">
1001
1021
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
1002
1022
  <div class="relative">
1003
1023
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
@@ -1036,6 +1056,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1036
1056
  const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
1037
1057
  const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
1038
1058
 
1059
+ /**
1060
+ * View Transitions API type definitions
1061
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
1062
+ */
1063
+
1039
1064
  /** Company/branch switcher displayed in top bar */
1040
1065
  class AppCompanyBranchSelector {
1041
1066
  destroyRef = inject(DestroyRef);
@@ -1044,26 +1069,34 @@ class AppCompanyBranchSelector {
1044
1069
  messageService = inject(MessageService);
1045
1070
  document = inject(DOCUMENT$1);
1046
1071
  elementRef = inject(ElementRef);
1047
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1072
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1073
+ isActive = this._isActive.asReadonly();
1048
1074
  constructor() {
1049
1075
  fromEvent(this.document, 'click')
1050
1076
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1051
1077
  .subscribe((event) => {
1052
1078
  const target = event.target;
1053
1079
  if (!this.elementRef.nativeElement.contains(target)) {
1054
- this.isActive.set(false);
1080
+ this._isActive.set(false);
1055
1081
  }
1056
1082
  });
1057
1083
  }
1058
1084
  currentCompanyName = computed(() => this.authState?.currentCompanyInfo()?.name ?? 'No Company', ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
1059
1085
  currentBranchName = computed(() => this.authState?.currentBranchInfo()?.name ?? null, ...(ngDevMode ? [{ debugName: "currentBranchName" }] : []));
1060
- companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
1061
- branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
1062
- selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
1063
- selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
1064
- isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingCompanies" }] : []));
1065
- isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingBranches" }] : []));
1066
- isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "isSwitching" }] : []));
1086
+ _companies = signal([], ...(ngDevMode ? [{ debugName: "_companies" }] : []));
1087
+ companies = this._companies.asReadonly();
1088
+ _branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
1089
+ branches = this._branches.asReadonly();
1090
+ _selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
1091
+ selectedCompanyId = this._selectedCompanyId.asReadonly();
1092
+ _selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
1093
+ selectedBranchId = this._selectedBranchId.asReadonly();
1094
+ _isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
1095
+ isLoadingCompanies = this._isLoadingCompanies.asReadonly();
1096
+ _isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
1097
+ isLoadingBranches = this._isLoadingBranches.asReadonly();
1098
+ _isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
1099
+ isSwitching = this._isSwitching.asReadonly();
1067
1100
  canSwitch = computed(() => {
1068
1101
  const selectedCompany = this.selectedCompanyId();
1069
1102
  if (!selectedCompany)
@@ -1074,7 +1107,7 @@ class AppCompanyBranchSelector {
1074
1107
  return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
1075
1108
  }, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
1076
1109
  onPanelToggle() {
1077
- this.isActive.update((v) => !v);
1110
+ this._isActive.update((v) => !v);
1078
1111
  if (this.isActive() && this.companies().length === 0) {
1079
1112
  this.loadCompanies();
1080
1113
  }
@@ -1082,61 +1115,53 @@ class AppCompanyBranchSelector {
1082
1115
  loadCompanies() {
1083
1116
  if (!this.authApi)
1084
1117
  return;
1085
- this.isLoadingCompanies.set(true);
1118
+ this._isLoadingCompanies.set(true);
1086
1119
  this.authApi
1087
1120
  .getUserCompanies()
1088
1121
  .pipe(takeUntilDestroyed(this.destroyRef))
1089
1122
  .subscribe({
1090
1123
  next: (companies) => {
1091
- this.companies.set(companies);
1092
- this.isLoadingCompanies.set(false);
1124
+ this._companies.set(companies);
1125
+ this._isLoadingCompanies.set(false);
1093
1126
  },
1094
- error: (err) => {
1095
- this.isLoadingCompanies.set(false);
1096
- this.messageService.add({
1097
- severity: 'error',
1098
- summary: 'Error',
1099
- detail: err?.message || 'Failed to load companies',
1100
- });
1127
+ error: () => {
1128
+ // Error toast handled by global interceptor
1129
+ this._isLoadingCompanies.set(false);
1101
1130
  },
1102
1131
  });
1103
1132
  }
1104
1133
  onCompanyChange(companyId) {
1105
- this.selectedCompanyId.set(companyId);
1134
+ this._selectedCompanyId.set(companyId);
1106
1135
  if (!companyId) {
1107
- this.branches.set([]);
1108
- this.selectedBranchId.set(null);
1136
+ this._branches.set([]);
1137
+ this._selectedBranchId.set(null);
1109
1138
  return;
1110
1139
  }
1111
1140
  this.loadBranches(companyId);
1112
1141
  }
1113
1142
  onBranchChange(branchId) {
1114
- this.selectedBranchId.set(branchId);
1143
+ this._selectedBranchId.set(branchId);
1115
1144
  }
1116
1145
  loadBranches(companyId) {
1117
1146
  if (!this.authApi)
1118
1147
  return;
1119
- this.isLoadingBranches.set(true);
1120
- this.selectedBranchId.set(null);
1148
+ this._isLoadingBranches.set(true);
1149
+ this._selectedBranchId.set(null);
1121
1150
  this.authApi
1122
1151
  .getCompanyBranches(companyId)
1123
1152
  .pipe(takeUntilDestroyed(this.destroyRef))
1124
1153
  .subscribe({
1125
1154
  next: (branches) => {
1126
- this.branches.set(branches);
1127
- this.isLoadingBranches.set(false);
1155
+ this._branches.set(branches);
1156
+ this._isLoadingBranches.set(false);
1128
1157
  // Auto-select if only one branch available
1129
1158
  if (branches.length === 1) {
1130
- this.selectedBranchId.set(branches[0].id);
1159
+ this._selectedBranchId.set(branches[0].id);
1131
1160
  }
1132
1161
  },
1133
- error: (err) => {
1134
- this.isLoadingBranches.set(false);
1135
- this.messageService.add({
1136
- severity: 'error',
1137
- summary: 'Error',
1138
- detail: err?.message || 'Failed to load branches',
1139
- });
1162
+ error: () => {
1163
+ // Error toast handled by global interceptor
1164
+ this._isLoadingBranches.set(false);
1140
1165
  },
1141
1166
  });
1142
1167
  }
@@ -1153,19 +1178,15 @@ class AppCompanyBranchSelector {
1153
1178
  });
1154
1179
  return;
1155
1180
  }
1156
- this.isSwitching.set(true);
1181
+ this._isSwitching.set(true);
1157
1182
  this.authApi
1158
1183
  .switchCompany(selectedCompany, selectedBranch || '')
1159
1184
  .pipe(takeUntilDestroyed(this.destroyRef))
1160
1185
  .subscribe({
1161
1186
  next: () => { },
1162
- error: (err) => {
1163
- this.isSwitching.set(false);
1164
- this.messageService.add({
1165
- severity: 'error',
1166
- summary: 'Error',
1167
- detail: err?.message || 'Failed to switch company',
1168
- });
1187
+ error: () => {
1188
+ // Error toast handled by global interceptor
1189
+ this._isSwitching.set(false);
1169
1190
  },
1170
1191
  });
1171
1192
  }
@@ -1190,10 +1211,10 @@ class AppCompanyBranchSelector {
1190
1211
  }
1191
1212
  </button>
1192
1213
  <div
1193
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1214
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
1194
1215
  style="background-color: var(--surface-overlay)"
1195
1216
  >
1196
- <div class="flex flex-col gap-4 min-w-[280px]">
1217
+ <div class="flex flex-col gap-4">
1197
1218
  <span class="text-sm text-muted-color font-semibold"
1198
1219
  >Switch Company & Branch</span
1199
1220
  >
@@ -1243,13 +1264,14 @@ class AppCompanyBranchSelector {
1243
1264
  </div>
1244
1265
  </div>
1245
1266
  </div>
1246
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] });
1267
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1247
1268
  }
1248
1269
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
1249
1270
  type: Component,
1250
1271
  args: [{
1251
1272
  selector: 'app-company-branch-selector',
1252
1273
  standalone: true,
1274
+ changeDetection: ChangeDetectionStrategy.OnPush,
1253
1275
  imports: [AngularModule, StyleClassModule, ButtonModule, SelectModule],
1254
1276
  host: { class: 'relative' },
1255
1277
  template: `
@@ -1272,10 +1294,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1272
1294
  }
1273
1295
  </button>
1274
1296
  <div
1275
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1297
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
1276
1298
  style="background-color: var(--surface-overlay)"
1277
1299
  >
1278
- <div class="flex flex-col gap-4 min-w-[280px]">
1300
+ <div class="flex flex-col gap-4">
1279
1301
  <span class="text-sm text-muted-color font-semibold"
1280
1302
  >Switch Company & Branch</span
1281
1303
  >
@@ -1334,19 +1356,20 @@ class AppLauncher {
1334
1356
  document = inject(DOCUMENT$1);
1335
1357
  elementRef = inject(ElementRef);
1336
1358
  layoutService = inject(LayoutService);
1337
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1359
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1360
+ isActive = this._isActive.asReadonly();
1338
1361
  constructor() {
1339
1362
  fromEvent(this.document, 'click')
1340
- .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1363
+ .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this._isActive()))
1341
1364
  .subscribe((event) => {
1342
1365
  const target = event.target;
1343
1366
  if (!this.elementRef.nativeElement.contains(target)) {
1344
- this.isActive.set(false);
1367
+ this._isActive.set(false);
1345
1368
  }
1346
1369
  });
1347
1370
  }
1348
1371
  togglePanel() {
1349
- this.isActive.update((v) => !v);
1372
+ this._isActive.update((v) => !v);
1350
1373
  }
1351
1374
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, deps: [], target: i0.ɵɵFactoryTarget.Component });
1352
1375
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppLauncher, isStandalone: true, selector: "app-launcher", host: { classAttribute: "relative" }, ngImport: i0, template: `
@@ -1366,15 +1389,15 @@ class AppLauncher {
1366
1389
  <span>Apps</span>
1367
1390
  </button>
1368
1391
  <div
1369
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1392
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1370
1393
  style="background-color: var(--surface-overlay)"
1371
1394
  >
1372
- <div class="flex flex-col gap-2 min-w-[240px]">
1395
+ <div class="flex flex-col gap-2">
1373
1396
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1374
- <div class="grid grid-cols-3 gap-2">
1397
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1375
1398
  @for (app of layoutService.apps(); track app.id) {
1376
1399
  <a
1377
- class="group flex flex-col items-center w-20 p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1400
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1378
1401
  [href]="app.url"
1379
1402
  target="_blank"
1380
1403
  rel="noopener noreferrer"
@@ -1389,7 +1412,7 @@ class AppLauncher {
1389
1412
  />
1390
1413
  </div>
1391
1414
  <span
1392
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1415
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1393
1416
  >{{ app.name }}</span
1394
1417
  >
1395
1418
  </a>
@@ -1397,13 +1420,14 @@ class AppLauncher {
1397
1420
  </div>
1398
1421
  </div>
1399
1422
  </div>
1400
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }] });
1423
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1401
1424
  }
1402
1425
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, decorators: [{
1403
1426
  type: Component,
1404
1427
  args: [{
1405
1428
  selector: 'app-launcher',
1406
1429
  standalone: true,
1430
+ changeDetection: ChangeDetectionStrategy.OnPush,
1407
1431
  imports: [StyleClassModule, IconComponent],
1408
1432
  host: { class: 'relative' },
1409
1433
  template: `
@@ -1423,15 +1447,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1423
1447
  <span>Apps</span>
1424
1448
  </button>
1425
1449
  <div
1426
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1450
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1427
1451
  style="background-color: var(--surface-overlay)"
1428
1452
  >
1429
- <div class="flex flex-col gap-2 min-w-[240px]">
1453
+ <div class="flex flex-col gap-2">
1430
1454
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1431
- <div class="grid grid-cols-3 gap-2">
1455
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1432
1456
  @for (app of layoutService.apps(); track app.id) {
1433
1457
  <a
1434
- class="group flex flex-col items-center w-20 p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1458
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1435
1459
  [href]="app.url"
1436
1460
  target="_blank"
1437
1461
  rel="noopener noreferrer"
@@ -1446,7 +1470,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1446
1470
  />
1447
1471
  </div>
1448
1472
  <span
1449
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1473
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1450
1474
  >{{ app.name }}</span
1451
1475
  >
1452
1476
  </a>
@@ -1462,26 +1486,28 @@ class AppProfile {
1462
1486
  destroyRef = inject(DestroyRef);
1463
1487
  authState = inject(LAYOUT_AUTH_STATE, { optional: true });
1464
1488
  authApi = inject(LAYOUT_AUTH_API, { optional: true });
1489
+ layoutService = inject(LayoutService);
1465
1490
  messageService = inject(MessageService);
1466
1491
  document = inject(DOCUMENT$1);
1467
1492
  elementRef = inject(ElementRef);
1468
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1493
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1494
+ isActive = this._isActive.asReadonly();
1469
1495
  constructor() {
1470
1496
  fromEvent(this.document, 'click')
1471
1497
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1472
1498
  .subscribe((event) => {
1473
1499
  const target = event.target;
1474
1500
  if (!this.elementRef.nativeElement.contains(target)) {
1475
- this.isActive.set(false);
1501
+ this._isActive.set(false);
1476
1502
  }
1477
1503
  });
1478
1504
  }
1479
1505
  togglePanel() {
1480
- this.isActive.update((v) => !v);
1506
+ this._isActive.update((v) => !v);
1481
1507
  }
1482
1508
  userName = computed(() => this.authState?.loginUserData()?.name ?? 'Guest', ...(ngDevMode ? [{ debugName: "userName" }] : []));
1483
1509
  userEmail = computed(() => this.authState?.loginUserData()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
1484
- profilePicture = computed(() => this.authState?.loginUserData()?.profilePicture?.url ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
1510
+ profilePicture = computed(() => this.layoutService.userProfilePictureUrl() ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
1485
1511
  logout() {
1486
1512
  if (!this.authApi)
1487
1513
  return;
@@ -1497,12 +1523,8 @@ class AppProfile {
1497
1523
  });
1498
1524
  this.authApi?.navigateLogin(false);
1499
1525
  },
1500
- error: (err) => {
1501
- this.messageService.add({
1502
- severity: 'error',
1503
- summary: 'Error',
1504
- detail: err?.message || 'Failed to logout',
1505
- });
1526
+ error: () => {
1527
+ // Error toast handled by global interceptor
1506
1528
  },
1507
1529
  });
1508
1530
  }
@@ -1544,10 +1566,10 @@ class AppProfile {
1544
1566
  <span>Profile</span>
1545
1567
  </button>
1546
1568
  <div
1547
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1569
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1548
1570
  style="background-color: var(--surface-overlay)"
1549
1571
  >
1550
- <div class="flex flex-col gap-3 min-w-[240px]">
1572
+ <div class="flex flex-col gap-3">
1551
1573
  <!-- User Info -->
1552
1574
  <a
1553
1575
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1566,9 +1588,9 @@ class AppProfile {
1566
1588
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1567
1589
  </div>
1568
1590
  }
1569
- <div class="flex flex-col">
1570
- <span class="font-semibold text-color">{{ userName() }}</span>
1571
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1591
+ <div class="flex flex-col min-w-0 flex-1">
1592
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1593
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1572
1594
  </div>
1573
1595
  </a>
1574
1596
 
@@ -1594,13 +1616,14 @@ class AppProfile {
1594
1616
  </div>
1595
1617
  </div>
1596
1618
  </div>
1597
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$2.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }] });
1619
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$2.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1598
1620
  }
1599
1621
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppProfile, decorators: [{
1600
1622
  type: Component,
1601
1623
  args: [{
1602
1624
  selector: 'app-profile',
1603
1625
  standalone: true,
1626
+ changeDetection: ChangeDetectionStrategy.OnPush,
1604
1627
  imports: [AngularModule, StyleClassModule],
1605
1628
  host: { class: 'relative' },
1606
1629
  template: `
@@ -1620,10 +1643,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1620
1643
  <span>Profile</span>
1621
1644
  </button>
1622
1645
  <div
1623
- class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
1646
+ class="hidden absolute top-[3.25rem] right-0 z-50 p-4 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
1624
1647
  style="background-color: var(--surface-overlay)"
1625
1648
  >
1626
- <div class="flex flex-col gap-3 min-w-[240px]">
1649
+ <div class="flex flex-col gap-3">
1627
1650
  <!-- User Info -->
1628
1651
  <a
1629
1652
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1642,9 +1665,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1642
1665
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1643
1666
  </div>
1644
1667
  }
1645
- <div class="flex flex-col">
1646
- <span class="font-semibold text-color">{{ userName() }}</span>
1647
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1668
+ <div class="flex flex-col min-w-0 flex-1">
1669
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1670
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1648
1671
  </div>
1649
1672
  </a>
1650
1673
 
@@ -1688,16 +1711,13 @@ class AppTopbar {
1688
1711
  this.handleOutsideClick(event);
1689
1712
  });
1690
1713
  }
1691
- // Use layoutService.companyName which falls back to appName
1692
1714
  companyName = this.layoutService.companyName;
1693
1715
  enableCompanyFeature = computed(() => {
1694
1716
  return isCompanyFeatureEnabled(this.appConfig);
1695
1717
  }, ...(ngDevMode ? [{ debugName: "enableCompanyFeature" }] : []));
1696
1718
  toggleDarkMode() {
1697
- this.layoutService.layoutConfig.update((state) => ({
1698
- ...state,
1699
- darkTheme: !state.darkTheme,
1700
- }));
1719
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
1720
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
1701
1721
  }
1702
1722
  togglePanel(panel) {
1703
1723
  this.activePanel.update((current) => (current === panel ? null : panel));
@@ -1731,11 +1751,9 @@ class AppTopbar {
1731
1751
  (click)="toggleDarkMode()"
1732
1752
  >
1733
1753
  <i
1734
- [ngClass]="{
1735
- 'pi ': true,
1736
- 'pi-moon': layoutService.isDarkTheme(),
1737
- 'pi-sun': !layoutService.isDarkTheme(),
1738
- }"
1754
+ class="pi"
1755
+ [class.pi-moon]="layoutService.isDarkTheme()"
1756
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1739
1757
  ></i>
1740
1758
  </button>
1741
1759
  <div class="relative" #configContainer>
@@ -1780,16 +1798,16 @@ class AppTopbar {
1780
1798
  </div>
1781
1799
  </div>
1782
1800
  </div>
1783
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }] });
1801
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i2$1.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1784
1802
  }
1785
1803
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppTopbar, decorators: [{
1786
1804
  type: Component,
1787
1805
  args: [{
1788
1806
  selector: 'app-topbar',
1789
1807
  standalone: true,
1808
+ changeDetection: ChangeDetectionStrategy.OnPush,
1790
1809
  imports: [
1791
1810
  RouterModule,
1792
- CommonModule,
1793
1811
  StyleClassModule,
1794
1812
  AppConfigurator,
1795
1813
  AppProfile,
@@ -1817,11 +1835,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1817
1835
  (click)="toggleDarkMode()"
1818
1836
  >
1819
1837
  <i
1820
- [ngClass]="{
1821
- 'pi ': true,
1822
- 'pi-moon': layoutService.isDarkTheme(),
1823
- 'pi-sun': !layoutService.isDarkTheme(),
1824
- }"
1838
+ class="pi"
1839
+ [class.pi-moon]="layoutService.isDarkTheme()"
1840
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1825
1841
  ></i>
1826
1842
  </button>
1827
1843
  <div class="relative" #configContainer>
@@ -1878,10 +1894,10 @@ class AppMenuitem {
1878
1894
  // Injected services
1879
1895
  router = inject(Router);
1880
1896
  layoutService = inject(LayoutService);
1881
- authState = inject(LAYOUT_AUTH_STATE, { optional: true });
1882
1897
  destroyRef = inject(DestroyRef);
1883
- // State signals
1884
- active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
1898
+ // State signals - private writable + public readonly pattern
1899
+ _active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
1900
+ active = this._active.asReadonly();
1885
1901
  // Computed signals
1886
1902
  key = computed(() => {
1887
1903
  const parent = this.parentKey();
@@ -1910,19 +1926,19 @@ class AppMenuitem {
1910
1926
  Promise.resolve(null).then(() => {
1911
1927
  const currentKey = this.key();
1912
1928
  if (value.routeEvent) {
1913
- this.active.set(value.key === currentKey ||
1929
+ this._active.set(value.key === currentKey ||
1914
1930
  value.key?.startsWith(currentKey + '-'));
1915
1931
  }
1916
1932
  else if (value.key !== currentKey &&
1917
1933
  !value.key?.startsWith(currentKey + '-')) {
1918
- this.active.set(false);
1934
+ this._active.set(false);
1919
1935
  }
1920
1936
  });
1921
1937
  });
1922
1938
  // Subscribe to menu reset
1923
1939
  this.layoutService.resetSource$
1924
1940
  .pipe(takeUntilDestroyed(this.destroyRef))
1925
- .subscribe(() => this.active.set(false));
1941
+ .subscribe(() => this._active.set(false));
1926
1942
  // Subscribe to navigation events
1927
1943
  this.router.events
1928
1944
  .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
@@ -1961,7 +1977,7 @@ class AppMenuitem {
1961
1977
  }
1962
1978
  itemClick() {
1963
1979
  if (this.item().children) {
1964
- this.active.update((prev) => !prev);
1980
+ this._active.update((prev) => !prev);
1965
1981
  }
1966
1982
  this.layoutService.onMenuStateChange({ key: this.key() });
1967
1983
  }
@@ -2022,11 +2038,11 @@ class AppMenuitem {
2022
2038
  </ul>
2023
2039
  }
2024
2040
  </ng-container>
2025
- `, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i2$3.Ripple, selector: "[pRipple]" }] });
2041
+ `, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i2$3.Ripple, selector: "[pRipple]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2026
2042
  }
2027
2043
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenuitem, decorators: [{
2028
2044
  type: Component,
2029
- args: [{ selector: '[app-menuitem]', standalone: true, imports: [IconComponent, RouterModule, RippleModule], template: `
2045
+ args: [{ selector: '[app-menuitem]', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, RouterModule, RippleModule], template: `
2030
2046
  <ng-container>
2031
2047
  @if (item().children?.length) {
2032
2048
  <a (click)="itemClick()" tabindex="0" pRipple>
@@ -2112,13 +2128,14 @@ class AppMenu {
2112
2128
  <li app-menuitem [item]="item" [index]="i"></li>
2113
2129
  }
2114
2130
  </ul>
2115
- </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
2131
+ </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2116
2132
  }
2117
2133
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenu, decorators: [{
2118
2134
  type: Component,
2119
2135
  args: [{
2120
2136
  selector: 'app-menu',
2121
2137
  standalone: true,
2138
+ changeDetection: ChangeDetectionStrategy.OnPush,
2122
2139
  imports: [AppMenuitem, RouterModule],
2123
2140
  template: `<div class="layout-menu">
2124
2141
  <ul>
@@ -2140,13 +2157,14 @@ class AppSidebar {
2140
2157
  <div class="layout-sidebar">
2141
2158
  <app-menu />
2142
2159
  </div>
2143
- `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
2160
+ `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2144
2161
  }
2145
2162
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppSidebar, decorators: [{
2146
2163
  type: Component,
2147
2164
  args: [{
2148
2165
  selector: 'app-sidebar',
2149
2166
  standalone: true,
2167
+ changeDetection: ChangeDetectionStrategy.OnPush,
2150
2168
  imports: [AppMenu],
2151
2169
  template: `
2152
2170
  <div class="layout-sidebar">
@@ -2163,8 +2181,6 @@ class AppLayout {
2163
2181
  renderer = inject(Renderer2);
2164
2182
  router = inject(Router);
2165
2183
  menuOutsideClickListener = null;
2166
- appSidebar;
2167
- appTopBar;
2168
2184
  constructor() {
2169
2185
  this.layoutService.overlayOpen$
2170
2186
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -2194,12 +2210,11 @@ class AppLayout {
2194
2210
  topbarEl?.contains(eventTarget));
2195
2211
  }
2196
2212
  hideMenu() {
2197
- this.layoutService.layoutState.update((prev) => ({
2198
- ...prev,
2213
+ this.layoutService.updateLayoutState({
2199
2214
  overlayMenuActive: false,
2200
2215
  staticMenuMobileActive: false,
2201
2216
  menuHoverActive: false,
2202
- }));
2217
+ });
2203
2218
  if (this.menuOutsideClickListener) {
2204
2219
  this.menuOutsideClickListener();
2205
2220
  this.menuOutsideClickListener = null;
@@ -2212,7 +2227,7 @@ class AppLayout {
2212
2227
  unblockBodyScroll() {
2213
2228
  this.document.body.classList.remove('blocked-scroll');
2214
2229
  }
2215
- get containerClass() {
2230
+ containerClass = computed(() => {
2216
2231
  const config = this.layoutService.layoutConfig();
2217
2232
  const state = this.layoutService.layoutState();
2218
2233
  return {
@@ -2222,10 +2237,10 @@ class AppLayout {
2222
2237
  'layout-overlay-active': state.overlayMenuActive,
2223
2238
  'layout-mobile-active': state.staticMenuMobileActive,
2224
2239
  };
2225
- }
2240
+ }, ...(ngDevMode ? [{ debugName: "containerClass" }] : []));
2226
2241
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
2227
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppLayout, isStandalone: true, selector: "app-layout", viewQueries: [{ propertyName: "appSidebar", first: true, predicate: AppSidebar, descendants: true }, { propertyName: "appTopBar", first: true, predicate: AppTopbar, descendants: true }], ngImport: i0, template: `
2228
- <div class="layout-wrapper" [ngClass]="containerClass">
2242
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
2243
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2229
2244
  <app-topbar></app-topbar>
2230
2245
  <app-sidebar></app-sidebar>
2231
2246
  <div class="layout-main-container">
@@ -2236,16 +2251,17 @@ class AppLayout {
2236
2251
  </div>
2237
2252
  <div class="layout-mask animate-fadein"></div>
2238
2253
  </div>
2239
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }] });
2254
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2240
2255
  }
2241
2256
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, decorators: [{
2242
2257
  type: Component,
2243
2258
  args: [{
2244
2259
  selector: 'app-layout',
2245
2260
  standalone: true,
2246
- imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter],
2261
+ changeDetection: ChangeDetectionStrategy.OnPush,
2262
+ imports: [NgClass, AppTopbar, AppSidebar, RouterModule, AppFooter],
2247
2263
  template: `
2248
- <div class="layout-wrapper" [ngClass]="containerClass">
2264
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2249
2265
  <app-topbar></app-topbar>
2250
2266
  <app-sidebar></app-sidebar>
2251
2267
  <div class="layout-main-container">
@@ -2258,13 +2274,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2258
2274
  </div>
2259
2275
  `,
2260
2276
  }]
2261
- }], ctorParameters: () => [], propDecorators: { appSidebar: [{
2262
- type: ViewChild,
2263
- args: [AppSidebar]
2264
- }], appTopBar: [{
2265
- type: ViewChild,
2266
- args: [AppTopbar]
2267
- }] } });
2277
+ }], ctorParameters: () => [] });
2268
2278
 
2269
2279
  const GreenTheme = definePreset(Material, {
2270
2280
  semantic: {