@sdcorejs/angular 20.1.3 → 20.1.5

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.
Files changed (168) hide show
  1. package/README.md +19 -14
  2. package/components/audit-diff/index.d.ts +74 -0
  3. package/components/badge/index.d.ts +1 -1
  4. package/components/breadcrumb/index.d.ts +53 -0
  5. package/components/data-state/index.d.ts +44 -0
  6. package/components/document-builder/index.d.ts +5 -5
  7. package/components/form-generic/index.d.ts +67 -104
  8. package/components/import-excel/index.d.ts +4 -14
  9. package/components/index.d.ts +4 -0
  10. package/components/job-progress/index.d.ts +38 -0
  11. package/components/mini-editor/index.d.ts +9 -9
  12. package/components/modal/index.d.ts +7 -1
  13. package/components/preview/index.d.ts +181 -28
  14. package/components/side-drawer/index.d.ts +7 -1
  15. package/components/tab/index.d.ts +9 -2
  16. package/components/table/index.d.ts +8 -7
  17. package/components/tree/index.d.ts +24 -2
  18. package/components/upload-file/index.d.ts +5 -4
  19. package/directives/index.d.ts +10 -8
  20. package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  21. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  23. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  24. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  25. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  27. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  28. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  29. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  30. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  31. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  33. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
  35. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
  37. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  39. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  40. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  41. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  43. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  45. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
  47. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  49. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
  53. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  55. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
  57. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
  59. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
  61. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  63. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  65. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
  67. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
  69. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
  71. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
  73. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
  75. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
  77. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
  79. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  81. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
  83. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
  85. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
  87. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  89. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
  91. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
  93. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
  95. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
  97. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
  99. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  100. package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
  101. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  102. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  103. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  104. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  105. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-i18n.mjs +375 -10
  107. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  109. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-modules-layout.mjs +994 -168
  111. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  113. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  114. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  115. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  116. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  117. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  119. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  121. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  122. package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
  123. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  125. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  127. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  128. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  129. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  130. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  131. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  132. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  133. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  134. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  135. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  136. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  137. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  138. package/forms/autocomplete/index.d.ts +3 -4
  139. package/forms/checkbox/index.d.ts +4 -8
  140. package/forms/date/index.d.ts +2 -2
  141. package/forms/date-range/index.d.ts +6 -7
  142. package/forms/datetime/index.d.ts +2 -2
  143. package/forms/entity-picker/index.d.ts +128 -0
  144. package/forms/index.d.ts +15 -5
  145. package/forms/input/index.d.ts +74 -11
  146. package/forms/input-number/index.d.ts +1 -1
  147. package/forms/models/index.d.ts +86 -3
  148. package/forms/radio/index.d.ts +12 -8
  149. package/forms/switch/index.d.ts +4 -7
  150. package/forms/time/index.d.ts +117 -0
  151. package/forms/time-range/index.d.ts +98 -0
  152. package/forms/tree-select/index.d.ts +76 -0
  153. package/i18n/index.d.ts +72 -2
  154. package/interceptors/index.d.ts +5 -6
  155. package/modules/layout/index.d.ts +354 -17
  156. package/package.json +49 -1
  157. package/pipes/index.d.ts +2 -2
  158. package/services/api/index.d.ts +68 -24
  159. package/services/cache/index.d.ts +52 -16
  160. package/services/confirm/index.d.ts +3 -2
  161. package/services/docx/index.d.ts +2 -3
  162. package/services/index.d.ts +4 -0
  163. package/services/loading/index.d.ts +16 -8
  164. package/services/persistence/index.d.ts +159 -0
  165. package/services/storage/index.d.ts +26 -8
  166. package/services/task/index.d.ts +104 -0
  167. package/services/unsaved-changes/index.d.ts +83 -0
  168. package/services/viewport/index.d.ts +42 -0
@@ -1,17 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, input, booleanAttribute, effect, Component, Injectable, InjectionToken, signal, Pipe, output, DestroyRef, computed, untracked, viewChild, NgModule } from '@angular/core';
3
- import * as i1$3 from '@angular/common';
4
- import { CommonModule, NgTemplateOutlet } from '@angular/common';
5
- import * as i1$5 from '@angular/router';
2
+ import { inject, ElementRef, input, booleanAttribute, effect, Component, Injectable, InjectionToken, signal, computed, Pipe, DestroyRef, viewChild, output, isSignal, ChangeDetectionStrategy, untracked, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { DOCUMENT, CommonModule, NgTemplateOutlet } from '@angular/common';
5
+ import * as i1$3 from '@angular/router';
6
6
  import { Router, NavigationEnd, RouterModule, ActivatedRoute } from '@angular/router';
7
7
  import { __decorate } from 'tslib';
8
8
  import { SdAvatar, SdTabComponent, SdButton } from '@sdcorejs/angular/components';
9
9
  import { I18nService, TranslatePipe, I18N_STORAGE_KEY, I18N_MESSAGES } from '@sdcorejs/angular/i18n';
10
- import * as i1 from '@sdcorejs/angular/services';
10
+ import { SdStorageService } from '@sdcorejs/angular/services';
11
+ import { StringUtilities, Utilities } from '@sdcorejs/utils/fns';
12
+ import { SD_VIEWPORT, SdViewportService } from '@sdcorejs/angular/services/viewport';
11
13
  import { resolveMaybeAsync } from '@sdcorejs/utils/models';
12
- import { Utilities, StringUtilities, BrowserUtilities } from '@sdcorejs/utils/fns';
13
- import * as i1$1 from '@sdcorejs/angular/modules/permission';
14
- import * as i1$4 from '@angular/material/sidenav';
14
+ import { SdPermissionService } from '@sdcorejs/angular/modules/permission';
15
+ import * as i1$1 from '@angular/material/sidenav';
15
16
  import { MatSidenavModule } from '@angular/material/sidenav';
16
17
  import { NestedTreeControl } from '@angular/cdk/tree';
17
18
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -19,15 +20,14 @@ import { FormsModule } from '@angular/forms';
19
20
  import { MatInputModule } from '@angular/material/input';
20
21
  import * as i3 from '@angular/material/tooltip';
21
22
  import { MatTooltipModule } from '@angular/material/tooltip';
22
- import * as i2$1 from '@angular/material/tree';
23
+ import * as i2 from '@angular/material/tree';
23
24
  import { MatTreeNestedDataSource, MatTreeModule } from '@angular/material/tree';
24
25
  import { SdInput, SdSuffixDefDirective } from '@sdcorejs/angular/forms';
25
26
  import { SdSafeHtmlPipe } from '@sdcorejs/angular/pipes';
26
- import * as i2 from '@angular/material/button';
27
- import { MatButtonModule } from '@angular/material/button';
28
- import * as i1$2 from '@angular/material/menu';
29
- import { MatMenuModule } from '@angular/material/menu';
30
27
  import { SdIcon } from '@sdcorejs/angular/modules/icon';
28
+ import { isObservable } from 'rxjs';
29
+ import * as i1$2 from '@angular/cdk/a11y';
30
+ import { A11yModule } from '@angular/cdk/a11y';
31
31
 
32
32
  class SdPageComponent {
33
33
  el = inject(ElementRef);
@@ -49,24 +49,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
49
49
  args: [{ selector: 'sd-page', template: "@if (!noHeader()) {\n <div class=\"c-page-header bg-white\">\n <div class=\"p-16 d-flex align-items-center w-full justify-content-between\">\n <div class=\"d-flex\">\n <ng-content select=\"[headerLeft]\">\n <div class=\"d-flex flex-column\">\n <div class=\"T20M\">{{ title() }}</div>\n @if (description()) {\n <div class=\"T14R text-secondary mt-4\">{{ description() }}</div>\n }\n </div>\n </ng-content>\n </div>\n <ng-content select=\"[headerRight]\"></ng-content>\n </div>\n </div>\n}\n\n<div class=\"c-page-wrapper px-0\">\n <div class=\"c-page-content\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [":host{height:100%;display:flex;flex-direction:column;background-color:#f9f9f9}.c-page-header{box-shadow:0 1px 2px #2f31361a;z-index:1;position:relative}.c-page-wrapper{flex:1;display:flex;flex-direction:row;width:100%;overflow-y:auto;max-height:100vh}.c-page-wrapper .c-page-content{flex:1;height:100%;width:100%}.c-page-wrapper .c-page-right{position:-webkit-sticky;position:sticky;top:0;height:100%;overflow-y:auto}\n"] }]
50
50
  }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], noHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "noHeader", required: false }] }] } });
51
51
 
52
+ function getMenuStableKey(menu, ancestors = []) {
53
+ if (!menu || typeof menu !== 'object')
54
+ return '';
55
+ if (typeof menu.id === 'string' && menu.id.trim())
56
+ return `id:${menu.id.trim()}`;
57
+ if ('path' in menu && typeof menu.path === 'string' && menu.path.trim())
58
+ return `path:${menu.path.trim()}`;
59
+ const title = typeof menu.title === 'string' ? menu.title.trim() : '';
60
+ const segments = [...ancestors, title]
61
+ .map(segment => StringUtilities.changeAliasLowerCase(segment).trim().replace(/\s+/g, '-'))
62
+ .filter(Boolean);
63
+ return segments.length ? `group:${segments.join('/')}` : '';
64
+ }
65
+ function searchMenuLeaves(menus, query) {
66
+ const leaves = flattenMenuLeaves(menus);
67
+ const normalizedQuery = query?.trim();
68
+ if (!normalizedQuery)
69
+ return leaves;
70
+ return leaves.filter(menu => [menu.title, menu.tooltipTitle].some(value => value && StringUtilities.aliasIncludes(value, normalizedQuery)));
71
+ }
72
+ function selectPrimaryMenuGroups(menus, requestedIds, maximum = 3) {
73
+ const limit = Number.isFinite(maximum) && maximum > 0 ? Math.min(3, Math.floor(maximum)) : 3;
74
+ const byId = new Map(menus.filter(menu => typeof menu.id === 'string').map(menu => [menu.id, menu]));
75
+ const selected = [];
76
+ const selectedKeys = new Set();
77
+ for (const id of requestedIds ?? []) {
78
+ const menu = byId.get(id);
79
+ const key = getMenuStableKey(menu);
80
+ if (!menu || !key || selectedKeys.has(key))
81
+ continue;
82
+ selected.push(menu);
83
+ selectedKeys.add(key);
84
+ if (selected.length === limit)
85
+ return selected;
86
+ }
87
+ for (const menu of menus) {
88
+ const key = getMenuStableKey(menu);
89
+ if (!key || selectedKeys.has(key))
90
+ continue;
91
+ selected.push(menu);
92
+ selectedKeys.add(key);
93
+ if (selected.length === limit)
94
+ break;
95
+ }
96
+ return selected;
97
+ }
98
+ function resolveMenuKeys(menus, keys) {
99
+ const menuByKey = new Map();
100
+ visitMenus(menus, [], (menu, ancestors) => {
101
+ const key = getMenuStableKey(menu, ancestors);
102
+ if (key && !menuByKey.has(key))
103
+ menuByKey.set(key, menu);
104
+ });
105
+ return [...new Set(keys)].map(key => menuByKey.get(key)).filter((menu) => !!menu);
106
+ }
107
+ function flattenMenuLeaves(menus) {
108
+ const leaves = [];
109
+ visitMenus(menus, [], menu => {
110
+ if ('path' in menu && typeof menu.path === 'string' && menu.path)
111
+ leaves.push(menu);
112
+ });
113
+ return leaves;
114
+ }
115
+ function visitMenus(menus, ancestors, visitor) {
116
+ for (const menu of menus ?? []) {
117
+ if (!menu || typeof menu !== 'object')
118
+ continue;
119
+ visitor(menu, ancestors);
120
+ if ('children' in menu && Array.isArray(menu.children)) {
121
+ const title = typeof menu.title === 'string' && menu.title.trim() ? menu.title.trim() : getMenuStableKey(menu, ancestors);
122
+ visitMenus(menu.children, title ? [...ancestors, title] : ancestors, visitor);
123
+ }
124
+ }
125
+ }
126
+
52
127
  class SdLayoutStorageService {
53
- sdStorageService;
54
- // Thông tin state của sidebar
55
- isShowSidebar;
56
- menuLockStatus;
57
- lastActiveMenuGroupId;
58
- pinnedMenuGroup;
59
- // End
60
- constructor(sdStorageService) {
61
- this.sdStorageService = sdStorageService;
62
- // State sidebar
63
- this.isShowSidebar = this.sdStorageService.create('cb07c316-ed6d-4620-9e92-53dbef6aa983');
64
- this.menuLockStatus = this.sdStorageService.create('2c2f4816-18b3-4ec3-8177-7b90bff036c3');
65
- this.lastActiveMenuGroupId = this.sdStorageService.create('2e6961d0-3380-4a94-a6a1-38837560cd96');
66
- this.pinnedMenuGroup = this.sdStorageService.create('e81da122-5dab-4250-b309-64197fb19d44');
67
- // End
68
- }
69
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, deps: [{ token: i1.SdStorageService }], target: i0.ɵɵFactoryTarget.Injectable });
128
+ #sdStorageService = inject(SdStorageService);
129
+ isShowSidebar = this.#sdStorageService.create('cb07c316-ed6d-4620-9e92-53dbef6aa983');
130
+ menuLockStatus = this.#sdStorageService.create('2c2f4816-18b3-4ec3-8177-7b90bff036c3');
131
+ lastActiveMenuGroupId = this.#sdStorageService.create('2e6961d0-3380-4a94-a6a1-38837560cd96');
132
+ pinnedMenuGroup = this.#sdStorageService.create('e81da122-5dab-4250-b309-64197fb19d44');
133
+ pinnedMenuKeys = this.#sdStorageService.create('4bdc7f80-9ff4-4fe0-908f-6c9f7fe4100a');
134
+ recentMenuKeys = this.#sdStorageService.create('fb989dc1-f6e7-4b62-a611-861a8f214a51');
135
+ versionStates = this.#sdStorageService.create('ca8e4261-fbd4-4209-af88-2dbe278c1a1b');
136
+ readPinnedMenuKeys(menus) {
137
+ const storedKeys = this.pinnedMenuKeys.has() ? this.pinnedMenuKeys.get() : this.#getLegacyPinnedKeys();
138
+ const validKeys = this.#resolveValidKeys(menus, storedKeys ?? []);
139
+ if (!this.pinnedMenuKeys.has() || !this.#sameKeys(storedKeys ?? [], validKeys)) {
140
+ this.pinnedMenuKeys.set(validKeys);
141
+ }
142
+ return validKeys;
143
+ }
144
+ writePinnedMenuKeys(keys) {
145
+ this.pinnedMenuKeys.set([...new Set(keys.filter(Boolean))]);
146
+ }
147
+ readRecentMenuKeys(menus, maximum = 5) {
148
+ const storedKeys = this.recentMenuKeys.get() ?? [];
149
+ const limit = Number.isFinite(maximum) && maximum > 0 ? Math.floor(maximum) : 5;
150
+ const validKeys = this.#resolveValidKeys(menus, storedKeys).slice(0, limit);
151
+ if (!this.#sameKeys(storedKeys, validKeys))
152
+ this.recentMenuKeys.set(validKeys);
153
+ return validKeys;
154
+ }
155
+ writeRecentMenuKeys(keys) {
156
+ this.recentMenuKeys.set([...new Set(keys.filter(Boolean))]);
157
+ }
158
+ readVersionState(version) {
159
+ return this.versionStates.get()?.[String(version)] ?? {};
160
+ }
161
+ patchVersionState(version, patch) {
162
+ const states = this.versionStates.get() ?? {};
163
+ const nextState = { ...(states[String(version)] ?? {}), ...patch };
164
+ this.versionStates.set({ ...states, [String(version)]: nextState });
165
+ return nextState;
166
+ }
167
+ #getLegacyPinnedKeys() {
168
+ const legacyGroup = this.pinnedMenuGroup.get();
169
+ if (!legacyGroup || !Array.isArray(legacyGroup.children))
170
+ return [];
171
+ return legacyGroup.children.map(menu => getMenuStableKey(menu)).filter(Boolean);
172
+ }
173
+ #resolveValidKeys(menus, keys) {
174
+ return resolveMenuKeys(menus, keys)
175
+ .map(menu => getMenuStableKey(menu))
176
+ .filter(Boolean);
177
+ }
178
+ #sameKeys(left, right) {
179
+ return left.length === right.length && left.every((key, index) => key === right[index]);
180
+ }
181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
70
182
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, providedIn: 'root' });
71
183
  }
