@flusys/ng-layout 1.1.0-beta → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,11 @@
1
- import * as i1 from '@angular/common';
2
- import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
3
1
  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';
2
+ import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect, ChangeDetectionStrategy, Component, afterNextRender, InjectionToken, DestroyRef, ElementRef, viewChild, input, Renderer2 } from '@angular/core';
3
+ import { isPlatformBrowser, NgClass, DOCUMENT as DOCUMENT$1 } from '@angular/common';
4
+ import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
5
+ import * as i2$2 from '@flusys/ng-shared';
6
+ import { evaluateLogicNode, PermissionValidatorService, AngularModule, IconComponent } from '@flusys/ng-shared';
7
+ import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
8
+ import * as i1 from '@angular/forms';
6
9
  import { FormsModule } from '@angular/forms';
7
10
  import * as i1$2 from '@angular/router';
8
11
  import { Router, RouterModule, NavigationEnd } from '@angular/router';
@@ -10,13 +13,8 @@ import { updatePreset, updateSurfacePalette, $t, definePreset } from '@primeuix/
10
13
  import Aura from '@primeuix/themes/aura';
11
14
  import Lara from '@primeuix/themes/lara';
12
15
  import Nora from '@primeuix/themes/nora';
13
- import { PrimeNG } from 'primeng/config';
14
- import * as i3 from 'primeng/selectbutton';
16
+ import * as i2 from 'primeng/selectbutton';
15
17
  import { SelectButtonModule } from 'primeng/selectbutton';
16
- import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled } from '@flusys/ng-core';
17
- import * as i2$2 from '@flusys/ng-shared';
18
- import { evaluateLogicNode, PermissionValidatorService, PlatformService, AngularModule, IconComponent } from '@flusys/ng-shared';
19
- 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';
22
20
  import * as i2$1 from 'primeng/styleclass';
@@ -194,104 +192,79 @@ class LayoutPersistenceService {
194
192
  const { _version, ...cleanConfig } = config;
195
193
  return cleanConfig;
196
194
  }
197
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutPersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
198
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutPersistenceService, providedIn: 'root' });
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutPersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
196
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutPersistenceService, providedIn: 'root' });
199
197
  }
200
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutPersistenceService, decorators: [{
198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutPersistenceService, decorators: [{
201
199
  type: Injectable,
202
200
  args: [{
203
201
  providedIn: 'root',
204
202
  }]
205
203
  }] });
206
204
 
207
- /**
208
- * Service managing layout configuration and state.
209
- * Provides signals for reactive layout updates.
210
- */
205
+ /** Layout configuration and state management service */
211
206
  class LayoutService {
212
207
  document = inject(DOCUMENT);
213
208
  platformId = inject(PLATFORM_ID);
214
209
  isBrowser = isPlatformBrowser(this.platformId);
215
210
  persistence = inject(LayoutPersistenceService);
216
211
  appConfig = inject(APP_CONFIG, { optional: true });
217
- defaultConfig = {
212
+ DEFAULT_CONFIG = {
218
213
  preset: 'Aura',
219
214
  primary: 'emerald',
220
215
  surface: null,
221
216
  darkTheme: false,
222
217
  menuMode: 'static',
223
218
  };
224
- // Load persisted config merged with defaults
225
- initialConfig = (() => {
226
- const persisted = this.persistence.load();
227
- return persisted
228
- ? { ...this.defaultConfig, ...persisted }
229
- : this.defaultConfig;
230
- })();
231
- defaultState = {
219
+ DEFAULT_STATE = {
232
220
  staticMenuDesktopInactive: false,
233
221
  overlayMenuActive: false,
234
222
  configSidebarVisible: false,
235
223
  staticMenuMobileActive: false,
236
224
  menuHoverActive: false,
237
225
  };
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" }] : []));
249
- // App Launcher Signals
250
- _rawApps = signal([], ...(ngDevMode ? [{ debugName: "_rawApps" }] : []));
251
- /**
252
- * Filtered launcher apps based on user permissions.
253
- * Automatically recomputes when raw apps or permissions change.
254
- */
255
- apps = computed(() => {
256
- const raw = this._rawApps();
257
- const permission = this.permissionValidator.permissions();
258
- return filterAppsByPermissions(raw, permission);
259
- }, ...(ngDevMode ? [{ debugName: "apps" }] : []));
260
- // Menu Signals
226
+ _layoutConfig = signal({
227
+ ...this.DEFAULT_CONFIG,
228
+ ...this.persistence.load(),
229
+ }, ...(ngDevMode ? [{ debugName: "_layoutConfig" }] : []));
230
+ _layoutState = signal(this.DEFAULT_STATE, ...(ngDevMode ? [{ debugName: "_layoutState" }] : []));
231
+ _transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "_transitionComplete" }] : []));
232
+ layoutConfig = this._layoutConfig.asReadonly();
233
+ layoutState = this._layoutState.asReadonly();
234
+ transitionComplete = this._transitionComplete.asReadonly();
235
+ _userProfile = signal(null, ...(ngDevMode ? [{ debugName: "_userProfile" }] : []));
236
+ _companyProfile = signal(null, ...(ngDevMode ? [{ debugName: "_companyProfile" }] : []));
261
237
  _rawMenu = signal([], ...(ngDevMode ? [{ debugName: "_rawMenu" }] : []));
238
+ _rawApps = signal([], ...(ngDevMode ? [{ debugName: "_rawApps" }] : []));
262
239
  permissionValidator = inject(PermissionValidatorService);
263
- /**
264
- * Filtered menu items based on user permissions.
265
- * Automatically recomputes when raw menu or permission checker changes.
266
- * Role checker is optional - if not set, role-based permissions will be denied.
267
- */
268
- menu = computed(() => {
269
- const raw = this._rawMenu(); // Track permission changes
270
- const permission = this.permissionValidator.permissions();
271
- return filterMenuByPermissions(raw, permission);
272
- }, ...(ngDevMode ? [{ debugName: "menu" }] : []));
240
+ userProfile = this._userProfile.asReadonly();
241
+ companyProfile = this._companyProfile.asReadonly();
242
+ // Static app info from config
243
+ appName = this.appConfig?.appName ?? DEFAULT_APP_NAME;
244
+ authorName = this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name;
245
+ authorUrl = this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url;
246
+ // Permission-filtered menu and apps
247
+ menu = computed(() => filterMenuByPermissions(this._rawMenu(), this.permissionValidator.permissions()), ...(ngDevMode ? [{ debugName: "menu" }] : []));
248
+ apps = computed(() => filterAppsByPermissions(this._rawApps(), this.permissionValidator.permissions()), ...(ngDevMode ? [{ debugName: "apps" }] : []));
249
+ hasApps = computed(() => this.apps().length > 0, ...(ngDevMode ? [{ debugName: "hasApps" }] : []));
273
250
  // 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
