@flusys/ng-layout 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -11
- package/assets/layout/base/_brand.scss +44 -0
- package/assets/layout/{_core.scss → base/_core.scss} +8 -1
- package/assets/layout/{_footer.scss → base/_footer.scss} +1 -1
- package/assets/layout/{_forms.scss → base/_forms.scss} +3 -3
- package/assets/layout/base/_menu.scss +52 -0
- package/assets/layout/base/_rtl.scss +230 -0
- package/assets/layout/base/_topbar.scss +132 -0
- package/assets/layout/base/variables/_common.scss +62 -0
- package/assets/layout/classic/_main.scss +14 -0
- package/assets/layout/classic/_menu.scss +77 -0
- package/assets/layout/classic/_responsive.scss +101 -0
- package/assets/layout/classic/_rtl.scss +26 -0
- package/assets/layout/classic/_topbar.scss +46 -0
- package/assets/layout/{_topbar_nav.scss → classic/_topbar_nav.scss} +31 -15
- package/assets/layout/classic/classic.scss +6 -0
- package/assets/layout/layout.scss +18 -16
- package/assets/layout/modern/_menu.scss +98 -0
- package/assets/layout/modern/_responsive.scss +93 -0
- package/assets/layout/modern/_rtl.scss +24 -0
- package/assets/layout/modern/_shell.scss +59 -0
- package/assets/layout/modern/_sidebar.scss +101 -0
- package/assets/layout/modern/_topbar.scss +37 -0
- package/assets/layout/modern/_variables.scss +114 -0
- package/assets/layout/modern/modern.scss +7 -0
- package/fesm2022/flusys-ng-layout.mjs +1315 -939
- package/fesm2022/flusys-ng-layout.mjs.map +1 -1
- package/package.json +4 -4
- package/types/flusys-ng-layout.d.ts +171 -64
- package/assets/layout/_main.scss +0 -13
- package/assets/layout/_menu.scss +0 -188
- package/assets/layout/_responsive.scss +0 -128
- package/assets/layout/_rtl.scss +0 -482
- package/assets/layout/_topbar.scss +0 -215
- package/assets/layout/variables/_common.scss +0 -20
- /package/assets/layout/{_mixins.scss → base/_mixins.scss} +0 -0
- /package/assets/layout/{_preloading.scss → base/_preloading.scss} +0 -0
- /package/assets/layout/{_typography.scss → base/_typography.scss} +0 -0
- /package/assets/layout/{_utils.scss → base/_utils.scss} +0 -0
- /package/assets/layout/{variables → base/variables}/_dark.scss +0 -0
- /package/assets/layout/{variables → base/variables}/_light.scss +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, DestroyRef, computed, effect,
|
|
3
|
-
import
|
|
4
|
-
import { evaluateLogicNode, PermissionValidatorService, InlineSvgComponent, TranslatePipe, TranslateService, IconComponent, AngularModule } from '@flusys/ng-shared';
|
|
5
|
-
import { isPlatformBrowser, NgComponentOutlet, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, DestroyRef, computed, effect, Renderer2, Component, input, viewChild, InjectionToken, ElementRef } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, DOCUMENT as DOCUMENT$1, NgComponentOutlet } from '@angular/common';
|
|
6
4
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
5
|
import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled, isSignUpEnabled } from '@flusys/ng-core';
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import * as i2$1 from '@angular/router';
|
|
12
|
-
import { Router, NavigationEnd, isActive, RouterModule } from '@angular/router';
|
|
6
|
+
import * as i3 from '@flusys/ng-shared';
|
|
7
|
+
import { evaluateLogicNode, PermissionValidatorService, InlineSvgComponent, TranslatePipe, IconComponent, TranslateService, AngularModule } from '@flusys/ng-shared';
|
|
8
|
+
import { Subject, fromEvent, filter } from 'rxjs';
|
|
13
9
|
import * as i1 from '@flusys/ng-ui';
|
|
14
|
-
import { PRESET_NAMES, applyTheme, SURFACE_PALETTES, PRIMARY_PALETTES, applyPrimaryPalette, applySurfacePalette, PRESETS,
|
|
10
|
+
import { PRESET_NAMES, UiModule, applyTheme, SURFACE_PALETTES, PRIMARY_PALETTES, NOIR_PRIMARY, applyPrimaryPalette, applySurfacePalette, PRESETS, MessageService } from '@flusys/ng-ui';
|
|
15
11
|
export { applyTheme, buildBootstrapPreset } from '@flusys/ng-ui';
|
|
16
|
-
import
|
|
12
|
+
import * as i2 from '@angular/router';
|
|
13
|
+
import { Router, NavigationEnd, isActive, RouterModule } from '@angular/router';
|
|
14
|
+
import { filter as filter$1 } from 'rxjs/operators';
|
|
15
|
+
import * as i2$1 from '@angular/forms';
|
|
16
|
+
import { FormsModule } from '@angular/forms';
|
|
17
17
|
|
|
18
18
|
/** Filter launcher apps based on user permission codes */
|
|
19
19
|
function filterAppsByPermissions(apps, permissionCodes) {
|
|
@@ -76,11 +76,13 @@ function filterMenuItem(item, permissionCode) {
|
|
|
76
76
|
|
|
77
77
|
const STORAGE_KEY = 'flusys.layout.config';
|
|
78
78
|
const STORAGE_VERSION = 1;
|
|
79
|
+
// Sourced from the theme package so a new preset never silently fails validation.
|
|
80
|
+
const VALID_PRESETS = PRESET_NAMES;
|
|
81
|
+
const VALID_MENU_MODES = ['static', 'overlay', 'topbar'];
|
|
82
|
+
const VALID_LAYOUT_THEMES = ['classic', 'modern'];
|
|
79
83
|
class LayoutPersistenceService {
|
|
80
84
|
platformId = inject(PLATFORM_ID);
|
|
81
85
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
82
|
-
validPresets = ['Aura', 'Lara', 'Nora'];
|
|
83
|
-
validMenuModes = ['static', 'overlay', 'topbar'];
|
|
84
86
|
load() {
|
|
85
87
|
if (!this.isBrowser)
|
|
86
88
|
return null;
|
|
@@ -88,8 +90,7 @@ class LayoutPersistenceService {
|
|
|
88
90
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
89
91
|
if (!stored)
|
|
90
92
|
return null;
|
|
91
|
-
|
|
92
|
-
return this.validateConfig(parsed);
|
|
93
|
+
return this.validateConfig(JSON.parse(stored));
|
|
93
94
|
}
|
|
94
95
|
catch {
|
|
95
96
|
this.clear();
|
|
@@ -107,7 +108,7 @@ class LayoutPersistenceService {
|
|
|
107
108
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(toStore));
|
|
108
109
|
}
|
|
109
110
|
catch {
|
|
110
|
-
//
|
|
111
|
+
// localStorage may be full or disabled — the config simply stays unsaved.
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
clear() {
|
|
@@ -115,22 +116,32 @@ class LayoutPersistenceService {
|
|
|
115
116
|
return;
|
|
116
117
|
localStorage.removeItem(STORAGE_KEY);
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Coerces a stored value only when it is present and unrecognised. A field the
|
|
121
|
+
* user never changed stays absent so the app's own default still wins when this
|
|
122
|
+
* result is merged over it.
|
|
123
|
+
*/
|
|
124
|
+
validateConfig(stored) {
|
|
125
|
+
if (stored._version && stored._version !== STORAGE_VERSION) {
|
|
120
126
|
this.clear();
|
|
121
127
|
return null;
|
|
122
128
|
}
|
|
123
|
-
|
|
129
|
+
const { _version, ...config } = stored;
|
|
130
|
+
if (config.preset !== undefined && !VALID_PRESETS.includes(config.preset)) {
|
|
124
131
|
config.preset = 'Aura';
|
|
125
132
|
}
|
|
126
|
-
if (config.menuMode
|
|
133
|
+
if (config.menuMode !== undefined &&
|
|
134
|
+
!VALID_MENU_MODES.includes(config.menuMode)) {
|
|
127
135
|
config.menuMode = 'static';
|
|
128
136
|
}
|
|
129
|
-
if (
|
|
137
|
+
if (config.layoutTheme !== undefined &&
|
|
138
|
+
!VALID_LAYOUT_THEMES.includes(config.layoutTheme)) {
|
|
139
|
+
config.layoutTheme = 'classic';
|
|
140
|
+
}
|
|
141
|
+
if (config.darkTheme !== undefined && typeof config.darkTheme !== 'boolean') {
|
|
130
142
|
config.darkTheme = false;
|
|
131
143
|
}
|
|
132
|
-
|
|
133
|
-
return cleanConfig;
|
|
144
|
+
return config;
|
|
134
145
|
}
|
|
135
146
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutPersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
136
147
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutPersistenceService, providedIn: 'root' });
|
|
@@ -140,6 +151,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
140
151
|
args: [{ providedIn: 'root' }]
|
|
141
152
|
}] });
|
|
142
153
|
|
|
154
|
+
/** Matches the 991px shell breakpoint both skins use in SCSS. */
|
|
143
155
|
const DESKTOP_BREAKPOINT = 991;
|
|
144
156
|
class LayoutService {
|
|
145
157
|
document = inject(DOCUMENT);
|
|
@@ -153,6 +165,7 @@ class LayoutService {
|
|
|
153
165
|
surface: null,
|
|
154
166
|
darkTheme: false,
|
|
155
167
|
menuMode: 'static',
|
|
168
|
+
layoutTheme: 'classic',
|
|
156
169
|
};
|
|
157
170
|
DEFAULT_STATE = {
|
|
158
171
|
staticMenuDesktopInactive: false,
|
|
@@ -211,22 +224,32 @@ class LayoutService {
|
|
|
211
224
|
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
212
225
|
isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', /* @ts-ignore */
|
|
213
226
|
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
214
|
-
|
|
227
|
+
layoutTheme = computed(() => this._layoutConfig().layoutTheme ?? 'classic', /* @ts-ignore */
|
|
228
|
+
...(ngDevMode ? [{ debugName: "layoutTheme" }] : /* istanbul ignore next */ []));
|
|
229
|
+
isModernLayout = computed(() => this.layoutTheme() === 'modern', /* @ts-ignore */
|
|
230
|
+
...(ngDevMode ? [{ debugName: "isModernLayout" }] : /* istanbul ignore next */ []));
|
|
231
|
+
isClassicLayout = computed(() => this.layoutTheme() === 'classic', /* @ts-ignore */
|
|
232
|
+
...(ngDevMode ? [{ debugName: "isClassicLayout" }] : /* istanbul ignore next */ []));
|
|
233
|
+
/** The modern skin has no horizontal nav, so topbar mode only applies to the classic skin. */
|
|
234
|
+
isTopbar = computed(() => this._layoutConfig().menuMode === 'topbar' && !this.isModernLayout(), /* @ts-ignore */
|
|
215
235
|
...(ngDevMode ? [{ debugName: "isTopbar" }] : /* istanbul ignore next */ []));
|
|
216
236
|
userName = computed(() => this._userProfile()?.name ?? 'Guest', /* @ts-ignore */
|
|
217
237
|
...(ngDevMode ? [{ debugName: "userName" }] : /* istanbul ignore next */ []));
|
|
218
238
|
userEmail = computed(() => this._userProfile()?.email ?? '', /* @ts-ignore */
|
|
219
239
|
...(ngDevMode ? [{ debugName: "userEmail" }] : /* istanbul ignore next */ []));
|
|
220
|
-
|
|
240
|
+
// `||`, not `??`, throughout the block below: the backend returns "no picture" and
|
|
241
|
+
// "no logo" as an empty string as often as it does null, and an empty string would
|
|
242
|
+
// slip past `??` and render as a broken image.
|
|
243
|
+
userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl || null, /* @ts-ignore */
|
|
221
244
|
...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : /* istanbul ignore next */ []));
|
|
222
|
-
companyLogoUrl = computed(() => this._companyProfile()?.logoUrl
|
|
245
|
+
companyLogoUrl = computed(() => this._companyProfile()?.logoUrl || null, /* @ts-ignore */
|
|
223
246
|
...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : /* istanbul ignore next */ []));
|
|
224
247
|
isAuthenticated = computed(() => !!this._userProfile(), /* @ts-ignore */
|
|
225
248
|
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
226
249
|
companyName = computed(() => {
|
|
227
250
|
if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig))
|
|
228
251
|
return this.appName;
|
|
229
|
-
return this._companyProfile()?.name
|
|
252
|
+
return this._companyProfile()?.name || this.appName;
|
|
230
253
|
}, /* @ts-ignore */
|
|
231
254
|
...(ngDevMode ? [{ debugName: "companyName" }] : /* istanbul ignore next */ []));
|
|
232
255
|
displayLogo = computed(() => this.companyLogoUrl() ?? this.appLogo, /* @ts-ignore */
|
|
@@ -240,6 +263,7 @@ class LayoutService {
|
|
|
240
263
|
resetSource$ = this.resetSource.asObservable();
|
|
241
264
|
overlayOpen$ = this.overlayOpen.asObservable();
|
|
242
265
|
initialized = false;
|
|
266
|
+
appliedDarkTheme = this._layoutConfig().darkTheme;
|
|
243
267
|
constructor() {
|
|
244
268
|
if (this.isBrowser) {
|
|
245
269
|
this.toggleDarkMode();
|
|
@@ -252,7 +276,13 @@ class LayoutService {
|
|
|
252
276
|
effect(() => {
|
|
253
277
|
const config = this._layoutConfig();
|
|
254
278
|
if (this.initialized) {
|
|
255
|
-
|
|
279
|
+
// Only the dark-mode flip is worth a view transition. Every other field —
|
|
280
|
+
// preset, primary, menu mode, layout skin — used to start one too, which
|
|
281
|
+
// cross-faded the whole page for a change that never touched it.
|
|
282
|
+
if (config.darkTheme !== this.appliedDarkTheme) {
|
|
283
|
+
this.appliedDarkTheme = config.darkTheme;
|
|
284
|
+
this.handleDarkModeTransition(config);
|
|
285
|
+
}
|
|
256
286
|
if (this.memorable)
|
|
257
287
|
this.persistence.save(config);
|
|
258
288
|
}
|
|
@@ -357,6 +387,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
357
387
|
args: [{ providedIn: 'root' }]
|
|
358
388
|
}], ctorParameters: () => [] });
|
|
359
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Shared shell behaviour for every layout skin.
|
|
392
|
+
* Holds the menu open/close lifecycle so a skin only owns markup and styling.
|
|
393
|
+
*/
|
|
394
|
+
class LayoutShellBase {
|
|
395
|
+
destroyRef = inject(DestroyRef);
|
|
396
|
+
document = inject(DOCUMENT);
|
|
397
|
+
renderer = inject(Renderer2);
|
|
398
|
+
router = inject(Router);
|
|
399
|
+
layoutService = inject(LayoutService);
|
|
400
|
+
menuOutsideClickListener = null;
|
|
401
|
+
/** Extra work a skin needs on every completed navigation. */
|
|
402
|
+
onNavigated() { }
|
|
403
|
+
constructor() {
|
|
404
|
+
this.layoutService.overlayOpen$
|
|
405
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
406
|
+
.subscribe(() => {
|
|
407
|
+
if (!this.menuOutsideClickListener) {
|
|
408
|
+
this.menuOutsideClickListener = this.renderer.listen(this.document, 'click', (event) => {
|
|
409
|
+
if (this.isOutsideClicked(event)) {
|
|
410
|
+
this.hideMenu();
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (this.layoutService.layoutState().staticMenuMobileActive) {
|
|
415
|
+
this.blockBodyScroll();
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
this.router.events
|
|
419
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
420
|
+
.subscribe(() => {
|
|
421
|
+
this.hideMenu();
|
|
422
|
+
this.onNavigated();
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
isOutsideClicked(event) {
|
|
426
|
+
const eventTarget = event.target;
|
|
427
|
+
// querySelectorAll, not querySelector: a skin may render the same control more
|
|
428
|
+
// than once (the modern shell has a menu button in both the sidebar and the
|
|
429
|
+
// topbar) and matching only the first one reads a real click as an outside one.
|
|
430
|
+
return !this.outsideClickSelectors().some((selector) => Array.from(this.document.querySelectorAll(selector)).some((element) => element.isSameNode(eventTarget) || element.contains(eventTarget)));
|
|
431
|
+
}
|
|
432
|
+
hideMenu() {
|
|
433
|
+
this.layoutService.updateLayoutState({
|
|
434
|
+
overlayMenuActive: false,
|
|
435
|
+
staticMenuMobileActive: false,
|
|
436
|
+
menuHoverActive: false,
|
|
437
|
+
});
|
|
438
|
+
if (this.menuOutsideClickListener) {
|
|
439
|
+
this.menuOutsideClickListener();
|
|
440
|
+
this.menuOutsideClickListener = null;
|
|
441
|
+
}
|
|
442
|
+
this.unblockBodyScroll();
|
|
443
|
+
}
|
|
444
|
+
blockBodyScroll() {
|
|
445
|
+
this.document.body.classList.add('blocked-scroll');
|
|
446
|
+
}
|
|
447
|
+
unblockBodyScroll() {
|
|
448
|
+
this.document.body.classList.remove('blocked-scroll');
|
|
449
|
+
}
|
|
450
|
+
isOverlay = this.layoutService.isOverlay;
|
|
451
|
+
/** Delegated so a skin that has no horizontal nav is never treated as being in it. */
|
|
452
|
+
isTopbarMode = this.layoutService.isTopbar;
|
|
453
|
+
isOverlayActive = computed(() => this.layoutService.layoutState().overlayMenuActive, /* @ts-ignore */
|
|
454
|
+
...(ngDevMode ? [{ debugName: "isOverlayActive" }] : /* istanbul ignore next */ []));
|
|
455
|
+
isMobileActive = computed(() => this.layoutService.layoutState().staticMenuMobileActive, /* @ts-ignore */
|
|
456
|
+
...(ngDevMode ? [{ debugName: "isMobileActive" }] : /* istanbul ignore next */ []));
|
|
457
|
+
/**
|
|
458
|
+
* The desktop sidebar is collapsed in place. Only meaningful for the modes that
|
|
459
|
+
* keep the sidebar in the layout flow — an overlay drawer and the horizontal nav
|
|
460
|
+
* have nothing to collapse.
|
|
461
|
+
*/
|
|
462
|
+
isDesktopCollapsed = computed(() => !!this.layoutService.layoutState().staticMenuDesktopInactive &&
|
|
463
|
+
!this.isOverlay() &&
|
|
464
|
+
!this.isTopbarMode(), /* @ts-ignore */
|
|
465
|
+
...(ngDevMode ? [{ debugName: "isDesktopCollapsed" }] : /* istanbul ignore next */ []));
|
|
466
|
+
}
|
|
467
|
+
|
|
360
468
|
class AppFooter {
|
|
361
469
|
layoutService = inject(LayoutService);
|
|
362
470
|
appName = this.layoutService.appName;
|
|
@@ -412,709 +520,698 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
412
520
|
}]
|
|
413
521
|
}] });
|
|
414
522
|
|
|
415
|
-
class
|
|
523
|
+
class AppMenuitem {
|
|
524
|
+
// Signal inputs
|
|
525
|
+
item = input.required(/* @ts-ignore */
|
|
526
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
527
|
+
index = input.required(/* @ts-ignore */
|
|
528
|
+
...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
529
|
+
parentKey = input('', /* @ts-ignore */
|
|
530
|
+
...(ngDevMode ? [{ debugName: "parentKey" }] : /* istanbul ignore next */ []));
|
|
531
|
+
// Injected services
|
|
416
532
|
router = inject(Router);
|
|
417
|
-
translateAdapter = inject(TranslateService, { optional: true });
|
|
418
|
-
platformId = inject(PLATFORM_ID);
|
|
419
533
|
layoutService = inject(LayoutService);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return
|
|
534
|
+
destroyRef = inject(DestroyRef);
|
|
535
|
+
// View references
|
|
536
|
+
parentLink = viewChild('parentLink', /* @ts-ignore */
|
|
537
|
+
...(ngDevMode ? [{ debugName: "parentLink" }] : /* istanbul ignore next */ []));
|
|
538
|
+
// State signals - private writable + public readonly pattern
|
|
539
|
+
_active = signal(false, /* @ts-ignore */
|
|
540
|
+
...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
541
|
+
active = this._active.asReadonly();
|
|
542
|
+
// Computed signals
|
|
543
|
+
key = computed(() => {
|
|
544
|
+
const parent = this.parentKey();
|
|
545
|
+
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
432
546
|
}, /* @ts-ignore */
|
|
433
|
-
...(ngDevMode ? [{ debugName: "
|
|
547
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
548
|
+
routerLink = computed(() => {
|
|
549
|
+
const menuItem = this.item();
|
|
550
|
+
return menuItem.routerLink ?? [];
|
|
551
|
+
}, /* @ts-ignore */
|
|
552
|
+
...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
553
|
+
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
554
|
+
routerLinkActiveOptions = computed(() => {
|
|
555
|
+
const link = this.routerLink();
|
|
556
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
557
|
+
return {
|
|
558
|
+
paths: pathsMatch,
|
|
559
|
+
queryParams: 'ignored',
|
|
560
|
+
matrixParams: 'ignored',
|
|
561
|
+
fragment: 'ignored',
|
|
562
|
+
};
|
|
563
|
+
}, /* @ts-ignore */
|
|
564
|
+
...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : /* istanbul ignore next */ []));
|
|
434
565
|
constructor() {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
566
|
+
// Subscribe to menu source changes
|
|
567
|
+
this.layoutService.menuSource$
|
|
568
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
569
|
+
.subscribe((value) => {
|
|
570
|
+
Promise.resolve(null).then(() => {
|
|
571
|
+
const currentKey = this.key();
|
|
572
|
+
if (value.routeEvent) {
|
|
573
|
+
this._active.set(value.key === currentKey ||
|
|
574
|
+
value.key?.startsWith(currentKey + '-'));
|
|
575
|
+
}
|
|
576
|
+
else if (value.key !== currentKey &&
|
|
577
|
+
!value.key?.startsWith(currentKey + '-')) {
|
|
578
|
+
this._active.set(false);
|
|
579
|
+
}
|
|
444
580
|
});
|
|
581
|
+
});
|
|
582
|
+
// Subscribe to menu reset
|
|
583
|
+
this.layoutService.resetSource$
|
|
584
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
585
|
+
.subscribe(() => this._active.set(false));
|
|
586
|
+
// Subscribe to navigation events
|
|
587
|
+
this.router.events
|
|
588
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
589
|
+
.subscribe(() => {
|
|
590
|
+
if (this.item().routerLink) {
|
|
591
|
+
this.updateActiveStateFromRoute();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
// Effect to update active state on init when item has routerLink
|
|
595
|
+
effect(() => {
|
|
596
|
+
const menuItem = this.item();
|
|
597
|
+
if (menuItem.routerLink) {
|
|
598
|
+
this.updateActiveStateFromRoute();
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
// Effect to update submenu position when active in topbar mode
|
|
602
|
+
effect(() => {
|
|
603
|
+
if (this.active() && this.parentLink()) {
|
|
604
|
+
Promise.resolve().then(() => this.updateSubmenuPosition());
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
onMouseEnter() {
|
|
609
|
+
if (this.layoutService.isTopbar() && this.item().children) {
|
|
610
|
+
this.updateSubmenuPosition();
|
|
445
611
|
}
|
|
446
612
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
return isDark ? surfaceName === 'zinc' : surfaceName === 'slate';
|
|
613
|
+
updateSubmenuPosition() {
|
|
614
|
+
const linkEl = this.parentLink()?.nativeElement;
|
|
615
|
+
if (!linkEl || typeof linkEl.getBoundingClientRect !== 'function')
|
|
616
|
+
return;
|
|
617
|
+
const rect = linkEl.getBoundingClientRect();
|
|
618
|
+
const hostEl = linkEl.closest('[app-menuitem]');
|
|
619
|
+
if (!hostEl)
|
|
620
|
+
return;
|
|
621
|
+
// Position submenu to the left of the parent link and above it
|
|
622
|
+
const left = rect.left;
|
|
623
|
+
const top = rect.top + 30; // 8px gap above the menu item
|
|
624
|
+
hostEl.style.setProperty('--submenu-left', `${left}px`);
|
|
625
|
+
hostEl.style.setProperty('--submenu-top', `${top}px`);
|
|
461
626
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (
|
|
476
|
-
this.layoutService.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
627
|
+
updateActiveStateFromRoute() {
|
|
628
|
+
const link = this.routerLink();
|
|
629
|
+
if (!link.length)
|
|
630
|
+
return;
|
|
631
|
+
// Use 'exact' for root path to avoid matching all routes
|
|
632
|
+
// Use 'subset' for all other paths to match child routes
|
|
633
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
634
|
+
const active = isActive(link[0], this.router, {
|
|
635
|
+
paths: pathsMatch,
|
|
636
|
+
queryParams: 'ignored',
|
|
637
|
+
matrixParams: 'ignored',
|
|
638
|
+
fragment: 'ignored',
|
|
639
|
+
})();
|
|
640
|
+
if (active) {
|
|
641
|
+
this.layoutService.onMenuStateChange({
|
|
642
|
+
key: this.key(),
|
|
643
|
+
routeEvent: true,
|
|
644
|
+
});
|
|
480
645
|
}
|
|
481
|
-
this._applyColorChange(type, color);
|
|
482
|
-
event.stopPropagation();
|
|
483
646
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const primary = color.name ?? 'violet';
|
|
488
|
-
const pal = primary === 'noir'
|
|
489
|
-
? Object.fromEntries(Object.entries(SURFACE_PALETTES['slate']).map(([k, v]) => [k, v]))
|
|
490
|
-
: PRIMARY_PALETTES[primary];
|
|
491
|
-
if (pal)
|
|
492
|
-
applyPrimaryPalette(pal, isDark);
|
|
493
|
-
}
|
|
494
|
-
else if (type === 'surface') {
|
|
495
|
-
const surfacePal = SURFACE_PALETTES[color.name ?? 'slate'];
|
|
496
|
-
if (surfacePal)
|
|
497
|
-
applySurfacePalette(surfacePal);
|
|
647
|
+
itemClick() {
|
|
648
|
+
if (this.item().children && !this.layoutService.isTopbar()) {
|
|
649
|
+
this._active.update((prev) => !prev);
|
|
498
650
|
}
|
|
651
|
+
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
499
652
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
<div>
|
|
517
|
-
<span class="text-sm text-muted-color font-semibold">{{
|
|
518
|
-
'layout.configurator.primary' | translate
|
|
519
|
-
}}</span>
|
|
520
|
-
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
521
|
-
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
522
|
-
<button
|
|
523
|
-
type="button"
|
|
524
|
-
[title]="primaryColor.name"
|
|
525
|
-
(click)="updateColors($event, 'primary', primaryColor)"
|
|
526
|
-
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
527
|
-
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
528
|
-
[style.background-color]="
|
|
529
|
-
primaryColor?.name === 'noir'
|
|
530
|
-
? 'var(--f-text-color)'
|
|
531
|
-
: primaryColor?.palette?.['500']
|
|
532
|
-
"
|
|
533
|
-
></button>
|
|
653
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
654
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", 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()" } }, viewQueries: [{ propertyName: "parentLink", first: true, predicate: ["parentLink"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
655
|
+
<ng-container>
|
|
656
|
+
@if (item().children?.length) {
|
|
657
|
+
<a
|
|
658
|
+
(click)="itemClick()"
|
|
659
|
+
(mouseenter)="onMouseEnter()"
|
|
660
|
+
#parentLink
|
|
661
|
+
tabindex="0"
|
|
662
|
+
>
|
|
663
|
+
@if (item().icon) {
|
|
664
|
+
<lib-icon
|
|
665
|
+
[icon]="item().icon!"
|
|
666
|
+
[iconType]="item().iconType"
|
|
667
|
+
class="layout-menuitem-icon"
|
|
668
|
+
/>
|
|
534
669
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
type="button"
|
|
545
|
-
[title]="surface.name"
|
|
546
|
-
(click)="updateColors($event, 'surface', surface)"
|
|
547
|
-
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
548
|
-
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
549
|
-
[style.background-color]="
|
|
550
|
-
surface?.name === 'noir'
|
|
551
|
-
? 'var(--f-text-color)'
|
|
552
|
-
: surface?.palette?.['500']
|
|
553
|
-
"
|
|
554
|
-
></button>
|
|
670
|
+
<span class="layout-menuitem-text">{{
|
|
671
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
672
|
+
}}</span>
|
|
673
|
+
@if (item().children) {
|
|
674
|
+
@if (layoutService.isTopbar()) {
|
|
675
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
676
|
+
} @else {
|
|
677
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
678
|
+
}
|
|
555
679
|
}
|
|
556
|
-
</
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
<
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
[
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
680
|
+
</a>
|
|
681
|
+
}
|
|
682
|
+
@if (item().routerLink && !item().children?.length) {
|
|
683
|
+
<a
|
|
684
|
+
(click)="itemClick()"
|
|
685
|
+
[routerLink]="routerLink()"
|
|
686
|
+
routerLinkActive="active-route"
|
|
687
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
688
|
+
tabindex="0"
|
|
689
|
+
>
|
|
690
|
+
@if (item().icon) {
|
|
691
|
+
<lib-icon
|
|
692
|
+
[icon]="item().icon!"
|
|
693
|
+
[iconType]="item().iconType"
|
|
694
|
+
class="layout-menuitem-icon"
|
|
695
|
+
/>
|
|
696
|
+
}
|
|
697
|
+
<span class="layout-menuitem-text">{{
|
|
698
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
574
699
|
}}</span>
|
|
575
|
-
|
|
576
|
-
[ngModel]="menuMode()"
|
|
577
|
-
(ngModelChange)="onMenuModeChange($event)"
|
|
578
|
-
[options]="menuModeOptions()"
|
|
579
|
-
[allowEmpty]="false"
|
|
580
|
-
size="small"
|
|
581
|
-
/>
|
|
582
|
-
</div>
|
|
700
|
+
</a>
|
|
583
701
|
}
|
|
584
|
-
|
|
585
|
-
|
|
702
|
+
@if (item().children) {
|
|
703
|
+
<ul
|
|
704
|
+
(mouseenter)="onMouseEnter()"
|
|
705
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
706
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
707
|
+
>
|
|
708
|
+
@for (
|
|
709
|
+
child of item().children;
|
|
710
|
+
track 'menu' + child.id + '' + i;
|
|
711
|
+
let i = $index
|
|
712
|
+
) {
|
|
713
|
+
<li
|
|
714
|
+
app-menuitem
|
|
715
|
+
[item]="child"
|
|
716
|
+
[index]="i"
|
|
717
|
+
[parentKey]="key()"
|
|
718
|
+
></li>
|
|
719
|
+
}
|
|
720
|
+
</ul>
|
|
721
|
+
}
|
|
722
|
+
</ng-container>
|
|
723
|
+
`, 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: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
586
724
|
}
|
|
587
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
725
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
588
726
|
type: Component,
|
|
589
|
-
args: [{
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
605
|
-
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
606
|
-
[style.background-color]="
|
|
607
|
-
primaryColor?.name === 'noir'
|
|
608
|
-
? 'var(--f-text-color)'
|
|
609
|
-
: primaryColor?.palette?.['500']
|
|
610
|
-
"
|
|
611
|
-
></button>
|
|
612
|
-
}
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
<div>
|
|
616
|
-
<span class="text-sm text-muted-color font-semibold">{{
|
|
617
|
-
'layout.configurator.surface' | translate
|
|
618
|
-
}}</span>
|
|
619
|
-
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
620
|
-
@for (surface of surfaces; track surface.name) {
|
|
621
|
-
<button
|
|
622
|
-
type="button"
|
|
623
|
-
[title]="surface.name"
|
|
624
|
-
(click)="updateColors($event, 'surface', surface)"
|
|
625
|
-
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
626
|
-
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
627
|
-
[style.background-color]="
|
|
628
|
-
surface?.name === 'noir'
|
|
629
|
-
? 'var(--f-text-color)'
|
|
630
|
-
: surface?.palette?.['500']
|
|
631
|
-
"
|
|
632
|
-
></button>
|
|
727
|
+
args: [{ selector: '[app-menuitem]', imports: [UiModule, IconComponent, RouterModule, TranslatePipe], template: `
|
|
728
|
+
<ng-container>
|
|
729
|
+
@if (item().children?.length) {
|
|
730
|
+
<a
|
|
731
|
+
(click)="itemClick()"
|
|
732
|
+
(mouseenter)="onMouseEnter()"
|
|
733
|
+
#parentLink
|
|
734
|
+
tabindex="0"
|
|
735
|
+
>
|
|
736
|
+
@if (item().icon) {
|
|
737
|
+
<lib-icon
|
|
738
|
+
[icon]="item().icon!"
|
|
739
|
+
[iconType]="item().iconType"
|
|
740
|
+
class="layout-menuitem-icon"
|
|
741
|
+
/>
|
|
633
742
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
<div class="flex flex-col gap-2">
|
|
637
|
-
<span class="text-sm text-muted-color font-semibold">{{
|
|
638
|
-
'layout.configurator.presets' | translate
|
|
639
|
-
}}</span>
|
|
640
|
-
<f-selectbutton
|
|
641
|
-
[options]="presets"
|
|
642
|
-
[ngModel]="selectedPreset()"
|
|
643
|
-
(ngModelChange)="onPresetChange($event)"
|
|
644
|
-
[allowEmpty]="false"
|
|
645
|
-
size="small"
|
|
646
|
-
/>
|
|
647
|
-
</div>
|
|
648
|
-
@if (showMenuModeButton()) {
|
|
649
|
-
<div class="flex flex-col gap-2">
|
|
650
|
-
<span class="text-sm text-muted-color font-semibold">{{
|
|
651
|
-
'layout.configurator.menu.mode' | translate
|
|
743
|
+
<span class="layout-menuitem-text">{{
|
|
744
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
652
745
|
}}</span>
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
</
|
|
746
|
+
@if (item().children) {
|
|
747
|
+
@if (layoutService.isTopbar()) {
|
|
748
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
749
|
+
} @else {
|
|
750
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
</a>
|
|
661
754
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
755
|
+
@if (item().routerLink && !item().children?.length) {
|
|
756
|
+
<a
|
|
757
|
+
(click)="itemClick()"
|
|
758
|
+
[routerLink]="routerLink()"
|
|
759
|
+
routerLinkActive="active-route"
|
|
760
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
761
|
+
tabindex="0"
|
|
762
|
+
>
|
|
763
|
+
@if (item().icon) {
|
|
764
|
+
<lib-icon
|
|
765
|
+
[icon]="item().icon!"
|
|
766
|
+
[iconType]="item().iconType"
|
|
767
|
+
class="layout-menuitem-icon"
|
|
768
|
+
/>
|
|
769
|
+
}
|
|
770
|
+
<span class="layout-menuitem-text">{{
|
|
771
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
772
|
+
}}</span>
|
|
773
|
+
</a>
|
|
774
|
+
}
|
|
775
|
+
@if (item().children) {
|
|
776
|
+
<ul
|
|
777
|
+
(mouseenter)="onMouseEnter()"
|
|
778
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
779
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
780
|
+
>
|
|
781
|
+
@for (
|
|
782
|
+
child of item().children;
|
|
783
|
+
track 'menu' + child.id + '' + i;
|
|
784
|
+
let i = $index
|
|
785
|
+
) {
|
|
786
|
+
<li
|
|
787
|
+
app-menuitem
|
|
788
|
+
[item]="child"
|
|
789
|
+
[index]="i"
|
|
790
|
+
[parentKey]="key()"
|
|
791
|
+
></li>
|
|
792
|
+
}
|
|
793
|
+
</ul>
|
|
794
|
+
}
|
|
795
|
+
</ng-container>
|
|
796
|
+
`, host: {
|
|
797
|
+
'[class.active-menuitem]': 'active()',
|
|
798
|
+
}, 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"] }]
|
|
799
|
+
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }], parentLink: [{ type: i0.ViewChild, args: ['parentLink', { isSignal: true }] }] } });
|
|
679
800
|
|
|
680
|
-
|
|
801
|
+
/**
|
|
802
|
+
* Main menu component that displays filtered menu items.
|
|
803
|
+
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
804
|
+
* The filtering happens internally in LayoutService via computed signal.
|
|
805
|
+
*/
|
|
806
|
+
class AppMenu {
|
|
681
807
|
layoutService = inject(LayoutService);
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
type="button"
|
|
699
|
-
(click)="toggleDarkMode()"
|
|
700
|
-
[rounded]="true"
|
|
701
|
-
severity="secondary"
|
|
702
|
-
>
|
|
703
|
-
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
704
|
-
</button>
|
|
705
|
-
@if (languageSelectorPanel) {
|
|
706
|
-
<div class="relative">
|
|
707
|
-
<button
|
|
708
|
-
fButton
|
|
709
|
-
fStyleClass="@next"
|
|
710
|
-
enterFromClass="hidden"
|
|
711
|
-
enterActiveClass="animate-scalein"
|
|
712
|
-
leaveToClass="hidden"
|
|
713
|
-
leaveActiveClass="animate-fadeout"
|
|
714
|
-
[hideOnOutsideClick]="true"
|
|
715
|
-
type="button"
|
|
716
|
-
rounded
|
|
717
|
-
>
|
|
718
|
-
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
719
|
-
</button>
|
|
720
|
-
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
721
|
-
</div>
|
|
808
|
+
/**
|
|
809
|
+
* Filtered menu items from layout service.
|
|
810
|
+
* This signal is computed in LayoutService and automatically updates when:
|
|
811
|
+
* - Raw menu changes (setMenu called)
|
|
812
|
+
* - Permission state changes (user permissions updated)
|
|
813
|
+
*/
|
|
814
|
+
menuItems = this.layoutService.menu;
|
|
815
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
816
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
817
|
+
<ul>
|
|
818
|
+
@for (
|
|
819
|
+
item of menuItems();
|
|
820
|
+
track 'menu' + item.id + '' + i;
|
|
821
|
+
let i = $index
|
|
822
|
+
) {
|
|
823
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
722
824
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
fButton
|
|
726
|
-
fStyleClass="@next"
|
|
727
|
-
enterFromClass="hidden"
|
|
728
|
-
enterActiveClass="animate-scalein"
|
|
729
|
-
leaveToClass="hidden"
|
|
730
|
-
leaveActiveClass="animate-fadeout"
|
|
731
|
-
[hideOnOutsideClick]="true"
|
|
732
|
-
type="button"
|
|
733
|
-
rounded
|
|
734
|
-
>
|
|
735
|
-
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
736
|
-
</button>
|
|
737
|
-
<app-configurator />
|
|
738
|
-
</div>
|
|
739
|
-
</div>
|
|
740
|
-
`, isInline: true, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }] });
|
|
825
|
+
</ul>
|
|
826
|
+
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
741
827
|
}
|
|
742
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, decorators: [{
|
|
743
829
|
type: Component,
|
|
744
|
-
args: [{
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
756
|
-
</button>
|
|
757
|
-
@if (languageSelectorPanel) {
|
|
758
|
-
<div class="relative">
|
|
759
|
-
<button
|
|
760
|
-
fButton
|
|
761
|
-
fStyleClass="@next"
|
|
762
|
-
enterFromClass="hidden"
|
|
763
|
-
enterActiveClass="animate-scalein"
|
|
764
|
-
leaveToClass="hidden"
|
|
765
|
-
leaveActiveClass="animate-fadeout"
|
|
766
|
-
[hideOnOutsideClick]="true"
|
|
767
|
-
type="button"
|
|
768
|
-
rounded
|
|
769
|
-
>
|
|
770
|
-
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
771
|
-
</button>
|
|
772
|
-
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
773
|
-
</div>
|
|
830
|
+
args: [{
|
|
831
|
+
selector: 'app-menu',
|
|
832
|
+
imports: [AppMenuitem, RouterModule],
|
|
833
|
+
template: `<div class="layout-menu">
|
|
834
|
+
<ul>
|
|
835
|
+
@for (
|
|
836
|
+
item of menuItems();
|
|
837
|
+
track 'menu' + item.id + '' + i;
|
|
838
|
+
let i = $index
|
|
839
|
+
) {
|
|
840
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
774
841
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
rounded
|
|
786
|
-
>
|
|
787
|
-
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
788
|
-
</button>
|
|
789
|
-
<app-configurator />
|
|
790
|
-
</div>
|
|
842
|
+
</ul>
|
|
843
|
+
</div>`,
|
|
844
|
+
}]
|
|
845
|
+
}] });
|
|
846
|
+
|
|
847
|
+
class AppSidebar {
|
|
848
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
849
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
|
|
850
|
+
<div class="layout-sidebar">
|
|
851
|
+
<app-menu />
|
|
791
852
|
</div>
|
|
792
|
-
`,
|
|
853
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
|
|
854
|
+
}
|
|
855
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, decorators: [{
|
|
856
|
+
type: Component,
|
|
857
|
+
args: [{
|
|
858
|
+
selector: 'app-sidebar',
|
|
859
|
+
imports: [AppMenu],
|
|
860
|
+
template: `
|
|
861
|
+
<div class="layout-sidebar">
|
|
862
|
+
<app-menu />
|
|
863
|
+
</div>
|
|
864
|
+
`,
|
|
865
|
+
}]
|
|
793
866
|
}] });
|
|
794
867
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
868
|
+
/**
|
|
869
|
+
* Home link showing the active company logo and name.
|
|
870
|
+
* Every skin renders this same markup; sizing and colour come from the bar it sits in.
|
|
871
|
+
*/
|
|
872
|
+
class AppBrandLogo {
|
|
873
|
+
layoutService = inject(LayoutService);
|
|
874
|
+
companyName = this.layoutService.companyName;
|
|
875
|
+
displayLogo = this.layoutService.displayLogo;
|
|
876
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppBrandLogo, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
877
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppBrandLogo, isStandalone: true, selector: "app-brand-logo", host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
|
|
878
|
+
<a class="layout-brand-logo" routerLink="/">
|
|
879
|
+
@if (displayLogo()) {
|
|
880
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
881
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
882
|
+
} @else {
|
|
883
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
<span>{{ companyName() }}</span>
|
|
887
|
+
</a>
|
|
888
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: InlineSvgComponent, selector: "inline-svg", inputs: ["src"] }] });
|
|
889
|
+
}
|
|
890
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppBrandLogo, decorators: [{
|
|
891
|
+
type: Component,
|
|
892
|
+
args: [{
|
|
893
|
+
selector: 'app-brand-logo',
|
|
894
|
+
imports: [RouterModule, InlineSvgComponent],
|
|
895
|
+
host: { style: 'display: contents' },
|
|
896
|
+
template: `
|
|
897
|
+
<a class="layout-brand-logo" routerLink="/">
|
|
898
|
+
@if (displayLogo()) {
|
|
899
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
900
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
901
|
+
} @else {
|
|
902
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
<span>{{ companyName() }}</span>
|
|
906
|
+
</a>
|
|
907
|
+
`,
|
|
908
|
+
}]
|
|
909
|
+
}] });
|
|
910
|
+
|
|
911
|
+
const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
|
|
912
|
+
const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
|
|
913
|
+
const LAYOUT_NOTIFICATION_BELL = new InjectionToken('LAYOUT_NOTIFICATION_BELL');
|
|
914
|
+
const LAYOUT_LANGUAGE_SELECTOR = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR');
|
|
915
|
+
const LAYOUT_LANGUAGE_SELECTOR_PANEL = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR_PANEL');
|
|
916
|
+
const LAYOUT_SEARCHBAR = new InjectionToken('LAYOUT_SEARCHBAR');
|
|
917
|
+
const LAYOUT_IS_RTL = new InjectionToken('LAYOUT_IS_RTL');
|
|
918
|
+
const LAYOUT_SEARCH_ADAPTER = new InjectionToken('LAYOUT_SEARCH_ADAPTER');
|
|
919
|
+
|
|
920
|
+
class AppConfigurator {
|
|
804
921
|
router = inject(Router);
|
|
922
|
+
translateAdapter = inject(TranslateService, { optional: true });
|
|
923
|
+
platformId = inject(PLATFORM_ID);
|
|
805
924
|
layoutService = inject(LayoutService);
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
820
|
-
routerLink = computed(() => {
|
|
821
|
-
const menuItem = this.item();
|
|
822
|
-
return menuItem.routerLink ?? [];
|
|
823
|
-
}, /* @ts-ignore */
|
|
824
|
-
...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
825
|
-
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
826
|
-
routerLinkActiveOptions = computed(() => {
|
|
827
|
-
const link = this.routerLink();
|
|
828
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
829
|
-
return {
|
|
830
|
-
paths: pathsMatch,
|
|
831
|
-
queryParams: 'ignored',
|
|
832
|
-
matrixParams: 'ignored',
|
|
833
|
-
fragment: 'ignored',
|
|
834
|
-
};
|
|
925
|
+
presets = PRESET_NAMES;
|
|
926
|
+
showMenuModeButton = signal(!this.router.url.includes('auth'), /* @ts-ignore */
|
|
927
|
+
...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : /* istanbul ignore next */ []));
|
|
928
|
+
menuModeOptions = computed(() => {
|
|
929
|
+
const options = [
|
|
930
|
+
{ label: this.translate('layout.configurator.menu.mode.static'), value: 'static' },
|
|
931
|
+
{ label: this.translate('layout.configurator.menu.mode.overlay'), value: 'overlay' },
|
|
932
|
+
];
|
|
933
|
+
// Topbar mode renders a horizontal nav, which only the classic skin provides.
|
|
934
|
+
if (this.layoutService.isDesktop() && !this.layoutService.isModernLayout()) {
|
|
935
|
+
options.push({ label: this.translate('layout.configurator.menu.mode.topbar'), value: 'topbar' });
|
|
936
|
+
}
|
|
937
|
+
return options;
|
|
835
938
|
}, /* @ts-ignore */
|
|
836
|
-
...(ngDevMode ? [{ debugName: "
|
|
939
|
+
...(ngDevMode ? [{ debugName: "menuModeOptions" }] : /* istanbul ignore next */ []));
|
|
940
|
+
layoutThemeOptions = computed(() => [
|
|
941
|
+
{ label: this.translate('layout.configurator.layout.theme.classic'), value: 'classic' },
|
|
942
|
+
{ label: this.translate('layout.configurator.layout.theme.modern'), value: 'modern' },
|
|
943
|
+
], /* @ts-ignore */
|
|
944
|
+
...(ngDevMode ? [{ debugName: "layoutThemeOptions" }] : /* istanbul ignore next */ []));
|
|
837
945
|
constructor() {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
else if (value.key !== currentKey &&
|
|
849
|
-
!value.key?.startsWith(currentKey + '-')) {
|
|
850
|
-
this._active.set(false);
|
|
851
|
-
}
|
|
946
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
947
|
+
effect(() => {
|
|
948
|
+
const config = this.layoutService.layoutConfig();
|
|
949
|
+
applyTheme({
|
|
950
|
+
preset: (config.preset ?? 'Aura'),
|
|
951
|
+
primary: config.primary ?? 'violet',
|
|
952
|
+
surface: config.surface ?? null,
|
|
953
|
+
darkTheme: config.darkTheme ?? false,
|
|
954
|
+
});
|
|
852
955
|
});
|
|
853
|
-
});
|
|
854
|
-
// Subscribe to menu reset
|
|
855
|
-
this.layoutService.resetSource$
|
|
856
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
857
|
-
.subscribe(() => this._active.set(false));
|
|
858
|
-
// Subscribe to navigation events
|
|
859
|
-
this.router.events
|
|
860
|
-
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
861
|
-
.subscribe(() => {
|
|
862
|
-
if (this.item().routerLink) {
|
|
863
|
-
this.updateActiveStateFromRoute();
|
|
864
|
-
}
|
|
865
|
-
});
|
|
866
|
-
// Effect to update active state on init when item has routerLink
|
|
867
|
-
effect(() => {
|
|
868
|
-
const menuItem = this.item();
|
|
869
|
-
if (menuItem.routerLink) {
|
|
870
|
-
this.updateActiveStateFromRoute();
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
// Effect to update submenu position when active in topbar mode
|
|
874
|
-
effect(() => {
|
|
875
|
-
if (this.active() && this.parentLink()) {
|
|
876
|
-
Promise.resolve().then(() => this.updateSubmenuPosition());
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
onMouseEnter() {
|
|
881
|
-
if (this.layoutService.isTopbar() && this.item().children) {
|
|
882
|
-
this.updateSubmenuPosition();
|
|
883
956
|
}
|
|
884
957
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
958
|
+
surfaces = Object.entries(SURFACE_PALETTES).map(([name, pal]) => ({
|
|
959
|
+
name,
|
|
960
|
+
palette: pal,
|
|
961
|
+
}));
|
|
962
|
+
selectedPrimaryColor = computed(() => this.layoutService.layoutConfig().primary, /* @ts-ignore */
|
|
963
|
+
...(ngDevMode ? [{ debugName: "selectedPrimaryColor" }] : /* istanbul ignore next */ []));
|
|
964
|
+
selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface, /* @ts-ignore */
|
|
965
|
+
...(ngDevMode ? [{ debugName: "selectedSurfaceColor" }] : /* istanbul ignore next */ []));
|
|
966
|
+
isSurfaceSelected(surfaceName) {
|
|
967
|
+
const sel = this.selectedSurfaceColor();
|
|
968
|
+
if (sel)
|
|
969
|
+
return sel === surfaceName;
|
|
970
|
+
const isDark = this.layoutService.layoutConfig().darkTheme;
|
|
971
|
+
return isDark ? surfaceName === 'zinc' : surfaceName === 'slate';
|
|
898
972
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
queryParams: 'ignored',
|
|
909
|
-
matrixParams: 'ignored',
|
|
910
|
-
fragment: 'ignored',
|
|
911
|
-
})();
|
|
912
|
-
if (active) {
|
|
913
|
-
this.layoutService.onMenuStateChange({
|
|
914
|
-
key: this.key(),
|
|
915
|
-
routeEvent: true,
|
|
916
|
-
});
|
|
973
|
+
selectedPreset = computed(() => this.layoutService.layoutConfig().preset, /* @ts-ignore */
|
|
974
|
+
...(ngDevMode ? [{ debugName: "selectedPreset" }] : /* istanbul ignore next */ []));
|
|
975
|
+
menuMode = computed(() => this.layoutService.layoutConfig().menuMode, /* @ts-ignore */
|
|
976
|
+
...(ngDevMode ? [{ debugName: "menuMode" }] : /* istanbul ignore next */ []));
|
|
977
|
+
layoutTheme = this.layoutService.layoutTheme;
|
|
978
|
+
primaryColors = computed(() => {
|
|
979
|
+
const palettes = [{ name: 'noir', palette: {} }];
|
|
980
|
+
for (const [name, pal] of Object.entries(PRIMARY_PALETTES)) {
|
|
981
|
+
palettes.push({ name, palette: pal });
|
|
917
982
|
}
|
|
983
|
+
return palettes;
|
|
984
|
+
}, /* @ts-ignore */
|
|
985
|
+
...(ngDevMode ? [{ debugName: "primaryColors" }] : /* istanbul ignore next */ []));
|
|
986
|
+
updateColors(event, type, color) {
|
|
987
|
+
if (type === 'primary') {
|
|
988
|
+
this.layoutService.updateLayoutConfig({ primary: color.name });
|
|
989
|
+
}
|
|
990
|
+
else if (type === 'surface') {
|
|
991
|
+
this.layoutService.updateLayoutConfig({ surface: color.name });
|
|
992
|
+
}
|
|
993
|
+
this._applyColorChange(type, color);
|
|
994
|
+
event.stopPropagation();
|
|
918
995
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
996
|
+
_applyColorChange(type, color) {
|
|
997
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
998
|
+
if (type === 'primary') {
|
|
999
|
+
const primary = color.name ?? 'violet';
|
|
1000
|
+
// NOIR_PRIMARY, not a copy of a surface palette: its shades are references to
|
|
1001
|
+
// the active surface ramp, which is what lets applyPrimaryPalette recognise the
|
|
1002
|
+
// neutral and derive the same tokens a preset switch to Nora would.
|
|
1003
|
+
const pal = primary === 'noir' ? NOIR_PRIMARY : PRIMARY_PALETTES[primary];
|
|
1004
|
+
if (pal)
|
|
1005
|
+
applyPrimaryPalette(pal, isDark);
|
|
1006
|
+
}
|
|
1007
|
+
else if (type === 'surface') {
|
|
1008
|
+
const surfacePal = SURFACE_PALETTES[color.name ?? 'slate'];
|
|
1009
|
+
if (surfacePal)
|
|
1010
|
+
applySurfacePalette(surfacePal);
|
|
922
1011
|
}
|
|
923
|
-
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
924
1012
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1013
|
+
onPresetChange(event) {
|
|
1014
|
+
const primary = PRESETS[event]?.signaturePrimary ?? 'violet';
|
|
1015
|
+
this.layoutService.updateLayoutConfig({ preset: event, primary });
|
|
1016
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
1017
|
+
const surface = this.layoutService.layoutConfig().surface ?? null;
|
|
1018
|
+
applyTheme({ preset: event, primary, surface, darkTheme: isDark });
|
|
1019
|
+
}
|
|
1020
|
+
onMenuModeChange(event) {
|
|
1021
|
+
this.layoutService.updateLayoutConfig({ menuMode: event });
|
|
1022
|
+
}
|
|
1023
|
+
onLayoutThemeChange(event) {
|
|
1024
|
+
const menuMode = this.layoutService.layoutConfig().menuMode;
|
|
1025
|
+
this.layoutService.updateLayoutConfig({
|
|
1026
|
+
layoutTheme: event,
|
|
1027
|
+
// Modern skin has no horizontal nav — fall back to the static sidebar.
|
|
1028
|
+
menuMode: event === 'modern' && menuMode === 'topbar' ? 'static' : menuMode,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
translate(key) {
|
|
1032
|
+
return this.translateAdapter?.translate(key) ?? key;
|
|
1033
|
+
}
|
|
1034
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1035
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay); inset-inline-end: 0;", classAttribute: "hidden absolute top-[3.25rem] w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-xl" }, ngImport: i0, template: `
|
|
1036
|
+
<div class="flex flex-col gap-4">
|
|
1037
|
+
<div>
|
|
1038
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1039
|
+
'layout.configurator.primary' | translate
|
|
1040
|
+
}}</span>
|
|
1041
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1042
|
+
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
1043
|
+
<button
|
|
1044
|
+
type="button"
|
|
1045
|
+
[title]="primaryColor.name"
|
|
1046
|
+
(click)="updateColors($event, 'primary', primaryColor)"
|
|
1047
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
1048
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1049
|
+
[style.background-color]="
|
|
1050
|
+
primaryColor?.name === 'noir'
|
|
1051
|
+
? 'var(--f-text-color)'
|
|
1052
|
+
: primaryColor?.palette?.['500']
|
|
1053
|
+
"
|
|
1054
|
+
></button>
|
|
941
1055
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1056
|
+
</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
<div>
|
|
1059
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1060
|
+
'layout.configurator.surface' | translate
|
|
1061
|
+
}}</span>
|
|
1062
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1063
|
+
@for (surface of surfaces; track surface.name) {
|
|
1064
|
+
<button
|
|
1065
|
+
type="button"
|
|
1066
|
+
[title]="surface.name"
|
|
1067
|
+
(click)="updateColors($event, 'surface', surface)"
|
|
1068
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
1069
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1070
|
+
[style.background-color]="
|
|
1071
|
+
surface?.name === 'noir'
|
|
1072
|
+
? 'var(--f-text-color)'
|
|
1073
|
+
: surface?.palette?.['500']
|
|
1074
|
+
"
|
|
1075
|
+
></button>
|
|
951
1076
|
}
|
|
952
|
-
</
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
<
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
[
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1077
|
+
</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
<div class="flex flex-col gap-2">
|
|
1080
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1081
|
+
'layout.configurator.presets' | translate
|
|
1082
|
+
}}</span>
|
|
1083
|
+
<f-selectbutton
|
|
1084
|
+
[options]="presets"
|
|
1085
|
+
[ngModel]="selectedPreset()"
|
|
1086
|
+
(ngModelChange)="onPresetChange($event)"
|
|
1087
|
+
[allowEmpty]="false"
|
|
1088
|
+
size="small"
|
|
1089
|
+
/>
|
|
1090
|
+
</div>
|
|
1091
|
+
@if (showMenuModeButton()) {
|
|
1092
|
+
<div class="flex flex-col gap-2">
|
|
1093
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1094
|
+
'layout.configurator.layout.theme' | translate
|
|
971
1095
|
}}</span>
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
>
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
</ul>
|
|
1096
|
+
<f-selectbutton
|
|
1097
|
+
[ngModel]="layoutTheme()"
|
|
1098
|
+
(ngModelChange)="onLayoutThemeChange($event)"
|
|
1099
|
+
[options]="layoutThemeOptions()"
|
|
1100
|
+
[allowEmpty]="false"
|
|
1101
|
+
size="small"
|
|
1102
|
+
/>
|
|
1103
|
+
</div>
|
|
1104
|
+
<div class="flex flex-col gap-2">
|
|
1105
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1106
|
+
'layout.configurator.menu.mode' | translate
|
|
1107
|
+
}}</span>
|
|
1108
|
+
<f-selectbutton
|
|
1109
|
+
[ngModel]="menuMode()"
|
|
1110
|
+
(ngModelChange)="onMenuModeChange($event)"
|
|
1111
|
+
[options]="menuModeOptions()"
|
|
1112
|
+
[allowEmpty]="false"
|
|
1113
|
+
size="small"
|
|
1114
|
+
/>
|
|
1115
|
+
</div>
|
|
993
1116
|
}
|
|
994
|
-
</
|
|
995
|
-
`, isInline: true,
|
|
1117
|
+
</div>
|
|
1118
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.SelectButtonComponent, selector: "f-selectbutton", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "multiple", "disabled", "invalid", "allowEmpty", "size", "styleClass", "style"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
996
1119
|
}
|
|
997
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
1120
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, decorators: [{
|
|
998
1121
|
type: Component,
|
|
999
|
-
args: [{
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
>
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
1023
|
-
}
|
|
1122
|
+
args: [{
|
|
1123
|
+
selector: 'app-configurator',
|
|
1124
|
+
imports: [UiModule, FormsModule, TranslatePipe],
|
|
1125
|
+
template: `
|
|
1126
|
+
<div class="flex flex-col gap-4">
|
|
1127
|
+
<div>
|
|
1128
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1129
|
+
'layout.configurator.primary' | translate
|
|
1130
|
+
}}</span>
|
|
1131
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1132
|
+
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
1133
|
+
<button
|
|
1134
|
+
type="button"
|
|
1135
|
+
[title]="primaryColor.name"
|
|
1136
|
+
(click)="updateColors($event, 'primary', primaryColor)"
|
|
1137
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
1138
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1139
|
+
[style.background-color]="
|
|
1140
|
+
primaryColor?.name === 'noir'
|
|
1141
|
+
? 'var(--f-text-color)'
|
|
1142
|
+
: primaryColor?.palette?.['500']
|
|
1143
|
+
"
|
|
1144
|
+
></button>
|
|
1024
1145
|
}
|
|
1025
|
-
</
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
<
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
[
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1146
|
+
</div>
|
|
1147
|
+
</div>
|
|
1148
|
+
<div>
|
|
1149
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1150
|
+
'layout.configurator.surface' | translate
|
|
1151
|
+
}}</span>
|
|
1152
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1153
|
+
@for (surface of surfaces; track surface.name) {
|
|
1154
|
+
<button
|
|
1155
|
+
type="button"
|
|
1156
|
+
[title]="surface.name"
|
|
1157
|
+
(click)="updateColors($event, 'surface', surface)"
|
|
1158
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
1159
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1160
|
+
[style.background-color]="
|
|
1161
|
+
surface?.name === 'noir'
|
|
1162
|
+
? 'var(--f-text-color)'
|
|
1163
|
+
: surface?.palette?.['500']
|
|
1164
|
+
"
|
|
1165
|
+
></button>
|
|
1041
1166
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1167
|
+
</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div class="flex flex-col gap-2">
|
|
1170
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1171
|
+
'layout.configurator.presets' | translate
|
|
1172
|
+
}}</span>
|
|
1173
|
+
<f-selectbutton
|
|
1174
|
+
[options]="presets"
|
|
1175
|
+
[ngModel]="selectedPreset()"
|
|
1176
|
+
(ngModelChange)="onPresetChange($event)"
|
|
1177
|
+
[allowEmpty]="false"
|
|
1178
|
+
size="small"
|
|
1179
|
+
/>
|
|
1180
|
+
</div>
|
|
1181
|
+
@if (showMenuModeButton()) {
|
|
1182
|
+
<div class="flex flex-col gap-2">
|
|
1183
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1184
|
+
'layout.configurator.layout.theme' | translate
|
|
1044
1185
|
}}</span>
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
>
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
</ul>
|
|
1066
|
-
}
|
|
1067
|
-
</ng-container>
|
|
1068
|
-
`, host: {
|
|
1069
|
-
'[class.active-menuitem]': 'active()',
|
|
1070
|
-
}, 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"] }]
|
|
1071
|
-
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }], parentLink: [{ type: i0.ViewChild, args: ['parentLink', { isSignal: true }] }] } });
|
|
1072
|
-
|
|
1073
|
-
/**
|
|
1074
|
-
* Main menu component that displays filtered menu items.
|
|
1075
|
-
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
1076
|
-
* The filtering happens internally in LayoutService via computed signal.
|
|
1077
|
-
*/
|
|
1078
|
-
class AppMenu {
|
|
1079
|
-
layoutService = inject(LayoutService);
|
|
1080
|
-
/**
|
|
1081
|
-
* Filtered menu items from layout service.
|
|
1082
|
-
* This signal is computed in LayoutService and automatically updates when:
|
|
1083
|
-
* - Raw menu changes (setMenu called)
|
|
1084
|
-
* - Permission state changes (user permissions updated)
|
|
1085
|
-
*/
|
|
1086
|
-
menuItems = this.layoutService.menu;
|
|
1087
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1088
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
1089
|
-
<ul>
|
|
1090
|
-
@for (
|
|
1091
|
-
item of menuItems();
|
|
1092
|
-
track 'menu' + item.id + '' + i;
|
|
1093
|
-
let i = $index
|
|
1094
|
-
) {
|
|
1095
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1096
|
-
}
|
|
1097
|
-
</ul>
|
|
1098
|
-
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
1099
|
-
}
|
|
1100
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, decorators: [{
|
|
1101
|
-
type: Component,
|
|
1102
|
-
args: [{
|
|
1103
|
-
selector: 'app-menu',
|
|
1104
|
-
imports: [AppMenuitem, RouterModule],
|
|
1105
|
-
template: `<div class="layout-menu">
|
|
1106
|
-
<ul>
|
|
1107
|
-
@for (
|
|
1108
|
-
item of menuItems();
|
|
1109
|
-
track 'menu' + item.id + '' + i;
|
|
1110
|
-
let i = $index
|
|
1111
|
-
) {
|
|
1112
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1186
|
+
<f-selectbutton
|
|
1187
|
+
[ngModel]="layoutTheme()"
|
|
1188
|
+
(ngModelChange)="onLayoutThemeChange($event)"
|
|
1189
|
+
[options]="layoutThemeOptions()"
|
|
1190
|
+
[allowEmpty]="false"
|
|
1191
|
+
size="small"
|
|
1192
|
+
/>
|
|
1193
|
+
</div>
|
|
1194
|
+
<div class="flex flex-col gap-2">
|
|
1195
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1196
|
+
'layout.configurator.menu.mode' | translate
|
|
1197
|
+
}}</span>
|
|
1198
|
+
<f-selectbutton
|
|
1199
|
+
[ngModel]="menuMode()"
|
|
1200
|
+
(ngModelChange)="onMenuModeChange($event)"
|
|
1201
|
+
[options]="menuModeOptions()"
|
|
1202
|
+
[allowEmpty]="false"
|
|
1203
|
+
size="small"
|
|
1204
|
+
/>
|
|
1205
|
+
</div>
|
|
1113
1206
|
}
|
|
1114
|
-
</
|
|
1115
|
-
|
|
1207
|
+
</div>
|
|
1208
|
+
`,
|
|
1209
|
+
host: {
|
|
1210
|
+
class: 'hidden absolute top-[3.25rem] w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-xl',
|
|
1211
|
+
style: 'background-color: var(--surface-overlay); inset-inline-end: 0;',
|
|
1212
|
+
},
|
|
1116
1213
|
}]
|
|
1117
|
-
}] });
|
|
1214
|
+
}], ctorParameters: () => [] });
|
|
1118
1215
|
|
|
1119
1216
|
/**
|
|
1120
1217
|
* View Transitions API type definitions
|
|
@@ -1137,7 +1234,7 @@ class AppCompanyBranchSelector {
|
|
|
1137
1234
|
isActive = this._isActive.asReadonly();
|
|
1138
1235
|
constructor() {
|
|
1139
1236
|
fromEvent(this.document, 'click')
|
|
1140
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1237
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1141
1238
|
.subscribe((event) => {
|
|
1142
1239
|
const target = event.target;
|
|
1143
1240
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1355,7 +1452,7 @@ class AppCompanyBranchSelector {
|
|
|
1355
1452
|
</div>
|
|
1356
1453
|
</div>
|
|
1357
1454
|
</div>
|
|
1358
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { 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: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1455
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1359
1456
|
}
|
|
1360
1457
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
|
|
1361
1458
|
type: Component,
|
|
@@ -1464,7 +1561,7 @@ class AppLauncher {
|
|
|
1464
1561
|
isActive = this._isActive.asReadonly();
|
|
1465
1562
|
constructor() {
|
|
1466
1563
|
fromEvent(this.document, 'click')
|
|
1467
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1564
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1468
1565
|
.subscribe((event) => {
|
|
1469
1566
|
const target = event.target;
|
|
1470
1567
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1617,7 +1714,7 @@ class AppProfile {
|
|
|
1617
1714
|
isActive = this._isActive.asReadonly();
|
|
1618
1715
|
constructor() {
|
|
1619
1716
|
fromEvent(this.document, 'click')
|
|
1620
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1717
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1621
1718
|
.subscribe((event) => {
|
|
1622
1719
|
const target = event.target;
|
|
1623
1720
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1757,7 +1854,7 @@ class AppProfile {
|
|
|
1757
1854
|
</div>
|
|
1758
1855
|
</div>
|
|
1759
1856
|
</div>
|
|
1760
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2
|
|
1857
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i3.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1761
1858
|
}
|
|
1762
1859
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppProfile, decorators: [{
|
|
1763
1860
|
type: Component,
|
|
@@ -2086,7 +2183,7 @@ class AppSearchbar {
|
|
|
2086
2183
|
}
|
|
2087
2184
|
}
|
|
2088
2185
|
</div>
|
|
2089
|
-
`, isInline: true, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.IconFieldComponent, selector: "f-iconfield", inputs: ["iconPosition", "styleClass"] }, { kind: "component", type: i1.InputIconComponent, selector: "f-inputicon", inputs: ["icon", "position", "styleClass", "style"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { 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: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2186
|
+
`, isInline: true, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:var(--layout-shell-topbar-bottom, 4rem);left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.IconFieldComponent, selector: "f-iconfield", inputs: ["iconPosition", "styleClass"] }, { kind: "component", type: i1.InputIconComponent, selector: "f-inputicon", inputs: ["icon", "position", "styleClass", "style"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2090
2187
|
}
|
|
2091
2188
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSearchbar, decorators: [{
|
|
2092
2189
|
type: Component,
|
|
@@ -2194,10 +2291,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2194
2291
|
}
|
|
2195
2292
|
}
|
|
2196
2293
|
</div>
|
|
2197
|
-
`, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"] }]
|
|
2294
|
+
`, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:var(--layout-shell-topbar-bottom, 4rem);left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"] }]
|
|
2198
2295
|
}], ctorParameters: () => [], propDecorators: { searchWrapper: [{ type: i0.ViewChild, args: ['searchWrapper', { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
2199
2296
|
|
|
2200
|
-
|
|
2297
|
+
/**
|
|
2298
|
+
* Trailing action rail of the topbar — search, theme controls and the widget menu.
|
|
2299
|
+
* Skins own the bar around it; the rail itself is identical in every skin, and
|
|
2300
|
+
* `display: contents` on the host keeps it a direct flex child of the bar.
|
|
2301
|
+
*/
|
|
2302
|
+
class AppTopbarActions {
|
|
2201
2303
|
appConfig = inject(APP_CONFIG);
|
|
2202
2304
|
document = inject(DOCUMENT$1);
|
|
2203
2305
|
destroyRef = inject(DestroyRef);
|
|
@@ -2211,111 +2313,34 @@ class AppTopbar {
|
|
|
2211
2313
|
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
2212
2314
|
optional: true,
|
|
2213
2315
|
});
|
|
2214
|
-
hasSearchAdapter = computed(() => !!this.searchAdapter, /* @ts-ignore */
|
|
2215
|
-
...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : /* istanbul ignore next */ []));
|
|
2216
2316
|
configContainer = viewChild('configContainer', /* @ts-ignore */
|
|
2217
2317
|
...(ngDevMode ? [{ debugName: "configContainer" }] : /* istanbul ignore next */ []));
|
|
2218
|
-
|
|
2219
|
-
...(ngDevMode ? [{ debugName: "
|
|
2220
|
-
|
|
2221
|
-
|
|
2318
|
+
_configPanelOpen = signal(false, /* @ts-ignore */
|
|
2319
|
+
...(ngDevMode ? [{ debugName: "_configPanelOpen" }] : /* istanbul ignore next */ []));
|
|
2320
|
+
configPanelOpen = this._configPanelOpen.asReadonly();
|
|
2321
|
+
hasSearchAdapter = computed(() => !!this.searchAdapter, /* @ts-ignore */
|
|
2322
|
+
...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : /* istanbul ignore next */ []));
|
|
2323
|
+
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
2222
2324
|
constructor() {
|
|
2223
2325
|
fromEvent(this.document, 'click')
|
|
2224
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.
|
|
2326
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this._configPanelOpen()))
|
|
2225
2327
|
.subscribe((event) => {
|
|
2226
|
-
this.
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
const nav = this.navContainer();
|
|
2230
|
-
if (nav && this.layoutService.isTopbar()) {
|
|
2231
|
-
fromEvent(nav.nativeElement, 'scroll')
|
|
2232
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2233
|
-
.subscribe(() => this.recalculateHoveredSubmenu(nav.nativeElement));
|
|
2328
|
+
const configEl = this.configContainer()?.nativeElement;
|
|
2329
|
+
if (configEl && !configEl.contains(event.target)) {
|
|
2330
|
+
this._configPanelOpen.set(false);
|
|
2234
2331
|
}
|
|
2235
2332
|
});
|
|
2236
2333
|
}
|
|
2237
|
-
companyName = this.layoutService.companyName;
|
|
2238
|
-
displayLogo = this.layoutService.displayLogo;
|
|
2239
|
-
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
2240
2334
|
toggleDarkMode() {
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
}
|
|
2244
|
-
togglePanel(panel) {
|
|
2245
|
-
this.activePanel.update((current) => (current === panel ? null : panel));
|
|
2246
|
-
}
|
|
2247
|
-
handleOutsideClick(event) {
|
|
2248
|
-
const target = event.target;
|
|
2249
|
-
const configEl = this.configContainer()?.nativeElement;
|
|
2250
|
-
if (configEl && !configEl.contains(target)) {
|
|
2251
|
-
this.activePanel.set(null);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
recalculateHoveredSubmenu(navEl) {
|
|
2255
|
-
const hoveredItems = navEl.querySelectorAll('li:hover');
|
|
2256
|
-
hoveredItems.forEach((li) => {
|
|
2257
|
-
const link = li.querySelector(':scope > a');
|
|
2258
|
-
if (!link)
|
|
2259
|
-
return;
|
|
2260
|
-
const rect = link.getBoundingClientRect();
|
|
2261
|
-
const top = rect.bottom + 12;
|
|
2262
|
-
const left = rect.left - 12;
|
|
2263
|
-
li.style.setProperty('--submenu-left', `${left}px`);
|
|
2264
|
-
li.style.setProperty('--submenu-top', `${top}px`);
|
|
2335
|
+
this.layoutService.updateLayoutConfig({
|
|
2336
|
+
darkTheme: !this.layoutService.layoutConfig().darkTheme,
|
|
2265
2337
|
});
|
|
2266
2338
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
<div
|
|
2270
|
-
class="layout-topbar-logo-container"
|
|
2271
|
-
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2272
|
-
>
|
|
2273
|
-
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2274
|
-
<button
|
|
2275
|
-
class="layout-menu-button layout-topbar-action"
|
|
2276
|
-
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2277
|
-
(click)="layoutService.onMenuToggle()"
|
|
2278
|
-
>
|
|
2279
|
-
<ui-icon name="bars"></ui-icon>
|
|
2280
|
-
</button>
|
|
2281
|
-
}
|
|
2282
|
-
<a class="layout-topbar-logo" routerLink="/">
|
|
2283
|
-
@if (displayLogo()) {
|
|
2284
|
-
@if (layoutService.isDisplayLogoSvg()) {
|
|
2285
|
-
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
2286
|
-
} @else {
|
|
2287
|
-
<img
|
|
2288
|
-
[src]="displayLogo()"
|
|
2289
|
-
[alt]="companyName()"
|
|
2290
|
-
class="logo-image"
|
|
2291
|
-
/>
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
<span class="hidden sm:inline">{{ companyName() }}</span>
|
|
2295
|
-
</a>
|
|
2296
|
-
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2297
|
-
<button
|
|
2298
|
-
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2299
|
-
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2300
|
-
(click)="layoutService.onMenuToggle()"
|
|
2301
|
-
>
|
|
2302
|
-
<ui-icon name="bars"></ui-icon>
|
|
2303
|
-
</button>
|
|
2304
|
-
}
|
|
2305
|
-
</div>
|
|
2306
|
-
|
|
2307
|
-
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2308
|
-
<div
|
|
2309
|
-
#navContainer
|
|
2310
|
-
class="layout-topbar-nav-horizontal"
|
|
2311
|
-
[class.layout-topbar-nav-hidden]="
|
|
2312
|
-
!layoutService.layoutState().topbarMenuVisible
|
|
2313
|
-
"
|
|
2314
|
-
>
|
|
2315
|
-
<app-menu />
|
|
2316
|
-
</div>
|
|
2339
|
+
toggleConfigPanel() {
|
|
2340
|
+
this._configPanelOpen.update((open) => !open);
|
|
2317
2341
|
}
|
|
2318
|
-
|
|
2342
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbarActions, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2343
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppTopbarActions, isStandalone: true, selector: "app-topbar-actions", host: { styleAttribute: "display: contents" }, viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2319
2344
|
<div class="layout-topbar-actions">
|
|
2320
2345
|
@if (hasSearchAdapter()) {
|
|
2321
2346
|
<app-searchbar />
|
|
@@ -2327,12 +2352,15 @@ class AppTopbar {
|
|
|
2327
2352
|
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
2328
2353
|
(click)="toggleDarkMode()"
|
|
2329
2354
|
>
|
|
2330
|
-
<ui-icon
|
|
2355
|
+
<ui-icon
|
|
2356
|
+
[name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"
|
|
2357
|
+
></ui-icon>
|
|
2331
2358
|
</button>
|
|
2332
2359
|
<div class="relative" #configContainer>
|
|
2333
2360
|
<button
|
|
2361
|
+
type="button"
|
|
2334
2362
|
class="layout-topbar-action"
|
|
2335
|
-
[class.layout-topbar-action-highlight]="
|
|
2363
|
+
[class.layout-topbar-action-highlight]="configPanelOpen()"
|
|
2336
2364
|
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2337
2365
|
fStyleClass="@next"
|
|
2338
2366
|
enterFromClass="hidden"
|
|
@@ -2340,7 +2368,7 @@ class AppTopbar {
|
|
|
2340
2368
|
leaveToClass="hidden"
|
|
2341
2369
|
leaveActiveClass="animate-fadeout"
|
|
2342
2370
|
[hideOnOutsideClick]="true"
|
|
2343
|
-
(click)="
|
|
2371
|
+
(click)="toggleConfigPanel()"
|
|
2344
2372
|
>
|
|
2345
2373
|
<ui-icon name="palette"></ui-icon>
|
|
2346
2374
|
</button>
|
|
@@ -2349,6 +2377,7 @@ class AppTopbar {
|
|
|
2349
2377
|
</div>
|
|
2350
2378
|
|
|
2351
2379
|
<button
|
|
2380
|
+
type="button"
|
|
2352
2381
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
2353
2382
|
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2354
2383
|
fStyleClass="@next"
|
|
@@ -2379,73 +2408,24 @@ class AppTopbar {
|
|
|
2379
2408
|
</div>
|
|
2380
2409
|
</div>
|
|
2381
2410
|
</div>
|
|
2382
|
-
|
|
2411
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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" }, { kind: "component", type: AppSearchbar, selector: "app-searchbar" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2383
2412
|
}
|
|
2384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
2413
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbarActions, decorators: [{
|
|
2385
2414
|
type: Component,
|
|
2386
2415
|
args: [{
|
|
2387
|
-
selector: 'app-topbar',
|
|
2388
|
-
imports: [
|
|
2416
|
+
selector: 'app-topbar-actions',
|
|
2417
|
+
imports: [
|
|
2418
|
+
UiModule,
|
|
2389
2419
|
NgComponentOutlet,
|
|
2390
2420
|
TranslatePipe,
|
|
2391
|
-
InlineSvgComponent,
|
|
2392
2421
|
AppConfigurator,
|
|
2393
|
-
AppMenu,
|
|
2394
2422
|
AppProfile,
|
|
2395
2423
|
AppCompanyBranchSelector,
|
|
2396
2424
|
AppLauncher,
|
|
2397
|
-
AppSearchbar,
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2402
|
-
>
|
|
2403
|
-
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2404
|
-
<button
|
|
2405
|
-
class="layout-menu-button layout-topbar-action"
|
|
2406
|
-
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2407
|
-
(click)="layoutService.onMenuToggle()"
|
|
2408
|
-
>
|
|
2409
|
-
<ui-icon name="bars"></ui-icon>
|
|
2410
|
-
</button>
|
|
2411
|
-
}
|
|
2412
|
-
<a class="layout-topbar-logo" routerLink="/">
|
|
2413
|
-
@if (displayLogo()) {
|
|
2414
|
-
@if (layoutService.isDisplayLogoSvg()) {
|
|
2415
|
-
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
2416
|
-
} @else {
|
|
2417
|
-
<img
|
|
2418
|
-
[src]="displayLogo()"
|
|
2419
|
-
[alt]="companyName()"
|
|
2420
|
-
class="logo-image"
|
|
2421
|
-
/>
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
<span class="hidden sm:inline">{{ companyName() }}</span>
|
|
2425
|
-
</a>
|
|
2426
|
-
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2427
|
-
<button
|
|
2428
|
-
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2429
|
-
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2430
|
-
(click)="layoutService.onMenuToggle()"
|
|
2431
|
-
>
|
|
2432
|
-
<ui-icon name="bars"></ui-icon>
|
|
2433
|
-
</button>
|
|
2434
|
-
}
|
|
2435
|
-
</div>
|
|
2436
|
-
|
|
2437
|
-
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2438
|
-
<div
|
|
2439
|
-
#navContainer
|
|
2440
|
-
class="layout-topbar-nav-horizontal"
|
|
2441
|
-
[class.layout-topbar-nav-hidden]="
|
|
2442
|
-
!layoutService.layoutState().topbarMenuVisible
|
|
2443
|
-
"
|
|
2444
|
-
>
|
|
2445
|
-
<app-menu />
|
|
2446
|
-
</div>
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2425
|
+
AppSearchbar,
|
|
2426
|
+
],
|
|
2427
|
+
host: { style: 'display: contents' },
|
|
2428
|
+
template: `
|
|
2449
2429
|
<div class="layout-topbar-actions">
|
|
2450
2430
|
@if (hasSearchAdapter()) {
|
|
2451
2431
|
<app-searchbar />
|
|
@@ -2457,12 +2437,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2457
2437
|
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
2458
2438
|
(click)="toggleDarkMode()"
|
|
2459
2439
|
>
|
|
2460
|
-
<ui-icon
|
|
2440
|
+
<ui-icon
|
|
2441
|
+
[name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"
|
|
2442
|
+
></ui-icon>
|
|
2461
2443
|
</button>
|
|
2462
2444
|
<div class="relative" #configContainer>
|
|
2463
2445
|
<button
|
|
2446
|
+
type="button"
|
|
2464
2447
|
class="layout-topbar-action"
|
|
2465
|
-
[class.layout-topbar-action-highlight]="
|
|
2448
|
+
[class.layout-topbar-action-highlight]="configPanelOpen()"
|
|
2466
2449
|
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2467
2450
|
fStyleClass="@next"
|
|
2468
2451
|
enterFromClass="hidden"
|
|
@@ -2470,7 +2453,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2470
2453
|
leaveToClass="hidden"
|
|
2471
2454
|
leaveActiveClass="animate-fadeout"
|
|
2472
2455
|
[hideOnOutsideClick]="true"
|
|
2473
|
-
(click)="
|
|
2456
|
+
(click)="toggleConfigPanel()"
|
|
2474
2457
|
>
|
|
2475
2458
|
<ui-icon name="palette"></ui-icon>
|
|
2476
2459
|
</button>
|
|
@@ -2479,6 +2462,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2479
2462
|
</div>
|
|
2480
2463
|
|
|
2481
2464
|
<button
|
|
2465
|
+
type="button"
|
|
2482
2466
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
2483
2467
|
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2484
2468
|
fStyleClass="@next"
|
|
@@ -2509,109 +2493,149 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2509
2493
|
</div>
|
|
2510
2494
|
</div>
|
|
2511
2495
|
</div>
|
|
2512
|
-
</div>`,
|
|
2513
|
-
}]
|
|
2514
|
-
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }], navContainer: [{ type: i0.ViewChild, args: ['navContainer', { isSignal: true }] }] } });
|
|
2515
|
-
|
|
2516
|
-
class AppSidebar {
|
|
2517
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2518
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
|
|
2519
|
-
<div class="layout-sidebar">
|
|
2520
|
-
<app-menu />
|
|
2521
|
-
</div>
|
|
2522
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
|
|
2523
|
-
}
|
|
2524
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, decorators: [{
|
|
2525
|
-
type: Component,
|
|
2526
|
-
args: [{
|
|
2527
|
-
selector: 'app-sidebar',
|
|
2528
|
-
imports: [AppMenu],
|
|
2529
|
-
template: `
|
|
2530
|
-
<div class="layout-sidebar">
|
|
2531
|
-
<app-menu />
|
|
2532
|
-
</div>
|
|
2533
2496
|
`,
|
|
2534
2497
|
}]
|
|
2535
|
-
}] });
|
|
2498
|
+
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }] } });
|
|
2536
2499
|
|
|
2537
|
-
class
|
|
2538
|
-
destroyRef = inject(DestroyRef);
|
|
2539
|
-
document = inject(DOCUMENT);
|
|
2500
|
+
class AppTopbar {
|
|
2540
2501
|
layoutService = inject(LayoutService);
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
menuOutsideClickListener = null;
|
|
2502
|
+
navContainer = viewChild('navContainer', /* @ts-ignore */
|
|
2503
|
+
...(ngDevMode ? [{ debugName: "navContainer" }] : /* istanbul ignore next */ []));
|
|
2544
2504
|
constructor() {
|
|
2545
|
-
|
|
2546
|
-
.
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
this.hideMenu();
|
|
2552
|
-
}
|
|
2553
|
-
});
|
|
2554
|
-
}
|
|
2555
|
-
if (this.layoutService.layoutState().staticMenuMobileActive) {
|
|
2556
|
-
this.blockBodyScroll();
|
|
2557
|
-
}
|
|
2505
|
+
effect((onCleanup) => {
|
|
2506
|
+
const nav = this.navContainer();
|
|
2507
|
+
if (!nav)
|
|
2508
|
+
return;
|
|
2509
|
+
const subscription = fromEvent(nav.nativeElement, 'scroll').subscribe(() => this.recalculateHoveredSubmenu(nav.nativeElement));
|
|
2510
|
+
onCleanup(() => subscription.unsubscribe());
|
|
2558
2511
|
});
|
|
2559
|
-
this.router.events
|
|
2560
|
-
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
2561
|
-
.subscribe(() => this.hideMenu());
|
|
2562
|
-
}
|
|
2563
|
-
isOutsideClicked(event) {
|
|
2564
|
-
const sidebarEl = this.document.querySelector('.layout-sidebar');
|
|
2565
|
-
const topbarEl = this.document.querySelector('.layout-menu-button');
|
|
2566
|
-
const topbarNavEl = this.document.querySelector('.layout-topbar-nav');
|
|
2567
|
-
const eventTarget = event.target;
|
|
2568
|
-
return !(sidebarEl?.isSameNode(eventTarget) ||
|
|
2569
|
-
sidebarEl?.contains(eventTarget) ||
|
|
2570
|
-
topbarEl?.isSameNode(eventTarget) ||
|
|
2571
|
-
topbarEl?.contains(eventTarget) ||
|
|
2572
|
-
topbarNavEl?.isSameNode(eventTarget) ||
|
|
2573
|
-
topbarNavEl?.contains(eventTarget));
|
|
2574
2512
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2513
|
+
/**
|
|
2514
|
+
* Horizontal-nav submenus are `position: fixed`, so scrolling the nav strip leaves
|
|
2515
|
+
* an open submenu behind — re-anchor it to the item it belongs to.
|
|
2516
|
+
*/
|
|
2517
|
+
recalculateHoveredSubmenu(navEl) {
|
|
2518
|
+
navEl.querySelectorAll('li:hover').forEach((li) => {
|
|
2519
|
+
const link = li.querySelector(':scope > a');
|
|
2520
|
+
if (!link)
|
|
2521
|
+
return;
|
|
2522
|
+
const rect = link.getBoundingClientRect();
|
|
2523
|
+
li.style.setProperty('--submenu-left', `${rect.left - 12}px`);
|
|
2524
|
+
li.style.setProperty('--submenu-top', `${rect.bottom + 12}px`);
|
|
2580
2525
|
});
|
|
2581
|
-
if (this.menuOutsideClickListener) {
|
|
2582
|
-
this.menuOutsideClickListener();
|
|
2583
|
-
this.menuOutsideClickListener = null;
|
|
2584
|
-
}
|
|
2585
|
-
this.unblockBodyScroll();
|
|
2586
2526
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2527
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2528
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "navContainer", first: true, predicate: ["navContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `<div class="layout-topbar">
|
|
2529
|
+
<div
|
|
2530
|
+
class="layout-topbar-logo-container"
|
|
2531
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2532
|
+
>
|
|
2533
|
+
@if (layoutService.isMobile()) {
|
|
2534
|
+
<button
|
|
2535
|
+
type="button"
|
|
2536
|
+
class="layout-menu-button layout-topbar-action"
|
|
2537
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2538
|
+
(click)="layoutService.onMenuToggle()"
|
|
2539
|
+
>
|
|
2540
|
+
<ui-icon name="bars"></ui-icon>
|
|
2541
|
+
</button>
|
|
2542
|
+
}
|
|
2543
|
+
<app-brand-logo />
|
|
2544
|
+
@if (layoutService.isDesktop()) {
|
|
2545
|
+
<button
|
|
2546
|
+
type="button"
|
|
2547
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2548
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2549
|
+
(click)="layoutService.onMenuToggle()"
|
|
2550
|
+
>
|
|
2551
|
+
<ui-icon name="bars"></ui-icon>
|
|
2552
|
+
</button>
|
|
2553
|
+
}
|
|
2554
|
+
</div>
|
|
2555
|
+
|
|
2556
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2557
|
+
<div
|
|
2558
|
+
#navContainer
|
|
2559
|
+
class="layout-topbar-nav-horizontal"
|
|
2560
|
+
[class.layout-topbar-nav-hidden]="
|
|
2561
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2562
|
+
"
|
|
2563
|
+
>
|
|
2564
|
+
<app-menu />
|
|
2565
|
+
</div>
|
|
2589
2566
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2567
|
+
|
|
2568
|
+
<app-topbar-actions />
|
|
2569
|
+
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppBrandLogo, selector: "app-brand-logo" }, { kind: "component", type: AppMenu, selector: "app-menu" }, { kind: "component", type: AppTopbarActions, selector: "app-topbar-actions" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2570
|
+
}
|
|
2571
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, decorators: [{
|
|
2572
|
+
type: Component,
|
|
2573
|
+
args: [{
|
|
2574
|
+
selector: 'app-topbar',
|
|
2575
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppMenu, AppTopbarActions],
|
|
2576
|
+
template: `<div class="layout-topbar">
|
|
2577
|
+
<div
|
|
2578
|
+
class="layout-topbar-logo-container"
|
|
2579
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2580
|
+
>
|
|
2581
|
+
@if (layoutService.isMobile()) {
|
|
2582
|
+
<button
|
|
2583
|
+
type="button"
|
|
2584
|
+
class="layout-menu-button layout-topbar-action"
|
|
2585
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2586
|
+
(click)="layoutService.onMenuToggle()"
|
|
2587
|
+
>
|
|
2588
|
+
<ui-icon name="bars"></ui-icon>
|
|
2589
|
+
</button>
|
|
2590
|
+
}
|
|
2591
|
+
<app-brand-logo />
|
|
2592
|
+
@if (layoutService.isDesktop()) {
|
|
2593
|
+
<button
|
|
2594
|
+
type="button"
|
|
2595
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2596
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2597
|
+
(click)="layoutService.onMenuToggle()"
|
|
2598
|
+
>
|
|
2599
|
+
<ui-icon name="bars"></ui-icon>
|
|
2600
|
+
</button>
|
|
2601
|
+
}
|
|
2602
|
+
</div>
|
|
2603
|
+
|
|
2604
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2605
|
+
<div
|
|
2606
|
+
#navContainer
|
|
2607
|
+
class="layout-topbar-nav-horizontal"
|
|
2608
|
+
[class.layout-topbar-nav-hidden]="
|
|
2609
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2610
|
+
"
|
|
2611
|
+
>
|
|
2612
|
+
<app-menu />
|
|
2613
|
+
</div>
|
|
2592
2614
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2615
|
+
|
|
2616
|
+
<app-topbar-actions />
|
|
2617
|
+
</div>`,
|
|
2618
|
+
}]
|
|
2619
|
+
}], ctorParameters: () => [], propDecorators: { navContainer: [{ type: i0.ViewChild, args: ['navContainer', { isSignal: true }] }] } });
|
|
2620
|
+
|
|
2621
|
+
class AppClassicLayout extends LayoutShellBase {
|
|
2595
2622
|
isStatic = computed(() => this.layoutService.layoutConfig().menuMode === 'static', /* @ts-ignore */
|
|
2596
2623
|
...(ngDevMode ? [{ debugName: "isStatic" }] : /* istanbul ignore next */ []));
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
...(ngDevMode ? [{ debugName: "isStaticInactive" }] : /* istanbul ignore next */ []));
|
|
2601
|
-
isOverlayActive = computed(() => this.layoutService.layoutState().overlayMenuActive, /* @ts-ignore */
|
|
2602
|
-
...(ngDevMode ? [{ debugName: "isOverlayActive" }] : /* istanbul ignore next */ []));
|
|
2603
|
-
isMobileActive = computed(() => this.layoutService.layoutState().staticMenuMobileActive, /* @ts-ignore */
|
|
2604
|
-
...(ngDevMode ? [{ debugName: "isMobileActive" }] : /* istanbul ignore next */ []));
|
|
2605
|
-
isTopbarNavHidden = computed(() => !this.layoutService.layoutState().topbarMenuVisible && this.isTopbarMode(), /* @ts-ignore */
|
|
2624
|
+
/** Topbar mode without its nav row leaves the content sitting under the bar alone. */
|
|
2625
|
+
isTopbarNavHidden = computed(() => this.isTopbarMode() &&
|
|
2626
|
+
!this.layoutService.layoutState().topbarMenuVisible, /* @ts-ignore */
|
|
2606
2627
|
...(ngDevMode ? [{ debugName: "isTopbarNavHidden" }] : /* istanbul ignore next */ []));
|
|
2607
|
-
|
|
2608
|
-
|
|
2628
|
+
outsideClickSelectors() {
|
|
2629
|
+
return ['.layout-sidebar', '.layout-menu-button', '.layout-topbar-nav'];
|
|
2630
|
+
}
|
|
2631
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppClassicLayout, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2632
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppClassicLayout, isStandalone: true, selector: "app-classic-layout", usesInheritance: true, ngImport: i0, template: `
|
|
2609
2633
|
<div
|
|
2610
2634
|
class="layout-wrapper"
|
|
2611
2635
|
[class.layout-overlay]="isOverlay()"
|
|
2612
2636
|
[class.layout-static]="isStatic()"
|
|
2613
2637
|
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2614
|
-
[class.layout-static-inactive]="
|
|
2638
|
+
[class.layout-static-inactive]="isDesktopCollapsed()"
|
|
2615
2639
|
[class.layout-overlay-active]="isOverlayActive()"
|
|
2616
2640
|
[class.layout-mobile-active]="isMobileActive()"
|
|
2617
2641
|
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
@@ -2626,12 +2650,12 @@ class AppLayout {
|
|
|
2626
2650
|
</div>
|
|
2627
2651
|
<div class="layout-mask animate-fadein"></div>
|
|
2628
2652
|
</div>
|
|
2629
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2
|
|
2653
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }] });
|
|
2630
2654
|
}
|
|
2631
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
2655
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppClassicLayout, decorators: [{
|
|
2632
2656
|
type: Component,
|
|
2633
2657
|
args: [{
|
|
2634
|
-
selector: 'app-layout',
|
|
2658
|
+
selector: 'app-classic-layout',
|
|
2635
2659
|
imports: [AppTopbar, AppSidebar, RouterModule, AppFooter],
|
|
2636
2660
|
template: `
|
|
2637
2661
|
<div
|
|
@@ -2639,7 +2663,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2639
2663
|
[class.layout-overlay]="isOverlay()"
|
|
2640
2664
|
[class.layout-static]="isStatic()"
|
|
2641
2665
|
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2642
|
-
[class.layout-static-inactive]="
|
|
2666
|
+
[class.layout-static-inactive]="isDesktopCollapsed()"
|
|
2643
2667
|
[class.layout-overlay-active]="isOverlayActive()"
|
|
2644
2668
|
[class.layout-mobile-active]="isMobileActive()"
|
|
2645
2669
|
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
@@ -2654,10 +2678,357 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2654
2678
|
</div>
|
|
2655
2679
|
<div class="layout-mask animate-fadein"></div>
|
|
2656
2680
|
</div>
|
|
2681
|
+
`,
|
|
2682
|
+
}]
|
|
2683
|
+
}] });
|
|
2684
|
+
|
|
2685
|
+
class AppModernSidebar {
|
|
2686
|
+
layoutService = inject(LayoutService);
|
|
2687
|
+
/**
|
|
2688
|
+
* Only the in-flow column can be collapsed from here. As an overlay or mobile
|
|
2689
|
+
* drawer the sidebar is opened by the topbar button and dismissed by the mask,
|
|
2690
|
+
* so a second toggle inside it would be dead weight next to the brand.
|
|
2691
|
+
*/
|
|
2692
|
+
showMenuButton = computed(() => !this.layoutService.isMobile() && !this.layoutService.isOverlay(), /* @ts-ignore */
|
|
2693
|
+
...(ngDevMode ? [{ debugName: "showMenuButton" }] : /* istanbul ignore next */ []));
|
|
2694
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2695
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppModernSidebar, isStandalone: true, selector: "app-modern-sidebar", ngImport: i0, template: `
|
|
2696
|
+
<aside class="layout-modern-sidebar">
|
|
2697
|
+
<div class="layout-modern-brand">
|
|
2698
|
+
<app-brand-logo />
|
|
2699
|
+
@if (showMenuButton()) {
|
|
2700
|
+
<button
|
|
2701
|
+
type="button"
|
|
2702
|
+
class="layout-modern-menu-button"
|
|
2703
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2704
|
+
(click)="layoutService.onMenuToggle()"
|
|
2705
|
+
>
|
|
2706
|
+
<ui-icon name="bars"></ui-icon>
|
|
2707
|
+
</button>
|
|
2708
|
+
}
|
|
2709
|
+
</div>
|
|
2710
|
+
|
|
2711
|
+
<app-menu />
|
|
2712
|
+
|
|
2713
|
+
<div class="layout-modern-sidebar-footer">
|
|
2714
|
+
<app-footer />
|
|
2715
|
+
</div>
|
|
2716
|
+
</aside>
|
|
2717
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppBrandLogo, selector: "app-brand-logo" }, { kind: "component", type: AppMenu, selector: "app-menu" }, { kind: "component", type: AppFooter, selector: "app-footer" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2718
|
+
}
|
|
2719
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernSidebar, decorators: [{
|
|
2720
|
+
type: Component,
|
|
2721
|
+
args: [{
|
|
2722
|
+
selector: 'app-modern-sidebar',
|
|
2723
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppMenu, AppFooter],
|
|
2724
|
+
template: `
|
|
2725
|
+
<aside class="layout-modern-sidebar">
|
|
2726
|
+
<div class="layout-modern-brand">
|
|
2727
|
+
<app-brand-logo />
|
|
2728
|
+
@if (showMenuButton()) {
|
|
2729
|
+
<button
|
|
2730
|
+
type="button"
|
|
2731
|
+
class="layout-modern-menu-button"
|
|
2732
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2733
|
+
(click)="layoutService.onMenuToggle()"
|
|
2734
|
+
>
|
|
2735
|
+
<ui-icon name="bars"></ui-icon>
|
|
2736
|
+
</button>
|
|
2737
|
+
}
|
|
2738
|
+
</div>
|
|
2739
|
+
|
|
2740
|
+
<app-menu />
|
|
2741
|
+
|
|
2742
|
+
<div class="layout-modern-sidebar-footer">
|
|
2743
|
+
<app-footer />
|
|
2744
|
+
</div>
|
|
2745
|
+
</aside>
|
|
2746
|
+
`,
|
|
2747
|
+
}]
|
|
2748
|
+
}] });
|
|
2749
|
+
|
|
2750
|
+
class AppModernTopbar {
|
|
2751
|
+
destroyRef = inject(DestroyRef);
|
|
2752
|
+
router = inject(Router);
|
|
2753
|
+
layoutService = inject(LayoutService);
|
|
2754
|
+
_pageTitle = signal('', /* @ts-ignore */
|
|
2755
|
+
...(ngDevMode ? [{ debugName: "_pageTitle" }] : /* istanbul ignore next */ []));
|
|
2756
|
+
pageTitle = this._pageTitle.asReadonly();
|
|
2757
|
+
/** The sidebar is a drawer (not a column) in these modes, so the topbar owns the brand. */
|
|
2758
|
+
showBrand = computed(() => this.layoutService.isMobile() ||
|
|
2759
|
+
this.layoutService.isOverlay() ||
|
|
2760
|
+
!!this.layoutService.layoutState().staticMenuDesktopInactive, /* @ts-ignore */
|
|
2761
|
+
...(ngDevMode ? [{ debugName: "showBrand" }] : /* istanbul ignore next */ []));
|
|
2762
|
+
constructor() {
|
|
2763
|
+
this.resolvePageTitle();
|
|
2764
|
+
this.router.events
|
|
2765
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
2766
|
+
.subscribe(() => this.resolvePageTitle());
|
|
2767
|
+
}
|
|
2768
|
+
resolvePageTitle() {
|
|
2769
|
+
let route = this.router.routerState.snapshot.root;
|
|
2770
|
+
while (route.firstChild) {
|
|
2771
|
+
route = route.firstChild;
|
|
2772
|
+
}
|
|
2773
|
+
this._pageTitle.set(route.title ?? '');
|
|
2774
|
+
}
|
|
2775
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2776
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppModernTopbar, isStandalone: true, selector: "app-modern-topbar", ngImport: i0, template: `
|
|
2777
|
+
<header class="layout-modern-topbar">
|
|
2778
|
+
@if (showBrand()) {
|
|
2779
|
+
<button
|
|
2780
|
+
type="button"
|
|
2781
|
+
class="layout-modern-menu-button layout-topbar-action"
|
|
2782
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2783
|
+
(click)="layoutService.onMenuToggle()"
|
|
2784
|
+
>
|
|
2785
|
+
<ui-icon name="bars"></ui-icon>
|
|
2786
|
+
</button>
|
|
2787
|
+
|
|
2788
|
+
<app-brand-logo />
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
<span class="layout-modern-page-title">{{ pageTitle() }}</span>
|
|
2792
|
+
|
|
2793
|
+
<app-topbar-actions />
|
|
2794
|
+
</header>
|
|
2795
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppBrandLogo, selector: "app-brand-logo" }, { kind: "component", type: AppTopbarActions, selector: "app-topbar-actions" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2796
|
+
}
|
|
2797
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernTopbar, decorators: [{
|
|
2798
|
+
type: Component,
|
|
2799
|
+
args: [{
|
|
2800
|
+
selector: 'app-modern-topbar',
|
|
2801
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppTopbarActions],
|
|
2802
|
+
template: `
|
|
2803
|
+
<header class="layout-modern-topbar">
|
|
2804
|
+
@if (showBrand()) {
|
|
2805
|
+
<button
|
|
2806
|
+
type="button"
|
|
2807
|
+
class="layout-modern-menu-button layout-topbar-action"
|
|
2808
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2809
|
+
(click)="layoutService.onMenuToggle()"
|
|
2810
|
+
>
|
|
2811
|
+
<ui-icon name="bars"></ui-icon>
|
|
2812
|
+
</button>
|
|
2813
|
+
|
|
2814
|
+
<app-brand-logo />
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
<span class="layout-modern-page-title">{{ pageTitle() }}</span>
|
|
2818
|
+
|
|
2819
|
+
<app-topbar-actions />
|
|
2820
|
+
</header>
|
|
2657
2821
|
`,
|
|
2658
2822
|
}]
|
|
2659
2823
|
}], ctorParameters: () => [] });
|
|
2660
2824
|
|
|
2825
|
+
class AppModernLayout extends LayoutShellBase {
|
|
2826
|
+
content = viewChild('content', /* @ts-ignore */
|
|
2827
|
+
...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
2828
|
+
outsideClickSelectors() {
|
|
2829
|
+
return ['.layout-modern-sidebar', '.layout-modern-menu-button'];
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* The page scrolls inside the content card rather than the window, so the router's
|
|
2833
|
+
* scroll restoration never reaches it — reset it here instead.
|
|
2834
|
+
*/
|
|
2835
|
+
onNavigated() {
|
|
2836
|
+
this.content()?.nativeElement.scrollTo({ top: 0 });
|
|
2837
|
+
}
|
|
2838
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernLayout, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2839
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.6", type: AppModernLayout, isStandalone: true, selector: "app-modern-layout", viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
2840
|
+
<div
|
|
2841
|
+
class="layout-modern"
|
|
2842
|
+
[class.layout-modern-overlay]="isOverlay()"
|
|
2843
|
+
[class.layout-modern-static-inactive]="isDesktopCollapsed()"
|
|
2844
|
+
[class.layout-modern-overlay-active]="isOverlayActive()"
|
|
2845
|
+
[class.layout-modern-mobile-active]="isMobileActive()"
|
|
2846
|
+
>
|
|
2847
|
+
<app-modern-sidebar />
|
|
2848
|
+
<div class="layout-modern-main">
|
|
2849
|
+
<app-modern-topbar />
|
|
2850
|
+
<div class="layout-modern-content" #content>
|
|
2851
|
+
<router-outlet></router-outlet>
|
|
2852
|
+
</div>
|
|
2853
|
+
</div>
|
|
2854
|
+
<div class="layout-modern-mask animate-fadein"></div>
|
|
2855
|
+
</div>
|
|
2856
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppModernTopbar, selector: "app-modern-topbar" }, { kind: "component", type: AppModernSidebar, selector: "app-modern-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
|
|
2857
|
+
}
|
|
2858
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernLayout, decorators: [{
|
|
2859
|
+
type: Component,
|
|
2860
|
+
args: [{
|
|
2861
|
+
selector: 'app-modern-layout',
|
|
2862
|
+
imports: [AppModernTopbar, AppModernSidebar, RouterModule],
|
|
2863
|
+
template: `
|
|
2864
|
+
<div
|
|
2865
|
+
class="layout-modern"
|
|
2866
|
+
[class.layout-modern-overlay]="isOverlay()"
|
|
2867
|
+
[class.layout-modern-static-inactive]="isDesktopCollapsed()"
|
|
2868
|
+
[class.layout-modern-overlay-active]="isOverlayActive()"
|
|
2869
|
+
[class.layout-modern-mobile-active]="isMobileActive()"
|
|
2870
|
+
>
|
|
2871
|
+
<app-modern-sidebar />
|
|
2872
|
+
<div class="layout-modern-main">
|
|
2873
|
+
<app-modern-topbar />
|
|
2874
|
+
<div class="layout-modern-content" #content>
|
|
2875
|
+
<router-outlet></router-outlet>
|
|
2876
|
+
</div>
|
|
2877
|
+
</div>
|
|
2878
|
+
<div class="layout-modern-mask animate-fadein"></div>
|
|
2879
|
+
</div>
|
|
2880
|
+
`,
|
|
2881
|
+
}]
|
|
2882
|
+
}], propDecorators: { content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Layout shell entry point.
|
|
2886
|
+
* Renders the skin selected by `LayoutConfig.layoutTheme` — every skin shares
|
|
2887
|
+
* the same services, menu and topbar components, only the markup and styling differ.
|
|
2888
|
+
*/
|
|
2889
|
+
class AppLayout {
|
|
2890
|
+
layoutService = inject(LayoutService);
|
|
2891
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2892
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
|
|
2893
|
+
@if (layoutService.isModernLayout()) {
|
|
2894
|
+
<app-modern-layout />
|
|
2895
|
+
} @else {
|
|
2896
|
+
<app-classic-layout />
|
|
2897
|
+
}
|
|
2898
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppClassicLayout, selector: "app-classic-layout" }, { kind: "component", type: AppModernLayout, selector: "app-modern-layout" }] });
|
|
2899
|
+
}
|
|
2900
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, decorators: [{
|
|
2901
|
+
type: Component,
|
|
2902
|
+
args: [{
|
|
2903
|
+
selector: 'app-layout',
|
|
2904
|
+
imports: [AppClassicLayout, AppModernLayout],
|
|
2905
|
+
template: `
|
|
2906
|
+
@if (layoutService.isModernLayout()) {
|
|
2907
|
+
<app-modern-layout />
|
|
2908
|
+
} @else {
|
|
2909
|
+
<app-classic-layout />
|
|
2910
|
+
}
|
|
2911
|
+
`,
|
|
2912
|
+
}]
|
|
2913
|
+
}] });
|
|
2914
|
+
|
|
2915
|
+
class AppFloatingConfigurator {
|
|
2916
|
+
layoutService = inject(LayoutService);
|
|
2917
|
+
languageSelectorPanel = inject(LAYOUT_LANGUAGE_SELECTOR_PANEL, {
|
|
2918
|
+
optional: true,
|
|
2919
|
+
});
|
|
2920
|
+
isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, /* @ts-ignore */
|
|
2921
|
+
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
2922
|
+
toggleDarkMode() {
|
|
2923
|
+
const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
|
|
2924
|
+
this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
|
|
2925
|
+
}
|
|
2926
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2927
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
|
|
2928
|
+
<div
|
|
2929
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
2930
|
+
>
|
|
2931
|
+
<button
|
|
2932
|
+
fButton
|
|
2933
|
+
type="button"
|
|
2934
|
+
(click)="toggleDarkMode()"
|
|
2935
|
+
[rounded]="true"
|
|
2936
|
+
severity="secondary"
|
|
2937
|
+
>
|
|
2938
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
2939
|
+
</button>
|
|
2940
|
+
@if (languageSelectorPanel) {
|
|
2941
|
+
<div class="relative">
|
|
2942
|
+
<button
|
|
2943
|
+
fButton
|
|
2944
|
+
fStyleClass="@next"
|
|
2945
|
+
enterFromClass="hidden"
|
|
2946
|
+
enterActiveClass="animate-scalein"
|
|
2947
|
+
leaveToClass="hidden"
|
|
2948
|
+
leaveActiveClass="animate-fadeout"
|
|
2949
|
+
[hideOnOutsideClick]="true"
|
|
2950
|
+
type="button"
|
|
2951
|
+
rounded
|
|
2952
|
+
>
|
|
2953
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
2954
|
+
</button>
|
|
2955
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
2956
|
+
</div>
|
|
2957
|
+
}
|
|
2958
|
+
<div class="relative">
|
|
2959
|
+
<button
|
|
2960
|
+
fButton
|
|
2961
|
+
fStyleClass="@next"
|
|
2962
|
+
enterFromClass="hidden"
|
|
2963
|
+
enterActiveClass="animate-scalein"
|
|
2964
|
+
leaveToClass="hidden"
|
|
2965
|
+
leaveActiveClass="animate-fadeout"
|
|
2966
|
+
[hideOnOutsideClick]="true"
|
|
2967
|
+
type="button"
|
|
2968
|
+
rounded
|
|
2969
|
+
>
|
|
2970
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
2971
|
+
</button>
|
|
2972
|
+
<app-configurator />
|
|
2973
|
+
</div>
|
|
2974
|
+
</div>
|
|
2975
|
+
`, isInline: true, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }] });
|
|
2976
|
+
}
|
|
2977
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
|
|
2978
|
+
type: Component,
|
|
2979
|
+
args: [{ selector: 'app-floating-configurator', imports: [NgComponentOutlet, UiModule, AppConfigurator], template: `
|
|
2980
|
+
<div
|
|
2981
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
2982
|
+
>
|
|
2983
|
+
<button
|
|
2984
|
+
fButton
|
|
2985
|
+
type="button"
|
|
2986
|
+
(click)="toggleDarkMode()"
|
|
2987
|
+
[rounded]="true"
|
|
2988
|
+
severity="secondary"
|
|
2989
|
+
>
|
|
2990
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
2991
|
+
</button>
|
|
2992
|
+
@if (languageSelectorPanel) {
|
|
2993
|
+
<div class="relative">
|
|
2994
|
+
<button
|
|
2995
|
+
fButton
|
|
2996
|
+
fStyleClass="@next"
|
|
2997
|
+
enterFromClass="hidden"
|
|
2998
|
+
enterActiveClass="animate-scalein"
|
|
2999
|
+
leaveToClass="hidden"
|
|
3000
|
+
leaveActiveClass="animate-fadeout"
|
|
3001
|
+
[hideOnOutsideClick]="true"
|
|
3002
|
+
type="button"
|
|
3003
|
+
rounded
|
|
3004
|
+
>
|
|
3005
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
3006
|
+
</button>
|
|
3007
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
3008
|
+
</div>
|
|
3009
|
+
}
|
|
3010
|
+
<div class="relative">
|
|
3011
|
+
<button
|
|
3012
|
+
fButton
|
|
3013
|
+
fStyleClass="@next"
|
|
3014
|
+
enterFromClass="hidden"
|
|
3015
|
+
enterActiveClass="animate-scalein"
|
|
3016
|
+
leaveToClass="hidden"
|
|
3017
|
+
leaveActiveClass="animate-fadeout"
|
|
3018
|
+
[hideOnOutsideClick]="true"
|
|
3019
|
+
type="button"
|
|
3020
|
+
rounded
|
|
3021
|
+
>
|
|
3022
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
3023
|
+
</button>
|
|
3024
|
+
<app-configurator />
|
|
3025
|
+
</div>
|
|
3026
|
+
</div>
|
|
3027
|
+
`, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"] }]
|
|
3028
|
+
}] });
|
|
3029
|
+
|
|
3030
|
+
// Shell entry point (renders the configured layout skin)
|
|
3031
|
+
|
|
2661
3032
|
function buildPreset(presetName, primary) {
|
|
2662
3033
|
return { preset: presetName, primary };
|
|
2663
3034
|
}
|
|
@@ -2673,7 +3044,7 @@ const GreenTheme = {
|
|
|
2673
3044
|
};
|
|
2674
3045
|
|
|
2675
3046
|
const LAYOUT_MESSAGES = {
|
|
2676
|
-
// components/top-bar/app.profile.ts
|
|
3047
|
+
// components/shared/top-bar/app.profile.ts
|
|
2677
3048
|
'layout.profile.title': 'Profile',
|
|
2678
3049
|
'layout.profile.profile.picture.alt': 'Profile picture',
|
|
2679
3050
|
'layout.profile.copy.sign.up.link': 'Copy SignUp Link',
|
|
@@ -2682,11 +3053,11 @@ const LAYOUT_MESSAGES = {
|
|
|
2682
3053
|
'layout.profile.link.copied': 'Link Copied',
|
|
2683
3054
|
'layout.profile.sign.up.link.copied': 'Sign Up Link copied.',
|
|
2684
3055
|
'layout.profile.failed.copy.sign.up.link': 'Failed to copy signup link.',
|
|
2685
|
-
// components/top-bar/app.launcher.ts
|
|
3056
|
+
// components/shared/top-bar/app.launcher.ts
|
|
2686
3057
|
'layout.launcher.apps': 'Apps',
|
|
2687
3058
|
'layout.launcher.applications': 'Applications',
|
|
2688
3059
|
'layout.launcher.no.apps': 'No apps available',
|
|
2689
|
-
// components/top-bar/app.company-branch-selector.ts
|
|
3060
|
+
// components/shared/top-bar/app.company-branch-selector.ts
|
|
2690
3061
|
'layout.company.branch.selector.title': 'Switch Company & Branch',
|
|
2691
3062
|
'layout.company.branch.selector.select.company': 'Select Company',
|
|
2692
3063
|
'layout.company.branch.selector.branch': 'Branch',
|
|
@@ -2695,15 +3066,17 @@ const LAYOUT_MESSAGES = {
|
|
|
2695
3066
|
'layout.company.branch.selector.no.company': 'No Company',
|
|
2696
3067
|
'layout.company.branch.selector.branch.required': 'Branch Required',
|
|
2697
3068
|
'layout.company.branch.selector.please.select.branch': 'Please select a branch',
|
|
2698
|
-
// components/top-bar/app.searchbar.ts
|
|
3069
|
+
// components/shared/top-bar/app.searchbar.ts
|
|
2699
3070
|
'layout.topbar.search': 'Search',
|
|
2700
3071
|
'layout.topbar.search.placeholder': 'Search for content...',
|
|
2701
|
-
// components/app.topbar.ts
|
|
3072
|
+
// components/layouts/classic/app.topbar.ts + components/layouts/modern/app.modern-topbar.ts
|
|
3073
|
+
// + components/layouts/modern/app.modern-sidebar.ts
|
|
2702
3074
|
'layout.topbar.toggle.menu': 'Toggle menu',
|
|
3075
|
+
// components/shared/top-bar/app.topbar-actions.ts
|
|
2703
3076
|
'layout.topbar.toggle.dark.mode': 'Toggle dark mode',
|
|
2704
3077
|
'layout.topbar.open.theme.settings': 'Open theme settings',
|
|
2705
3078
|
'layout.topbar.more.options': 'More options',
|
|
2706
|
-
// components/app.configurator.ts
|
|
3079
|
+
// components/shared/app.configurator.ts
|
|
2707
3080
|
'layout.configurator.primary': 'Primary',
|
|
2708
3081
|
'layout.configurator.surface': 'Surface',
|
|
2709
3082
|
'layout.configurator.presets': 'Presets',
|
|
@@ -2711,7 +3084,10 @@ const LAYOUT_MESSAGES = {
|
|
|
2711
3084
|
'layout.configurator.menu.mode.static': 'Static',
|
|
2712
3085
|
'layout.configurator.menu.mode.overlay': 'Overlay',
|
|
2713
3086
|
'layout.configurator.menu.mode.topbar': 'Topbar',
|
|
2714
|
-
|
|
3087
|
+
'layout.configurator.layout.theme': 'Layout',
|
|
3088
|
+
'layout.configurator.layout.theme.classic': 'Classic',
|
|
3089
|
+
'layout.configurator.layout.theme.modern': 'Modern',
|
|
3090
|
+
// components/shared/app.footer.ts
|
|
2715
3091
|
'layout.footer.by': 'by',
|
|
2716
3092
|
// app-menu.config.ts (main app menu configuration)
|
|
2717
3093
|
'menu.dashboard': 'Dashboard',
|
|
@@ -2761,5 +3137,5 @@ const LAYOUT_MESSAGES = {
|
|
|
2761
3137
|
* Generated bundle index. Do not edit.
|
|
2762
3138
|
*/
|
|
2763
3139
|
|
|
2764
|
-
export { AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppProfile, AppSearchbar, AppSidebar, AppTopbar, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LAYOUT_IS_RTL, LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL, LAYOUT_MESSAGES, LAYOUT_NOTIFICATION_BELL, LAYOUT_SEARCHBAR, LAYOUT_SEARCH_ADAPTER, LayoutPersistenceService, LayoutService, NavyBlueTheme, buildPreset, filterAppsByPermissions, filterMenuByPermissions };
|
|
3140
|
+
export { AppBrandLogo, AppClassicLayout, AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppModernLayout, AppModernSidebar, AppModernTopbar, AppProfile, AppSearchbar, AppSidebar, AppTopbar, AppTopbarActions, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LAYOUT_IS_RTL, LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL, LAYOUT_MESSAGES, LAYOUT_NOTIFICATION_BELL, LAYOUT_SEARCHBAR, LAYOUT_SEARCH_ADAPTER, LayoutPersistenceService, LayoutService, LayoutShellBase, NavyBlueTheme, buildPreset, filterAppsByPermissions, filterMenuByPermissions };
|
|
2765
3141
|
//# sourceMappingURL=flusys-ng-layout.mjs.map
|