72
184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, decorators: [{
@@ -74,10 +186,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
74
186
  args: [{
75
187
  providedIn: 'root',
76
188
  }]
77
- }], ctorParameters: () => [{ type: i1.SdStorageService }] });
189
+ }] });
78
190
 
191
+ const DEFAULT_LAYOUT_MOBILE_BREAKPOINT = 1024;
192
+ function normalizeLayoutMobileBreakpoint(value) {
193
+ if (!Number.isFinite(value) || Number(value) <= 0) {
194
+ return DEFAULT_LAYOUT_MOBILE_BREAKPOINT;
195
+ }
196
+ return Math.floor(Number(value));
197
+ }
198
+ function resolveSidebarV2Interaction(sidebar) {
199
+ return sidebar.interaction === 'hover-lock' ? 'hover-lock' : 'click';
200
+ }
201
+ function resolveSidebarV3Recent(sidebar) {
202
+ const configuredLimit = sidebar.recent?.maxItems;
203
+ const maxItems = Number.isFinite(configuredLimit) && Number(configuredLimit) > 0 ? Math.floor(Number(configuredLimit)) : 5;
204
+ return {
205
+ enabled: sidebar.recent?.enabled ?? true,
206
+ maxItems,
207
+ };
208
+ }
209
+ function normalizeSidebarConfiguration(sidebar) {
210
+ if (sidebar.version === 2) {
211
+ return {
212
+ ...sidebar,
213
+ interaction: resolveSidebarV2Interaction(sidebar),
214
+ primaryMenuIds: [...new Set((sidebar.primaryMenuIds ?? []).filter(id => typeof id === 'string' && id.trim()))].slice(0, 3),
215
+ };
216
+ }
217
+ if (sidebar.version === 3) {
218
+ return {
219
+ ...sidebar,
220
+ defaultCollapsed: sidebar.defaultCollapsed ?? false,
221
+ recent: resolveSidebarV3Recent(sidebar),
222
+ };
223
+ }
224
+ return sidebar;
225
+ }
79
226
  const SD_LAYOUT_CONFIGURATION = new InjectionToken('sd.layout.configuration');
80
227
 
228
+ const SD_LAYOUT_VIEWPORT = SD_VIEWPORT;
229
+ /** @deprecated Prefer `SdViewportService` from `@sdcorejs/angular/services/viewport`. */
230
+ class SdLayoutResponsiveService {
231
+ #viewport = inject(SdViewportService);
232
+ viewportWidth = this.#viewport.width;
233
+ isMobile(breakpoint) {
234
+ return this.viewportWidth() < normalizeLayoutMobileBreakpoint(breakpoint);
235
+ }
236
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
237
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, providedIn: 'root' });
238
+ }
239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, decorators: [{
240
+ type: Injectable,
241
+ args: [{ providedIn: 'root' }]
242
+ }] });
243
+
244
+ class SdLayoutNavigationStateService {
245
+ #storage = inject(SdLayoutStorageService);
246
+ #menus = signal([], ...(ngDevMode ? [{ debugName: "#menus" }] : []));
247
+ pinnedKeys = signal([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : []));
248
+ recentKeys = signal([], ...(ngDevMode ? [{ debugName: "recentKeys" }] : []));
249
+ pinnedMenus = computed(() => resolveMenuKeys(this.#menus(), this.pinnedKeys()), ...(ngDevMode ? [{ debugName: "pinnedMenus" }] : []));
250
+ recentMenus = computed(() => resolveMenuKeys(this.#menus(), this.recentKeys()), ...(ngDevMode ? [{ debugName: "recentMenus" }] : []));
251
+ hydrate(menus, recentMaximum = 5) {
252
+ this.#menus.set(menus);
253
+ this.pinnedKeys.set(this.#storage.readPinnedMenuKeys(menus));
254
+ this.recentKeys.set(this.#storage.readRecentMenuKeys(menus, recentMaximum));
255
+ }
256
+ togglePinned(menu) {
257
+ const key = getMenuStableKey(menu);
258
+ if (!key)
259
+ return;
260
+ const nextKeys = this.pinnedKeys().includes(key)
261
+ ? this.pinnedKeys().filter(currentKey => currentKey !== key)
262
+ : [...this.pinnedKeys(), key];
263
+ this.pinnedKeys.set(nextKeys);
264
+ this.#storage.writePinnedMenuKeys(nextKeys);
265
+ }
266
+ recordRecent(menu, configuration) {
267
+ if (!configuration.enabled)
268
+ return;
269
+ const key = getMenuStableKey(menu);
270
+ if (!key)
271
+ return;
272
+ const maximum = Number.isFinite(configuration.maxItems) && configuration.maxItems > 0 ? Math.floor(configuration.maxItems) : 5;
273
+ const nextKeys = [key, ...this.recentKeys().filter(currentKey => currentKey !== key)].slice(0, maximum);
274
+ this.recentKeys.set(nextKeys);
275
+ this.#storage.writeRecentMenuKeys(nextKeys);
276
+ }
277
+ patchVersionState(version, patch) {
278
+ this.#storage.patchVersionState(version, patch);
279
+ }
280
+ versionState(version) {
281
+ return this.#storage.readVersionState(version);
282
+ }
283
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
284
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, providedIn: 'root' });
285
+ }
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, decorators: [{
287
+ type: Injectable,
288
+ args: [{ providedIn: 'root' }]
289
+ }] });
290
+
81
291
  class SdLayoutService {
82
292
  // ==========================================
83
293
  // INJECT CONFIGURATIONS
@@ -154,10 +364,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
154
364
 
155
365
  // End
156
366
  class MenuPipe {
157
- permissionService;
158
- constructor(permissionService) {
159
- this.permissionService = permissionService;
160
- }
367
+ permissionService = inject(SdPermissionService);
368
+ constructor() { }
161
369
  transform(menus) {
162
370
  const results = [];
163
371
  const check = (menu) => {
@@ -221,6 +429,9 @@ class MenuPipe {
221
429
  return results;
222
430
  }
223
431
  #getHashIdMenu = (menu) => {
432
+ const explicitId = typeof menu.id === 'string' ? menu.id.trim() : '';
433
+ if (explicitId)
434
+ return explicitId;
224
435
  const extendedMenu = menu;
225
436
  const hashKeys = [
226
437
  'title',
@@ -241,7 +452,7 @@ class MenuPipe {
241
452
  }, {});
242
453
  return Object.keys(hashData).length > 0 ? Utilities.hash(hashData) : Utilities.generateUuid();
243
454
  };
244
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, deps: [{ token: i1$1.SdPermissionService }], target: i0.ɵɵFactoryTarget.Pipe });
455
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
245
456
  static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, isStandalone: true, name: "menu" });
246
457
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, providedIn: 'root' });
247
458
  }
@@ -255,7 +466,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
255
466
  args: [{
256
467
  providedIn: 'root',
257
468
  }]
258
- }], ctorParameters: () => [{ type: i1$1.SdPermissionService }] });
469
+ }], ctorParameters: () => [] });
259
470
 
260
471
  // End
261
472
  class MenuFocusPipe {
@@ -335,53 +546,181 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
335
546
  }]
336
547
  }] });
337
548
 