- // 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" }] : []));
251
+ isSidebarActive = computed(() => this._layoutState().overlayMenuActive ||
252
+ this._layoutState().staticMenuMobileActive, ...(ngDevMode ? [{ debugName: "isSidebarActive" }] : []));
253
+ isDarkTheme = computed(() => this._layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
254
+ getPrimary = computed(() => this._layoutConfig().primary, ...(ngDevMode ? [{ debugName: "getPrimary" }] : []));
255
+ getSurface = computed(() => this._layoutConfig().surface, ...(ngDevMode ? [{ debugName: "getSurface" }] : []));
256
+ isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', ...(ngDevMode ? [{ debugName: "isOverlay" }] : []));
257
+ // User profile computed signals
258
+ userName = computed(() => this._userProfile()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
259
+ userEmail = computed(() => this._userProfile()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
260
+ userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl ?? null, ...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : []));
261
+ companyLogoUrl = computed(() => this._companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
262
+ isAuthenticated = computed(() => !!this._userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
284
263
  companyName = computed(() => {
285
- // If company feature is disabled, always show app name
286
- if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig)) {
287
- return this.appName();
288
- }
289
- return this.companyProfile()?.name ?? this.appName();
264
+ if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig))
265
+ return this.appName;
266
+ return this._companyProfile()?.name ?? this.appName;
290
267
  }, ...(ngDevMode ? [{ debugName: "companyName" }] : []));
291
- companyLogoUrl = computed(() => this.companyProfile()?.logoUrl ?? null, ...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : []));
292
- isAuthenticated = computed(() => !!this.userProfile(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
293
- // Computed signals - App Launcher
294
- hasApps = computed(() => this.apps().length > 0, ...(ngDevMode ? [{ debugName: "hasApps" }] : []));
295
268
  // RxJS Subjects for event communication
296
269
  configUpdate = new Subject();
297
270
  overlayOpen = new Subject();
@@ -304,79 +277,56 @@ class LayoutService {
304
277
  initialized = false;
305
278
  constructor() {
306
279
  effect(() => {
307
- const config = this.layoutConfig();
308
- if (config) {
309
- this.onConfigUpdate();
310
- }
311
- });
312
- effect(() => {
313
- const config = this.layoutConfig();
314
- if (!this.initialized || !config) {
315
- this.initialized = true;
316
- return;
317
- }
318
- this.handleDarkModeTransition(config);
319
- });
320
- // Auto-save configuration changes to localStorage
321
- effect(() => {
322
- const config = this.layoutConfig();
323
- if (config && this.initialized) {
280
+ const config = this._layoutConfig();
281
+ this.configUpdate.next(config);
282
+ if (this.initialized) {
283
+ this.handleDarkModeTransition(config);
324
284
  this.persistence.save(config);
325
285
  }
286
+ this.initialized = true;
326
287
  });
327
288
  }
289
+ toggleDarkMode(config) {
290
+ const isDark = (config ?? this._layoutConfig()).darkTheme;
291
+ this.document.documentElement.classList.toggle('app-dark', isDark);
292
+ }
328
293
  handleDarkModeTransition(config) {
329
- if (this.document.startViewTransition) {
330
- this.startViewTransition(config);
294
+ const doc = this.document;
295
+ if ('startViewTransition' in doc && typeof doc.startViewTransition === 'function') {
296
+ doc
297
+ .startViewTransition(() => this.toggleDarkMode(config))
298
+ .ready.then(() => this.onTransitionEnd())
299
+ .catch(() => { });
331
300
  }
332
301
  else {
333
302
  this.toggleDarkMode(config);
334
303
  this.onTransitionEnd();
335
304
  }
336
305
  }
337
- startViewTransition(config) {
338
- const transition = this.document.startViewTransition(() => {
339
- this.toggleDarkMode(config);
340
- });
341
- transition.ready.then(() => this.onTransitionEnd()).catch(() => { });
342
- }
343
- toggleDarkMode(config) {
344
- const _config = config || this.layoutConfig();
345
- if (_config.darkTheme) {
346
- this.document.documentElement.classList.add('app-dark');
347
- }
348
- else {
349
- this.document.documentElement.classList.remove('app-dark');
350
- }
351
- }
352
306
  onTransitionEnd() {
353
- this.transitionComplete.set(true);
354
- setTimeout(() => this.transitionComplete.set(false));
307
+ this._transitionComplete.set(true);
308
+ setTimeout(() => this._transitionComplete.set(false));
355
309
  }
356
310
  onMenuToggle() {
311
+ const state = this._layoutState();
357
312
  if (this.isOverlay()) {
358
- this.layoutState.update((prev) => ({
359
- ...prev,
360
- overlayMenuActive: !this.layoutState().overlayMenuActive,
361
- }));
362
- if (this.layoutState().overlayMenuActive) {
313
+ const newOverlayActive = !state.overlayMenuActive;
314
+ this._layoutState.update((prev) => ({ ...prev, overlayMenuActive: newOverlayActive }));
315
+ if (newOverlayActive)
363
316
  this.overlayOpen.next();
364
- }
317
+ return;
365
318
  }
366
319
  if (this.isDesktop()) {
367
- this.layoutState.update((prev) => ({
320
+ this._layoutState.update((prev) => ({
368
321
  ...prev,
369
- staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive,
322
+ staticMenuDesktopInactive: !state.staticMenuDesktopInactive,
370
323
  }));
371
324
  }
372
325
  else {
373
- this.layoutState.update((prev) => ({
374
- ...prev,
375
- staticMenuMobileActive: !this.layoutState().staticMenuMobileActive,
376
- }));
377
- if (this.layoutState().staticMenuMobileActive) {
326
+ const newMobileActive = !state.staticMenuMobileActive;
327
+ this._layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: newMobileActive }));
328
+ if (newMobileActive)
378
329
  this.overlayOpen.next();
379
- }
380
330
  }
381
331
  }
382
332
  isDesktop() {
@@ -385,77 +335,70 @@ class LayoutService {
385
335
  isMobile() {
386
336
  return !this.isDesktop();
387
337
  }
388
- onConfigUpdate() {
389
- this.configUpdate.next(this.layoutConfig());
390
- }
391
338
  onMenuStateChange(event) {
392
339
  this.menuSource.next(event);
393
340
  }
394
341
  reset() {
395
342
  this.resetSource.next(true);
396
343
  }
397
- // ==========================================================================
398
- // User Profile Methods
399
- // ==========================================================================
400
- /**
401
- * Set the current user profile for display in layout.
402
- * Called by auth integration to sync user data.
403
- */
344
+ // Config & state updates
345
+ updateLayoutConfig(config) {
346
+ this._layoutConfig.update((prev) => ({ ...prev, ...config }));
347
+ }
348
+ updateLayoutState(state) {
349
+ this._layoutState.update((prev) => ({ ...prev, ...state }));
350
+ }
351
+ // Profile setters
404
352
  setUserProfile(profile) {
405
- this.userProfile.set(profile);
353
+ this._userProfile.set(profile);
406
354
  }
407
- /**
408
- * Set the current company profile for display in layout.
409
- * Called by auth integration to sync company data.
410
- */
411
355
  setCompanyProfile(profile) {
412
- this.companyProfile.set(profile);
356
+ this._companyProfile.set(profile);
413
357
  }
414
- // ==========================================================================
415
- // Menu Methods
416
- // ==========================================================================
417
- /**
418
- * Set the raw menu items (unfiltered).
419
- * Menu will be automatically filtered based on permission checker.
420
- * Called by app initialization to set menu from IAM or other source.
421
- */
358
+ // Menu & apps
422
359
  setMenu(items) {
423
360
  this._rawMenu.set(items);
424
361
  }
425
- /**
426
- * Clear menu and permission/role checkers.
427
- * Called on logout.
428
- */
429
362
  clearMenu() {
430
363
  this._rawMenu.set([]);
431
364
  }
432
- // ==========================================================================
433
- // App Launcher Methods
434
- // ==========================================================================
435
- /**
436
- * Set launcher apps for display in header.
437
- * Apps will be automatically filtered based on user permissions.
438
- * If empty after filtering, the app launcher button is hidden.
439
- */
440
365
  setApps(apps) {
441
366
  this._rawApps.set(apps);
442
367
  }
443
- /**
444
- * Clear launcher apps.
445
- * Called on logout.
446
- */
447
368
  clearApps() {
448
369
  this._rawApps.set([]);
449
370
  }
450
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
451
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutService, providedIn: 'root' });
371
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
372
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutService, providedIn: 'root' });
452
373
  }
453
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LayoutService, decorators: [{
374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LayoutService, decorators: [{
454
375
  type: Injectable,
376
+ args: [{ providedIn: 'root' }]
377
+ }], ctorParameters: () => [] });
378
+
379
+ class AppFooter {
380
+ layoutService = inject(LayoutService);
381
+ // Footer shows product branding (appName), not user's company
382
+ appName = this.layoutService.appName;
383
+ authorName = this.layoutService.authorName;
384
+ authorUrl = this.layoutService.authorUrl;
385
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
386
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
387
+ {{ appName }} by
388
+ <a [href]="authorUrl" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName }}</a>
389
+ </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
390
+ }
391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFooter, decorators: [{
392
+ type: Component,
455
393
  args: [{
456
- providedIn: 'root',
394
+ selector: 'app-footer',
395
+ changeDetection: ChangeDetectionStrategy.OnPush,
396
+ template: `<div class="layout-footer">
397
+ {{ appName }} by
398
+ <a [href]="authorUrl" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName }}</a>
399
+ </div>`,
457
400
  }]
458
- }], ctorParameters: () => [] });
401
+ }] });
459
402
 
