@flusys/ng-layout 6.3.0 → 6.4.1
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 +152 -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 +95 -0
- package/assets/layout/classic/_rtl.scss +26 -0
- package/assets/layout/classic/_topbar.scss +53 -0
- package/assets/layout/{_topbar_nav.scss → classic/_topbar_nav.scss} +45 -17
- 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 +1353 -954
- package/fesm2022/flusys-ng-layout.mjs.map +1 -1
- package/package.json +4 -4
- package/types/flusys-ng-layout.d.ts +175 -65
- 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,
|
|
@@ -160,7 +173,6 @@ class LayoutService {
|
|
|
160
173
|
configSidebarVisible: false,
|
|
161
174
|
staticMenuMobileActive: false,
|
|
162
175
|
menuHoverActive: false,
|
|
163
|
-
topbarMenuVisible: true,
|
|
164
176
|
};
|
|
165
177
|
memorable = this.DEFAULT_CONFIG.isMemorable !== false;
|
|
166
178
|
_layoutConfig = signal({
|
|
@@ -211,22 +223,32 @@ class LayoutService {
|
|
|
211
223
|
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
212
224
|
isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', /* @ts-ignore */
|
|
213
225
|
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
214
|
-
|
|
226
|
+
layoutTheme = computed(() => this._layoutConfig().layoutTheme ?? 'classic', /* @ts-ignore */
|
|
227
|
+
...(ngDevMode ? [{ debugName: "layoutTheme" }] : /* istanbul ignore next */ []));
|
|
228
|
+
isModernLayout = computed(() => this.layoutTheme() === 'modern', /* @ts-ignore */
|
|
229
|
+
...(ngDevMode ? [{ debugName: "isModernLayout" }] : /* istanbul ignore next */ []));
|
|
230
|
+
isClassicLayout = computed(() => this.layoutTheme() === 'classic', /* @ts-ignore */
|
|
231
|
+
...(ngDevMode ? [{ debugName: "isClassicLayout" }] : /* istanbul ignore next */ []));
|
|
232
|
+
/** The modern skin has no horizontal nav, so topbar mode only applies to the classic skin. */
|
|
233
|
+
isTopbar = computed(() => this._layoutConfig().menuMode === 'topbar' && !this.isModernLayout(), /* @ts-ignore */
|
|
215
234
|
...(ngDevMode ? [{ debugName: "isTopbar" }] : /* istanbul ignore next */ []));
|
|
216
235
|
userName = computed(() => this._userProfile()?.name ?? 'Guest', /* @ts-ignore */
|
|
217
236
|
...(ngDevMode ? [{ debugName: "userName" }] : /* istanbul ignore next */ []));
|
|
218
237
|
userEmail = computed(() => this._userProfile()?.email ?? '', /* @ts-ignore */
|
|
219
238
|
...(ngDevMode ? [{ debugName: "userEmail" }] : /* istanbul ignore next */ []));
|
|
220
|
-
|
|
239
|
+
// `||`, not `??`, throughout the block below: the backend returns "no picture" and
|
|
240
|
+
// "no logo" as an empty string as often as it does null, and an empty string would
|
|
241
|
+
// slip past `??` and render as a broken image.
|
|
242
|
+
userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl || null, /* @ts-ignore */
|
|
221
243
|
...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : /* istanbul ignore next */ []));
|
|
222
|
-
companyLogoUrl = computed(() => this._companyProfile()?.logoUrl
|
|
244
|
+
companyLogoUrl = computed(() => this._companyProfile()?.logoUrl || null, /* @ts-ignore */
|
|
223
245
|
...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : /* istanbul ignore next */ []));
|
|
224
246
|
isAuthenticated = computed(() => !!this._userProfile(), /* @ts-ignore */
|
|
225
247
|
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
226
248
|
companyName = computed(() => {
|
|
227
249
|
if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig))
|
|
228
250
|
return this.appName;
|
|
229
|
-
return this._companyProfile()?.name
|
|
251
|
+
return this._companyProfile()?.name || this.appName;
|
|
230
252
|
}, /* @ts-ignore */
|
|
231
253
|
...(ngDevMode ? [{ debugName: "companyName" }] : /* istanbul ignore next */ []));
|
|
232
254
|
displayLogo = computed(() => this.companyLogoUrl() ?? this.appLogo, /* @ts-ignore */
|
|
@@ -240,6 +262,7 @@ class LayoutService {
|
|
|
240
262
|
resetSource$ = this.resetSource.asObservable();
|
|
241
263
|
overlayOpen$ = this.overlayOpen.asObservable();
|
|
242
264
|
initialized = false;
|
|
265
|
+
appliedDarkTheme = this._layoutConfig().darkTheme;
|
|
243
266
|
constructor() {
|
|
244
267
|
if (this.isBrowser) {
|
|
245
268
|
this.toggleDarkMode();
|
|
@@ -252,7 +275,13 @@ class LayoutService {
|
|
|
252
275
|
effect(() => {
|
|
253
276
|
const config = this._layoutConfig();
|
|
254
277
|
if (this.initialized) {
|
|
255
|
-
|
|
278
|
+
// Only the dark-mode flip is worth a view transition. Every other field —
|
|
279
|
+
// preset, primary, menu mode, layout skin — used to start one too, which
|
|
280
|
+
// cross-faded the whole page for a change that never touched it.
|
|
281
|
+
if (config.darkTheme !== this.appliedDarkTheme) {
|
|
282
|
+
this.appliedDarkTheme = config.darkTheme;
|
|
283
|
+
this.handleDarkModeTransition(config);
|
|
284
|
+
}
|
|
256
285
|
if (this.memorable)
|
|
257
286
|
this.persistence.save(config);
|
|
258
287
|
}
|
|
@@ -283,14 +312,6 @@ class LayoutService {
|
|
|
283
312
|
}
|
|
284
313
|
onMenuToggle() {
|
|
285
314
|
const state = this._layoutState();
|
|
286
|
-
// Desktop topbar mode: toggle topbar nav visibility
|
|
287
|
-
if (this.isDesktop() && this.isTopbar()) {
|
|
288
|
-
this._layoutState.update((prev) => ({
|
|
289
|
-
...prev,
|
|
290
|
-
topbarMenuVisible: !prev.topbarMenuVisible,
|
|
291
|
-
}));
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
315
|
// Mobile always uses staticMenuMobileActive regardless of menu mode
|
|
295
316
|
if (!this.isDesktop()) {
|
|
296
317
|
const newMobileActive = !state.staticMenuMobileActive;
|
|
@@ -357,6 +378,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
357
378
|
args: [{ providedIn: 'root' }]
|
|
358
379
|
}], ctorParameters: () => [] });
|
|
359
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Shared shell behaviour for every layout skin.
|
|
383
|
+
* Holds the menu open/close lifecycle so a skin only owns markup and styling.
|
|
384
|
+
*/
|
|
385
|
+
class LayoutShellBase {
|
|
386
|
+
destroyRef = inject(DestroyRef);
|
|
387
|
+
document = inject(DOCUMENT);
|
|
388
|
+
renderer = inject(Renderer2);
|
|
389
|
+
router = inject(Router);
|
|
390
|
+
layoutService = inject(LayoutService);
|
|
391
|
+
menuOutsideClickListener = null;
|
|
392
|
+
/** Extra work a skin needs on every completed navigation. */
|
|
393
|
+
onNavigated() { }
|
|
394
|
+
constructor() {
|
|
395
|
+
this.layoutService.overlayOpen$
|
|
396
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
397
|
+
.subscribe(() => {
|
|
398
|
+
if (!this.menuOutsideClickListener) {
|
|
399
|
+
this.menuOutsideClickListener = this.renderer.listen(this.document, 'click', (event) => {
|
|
400
|
+
if (this.isOutsideClicked(event)) {
|
|
401
|
+
this.hideMenu();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (this.layoutService.layoutState().staticMenuMobileActive) {
|
|
406
|
+
this.blockBodyScroll();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
this.router.events
|
|
410
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
411
|
+
.subscribe(() => {
|
|
412
|
+
this.hideMenu();
|
|
413
|
+
this.onNavigated();
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
isOutsideClicked(event) {
|
|
417
|
+
const eventTarget = event.target;
|
|
418
|
+
// querySelectorAll, not querySelector: a skin may render the same control more
|
|
419
|
+
// than once (the modern shell has a menu button in both the sidebar and the
|
|
420
|
+
// topbar) and matching only the first one reads a real click as an outside one.
|
|
421
|
+
return !this.outsideClickSelectors().some((selector) => Array.from(this.document.querySelectorAll(selector)).some((element) => element.isSameNode(eventTarget) || element.contains(eventTarget)));
|
|
422
|
+
}
|
|
423
|
+
hideMenu() {
|
|
424
|
+
this.layoutService.updateLayoutState({
|
|
425
|
+
overlayMenuActive: false,
|
|
426
|
+
staticMenuMobileActive: false,
|
|
427
|
+
menuHoverActive: false,
|
|
428
|
+
});
|
|
429
|
+
if (this.menuOutsideClickListener) {
|
|
430
|
+
this.menuOutsideClickListener();
|
|
431
|
+
this.menuOutsideClickListener = null;
|
|
432
|
+
}
|
|
433
|
+
this.unblockBodyScroll();
|
|
434
|
+
}
|
|
435
|
+
blockBodyScroll() {
|
|
436
|
+
this.document.body.classList.add('blocked-scroll');
|
|
437
|
+
}
|
|
438
|
+
unblockBodyScroll() {
|
|
439
|
+
this.document.body.classList.remove('blocked-scroll');
|
|
440
|
+
}
|
|
441
|
+
isOverlay = this.layoutService.isOverlay;
|
|
442
|
+
/** Delegated so a skin that has no horizontal nav is never treated as being in it. */
|
|
443
|
+
isTopbarMode = this.layoutService.isTopbar;
|
|
444
|
+
isOverlayActive = computed(() => this.layoutService.layoutState().overlayMenuActive, /* @ts-ignore */
|
|
445
|
+
...(ngDevMode ? [{ debugName: "isOverlayActive" }] : /* istanbul ignore next */ []));
|
|
446
|
+
isMobileActive = computed(() => this.layoutService.layoutState().staticMenuMobileActive, /* @ts-ignore */
|
|
447
|
+
...(ngDevMode ? [{ debugName: "isMobileActive" }] : /* istanbul ignore next */ []));
|
|
448
|
+
/**
|
|
449
|
+
* The desktop sidebar is collapsed in place. Only meaningful for the modes that
|
|
450
|
+
* keep the sidebar in the layout flow — an overlay drawer and the horizontal nav
|
|
451
|
+
* have nothing to collapse.
|
|
452
|
+
*/
|
|
453
|
+
isDesktopCollapsed = computed(() => !!this.layoutService.layoutState().staticMenuDesktopInactive &&
|
|
454
|
+
!this.isOverlay() &&
|
|
455
|
+
!this.isTopbarMode(), /* @ts-ignore */
|
|
456
|
+
...(ngDevMode ? [{ debugName: "isDesktopCollapsed" }] : /* istanbul ignore next */ []));
|
|
457
|
+
}
|
|
458
|
+
|
|
360
459
|
class AppFooter {
|
|
361
460
|
layoutService = inject(LayoutService);
|
|
362
461
|
appName = this.layoutService.appName;
|
|
@@ -412,262 +511,394 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
412
511
|
}]
|
|
413
512
|
}] });
|
|
414
513
|
|
|
415
|
-
class
|
|
514
|
+
class AppMenuitem {
|
|
515
|
+
// Signal inputs
|
|
516
|
+
item = input.required(/* @ts-ignore */
|
|
517
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
518
|
+
index = input.required(/* @ts-ignore */
|
|
519
|
+
...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
520
|
+
parentKey = input('', /* @ts-ignore */
|
|
521
|
+
...(ngDevMode ? [{ debugName: "parentKey" }] : /* istanbul ignore next */ []));
|
|
522
|
+
// Injected services
|
|
416
523
|
router = inject(Router);
|
|
417
|
-
translateAdapter = inject(TranslateService, { optional: true });
|
|
418
|
-
platformId = inject(PLATFORM_ID);
|
|
419
524
|
layoutService = inject(LayoutService);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return
|
|
525
|
+
destroyRef = inject(DestroyRef);
|
|
526
|
+
// View references
|
|
527
|
+
parentLink = viewChild('parentLink', /* @ts-ignore */
|
|
528
|
+
...(ngDevMode ? [{ debugName: "parentLink" }] : /* istanbul ignore next */ []));
|
|
529
|
+
// State signals - private writable + public readonly pattern
|
|
530
|
+
_active = signal(false, /* @ts-ignore */
|
|
531
|
+
...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
532
|
+
active = this._active.asReadonly();
|
|
533
|
+
// Computed signals
|
|
534
|
+
key = computed(() => {
|
|
535
|
+
const parent = this.parentKey();
|
|
536
|
+
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
432
537
|
}, /* @ts-ignore */
|
|
433
|
-
...(ngDevMode ? [{ debugName: "
|
|
538
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
539
|
+
routerLink = computed(() => {
|
|
540
|
+
const menuItem = this.item();
|
|
541
|
+
return menuItem.routerLink ?? [];
|
|
542
|
+
}, /* @ts-ignore */
|
|
543
|
+
...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
544
|
+
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
545
|
+
routerLinkActiveOptions = computed(() => {
|
|
546
|
+
const link = this.routerLink();
|
|
547
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
548
|
+
return {
|
|
549
|
+
paths: pathsMatch,
|
|
550
|
+
queryParams: 'ignored',
|
|
551
|
+
matrixParams: 'ignored',
|
|
552
|
+
fragment: 'ignored',
|
|
553
|
+
};
|
|
554
|
+
}, /* @ts-ignore */
|
|
555
|
+
...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : /* istanbul ignore next */ []));
|
|
434
556
|
constructor() {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
557
|
+
// Subscribe to menu source changes
|
|
558
|
+
this.layoutService.menuSource$
|
|
559
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
560
|
+
.subscribe((value) => {
|
|
561
|
+
Promise.resolve(null).then(() => {
|
|
562
|
+
const currentKey = this.key();
|
|
563
|
+
if (value.routeEvent) {
|
|
564
|
+
this._active.set(value.key === currentKey ||
|
|
565
|
+
value.key?.startsWith(currentKey + '-'));
|
|
566
|
+
}
|
|
567
|
+
else if (value.key !== currentKey &&
|
|
568
|
+
!value.key?.startsWith(currentKey + '-')) {
|
|
569
|
+
this._active.set(false);
|
|
570
|
+
}
|
|
444
571
|
});
|
|
572
|
+
});
|
|
573
|
+
// Subscribe to menu reset
|
|
574
|
+
this.layoutService.resetSource$
|
|
575
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
576
|
+
.subscribe(() => this._active.set(false));
|
|
577
|
+
// Subscribe to navigation events
|
|
578
|
+
this.router.events
|
|
579
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
580
|
+
.subscribe(() => {
|
|
581
|
+
if (this.item().routerLink) {
|
|
582
|
+
this.updateActiveStateFromRoute();
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
// Effect to update active state on init when item has routerLink
|
|
586
|
+
effect(() => {
|
|
587
|
+
const menuItem = this.item();
|
|
588
|
+
if (menuItem.routerLink) {
|
|
589
|
+
this.updateActiveStateFromRoute();
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
// Effect to update submenu position when active in topbar mode
|
|
593
|
+
effect(() => {
|
|
594
|
+
if (this.active() && this.parentLink()) {
|
|
595
|
+
Promise.resolve().then(() => this.updateSubmenuPosition());
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
onMouseEnter() {
|
|
600
|
+
if (this.layoutService.isTopbar() && this.item().children) {
|
|
601
|
+
this.updateSubmenuPosition();
|
|
445
602
|
}
|
|
446
603
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
return isDark ? surfaceName === 'zinc' : surfaceName === 'slate';
|
|
604
|
+
updateSubmenuPosition() {
|
|
605
|
+
const linkEl = this.parentLink()?.nativeElement;
|
|
606
|
+
if (!linkEl || typeof linkEl.getBoundingClientRect !== 'function')
|
|
607
|
+
return;
|
|
608
|
+
const rect = linkEl.getBoundingClientRect();
|
|
609
|
+
const hostEl = linkEl.closest('[app-menuitem]');
|
|
610
|
+
if (!hostEl)
|
|
611
|
+
return;
|
|
612
|
+
// Position submenu to the left of the parent link and above it
|
|
613
|
+
const left = rect.left;
|
|
614
|
+
const top = rect.top + 30; // 8px gap above the menu item
|
|
615
|
+
hostEl.style.setProperty('--submenu-left', `${left}px`);
|
|
616
|
+
hostEl.style.setProperty('--submenu-top', `${top}px`);
|
|
461
617
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (
|
|
476
|
-
this.layoutService.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
618
|
+
updateActiveStateFromRoute() {
|
|
619
|
+
const link = this.routerLink();
|
|
620
|
+
if (!link.length)
|
|
621
|
+
return;
|
|
622
|
+
// Use 'exact' for root path to avoid matching all routes
|
|
623
|
+
// Use 'subset' for all other paths to match child routes
|
|
624
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
625
|
+
const active = isActive(link[0], this.router, {
|
|
626
|
+
paths: pathsMatch,
|
|
627
|
+
queryParams: 'ignored',
|
|
628
|
+
matrixParams: 'ignored',
|
|
629
|
+
fragment: 'ignored',
|
|
630
|
+
})();
|
|
631
|
+
if (active) {
|
|
632
|
+
this.layoutService.onMenuStateChange({
|
|
633
|
+
key: this.key(),
|
|
634
|
+
routeEvent: true,
|
|
635
|
+
});
|
|
480
636
|
}
|
|
481
|
-
this._applyColorChange(type, color);
|
|
482
|
-
event.stopPropagation();
|
|
483
637
|
}
|
|
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);
|
|
638
|
+
itemClick() {
|
|
639
|
+
if (this.item().children && !this.layoutService.isTopbar()) {
|
|
640
|
+
this._active.update((prev) => !prev);
|
|
493
641
|
}
|
|
494
|
-
|
|
495
|
-
const surfacePal = SURFACE_PALETTES[color.name ?? 'slate'];
|
|
496
|
-
if (surfacePal)
|
|
497
|
-
applySurfacePalette(surfacePal);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
onPresetChange(event) {
|
|
501
|
-
const primary = PRESETS[event]?.signaturePrimary ?? 'violet';
|
|
502
|
-
this.layoutService.updateLayoutConfig({ preset: event, primary });
|
|
503
|
-
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
504
|
-
const surface = this.layoutService.layoutConfig().surface ?? null;
|
|
505
|
-
applyTheme({ preset: event, primary, surface, darkTheme: isDark });
|
|
506
|
-
}
|
|
507
|
-
onMenuModeChange(event) {
|
|
508
|
-
this.layoutService.updateLayoutConfig({ menuMode: event });
|
|
509
|
-
}
|
|
510
|
-
translate(key) {
|
|
511
|
-
return this.translateAdapter?.translate(key) ?? key;
|
|
642
|
+
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
512
643
|
}
|
|
513
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
514
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type:
|
|
515
|
-
<
|
|
516
|
-
|
|
517
|
-
<
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
[
|
|
527
|
-
class="
|
|
528
|
-
|
|
529
|
-
primaryColor?.name === 'noir'
|
|
530
|
-
? 'var(--f-text-color)'
|
|
531
|
-
: primaryColor?.palette?.['500']
|
|
532
|
-
"
|
|
533
|
-
></button>
|
|
644
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
645
|
+
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: `
|
|
646
|
+
<ng-container>
|
|
647
|
+
@if (item().children?.length) {
|
|
648
|
+
<a
|
|
649
|
+
(click)="itemClick()"
|
|
650
|
+
(mouseenter)="onMouseEnter()"
|
|
651
|
+
#parentLink
|
|
652
|
+
tabindex="0"
|
|
653
|
+
>
|
|
654
|
+
@if (item().icon) {
|
|
655
|
+
<lib-icon
|
|
656
|
+
[icon]="item().icon!"
|
|
657
|
+
[iconType]="item().iconType"
|
|
658
|
+
class="layout-menuitem-icon"
|
|
659
|
+
/>
|
|
534
660
|
}
|
|
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>
|
|
661
|
+
<span class="layout-menuitem-text">{{
|
|
662
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
663
|
+
}}</span>
|
|
664
|
+
@if (item().children) {
|
|
665
|
+
@if (layoutService.isTopbar()) {
|
|
666
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
667
|
+
} @else {
|
|
668
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
669
|
+
}
|
|
555
670
|
}
|
|
556
|
-
</
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
<
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
[
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
671
|
+
</a>
|
|
672
|
+
}
|
|
673
|
+
@if (item().routerLink && !item().children?.length) {
|
|
674
|
+
<a
|
|
675
|
+
(click)="itemClick()"
|
|
676
|
+
[routerLink]="routerLink()"
|
|
677
|
+
routerLinkActive="active-route"
|
|
678
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
679
|
+
tabindex="0"
|
|
680
|
+
>
|
|
681
|
+
@if (item().icon) {
|
|
682
|
+
<lib-icon
|
|
683
|
+
[icon]="item().icon!"
|
|
684
|
+
[iconType]="item().iconType"
|
|
685
|
+
class="layout-menuitem-icon"
|
|
686
|
+
/>
|
|
687
|
+
}
|
|
688
|
+
<span class="layout-menuitem-text">{{
|
|
689
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
574
690
|
}}</span>
|
|
575
|
-
|
|
576
|
-
[ngModel]="menuMode()"
|
|
577
|
-
(ngModelChange)="onMenuModeChange($event)"
|
|
578
|
-
[options]="menuModeOptions()"
|
|
579
|
-
[allowEmpty]="false"
|
|
580
|
-
size="small"
|
|
581
|
-
/>
|
|
582
|
-
</div>
|
|
691
|
+
</a>
|
|
583
692
|
}
|
|
584
|
-
|
|
585
|
-
|
|
693
|
+
@if (item().children) {
|
|
694
|
+
<ul
|
|
695
|
+
(mouseenter)="onMouseEnter()"
|
|
696
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
697
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
698
|
+
>
|
|
699
|
+
@for (
|
|
700
|
+
child of item().children;
|
|
701
|
+
track 'menu' + child.id + '' + i;
|
|
702
|
+
let i = $index
|
|
703
|
+
) {
|
|
704
|
+
<li
|
|
705
|
+
app-menuitem
|
|
706
|
+
[item]="child"
|
|
707
|
+
[index]="i"
|
|
708
|
+
[parentKey]="key()"
|
|
709
|
+
></li>
|
|
710
|
+
}
|
|
711
|
+
</ul>
|
|
712
|
+
}
|
|
713
|
+
</ng-container>
|
|
714
|
+
`, 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
715
|
}
|
|
587
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
716
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
588
717
|
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>
|
|
718
|
+
args: [{ selector: '[app-menuitem]', imports: [UiModule, IconComponent, RouterModule, TranslatePipe], template: `
|
|
719
|
+
<ng-container>
|
|
720
|
+
@if (item().children?.length) {
|
|
721
|
+
<a
|
|
722
|
+
(click)="itemClick()"
|
|
723
|
+
(mouseenter)="onMouseEnter()"
|
|
724
|
+
#parentLink
|
|
725
|
+
tabindex="0"
|
|
726
|
+
>
|
|
727
|
+
@if (item().icon) {
|
|
728
|
+
<lib-icon
|
|
729
|
+
[icon]="item().icon!"
|
|
730
|
+
[iconType]="item().iconType"
|
|
731
|
+
class="layout-menuitem-icon"
|
|
732
|
+
/>
|
|
633
733
|
}
|
|
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
|
|
734
|
+
<span class="layout-menuitem-text">{{
|
|
735
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
652
736
|
}}</span>
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
</
|
|
737
|
+
@if (item().children) {
|
|
738
|
+
@if (layoutService.isTopbar()) {
|
|
739
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
740
|
+
} @else {
|
|
741
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
</a>
|
|
661
745
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
746
|
+
@if (item().routerLink && !item().children?.length) {
|
|
747
|
+
<a
|
|
748
|
+
(click)="itemClick()"
|
|
749
|
+
[routerLink]="routerLink()"
|
|
750
|
+
routerLinkActive="active-route"
|
|
751
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
752
|
+
tabindex="0"
|
|
753
|
+
>
|
|
754
|
+
@if (item().icon) {
|
|
755
|
+
<lib-icon
|
|
756
|
+
[icon]="item().icon!"
|
|
757
|
+
[iconType]="item().iconType"
|
|
758
|
+
class="layout-menuitem-icon"
|
|
759
|
+
/>
|
|
760
|
+
}
|
|
761
|
+
<span class="layout-menuitem-text">{{
|
|
762
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
763
|
+
}}</span>
|
|
764
|
+
</a>
|
|
765
|
+
}
|
|
766
|
+
@if (item().children) {
|
|
767
|
+
<ul
|
|
768
|
+
(mouseenter)="onMouseEnter()"
|
|
769
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
770
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
771
|
+
>
|
|
772
|
+
@for (
|
|
773
|
+
child of item().children;
|
|
774
|
+
track 'menu' + child.id + '' + i;
|
|
775
|
+
let i = $index
|
|
776
|
+
) {
|
|
777
|
+
<li
|
|
778
|
+
app-menuitem
|
|
779
|
+
[item]="child"
|
|
780
|
+
[index]="i"
|
|
781
|
+
[parentKey]="key()"
|
|
782
|
+
></li>
|
|
783
|
+
}
|
|
784
|
+
</ul>
|
|
785
|
+
}
|
|
786
|
+
</ng-container>
|
|
787
|
+
`, host: {
|
|
788
|
+
'[class.active-menuitem]': 'active()',
|
|
789
|
+
}, 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"] }]
|
|
790
|
+
}], 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 }] }] } });
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Main menu component that displays filtered menu items.
|
|
794
|
+
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
795
|
+
* The filtering happens internally in LayoutService via computed signal.
|
|
796
|
+
*/
|
|
797
|
+
class AppMenu {
|
|
798
|
+
layoutService = inject(LayoutService);
|
|
799
|
+
/**
|
|
800
|
+
* Filtered menu items from layout service.
|
|
801
|
+
* This signal is computed in LayoutService and automatically updates when:
|
|
802
|
+
* - Raw menu changes (setMenu called)
|
|
803
|
+
* - Permission state changes (user permissions updated)
|
|
804
|
+
*/
|
|
805
|
+
menuItems = this.layoutService.menu;
|
|
806
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
807
|
+
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">
|
|
808
|
+
<ul>
|
|
809
|
+
@for (
|
|
810
|
+
item of menuItems();
|
|
811
|
+
track 'menu' + item.id + '' + i;
|
|
812
|
+
let i = $index
|
|
813
|
+
) {
|
|
814
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
815
|
+
}
|
|
816
|
+
</ul>
|
|
817
|
+
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
818
|
+
}
|
|
819
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, decorators: [{
|
|
820
|
+
type: Component,
|
|
821
|
+
args: [{
|
|
822
|
+
selector: 'app-menu',
|
|
823
|
+
imports: [AppMenuitem, RouterModule],
|
|
824
|
+
template: `<div class="layout-menu">
|
|
825
|
+
<ul>
|
|
826
|
+
@for (
|
|
827
|
+
item of menuItems();
|
|
828
|
+
track 'menu' + item.id + '' + i;
|
|
829
|
+
let i = $index
|
|
830
|
+
) {
|
|
831
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
832
|
+
}
|
|
833
|
+
</ul>
|
|
834
|
+
</div>`,
|
|
835
|
+
}]
|
|
836
|
+
}] });
|
|
837
|
+
|
|
838
|
+
class AppSidebar {
|
|
839
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
840
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
|
|
841
|
+
<div class="layout-sidebar">
|
|
842
|
+
<app-menu />
|
|
843
|
+
</div>
|
|
844
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
|
|
845
|
+
}
|
|
846
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, decorators: [{
|
|
847
|
+
type: Component,
|
|
848
|
+
args: [{
|
|
849
|
+
selector: 'app-sidebar',
|
|
850
|
+
imports: [AppMenu],
|
|
851
|
+
template: `
|
|
852
|
+
<div class="layout-sidebar">
|
|
853
|
+
<app-menu />
|
|
854
|
+
</div>
|
|
855
|
+
`,
|
|
856
|
+
}]
|
|
857
|
+
}] });
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Home link showing the active company logo and name.
|
|
861
|
+
* Every skin renders this same markup; sizing and colour come from the bar it sits in.
|
|
862
|
+
*/
|
|
863
|
+
class AppBrandLogo {
|
|
864
|
+
layoutService = inject(LayoutService);
|
|
865
|
+
companyName = this.layoutService.companyName;
|
|
866
|
+
displayLogo = this.layoutService.displayLogo;
|
|
867
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppBrandLogo, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
868
|
+
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: `
|
|
869
|
+
<a class="layout-brand-logo" routerLink="/">
|
|
870
|
+
@if (displayLogo()) {
|
|
871
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
872
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
873
|
+
} @else {
|
|
874
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
<span>{{ companyName() }}</span>
|
|
878
|
+
</a>
|
|
879
|
+
`, 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"] }] });
|
|
880
|
+
}
|
|
881
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppBrandLogo, decorators: [{
|
|
882
|
+
type: Component,
|
|
883
|
+
args: [{
|
|
884
|
+
selector: 'app-brand-logo',
|
|
885
|
+
imports: [RouterModule, InlineSvgComponent],
|
|
886
|
+
host: { style: 'display: contents' },
|
|
887
|
+
template: `
|
|
888
|
+
<a class="layout-brand-logo" routerLink="/">
|
|
889
|
+
@if (displayLogo()) {
|
|
890
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
891
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
892
|
+
} @else {
|
|
893
|
+
<img [src]="displayLogo()" [alt]="companyName()" class="logo-image" />
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
<span>{{ companyName() }}</span>
|
|
897
|
+
</a>
|
|
898
|
+
`,
|
|
899
|
+
}]
|
|
900
|
+
}] });
|
|
901
|
+
|
|
671
902
|
const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
|
|
672
903
|
const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
|
|
673
904
|
const LAYOUT_NOTIFICATION_BELL = new InjectionToken('LAYOUT_NOTIFICATION_BELL');
|
|
@@ -677,444 +908,301 @@ const LAYOUT_SEARCHBAR = new InjectionToken('LAYOUT_SEARCHBAR');
|
|
|
677
908
|
const LAYOUT_IS_RTL = new InjectionToken('LAYOUT_IS_RTL');
|
|
678
909
|
const LAYOUT_SEARCH_ADAPTER = new InjectionToken('LAYOUT_SEARCH_ADAPTER');
|
|
679
910
|
|
|
680
|
-
class
|
|
911
|
+
class AppConfigurator {
|
|
912
|
+
router = inject(Router);
|
|
913
|
+
translateAdapter = inject(TranslateService, { optional: true });
|
|
914
|
+
platformId = inject(PLATFORM_ID);
|
|
681
915
|
layoutService = inject(LayoutService);
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
916
|
+
presets = PRESET_NAMES;
|
|
917
|
+
showMenuModeButton = signal(!this.router.url.includes('auth'), /* @ts-ignore */
|
|
918
|
+
...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : /* istanbul ignore next */ []));
|
|
919
|
+
menuModeOptions = computed(() => {
|
|
920
|
+
const options = [
|
|
921
|
+
{ label: this.translate('layout.configurator.menu.mode.static'), value: 'static' },
|
|
922
|
+
{ label: this.translate('layout.configurator.menu.mode.overlay'), value: 'overlay' },
|
|
923
|
+
];
|
|
924
|
+
// Topbar mode renders a horizontal nav, which only the classic skin provides.
|
|
925
|
+
if (this.layoutService.isDesktop() && !this.layoutService.isModernLayout()) {
|
|
926
|
+
options.push({ label: this.translate('layout.configurator.menu.mode.topbar'), value: 'topbar' });
|
|
927
|
+
}
|
|
928
|
+
return options;
|
|
929
|
+
}, /* @ts-ignore */
|
|
930
|
+
...(ngDevMode ? [{ debugName: "menuModeOptions" }] : /* istanbul ignore next */ []));
|
|
931
|
+
layoutThemeOptions = computed(() => [
|
|
932
|
+
{ label: this.translate('layout.configurator.layout.theme.classic'), value: 'classic' },
|
|
933
|
+
{ label: this.translate('layout.configurator.layout.theme.modern'), value: 'modern' },
|
|
934
|
+
], /* @ts-ignore */
|
|
935
|
+
...(ngDevMode ? [{ debugName: "layoutThemeOptions" }] : /* istanbul ignore next */ []));
|
|
936
|
+
constructor() {
|
|
937
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
938
|
+
effect(() => {
|
|
939
|
+
const config = this.layoutService.layoutConfig();
|
|
940
|
+
applyTheme({
|
|
941
|
+
preset: (config.preset ?? 'Aura'),
|
|
942
|
+
primary: config.primary ?? 'violet',
|
|
943
|
+
surface: config.surface ?? null,
|
|
944
|
+
darkTheme: config.darkTheme ?? false,
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
}
|
|
690
948
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
>
|
|
718
|
-
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
719
|
-
</button>
|
|
720
|
-
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
721
|
-
</div>
|
|
722
|
-
}
|
|
723
|
-
<div class="relative">
|
|
724
|
-
<button
|
|
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" }] });
|
|
741
|
-
}
|
|
742
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
|
|
743
|
-
type: Component,
|
|
744
|
-
args: [{ selector: 'app-floating-configurator', imports: [NgComponentOutlet, UiModule, AppConfigurator], template: `
|
|
745
|
-
<div
|
|
746
|
-
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
747
|
-
>
|
|
748
|
-
<button
|
|
749
|
-
fButton
|
|
750
|
-
type="button"
|
|
751
|
-
(click)="toggleDarkMode()"
|
|
752
|
-
[rounded]="true"
|
|
753
|
-
severity="secondary"
|
|
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>
|
|
774
|
-
}
|
|
775
|
-
<div class="relative">
|
|
776
|
-
<button
|
|
777
|
-
fButton
|
|
778
|
-
fStyleClass="@next"
|
|
779
|
-
enterFromClass="hidden"
|
|
780
|
-
enterActiveClass="animate-scalein"
|
|
781
|
-
leaveToClass="hidden"
|
|
782
|
-
leaveActiveClass="animate-fadeout"
|
|
783
|
-
[hideOnOutsideClick]="true"
|
|
784
|
-
type="button"
|
|
785
|
-
rounded
|
|
786
|
-
>
|
|
787
|
-
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
788
|
-
</button>
|
|
789
|
-
<app-configurator />
|
|
790
|
-
</div>
|
|
791
|
-
</div>
|
|
792
|
-
`, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"] }]
|
|
793
|
-
}] });
|
|
794
|
-
|
|
795
|
-
class AppMenuitem {
|
|
796
|
-
// Signal inputs
|
|
797
|
-
item = input.required(/* @ts-ignore */
|
|
798
|
-
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
799
|
-
index = input.required(/* @ts-ignore */
|
|
800
|
-
...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
801
|
-
parentKey = input('', /* @ts-ignore */
|
|
802
|
-
...(ngDevMode ? [{ debugName: "parentKey" }] : /* istanbul ignore next */ []));
|
|
803
|
-
// Injected services
|
|
804
|
-
router = inject(Router);
|
|
805
|
-
layoutService = inject(LayoutService);
|
|
806
|
-
destroyRef = inject(DestroyRef);
|
|
807
|
-
// View references
|
|
808
|
-
parentLink = viewChild('parentLink', /* @ts-ignore */
|
|
809
|
-
...(ngDevMode ? [{ debugName: "parentLink" }] : /* istanbul ignore next */ []));
|
|
810
|
-
// State signals - private writable + public readonly pattern
|
|
811
|
-
_active = signal(false, /* @ts-ignore */
|
|
812
|
-
...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
813
|
-
active = this._active.asReadonly();
|
|
814
|
-
// Computed signals
|
|
815
|
-
key = computed(() => {
|
|
816
|
-
const parent = this.parentKey();
|
|
817
|
-
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
818
|
-
}, /* @ts-ignore */
|
|
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
|
-
};
|
|
949
|
+
surfaces = Object.entries(SURFACE_PALETTES).map(([name, pal]) => ({
|
|
950
|
+
name,
|
|
951
|
+
palette: pal,
|
|
952
|
+
}));
|
|
953
|
+
selectedPrimaryColor = computed(() => this.layoutService.layoutConfig().primary, /* @ts-ignore */
|
|
954
|
+
...(ngDevMode ? [{ debugName: "selectedPrimaryColor" }] : /* istanbul ignore next */ []));
|
|
955
|
+
selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface, /* @ts-ignore */
|
|
956
|
+
...(ngDevMode ? [{ debugName: "selectedSurfaceColor" }] : /* istanbul ignore next */ []));
|
|
957
|
+
isSurfaceSelected(surfaceName) {
|
|
958
|
+
const sel = this.selectedSurfaceColor();
|
|
959
|
+
if (sel)
|
|
960
|
+
return sel === surfaceName;
|
|
961
|
+
const isDark = this.layoutService.layoutConfig().darkTheme;
|
|
962
|
+
return isDark ? surfaceName === 'zinc' : surfaceName === 'slate';
|
|
963
|
+
}
|
|
964
|
+
selectedPreset = computed(() => this.layoutService.layoutConfig().preset, /* @ts-ignore */
|
|
965
|
+
...(ngDevMode ? [{ debugName: "selectedPreset" }] : /* istanbul ignore next */ []));
|
|
966
|
+
menuMode = computed(() => this.layoutService.layoutConfig().menuMode, /* @ts-ignore */
|
|
967
|
+
...(ngDevMode ? [{ debugName: "menuMode" }] : /* istanbul ignore next */ []));
|
|
968
|
+
layoutTheme = this.layoutService.layoutTheme;
|
|
969
|
+
primaryColors = computed(() => {
|
|
970
|
+
const palettes = [{ name: 'noir', palette: {} }];
|
|
971
|
+
for (const [name, pal] of Object.entries(PRIMARY_PALETTES)) {
|
|
972
|
+
palettes.push({ name, palette: pal });
|
|
973
|
+
}
|
|
974
|
+
return palettes;
|
|
835
975
|
}, /* @ts-ignore */
|
|
836
|
-
...(ngDevMode ? [{ debugName: "
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
value.key?.startsWith(currentKey + '-'));
|
|
847
|
-
}
|
|
848
|
-
else if (value.key !== currentKey &&
|
|
849
|
-
!value.key?.startsWith(currentKey + '-')) {
|
|
850
|
-
this._active.set(false);
|
|
851
|
-
}
|
|
852
|
-
});
|
|
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
|
-
});
|
|
976
|
+
...(ngDevMode ? [{ debugName: "primaryColors" }] : /* istanbul ignore next */ []));
|
|
977
|
+
updateColors(event, type, color) {
|
|
978
|
+
if (type === 'primary') {
|
|
979
|
+
this.layoutService.updateLayoutConfig({ primary: color.name });
|
|
980
|
+
}
|
|
981
|
+
else if (type === 'surface') {
|
|
982
|
+
this.layoutService.updateLayoutConfig({ surface: color.name });
|
|
983
|
+
}
|
|
984
|
+
this._applyColorChange(type, color);
|
|
985
|
+
event.stopPropagation();
|
|
879
986
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
987
|
+
_applyColorChange(type, color) {
|
|
988
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
989
|
+
if (type === 'primary') {
|
|
990
|
+
const primary = color.name ?? 'violet';
|
|
991
|
+
// NOIR_PRIMARY, not a copy of a surface palette: its shades are references to
|
|
992
|
+
// the active surface ramp, which is what lets applyPrimaryPalette recognise the
|
|
993
|
+
// neutral and derive the same tokens a preset switch to Nora would.
|
|
994
|
+
const pal = primary === 'noir' ? NOIR_PRIMARY : PRIMARY_PALETTES[primary];
|
|
995
|
+
if (pal)
|
|
996
|
+
applyPrimaryPalette(pal, isDark);
|
|
997
|
+
}
|
|
998
|
+
else if (type === 'surface') {
|
|
999
|
+
const surfacePal = SURFACE_PALETTES[color.name ?? 'slate'];
|
|
1000
|
+
if (surfacePal)
|
|
1001
|
+
applySurfacePalette(surfacePal);
|
|
883
1002
|
}
|
|
884
1003
|
}
|
|
885
|
-
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
if (!hostEl)
|
|
892
|
-
return;
|
|
893
|
-
// Position submenu to the left of the parent link and above it
|
|
894
|
-
const left = rect.left;
|
|
895
|
-
const top = rect.top + 30; // 8px gap above the menu item
|
|
896
|
-
hostEl.style.setProperty('--submenu-left', `${left}px`);
|
|
897
|
-
hostEl.style.setProperty('--submenu-top', `${top}px`);
|
|
1004
|
+
onPresetChange(event) {
|
|
1005
|
+
const primary = PRESETS[event]?.signaturePrimary ?? 'violet';
|
|
1006
|
+
this.layoutService.updateLayoutConfig({ preset: event, primary });
|
|
1007
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
1008
|
+
const surface = this.layoutService.layoutConfig().surface ?? null;
|
|
1009
|
+
applyTheme({ preset: event, primary, surface, darkTheme: isDark });
|
|
898
1010
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (!link.length)
|
|
902
|
-
return;
|
|
903
|
-
// Use 'exact' for root path to avoid matching all routes
|
|
904
|
-
// Use 'subset' for all other paths to match child routes
|
|
905
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
906
|
-
const active = isActive(link[0], this.router, {
|
|
907
|
-
paths: pathsMatch,
|
|
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
|
-
});
|
|
917
|
-
}
|
|
1011
|
+
onMenuModeChange(event) {
|
|
1012
|
+
this.layoutService.updateLayoutConfig({ menuMode: event });
|
|
918
1013
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1014
|
+
onLayoutThemeChange(event) {
|
|
1015
|
+
const menuMode = this.layoutService.layoutConfig().menuMode;
|
|
1016
|
+
this.layoutService.updateLayoutConfig({
|
|
1017
|
+
layoutTheme: event,
|
|
1018
|
+
// Modern skin has no horizontal nav — fall back to the static sidebar.
|
|
1019
|
+
menuMode: event === 'modern' && menuMode === 'topbar' ? 'static' : menuMode,
|
|
1020
|
+
});
|
|
924
1021
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
>
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
</a>
|
|
953
|
-
}
|
|
954
|
-
@if (item().routerLink && !item().children?.length) {
|
|
955
|
-
<a
|
|
956
|
-
(click)="itemClick()"
|
|
957
|
-
[routerLink]="routerLink()"
|
|
958
|
-
routerLinkActive="active-route"
|
|
959
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
960
|
-
tabindex="0"
|
|
961
|
-
>
|
|
962
|
-
@if (item().icon) {
|
|
963
|
-
<lib-icon
|
|
964
|
-
[icon]="item().icon!"
|
|
965
|
-
[iconType]="item().iconType"
|
|
966
|
-
class="layout-menuitem-icon"
|
|
967
|
-
/>
|
|
968
|
-
}
|
|
969
|
-
<span class="layout-menuitem-text">{{
|
|
970
|
-
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
971
|
-
}}</span>
|
|
972
|
-
</a>
|
|
973
|
-
}
|
|
974
|
-
@if (item().children) {
|
|
975
|
-
<ul
|
|
976
|
-
(mouseenter)="onMouseEnter()"
|
|
977
|
-
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
978
|
-
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
979
|
-
>
|
|
980
|
-
@for (
|
|
981
|
-
child of item().children;
|
|
982
|
-
track 'menu' + child.id + '' + i;
|
|
983
|
-
let i = $index
|
|
984
|
-
) {
|
|
985
|
-
<li
|
|
986
|
-
app-menuitem
|
|
987
|
-
[item]="child"
|
|
988
|
-
[index]="i"
|
|
989
|
-
[parentKey]="key()"
|
|
990
|
-
></li>
|
|
1022
|
+
translate(key) {
|
|
1023
|
+
return this.translateAdapter?.translate(key) ?? key;
|
|
1024
|
+
}
|
|
1025
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1026
|
+
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: `
|
|
1027
|
+
<div class="flex flex-col gap-4">
|
|
1028
|
+
<div>
|
|
1029
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1030
|
+
'layout.configurator.primary' | translate
|
|
1031
|
+
}}</span>
|
|
1032
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1033
|
+
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
1034
|
+
<button
|
|
1035
|
+
type="button"
|
|
1036
|
+
[title]="primaryColor.name"
|
|
1037
|
+
(click)="updateColors($event, 'primary', primaryColor)"
|
|
1038
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
1039
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1040
|
+
[style.background-color]="
|
|
1041
|
+
primaryColor?.name === 'noir'
|
|
1042
|
+
? 'var(--f-text-color)'
|
|
1043
|
+
: primaryColor?.palette?.['500']
|
|
1044
|
+
"
|
|
1045
|
+
></button>
|
|
991
1046
|
}
|
|
992
|
-
</
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
class="layout-menuitem-icon"
|
|
1013
|
-
/>
|
|
1047
|
+
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
<div>
|
|
1050
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1051
|
+
'layout.configurator.surface' | translate
|
|
1052
|
+
}}</span>
|
|
1053
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1054
|
+
@for (surface of surfaces; track surface.name) {
|
|
1055
|
+
<button
|
|
1056
|
+
type="button"
|
|
1057
|
+
[title]="surface.name"
|
|
1058
|
+
(click)="updateColors($event, 'surface', surface)"
|
|
1059
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
1060
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1061
|
+
[style.background-color]="
|
|
1062
|
+
surface?.name === 'noir'
|
|
1063
|
+
? 'var(--f-text-color)'
|
|
1064
|
+
: surface?.palette?.['500']
|
|
1065
|
+
"
|
|
1066
|
+
></button>
|
|
1014
1067
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
<div class="flex flex-col gap-2">
|
|
1071
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1072
|
+
'layout.configurator.presets' | translate
|
|
1073
|
+
}}</span>
|
|
1074
|
+
<f-selectbutton
|
|
1075
|
+
[options]="presets"
|
|
1076
|
+
[ngModel]="selectedPreset()"
|
|
1077
|
+
(ngModelChange)="onPresetChange($event)"
|
|
1078
|
+
[allowEmpty]="false"
|
|
1079
|
+
size="small"
|
|
1080
|
+
/>
|
|
1081
|
+
</div>
|
|
1082
|
+
@if (showMenuModeButton()) {
|
|
1083
|
+
<div class="flex flex-col gap-2">
|
|
1084
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1085
|
+
'layout.configurator.layout.theme' | translate
|
|
1017
1086
|
}}</span>
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
</
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
(click)="itemClick()"
|
|
1030
|
-
[routerLink]="routerLink()"
|
|
1031
|
-
routerLinkActive="active-route"
|
|
1032
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1033
|
-
tabindex="0"
|
|
1034
|
-
>
|
|
1035
|
-
@if (item().icon) {
|
|
1036
|
-
<lib-icon
|
|
1037
|
-
[icon]="item().icon!"
|
|
1038
|
-
[iconType]="item().iconType"
|
|
1039
|
-
class="layout-menuitem-icon"
|
|
1040
|
-
/>
|
|
1041
|
-
}
|
|
1042
|
-
<span class="layout-menuitem-text">{{
|
|
1043
|
-
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1087
|
+
<f-selectbutton
|
|
1088
|
+
[ngModel]="layoutTheme()"
|
|
1089
|
+
(ngModelChange)="onLayoutThemeChange($event)"
|
|
1090
|
+
[options]="layoutThemeOptions()"
|
|
1091
|
+
[allowEmpty]="false"
|
|
1092
|
+
size="small"
|
|
1093
|
+
/>
|
|
1094
|
+
</div>
|
|
1095
|
+
<div class="flex flex-col gap-2">
|
|
1096
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1097
|
+
'layout.configurator.menu.mode' | translate
|
|
1044
1098
|
}}</span>
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
>
|
|
1053
|
-
@for (
|
|
1054
|
-
child of item().children;
|
|
1055
|
-
track 'menu' + child.id + '' + i;
|
|
1056
|
-
let i = $index
|
|
1057
|
-
) {
|
|
1058
|
-
<li
|
|
1059
|
-
app-menuitem
|
|
1060
|
-
[item]="child"
|
|
1061
|
-
[index]="i"
|
|
1062
|
-
[parentKey]="key()"
|
|
1063
|
-
></li>
|
|
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>
|
|
1099
|
+
<f-selectbutton
|
|
1100
|
+
[ngModel]="menuMode()"
|
|
1101
|
+
(ngModelChange)="onMenuModeChange($event)"
|
|
1102
|
+
[options]="menuModeOptions()"
|
|
1103
|
+
[allowEmpty]="false"
|
|
1104
|
+
size="small"
|
|
1105
|
+
/>
|
|
1106
|
+
</div>
|
|
1096
1107
|
}
|
|
1097
|
-
</
|
|
1098
|
-
|
|
1108
|
+
</div>
|
|
1109
|
+
`, 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" }] });
|
|
1099
1110
|
}
|
|
1100
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
1111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, decorators: [{
|
|
1101
1112
|
type: Component,
|
|
1102
1113
|
args: [{
|
|
1103
|
-
selector: 'app-
|
|
1104
|
-
imports: [
|
|
1105
|
-
template:
|
|
1106
|
-
<
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1114
|
+
selector: 'app-configurator',
|
|
1115
|
+
imports: [UiModule, FormsModule, TranslatePipe],
|
|
1116
|
+
template: `
|
|
1117
|
+
<div class="flex flex-col gap-4">
|
|
1118
|
+
<div>
|
|
1119
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1120
|
+
'layout.configurator.primary' | translate
|
|
1121
|
+
}}</span>
|
|
1122
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1123
|
+
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
1124
|
+
<button
|
|
1125
|
+
type="button"
|
|
1126
|
+
[title]="primaryColor.name"
|
|
1127
|
+
(click)="updateColors($event, 'primary', primaryColor)"
|
|
1128
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
1129
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1130
|
+
[style.background-color]="
|
|
1131
|
+
primaryColor?.name === 'noir'
|
|
1132
|
+
? 'var(--f-text-color)'
|
|
1133
|
+
: primaryColor?.palette?.['500']
|
|
1134
|
+
"
|
|
1135
|
+
></button>
|
|
1136
|
+
}
|
|
1137
|
+
</div>
|
|
1138
|
+
</div>
|
|
1139
|
+
<div>
|
|
1140
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1141
|
+
'layout.configurator.surface' | translate
|
|
1142
|
+
}}</span>
|
|
1143
|
+
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
1144
|
+
@for (surface of surfaces; track surface.name) {
|
|
1145
|
+
<button
|
|
1146
|
+
type="button"
|
|
1147
|
+
[title]="surface.name"
|
|
1148
|
+
(click)="updateColors($event, 'surface', surface)"
|
|
1149
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
1150
|
+
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
1151
|
+
[style.background-color]="
|
|
1152
|
+
surface?.name === 'noir'
|
|
1153
|
+
? 'var(--f-text-color)'
|
|
1154
|
+
: surface?.palette?.['500']
|
|
1155
|
+
"
|
|
1156
|
+
></button>
|
|
1157
|
+
}
|
|
1158
|
+
</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
<div class="flex flex-col gap-2">
|
|
1161
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1162
|
+
'layout.configurator.presets' | translate
|
|
1163
|
+
}}</span>
|
|
1164
|
+
<f-selectbutton
|
|
1165
|
+
[options]="presets"
|
|
1166
|
+
[ngModel]="selectedPreset()"
|
|
1167
|
+
(ngModelChange)="onPresetChange($event)"
|
|
1168
|
+
[allowEmpty]="false"
|
|
1169
|
+
size="small"
|
|
1170
|
+
/>
|
|
1171
|
+
</div>
|
|
1172
|
+
@if (showMenuModeButton()) {
|
|
1173
|
+
<div class="flex flex-col gap-2">
|
|
1174
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1175
|
+
'layout.configurator.layout.theme' | translate
|
|
1176
|
+
}}</span>
|
|
1177
|
+
<f-selectbutton
|
|
1178
|
+
[ngModel]="layoutTheme()"
|
|
1179
|
+
(ngModelChange)="onLayoutThemeChange($event)"
|
|
1180
|
+
[options]="layoutThemeOptions()"
|
|
1181
|
+
[allowEmpty]="false"
|
|
1182
|
+
size="small"
|
|
1183
|
+
/>
|
|
1184
|
+
</div>
|
|
1185
|
+
<div class="flex flex-col gap-2">
|
|
1186
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1187
|
+
'layout.configurator.menu.mode' | translate
|
|
1188
|
+
}}</span>
|
|
1189
|
+
<f-selectbutton
|
|
1190
|
+
[ngModel]="menuMode()"
|
|
1191
|
+
(ngModelChange)="onMenuModeChange($event)"
|
|
1192
|
+
[options]="menuModeOptions()"
|
|
1193
|
+
[allowEmpty]="false"
|
|
1194
|
+
size="small"
|
|
1195
|
+
/>
|
|
1196
|
+
</div>
|
|
1113
1197
|
}
|
|
1114
|
-
</
|
|
1115
|
-
|
|
1198
|
+
</div>
|
|
1199
|
+
`,
|
|
1200
|
+
host: {
|
|
1201
|
+
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',
|
|
1202
|
+
style: 'background-color: var(--surface-overlay); inset-inline-end: 0;',
|
|
1203
|
+
},
|
|
1116
1204
|
}]
|
|
1117
|
-
}] });
|
|
1205
|
+
}], ctorParameters: () => [] });
|
|
1118
1206
|
|
|
1119
1207
|
/**
|
|
1120
1208
|
* View Transitions API type definitions
|
|
@@ -1137,7 +1225,7 @@ class AppCompanyBranchSelector {
|
|
|
1137
1225
|
isActive = this._isActive.asReadonly();
|
|
1138
1226
|
constructor() {
|
|
1139
1227
|
fromEvent(this.document, 'click')
|
|
1140
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1228
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1141
1229
|
.subscribe((event) => {
|
|
1142
1230
|
const target = event.target;
|
|
1143
1231
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1355,7 +1443,7 @@ class AppCompanyBranchSelector {
|
|
|
1355
1443
|
</div>
|
|
1356
1444
|
</div>
|
|
1357
1445
|
</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" }] });
|
|
1446
|
+
`, 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
1447
|
}
|
|
1360
1448
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
|
|
1361
1449
|
type: Component,
|
|
@@ -1464,7 +1552,7 @@ class AppLauncher {
|
|
|
1464
1552
|
isActive = this._isActive.asReadonly();
|
|
1465
1553
|
constructor() {
|
|
1466
1554
|
fromEvent(this.document, 'click')
|
|
1467
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1555
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1468
1556
|
.subscribe((event) => {
|
|
1469
1557
|
const target = event.target;
|
|
1470
1558
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1617,7 +1705,7 @@ class AppProfile {
|
|
|
1617
1705
|
isActive = this._isActive.asReadonly();
|
|
1618
1706
|
constructor() {
|
|
1619
1707
|
fromEvent(this.document, 'click')
|
|
1620
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1708
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this.isActive()))
|
|
1621
1709
|
.subscribe((event) => {
|
|
1622
1710
|
const target = event.target;
|
|
1623
1711
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1757,7 +1845,7 @@ class AppProfile {
|
|
|
1757
1845
|
</div>
|
|
1758
1846
|
</div>
|
|
1759
1847
|
</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
|
|
1848
|
+
`, 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
1849
|
}
|
|
1762
1850
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppProfile, decorators: [{
|
|
1763
1851
|
type: Component,
|
|
@@ -2086,7 +2174,7 @@ class AppSearchbar {
|
|
|
2086
2174
|
}
|
|
2087
2175
|
}
|
|
2088
2176
|
</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" }] });
|
|
2177
|
+
`, 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
2178
|
}
|
|
2091
2179
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSearchbar, decorators: [{
|
|
2092
2180
|
type: Component,
|
|
@@ -2194,10 +2282,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2194
2282
|
}
|
|
2195
2283
|
}
|
|
2196
2284
|
</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"] }]
|
|
2285
|
+
`, 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
2286
|
}], ctorParameters: () => [], propDecorators: { searchWrapper: [{ type: i0.ViewChild, args: ['searchWrapper', { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
2199
2287
|
|
|
2200
|
-
|
|
2288
|
+
/**
|
|
2289
|
+
* Trailing action rail of the topbar — search, theme controls and the widget menu.
|
|
2290
|
+
* Skins own the bar around it; the rail itself is identical in every skin, and
|
|
2291
|
+
* `display: contents` on the host keeps it a direct flex child of the bar.
|
|
2292
|
+
*/
|
|
2293
|
+
class AppTopbarActions {
|
|
2201
2294
|
appConfig = inject(APP_CONFIG);
|
|
2202
2295
|
document = inject(DOCUMENT$1);
|
|
2203
2296
|
destroyRef = inject(DestroyRef);
|
|
@@ -2211,111 +2304,34 @@ class AppTopbar {
|
|
|
2211
2304
|
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
2212
2305
|
optional: true,
|
|
2213
2306
|
});
|
|
2214
|
-
hasSearchAdapter = computed(() => !!this.searchAdapter, /* @ts-ignore */
|
|
2215
|
-
...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : /* istanbul ignore next */ []));
|
|
2216
2307
|
configContainer = viewChild('configContainer', /* @ts-ignore */
|
|
2217
2308
|
...(ngDevMode ? [{ debugName: "configContainer" }] : /* istanbul ignore next */ []));
|
|
2218
|
-
|
|
2219
|
-
...(ngDevMode ? [{ debugName: "
|
|
2220
|
-
|
|
2221
|
-
|
|
2309
|
+
_configPanelOpen = signal(false, /* @ts-ignore */
|
|
2310
|
+
...(ngDevMode ? [{ debugName: "_configPanelOpen" }] : /* istanbul ignore next */ []));
|
|
2311
|
+
configPanelOpen = this._configPanelOpen.asReadonly();
|
|
2312
|
+
hasSearchAdapter = computed(() => !!this.searchAdapter, /* @ts-ignore */
|
|
2313
|
+
...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : /* istanbul ignore next */ []));
|
|
2314
|
+
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
2222
2315
|
constructor() {
|
|
2223
2316
|
fromEvent(this.document, 'click')
|
|
2224
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.
|
|
2317
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter$1(() => this._configPanelOpen()))
|
|
2225
2318
|
.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));
|
|
2319
|
+
const configEl = this.configContainer()?.nativeElement;
|
|
2320
|
+
if (configEl && !configEl.contains(event.target)) {
|
|
2321
|
+
this._configPanelOpen.set(false);
|
|
2234
2322
|
}
|
|
2235
2323
|
});
|
|
2236
2324
|
}
|
|
2237
|
-
companyName = this.layoutService.companyName;
|
|
2238
|
-
displayLogo = this.layoutService.displayLogo;
|
|
2239
|
-
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
2240
2325
|
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`);
|
|
2326
|
+
this.layoutService.updateLayoutConfig({
|
|
2327
|
+
darkTheme: !this.layoutService.layoutConfig().darkTheme,
|
|
2265
2328
|
});
|
|
2266
2329
|
}
|
|
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>
|
|
2330
|
+
toggleConfigPanel() {
|
|
2331
|
+
this._configPanelOpen.update((open) => !open);
|
|
2317
2332
|
}
|
|
2318
|
-
|
|
2333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbarActions, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2334
|
+
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
2335
|
<div class="layout-topbar-actions">
|
|
2320
2336
|
@if (hasSearchAdapter()) {
|
|
2321
2337
|
<app-searchbar />
|
|
@@ -2327,12 +2343,15 @@ class AppTopbar {
|
|
|
2327
2343
|
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
2328
2344
|
(click)="toggleDarkMode()"
|
|
2329
2345
|
>
|
|
2330
|
-
<ui-icon
|
|
2346
|
+
<ui-icon
|
|
2347
|
+
[name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"
|
|
2348
|
+
></ui-icon>
|
|
2331
2349
|
</button>
|
|
2332
2350
|
<div class="relative" #configContainer>
|
|
2333
2351
|
<button
|
|
2352
|
+
type="button"
|
|
2334
2353
|
class="layout-topbar-action"
|
|
2335
|
-
[class.layout-topbar-action-highlight]="
|
|
2354
|
+
[class.layout-topbar-action-highlight]="configPanelOpen()"
|
|
2336
2355
|
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2337
2356
|
fStyleClass="@next"
|
|
2338
2357
|
enterFromClass="hidden"
|
|
@@ -2340,7 +2359,7 @@ class AppTopbar {
|
|
|
2340
2359
|
leaveToClass="hidden"
|
|
2341
2360
|
leaveActiveClass="animate-fadeout"
|
|
2342
2361
|
[hideOnOutsideClick]="true"
|
|
2343
|
-
(click)="
|
|
2362
|
+
(click)="toggleConfigPanel()"
|
|
2344
2363
|
>
|
|
2345
2364
|
<ui-icon name="palette"></ui-icon>
|
|
2346
2365
|
</button>
|
|
@@ -2349,6 +2368,7 @@ class AppTopbar {
|
|
|
2349
2368
|
</div>
|
|
2350
2369
|
|
|
2351
2370
|
<button
|
|
2371
|
+
type="button"
|
|
2352
2372
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
2353
2373
|
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2354
2374
|
fStyleClass="@next"
|
|
@@ -2379,73 +2399,24 @@ class AppTopbar {
|
|
|
2379
2399
|
</div>
|
|
2380
2400
|
</div>
|
|
2381
2401
|
</div>
|
|
2382
|
-
|
|
2402
|
+
`, 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
2403
|
}
|
|
2384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
2404
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbarActions, decorators: [{
|
|
2385
2405
|
type: Component,
|
|
2386
2406
|
args: [{
|
|
2387
|
-
selector: 'app-topbar',
|
|
2388
|
-
imports: [
|
|
2407
|
+
selector: 'app-topbar-actions',
|
|
2408
|
+
imports: [
|
|
2409
|
+
UiModule,
|
|
2389
2410
|
NgComponentOutlet,
|
|
2390
2411
|
TranslatePipe,
|
|
2391
|
-
InlineSvgComponent,
|
|
2392
2412
|
AppConfigurator,
|
|
2393
|
-
AppMenu,
|
|
2394
2413
|
AppProfile,
|
|
2395
2414
|
AppCompanyBranchSelector,
|
|
2396
2415
|
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
|
-
|
|
2416
|
+
AppSearchbar,
|
|
2417
|
+
],
|
|
2418
|
+
host: { style: 'display: contents' },
|
|
2419
|
+
template: `
|
|
2449
2420
|
<div class="layout-topbar-actions">
|
|
2450
2421
|
@if (hasSearchAdapter()) {
|
|
2451
2422
|
<app-searchbar />
|
|
@@ -2457,12 +2428,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2457
2428
|
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
2458
2429
|
(click)="toggleDarkMode()"
|
|
2459
2430
|
>
|
|
2460
|
-
<ui-icon
|
|
2431
|
+
<ui-icon
|
|
2432
|
+
[name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"
|
|
2433
|
+
></ui-icon>
|
|
2461
2434
|
</button>
|
|
2462
2435
|
<div class="relative" #configContainer>
|
|
2463
2436
|
<button
|
|
2437
|
+
type="button"
|
|
2464
2438
|
class="layout-topbar-action"
|
|
2465
|
-
[class.layout-topbar-action-highlight]="
|
|
2439
|
+
[class.layout-topbar-action-highlight]="configPanelOpen()"
|
|
2466
2440
|
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2467
2441
|
fStyleClass="@next"
|
|
2468
2442
|
enterFromClass="hidden"
|
|
@@ -2470,7 +2444,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2470
2444
|
leaveToClass="hidden"
|
|
2471
2445
|
leaveActiveClass="animate-fadeout"
|
|
2472
2446
|
[hideOnOutsideClick]="true"
|
|
2473
|
-
(click)="
|
|
2447
|
+
(click)="toggleConfigPanel()"
|
|
2474
2448
|
>
|
|
2475
2449
|
<ui-icon name="palette"></ui-icon>
|
|
2476
2450
|
</button>
|
|
@@ -2479,6 +2453,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2479
2453
|
</div>
|
|
2480
2454
|
|
|
2481
2455
|
<button
|
|
2456
|
+
type="button"
|
|
2482
2457
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
2483
2458
|
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2484
2459
|
fStyleClass="@next"
|
|
@@ -2509,112 +2484,185 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2509
2484
|
</div>
|
|
2510
2485
|
</div>
|
|
2511
2486
|
</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
2487
|
`,
|
|
2534
2488
|
}]
|
|
2535
|
-
}] });
|
|
2489
|
+
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }] } });
|
|
2536
2490
|
|
|
2537
|
-
class
|
|
2538
|
-
destroyRef = inject(DestroyRef);
|
|
2539
|
-
document = inject(DOCUMENT);
|
|
2491
|
+
class AppTopbar {
|
|
2540
2492
|
layoutService = inject(LayoutService);
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2493
|
+
navContainer = viewChild('navContainer', /* @ts-ignore */
|
|
2494
|
+
...(ngDevMode ? [{ debugName: "navContainer" }] : /* istanbul ignore next */ []));
|
|
2495
|
+
_scrollThumb = signal({ width: 0, offset: 0 }, /* @ts-ignore */
|
|
2496
|
+
...(ngDevMode ? [{ debugName: "_scrollThumb" }] : /* istanbul ignore next */ []));
|
|
2497
|
+
scrollThumb = this._scrollThumb.asReadonly();
|
|
2544
2498
|
constructor() {
|
|
2545
|
-
|
|
2546
|
-
.
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
if (this.isOutsideClicked(event)) {
|
|
2551
|
-
this.hideMenu();
|
|
2552
|
-
}
|
|
2553
|
-
});
|
|
2554
|
-
}
|
|
2555
|
-
if (this.layoutService.layoutState().staticMenuMobileActive) {
|
|
2556
|
-
this.blockBodyScroll();
|
|
2499
|
+
effect((onCleanup) => {
|
|
2500
|
+
const nav = this.navContainer();
|
|
2501
|
+
if (!nav) {
|
|
2502
|
+
this._scrollThumb.set({ width: 0, offset: 0 });
|
|
2503
|
+
return;
|
|
2557
2504
|
}
|
|
2505
|
+
const subscription = fromEvent(nav.nativeElement, 'scroll').subscribe(() => {
|
|
2506
|
+
this.recalculateHoveredSubmenu(nav.nativeElement);
|
|
2507
|
+
this.syncScrollThumb();
|
|
2508
|
+
});
|
|
2509
|
+
const resizeObserver = typeof ResizeObserver === 'undefined'
|
|
2510
|
+
? null
|
|
2511
|
+
: new ResizeObserver(() => this.syncScrollThumb());
|
|
2512
|
+
resizeObserver?.observe(nav.nativeElement);
|
|
2513
|
+
onCleanup(() => {
|
|
2514
|
+
subscription.unsubscribe();
|
|
2515
|
+
resizeObserver?.disconnect();
|
|
2516
|
+
});
|
|
2558
2517
|
});
|
|
2559
|
-
this.router.events
|
|
2560
|
-
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
2561
|
-
.subscribe(() => this.hideMenu());
|
|
2562
2518
|
}
|
|
2563
|
-
|
|
2564
|
-
const
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
const
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2519
|
+
syncScrollThumb() {
|
|
2520
|
+
const nav = this.navContainer()?.nativeElement;
|
|
2521
|
+
if (!nav)
|
|
2522
|
+
return;
|
|
2523
|
+
const { clientWidth, scrollWidth, scrollLeft } = nav;
|
|
2524
|
+
if (scrollWidth <= clientWidth + 1) {
|
|
2525
|
+
this._scrollThumb.set({ width: 0, offset: 0 });
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
const ratio = clientWidth / scrollWidth;
|
|
2529
|
+
this._scrollThumb.set({
|
|
2530
|
+
width: Math.round(clientWidth * ratio),
|
|
2531
|
+
offset: Math.round(Math.abs(scrollLeft) * ratio),
|
|
2532
|
+
});
|
|
2574
2533
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2534
|
+
/**
|
|
2535
|
+
* Horizontal-nav submenus are `position: fixed`, so scrolling the nav strip leaves
|
|
2536
|
+
* an open submenu behind — re-anchor it to the item it belongs to.
|
|
2537
|
+
*/
|
|
2538
|
+
recalculateHoveredSubmenu(navEl) {
|
|
2539
|
+
navEl.querySelectorAll('li:hover').forEach((li) => {
|
|
2540
|
+
const link = li.querySelector(':scope > a');
|
|
2541
|
+
if (!link)
|
|
2542
|
+
return;
|
|
2543
|
+
const rect = link.getBoundingClientRect();
|
|
2544
|
+
li.style.setProperty('--submenu-left', `${rect.left - 12}px`);
|
|
2545
|
+
li.style.setProperty('--submenu-top', `${rect.bottom + 12}px`);
|
|
2580
2546
|
});
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2547
|
+
}
|
|
2548
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2549
|
+
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">
|
|
2550
|
+
<div
|
|
2551
|
+
class="layout-topbar-logo-container"
|
|
2552
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2553
|
+
>
|
|
2554
|
+
@if (layoutService.isMobile()) {
|
|
2555
|
+
<button
|
|
2556
|
+
type="button"
|
|
2557
|
+
class="layout-menu-button layout-topbar-action"
|
|
2558
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2559
|
+
(click)="layoutService.onMenuToggle()"
|
|
2560
|
+
>
|
|
2561
|
+
<ui-icon name="bars"></ui-icon>
|
|
2562
|
+
</button>
|
|
2563
|
+
}
|
|
2564
|
+
<app-brand-logo />
|
|
2565
|
+
@if (layoutService.isDesktop() && !layoutService.isTopbar()) {
|
|
2566
|
+
<button
|
|
2567
|
+
type="button"
|
|
2568
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2569
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2570
|
+
(click)="layoutService.onMenuToggle()"
|
|
2571
|
+
>
|
|
2572
|
+
<ui-icon name="bars"></ui-icon>
|
|
2573
|
+
</button>
|
|
2574
|
+
}
|
|
2575
|
+
</div>
|
|
2576
|
+
|
|
2577
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2578
|
+
<div class="layout-topbar-nav" (pointerenter)="syncScrollThumb()">
|
|
2579
|
+
<div #navContainer class="layout-topbar-nav-horizontal">
|
|
2580
|
+
<app-menu />
|
|
2581
|
+
</div>
|
|
2582
|
+
@if (scrollThumb().width > 0) {
|
|
2583
|
+
<span
|
|
2584
|
+
aria-hidden="true"
|
|
2585
|
+
class="layout-topbar-nav-scroll-thumb"
|
|
2586
|
+
[style.width.px]="scrollThumb().width"
|
|
2587
|
+
[style.marginInlineStart.px]="scrollThumb().offset"
|
|
2588
|
+
></span>
|
|
2589
|
+
}
|
|
2590
|
+
</div>
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
<app-topbar-actions />
|
|
2594
|
+
</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" }] });
|
|
2595
|
+
}
|
|
2596
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, decorators: [{
|
|
2597
|
+
type: Component,
|
|
2598
|
+
args: [{
|
|
2599
|
+
selector: 'app-topbar',
|
|
2600
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppMenu, AppTopbarActions],
|
|
2601
|
+
template: `<div class="layout-topbar">
|
|
2602
|
+
<div
|
|
2603
|
+
class="layout-topbar-logo-container"
|
|
2604
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2605
|
+
>
|
|
2606
|
+
@if (layoutService.isMobile()) {
|
|
2607
|
+
<button
|
|
2608
|
+
type="button"
|
|
2609
|
+
class="layout-menu-button layout-topbar-action"
|
|
2610
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2611
|
+
(click)="layoutService.onMenuToggle()"
|
|
2612
|
+
>
|
|
2613
|
+
<ui-icon name="bars"></ui-icon>
|
|
2614
|
+
</button>
|
|
2615
|
+
}
|
|
2616
|
+
<app-brand-logo />
|
|
2617
|
+
@if (layoutService.isDesktop() && !layoutService.isTopbar()) {
|
|
2618
|
+
<button
|
|
2619
|
+
type="button"
|
|
2620
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2621
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2622
|
+
(click)="layoutService.onMenuToggle()"
|
|
2623
|
+
>
|
|
2624
|
+
<ui-icon name="bars"></ui-icon>
|
|
2625
|
+
</button>
|
|
2626
|
+
}
|
|
2627
|
+
</div>
|
|
2628
|
+
|
|
2629
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2630
|
+
<div class="layout-topbar-nav" (pointerenter)="syncScrollThumb()">
|
|
2631
|
+
<div #navContainer class="layout-topbar-nav-horizontal">
|
|
2632
|
+
<app-menu />
|
|
2633
|
+
</div>
|
|
2634
|
+
@if (scrollThumb().width > 0) {
|
|
2635
|
+
<span
|
|
2636
|
+
aria-hidden="true"
|
|
2637
|
+
class="layout-topbar-nav-scroll-thumb"
|
|
2638
|
+
[style.width.px]="scrollThumb().width"
|
|
2639
|
+
[style.marginInlineStart.px]="scrollThumb().offset"
|
|
2640
|
+
></span>
|
|
2584
2641
|
}
|
|
2585
|
-
|
|
2586
|
-
}
|
|
2587
|
-
blockBodyScroll() {
|
|
2588
|
-
this.document.body.classList.add('blocked-scroll');
|
|
2589
|
-
}
|
|
2590
|
-
unblockBodyScroll() {
|
|
2591
|
-
this.document.body.classList.remove('blocked-scroll');
|
|
2642
|
+
</div>
|
|
2592
2643
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2644
|
+
|
|
2645
|
+
<app-topbar-actions />
|
|
2646
|
+
</div>`,
|
|
2647
|
+
}]
|
|
2648
|
+
}], ctorParameters: () => [], propDecorators: { navContainer: [{ type: i0.ViewChild, args: ['navContainer', { isSignal: true }] }] } });
|
|
2649
|
+
|
|
2650
|
+
class AppClassicLayout extends LayoutShellBase {
|
|
2595
2651
|
isStatic = computed(() => this.layoutService.layoutConfig().menuMode === 'static', /* @ts-ignore */
|
|
2596
2652
|
...(ngDevMode ? [{ debugName: "isStatic" }] : /* istanbul ignore next */ []));
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
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 */
|
|
2606
|
-
...(ngDevMode ? [{ debugName: "isTopbarNavHidden" }] : /* istanbul ignore next */ []));
|
|
2607
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2608
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
|
|
2653
|
+
outsideClickSelectors() {
|
|
2654
|
+
return ['.layout-sidebar', '.layout-menu-button', '.layout-topbar-nav'];
|
|
2655
|
+
}
|
|
2656
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppClassicLayout, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2657
|
+
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
2658
|
<div
|
|
2610
2659
|
class="layout-wrapper"
|
|
2611
2660
|
[class.layout-overlay]="isOverlay()"
|
|
2612
2661
|
[class.layout-static]="isStatic()"
|
|
2613
2662
|
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2614
|
-
[class.layout-static-inactive]="
|
|
2663
|
+
[class.layout-static-inactive]="isDesktopCollapsed()"
|
|
2615
2664
|
[class.layout-overlay-active]="isOverlayActive()"
|
|
2616
2665
|
[class.layout-mobile-active]="isMobileActive()"
|
|
2617
|
-
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
2618
2666
|
>
|
|
2619
2667
|
<app-topbar></app-topbar>
|
|
2620
2668
|
<app-sidebar></app-sidebar>
|
|
@@ -2626,12 +2674,12 @@ class AppLayout {
|
|
|
2626
2674
|
</div>
|
|
2627
2675
|
<div class="layout-mask animate-fadein"></div>
|
|
2628
2676
|
</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
|
|
2677
|
+
`, 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
2678
|
}
|
|
2631
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type:
|
|
2679
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppClassicLayout, decorators: [{
|
|
2632
2680
|
type: Component,
|
|
2633
2681
|
args: [{
|
|
2634
|
-
selector: 'app-layout',
|
|
2682
|
+
selector: 'app-classic-layout',
|
|
2635
2683
|
imports: [AppTopbar, AppSidebar, RouterModule, AppFooter],
|
|
2636
2684
|
template: `
|
|
2637
2685
|
<div
|
|
@@ -2639,10 +2687,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2639
2687
|
[class.layout-overlay]="isOverlay()"
|
|
2640
2688
|
[class.layout-static]="isStatic()"
|
|
2641
2689
|
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2642
|
-
[class.layout-static-inactive]="
|
|
2690
|
+
[class.layout-static-inactive]="isDesktopCollapsed()"
|
|
2643
2691
|
[class.layout-overlay-active]="isOverlayActive()"
|
|
2644
2692
|
[class.layout-mobile-active]="isMobileActive()"
|
|
2645
|
-
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
2646
2693
|
>
|
|
2647
2694
|
<app-topbar></app-topbar>
|
|
2648
2695
|
<app-sidebar></app-sidebar>
|
|
@@ -2654,10 +2701,357 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
2654
2701
|
</div>
|
|
2655
2702
|
<div class="layout-mask animate-fadein"></div>
|
|
2656
2703
|
</div>
|
|
2704
|
+
`,
|
|
2705
|
+
}]
|
|
2706
|
+
}] });
|
|
2707
|
+
|
|
2708
|
+
class AppModernSidebar {
|
|
2709
|
+
layoutService = inject(LayoutService);
|
|
2710
|
+
/**
|
|
2711
|
+
* Only the in-flow column can be collapsed from here. As an overlay or mobile
|
|
2712
|
+
* drawer the sidebar is opened by the topbar button and dismissed by the mask,
|
|
2713
|
+
* so a second toggle inside it would be dead weight next to the brand.
|
|
2714
|
+
*/
|
|
2715
|
+
showMenuButton = computed(() => !this.layoutService.isMobile() && !this.layoutService.isOverlay(), /* @ts-ignore */
|
|
2716
|
+
...(ngDevMode ? [{ debugName: "showMenuButton" }] : /* istanbul ignore next */ []));
|
|
2717
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2718
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppModernSidebar, isStandalone: true, selector: "app-modern-sidebar", ngImport: i0, template: `
|
|
2719
|
+
<aside class="layout-modern-sidebar">
|
|
2720
|
+
<div class="layout-modern-brand">
|
|
2721
|
+
<app-brand-logo />
|
|
2722
|
+
@if (showMenuButton()) {
|
|
2723
|
+
<button
|
|
2724
|
+
type="button"
|
|
2725
|
+
class="layout-modern-menu-button"
|
|
2726
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2727
|
+
(click)="layoutService.onMenuToggle()"
|
|
2728
|
+
>
|
|
2729
|
+
<ui-icon name="bars"></ui-icon>
|
|
2730
|
+
</button>
|
|
2731
|
+
}
|
|
2732
|
+
</div>
|
|
2733
|
+
|
|
2734
|
+
<app-menu />
|
|
2735
|
+
|
|
2736
|
+
<div class="layout-modern-sidebar-footer">
|
|
2737
|
+
<app-footer />
|
|
2738
|
+
</div>
|
|
2739
|
+
</aside>
|
|
2740
|
+
`, 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" }] });
|
|
2741
|
+
}
|
|
2742
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernSidebar, decorators: [{
|
|
2743
|
+
type: Component,
|
|
2744
|
+
args: [{
|
|
2745
|
+
selector: 'app-modern-sidebar',
|
|
2746
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppMenu, AppFooter],
|
|
2747
|
+
template: `
|
|
2748
|
+
<aside class="layout-modern-sidebar">
|
|
2749
|
+
<div class="layout-modern-brand">
|
|
2750
|
+
<app-brand-logo />
|
|
2751
|
+
@if (showMenuButton()) {
|
|
2752
|
+
<button
|
|
2753
|
+
type="button"
|
|
2754
|
+
class="layout-modern-menu-button"
|
|
2755
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2756
|
+
(click)="layoutService.onMenuToggle()"
|
|
2757
|
+
>
|
|
2758
|
+
<ui-icon name="bars"></ui-icon>
|
|
2759
|
+
</button>
|
|
2760
|
+
}
|
|
2761
|
+
</div>
|
|
2762
|
+
|
|
2763
|
+
<app-menu />
|
|
2764
|
+
|
|
2765
|
+
<div class="layout-modern-sidebar-footer">
|
|
2766
|
+
<app-footer />
|
|
2767
|
+
</div>
|
|
2768
|
+
</aside>
|
|
2769
|
+
`,
|
|
2770
|
+
}]
|
|
2771
|
+
}] });
|
|
2772
|
+
|
|
2773
|
+
class AppModernTopbar {
|
|
2774
|
+
destroyRef = inject(DestroyRef);
|
|
2775
|
+
router = inject(Router);
|
|
2776
|
+
layoutService = inject(LayoutService);
|
|
2777
|
+
_pageTitle = signal('', /* @ts-ignore */
|
|
2778
|
+
...(ngDevMode ? [{ debugName: "_pageTitle" }] : /* istanbul ignore next */ []));
|
|
2779
|
+
pageTitle = this._pageTitle.asReadonly();
|
|
2780
|
+
/** The sidebar is a drawer (not a column) in these modes, so the topbar owns the brand. */
|
|
2781
|
+
showBrand = computed(() => this.layoutService.isMobile() ||
|
|
2782
|
+
this.layoutService.isOverlay() ||
|
|
2783
|
+
!!this.layoutService.layoutState().staticMenuDesktopInactive, /* @ts-ignore */
|
|
2784
|
+
...(ngDevMode ? [{ debugName: "showBrand" }] : /* istanbul ignore next */ []));
|
|
2785
|
+
constructor() {
|
|
2786
|
+
this.resolvePageTitle();
|
|
2787
|
+
this.router.events
|
|
2788
|
+
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
2789
|
+
.subscribe(() => this.resolvePageTitle());
|
|
2790
|
+
}
|
|
2791
|
+
resolvePageTitle() {
|
|
2792
|
+
let route = this.router.routerState.snapshot.root;
|
|
2793
|
+
while (route.firstChild) {
|
|
2794
|
+
route = route.firstChild;
|
|
2795
|
+
}
|
|
2796
|
+
this._pageTitle.set(route.title ?? '');
|
|
2797
|
+
}
|
|
2798
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2799
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppModernTopbar, isStandalone: true, selector: "app-modern-topbar", ngImport: i0, template: `
|
|
2800
|
+
<header class="layout-modern-topbar">
|
|
2801
|
+
@if (showBrand()) {
|
|
2802
|
+
<button
|
|
2803
|
+
type="button"
|
|
2804
|
+
class="layout-modern-menu-button layout-topbar-action"
|
|
2805
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2806
|
+
(click)="layoutService.onMenuToggle()"
|
|
2807
|
+
>
|
|
2808
|
+
<ui-icon name="bars"></ui-icon>
|
|
2809
|
+
</button>
|
|
2810
|
+
|
|
2811
|
+
<app-brand-logo />
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
<span class="layout-modern-page-title">{{ pageTitle() }}</span>
|
|
2815
|
+
|
|
2816
|
+
<app-topbar-actions />
|
|
2817
|
+
</header>
|
|
2818
|
+
`, 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" }] });
|
|
2819
|
+
}
|
|
2820
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernTopbar, decorators: [{
|
|
2821
|
+
type: Component,
|
|
2822
|
+
args: [{
|
|
2823
|
+
selector: 'app-modern-topbar',
|
|
2824
|
+
imports: [UiModule, TranslatePipe, AppBrandLogo, AppTopbarActions],
|
|
2825
|
+
template: `
|
|
2826
|
+
<header class="layout-modern-topbar">
|
|
2827
|
+
@if (showBrand()) {
|
|
2828
|
+
<button
|
|
2829
|
+
type="button"
|
|
2830
|
+
class="layout-modern-menu-button layout-topbar-action"
|
|
2831
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2832
|
+
(click)="layoutService.onMenuToggle()"
|
|
2833
|
+
>
|
|
2834
|
+
<ui-icon name="bars"></ui-icon>
|
|
2835
|
+
</button>
|
|
2836
|
+
|
|
2837
|
+
<app-brand-logo />
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
<span class="layout-modern-page-title">{{ pageTitle() }}</span>
|
|
2841
|
+
|
|
2842
|
+
<app-topbar-actions />
|
|
2843
|
+
</header>
|
|
2657
2844
|
`,
|
|
2658
2845
|
}]
|
|
2659
2846
|
}], ctorParameters: () => [] });
|
|
2660
2847
|
|
|
2848
|
+
class AppModernLayout extends LayoutShellBase {
|
|
2849
|
+
content = viewChild('content', /* @ts-ignore */
|
|
2850
|
+
...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
2851
|
+
outsideClickSelectors() {
|
|
2852
|
+
return ['.layout-modern-sidebar', '.layout-modern-menu-button'];
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* The page scrolls inside the content card rather than the window, so the router's
|
|
2856
|
+
* scroll restoration never reaches it — reset it here instead.
|
|
2857
|
+
*/
|
|
2858
|
+
onNavigated() {
|
|
2859
|
+
this.content()?.nativeElement.scrollTo({ top: 0 });
|
|
2860
|
+
}
|
|
2861
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernLayout, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2862
|
+
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: `
|
|
2863
|
+
<div
|
|
2864
|
+
class="layout-modern"
|
|
2865
|
+
[class.layout-modern-overlay]="isOverlay()"
|
|
2866
|
+
[class.layout-modern-static-inactive]="isDesktopCollapsed()"
|
|
2867
|
+
[class.layout-modern-overlay-active]="isOverlayActive()"
|
|
2868
|
+
[class.layout-modern-mobile-active]="isMobileActive()"
|
|
2869
|
+
>
|
|
2870
|
+
<app-modern-sidebar />
|
|
2871
|
+
<div class="layout-modern-main">
|
|
2872
|
+
<app-modern-topbar />
|
|
2873
|
+
<div class="layout-modern-content" #content>
|
|
2874
|
+
<router-outlet></router-outlet>
|
|
2875
|
+
</div>
|
|
2876
|
+
</div>
|
|
2877
|
+
<div class="layout-modern-mask animate-fadein"></div>
|
|
2878
|
+
</div>
|
|
2879
|
+
`, 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"] }] });
|
|
2880
|
+
}
|
|
2881
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppModernLayout, decorators: [{
|
|
2882
|
+
type: Component,
|
|
2883
|
+
args: [{
|
|
2884
|
+
selector: 'app-modern-layout',
|
|
2885
|
+
imports: [AppModernTopbar, AppModernSidebar, RouterModule],
|
|
2886
|
+
template: `
|
|
2887
|
+
<div
|
|
2888
|
+
class="layout-modern"
|
|
2889
|
+
[class.layout-modern-overlay]="isOverlay()"
|
|
2890
|
+
[class.layout-modern-static-inactive]="isDesktopCollapsed()"
|
|
2891
|
+
[class.layout-modern-overlay-active]="isOverlayActive()"
|
|
2892
|
+
[class.layout-modern-mobile-active]="isMobileActive()"
|
|
2893
|
+
>
|
|
2894
|
+
<app-modern-sidebar />
|
|
2895
|
+
<div class="layout-modern-main">
|
|
2896
|
+
<app-modern-topbar />
|
|
2897
|
+
<div class="layout-modern-content" #content>
|
|
2898
|
+
<router-outlet></router-outlet>
|
|
2899
|
+
</div>
|
|
2900
|
+
</div>
|
|
2901
|
+
<div class="layout-modern-mask animate-fadein"></div>
|
|
2902
|
+
</div>
|
|
2903
|
+
`,
|
|
2904
|
+
}]
|
|
2905
|
+
}], propDecorators: { content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
|
|
2906
|
+
|
|
2907
|
+
/**
|
|
2908
|
+
* Layout shell entry point.
|
|
2909
|
+
* Renders the skin selected by `LayoutConfig.layoutTheme` — every skin shares
|
|
2910
|
+
* the same services, menu and topbar components, only the markup and styling differ.
|
|
2911
|
+
*/
|
|
2912
|
+
class AppLayout {
|
|
2913
|
+
layoutService = inject(LayoutService);
|
|
2914
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2915
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
|
|
2916
|
+
@if (layoutService.isModernLayout()) {
|
|
2917
|
+
<app-modern-layout />
|
|
2918
|
+
} @else {
|
|
2919
|
+
<app-classic-layout />
|
|
2920
|
+
}
|
|
2921
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppClassicLayout, selector: "app-classic-layout" }, { kind: "component", type: AppModernLayout, selector: "app-modern-layout" }] });
|
|
2922
|
+
}
|
|
2923
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, decorators: [{
|
|
2924
|
+
type: Component,
|
|
2925
|
+
args: [{
|
|
2926
|
+
selector: 'app-layout',
|
|
2927
|
+
imports: [AppClassicLayout, AppModernLayout],
|
|
2928
|
+
template: `
|
|
2929
|
+
@if (layoutService.isModernLayout()) {
|
|
2930
|
+
<app-modern-layout />
|
|
2931
|
+
} @else {
|
|
2932
|
+
<app-classic-layout />
|
|
2933
|
+
}
|
|
2934
|
+
`,
|
|
2935
|
+
}]
|
|
2936
|
+
}] });
|
|
2937
|
+
|
|
2938
|
+
class AppFloatingConfigurator {
|
|
2939
|
+
layoutService = inject(LayoutService);
|
|
2940
|
+
languageSelectorPanel = inject(LAYOUT_LANGUAGE_SELECTOR_PANEL, {
|
|
2941
|
+
optional: true,
|
|
2942
|
+
});
|
|
2943
|
+
isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, /* @ts-ignore */
|
|
2944
|
+
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
2945
|
+
toggleDarkMode() {
|
|
2946
|
+
const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
|
|
2947
|
+
this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
|
|
2948
|
+
}
|
|
2949
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2950
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
|
|
2951
|
+
<div
|
|
2952
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
2953
|
+
>
|
|
2954
|
+
<button
|
|
2955
|
+
fButton
|
|
2956
|
+
type="button"
|
|
2957
|
+
(click)="toggleDarkMode()"
|
|
2958
|
+
[rounded]="true"
|
|
2959
|
+
severity="secondary"
|
|
2960
|
+
>
|
|
2961
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
2962
|
+
</button>
|
|
2963
|
+
@if (languageSelectorPanel) {
|
|
2964
|
+
<div class="relative">
|
|
2965
|
+
<button
|
|
2966
|
+
fButton
|
|
2967
|
+
fStyleClass="@next"
|
|
2968
|
+
enterFromClass="hidden"
|
|
2969
|
+
enterActiveClass="animate-scalein"
|
|
2970
|
+
leaveToClass="hidden"
|
|
2971
|
+
leaveActiveClass="animate-fadeout"
|
|
2972
|
+
[hideOnOutsideClick]="true"
|
|
2973
|
+
type="button"
|
|
2974
|
+
rounded
|
|
2975
|
+
>
|
|
2976
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
2977
|
+
</button>
|
|
2978
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
2979
|
+
</div>
|
|
2980
|
+
}
|
|
2981
|
+
<div class="relative">
|
|
2982
|
+
<button
|
|
2983
|
+
fButton
|
|
2984
|
+
fStyleClass="@next"
|
|
2985
|
+
enterFromClass="hidden"
|
|
2986
|
+
enterActiveClass="animate-scalein"
|
|
2987
|
+
leaveToClass="hidden"
|
|
2988
|
+
leaveActiveClass="animate-fadeout"
|
|
2989
|
+
[hideOnOutsideClick]="true"
|
|
2990
|
+
type="button"
|
|
2991
|
+
rounded
|
|
2992
|
+
>
|
|
2993
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
2994
|
+
</button>
|
|
2995
|
+
<app-configurator />
|
|
2996
|
+
</div>
|
|
2997
|
+
</div>
|
|
2998
|
+
`, 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" }] });
|
|
2999
|
+
}
|
|
3000
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
|
|
3001
|
+
type: Component,
|
|
3002
|
+
args: [{ selector: 'app-floating-configurator', imports: [NgComponentOutlet, UiModule, AppConfigurator], template: `
|
|
3003
|
+
<div
|
|
3004
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
3005
|
+
>
|
|
3006
|
+
<button
|
|
3007
|
+
fButton
|
|
3008
|
+
type="button"
|
|
3009
|
+
(click)="toggleDarkMode()"
|
|
3010
|
+
[rounded]="true"
|
|
3011
|
+
severity="secondary"
|
|
3012
|
+
>
|
|
3013
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
3014
|
+
</button>
|
|
3015
|
+
@if (languageSelectorPanel) {
|
|
3016
|
+
<div class="relative">
|
|
3017
|
+
<button
|
|
3018
|
+
fButton
|
|
3019
|
+
fStyleClass="@next"
|
|
3020
|
+
enterFromClass="hidden"
|
|
3021
|
+
enterActiveClass="animate-scalein"
|
|
3022
|
+
leaveToClass="hidden"
|
|
3023
|
+
leaveActiveClass="animate-fadeout"
|
|
3024
|
+
[hideOnOutsideClick]="true"
|
|
3025
|
+
type="button"
|
|
3026
|
+
rounded
|
|
3027
|
+
>
|
|
3028
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
3029
|
+
</button>
|
|
3030
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
3031
|
+
</div>
|
|
3032
|
+
}
|
|
3033
|
+
<div class="relative">
|
|
3034
|
+
<button
|
|
3035
|
+
fButton
|
|
3036
|
+
fStyleClass="@next"
|
|
3037
|
+
enterFromClass="hidden"
|
|
3038
|
+
enterActiveClass="animate-scalein"
|
|
3039
|
+
leaveToClass="hidden"
|
|
3040
|
+
leaveActiveClass="animate-fadeout"
|
|
3041
|
+
[hideOnOutsideClick]="true"
|
|
3042
|
+
type="button"
|
|
3043
|
+
rounded
|
|
3044
|
+
>
|
|
3045
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
3046
|
+
</button>
|
|
3047
|
+
<app-configurator />
|
|
3048
|
+
</div>
|
|
3049
|
+
</div>
|
|
3050
|
+
`, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"] }]
|
|
3051
|
+
}] });
|
|
3052
|
+
|
|
3053
|
+
// Shell entry point (renders the configured layout skin)
|
|
3054
|
+
|
|
2661
3055
|
function buildPreset(presetName, primary) {
|
|
2662
3056
|
return { preset: presetName, primary };
|
|
2663
3057
|
}
|
|
@@ -2673,7 +3067,7 @@ const GreenTheme = {
|
|
|
2673
3067
|
};
|
|
2674
3068
|
|
|
2675
3069
|
const LAYOUT_MESSAGES = {
|
|
2676
|
-
// components/top-bar/app.profile.ts
|
|
3070
|
+
// components/shared/top-bar/app.profile.ts
|
|
2677
3071
|
'layout.profile.title': 'Profile',
|
|
2678
3072
|
'layout.profile.profile.picture.alt': 'Profile picture',
|
|
2679
3073
|
'layout.profile.copy.sign.up.link': 'Copy SignUp Link',
|
|
@@ -2682,11 +3076,11 @@ const LAYOUT_MESSAGES = {
|
|
|
2682
3076
|
'layout.profile.link.copied': 'Link Copied',
|
|
2683
3077
|
'layout.profile.sign.up.link.copied': 'Sign Up Link copied.',
|
|
2684
3078
|
'layout.profile.failed.copy.sign.up.link': 'Failed to copy signup link.',
|
|
2685
|
-
// components/top-bar/app.launcher.ts
|
|
3079
|
+
// components/shared/top-bar/app.launcher.ts
|
|
2686
3080
|
'layout.launcher.apps': 'Apps',
|
|
2687
3081
|
'layout.launcher.applications': 'Applications',
|
|
2688
3082
|
'layout.launcher.no.apps': 'No apps available',
|
|
2689
|
-
// components/top-bar/app.company-branch-selector.ts
|
|
3083
|
+
// components/shared/top-bar/app.company-branch-selector.ts
|
|
2690
3084
|
'layout.company.branch.selector.title': 'Switch Company & Branch',
|
|
2691
3085
|
'layout.company.branch.selector.select.company': 'Select Company',
|
|
2692
3086
|
'layout.company.branch.selector.branch': 'Branch',
|
|
@@ -2695,15 +3089,17 @@ const LAYOUT_MESSAGES = {
|
|
|
2695
3089
|
'layout.company.branch.selector.no.company': 'No Company',
|
|
2696
3090
|
'layout.company.branch.selector.branch.required': 'Branch Required',
|
|
2697
3091
|
'layout.company.branch.selector.please.select.branch': 'Please select a branch',
|
|
2698
|
-
// components/top-bar/app.searchbar.ts
|
|
3092
|
+
// components/shared/top-bar/app.searchbar.ts
|
|
2699
3093
|
'layout.topbar.search': 'Search',
|
|
2700
3094
|
'layout.topbar.search.placeholder': 'Search for content...',
|
|
2701
|
-
// components/app.topbar.ts
|
|
3095
|
+
// components/layouts/classic/app.topbar.ts + components/layouts/modern/app.modern-topbar.ts
|
|
3096
|
+
// + components/layouts/modern/app.modern-sidebar.ts
|
|
2702
3097
|
'layout.topbar.toggle.menu': 'Toggle menu',
|
|
3098
|
+
// components/shared/top-bar/app.topbar-actions.ts
|
|
2703
3099
|
'layout.topbar.toggle.dark.mode': 'Toggle dark mode',
|
|
2704
3100
|
'layout.topbar.open.theme.settings': 'Open theme settings',
|
|
2705
3101
|
'layout.topbar.more.options': 'More options',
|
|
2706
|
-
// components/app.configurator.ts
|
|
3102
|
+
// components/shared/app.configurator.ts
|
|
2707
3103
|
'layout.configurator.primary': 'Primary',
|
|
2708
3104
|
'layout.configurator.surface': 'Surface',
|
|
2709
3105
|
'layout.configurator.presets': 'Presets',
|
|
@@ -2711,7 +3107,10 @@ const LAYOUT_MESSAGES = {
|
|
|
2711
3107
|
'layout.configurator.menu.mode.static': 'Static',
|
|
2712
3108
|
'layout.configurator.menu.mode.overlay': 'Overlay',
|
|
2713
3109
|
'layout.configurator.menu.mode.topbar': 'Topbar',
|
|
2714
|
-
|
|
3110
|
+
'layout.configurator.layout.theme': 'Layout',
|
|
3111
|
+
'layout.configurator.layout.theme.classic': 'Classic',
|
|
3112
|
+
'layout.configurator.layout.theme.modern': 'Modern',
|
|
3113
|
+
// components/shared/app.footer.ts
|
|
2715
3114
|
'layout.footer.by': 'by',
|
|
2716
3115
|
// app-menu.config.ts (main app menu configuration)
|
|
2717
3116
|
'menu.dashboard': 'Dashboard',
|
|
@@ -2761,5 +3160,5 @@ const LAYOUT_MESSAGES = {
|
|
|
2761
3160
|
* Generated bundle index. Do not edit.
|
|
2762
3161
|
*/
|
|
2763
3162
|
|
|
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 };
|
|
3163
|
+
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
3164
|
//# sourceMappingURL=flusys-ng-layout.mjs.map
|