549
+ class SdLayoutUserMenuComponent {
550
+ #configuration = inject(SD_LAYOUT_CONFIGURATION, { optional: true });
551
+ #destroyRef = inject(DestroyRef);
552
+ #document = inject(DOCUMENT);
553
+ #host = inject(ElementRef);
554
+ trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
555
+ menu = viewChild('menu', ...(ngDevMode ? [{ debugName: "menu" }] : []));
556
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
557
+ signout = input(...(ngDevMode ? [undefined, { debugName: "signout" }] : []));
558
+ changePassword = input(...(ngDevMode ? [undefined, { debugName: "changePassword" }] : []));
559
+ updateProfile = input(...(ngDevMode ? [undefined, { debugName: "updateProfile" }] : []));
560
+ setting = input(...(ngDevMode ? [undefined, { debugName: "setting" }] : []));
561
+ notification = input(...(ngDevMode ? [undefined, { debugName: "notification" }] : []));
562
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : []));
563
+ presentation = input('disclosure', ...(ngDevMode ? [{ debugName: "presentation" }] : []));
564
+ opened = output();
565
+ closed = output();
566
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
567
+ #observableNotificationCount = signal(undefined, ...(ngDevMode ? [{ debugName: "#observableNotificationCount" }] : []));
568
+ avatar = computed(() => {
569
+ const user = this.userInfo();
570
+ return user.avatar || user.fullName || user.username || user.email;
571
+ }, ...(ngDevMode ? [{ debugName: "avatar" }] : []));
572
+ displayName = computed(() => this.userInfo().fullName || this.userInfo().username || this.userInfo().email || 'User', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
573
+ role = computed(() => {
574
+ const role = this.userInfo().role;
575
+ const text = role?.text?.trim();
576
+ return text ? { ...role, text } : undefined;
577
+ }, ...(ngDevMode ? [{ debugName: "role" }] : []));
578
+ signoutAction = computed(() => this.signout() ?? this.#configuration?.signout, ...(ngDevMode ? [{ debugName: "signoutAction" }] : []));
579
+ changePasswordAction = computed(() => this.changePassword() ?? this.#configuration?.changePassword, ...(ngDevMode ? [{ debugName: "changePasswordAction" }] : []));
580
+ updateProfileAction = computed(() => this.updateProfile() ?? this.#configuration?.updateProfile, ...(ngDevMode ? [{ debugName: "updateProfileAction" }] : []));
581
+ settingAction = computed(() => this.setting() ?? this.#configuration?.setting, ...(ngDevMode ? [{ debugName: "settingAction" }] : []));
582
+ notificationConfiguration = computed(() => this.notification() ?? this.#configuration?.notification, ...(ngDevMode ? [{ debugName: "notificationConfiguration" }] : []));
583
+ notificationAction = computed(() => this.notificationConfiguration()?.action, ...(ngDevMode ? [{ debugName: "notificationAction" }] : []));
584
+ notificationCount = computed(() => {
585
+ const source = this.notificationConfiguration()?.count;
586
+ const value = isSignal(source) ? source() : isObservable(source) ? this.#observableNotificationCount() : source;
587
+ return this.#normalizeNotificationCount(value);
588
+ }, ...(ngDevMode ? [{ debugName: "notificationCount" }] : []));
589
+ notificationBadge = computed(() => {
590
+ const count = this.notificationCount();
591
+ return count > 99 ? '99+' : count > 0 ? String(count) : undefined;
592
+ }, ...(ngDevMode ? [{ debugName: "notificationBadge" }] : []));
593
+ constructor() {
594
+ // Observable sources may change with component inputs; effect cleanup guarantees one active subscription.
595
+ effect(onCleanup => {
596
+ const source = this.notificationConfiguration()?.count;
597
+ this.#observableNotificationCount.set(undefined);
598
+ if (!isObservable(source))
599
+ return;
600
+ const subscription = source.subscribe(value => this.#observableNotificationCount.set(value));
601
+ onCleanup(() => subscription.unsubscribe());
602
+ });
603
+ effect(() => {
604
+ if (!this.isOpen())
605
+ return;
606
+ this.menu()?.nativeElement.querySelector('[role="menuitem"]')?.focus();
607
+ });
608
+ const onDocumentKeydown = (event) => {
609
+ if (!this.isOpen() || event.key !== 'Escape')
610
+ return;
611
+ event.preventDefault();
612
+ this.close();
613
+ };
614
+ const onDocumentPointerDown = (event) => {
615
+ if (!this.isOpen())
616
+ return;
617
+ const nodeConstructor = this.#document.defaultView?.Node;
618
+ if (!nodeConstructor || !(event.target instanceof nodeConstructor) || this.#host.nativeElement.contains(event.target))
619
+ return;
620
+ this.close(false);
621
+ };
622
+ this.#document.addEventListener('keydown', onDocumentKeydown);
623
+ this.#document.addEventListener('pointerdown', onDocumentPointerDown);
624
+ this.#destroyRef.onDestroy(() => {
625
+ this.#document.removeEventListener('keydown', onDocumentKeydown);
626
+ this.#document.removeEventListener('pointerdown', onDocumentPointerDown);
627
+ });
628
+ }
629
+ open() {
630
+ if (this.isOpen())
631
+ return;
632
+ this.isOpen.set(true);
633
+ this.opened.emit();
634
+ }
635
+ toggle() {
636
+ if (this.isOpen())
637
+ this.close(false);
638
+ else
639
+ this.open();
640
+ }
641
+ close(restoreFocus = true) {
642
+ const wasOpen = this.isOpen();
643
+ this.isOpen.set(false);
644
+ if (wasOpen)
645
+ this.closed.emit();
646
+ if (restoreFocus)
647
+ this.trigger()?.nativeElement.focus();
648
+ }
649
+ onMenuKeydown(event) {
650
+ if (event.key === 'Escape') {
651
+ event.preventDefault();
652
+ this.close();
653
+ return;
654
+ }
655
+ if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) || !(event.currentTarget instanceof HTMLElement))
656
+ return;
657
+ const actions = [...event.currentTarget.querySelectorAll('[role="menuitem"]')];
658
+ if (!actions.length)
659
+ return;
660
+ event.preventDefault();
661
+ const currentIndex = actions.findIndex(action => action === this.#document.activeElement);
662
+ const nextIndex = event.key === 'Home'
663
+ ? 0
664
+ : event.key === 'End'
665
+ ? actions.length - 1
666
+ : event.key === 'ArrowDown'
667
+ ? (currentIndex + 1) % actions.length
668
+ : (currentIndex - 1 + actions.length) % actions.length;
669
+ actions[nextIndex].focus();
670
+ }
671
+ runChangePassword() {
672
+ this.#runAction(this.changePasswordAction());
673
+ }
674
+ runSignout() {
675
+ this.#runAction(this.signoutAction());
676
+ }
677
+ runUpdateProfile() {
678
+ this.#runAction(this.updateProfileAction());
679
+ }
680
+ runSetting() {
681
+ this.#runAction(this.settingAction());
682
+ }
683
+ runNotification() {
684
+ this.#runAction(this.notificationAction());
685
+ }
686
+ #runAction(action) {
687
+ action?.();
688
+ this.close();
689
+ }
690
+ #normalizeNotificationCount(value) {
691
+ return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 0;
692
+ }
693
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
694
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutUserMenuComponent, isStandalone: true, selector: "sd-layout-user-menu", inputs: { userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, signout: { classPropertyName: "signout", publicName: "signout", isSignal: true, isRequired: false, transformFunction: null }, changePassword: { classPropertyName: "changePassword", publicName: "changePassword", isSignal: true, isRequired: false, transformFunction: null }, updateProfile: { classPropertyName: "updateProfile", publicName: "updateProfile", isSignal: true, isRequired: false, transformFunction: null }, setting: { classPropertyName: "setting", publicName: "setting", isSignal: true, isRequired: false, transformFunction: null }, notification: { classPropertyName: "notification", publicName: "notification", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, presentation: { classPropertyName: "presentation", publicName: "presentation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "menu", first: true, predicate: ["menu"], descendants: true, isSignal: true }], ngImport: i0, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"], dependencies: [{ kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
695
+ }
696
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, decorators: [{
697
+ type: Component,
698
+ args: [{ selector: 'sd-layout-user-menu', standalone: true, imports: [SdAvatar, SdIcon, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"] }]
699
+ }], ctorParameters: () => [], propDecorators: { trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], menu: [{ type: i0.ViewChild, args: ['menu', { isSignal: true }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], signout: [{ type: i0.Input, args: [{ isSignal: true, alias: "signout", required: false }] }], changePassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "changePassword", required: false }] }], updateProfile: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateProfile", required: false }] }], setting: [{ type: i0.Input, args: [{ isSignal: true, alias: "setting", required: false }] }], notification: [{ type: i0.Input, args: [{ isSignal: true, alias: "notification", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], presentation: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentation", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
700
+
338
701
  let LayoutUserComponent$1 = class LayoutUserComponent {
339
- // ==========================================
340
- // INJECT SERVICES
341
- // ==========================================
342
- #layoutConfiguration = inject(SD_LAYOUT_CONFIGURATION);
343
- // ==========================================
344
- // SIGNAL INPUTS & OUTPUTS
345
- // ==========================================
346
702
  isMobileOrTablet = input(false, ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
347
703
  isMenuLock = input(false, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
348
704
  isShowSidebar = input(false, ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : []));
349
705
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
350
- // Chuyển thành void vì các hàm emit hiện tại không truyền data
351
706
  menuClosed = output();
352
707
  menuOpened = output();
353
708
  toggleMenuLock = output();
354
- // Config actions
355
- singoutLayoutConfig = this.#layoutConfiguration.signout;
356
- changePasswordLayoutConfig = this.#layoutConfiguration?.changePassword;
357
- // ==========================================
358
- // EVENT HANDLERS
359
- // ==========================================
360
- onMenuOpened = () => {
709
+ onMenuOpened() {
361
710
  this.menuOpened.emit();
362
- };
363
- onMenuClosed = () => {
711
+ }
712
+ onMenuClosed() {
364
713
  this.menuClosed.emit();
365
- };
366
- keepOpenWhenClickInsideMenu = (event) => {
367
- event.stopPropagation();
368
- };
369
- logout = () => {
370
- this.singoutLayoutConfig();
371
- };
372
- changePassword = () => {
373
- // Dùng Optional Chaining gọi hàm cực gọn
374
- this.changePasswordLayoutConfig?.();
375
- };
376
- onToggleMenuLock = (event) => {
714
+ }
715
+ onToggleMenuLock(event) {
377
716
  this.toggleMenuLock.emit(event);
378
- };
717
+ }
379
718
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
380
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n<div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n @if (!isMobileOrTablet()) {\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n }\n</div>\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo.fullName || _userInfo.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>", styles: ["::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i1$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
719
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "<div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n @if (!isMobileOrTablet()) {\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | translate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n }\n</div>\n", styles: [".c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
381
720
  };
382
721
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent$1, decorators: [{
383
722
  type: Component,
384
- args: [{ selector: 'lib-layout-user', imports: [SdIcon, MatMenuModule, MatButtonModule, MatTooltipModule, SdAvatar, TranslatePipe], standalone: true, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n<div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n @if (!isMobileOrTablet()) {\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n }\n</div>\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo.fullName || _userInfo.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>", styles: ["::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"] }]
723
+ args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent, TranslatePipe], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n @if (!isMobileOrTablet()) {\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | translate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n }\n</div>\n", styles: [".c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"] }]
385
724
  }], propDecorators: { isMobileOrTablet: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobileOrTablet", required: false }] }], isMenuLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMenuLock", required: false }] }], isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], menuClosed: [{ type: i0.Output, args: ["menuClosed"] }], menuOpened: [{ type: i0.Output, args: ["menuOpened"] }], toggleMenuLock: [{ type: i0.Output, args: ["toggleMenuLock"] }] } });
386
725
 
387
726
  class SidebarComponent {
@@ -390,7 +729,9 @@ class SidebarComponent {
390
729
  // ==========================================
391
730
  #router = inject(Router);
392
731
  #layoutStorageService = inject(SdLayoutStorageService);
732
+ #navigationState = inject(SdLayoutNavigationStateService);
393
733
  #menuFocusPipe = inject(MenuFocusPipe);
734
+ #window = inject(DOCUMENT).defaultView;
394
735
  #destroyRef = inject(DestroyRef); // Dùng để unsubscribe RxJS tự động
395
736
  // ==========================================
396
737
  // SIGNAL INPUTS & OUTPUTS
@@ -399,6 +740,7 @@ class SidebarComponent {
399
740
  menus = input.required(...(ngDevMode ? [{ debugName: "menus" }] : []));
400
741
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
401
742
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
743
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
402
744
  expandSideBar = output();
403
745
  popupUserMenuClosed = output();
404
746
  popupUserMenuOpened = output();
@@ -406,25 +748,24 @@ class SidebarComponent {
406
748
  // ==========================================
407
749
  // STATE SIGNALS
408
750
  // ==========================================
409
- screenHeight = window.innerHeight;
410
- isMobileOrTablet = BrowserUtilities.isMobile();
411
751
  isMenuLock = signal(this.#layoutStorageService.menuLockStatus?.get() ?? true, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
412
- currentPath = signal(window.location.pathname, ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
752
+ currentPath = signal(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
413
753
  searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
414
754
  titleMenuGroup = signal('', ...(ngDevMode ? [{ debugName: "titleMenuGroup" }] : []));
415
755
  idMenuGroupActive = signal('', ...(ngDevMode ? [{ debugName: "idMenuGroupActive" }] : []));
416
756
  menusByGroup = signal([], ...(ngDevMode ? [{ debugName: "menusByGroup" }] : []));
417
757
  #hoveredMenuNodeKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredMenuNodeKey" }] : []));
418
758
  #pinIconHoverTimerId = signal(null, ...(ngDevMode ? [{ debugName: "#pinIconHoverTimerId" }] : []));
419
- pinnedMenuGroup = signal(this.#layoutStorageService.pinnedMenuGroup.get() ?? {
759
+ pinnedMenuGroup = computed(() => ({
420
760
  id: 'pinned-menu-group',
421
761
  title: 'Đã ghim',
422
- children: [],
423
- }, ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : []));
762
+ children: this.#navigationState.pinnedMenus(),
763
+ }), ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : []));
424
764
  // ==========================================
425
765
  // COMPUTED STATE
426
766
  // ==========================================
427
767
  totalMenuInMenusByGroup = computed(() => this.#getTotalMenus(this.menusByGroup()), ...(ngDevMode ? [{ debugName: "totalMenuInMenusByGroup" }] : []));
768
+ logoUrl = computed(() => this.sidebar().logoUrl?.trim() || undefined, ...(ngDevMode ? [{ debugName: "logoUrl" }] : []));
428
769
  pinnedNodeKeys = computed(() => new Set(this.pinnedMenuGroup().children?.map(m => this.#getMenuNodeKey(m)) ?? []), ...(ngDevMode ? [{ debugName: "pinnedNodeKeys" }] : []));
429
770
  isPinnedMenuGroupActive = computed(() => this.idMenuGroupActive() === this.pinnedMenuGroup().id, ...(ngDevMode ? [{ debugName: "isPinnedMenuGroupActive" }] : []));
430
771
  // ==========================================
@@ -442,6 +783,7 @@ class SidebarComponent {
442
783
  effect(() => {
443
784
  const currentMenus = this.menus();
444
785
  untracked(() => {
786
+ this.#navigationState.hydrate(currentMenus);
445
787
  const lastActiveMenuGroupId = this.#layoutStorageService.lastActiveMenuGroupId.get();
446
788
  const pinnedGroup = this.pinnedMenuGroup();
447
789
  const isPinEnabled = this.sidebar()?.pin?.enabled;
@@ -459,8 +801,9 @@ class SidebarComponent {
459
801
  #setupSubscriptions() {
460
802
  this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
461
803
  if (event instanceof NavigationEnd) {
462
- if (this.currentPath() !== window.location.pathname) {
463
- this.currentPath.set(window.location.pathname);
804
+ const currentPath = event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '';
805
+ if (this.currentPath() !== currentPath) {
806
+ this.currentPath.set(currentPath);
464
807
  this.#bindingMenuGroupByCurrentPath(this.menus());
465
808
  }
466
809
  }
@@ -486,15 +829,7 @@ class SidebarComponent {
486
829
  };
487
830
  onTogglePin = (event, node) => {
488
831
  event.stopPropagation();
489
- const key = this.#getMenuNodeKey(node);
490
- this.pinnedMenuGroup.update(group => {
491
- const children = group.children ?? [];
492
- const exists = children.some(m => this.#getMenuNodeKey(m) === key);
493
- const updatedChildren = exists ? children.filter(m => this.#getMenuNodeKey(m) !== key) : [...children, node];
494
- const updatedGroup = { id: 'pinned-menu-group', title: 'Đã ghim', children: updatedChildren };
495
- this.#layoutStorageService.pinnedMenuGroup.set(updatedGroup);
496
- return updatedGroup;
497
- });
832
+ this.#navigationState.togglePinned(node);
498
833
  };
499
834
  #onExpandAllMenuNodes = (menus) => {
500
835
  menus?.forEach(menu => {
@@ -519,12 +854,7 @@ class SidebarComponent {
519
854
  this.showSideBar.emit(this.isMenuLock());
520
855
  }
521
856
  openHomePage = () => {
522
- if (!this.isMobileOrTablet) {
523
- this.#router.navigateByUrl('/layout/home');
524
- }
525
- else {
526
- window.location.href = '/layout/home';
527
- }
857
+ this.#router.navigateByUrl('/layout/home');
528
858
  this.searchText.set('');
529
859
  this.#layoutStorageService.lastActiveMenuGroupId.set('');
530
860
  };
@@ -533,14 +863,14 @@ class SidebarComponent {
533
863
  navigate = (args) => {
534
864
  const { path, queryParams } = args;
535
865
  if (path.includes('http')) {
536
- window.open(path);
866
+ this.#window?.open(path, '_blank', 'noopener');
537
867
  return;
538
868
  }
539
869
  this.#router.navigate([path.split('?')[0]], {
540
870
  queryParams,
541
871
  state: { switchTab: true },
542
872
  });
543
- if (this.isMobileOrTablet) {
873
+ if (this.isMobile()) {
544
874
  this.#closeMenu();
545
875
  }
546
876
  };
@@ -571,7 +901,7 @@ class SidebarComponent {
571
901
  this.idMenuGroupActive.set(menuGroupNode?.id);
572
902
  this.#layoutStorageService.lastActiveMenuGroupId.set(menuGroupNode?.id || '');
573
903
  if (!this.currentPath()) {
574
- this.currentPath.set(window.location.pathname);
904
+ this.currentPath.set(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '');
575
905
  }
576
906
  };
577
907
  // ==========================================
@@ -887,20 +1217,20 @@ class SidebarComponent {
887
1217
  return str.trim();
888
1218
  };
889
1219
  #getTotalMenus = (menus) => {
890
- return menus.reduce((total, menu) => total + this.#countMenuChildrenNode(menu), 0);
1220
+ return menus.reduce((total, menu) => total + this.#countMenuNode(menu), 0);
891
1221
  };
892
- #countMenuChildrenNode = (menu) => {
1222
+ #countMenuNode = (menu) => {
893
1223
  if (!('children' in menu) || !menu.children?.length) {
894
- return 0;
1224
+ return 1;
895
1225
  }
896
- let count = menu.children.length;
1226
+ let count = 1;
897
1227
  for (const child of menu.children) {
898
- count += this.#countMenuChildrenNode(child);
1228
+ count += this.#countMenuNode(child);
899
1229
  }
900
1230
  return count;
901
1231
  };
902
1232
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input size=\"sm\" [placeholder]=\"'core.module.layout.sidebar.search' | translate\" [model]=\"searchText()\" (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node) ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\"\n (click)=\"onTogglePin($event, node)\" name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon class=\"c-menu-node-description-icon-expand\" [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2$1.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i2$1.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i2$1.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2$1.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1233
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" aria-label=\"Trang ch\u1EE7\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n (click)=\"onTogglePin($event, node)\"\n name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i2.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i2.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
904
1234
  }
905
1235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarComponent, decorators: [{
906
1236
  type: Component,
@@ -919,8 +1249,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
919
1249
  SdSuffixDefDirective,
920
1250
  LayoutUserComponent$1,
921
1251
  TranslatePipe,
922
- ], template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input size=\"sm\" [placeholder]=\"'core.module.layout.sidebar.search' | translate\" [model]=\"searchText()\" (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node) ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\"\n (click)=\"onTogglePin($event, node)\" name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon class=\"c-menu-node-description-icon-expand\" [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"] }]
923
- }], ctorParameters: () => [], propDecorators: { isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: true }] }], menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: true }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }], expandSideBar: [{ type: i0.Output, args: ["expandSideBar"] }], popupUserMenuClosed: [{ type: i0.Output, args: ["popupUserMenuClosed"] }], popupUserMenuOpened: [{ type: i0.Output, args: ["popupUserMenuOpened"] }], showSideBar: [{ type: i0.Output, args: ["showSideBar"] }] } });
1252
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" aria-label=\"Trang ch\u1EE7\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n (click)=\"onTogglePin($event, node)\"\n name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"] }]
1253
+ }], ctorParameters: () => [], propDecorators: { isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: true }] }], menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: true }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }], isMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobile", required: false }] }], expandSideBar: [{ type: i0.Output, args: ["expandSideBar"] }], popupUserMenuClosed: [{ type: i0.Output, args: ["popupUserMenuClosed"] }], popupUserMenuOpened: [{ type: i0.Output, args: ["popupUserMenuOpened"] }], showSideBar: [{ type: i0.Output, args: ["showSideBar"] }] } });
924
1254
 