460
403
  const presets = {
461
404
  Aura,
@@ -464,19 +407,19 @@ const presets = {
464
407
  };
465
408
  class AppConfigurator {
466
409
  router = inject(Router);
467
- primeng = inject(PrimeNG);
468
410
  layoutService = inject(LayoutService);
469
- platformService = inject(PlatformService);
470
411
  presets = Object.keys(presets);
471
412
  showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
472
413
  menuModeOptions = [
473
414
  { label: 'Static', value: 'static' },
474
415
  { label: 'Overlay', value: 'overlay' },
475
416
  ];
476
- ngOnInit() {
477
- if (!this.platformService.isServer) {
478
- this.onPresetChange(this.layoutService.layoutConfig().preset);
479
- }
417
+ constructor() {
418
+ // Use afterNextRender for browser-only initialization (replaces isServer check)
419
+ afterNextRender(() => {
420
+ const preset = this.layoutService.layoutConfig().preset ?? 'Aura';
421
+ this.onPresetChange(preset);
422
+ });
480
423
  }
481
424
  surfaces = [
482
425
  {
@@ -782,16 +725,10 @@ class AppConfigurator {
782
725
  }
783
726
  updateColors(event, type, color) {
784
727
  if (type === 'primary') {
785
- this.layoutService.layoutConfig.update((state) => ({
786
- ...state,
787
- primary: color.name,
788
- }));
728
+ this.layoutService.updateLayoutConfig({ primary: color.name });
789
729
  }
790
730
  else if (type === 'surface') {
791
- this.layoutService.layoutConfig.update((state) => ({
792
- ...state,
793
- surface: color.name,
794
- }));
731
+ this.layoutService.updateLayoutConfig({ surface: color.name });
795
732
  }
796
733
  this.applyTheme(type, color);
797
734
  event.stopPropagation();
@@ -805,12 +742,12 @@ class AppConfigurator {
805
742
  }
806
743
  }
807
744
  onPresetChange(event) {
808
- this.layoutService.layoutConfig.update((state) => ({
809
- ...state,
810
- preset: event,
811
- }));
745
+ this.layoutService.updateLayoutConfig({ preset: event });
812
746
  const preset = presets[event];
813
- const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
747
+ // Use selected surface or default (slate for light, zinc for dark)
748
+ const selectedSurface = this.selectedSurfaceColor();
749
+ const defaultSurface = this.layoutService.layoutConfig().darkTheme ? 'zinc' : 'slate';
750
+ const surfacePalette = this.surfaces.find((s) => s.name === (selectedSurface || defaultSurface))?.palette;
814
751
  $t()
815
752
  .preset(preset)
816
753
  .preset(this.getPresetExt())
@@ -818,13 +755,10 @@ class AppConfigurator {
818
755
  .use({ useDefaultOptions: true });
819
756
  }
820
757
  onMenuModeChange(event) {
821
- this.layoutService.layoutConfig.update((prev) => ({
822
- ...prev,
823
- menuMode: event,
824
- }));
758
+ this.layoutService.updateLayoutConfig({ menuMode: event });
825
759
  }
826
- 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: `
760
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
761
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", 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
762
  <div class="flex flex-col gap-4">
829
763
  <div>
830
764
  <span class="text-sm text-muted-color font-semibold">Primary</span>
@@ -891,14 +825,14 @@ class AppConfigurator {
891
825
  </div>
892
826
  }
893
827
  </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"] }] });
828
+ `, 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
829
  }
896
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppConfigurator, decorators: [{
830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppConfigurator, decorators: [{
897
831
  type: Component,
898
832
  args: [{
899
833
  selector: 'app-configurator',
900
- standalone: true,
901
- imports: [CommonModule, FormsModule, SelectButtonModule],
834
+ changeDetection: ChangeDetectionStrategy.OnPush,
835
+ imports: [NgClass, FormsModule, SelectButtonModule],
902
836
  template: `
903
837
  <div class="flex flex-col gap-4">
904
838
  <div>
@@ -968,36 +902,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
968
902
  </div>
969
903
  `,
970
904
  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)]',
905
+ 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
906
  style: 'background-color: var(--surface-overlay)',
973
907
  },
974
908
  }]
975
- }] });
909
+ }], ctorParameters: () => [] });
976
910
 
