@sdcorejs/angular 21.1.3 → 21.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/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  3. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  4. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  5. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  6. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  7. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  8. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  9. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  10. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  11. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  12. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  13. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  14. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  15. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  16. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
  17. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  18. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
  19. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  21. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  22. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  23. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  24. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  25. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  27. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
  29. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  31. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  33. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
  35. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  37. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
  39. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  40. package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
  41. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
  43. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  45. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  47. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
  49. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -71
  51. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
  53. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
  55. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
  57. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
  59. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
  61. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  63. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  64. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
  65. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
  67. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
  69. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  71. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -66
  73. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
  75. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -64
  77. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
  79. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +277 -0
  81. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-time.mjs +307 -0
  83. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  84. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  85. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  86. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  87. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-i18n.mjs +375 -10
  89. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  91. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-modules-layout.mjs +994 -168
  93. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  95. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  97. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  99. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  100. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  101. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  102. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  103. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  104. package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
  105. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  107. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  109. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  110. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  111. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  113. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  114. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  115. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  116. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  117. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  118. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  119. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  120. package/package.json +49 -1
  121. package/types/sdcorejs-angular-components-audit-diff.d.ts +74 -0
  122. package/types/sdcorejs-angular-components-badge.d.ts +1 -1
  123. package/types/sdcorejs-angular-components-breadcrumb.d.ts +53 -0
  124. package/types/sdcorejs-angular-components-data-state.d.ts +44 -0
  125. package/types/sdcorejs-angular-components-document-builder.d.ts +5 -5
  126. package/types/sdcorejs-angular-components-form-generic.d.ts +67 -104
  127. package/types/sdcorejs-angular-components-import-excel.d.ts +4 -14
  128. package/types/sdcorejs-angular-components-job-progress.d.ts +38 -0
  129. package/types/sdcorejs-angular-components-mini-editor.d.ts +9 -9
  130. package/types/sdcorejs-angular-components-modal.d.ts +7 -1
  131. package/types/sdcorejs-angular-components-preview.d.ts +181 -28
  132. package/types/sdcorejs-angular-components-side-drawer.d.ts +7 -1
  133. package/types/sdcorejs-angular-components-tab.d.ts +9 -2
  134. package/types/sdcorejs-angular-components-table.d.ts +8 -7
  135. package/types/sdcorejs-angular-components-tree.d.ts +24 -2
  136. package/types/sdcorejs-angular-components-upload-file.d.ts +5 -4
  137. package/types/sdcorejs-angular-components.d.ts +4 -0
  138. package/types/sdcorejs-angular-directives.d.ts +10 -8
  139. package/types/sdcorejs-angular-forms-autocomplete.d.ts +3 -4
  140. package/types/sdcorejs-angular-forms-checkbox.d.ts +4 -8
  141. package/types/sdcorejs-angular-forms-date-range.d.ts +6 -7
  142. package/types/sdcorejs-angular-forms-date.d.ts +2 -2
  143. package/types/sdcorejs-angular-forms-datetime.d.ts +2 -2
  144. package/types/sdcorejs-angular-forms-entity-picker.d.ts +128 -0
  145. package/types/sdcorejs-angular-forms-input-number.d.ts +1 -1
  146. package/types/sdcorejs-angular-forms-input.d.ts +74 -11
  147. package/types/sdcorejs-angular-forms-models.d.ts +86 -3
  148. package/types/sdcorejs-angular-forms-radio.d.ts +12 -8
  149. package/types/sdcorejs-angular-forms-switch.d.ts +4 -7
  150. package/types/sdcorejs-angular-forms-time-range.d.ts +98 -0
  151. package/types/sdcorejs-angular-forms-time.d.ts +117 -0
  152. package/types/sdcorejs-angular-forms-tree-select.d.ts +76 -0
  153. package/types/sdcorejs-angular-forms.d.ts +15 -5
  154. package/types/sdcorejs-angular-i18n.d.ts +72 -2
  155. package/types/sdcorejs-angular-interceptors.d.ts +5 -6
  156. package/types/sdcorejs-angular-modules-layout.d.ts +354 -17
  157. package/types/sdcorejs-angular-pipes.d.ts +2 -2
  158. package/types/sdcorejs-angular-services-api.d.ts +68 -24
  159. package/types/sdcorejs-angular-services-cache.d.ts +52 -16
  160. package/types/sdcorejs-angular-services-confirm.d.ts +3 -2
  161. package/types/sdcorejs-angular-services-docx.d.ts +2 -3
  162. package/types/sdcorejs-angular-services-loading.d.ts +16 -8
  163. package/types/sdcorejs-angular-services-persistence.d.ts +159 -0
  164. package/types/sdcorejs-angular-services-storage.d.ts +26 -8
  165. package/types/sdcorejs-angular-services-task.d.ts +104 -0
  166. package/types/sdcorejs-angular-services-unsaved-changes.d.ts +83 -0
  167. package/types/sdcorejs-angular-services-viewport.d.ts +42 -0
  168. package/types/sdcorejs-angular-services.d.ts +4 -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: "21.2.17", 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: "21.2.17", 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: "21.2.17", ngImport: i0, type: SdLayoutStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