925
1255
  class SidebarV1Component {
926
1256
  // ==========================================
@@ -934,14 +1264,14 @@ class SidebarV1Component {
934
1264
  menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
935
1265
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
936
1266
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1267
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
937
1268
  // ==========================================
938
1269
  // STATE SIGNALS
939
1270
  // ==========================================
940
- isMobileOrTablet = signal(BrowserUtilities.isMobile(), ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
941
1271
  // Trạng thái khóa menu (luôn mở rộng hay không)
942
1272
  isMenuLock = signal(this.#layoutStorageService.menuLockStatus?.get() ?? true, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
943
1273
  // Trạng thái hiển thị (mở/đóng) của Sidenav
944
- isShowSidebar = signal(BrowserUtilities.isMobile() ? false : (this.#layoutStorageService.menuLockStatus?.get() ?? true), ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : []));
1274
+ isShowSidebar = signal(this.isMobile() ? false : (this.#layoutStorageService.menuLockStatus?.get() ?? true), ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : []));
945
1275
  onhover = signal(false, ...(ngDevMode ? [{ debugName: "onhover" }] : []));
946
1276
  // ==========================================
947
1277
  // PRIVATE TIMERS
@@ -983,7 +1313,7 @@ class SidebarV1Component {
983
1313
  }
984
1314
  };
985
1315
  openSidebar = () => {
986
- if (!this.isMobileOrTablet()) {
1316
+ if (!this.isMobile()) {
987
1317
  return;
988
1318
  }
989
1319
  if (!this.isShowSidebar()) {
@@ -1005,7 +1335,7 @@ class SidebarV1Component {
1005
1335
  }
1006
1336
  };
1007
1337
  onExpandSideBar = () => {
1008
- if (this.isMobileOrTablet()) {
1338
+ if (this.isMobile()) {
1009
1339
  return;
1010
1340
  }
1011
1341
  if (!this.isShowSidebar()) {
@@ -1014,11 +1344,16 @@ class SidebarV1Component {
1014
1344
  }
1015
1345
  };
1016
1346
  onMouseleaveSideBar = () => {
1017
- if (this.isMobileOrTablet()) {
1347
+ if (this.isMobile()) {
1018
1348
  return;
1019
1349
  }
1020
1350
  this.#mouseLeave();
1021
1351
  };
1352
+ onSidenavOpenedChange = (opened) => {
1353
+ if (this.isMobile()) {
1354
+ this.isShowSidebar.set(opened);
1355
+ }
1356
+ };
1022
1357
  onToggle = (data) => {
1023
1358
  if (data === null) {
1024
1359
  this.#handleMouseLeaveTransition();
@@ -1027,34 +1362,29 @@ class SidebarV1Component {
1027
1362
  this.#mouseLeave();
1028
1363
  this.isShowSidebar.set(data);
1029
1364
  this.isMenuLock.set(data);
1030
- // Toggle component MatSidenav
1031
- const sidenavComp = this.sidenav();
1032
- if (sidenavComp) {
1033
- if (data) {
1034
- sidenavComp.open();
1035
- }
1036
- else {
1037
- sidenavComp.close();
1365
+ // Desktop keeps the Material drawer mounted as a 60px rail; mobile still opens and closes the overlay.
1366
+ if (this.isMobile()) {
1367
+ const sidenavComp = this.sidenav();
1368
+ if (sidenavComp) {
1369
+ if (data) {
1370
+ sidenavComp.open();
1371
+ }
1372
+ else {
1373
+ sidenavComp.close();
1374
+ }
1038
1375
  }
1039
1376
  }
1040
1377
  }
1041
1378
  };
1042
1379
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV1Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1043
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type: SidebarV1Component, isStandalone: true, selector: "sidebar-v1", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "sidenav", first: true, predicate: ["sidenav"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"isShowSidebar()\"\n (openedChange)=\"isShowSidebar.set($event)\"\n class=\"c-layout-sidebar\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobileOrTablet() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobileOrTablet()\">\n <sidebar\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\">\n </sidebar>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:hidden!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.mat-drawer-opened{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i1$4.MatDrawerContainer, selector: "mat-drawer-container", inputs: ["autosize", "hasBackdrop"], outputs: ["backdropClick"], exportAs: ["matDrawerContainer"] }, { kind: "component", type: i1$4.MatDrawerContent, selector: "mat-drawer-content" }, { kind: "component", type: i1$4.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i1$4.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i1$4.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: SidebarComponent, selector: "sidebar", inputs: ["isShowSidebar", "menus", "userInfo", "sidebar"], outputs: ["expandSideBar", "popupUserMenuClosed", "popupUserMenuOpened", "showSideBar"] }] });
1380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type: SidebarV1Component, isStandalone: true, selector: "sidebar-v1", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "sidenav", first: true, predicate: ["sidenav"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"!isMobile() || isShowSidebar()\"\n (openedChange)=\"onSidenavOpenedChange($event)\"\n class=\"c-layout-sidebar\"\n [class.is-expanded]=\"isShowSidebar()\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobile() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobile()\">\n <sidebar\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isMobile]=\"isMobile()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\">\n </sidebar>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>\n", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:clip!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.is-expanded{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i1$1.MatDrawerContainer, selector: "mat-drawer-container", inputs: ["autosize", "hasBackdrop"], outputs: ["backdropClick"], exportAs: ["matDrawerContainer"] }, { kind: "component", type: i1$1.MatDrawerContent, selector: "mat-drawer-content" }, { kind: "component", type: i1$1.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i1$1.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i1$1.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: SidebarComponent, selector: "sidebar", inputs: ["isShowSidebar", "menus", "userInfo", "sidebar", "isMobile"], outputs: ["expandSideBar", "popupUserMenuClosed", "popupUserMenuOpened", "showSideBar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1044
1381
  }
1045
1382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV1Component, decorators: [{
1046
1383
  type: Component,
1047
- args: [{ selector: 'sidebar-v1', imports: [MatSidenavModule, CommonModule, SidebarComponent], standalone: true, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"isShowSidebar()\"\n (openedChange)=\"isShowSidebar.set($event)\"\n class=\"c-layout-sidebar\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobileOrTablet() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobileOrTablet()\">\n <sidebar\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\">\n </sidebar>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:hidden!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.mat-drawer-opened{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"] }]
1048
- }], ctorParameters: () => [], propDecorators: { sidenav: [{ type: i0.ViewChild, args: ['sidenav', { isSignal: true }] }], menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
1384
+ args: [{ selector: 'sidebar-v1', imports: [MatSidenavModule, CommonModule, SidebarComponent], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"!isMobile() || isShowSidebar()\"\n (openedChange)=\"onSidenavOpenedChange($event)\"\n class=\"c-layout-sidebar\"\n [class.is-expanded]=\"isShowSidebar()\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobile() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobile()\">\n <sidebar\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isMobile]=\"isMobile()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\">\n </sidebar>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>\n", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:clip!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.is-expanded{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"] }]
1385
+ }], ctorParameters: () => [], propDecorators: { sidenav: [{ type: i0.ViewChild, args: ['sidenav', { isSignal: true }] }], menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }], isMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobile", required: false }] }] } });
1049
1386
 
1050
1387
  class LayoutUserComponent {
1051
- // ==========================================
1052
- // INJECT SERVICES
1053
- // ==========================================
1054
- #layoutConfiguration = inject(SD_LAYOUT_CONFIGURATION);
1055
- // ==========================================
1056
- // SIGNAL INPUTS & OUTPUTS
1057
- // ==========================================
1058
1388
  isMobileOrTablet = input(false, ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
1059
1389
  isMenuLock = input(false, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
1060
1390
  isShowSidebar = input(false, ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : []));
@@ -1062,47 +1392,21 @@ class LayoutUserComponent {
1062
1392
  menuClosed = output();
1063
1393
  menuOpened = output();
1064
1394
  toggleMenuLock = output();
1065
- // Config actions
1066
- singoutLayoutConfig = this.#layoutConfiguration.signout;
1067
- changePasswordLayoutConfig = this.#layoutConfiguration?.changePassword;
1068
- // Accordion state for mobile inline expand
1069
- isExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isExpanded" }] : []));
1070
- // ==========================================
1071
- // EVENT HANDLERS
1072
- // ==========================================
1073
- onMenuOpened = () => {
1395
+ onMenuOpened() {
1074
1396
  this.menuOpened.emit();
1075
- };
1076
- onMenuClosed = () => {
1397
+ }
1398
+ onMenuClosed() {
1077
1399
  this.menuClosed.emit();
1078
- };
1079
- keepOpenWhenClickInsideMenu = (event) => {
1080
- event.stopPropagation();
1081
- };
1082
- toggleExpanded = () => {
1083
- this.isExpanded.update(v => !v);
1084
- if (this.isExpanded()) {
1085
- this.menuOpened.emit();
1086
- }
1087
- else {
1088
- this.menuClosed.emit();
1089
- }
1090
- };
1091
- logout = () => {
1092
- this.singoutLayoutConfig();
1093
- };
1094
- changePassword = () => {
1095
- this.changePasswordLayoutConfig?.();
1096
- };
1097
- onToggleMenuLock = (event) => {
1400
+ }
1401
+ onToggleMenuLock(event) {
1098
1402
  this.toggleMenuLock.emit(event);
1099
- };
1403
+ }
1100
1404
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n@if (isMobileOrTablet()) {\n <!-- Mobile: accordion inline expand -->\n <div class=\"m-user-card\">\n <button class=\"m-user-row\" (click)=\"toggleExpanded()\">\n <sd-avatar [src]=\"avatar\" [size]=\"44\"></sd-avatar>\n <div class=\"m-user-info\">\n <span class=\"m-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n @if (_userInfo?.email) {\n <span class=\"m-user-email\">{{ _userInfo?.email }}</span>\n }\n </div>\n <sd-icon class=\"m-expand-icon\" [class.m-expand-icon--open]=\"isExpanded()\" name=\"keyboard_arrow_down\"></sd-icon>\n </button>\n\n @if (isExpanded()) {\n <div class=\"m-user-body\">\n @if (changePasswordLayoutConfig) {\n <button class=\"m-user-action\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n }\n <button class=\"m-user-action m-user-action--logout\" (click)=\"logout()\">\n <sd-icon name=\"logout\"></sd-icon>\n <span>{{ 'core.module.layout.user.logout' | translate }}</span>\n </button>\n </div>\n }\n </div>\n} @else {\n <!-- Desktop: ch\u1EC9 hi\u1EC3n th\u1ECB avatar + n\u00FAt toggle -->\n <div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n </div>\n}\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo?.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>\n", styles: ["@media(min-width:1024px){::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}}.m-user-card{width:100%}.m-user-row{display:flex;align-items:center;gap:12px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent}.m-user-info{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden}.m-user-name{font-size:15px;font-weight:600;color:#1f1f1f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-user-email{font-size:13px;color:#8c8c8c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-expand-icon{color:#8c8c8c;flex-shrink:0;transition:transform .2s ease}.m-expand-icon--open{transform:rotate(180deg)}.m-user-body{margin-top:8px;border-top:1px solid #EBEBEB;padding-top:4px;display:flex;flex-direction:column}.m-user-action{display:flex;align-items:center;gap:12px;width:100%;padding:12px 4px;border:none;background:transparent;cursor:pointer;font-size:15px;color:#1f1f1f;-webkit-tap-highlight-color:transparent}.m-user-action sd-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-user-action:active{background-color:#f5f5f5}.m-user-action--logout{color:#e53935}.m-user-action--logout sd-icon{color:#e53935}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i1$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1405
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@if (isMobileOrTablet()) {\n <sd-layout-user-menu [userInfo]=\"userInfo()\" presentation=\"mobile\"></sd-layout-user-menu>\n} @else {\n <div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | translate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n </div>\n}\n", styles: [":host{display:block;width:100%}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1102
1406
  }
1103
1407
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, decorators: [{
1104
1408
  type: Component,
1105
- args: [{ selector: 'lib-layout-user', imports: [SdIcon, MatMenuModule, MatButtonModule, MatTooltipModule, SdAvatar, TranslatePipe], standalone: true, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n@if (isMobileOrTablet()) {\n <!-- Mobile: accordion inline expand -->\n <div class=\"m-user-card\">\n <button class=\"m-user-row\" (click)=\"toggleExpanded()\">\n <sd-avatar [src]=\"avatar\" [size]=\"44\"></sd-avatar>\n <div class=\"m-user-info\">\n <span class=\"m-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n @if (_userInfo?.email) {\n <span class=\"m-user-email\">{{ _userInfo?.email }}</span>\n }\n </div>\n <sd-icon class=\"m-expand-icon\" [class.m-expand-icon--open]=\"isExpanded()\" name=\"keyboard_arrow_down\"></sd-icon>\n </button>\n\n @if (isExpanded()) {\n <div class=\"m-user-body\">\n @if (changePasswordLayoutConfig) {\n <button class=\"m-user-action\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n }\n <button class=\"m-user-action m-user-action--logout\" (click)=\"logout()\">\n <sd-icon name=\"logout\"></sd-icon>\n <span>{{ 'core.module.layout.user.logout' | translate }}</span>\n </button>\n </div>\n }\n </div>\n} @else {\n <!-- Desktop: ch\u1EC9 hi\u1EC3n th\u1ECB avatar + n\u00FAt toggle -->\n <div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n </div>\n}\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo?.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>\n", styles: ["@media(min-width:1024px){::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}}.m-user-card{width:100%}.m-user-row{display:flex;align-items:center;gap:12px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent}.m-user-info{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden}.m-user-name{font-size:15px;font-weight:600;color:#1f1f1f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-user-email{font-size:13px;color:#8c8c8c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-expand-icon{color:#8c8c8c;flex-shrink:0;transition:transform .2s ease}.m-expand-icon--open{transform:rotate(180deg)}.m-user-body{margin-top:8px;border-top:1px solid #EBEBEB;padding-top:4px;display:flex;flex-direction:column}.m-user-action{display:flex;align-items:center;gap:12px;width:100%;padding:12px 4px;border:none;background:transparent;cursor:pointer;font-size:15px;color:#1f1f1f;-webkit-tap-highlight-color:transparent}.m-user-action sd-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-user-action:active{background-color:#f5f5f5}.m-user-action--logout{color:#e53935}.m-user-action--logout sd-icon{color:#e53935}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"] }]
1409
+ args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent, TranslatePipe], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isMobileOrTablet()) {\n <sd-layout-user-menu [userInfo]=\"userInfo()\" presentation=\"mobile\"></sd-layout-user-menu>\n} @else {\n <div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | translate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n </div>\n}\n", styles: [":host{display:block;width:100%}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"] }]
1106
1410
  }], propDecorators: { isMobileOrTablet: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobileOrTablet", required: false }] }], isMenuLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMenuLock", required: false }] }], isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], menuClosed: [{ type: i0.Output, args: ["menuClosed"] }], menuOpened: [{ type: i0.Output, args: ["menuOpened"] }], toggleMenuLock: [{ type: i0.Output, args: ["toggleMenuLock"] }] } });
1107
1411
 
1108
1412
  class SidebarMobileOverlayComponent {
@@ -1296,12 +1600,513 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1296
1600
  args: [{ selector: 'sidebar-mobile-v1', imports: [SdIcon, CommonModule, SidebarMobileOverlayComponent], standalone: true, template: "<div class=\"c-mobile-layout\">\n <div class=\"c-mobile-topbar\">\n <div class=\"c-topbar-brand\">\n @if (sidebar().logoUrl) {\n <img class=\"c-topbar-logo\" [src]=\"sidebar().logoUrl\" alt=\"logo\" />\n }\n <span class=\"c-topbar-title\">{{ titleMenuGroup() || sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button class=\"c-hamburger-btn\" (click)=\"openSidebar()\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </div>\n\n <div class=\"c-mobile-content\">\n <ng-content></ng-content>\n </div>\n\n <sd-sidebar-mobile-overlay\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\"\n (titleMenuGroupChanged)=\"onTitleMenuGroupChanged($event)\">\n </sd-sidebar-mobile-overlay>\n</div>\n", styles: [":host{display:block;height:100vh;position:relative;overflow:hidden}.c-mobile-layout{display:flex;flex-direction:column;height:100%;position:relative}.c-mobile-topbar{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:56px;background-color:#fff;border-bottom:1px solid #e0e0e0;flex-shrink:0}.c-mobile-topbar .c-topbar-brand{display:flex;align-items:center;gap:10px;overflow:hidden}.c-mobile-topbar .c-topbar-brand .c-topbar-logo{width:28px;height:28px;object-fit:contain;flex-shrink:0}.c-mobile-topbar .c-topbar-brand .c-topbar-title{font-size:16px;font-weight:600;color:#1f1f1f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.c-mobile-topbar .c-hamburger-btn{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:6px;border-radius:50%;color:#333;flex-shrink:0}.c-mobile-topbar .c-hamburger-btn:hover{background-color:#0000000f}.c-mobile-content{flex:1;overflow-y:auto}\n"] }]
1297
1601
  }], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
1298
1602
 
1603
+ class SdLayoutMenuTreeComponent {
1604
+ #destroyRef = inject(DestroyRef);
1605
+ #hoveredPinKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredPinKey" }] : []));
1606
+ #pinHoverTimerId;
1607
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1608
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : []));
1609
+ activePath = input('', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1610
+ pinnedKeys = input([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : []));
1611
+ showPin = input(true, ...(ngDevMode ? [{ debugName: "showPin" }] : []));
1612
+ pinVisibility = input('hover', ...(ngDevMode ? [{ debugName: "pinVisibility" }] : []));
1613
+ navigate = output();
1614
+ togglePinned = output();
1615
+ nodes = computed(() => {
1616
+ const pinnedKeys = new Set(this.pinnedKeys());
1617
+ const hoveredPinKey = this.#hoveredPinKey();
1618
+ const alwaysShowPin = this.pinVisibility() === 'always';
1619
+ const query = this.query().trim();
1620
+ const menus = query ? searchMenuLeaves(this.menus(), query) : this.menus();
1621
+ const nodes = [];
1622
+ const append = (items, depth, ancestors) => {
1623
+ for (const menu of items) {
1624
+ const key = getMenuStableKey(menu, ancestors);
1625
+ if (!key)
1626
+ continue;
1627
+ const isGroup = 'children' in menu && !!menu.children?.length;
1628
+ const path = 'path' in menu ? menu.path : '';
1629
+ nodes.push({
1630
+ menu,
1631
+ key,
1632
+ title: menu.title ?? menu.tooltipTitle ?? '',
1633
+ depth,
1634
+ paddingLeft: 12 + depth * 16,
1635
+ isGroup,
1636
+ isActive: !!path && this.#pathMatches(this.activePath(), path),
1637
+ isPinned: pinnedKeys.has(key),
1638
+ isPinVisible: alwaysShowPin || pinnedKeys.has(key) || hoveredPinKey === key,
1639
+ });
1640
+ if (isGroup && 'children' in menu)
1641
+ append(menu.children ?? [], depth + 1, [...ancestors, menu.title ?? key]);
1642
+ }
1643
+ };
1644
+ append(menus, 0, []);
1645
+ return nodes;
1646
+ }, ...(ngDevMode ? [{ debugName: "nodes" }] : []));
1647
+ constructor() {
1648
+ this.#destroyRef.onDestroy(() => this.#clearPinHoverTimer());
1649
+ }
1650
+ onNavigate(menu) {
1651
+ if ('path' in menu)
1652
+ this.navigate.emit(menu);
1653
+ }
1654
+ onTogglePinned(event, menu) {
1655
+ event.stopPropagation();
1656
+ this.togglePinned.emit(menu);
1657
+ }
1658
+ onPinHoverStart(key) {
1659
+ if (this.pinVisibility() === 'always')
1660
+ return;
1661
+ this.#clearPinHoverTimer();
1662
+ this.#pinHoverTimerId = setTimeout(() => {
1663
+ this.#hoveredPinKey.set(key);
1664
+ this.#pinHoverTimerId = undefined;
1665
+ }, 300);
1666
+ }
1667
+ onPinHoverEnd(key) {
1668
+ this.#clearPinHoverTimer();
1669
+ if (this.#hoveredPinKey() === key)
1670
+ this.#hoveredPinKey.set(null);
1671
+ }
1672
+ #clearPinHoverTimer() {
1673
+ if (this.#pinHoverTimerId === undefined)
1674
+ return;
1675
+ clearTimeout(this.#pinHoverTimerId);
1676
+ this.#pinHoverTimerId = undefined;
1677
+ }
1678
+ #pathMatches(currentPath, menuPath) {
1679
+ const normalize = (path) => (path.endsWith('/') ? path : `${path}/`);
1680
+ return currentPath === menuPath || normalize(currentPath).startsWith(normalize(menuPath));
1681
+ }
1682
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1683
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutMenuTreeComponent, isStandalone: true, selector: "sd-layout-menu-tree", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, activePath: { classPropertyName: "activePath", publicName: "activePath", isSignal: true, isRequired: false, transformFunction: null }, pinnedKeys: { classPropertyName: "pinnedKeys", publicName: "pinnedKeys", isSignal: true, isRequired: false, transformFunction: null }, showPin: { classPropertyName: "showPin", publicName: "showPin", isSignal: true, isRequired: false, transformFunction: null }, pinVisibility: { classPropertyName: "pinVisibility", publicName: "pinVisibility", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate", togglePinned: "togglePinned" }, ngImport: i0, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.isPinned ? 'Unpin ' + node.title : 'Pin ' + node.title\"\n [attr.aria-pressed]=\"node.isPinned\"\n (click)=\"onTogglePinned($event, node.menu)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n }\n } @empty {\n <div class=\"d-flex flex-column align-items-center justify-content-center gap-12 p-24 text-black400 T14R\">\n Kh\u00F4ng t\u00ECm th\u1EA5y menu ph\u00F9 h\u1EE3p\n </div>\n }\n</nav>\n", styles: [".sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{min-height:40px;border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-menu-tree__route-button--active{color:var(--sd-primary);background:var(--sd-primary-light);font-weight:500}.sd-layout-menu-tree__route-button:hover,.sd-layout-menu-tree__pin:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3));color:var(--sd-primary, #005cbb)}.sd-layout-menu-tree__route-button--active:hover{background:var(--sd-primary-light, #d7e3ff)}.sd-layout-menu-tree__route-button:focus-visible,.sd-layout-menu-tree__pin:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-menu-tree__pin{width:36px;flex:0 0 36px;opacity:0;pointer-events:none;transition:opacity .12s ease,background-color .12s ease,color .12s ease}.sd-layout-menu-tree__pin--visible,.sd-layout-menu-tree__pin:focus-visible{opacity:1;pointer-events:auto}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1684
+ }
1685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, decorators: [{
1686
+ type: Component,
1687
+ args: [{ selector: 'sd-layout-menu-tree', standalone: true, imports: [SdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.isPinned ? 'Unpin ' + node.title : 'Pin ' + node.title\"\n [attr.aria-pressed]=\"node.isPinned\"\n (click)=\"onTogglePinned($event, node.menu)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n }\n } @empty {\n <div class=\"d-flex flex-column align-items-center justify-content-center gap-12 p-24 text-black400 T14R\">\n Kh\u00F4ng t\u00ECm th\u1EA5y menu ph\u00F9 h\u1EE3p\n </div>\n }\n</nav>\n", styles: [".sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{min-height:40px;border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-menu-tree__route-button--active{color:var(--sd-primary);background:var(--sd-primary-light);font-weight:500}.sd-layout-menu-tree__route-button:hover,.sd-layout-menu-tree__pin:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3));color:var(--sd-primary, #005cbb)}.sd-layout-menu-tree__route-button--active:hover{background:var(--sd-primary-light, #d7e3ff)}.sd-layout-menu-tree__route-button:focus-visible,.sd-layout-menu-tree__pin:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-menu-tree__pin{width:36px;flex:0 0 36px;opacity:0;pointer-events:none;transition:opacity .12s ease,background-color .12s ease,color .12s ease}.sd-layout-menu-tree__pin--visible,.sd-layout-menu-tree__pin:focus-visible{opacity:1;pointer-events:auto}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{transition:none}}\n"] }]
1688
+ }], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], activePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "activePath", required: false }] }], pinnedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedKeys", required: false }] }], showPin: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPin", required: false }] }], pinVisibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinVisibility", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }], togglePinned: [{ type: i0.Output, args: ["togglePinned"] }] } });
1689
+
1690
+ class SdLayoutSearchFieldComponent {
1691
+ model = input('', ...(ngDevMode ? [{ debugName: "model" }] : []));
1692
+ placeholder = input.required(...(ngDevMode ? [{ debugName: "placeholder" }] : []));
1693
+ autoId = input.required(...(ngDevMode ? [{ debugName: "autoId" }] : []));
1694
+ sdChange = output();
1695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutSearchFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: SdLayoutSearchFieldComponent, isStandalone: true, selector: "sd-layout-search-field", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: true, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { sdChange: "sdChange" }, ngImport: i0, template: "<div\n data-layout-search\n class=\"sd-layout-search-field sd-layout-search-field--soft-pill d-flex align-items-center gap-8\">\n <sd-icon class=\"sd-layout-search-field__icon\" name=\"search\"></sd-icon>\n <sd-input\n class=\"sd-layout-search-field__control\"\n size=\"sm\"\n hideInlineError\n [placeholder]=\"placeholder()\"\n [autoId]=\"autoId()\"\n [model]=\"model()\"\n (sdChange)=\"sdChange.emit($event)\">\n </sd-input>\n</div>\n", styles: [":host{display:block;min-width:0}.sd-layout-search-field{min-height:38px;padding:0 12px;border:0;border-radius:9999px;outline:1px solid transparent;outline-offset:1px;background:var(--sd-black100, var(--sd-surface-muted));color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field:focus-within{outline-color:var(--sd-primary)}.sd-layout-search-field__icon{flex:0 0 auto;color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field__control{flex:1;min-width:0;--sd-border: transparent;--mdc-outlined-text-field-outline-color: transparent;--mdc-outlined-text-field-hover-outline-color: transparent;--mdc-outlined-text-field-focus-outline-color: transparent;--mdc-outlined-text-field-input-text-placeholder-color: var(--sd-black400, var(--sd-text-muted));--mdc-outlined-text-field-container-shape: 0px}:host ::ng-deep .sd-layout-search-field__control input:focus-visible{outline:0}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1697
+ }
1698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutSearchFieldComponent, decorators: [{
1699
+ type: Component,
1700
+ args: [{ selector: 'sd-layout-search-field', standalone: true, imports: [SdIcon, SdInput], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n data-layout-search\n class=\"sd-layout-search-field sd-layout-search-field--soft-pill d-flex align-items-center gap-8\">\n <sd-icon class=\"sd-layout-search-field__icon\" name=\"search\"></sd-icon>\n <sd-input\n class=\"sd-layout-search-field__control\"\n size=\"sm\"\n hideInlineError\n [placeholder]=\"placeholder()\"\n [autoId]=\"autoId()\"\n [model]=\"model()\"\n (sdChange)=\"sdChange.emit($event)\">\n </sd-input>\n</div>\n", styles: [":host{display:block;min-width:0}.sd-layout-search-field{min-height:38px;padding:0 12px;border:0;border-radius:9999px;outline:1px solid transparent;outline-offset:1px;background:var(--sd-black100, var(--sd-surface-muted));color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field:focus-within{outline-color:var(--sd-primary)}.sd-layout-search-field__icon{flex:0 0 auto;color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field__control{flex:1;min-width:0;--sd-border: transparent;--mdc-outlined-text-field-outline-color: transparent;--mdc-outlined-text-field-hover-outline-color: transparent;--mdc-outlined-text-field-focus-outline-color: transparent;--mdc-outlined-text-field-input-text-placeholder-color: var(--sd-black400, var(--sd-text-muted));--mdc-outlined-text-field-container-shape: 0px}:host ::ng-deep .sd-layout-search-field__control input:focus-visible{outline:0}\n"] }]
1701
+ }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: true }] }], autoId: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: true }] }], sdChange: [{ type: i0.Output, args: ["sdChange"] }] } });
1702
+
1703
+ class SidebarV2Component {
1704
+ #router = inject(Router);
1705
+ #document = inject(DOCUMENT);
1706
+ #destroyRef = inject(DestroyRef);
1707
+ #navigationState = inject(SdLayoutNavigationStateService);
1708
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1709
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1710
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1711
+ activeGroupKey = signal('', ...(ngDevMode ? [{ debugName: "activeGroupKey" }] : []));
1712
+ isFlyoutOpen = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutOpen" }] : []));
1713
+ isFlyoutLocked = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutLocked" }] : []));
1714
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1715
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1716
+ interaction = computed(() => resolveSidebarV2Interaction(this.sidebar()), ...(ngDevMode ? [{ debugName: "interaction" }] : []));
1717
+ railItems = computed(() => this.menus()
1718
+ .map(menu => ({ menu, key: getMenuStableKey(menu), title: menu.tooltipTitle || menu.title || 'Menu' }))
1719
+ .filter(item => !!item.key), ...(ngDevMode ? [{ debugName: "railItems" }] : []));
1720
+ activeGroup = computed(() => this.railItems().find(item => item.key === this.activeGroupKey())?.menu, ...(ngDevMode ? [{ debugName: "activeGroup" }] : []));
1721
+ contextMenus = computed(() => {
1722
+ const activeGroup = this.activeGroup();
1723
+ if (!activeGroup)
1724
+ return [];
1725
+ return 'children' in activeGroup && activeGroup.children?.length ? activeGroup.children : [activeGroup];
1726
+ }, ...(ngDevMode ? [{ debugName: "contextMenus" }] : []));
1727
+ pinnedKeys = this.#navigationState.pinnedKeys;
1728
+ constructor() {
1729
+ effect(() => {
1730
+ const menus = this.menus();
1731
+ untracked(() => {
1732
+ this.#navigationState.hydrate(menus);
1733
+ const persistedState = this.#navigationState.versionState(2);
1734
+ if (persistedState.activeGroupKey && !this.activeGroupKey())
1735
+ this.activeGroupKey.set(persistedState.activeGroupKey);
1736
+ });
1737
+ });
1738
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1739
+ if (!(event instanceof NavigationEnd))
1740
+ return;
1741
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1742
+ this.closeFromNavigation();
1743
+ });
1744
+ }
1745
+ selectGroup(menu) {
1746
+ const key = getMenuStableKey(menu);
1747
+ if (!key)
1748
+ return;
1749
+ if (this.interaction() === 'click') {
1750
+ if (this.activeGroupKey() === key && this.isFlyoutOpen()) {
1751
+ this.#closeFlyout();
1752
+ return;
1753
+ }
1754
+ this.#openGroup(key, false);
1755
+ return;
1756
+ }
1757
+ if (this.activeGroupKey() === key && this.isFlyoutLocked()) {
1758
+ this.#closeFlyout();
1759
+ return;
1760
+ }
1761
+ this.#openGroup(key, true);
1762
+ }
1763
+ previewGroup(menu) {
1764
+ if (this.interaction() !== 'hover-lock' || this.isFlyoutLocked())
1765
+ return;
1766
+ const key = getMenuStableKey(menu);
1767
+ if (key)
1768
+ this.#openGroup(key, false, false);
1769
+ }
1770
+ leaveFlyout() {
1771
+ if (this.interaction() === 'hover-lock' && !this.isFlyoutLocked())
1772
+ this.#closeFlyout(false);
1773
+ }
1774
+ closeFromBackdrop() {
1775
+ this.#closeFlyout();
1776
+ }
1777
+ closeFromEscape() {
1778
+ this.#closeFlyout();
1779
+ }
1780
+ closeFromNavigation() {
1781
+ this.#closeFlyout();
1782
+ }
1783
+ onNavigate(menu) {
1784
+ if (!('path' in menu))
1785
+ return;
1786
+ this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
1787
+ if (menu.path.includes('http')) {
1788
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1789
+ this.closeFromNavigation();
1790
+ return;
1791
+ }
1792
+ void this.#router.navigate([menu.path.split('?')[0]], {
1793
+ queryParams: menu.queryParams ?? {},
1794
+ state: { switchTab: true },
1795
+ });
1796
+ }
1797
+ onTogglePinned(menu) {
1798
+ this.#navigationState.togglePinned(menu);
1799
+ }
1800
+ #openGroup(key, locked, persist = true) {
1801
+ this.activeGroupKey.set(key);
1802
+ this.isFlyoutOpen.set(true);
1803
+ this.isFlyoutLocked.set(locked);
1804
+ this.searchText.set('');
1805
+ if (persist)
1806
+ this.#navigationState.patchVersionState(2, { activeGroupKey: key, locked });
1807
+ }
1808
+ #closeFlyout(persist = true) {
1809
+ this.isFlyoutOpen.set(false);
1810
+ this.isFlyoutLocked.set(false);
1811
+ this.searchText.set('');
1812
+ if (persist)
1813
+ this.#navigationState.patchVersionState(2, { locked: false });
1814
+ }
1815
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1816
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarV2Component, isStandalone: true, selector: "sidebar-v2", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-v2\" (keydown.escape)=\"closeFromEscape()\">\n <aside class=\"sd-sidebar-v2__rail d-flex flex-column align-items-center gap-8 py-12 bg-white border-right border-black200\">\n <button type=\"button\" class=\"sd-sidebar-v2__brand d-flex align-items-center justify-content-center\" aria-label=\"Trang ch\u1EE7\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v2__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </button>\n\n <nav class=\"d-flex flex-column align-items-center gap-4 flex-1\" aria-label=\"Nh\u00F3m menu\">\n @for (item of railItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__rail-action d-flex flex-column align-items-center justify-content-center gap-4\"\n [attr.data-v2-group-key]=\"item.key\"\n [attr.aria-label]=\"item.title\"\n [attr.aria-expanded]=\"activeGroupKey() === item.key && isFlyoutOpen()\"\n [class.sd-sidebar-v2__rail-action--active]=\"activeGroupKey() === item.key\"\n (click)=\"selectGroup(item.menu)\"\n (mouseenter)=\"previewGroup(item.menu)\"\n (focus)=\"previewGroup(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v2__rail-icon\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-v2__rail-label text-ellipsis\">{{ item.title }}</span>\n </button>\n }\n </nav>\n\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"true\"></sd-layout-user-menu>\n </aside>\n\n @if (isFlyoutOpen()) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__backdrop\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\"\n (keydown.escape)=\"closeFromEscape()\"></button>\n <section\n class=\"sd-sidebar-v2__flyout d-flex flex-column bg-white border-right border-black200 mat-elevation-z4\"\n [attr.aria-label]=\"activeGroup()?.title || 'Menu'\"\n (mouseleave)=\"leaveFlyout()\"\n (keydown.escape)=\"closeFromEscape()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ activeGroup()?.title || sidebar().defaultTitle || 'Menu' }}</span>\n @if (isFlyoutLocked()) {\n <sd-icon name=\"lock\"></sd-icon>\n }\n </header>\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong nh\u00F3m\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"flex-1 overflow-auto px-8 pb-12\">\n <sd-layout-menu-tree\n [menus]=\"contextMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n\n <main class=\"sd-sidebar-v2__content\" data-v2-content data-content-offset=\"rail\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v2{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v2__rail{position:fixed;z-index:30;inset:0 auto 0 0;width:72px}.sd-sidebar-v2__brand,.sd-sidebar-v2__rail-action{width:56px;min-height:52px;border:0;border-radius:10px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v2__rail-action:hover,.sd-sidebar-v2__rail-action--active{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v2__brand:focus-visible,.sd-sidebar-v2__rail-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v2__rail-label{width:52px;font-size:10px;line-height:12px;text-align:center}.sd-sidebar-v2__logo,.sd-sidebar-v2__rail-icon{width:24px;height:24px;object-fit:contain}.sd-sidebar-v2__backdrop{position:fixed;z-index:10;inset:0;border:0;background:transparent}.sd-sidebar-v2__flyout{position:fixed;z-index:20;inset:0 auto 0 72px;width:320px}.sd-sidebar-v2__content{min-height:100vh;margin-left:72px}@media(prefers-reduced-motion:reduce){.sd-sidebar-v2__flyout{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1817
+ }
1818
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV2Component, decorators: [{
1819
+ type: Component,
1820
+ args: [{ selector: 'sidebar-v2', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-v2\" (keydown.escape)=\"closeFromEscape()\">\n <aside class=\"sd-sidebar-v2__rail d-flex flex-column align-items-center gap-8 py-12 bg-white border-right border-black200\">\n <button type=\"button\" class=\"sd-sidebar-v2__brand d-flex align-items-center justify-content-center\" aria-label=\"Trang ch\u1EE7\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v2__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </button>\n\n <nav class=\"d-flex flex-column align-items-center gap-4 flex-1\" aria-label=\"Nh\u00F3m menu\">\n @for (item of railItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__rail-action d-flex flex-column align-items-center justify-content-center gap-4\"\n [attr.data-v2-group-key]=\"item.key\"\n [attr.aria-label]=\"item.title\"\n [attr.aria-expanded]=\"activeGroupKey() === item.key && isFlyoutOpen()\"\n [class.sd-sidebar-v2__rail-action--active]=\"activeGroupKey() === item.key\"\n (click)=\"selectGroup(item.menu)\"\n (mouseenter)=\"previewGroup(item.menu)\"\n (focus)=\"previewGroup(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v2__rail-icon\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-v2__rail-label text-ellipsis\">{{ item.title }}</span>\n </button>\n }\n </nav>\n\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"true\"></sd-layout-user-menu>\n </aside>\n\n @if (isFlyoutOpen()) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__backdrop\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\"\n (keydown.escape)=\"closeFromEscape()\"></button>\n <section\n class=\"sd-sidebar-v2__flyout d-flex flex-column bg-white border-right border-black200 mat-elevation-z4\"\n [attr.aria-label]=\"activeGroup()?.title || 'Menu'\"\n (mouseleave)=\"leaveFlyout()\"\n (keydown.escape)=\"closeFromEscape()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ activeGroup()?.title || sidebar().defaultTitle || 'Menu' }}</span>\n @if (isFlyoutLocked()) {\n <sd-icon name=\"lock\"></sd-icon>\n }\n </header>\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong nh\u00F3m\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"flex-1 overflow-auto px-8 pb-12\">\n <sd-layout-menu-tree\n [menus]=\"contextMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n\n <main class=\"sd-sidebar-v2__content\" data-v2-content data-content-offset=\"rail\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v2{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v2__rail{position:fixed;z-index:30;inset:0 auto 0 0;width:72px}.sd-sidebar-v2__brand,.sd-sidebar-v2__rail-action{width:56px;min-height:52px;border:0;border-radius:10px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v2__rail-action:hover,.sd-sidebar-v2__rail-action--active{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v2__brand:focus-visible,.sd-sidebar-v2__rail-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v2__rail-label{width:52px;font-size:10px;line-height:12px;text-align:center}.sd-sidebar-v2__logo,.sd-sidebar-v2__rail-icon{width:24px;height:24px;object-fit:contain}.sd-sidebar-v2__backdrop{position:fixed;z-index:10;inset:0;border:0;background:transparent}.sd-sidebar-v2__flyout{position:fixed;z-index:20;inset:0 auto 0 72px;width:320px}.sd-sidebar-v2__content{min-height:100vh;margin-left:72px}@media(prefers-reduced-motion:reduce){.sd-sidebar-v2__flyout{transition:none}}\n"] }]
1821
+ }], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
1822
+
1823
+ class SidebarMobileV2Component {
1824
+ #router = inject(Router);
1825
+ #document = inject(DOCUMENT);
1826
+ #destroyRef = inject(DestroyRef);
1827
+ #navigationState = inject(SdLayoutNavigationStateService);
1828
+ #restoreFocusElement = null;
1829
+ #previousBodyOverflow = '';
1830
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1831
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1832
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1833
+ primaryMenus = computed(() => selectPrimaryMenuGroups(this.menus(), this.sidebar().primaryMenuIds), ...(ngDevMode ? [{ debugName: "primaryMenus" }] : []));
1834
+ overflowMenus = computed(() => {
1835
+ const primaryKeys = new Set(this.primaryMenus().map(menu => getMenuStableKey(menu)));
1836
+ return this.menus().filter(menu => !primaryKeys.has(getMenuStableKey(menu)));
1837
+ }, ...(ngDevMode ? [{ debugName: "overflowMenus" }] : []));
1838
+ isSheetOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSheetOpen" }] : []));
1839
+ sheetGroupKey = signal('', ...(ngDevMode ? [{ debugName: "sheetGroupKey" }] : []));
1840
+ sheetTitle = signal('', ...(ngDevMode ? [{ debugName: "sheetTitle" }] : []));
1841
+ sheetMenus = signal([], ...(ngDevMode ? [{ debugName: "sheetMenus" }] : []));
1842
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1843
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1844
+ pinnedKeys = this.#navigationState.pinnedKeys;
1845
+ constructor() {
1846
+ effect(() => {
1847
+ const menus = this.menus();
1848
+ untracked(() => this.#navigationState.hydrate(menus));
1849
+ });
1850
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1851
+ if (!(event instanceof NavigationEnd))
1852
+ return;
1853
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1854
+ this.closeSheet(false);
1855
+ });
1856
+ this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
1857
+ }
1858
+ openMore(trigger) {
1859
+ this.#openSheet('more', 'Thêm', this.overflowMenus(), trigger);
1860
+ }
1861
+ activateMenu(menu, trigger) {
1862
+ if ('path' in menu) {
1863
+ this.#navigate(menu);
1864
+ return;
1865
+ }
1866
+ this.#openSheet(getMenuStableKey(menu), menu.title || 'Menu', menu.children ?? [], trigger);
1867
+ }
1868
+ closeSheet(restoreFocus = true) {
1869
+ if (!this.isSheetOpen())
1870
+ return;
1871
+ this.isSheetOpen.set(false);
1872
+ this.searchText.set('');
1873
+ this.#releaseBodyScroll();
1874
+ if (restoreFocus)
1875
+ this.#restoreFocusElement?.focus();
1876
+ this.#restoreFocusElement = null;
1877
+ }
1878
+ onSheetKeydown(event) {
1879
+ if (event.key !== 'Escape')
1880
+ return;
1881
+ event.preventDefault();
1882
+ this.closeSheet();
1883
+ }
1884
+ onNavigate(menu) {
1885
+ if ('path' in menu)
1886
+ this.#navigate(menu);
1887
+ }
1888
+ onTogglePinned(menu) {
1889
+ this.#navigationState.togglePinned(menu);
1890
+ }
1891
+ #openSheet(key, title, menus, trigger) {
1892
+ this.sheetGroupKey.set(key);
1893
+ this.sheetTitle.set(title);
1894
+ this.sheetMenus.set(menus);
1895
+ this.searchText.set('');
1896
+ this.#restoreFocusElement =
1897
+ trigger instanceof HTMLElement ? trigger : this.#document.activeElement instanceof HTMLElement ? this.#document.activeElement : null;
1898
+ if (!this.isSheetOpen()) {
1899
+ this.#previousBodyOverflow = this.#document.body.style.overflow;
1900
+ this.#document.body.style.overflow = 'hidden';
1901
+ }
1902
+ this.isSheetOpen.set(true);
1903
+ }
1904
+ #navigate(menu) {
1905
+ this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
1906
+ if (menu.path.includes('http')) {
1907
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1908
+ this.closeSheet();
1909
+ return;
1910
+ }
1911
+ void this.#router
1912
+ .navigate([menu.path.split('?')[0]], { queryParams: menu.queryParams ?? {}, state: { switchTab: true } })
1913
+ .then(navigated => {
1914
+ if (navigated)
1915
+ this.closeSheet();
1916
+ });
1917
+ }
1918
+ #releaseBodyScroll() {
1919
+ this.#document.body.style.overflow = this.#previousBodyOverflow;
1920
+ }
1921
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1922
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarMobileV2Component, isStandalone: true, selector: "sidebar-mobile-v2", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-mobile-v2 d-flex flex-column\">\n <main class=\"sd-sidebar-mobile-v2__content flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n <nav class=\"sd-sidebar-mobile-v2__bar d-flex align-items-stretch bg-white border-top border-black200\" aria-label=\"\u0110i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n @for (menu of primaryMenus(); track menu.id || menu.title) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n [attr.data-mobile-menu-key]=\"menu.id ? 'id:' + menu.id : null\"\n [attr.aria-label]=\"menu.tooltipTitle || menu.title || 'Menu'\"\n [attr.aria-expanded]=\"sheetGroupKey() === (menu.id ? 'id:' + menu.id : '') && isSheetOpen()\"\n (click)=\"activateMenu(menu, $event.currentTarget)\">\n @if (menu.iconUrl) {\n <img class=\"sd-sidebar-mobile-v2__icon\" [src]=\"menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-mobile-v2__label text-ellipsis\">{{ menu.tooltipTitle || menu.title }}</span>\n </button>\n }\n @if (overflowMenus().length) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n aria-label=\"Th\u00EAm menu\"\n [attr.aria-expanded]=\"sheetGroupKey() === 'more' && isSheetOpen()\"\n (click)=\"openMore($event.currentTarget)\">\n <sd-icon name=\"more_horiz\"></sd-icon>\n <span class=\"sd-sidebar-mobile-v2__label\">Th\u00EAm</span>\n </button>\n }\n </nav>\n\n @if (isSheetOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v2__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeSheet()\"></button>\n <section\n data-v2-sheet\n class=\"sd-sidebar-mobile-v2__sheet d-flex flex-column bg-white rounded-top-16 mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"sheetTitle()\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onSheetKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ sheetTitle() }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__close d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng\"\n (click)=\"closeSheet()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"d-flex flex-column gap-12 p-12 overflow-auto\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong menu\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v2{min-height:100dvh;padding-bottom:calc(64px + env(safe-area-inset-bottom));background:var(--sd-light)}.sd-sidebar-mobile-v2__bar{position:fixed;z-index:30;inset:auto 0 0;min-height:64px;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v2__bar-action,.sd-sidebar-mobile-v2__close{min-width:0;min-height:48px;border:0;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-mobile-v2__bar-action:hover,.sd-sidebar-mobile-v2__close:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v2__bar-action:focus-visible,.sd-sidebar-mobile-v2__close:focus-visible{outline:2px solid var(--sd-primary);outline-offset:-2px}.sd-sidebar-mobile-v2__label{max-width:72px;font-size:11px;line-height:14px}.sd-sidebar-mobile-v2__icon{width:22px;height:22px;object-fit:contain}.sd-sidebar-mobile-v2__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v2__sheet{position:fixed;z-index:50;inset:auto 0 0;max-height:min(78dvh,720px);padding-bottom:env(safe-area-inset-bottom);overflow:hidden}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v2__sheet{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1923
+ }
1924
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV2Component, decorators: [{
1925
+ type: Component,
1926
+ args: [{ selector: 'sidebar-mobile-v2', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-mobile-v2 d-flex flex-column\">\n <main class=\"sd-sidebar-mobile-v2__content flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n <nav class=\"sd-sidebar-mobile-v2__bar d-flex align-items-stretch bg-white border-top border-black200\" aria-label=\"\u0110i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n @for (menu of primaryMenus(); track menu.id || menu.title) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n [attr.data-mobile-menu-key]=\"menu.id ? 'id:' + menu.id : null\"\n [attr.aria-label]=\"menu.tooltipTitle || menu.title || 'Menu'\"\n [attr.aria-expanded]=\"sheetGroupKey() === (menu.id ? 'id:' + menu.id : '') && isSheetOpen()\"\n (click)=\"activateMenu(menu, $event.currentTarget)\">\n @if (menu.iconUrl) {\n <img class=\"sd-sidebar-mobile-v2__icon\" [src]=\"menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-mobile-v2__label text-ellipsis\">{{ menu.tooltipTitle || menu.title }}</span>\n </button>\n }\n @if (overflowMenus().length) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n aria-label=\"Th\u00EAm menu\"\n [attr.aria-expanded]=\"sheetGroupKey() === 'more' && isSheetOpen()\"\n (click)=\"openMore($event.currentTarget)\">\n <sd-icon name=\"more_horiz\"></sd-icon>\n <span class=\"sd-sidebar-mobile-v2__label\">Th\u00EAm</span>\n </button>\n }\n </nav>\n\n @if (isSheetOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v2__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeSheet()\"></button>\n <section\n data-v2-sheet\n class=\"sd-sidebar-mobile-v2__sheet d-flex flex-column bg-white rounded-top-16 mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"sheetTitle()\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onSheetKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ sheetTitle() }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__close d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng\"\n (click)=\"closeSheet()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"d-flex flex-column gap-12 p-12 overflow-auto\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong menu\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v2{min-height:100dvh;padding-bottom:calc(64px + env(safe-area-inset-bottom));background:var(--sd-light)}.sd-sidebar-mobile-v2__bar{position:fixed;z-index:30;inset:auto 0 0;min-height:64px;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v2__bar-action,.sd-sidebar-mobile-v2__close{min-width:0;min-height:48px;border:0;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-mobile-v2__bar-action:hover,.sd-sidebar-mobile-v2__close:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v2__bar-action:focus-visible,.sd-sidebar-mobile-v2__close:focus-visible{outline:2px solid var(--sd-primary);outline-offset:-2px}.sd-sidebar-mobile-v2__label{max-width:72px;font-size:11px;line-height:14px}.sd-sidebar-mobile-v2__icon{width:22px;height:22px;object-fit:contain}.sd-sidebar-mobile-v2__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v2__sheet{position:fixed;z-index:50;inset:auto 0 0;max-height:min(78dvh,720px);padding-bottom:env(safe-area-inset-bottom);overflow:hidden}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v2__sheet{transition:none}}\n"] }]
1927
+ }], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
1928
+
1929
+ class SidebarV3Component {
1930
+ #router = inject(Router);
1931
+ #document = inject(DOCUMENT);
1932
+ #destroyRef = inject(DestroyRef);
1933
+ #navigationState = inject(SdLayoutNavigationStateService);
1934
+ #initialized = false;
1935
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1936
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1937
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1938
+ isCollapsed = signal(false, ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
1939
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1940
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1941
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : []));
1942
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : []));
1943
+ pinnedKeys = this.#navigationState.pinnedKeys;
1944
+ pinnedMenus = this.#navigationState.pinnedMenus;
1945
+ recentMenus = this.#navigationState.recentMenus;
1946
+ collapsedItems = computed(() => this.menus().map(menu => ({ menu, key: getMenuStableKey(menu), title: menu.tooltipTitle || menu.title || 'Menu' })), ...(ngDevMode ? [{ debugName: "collapsedItems" }] : []));
1947
+ constructor() {
1948
+ effect(() => {
1949
+ const menus = this.menus();
1950
+ const sidebar = this.sidebar();
1951
+ const recentConfiguration = resolveSidebarV3Recent(sidebar);
1952
+ untracked(() => {
1953
+ this.#navigationState.hydrate(menus, recentConfiguration.maxItems);
1954
+ if (this.#initialized)
1955
+ return;
1956
+ const persistedCollapsed = this.#navigationState.versionState(3).collapsed;
1957
+ this.isCollapsed.set(typeof persistedCollapsed === 'boolean' ? persistedCollapsed : (sidebar.defaultCollapsed ?? false));
1958
+ this.#initialized = true;
1959
+ });
1960
+ });
1961
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1962
+ if (event instanceof NavigationEnd)
1963
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1964
+ });
1965
+ }
1966
+ toggleCollapsed() {
1967
+ this.isCollapsed.update(collapsed => !collapsed);
1968
+ this.#navigationState.patchVersionState(3, { collapsed: this.isCollapsed() });
1969
+ }
1970
+ activateCollapsedMenu(menu) {
1971
+ if ('path' in menu)
1972
+ this.navigateMenu(menu);
1973
+ else {
1974
+ this.isCollapsed.set(false);
1975
+ this.#navigationState.patchVersionState(3, { collapsed: false });
1976
+ }
1977
+ }
1978
+ navigateMenu(menu) {
1979
+ if (!('path' in menu))
1980
+ return;
1981
+ this.#navigationState.recordRecent(menu, this.recentConfiguration());
1982
+ if (menu.path.includes('http')) {
1983
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1984
+ return;
1985
+ }
1986
+ void this.#router.navigate([menu.path.split('?')[0]], {
1987
+ queryParams: menu.queryParams ?? {},
1988
+ state: { switchTab: true },
1989
+ });
1990
+ }
1991
+ onTogglePinned(menu) {
1992
+ this.#navigationState.togglePinned(menu);
1993
+ }
1994
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1995
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarV3Component, isStandalone: true, selector: "sidebar-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header\n data-v3-header\n class=\"sd-sidebar-v3__header d-flex align-items-center gap-8 p-12 border-bottom border-black200\"\n [class.sd-sidebar-v3__header--collapsed]=\"isCollapsed()\"\n [class.justify-content-between]=\"!isCollapsed()\"\n [class.justify-content-center]=\"isCollapsed()\">\n @if (!isCollapsed()) {\n <div data-v3-brand class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n }\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__icon-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"isCollapsed() ? 'M\u1EDF r\u1ED9ng sidebar' : 'Thu g\u1ECDn sidebar'\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapsed()\">\n <sd-icon [name]=\"isCollapsed() ? 'chevron_right' : 'chevron_left'\"></sd-icon>\n </button>\n </header>\n\n @if (isCollapsed()) {\n <nav class=\"d-flex flex-column align-items-center gap-4 p-8 flex-1\" aria-label=\"Menu thu g\u1ECDn\">\n @for (item of collapsedItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__collapsed-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"item.title\"\n (click)=\"activateCollapsedMenu(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </nav>\n } @else {\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"sd-sidebar-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto px-8 pb-12\">\n @if (pinnedMenus().length) {\n <section data-v3-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">\u0110\u00E3 ghim</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">G\u1EA7n \u0111\u00E2y</h2>\n <sd-layout-menu-tree [menus]=\"recentMenus()\" [activePath]=\"activePath()\" [showPin]=\"false\" (navigate)=\"navigateMenu($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n <section class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">T\u1EA5t c\u1EA3 menu</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n }\n\n <div class=\"p-8 border-top border-black200\">\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"isCollapsed()\"></sd-layout-user-menu>\n </div>\n </aside>\n\n <main class=\"sd-sidebar-v3__content\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v3{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v3__drawer{position:fixed;z-index:20;inset:0 auto 0 0;width:304px;transition:width .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__drawer{width:72px}.sd-sidebar-v3__content{min-height:100vh;margin-left:304px;transition:margin-left .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__content{margin-left:72px}.sd-sidebar-v3__icon-action,.sd-sidebar-v3__collapsed-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v3__icon-action:hover,.sd-sidebar-v3__collapsed-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v3__icon-action:focus-visible,.sd-sidebar-v3__collapsed-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v3__logo{width:28px;height:28px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-sidebar-v3__drawer,.sd-sidebar-v3__content{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1996
+ }
1997
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV3Component, decorators: [{
1998
+ type: Component,
1999
+ args: [{ selector: 'sidebar-v3', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header\n data-v3-header\n class=\"sd-sidebar-v3__header d-flex align-items-center gap-8 p-12 border-bottom border-black200\"\n [class.sd-sidebar-v3__header--collapsed]=\"isCollapsed()\"\n [class.justify-content-between]=\"!isCollapsed()\"\n [class.justify-content-center]=\"isCollapsed()\">\n @if (!isCollapsed()) {\n <div data-v3-brand class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n }\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__icon-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"isCollapsed() ? 'M\u1EDF r\u1ED9ng sidebar' : 'Thu g\u1ECDn sidebar'\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapsed()\">\n <sd-icon [name]=\"isCollapsed() ? 'chevron_right' : 'chevron_left'\"></sd-icon>\n </button>\n </header>\n\n @if (isCollapsed()) {\n <nav class=\"d-flex flex-column align-items-center gap-4 p-8 flex-1\" aria-label=\"Menu thu g\u1ECDn\">\n @for (item of collapsedItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__collapsed-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"item.title\"\n (click)=\"activateCollapsedMenu(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </nav>\n } @else {\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"sd-sidebar-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto px-8 pb-12\">\n @if (pinnedMenus().length) {\n <section data-v3-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">\u0110\u00E3 ghim</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">G\u1EA7n \u0111\u00E2y</h2>\n <sd-layout-menu-tree [menus]=\"recentMenus()\" [activePath]=\"activePath()\" [showPin]=\"false\" (navigate)=\"navigateMenu($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n <section class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">T\u1EA5t c\u1EA3 menu</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n }\n\n <div class=\"p-8 border-top border-black200\">\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"isCollapsed()\"></sd-layout-user-menu>\n </div>\n </aside>\n\n <main class=\"sd-sidebar-v3__content\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v3{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v3__drawer{position:fixed;z-index:20;inset:0 auto 0 0;width:304px;transition:width .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__drawer{width:72px}.sd-sidebar-v3__content{min-height:100vh;margin-left:304px;transition:margin-left .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__content{margin-left:72px}.sd-sidebar-v3__icon-action,.sd-sidebar-v3__collapsed-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v3__icon-action:hover,.sd-sidebar-v3__collapsed-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v3__icon-action:focus-visible,.sd-sidebar-v3__collapsed-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v3__logo{width:28px;height:28px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-sidebar-v3__drawer,.sd-sidebar-v3__content{transition:none}}\n"] }]
2000
+ }], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
2001
+
2002
+ class SidebarMobileV3Component {
2003
+ #router = inject(Router);
2004
+ #document = inject(DOCUMENT);
2005
+ #destroyRef = inject(DestroyRef);
2006
+ #navigationState = inject(SdLayoutNavigationStateService);
2007
+ trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
2008
+ #restoreFocusElement = null;
2009
+ #previousBodyOverflow = '';
2010
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
2011
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
2012
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
2013
+ isDrawerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDrawerOpen" }] : []));
2014
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
2015
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
2016
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : []));
2017
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : []));
2018
+ pinnedKeys = this.#navigationState.pinnedKeys;
2019
+ pinnedMenus = this.#navigationState.pinnedMenus;
2020
+ recentMenus = this.#navigationState.recentMenus;
2021
+ constructor() {
2022
+ effect(() => {
2023
+ const menus = this.menus();
2024
+ const maximum = resolveSidebarV3Recent(this.sidebar()).maxItems;
2025
+ untracked(() => this.#navigationState.hydrate(menus, maximum));
2026
+ });
2027
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
2028
+ if (!(event instanceof NavigationEnd))
2029
+ return;
2030
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
2031
+ this.closeFromNavigation();
2032
+ });
2033
+ this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
2034
+ }
2035
+ openDrawer(trigger) {
2036
+ this.#restoreFocusElement =
2037
+ trigger instanceof HTMLElement
2038
+ ? trigger
2039
+ : this.#document.activeElement instanceof HTMLElement
2040
+ ? this.#document.activeElement
2041
+ : (this.trigger()?.nativeElement ?? null);
2042
+ if (!this.isDrawerOpen()) {
2043
+ this.#previousBodyOverflow = this.#document.body.style.overflow;
2044
+ this.#document.body.style.overflow = 'hidden';
2045
+ }
2046
+ this.isDrawerOpen.set(true);
2047
+ }
2048
+ closeFromBackdrop() {
2049
+ this.#closeDrawer();
2050
+ }
2051
+ closeFromEscape() {
2052
+ this.#closeDrawer();
2053
+ }
2054
+ closeFromNavigation() {
2055
+ this.#closeDrawer();
2056
+ }
2057
+ onDrawerKeydown(event) {
2058
+ if (event.key !== 'Escape')
2059
+ return;
2060
+ event.preventDefault();
2061
+ this.closeFromEscape();
2062
+ }
2063
+ navigateMenu(menu) {
2064
+ if (!('path' in menu))
2065
+ return;
2066
+ this.#navigationState.recordRecent(menu, this.recentConfiguration());
2067
+ if (menu.path.includes('http')) {
2068
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
2069
+ this.closeFromNavigation();
2070
+ return;
2071
+ }
2072
+ void this.#router
2073
+ .navigate([menu.path.split('?')[0]], { queryParams: menu.queryParams ?? {}, state: { switchTab: true } })
2074
+ .then(navigated => {
2075
+ if (navigated)
2076
+ this.closeFromNavigation();
2077
+ });
2078
+ }
2079
+ onTogglePinned(menu) {
2080
+ this.#navigationState.togglePinned(menu);
2081
+ }
2082
+ #closeDrawer() {
2083
+ if (!this.isDrawerOpen())
2084
+ return;
2085
+ this.isDrawerOpen.set(false);
2086
+ this.searchText.set('');
2087
+ this.#releaseBodyScroll();
2088
+ (this.#restoreFocusElement ?? this.trigger()?.nativeElement)?.focus();
2089
+ this.#restoreFocusElement = null;
2090
+ }
2091
+ #releaseBodyScroll() {
2092
+ this.#document.body.style.overflow = this.#previousBodyOverflow;
2093
+ }
2094
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
2095
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarMobileV3Component, isStandalone: true, selector: "sidebar-mobile-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-mobile-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n @if (pinnedMenus().length) {\n <section data-v3-mobile-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">\u0110\u00E3 ghim</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-mobile-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">G\u1EA7n \u0111\u00E2y</h2>\n <sd-layout-menu-tree [menus]=\"recentMenus()\" [activePath]=\"activePath()\" [showPin]=\"false\" (navigate)=\"navigateMenu($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n <section class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">T\u1EA5t c\u1EA3 menu</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v3{min-height:100dvh;background:var(--sd-light)}.sd-sidebar-mobile-v3__topbar{min-height:56px}.sd-sidebar-mobile-v3__icon-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black500);cursor:pointer}.sd-sidebar-mobile-v3__icon-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v3__icon-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-mobile-v3__logo{width:28px;height:28px;object-fit:contain}.sd-sidebar-mobile-v3__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v3__drawer{position:fixed;z-index:50;inset:0 auto 0 0;width:min(88vw,360px);max-width:100%;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v3__search{position:sticky;z-index:2;top:-12px;margin:-12px;padding:12px;background:var(--sd-white, #fff)}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2096
+ }
2097
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV3Component, decorators: [{
2098
+ type: Component,
2099
+ args: [{ selector: 'sidebar-mobile-v3', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-mobile-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n @if (pinnedMenus().length) {\n <section data-v3-mobile-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">\u0110\u00E3 ghim</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-mobile-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">G\u1EA7n \u0111\u00E2y</h2>\n <sd-layout-menu-tree [menus]=\"recentMenus()\" [activePath]=\"activePath()\" [showPin]=\"false\" (navigate)=\"navigateMenu($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n <section class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">T\u1EA5t c\u1EA3 menu</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v3{min-height:100dvh;background:var(--sd-light)}.sd-sidebar-mobile-v3__topbar{min-height:56px}.sd-sidebar-mobile-v3__icon-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black500);cursor:pointer}.sd-sidebar-mobile-v3__icon-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v3__icon-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-mobile-v3__logo{width:28px;height:28px;object-fit:contain}.sd-sidebar-mobile-v3__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v3__drawer{position:fixed;z-index:50;inset:0 auto 0 0;width:min(88vw,360px);max-width:100%;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v3__search{position:sticky;z-index:2;top:-12px;margin:-12px;padding:12px;background:var(--sd-white, #fff)}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"] }]
2100
+ }], ctorParameters: () => [], propDecorators: { trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], sidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebar", required: true }] }] } });
2101
+
1299
2102
  class SdLayoutComponent {
1300
2103
  // ==========================================
1301
2104
  // INJECT SERVICES
1302
2105
  // ==========================================
1303
2106
  #menuPipe = inject(MenuPipe);
1304
2107
  #layoutService = inject(SdLayoutService);
2108
+ #responsiveService = inject(SdLayoutResponsiveService);
2109
+ #layoutConfiguration = inject(SD_LAYOUT_CONFIGURATION, { optional: true });
1305
2110
  // ==========================================
1306
2111
  // SIGNAL INPUTS
1307
2112
  // ==========================================
@@ -1312,13 +2117,34 @@ class SdLayoutComponent {
1312
2117
  // ==========================================
1313
2118
  userInfo = this.#layoutService.userInfo;
1314
2119
  sidebar = this.#layoutService.sidebar;
1315
- isMobileOrTablet = signal(BrowserUtilities.isMobile(), ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
2120
+ sidebarV1 = computed(() => {
2121
+ const sidebar = this.sidebar();
2122
+ return sidebar?.version === 1 ? sidebar : undefined;
2123
+ }, ...(ngDevMode ? [{ debugName: "sidebarV1" }] : []));
2124
+ sidebarV2 = computed(() => {
2125
+ const sidebar = this.sidebar();
2126
+ return sidebar?.version === 2 ? sidebar : undefined;
2127
+ }, ...(ngDevMode ? [{ debugName: "sidebarV2" }] : []));
2128
+ sidebarV3 = computed(() => {
2129
+ const sidebar = this.sidebar();
2130
+ return sidebar?.version === 3 ? sidebar : undefined;
2131
+ }, ...(ngDevMode ? [{ debugName: "sidebarV3" }] : []));
2132
+ mobileBreakpoint = normalizeLayoutMobileBreakpoint(this.#layoutConfiguration?.mobileBreakpoint);
2133
+ isMobile = computed(() => this.#responsiveService.isMobile(this.mobileBreakpoint), ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
1316
2134
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1317
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutComponent, isStandalone: true, selector: "sd-layout", inputs: { menusInput: { classPropertyName: "menusInput", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar?.version === 1) {\n @if (!isMobileOrTablet()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n", styles: [""], dependencies: [{ kind: "component", type: SidebarV1Component, selector: "sidebar-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarMobileV1Component, selector: "sidebar-mobile-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2135
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutComponent, isStandalone: true, selector: "sd-layout", inputs: { menusInput: { classPropertyName: "menusInput", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: SidebarV1Component, selector: "sidebar-v1", inputs: ["menus", "userInfo", "sidebar", "isMobile"] }, { kind: "component", type: SidebarMobileV1Component, selector: "sidebar-mobile-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarV2Component, selector: "sidebar-v2", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarMobileV2Component, selector: "sidebar-mobile-v2", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarV3Component, selector: "sidebar-v3", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarMobileV3Component, selector: "sidebar-mobile-v3", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1318
2136
  }
1319
2137
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, decorators: [{
1320
2138
  type: Component,
1321
- args: [{ selector: 'sd-layout', imports: [SidebarV1Component, SidebarMobileV1Component, NgTemplateOutlet], standalone: true, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar?.version === 1) {\n @if (!isMobileOrTablet()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n" }]
2139
+ args: [{ selector: 'sd-layout', imports: [
2140
+ SidebarV1Component,
2141
+ SidebarMobileV1Component,
2142
+ SidebarV2Component,
2143
+ SidebarMobileV2Component,
2144
+ SidebarV3Component,
2145
+ SidebarMobileV3Component,
2146
+ NgTemplateOutlet,
2147
+ ], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"] }]
1322
2148
  }], propDecorators: { menusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }] } });
1323
2149
 
1324
2150
  // End
@@ -1440,7 +2266,7 @@ const routes = [
1440
2266
  ];
1441
2267
  class HomeRoutingModule {
1442
2268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1443
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [i1$5.RouterModule], exports: [RouterModule] });
2269
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [i1$3.RouterModule], exports: [RouterModule] });
1444
2270
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
1445
2271
  }
1446
2272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, decorators: [{
@@ -1515,7 +2341,7 @@ const Routes$2 = [{ path: '', component: RootComponent$1 }];
1515
2341
 
1516
2342
  class NotFoundModule {
1517
2343
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1518
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$5.RouterModule] });
2344
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$3.RouterModule] });
1519
2345
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, RouterModule.forChild(Routes$2)] });
1520
2346
  }
1521
2347
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, decorators: [{
@@ -1577,7 +2403,7 @@ const Routes$1 = [{ path: '', component: RootComponent }];
1577
2403
 
1578
2404
  class ForbiddenModule {
1579
2405
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1580
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$5.RouterModule] });
2406
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$3.RouterModule] });
1581
2407
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, RouterModule.forChild(Routes$1)] });
1582
2408
  }