977
911
  class AppFloatingConfigurator {
978
- LayoutService = inject(LayoutService);
979
- isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
912
+ layoutService = inject(LayoutService);
913
+ isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, ...(ngDevMode ? [{ debugName: "isDarkTheme" }] : []));
980
914
  toggleDarkMode() {
981
- this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
915
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
916
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
982
917
  }
983
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
984
- 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">
918
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
919
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
920
+ <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
921
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
987
922
  <div class="relative">
988
923
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
989
924
  <app-configurator />
990
925
  </div>
991
926
  </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" }] });
927
+ `, 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
928
  }
994
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
929
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
995
930
  type: Component,
996
931
  args: [{
997
932
  selector: 'app-floating-configurator',
933
+ changeDetection: ChangeDetectionStrategy.OnPush,
998
934
  imports: [ButtonModule, StyleClassModule, AppConfigurator],
999
935
  template: `
1000
- <div class="fixed flex flex-col md:flex-row gap-4 top-8 right-0 md:right-8 z-99">
936
+ <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
937
  <p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
1002
938
  <div class="relative">
1003
939
  <p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
@@ -1008,34 +944,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1008
944
  }]
1009
945
  }] });
1010
946
 
1011
- class AppFooter {
1012
- layoutService = inject(LayoutService);
1013
- // Footer shows product branding (appName), not user's company
1014
- appName = this.layoutService.appName;
1015
- authorName = this.layoutService.authorName;
1016
- authorUrl = this.layoutService.authorUrl;
1017
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
1018
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
1019
- {{ appName() }} by
1020
- <a [href]="authorUrl()" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName() }}</a>
1021
- </div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1022
- }
1023
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppFooter, decorators: [{
1024
- type: Component,
1025
- args: [{
1026
- standalone: true,
1027
- selector: 'app-footer',
1028
- changeDetection: ChangeDetectionStrategy.OnPush,
1029
- template: `<div class="layout-footer">
1030
- {{ appName() }} by
1031
- <a [href]="authorUrl()" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">{{ authorName() }}</a>
1032
- </div>`,
1033
- }]
1034
- }] });
1035
-
1036
947
  const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
1037
948
  const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
1038
949
 
950
+ /**
951
+ * View Transitions API type definitions
952
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
953
+ */
954
+
1039
955
  /** Company/branch switcher displayed in top bar */
1040
956
  class AppCompanyBranchSelector {
1041
957
  destroyRef = inject(DestroyRef);
@@ -1044,26 +960,34 @@ class AppCompanyBranchSelector {
1044
960
  messageService = inject(MessageService);
1045
961
  document = inject(DOCUMENT$1);
1046
962
  elementRef = inject(ElementRef);
1047
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
963
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
964
+ isActive = this._isActive.asReadonly();
1048
965
  constructor() {
1049
966
  fromEvent(this.document, 'click')
1050
967
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1051
968
  .subscribe((event) => {
1052
969
  const target = event.target;
1053
970
  if (!this.elementRef.nativeElement.contains(target)) {
1054
- this.isActive.set(false);
971
+ this._isActive.set(false);
1055
972
  }
1056
973
  });
1057
974
  }
1058
975
  currentCompanyName = computed(() => this.authState?.currentCompanyInfo()?.name ?? 'No Company', ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
1059
976
  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" }] : []));
977
+ _companies = signal([], ...(ngDevMode ? [{ debugName: "_companies" }] : []));
978
+ companies = this._companies.asReadonly();
979
+ _branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
980
+ branches = this._branches.asReadonly();
981
+ _selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
982
+ selectedCompanyId = this._selectedCompanyId.asReadonly();
983
+ _selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
984
+ selectedBranchId = this._selectedBranchId.asReadonly();
985
+ _isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
986
+ isLoadingCompanies = this._isLoadingCompanies.asReadonly();
987
+ _isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
988
+ isLoadingBranches = this._isLoadingBranches.asReadonly();
989
+ _isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
990
+ isSwitching = this._isSwitching.asReadonly();
1067
991
  canSwitch = computed(() => {
1068
992
  const selectedCompany = this.selectedCompanyId();
1069
993
  if (!selectedCompany)
@@ -1074,7 +998,7 @@ class AppCompanyBranchSelector {
1074
998
  return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
1075
999
  }, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
1076
1000
  onPanelToggle() {
1077
- this.isActive.update((v) => !v);
1001
+ this._isActive.update((v) => !v);
1078
1002
  if (this.isActive() && this.companies().length === 0) {
1079
1003
  this.loadCompanies();
1080
1004
  }
@@ -1082,61 +1006,53 @@ class AppCompanyBranchSelector {
1082
1006
  loadCompanies() {
1083
1007
  if (!this.authApi)
1084
1008
  return;
1085
- this.isLoadingCompanies.set(true);
1009
+ this._isLoadingCompanies.set(true);
1086
1010
  this.authApi
1087
1011
  .getUserCompanies()
1088
1012
  .pipe(takeUntilDestroyed(this.destroyRef))
1089
1013
  .subscribe({
1090
1014
  next: (companies) => {
1091
- this.companies.set(companies);
1092
- this.isLoadingCompanies.set(false);
1015
+ this._companies.set(companies);
1016
+ this._isLoadingCompanies.set(false);
1093
1017
  },
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
- });
1018
+ error: () => {
1019
+ // Error toast handled by global interceptor
1020
+ this._isLoadingCompanies.set(false);
1101
1021
  },
1102
1022
  });
1103
1023
  }
1104
1024
  onCompanyChange(companyId) {
1105
- this.selectedCompanyId.set(companyId);
1025
+ this._selectedCompanyId.set(companyId);
1106
1026
  if (!companyId) {
1107
- this.branches.set([]);
1108
- this.selectedBranchId.set(null);
1027
+ this._branches.set([]);
1028
+ this._selectedBranchId.set(null);
1109
1029
  return;
1110
1030
  }
1111
1031
  this.loadBranches(companyId);
1112
1032
  }
1113
1033
  onBranchChange(branchId) {
1114
- this.selectedBranchId.set(branchId);
1034
+ this._selectedBranchId.set(branchId);
1115
1035
  }
1116
1036
  loadBranches(companyId) {
1117
1037
  if (!this.authApi)
1118
1038
  return;
1119
- this.isLoadingBranches.set(true);
1120
- this.selectedBranchId.set(null);
1039
+ this._isLoadingBranches.set(true);
1040
+ this._selectedBranchId.set(null);
1121
1041
  this.authApi
1122
1042
  .getCompanyBranches(companyId)
1123
1043
  .pipe(takeUntilDestroyed(this.destroyRef))
1124
1044
  .subscribe({
1125
1045
  next: (branches) => {
1126
- this.branches.set(branches);
1127
- this.isLoadingBranches.set(false);
1046
+ this._branches.set(branches);
1047
+ this._isLoadingBranches.set(false);
1128
1048
  // Auto-select if only one branch available
1129
1049
  if (branches.length === 1) {
1130
- this.selectedBranchId.set(branches[0].id);
1050
+ this._selectedBranchId.set(branches[0].id);
1131
1051
  }
1132
1052
  },
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
- });
1053
+ error: () => {
1054
+ // Error toast handled by global interceptor
1055
+ this._isLoadingBranches.set(false);
1140
1056
  },
1141
1057
  });
1142
1058
  }
@@ -1153,24 +1069,20 @@ class AppCompanyBranchSelector {
1153
1069
  });
1154
1070
  return;
1155
1071
  }
1156
- this.isSwitching.set(true);
1072
+ this._isSwitching.set(true);
1157
1073
  this.authApi
1158
1074
  .switchCompany(selectedCompany, selectedBranch || '')
1159
1075
  .pipe(takeUntilDestroyed(this.destroyRef))
1160
1076
  .subscribe({
1161
1077
  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
- });
1078
+ error: () => {
1079
+ // Error toast handled by global interceptor
1080
+ this._isSwitching.set(false);
1169
1081
  },
1170
1082
  });