70
182
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutStorageService, providedIn: 'root' });
71
183
  }
72
184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutStorageService, decorators: [{
@@ -74,10 +186,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", ngImport: i0, type: SdLayoutResponsiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
237
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutResponsiveService, providedIn: 'root' });
238
+ }
239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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" }] : /* istanbul ignore next */ []));
247
+ pinnedKeys = signal([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : /* istanbul ignore next */ []));
248
+ recentKeys = signal([], ...(ngDevMode ? [{ debugName: "recentKeys" }] : /* istanbul ignore next */ []));
249
+ pinnedMenus = computed(() => resolveMenuKeys(this.#menus(), this.pinnedKeys()), ...(ngDevMode ? [{ debugName: "pinnedMenus" }] : /* istanbul ignore next */ []));
250
+ recentMenus = computed(() => resolveMenuKeys(this.#menus(), this.recentKeys()), ...(ngDevMode ? [{ debugName: "recentMenus" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SdLayoutNavigationStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
284
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutNavigationStateService, providedIn: 'root' });
285
+ }
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", ngImport: i0, type: MenuPipe, deps: [{ token: i1$1.SdPermissionService }], target: i0.ɵɵFactoryTarget.Pipe });
455
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MenuPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
245
456
  static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: MenuPipe, isStandalone: true, name: "menu" });
246
457
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MenuPipe, providedIn: 'root' });
247
458
  }