1583
2409
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, decorators: [{
@@ -1615,7 +2441,7 @@ const Routes = [
1615
2441
 
1616
2442
  class SdLayoutModule {
1617
2443
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1618
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [i1$5.RouterModule] });
2444
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [i1$3.RouterModule] });
1619
2445
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [RouterModule.forChild(Routes)] });
1620
2446
  }
1621
2447
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, decorators: [{
@@ -1631,5 +2457,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1631
2457
  * Generated bundle index. Do not edit.
1632
2458
  */
1633
2459
 
1634
- export { ForbiddenModule, HomeModule, NotFoundModule, SD_LAYOUT_CONFIGURATION, SdLayoutComponent, SdLayoutModule, SdLayoutService, SdLayoutStorageService, SdPageComponent, SidebarMobileOverlayComponent, SidebarMobileV1Component };
2460
+ export { DEFAULT_LAYOUT_MOBILE_BREAKPOINT, ForbiddenModule, HighlightSearchPipe, HomeModule, MenuFocusPipe, MenuPipe, NotFoundModule, SD_LAYOUT_CONFIGURATION, SD_LAYOUT_VIEWPORT, SdLayoutComponent, SdLayoutMenuTreeComponent, SdLayoutModule, SdLayoutNavigationStateService, SdLayoutResponsiveService, SdLayoutService, SdLayoutStorageService, SdLayoutUserMenuComponent, SdPageComponent, SidebarMobileOverlayComponent, SidebarMobileV1Component, SidebarMobileV2Component, SidebarMobileV3Component, SidebarV2Component, SidebarV3Component, flattenMenuLeaves, getMenuStableKey, normalizeLayoutMobileBreakpoint, normalizeSidebarConfiguration, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, searchMenuLeaves, selectPrimaryMenuGroups };
1635
2461
  //# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map