1171
1083
  }
1172
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppCompanyBranchSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
1173
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppCompanyBranchSelector, isStandalone: true, selector: "app-company-branch-selector", host: { classAttribute: "relative" }, ngImport: i0, template: `
1084
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppCompanyBranchSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
1085
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppCompanyBranchSelector, isStandalone: true, selector: "app-company-branch-selector", host: { classAttribute: "relative" }, ngImport: i0, template: `
1174
1086
  <button
1175
1087
  type="button"
1176
1088
  class="layout-topbar-action"
@@ -1190,10 +1102,10 @@ class AppCompanyBranchSelector {
1190
1102
  }
1191
1103
  </button>
1192
1104
  <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)]"
1105
+ 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
1106
  style="background-color: var(--surface-overlay)"
1195
1107
  >
1196
- <div class="flex flex-col gap-4 min-w-[280px]">
1108
+ <div class="flex flex-col gap-4">
1197
1109
  <span class="text-sm text-muted-color font-semibold"
1198
1110
  >Switch Company & Branch</span
1199
1111
  >
@@ -1243,13 +1155,13 @@ class AppCompanyBranchSelector {
1243
1155
  </div>
1244
1156
  </div>
1245
1157
  </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"] }] });
1158
+ `, 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
1159
  }
1248
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
1160
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
1249
1161
  type: Component,
1250
1162
  args: [{
1251
1163
  selector: 'app-company-branch-selector',
1252
- standalone: true,
1164
+ changeDetection: ChangeDetectionStrategy.OnPush,
1253
1165
  imports: [AngularModule, StyleClassModule, ButtonModule, SelectModule],
1254
1166
  host: { class: 'relative' },
1255
1167
  template: `
@@ -1272,10 +1184,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1272
1184
  }
1273
1185
  </button>
1274
1186
  <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)]"
1187
+ 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
1188
  style="background-color: var(--surface-overlay)"
1277
1189
  >
1278
- <div class="flex flex-col gap-4 min-w-[280px]">
1190
+ <div class="flex flex-col gap-4">
1279
1191
  <span class="text-sm text-muted-color font-semibold"
1280
1192
  >Switch Company & Branch</span
1281
1193
  >
@@ -1334,22 +1246,23 @@ class AppLauncher {
1334
1246
  document = inject(DOCUMENT$1);
1335
1247
  elementRef = inject(ElementRef);
1336
1248
  layoutService = inject(LayoutService);
1337
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1249
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1250
+ isActive = this._isActive.asReadonly();
1338
1251
  constructor() {
1339
1252
  fromEvent(this.document, 'click')
1340
- .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1253
+ .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this._isActive()))
1341
1254
  .subscribe((event) => {
1342
1255
  const target = event.target;
1343
1256
  if (!this.elementRef.nativeElement.contains(target)) {
1344
- this.isActive.set(false);
1257
+ this._isActive.set(false);
1345
1258
  }
1346
1259
  });
1347
1260
  }
1348
1261
  togglePanel() {
1349
- this.isActive.update((v) => !v);
1262
+ this._isActive.update((v) => !v);
1350
1263
  }
1351
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, deps: [], target: i0.ɵɵFactoryTarget.Component });
1352
- 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: `
1264
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppLauncher, deps: [], target: i0.ɵɵFactoryTarget.Component });
1265
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppLauncher, isStandalone: true, selector: "app-launcher", host: { classAttribute: "relative" }, ngImport: i0, template: `
1353
1266
  <button
1354
1267
  type="button"
1355
1268
  class="layout-topbar-action"
@@ -1366,15 +1279,15 @@ class AppLauncher {
1366
1279
  <span>Apps</span>
1367
1280
  </button>
1368
1281
  <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)]"
1282
+ 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
1283
  style="background-color: var(--surface-overlay)"
1371
1284
  >