@@ -255,7 +466,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
555
+ menu = viewChild('menu', ...(ngDevMode ? [{ debugName: "menu" }] : /* istanbul ignore next */ []));
556
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
557
+ signout = input(...(ngDevMode ? [undefined, { debugName: "signout" }] : /* istanbul ignore next */ []));
558
+ changePassword = input(...(ngDevMode ? [undefined, { debugName: "changePassword" }] : /* istanbul ignore next */ []));
559
+ updateProfile = input(...(ngDevMode ? [undefined, { debugName: "updateProfile" }] : /* istanbul ignore next */ []));
560
+ setting = input(...(ngDevMode ? [undefined, { debugName: "setting" }] : /* istanbul ignore next */ []));
561
+ notification = input(...(ngDevMode ? [undefined, { debugName: "notification" }] : /* istanbul ignore next */ []));
562
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
563
+ presentation = input('disclosure', ...(ngDevMode ? [{ debugName: "presentation" }] : /* istanbul ignore next */ []));
564
+ opened = output();
565
+ closed = output();
566
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
567
+ #observableNotificationCount = signal(undefined, ...(ngDevMode ? [{ debugName: "#observableNotificationCount" }] : /* istanbul ignore next */ []));
568
+ avatar = computed(() => {
569
+ const user = this.userInfo();
570
+ return user.avatar || user.fullName || user.username || user.email;
571
+ }, ...(ngDevMode ? [{ debugName: "avatar" }] : /* istanbul ignore next */ []));
572
+ displayName = computed(() => this.userInfo().fullName || this.userInfo().username || this.userInfo().email || 'User', ...(ngDevMode ? [{ debugName: "displayName" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
578
+ signoutAction = computed(() => this.signout() ?? this.#configuration?.signout, ...(ngDevMode ? [{ debugName: "signoutAction" }] : /* istanbul ignore next */ []));
579
+ changePasswordAction = computed(() => this.changePassword() ?? this.#configuration?.changePassword, ...(ngDevMode ? [{ debugName: "changePasswordAction" }] : /* istanbul ignore next */ []));
580
+ updateProfileAction = computed(() => this.updateProfile() ?? this.#configuration?.updateProfile, ...(ngDevMode ? [{ debugName: "updateProfileAction" }] : /* istanbul ignore next */ []));
581
+ settingAction = computed(() => this.setting() ?? this.#configuration?.setting, ...(ngDevMode ? [{ debugName: "settingAction" }] : /* istanbul ignore next */ []));
582
+ notificationConfiguration = computed(() => this.notification() ?? this.#configuration?.notification, ...(ngDevMode ? [{ debugName: "notificationConfiguration" }] : /* istanbul ignore next */ []));
583
+ notificationAction = computed(() => this.notificationConfiguration()?.action, ...(ngDevMode ? [{ debugName: "notificationAction" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
589
+ notificationBadge = computed(() => {
590
+ const count = this.notificationCount();
591
+ return count > 99 ? '99+' : count > 0 ? String(count) : undefined;
592
+ }, ...(ngDevMode ? [{ debugName: "notificationBadge" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SdLayoutUserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
694
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
347
703
  isMenuLock = input(false, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : /* istanbul ignore next */ []));
348
704
  isShowSidebar = input(false, ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : /* istanbul ignore next */ []));
349
705
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
380
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
400
741
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
401
742
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
743
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
412
- currentPath = signal(window.location.pathname, ...(ngDevMode ? [{ debugName: "currentPath" }] : /* istanbul ignore next */ []));
752
+ currentPath = signal(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "currentPath" }] : /* istanbul ignore next */ []));
413
753
  searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
414
754
  titleMenuGroup = signal('', ...(ngDevMode ? [{ debugName: "titleMenuGroup" }] : /* istanbul ignore next */ []));
415
755
  idMenuGroupActive = signal('', ...(ngDevMode ? [{ debugName: "idMenuGroupActive" }] : /* istanbul ignore next */ []));
416
756
  menusByGroup = signal([], ...(ngDevMode ? [{ debugName: "menusByGroup" }] : /* istanbul ignore next */ []));
417
757
  #hoveredMenuNodeKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredMenuNodeKey" }] : /* istanbul ignore next */ []));
418
758
  #pinIconHoverTimerId = signal(null, ...(ngDevMode ? [{ debugName: "#pinIconHoverTimerId" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
762
+ children: this.#navigationState.pinnedMenus(),
763
+ }), ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : /* istanbul ignore next */ []));
424
764
  // ==========================================
425
765
  // COMPUTED STATE
426
766
  // ==========================================
427
767
  totalMenuInMenusByGroup = computed(() => this.#getTotalMenus(this.menusByGroup()), ...(ngDevMode ? [{ debugName: "totalMenuInMenusByGroup" }] : /* istanbul ignore next */ []));
768
+ logoUrl = computed(() => this.sidebar().logoUrl?.trim() || undefined, ...(ngDevMode ? [{ debugName: "logoUrl" }] : /* istanbul ignore next */ []));
428
769
  pinnedNodeKeys = computed(() => new Set(this.pinnedMenuGroup().children?.map(m => this.#getMenuNodeKey(m)) ?? []), ...(ngDevMode ? [{ debugName: "pinnedNodeKeys" }] : /* istanbul ignore next */ []));
429
770
  isPinnedMenuGroupActive = computed(() => this.idMenuGroupActive() === this.pinnedMenuGroup().id, ...(ngDevMode ? [{ debugName: "isPinnedMenuGroupActive" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", ngImport: i0, type: SidebarComponent, decorators: [{
906
1236
  type: Component,
@@ -919,8 +1249,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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" }] : /* istanbul ignore next */ []));
935
1265
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
936
1266
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
1267
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
937
1268
  // ==========================================
938
1269
  // STATE SIGNALS
939
1270
  // ==========================================
940
- isMobileOrTablet = signal(BrowserUtilities.isMobile(), ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
1274
+ isShowSidebar = signal(this.isMobile() ? false : (this.#layoutStorageService.menuLockStatus?.get() ?? true), ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : /* istanbul ignore next */ []));
945
1275
  onhover = signal(false, ...(ngDevMode ? [{ debugName: "onhover" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarV1Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1043
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1059
1389
  isMenuLock = input(false, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : /* istanbul ignore next */ []));
1060
1390
  isShowSidebar = input(false, ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : /* istanbul ignore next */ []));
@@ -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" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1606
+ #pinHoverTimerId;
1607
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
1608
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
1609
+ activePath = input('', ...(ngDevMode ? [{ debugName: "activePath" }] : /* istanbul ignore next */ []));
1610
+ pinnedKeys = input([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : /* istanbul ignore next */ []));
1611
+ showPin = input(true, ...(ngDevMode ? [{ debugName: "showPin" }] : /* istanbul ignore next */ []));
1612
+ pinVisibility = input('hover', ...(ngDevMode ? [{ debugName: "pinVisibility" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SdLayoutMenuTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1683
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1692
+ placeholder = input.required(...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1693
+ autoId = input.required(...(ngDevMode ? [{ debugName: "autoId" }] : /* istanbul ignore next */ []));
1694
+ sdChange = output();
1695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutSearchFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1709
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
1710
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
1711
+ activeGroupKey = signal('', ...(ngDevMode ? [{ debugName: "activeGroupKey" }] : /* istanbul ignore next */ []));
1712
+ isFlyoutOpen = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutOpen" }] : /* istanbul ignore next */ []));
1713
+ isFlyoutLocked = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutLocked" }] : /* istanbul ignore next */ []));
1714
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
1715
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : /* istanbul ignore next */ []));
1716
+ interaction = computed(() => resolveSidebarV2Interaction(this.sidebar()), ...(ngDevMode ? [{ debugName: "interaction" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
1720
+ activeGroup = computed(() => this.railItems().find(item => item.key === this.activeGroupKey())?.menu, ...(ngDevMode ? [{ debugName: "activeGroup" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1816
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1831
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
1832
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
1833
+ primaryMenus = computed(() => selectPrimaryMenuGroups(this.menus(), this.sidebar().primaryMenuIds), ...(ngDevMode ? [{ debugName: "primaryMenus" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
1838
+ isSheetOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSheetOpen" }] : /* istanbul ignore next */ []));
1839
+ sheetGroupKey = signal('', ...(ngDevMode ? [{ debugName: "sheetGroupKey" }] : /* istanbul ignore next */ []));
1840
+ sheetTitle = signal('', ...(ngDevMode ? [{ debugName: "sheetTitle" }] : /* istanbul ignore next */ []));
1841
+ sheetMenus = signal([], ...(ngDevMode ? [{ debugName: "sheetMenus" }] : /* istanbul ignore next */ []));
1842
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
1843
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarMobileV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1922
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
1936
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
1937
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
1938
+ isCollapsed = signal(false, ...(ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []));
1939
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
1940
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : /* istanbul ignore next */ []));
1941
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : /* istanbul ignore next */ []));
1942
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : /* istanbul ignore next */ []));
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" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1995
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
2008
+ #restoreFocusElement = null;
2009
+ #previousBodyOverflow = '';
2010
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
2011
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : /* istanbul ignore next */ []));
2012
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : /* istanbul ignore next */ []));
2013
+ isDrawerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDrawerOpen" }] : /* istanbul ignore next */ []));
2014
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
2015
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : /* istanbul ignore next */ []));
2016
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : /* istanbul ignore next */ []));
2017
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: SidebarMobileV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
2095
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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" }] : /* istanbul ignore next */ []));
2120
+ sidebarV1 = computed(() => {
2121
+ const sidebar = this.sidebar();
2122
+ return sidebar?.version === 1 ? sidebar : undefined;
2123
+ }, ...(ngDevMode ? [{ debugName: "sidebarV1" }] : /* istanbul ignore next */ []));
2124
+ sidebarV2 = computed(() => {
2125
+ const sidebar = this.sidebar();
2126
+ return sidebar?.version === 2 ? sidebar : undefined;
2127
+ }, ...(ngDevMode ? [{ debugName: "sidebarV2" }] : /* istanbul ignore next */ []));
2128
+ sidebarV3 = computed(() => {
2129
+ const sidebar = this.sidebar();
2130
+ return sidebar?.version === 3 ? sidebar : undefined;
2131
+ }, ...(ngDevMode ? [{ debugName: "sidebarV3" }] : /* istanbul ignore next */ []));
2132
+ mobileBreakpoint = normalizeLayoutMobileBreakpoint(this.#layoutConfiguration?.mobileBreakpoint);
2133
+ isMobile = computed(() => this.#responsiveService.isMobile(this.mobileBreakpoint), ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
1316
2134
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1317
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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: "21.2.17", ngImport: i0, type: HomeRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1443
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: HomeRoutingModule, imports: [i1$5.RouterModule], exports: [RouterModule] });
2269
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: HomeRoutingModule, imports: [i1$3.RouterModule], exports: [RouterModule] });
1444
2270
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HomeRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
1445
2271
  }
1446
2272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", ngImport: i0, type: NotFoundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1518
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$5.RouterModule] });
2344
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$3.RouterModule] });
1519
2345
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NotFoundModule, imports: [CommonModule, RouterModule.forChild(Routes$2)] });
1520
2346
  }
1521
2347
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", ngImport: i0, type: ForbiddenModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1580
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$5.RouterModule] });
2406
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$3.RouterModule] });
1581
2407
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, RouterModule.forChild(Routes$1)] });
1582
2408
  }
1583
2409
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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: "21.2.17", ngImport: i0, type: SdLayoutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1618
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutModule, imports: [i1$5.RouterModule] });
2444
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutModule, imports: [i1$3.RouterModule] });
1619
2445
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutModule, imports: [RouterModule.forChild(Routes)] });
1620
2446
  }
1621
2447
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLayoutModule, decorators: [{
@@ -1631,5 +2457,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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