1372
- <div class="flex flex-col gap-2 min-w-[240px]">
1285
+ <div class="flex flex-col gap-2">
1373
1286
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1374
- <div class="grid grid-cols-3 gap-2">
1287
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1375
1288
  @for (app of layoutService.apps(); track app.id) {
1376
1289
  <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"
1290
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1378
1291
  [href]="app.url"
1379
1292
  target="_blank"
1380
1293
  rel="noopener noreferrer"
@@ -1389,7 +1302,7 @@ class AppLauncher {
1389
1302
  />
1390
1303
  </div>
1391
1304
  <span
1392
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1305
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1393
1306
  >{{ app.name }}</span
1394
1307
  >
1395
1308
  </a>
@@ -1397,13 +1310,13 @@ class AppLauncher {
1397
1310
  </div>
1398
1311
  </div>
1399
1312
  </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"] }] });
1313
+ `, 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
1314
  }
1402
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLauncher, decorators: [{
1315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppLauncher, decorators: [{
1403
1316
  type: Component,
1404
1317
  args: [{
1405
1318
  selector: 'app-launcher',
1406
- standalone: true,
1319
+ changeDetection: ChangeDetectionStrategy.OnPush,
1407
1320
  imports: [StyleClassModule, IconComponent],
1408
1321
  host: { class: 'relative' },
1409
1322
  template: `
@@ -1423,15 +1336,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1423
1336
  <span>Apps</span>
1424
1337
  </button>
1425
1338
  <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)]"
1339
+ 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
1340
  style="background-color: var(--surface-overlay)"
1428
1341
  >
1429
- <div class="flex flex-col gap-2 min-w-[240px]">
1342
+ <div class="flex flex-col gap-2">
1430
1343
  <span class="text-sm text-muted-color font-semibold">Applications</span>
1431
- <div class="grid grid-cols-3 gap-2">
1344
+ <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
1432
1345
  @for (app of layoutService.apps(); track app.id) {
1433
1346
  <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"
1347
+ class="group flex flex-col items-center p-2 rounded-border cursor-pointer no-underline transition-all duration-200 hover:bg-emphasis"
1435
1348
  [href]="app.url"
1436
1349
  target="_blank"
1437
1350
  rel="noopener noreferrer"
@@ -1446,7 +1359,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1446
1359
  />
1447
1360
  </div>
1448
1361
  <span
1449
- class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200"
1362
+ class="text-xs mt-2 text-center text-muted-color group-hover:text-color transition-colors duration-200 truncate max-w-full"
1450
1363
  >{{ app.name }}</span
1451
1364
  >
1452
1365
  </a>
@@ -1466,23 +1379,23 @@ class AppProfile {
1466
1379
  messageService = inject(MessageService);
1467
1380
  document = inject(DOCUMENT$1);
1468
1381
  elementRef = inject(ElementRef);
1469
- isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
1382
+ _isActive = signal(false, ...(ngDevMode ? [{ debugName: "_isActive" }] : []));
1383
+ isActive = this._isActive.asReadonly();
1470
1384
  constructor() {
1471
1385
  fromEvent(this.document, 'click')
1472
1386
  .pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
1473
1387
  .subscribe((event) => {
1474
1388
  const target = event.target;
1475
1389
  if (!this.elementRef.nativeElement.contains(target)) {
1476
- this.isActive.set(false);
1390
+ this._isActive.set(false);
1477
1391
  }
1478
1392
  });
1479
1393
  }
1480
1394
  togglePanel() {
1481
- this.isActive.update((v) => !v);
1395
+ this._isActive.update((v) => !v);
1482
1396
  }
1483
1397
  userName = computed(() => this.authState?.loginUserData()?.name ?? 'Guest', ...(ngDevMode ? [{ debugName: "userName" }] : []));
1484
1398
  userEmail = computed(() => this.authState?.loginUserData()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
1485
- // Use LayoutService for profile picture URL (synced by AuthLayoutSyncService)
1486
1399
  profilePicture = computed(() => this.layoutService.userProfilePictureUrl() ?? '', ...(ngDevMode ? [{ debugName: "profilePicture" }] : []));
1487
1400
  logout() {
1488
1401
  if (!this.authApi)
@@ -1499,12 +1412,8 @@ class AppProfile {
1499
1412
  });
1500
1413
  this.authApi?.navigateLogin(false);
1501
1414
  },
1502
- error: (err) => {
1503
- this.messageService.add({
1504
- severity: 'error',
1505
- summary: 'Error',
1506
- detail: err?.message || 'Failed to logout',
1507
- });
1415
+ error: () => {
1416
+ // Error toast handled by global interceptor
1508
1417
  },
1509
1418
  });
1510
1419
  }
@@ -1528,8 +1437,8 @@ class AppProfile {
1528
1437
  });
1529
1438
  });
1530
1439
  }
1531
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppProfile, deps: [], target: i0.ɵɵFactoryTarget.Component });
1532
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppProfile, isStandalone: true, selector: "app-profile", host: { classAttribute: "relative" }, ngImport: i0, template: `
1440
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppProfile, deps: [], target: i0.ɵɵFactoryTarget.Component });
1441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppProfile, isStandalone: true, selector: "app-profile", host: { classAttribute: "relative" }, ngImport: i0, template: `
1533
1442
  <button
1534
1443
  type="button"
1535
1444
  class="layout-topbar-action"
@@ -1546,10 +1455,10 @@ class AppProfile {
1546
1455
  <span>Profile</span>
1547
1456
  </button>
1548
1457
  <div
1549
- 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)]"
1458
+ 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]"
1550
1459
  style="background-color: var(--surface-overlay)"
1551
1460
  >
1552
- <div class="flex flex-col gap-3 min-w-[240px]">
1461
+ <div class="flex flex-col gap-3">
1553
1462
  <!-- User Info -->
1554
1463
  <a
1555
1464
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1568,9 +1477,9 @@ class AppProfile {
1568
1477
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1569
1478
  </div>
1570
1479
  }
1571
- <div class="flex flex-col">
1572
- <span class="font-semibold text-color">{{ userName() }}</span>
1573
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1480
+ <div class="flex flex-col min-w-0 flex-1">
1481
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1482
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1574
1483
  </div>
1575
1484
  </a>
1576
1485
 
@@ -1596,13 +1505,13 @@ class AppProfile {
1596
1505
  </div>
1597
1506
  </div>
1598
1507
  </div>
1599
- `, 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"] }] });
1508
+ `, 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 });
1600
1509
  }
1601
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppProfile, decorators: [{
1510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppProfile, decorators: [{
1602
1511
  type: Component,
1603
1512
  args: [{
1604
1513
  selector: 'app-profile',
1605
- standalone: true,
1514
+ changeDetection: ChangeDetectionStrategy.OnPush,
1606
1515
  imports: [AngularModule, StyleClassModule],
1607
1516
  host: { class: 'relative' },
1608
1517
  template: `
@@ -1622,10 +1531,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1622
1531
  <span>Profile</span>
1623
1532
  </button>
1624
1533
  <div
1625
- 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)]"
1534
+ 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]"
1626
1535
  style="background-color: var(--surface-overlay)"
1627
1536
  >
1628
- <div class="flex flex-col gap-3 min-w-[240px]">
1537
+ <div class="flex flex-col gap-3">
1629
1538
  <!-- User Info -->
1630
1539
  <a
1631
1540
  class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline hover:bg-emphasis transition-all duration-200"
@@ -1644,9 +1553,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1644
1553
  <i class="pi pi-user text-xl text-primary-contrast"></i>
1645
1554
  </div>
1646
1555
  }
1647
- <div class="flex flex-col">
1648
- <span class="font-semibold text-color">{{ userName() }}</span>
1649
- <span class="text-sm text-muted-color">{{ userEmail() }}</span>
1556
+ <div class="flex flex-col min-w-0 flex-1">
1557
+ <span class="font-semibold text-color truncate">{{ userName() }}</span>
1558
+ <span class="text-sm text-muted-color truncate">{{ userEmail() }}</span>
1650
1559
  </div>
1651
1560
  </a>
1652
1561
 
@@ -1690,16 +1599,13 @@ class AppTopbar {
1690
1599
  this.handleOutsideClick(event);
1691
1600
  });
1692
1601
  }
1693
- // Use layoutService.companyName which falls back to appName
1694
1602
  companyName = this.layoutService.companyName;
1695
1603
  enableCompanyFeature = computed(() => {
1696
1604
  return isCompanyFeatureEnabled(this.appConfig);
1697
1605
  }, ...(ngDevMode ? [{ debugName: "enableCompanyFeature" }] : []));
1698
1606
  toggleDarkMode() {
1699
- this.layoutService.layoutConfig.update((state) => ({
1700
- ...state,
1701
- darkTheme: !state.darkTheme,
1702
- }));
1607
+ const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
1608
+ this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
1703
1609
  }
1704
1610
  togglePanel(panel) {
1705
1611
  this.activePanel.update((current) => (current === panel ? null : panel));
@@ -1711,8 +1617,8 @@ class AppTopbar {
1711
1617
  this.activePanel.set(null);
1712
1618
  }
1713
1619
  }
1714
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1715
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }], ngImport: i0, template: ` <div class="layout-topbar">
1620
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1621
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }], ngImport: i0, template: ` <div class="layout-topbar">
1716
1622
  <div class="layout-topbar-logo-container">
1717
1623
  <button
1718
1624
  class="layout-menu-button layout-topbar-action"
@@ -1733,11 +1639,9 @@ class AppTopbar {
1733
1639
  (click)="toggleDarkMode()"
1734
1640
  >
1735
1641
  <i
1736
- [ngClass]="{
1737
- 'pi ': true,
1738
- 'pi-moon': layoutService.isDarkTheme(),
1739
- 'pi-sun': !layoutService.isDarkTheme(),
1740
- }"
1642
+ class="pi"
1643
+ [class.pi-moon]="layoutService.isDarkTheme()"
1644
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1741
1645
  ></i>
1742
1646
  </button>
1743
1647
  <div class="relative" #configContainer>
@@ -1782,16 +1686,15 @@ class AppTopbar {
1782
1686
  </div>
1783
1687
  </div>
1784
1688
  </div>
1785
- </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" }] });
1689
+ </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 });
1786
1690
  }
1787
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppTopbar, decorators: [{
1691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppTopbar, decorators: [{
1788
1692
  type: Component,
1789
1693
  args: [{
1790
1694
  selector: 'app-topbar',
1791
- standalone: true,
1695
+ changeDetection: ChangeDetectionStrategy.OnPush,
1792
1696
  imports: [
1793
1697
  RouterModule,
1794
- CommonModule,
1795
1698
  StyleClassModule,
1796
1699
  AppConfigurator,
1797
1700
  AppProfile,
@@ -1819,11 +1722,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1819
1722
  (click)="toggleDarkMode()"
1820
1723
  >
1821
1724
  <i
1822
- [ngClass]="{
1823
- 'pi ': true,
1824
- 'pi-moon': layoutService.isDarkTheme(),
1825
- 'pi-sun': !layoutService.isDarkTheme(),
1826
- }"
1725
+ class="pi"
1726
+ [class.pi-moon]="layoutService.isDarkTheme()"
1727
+ [class.pi-sun]="!layoutService.isDarkTheme()"
1827
1728
  ></i>
1828
1729
  </button>
1829
1730
  <div class="relative" #configContainer>
@@ -1880,10 +1781,10 @@ class AppMenuitem {
1880
1781
  // Injected services
1881
1782
  router = inject(Router);
1882
1783
  layoutService = inject(LayoutService);
1883
- authState = inject(LAYOUT_AUTH_STATE, { optional: true });
1884
1784
  destroyRef = inject(DestroyRef);
1885
- // State signals
1886
- active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
1785
+ // State signals - private writable + public readonly pattern
1786
+ _active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
1787
+ active = this._active.asReadonly();
1887
1788
  // Computed signals
1888
1789
  key = computed(() => {
1889
1790
  const parent = this.parentKey();
@@ -1912,19 +1813,19 @@ class AppMenuitem {
1912
1813
  Promise.resolve(null).then(() => {
1913
1814
  const currentKey = this.key();
1914
1815
  if (value.routeEvent) {
1915
- this.active.set(value.key === currentKey ||
1816
+ this._active.set(value.key === currentKey ||
1916
1817
  value.key?.startsWith(currentKey + '-'));
1917
1818
  }
1918
1819
  else if (value.key !== currentKey &&
1919
1820
  !value.key?.startsWith(currentKey + '-')) {
1920
- this.active.set(false);
1821
+ this._active.set(false);
1921
1822
  }
1922
1823
  });
1923
1824
  });
1924
1825
  // Subscribe to menu reset
1925
1826
  this.layoutService.resetSource$
1926
1827
  .pipe(takeUntilDestroyed(this.destroyRef))
1927
- .subscribe(() => this.active.set(false));
1828
+ .subscribe(() => this._active.set(false));
1928
1829
  // Subscribe to navigation events
1929
1830
  this.router.events
1930
1831
  .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
@@ -1963,12 +1864,12 @@ class AppMenuitem {
1963
1864
  }
1964
1865
  itemClick() {
1965
1866
  if (this.item().children) {
1966
- this.active.update((prev) => !prev);
1867
+ this._active.update((prev) => !prev);
1967
1868
  }
1968
1869
  this.layoutService.onMenuStateChange({ key: this.key() });
1969
1870
  }
1970
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
1971
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, ngImport: i0, template: `
1871
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
1872
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, ngImport: i0, template: `
1972
1873
  <ng-container>
1973
1874
  @if (item().children?.length) {
1974
1875
  <a (click)="itemClick()" tabindex="0" pRipple>
@@ -2024,11 +1925,11 @@ class AppMenuitem {
2024
1925
  </ul>
2025
1926
  }
2026
1927
  </ng-container>
2027
- `, 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]" }] });
1928
+ `, 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 });
2028
1929
  }
2029
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenuitem, decorators: [{
1930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, decorators: [{
2030
1931
  type: Component,
2031
- args: [{ selector: '[app-menuitem]', standalone: true, imports: [IconComponent, RouterModule, RippleModule], template: `
1932
+ args: [{ selector: '[app-menuitem]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, RouterModule, RippleModule], template: `
2032
1933
  <ng-container>
2033
1934
  @if (item().children?.length) {
2034
1935
  <a (click)="itemClick()" tabindex="0" pRipple>
@@ -2103,8 +2004,8 @@ class AppMenu {
2103
2004
  * - Permission state changes (user permissions updated)
2104
2005
  */
2105
2006
  menuItems = this.layoutService.menu;
2106
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
2107
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
2007
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
2008
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
2108
2009
  <ul>
2109
2010
  @for (
2110
2011
  item of menuItems();
@@ -2114,13 +2015,13 @@ class AppMenu {
2114
2015
  <li app-menuitem [item]="item" [index]="i"></li>
2115
2016
  }
2116
2017
  </ul>
2117
- </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
2018
+ </div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2118
2019
  }
2119
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppMenu, decorators: [{
2020
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, decorators: [{
2120
2021
  type: Component,
2121
2022
  args: [{
2122
2023
  selector: 'app-menu',
2123
- standalone: true,
2024
+ changeDetection: ChangeDetectionStrategy.OnPush,
2124
2025
  imports: [AppMenuitem, RouterModule],
2125
2026
  template: `<div class="layout-menu">
2126
2027
  <ul>
@@ -2137,18 +2038,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2137
2038
  }] });
2138
2039
 
2139
2040
  class AppSidebar {
2140
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
2141
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
2041
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
2042
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
2142
2043
  <div class="layout-sidebar">
2143
2044
  <app-menu />
2144
2045
  </div>
2145
- `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
2046
+ `, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2146
2047
  }
2147
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppSidebar, decorators: [{
2048
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppSidebar, decorators: [{
2148
2049
  type: Component,
2149
2050
  args: [{
2150
2051
  selector: 'app-sidebar',
2151
- standalone: true,
2052
+ changeDetection: ChangeDetectionStrategy.OnPush,
2152
2053
  imports: [AppMenu],
2153
2054
  template: `
2154
2055
  <div class="layout-sidebar">
@@ -2165,8 +2066,6 @@ class AppLayout {
2165
2066
  renderer = inject(Renderer2);
2166
2067
  router = inject(Router);
2167
2068
  menuOutsideClickListener = null;
2168
- appSidebar;
2169
- appTopBar;
2170
2069
  constructor() {
2171
2070
  this.layoutService.overlayOpen$
2172
2071
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -2196,12 +2095,11 @@ class AppLayout {
2196
2095
  topbarEl?.contains(eventTarget));
2197
2096
  }
2198
2097
  hideMenu() {
2199
- this.layoutService.layoutState.update((prev) => ({
2200
- ...prev,
2098
+ this.layoutService.updateLayoutState({
2201
2099
  overlayMenuActive: false,
2202
2100
  staticMenuMobileActive: false,
2203
2101
  menuHoverActive: false,
2204
- }));
2102
+ });
2205
2103
  if (this.menuOutsideClickListener) {
2206
2104
  this.menuOutsideClickListener();
2207
2105
  this.menuOutsideClickListener = null;
@@ -2214,7 +2112,7 @@ class AppLayout {
2214
2112
  unblockBodyScroll() {
2215
2113
  this.document.body.classList.remove('blocked-scroll');
2216
2114
  }
2217
- get containerClass() {
2115
+ containerClass = computed(() => {
2218
2116
  const config = this.layoutService.layoutConfig();
2219
2117
  const state = this.layoutService.layoutState();
2220
2118
  return {
@@ -2224,10 +2122,10 @@ class AppLayout {
2224
2122
  'layout-overlay-active': state.overlayMenuActive,
2225
2123
  'layout-mobile-active': state.staticMenuMobileActive,
2226
2124
  };
2227
- }
2228
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
2229
- 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: `
2230
- <div class="layout-wrapper" [ngClass]="containerClass">
2125
+ }, ...(ngDevMode ? [{ debugName: "containerClass" }] : []));
2126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
2127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
2128
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2231
2129
  <app-topbar></app-topbar>
2232
2130
  <app-sidebar></app-sidebar>
2233
2131
  <div class="layout-main-container">
@@ -2238,16 +2136,16 @@ class AppLayout {
2238
2136
  </div>
2239
2137
  <div class="layout-mask animate-fadein"></div>
2240
2138
  </div>
2241
- `, 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" }] });
2139
+ `, 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 });
2242
2140
  }
2243
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AppLayout, decorators: [{
2141
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppLayout, decorators: [{
2244
2142
  type: Component,
2245
2143
  args: [{
2246
2144
  selector: 'app-layout',
2247
- standalone: true,
2248
- imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter],
2145
+ changeDetection: ChangeDetectionStrategy.OnPush,
2146
+ imports: [NgClass, AppTopbar, AppSidebar, RouterModule, AppFooter],
2249
2147
  template: `
2250
- <div class="layout-wrapper" [ngClass]="containerClass">
2148
+ <div class="layout-wrapper" [ngClass]="containerClass()">
2251
2149
  <app-topbar></app-topbar>
2252
2150
  <app-sidebar></app-sidebar>
2253
2151
  <div class="layout-main-container">
@@ -2260,50 +2158,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2260
2158
  </div>
2261
2159
  `,
2262
2160
  }]
2263
- }], ctorParameters: () => [], propDecorators: { appSidebar: [{
2264
- type: ViewChild,
2265
- args: [AppSidebar]
2266
- }], appTopBar: [{
2267
- type: ViewChild,
2268
- args: [AppTopbar]
2269
- }] } });
2161
+ }], ctorParameters: () => [] });
2270
2162
 
2271
- const GreenTheme = definePreset(Material, {
2163
+ const NavyBlueTheme = definePreset(Material, {
2272
2164
  semantic: {
2273
2165
  colorScheme: {
2274
2166
  light: {
2275
2167
  primary: {
2276
- color: '#01712c',
2277
- inverseColor: '#119744',
2278
- hoverColor: '#119744',
2279
- activeColor: '#119744',
2168
+ color: '#3535cd',
2169
+ inverseColor: '#0707a9',
2170
+ hoverColor: '#0707a9',
2171
+ activeColor: '#0707a9',
2280
2172
  },
2281
2173
  highlight: {
2282
2174
  background: '#e2e8f0',
2283
2175
  focusBackground: '#e2e8f0',
2284
- color: '#01712c',
2285
- focusColor: '#01712c',
2176
+ color: '#3535cd',
2177
+ focusColor: '#3535cd',
2286
2178
  },
2287
2179
  },
2288
2180
  },
2289
2181
  },
2290
2182
  });
2291
2183
 
2292
- const NavyBlueTheme = definePreset(Material, {
2184
+ const GreenTheme = definePreset(Material, {
2293
2185
  semantic: {
2294
2186
  colorScheme: {
2295
2187
  light: {
2296
2188
  primary: {
2297
- color: '#3535cd',
2298
- inverseColor: '#0707a9',
2299
- hoverColor: '#0707a9',
2300
- activeColor: '#0707a9',
2189
+ color: '#01712c',
2190
+ inverseColor: '#119744',
2191
+ hoverColor: '#119744',
2192
+ activeColor: '#119744',
2301
2193
  },
2302
2194
  highlight: {
2303
2195
  background: '#e2e8f0',
2304
2196
  focusBackground: '#e2e8f0',
2305
- color: '#3535cd',
2306
- focusColor: '#3535cd',
2197
+ color: '#01712c',
2198
+ focusColor: '#01712c',
2307
2199
  },
2308
2200
  },
2309
2201
  },