@sdcorejs/angular 20.1.2 → 20.1.4

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 (177) hide show
  1. package/README.md +127 -807
  2. package/components/audit-diff/index.d.ts +74 -0
  3. package/components/badge/index.d.ts +1 -1
  4. package/components/breadcrumb/index.d.ts +53 -0
  5. package/components/data-state/index.d.ts +44 -0
  6. package/components/document-builder/index.d.ts +5 -5
  7. package/components/form-generic/index.d.ts +89 -103
  8. package/components/import-excel/index.d.ts +4 -14
  9. package/components/index.d.ts +4 -0
  10. package/components/job-progress/index.d.ts +38 -0
  11. package/components/mini-editor/index.d.ts +9 -9
  12. package/components/modal/index.d.ts +7 -1
  13. package/components/preview/index.d.ts +181 -28
  14. package/components/side-drawer/index.d.ts +7 -1
  15. package/components/tab/index.d.ts +9 -2
  16. package/components/table/index.d.ts +8 -7
  17. package/components/tree/index.d.ts +24 -2
  18. package/components/upload-file/index.d.ts +5 -4
  19. package/directives/index.d.ts +10 -8
  20. package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  21. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  23. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  24. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  25. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  27. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  28. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  29. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  30. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  31. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  33. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +633 -886
  35. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +26 -41
  37. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  39. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  40. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  41. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  43. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  45. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +9 -17
  47. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  49. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +76 -52
  53. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  55. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-table.mjs +123 -89
  57. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-tree.mjs +188 -18
  59. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +17 -23
  61. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  63. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  65. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +9 -8
  67. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
  69. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +8 -4
  71. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-chip.mjs +8 -4
  73. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +18 -14
  75. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-date.mjs +48 -70
  77. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +62 -528
  79. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  81. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +5 -3
  83. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +16 -11
  85. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-forms-input.mjs +297 -59
  87. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  89. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
  91. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-forms-select.mjs +8 -21
  93. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
  95. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +8 -6
  97. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
  99. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  100. package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
  101. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  102. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  103. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  104. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  105. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-i18n.mjs +355 -10
  107. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  109. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
  111. package/fesm2022/sdcorejs-angular-modules-layout.mjs +861 -80
  112. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  113. package/fesm2022/sdcorejs-angular-modules.mjs +0 -1
  114. package/fesm2022/sdcorejs-angular-modules.mjs.map +1 -1
  115. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  116. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  117. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  118. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  119. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  120. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  121. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  122. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  123. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  124. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  125. package/fesm2022/sdcorejs-angular-services-loading.mjs +340 -66
  126. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  127. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  128. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  129. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  130. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  131. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  132. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  133. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  134. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  135. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  136. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  137. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  138. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  139. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  140. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  141. package/forms/autocomplete/index.d.ts +3 -4
  142. package/forms/checkbox/index.d.ts +4 -8
  143. package/forms/date/index.d.ts +3 -3
  144. package/forms/date-range/index.d.ts +6 -7
  145. package/forms/datetime/index.d.ts +3 -3
  146. package/forms/entity-picker/index.d.ts +128 -0
  147. package/forms/index.d.ts +15 -5
  148. package/forms/input/index.d.ts +76 -11
  149. package/forms/input-color/index.d.ts +3 -1
  150. package/forms/input-number/index.d.ts +4 -2
  151. package/forms/models/index.d.ts +86 -3
  152. package/forms/radio/index.d.ts +12 -8
  153. package/forms/switch/index.d.ts +4 -7
  154. package/forms/time/index.d.ts +117 -0
  155. package/forms/time-range/index.d.ts +98 -0
  156. package/forms/tree-select/index.d.ts +76 -0
  157. package/i18n/index.d.ts +68 -2
  158. package/interceptors/index.d.ts +5 -6
  159. package/modules/icon/index.d.ts +2 -3
  160. package/modules/index.d.ts +0 -1
  161. package/modules/layout/index.d.ts +288 -15
  162. package/package.json +50 -5
  163. package/pipes/index.d.ts +2 -2
  164. package/services/api/index.d.ts +68 -24
  165. package/services/cache/index.d.ts +52 -16
  166. package/services/confirm/index.d.ts +3 -2
  167. package/services/docx/index.d.ts +2 -3
  168. package/services/index.d.ts +4 -0
  169. package/services/loading/index.d.ts +16 -8
  170. package/services/persistence/index.d.ts +159 -0
  171. package/services/storage/index.d.ts +26 -8
  172. package/services/task/index.d.ts +104 -0
  173. package/services/unsaved-changes/index.d.ts +83 -0
  174. package/services/viewport/index.d.ts +42 -0
  175. package/fesm2022/sdcorejs-angular-modules-authom.mjs +0 -360
  176. package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +0 -1
  177. package/modules/authom/index.d.ts +0 -70
@@ -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, output, DestroyRef, untracked, ChangeDetectionStrategy, viewChild, NgModule } from '@angular/core';
3
+ import * as i1$1 from '@angular/common';
4
+ import { DOCUMENT, CommonModule, NgTemplateOutlet } from '@angular/common';
5
+ import * as i1$4 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$2 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';
@@ -25,9 +26,11 @@ import { SdInput, SdSuffixDefDirective } from '@sdcorejs/angular/forms';
25
26
  import { SdSafeHtmlPipe } from '@sdcorejs/angular/pipes';
26
27
  import * as i2 from '@angular/material/button';
27
28
  import { MatButtonModule } from '@angular/material/button';
28
- import * as i1$2 from '@angular/material/menu';
29
+ import * as i1 from '@angular/material/menu';
29
30
  import { MatMenuModule } from '@angular/material/menu';
30
31
  import { SdIcon } from '@sdcorejs/angular/modules/icon';
32
+ import * as i1$3 from '@angular/cdk/a11y';
33
+ import { A11yModule } from '@angular/cdk/a11y';
31
34
 
32
35
  class SdPageComponent {
33
36
  el = inject(ElementRef);
@@ -49,24 +52,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
49
52
  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
53
  }], 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
54
 
55
+ function getMenuStableKey(menu, ancestors = []) {
56
+ if (!menu || typeof menu !== 'object')
57
+ return '';
58
+ if (typeof menu.id === 'string' && menu.id.trim())
59
+ return `id:${menu.id.trim()}`;
60
+ if ('path' in menu && typeof menu.path === 'string' && menu.path.trim())
61
+ return `path:${menu.path.trim()}`;
62
+ const title = typeof menu.title === 'string' ? menu.title.trim() : '';
63
+ const segments = [...ancestors, title]
64
+ .map(segment => StringUtilities.changeAliasLowerCase(segment).trim().replace(/\s+/g, '-'))
65
+ .filter(Boolean);
66
+ return segments.length ? `group:${segments.join('/')}` : '';
67
+ }
68
+ function searchMenuLeaves(menus, query) {
69
+ const leaves = flattenMenuLeaves(menus);
70
+ const normalizedQuery = query?.trim();
71
+ if (!normalizedQuery)
72
+ return leaves;
73
+ return leaves.filter(menu => [menu.title, menu.tooltipTitle].some(value => value && StringUtilities.aliasIncludes(value, normalizedQuery)));
74
+ }
75
+ function selectPrimaryMenuGroups(menus, requestedIds, maximum = 3) {
76
+ const limit = Number.isFinite(maximum) && maximum > 0 ? Math.min(3, Math.floor(maximum)) : 3;
77
+ const byId = new Map(menus.filter(menu => typeof menu.id === 'string').map(menu => [menu.id, menu]));
78
+ const selected = [];
79
+ const selectedKeys = new Set();
80
+ for (const id of requestedIds ?? []) {
81
+ const menu = byId.get(id);
82
+ const key = getMenuStableKey(menu);
83
+ if (!menu || !key || selectedKeys.has(key))
84
+ continue;
85
+ selected.push(menu);
86
+ selectedKeys.add(key);
87
+ if (selected.length === limit)
88
+ return selected;
89
+ }
90
+ for (const menu of menus) {
91
+ const key = getMenuStableKey(menu);
92
+ if (!key || selectedKeys.has(key))
93
+ continue;
94
+ selected.push(menu);
95
+ selectedKeys.add(key);
96
+ if (selected.length === limit)
97
+ break;
98
+ }
99
+ return selected;
100
+ }
101
+ function resolveMenuKeys(menus, keys) {
102
+ const menuByKey = new Map();
103
+ visitMenus(menus, [], (menu, ancestors) => {
104
+ const key = getMenuStableKey(menu, ancestors);
105
+ if (key && !menuByKey.has(key))
106
+ menuByKey.set(key, menu);
107
+ });
108
+ return [...new Set(keys)].map(key => menuByKey.get(key)).filter((menu) => !!menu);
109
+ }
110
+ function flattenMenuLeaves(menus) {
111
+ const leaves = [];
112
+ visitMenus(menus, [], menu => {
113
+ if ('path' in menu && typeof menu.path === 'string' && menu.path)
114
+ leaves.push(menu);
115
+ });
116
+ return leaves;
117
+ }
118
+ function visitMenus(menus, ancestors, visitor) {
119
+ for (const menu of menus ?? []) {
120
+ if (!menu || typeof menu !== 'object')
121
+ continue;
122
+ visitor(menu, ancestors);
123
+ if ('children' in menu && Array.isArray(menu.children)) {
124
+ const title = typeof menu.title === 'string' && menu.title.trim() ? menu.title.trim() : getMenuStableKey(menu, ancestors);
125
+ visitMenus(menu.children, title ? [...ancestors, title] : ancestors, visitor);
126
+ }
127
+ }
128
+ }
129
+
52
130
  class SdLayoutStorageService {
53
- sdStorageService;
54
- // Thông tin state của sidebar
55
- isShowSidebar;
56
- menuLockStatus;
57
- lastActiveMenuGroupId;
58
- pinnedMenuGroup;
59
- // End
60
- constructor(sdStorageService) {
61
- this.sdStorageService = sdStorageService;
62
- // State sidebar
63
- this.isShowSidebar = this.sdStorageService.create('cb07c316-ed6d-4620-9e92-53dbef6aa983');
64
- this.menuLockStatus = this.sdStorageService.create('2c2f4816-18b3-4ec3-8177-7b90bff036c3');
65
- this.lastActiveMenuGroupId = this.sdStorageService.create('2e6961d0-3380-4a94-a6a1-38837560cd96');
66
- this.pinnedMenuGroup = this.sdStorageService.create('e81da122-5dab-4250-b309-64197fb19d44');
67
- // End
68
- }
69
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, deps: [{ token: i1.SdStorageService }], target: i0.ɵɵFactoryTarget.Injectable });
131
+ #sdStorageService = inject(SdStorageService);
132
+ isShowSidebar = this.#sdStorageService.create('cb07c316-ed6d-4620-9e92-53dbef6aa983');
133
+ menuLockStatus = this.#sdStorageService.create('2c2f4816-18b3-4ec3-8177-7b90bff036c3');
134
+ lastActiveMenuGroupId = this.#sdStorageService.create('2e6961d0-3380-4a94-a6a1-38837560cd96');
135
+ pinnedMenuGroup = this.#sdStorageService.create('e81da122-5dab-4250-b309-64197fb19d44');
136
+ pinnedMenuKeys = this.#sdStorageService.create('4bdc7f80-9ff4-4fe0-908f-6c9f7fe4100a');
137
+ recentMenuKeys = this.#sdStorageService.create('fb989dc1-f6e7-4b62-a611-861a8f214a51');
138
+ versionStates = this.#sdStorageService.create('ca8e4261-fbd4-4209-af88-2dbe278c1a1b');
139
+ readPinnedMenuKeys(menus) {
140
+ const storedKeys = this.pinnedMenuKeys.has() ? this.pinnedMenuKeys.get() : this.#getLegacyPinnedKeys();
141
+ const validKeys = this.#resolveValidKeys(menus, storedKeys ?? []);
142
+ if (!this.pinnedMenuKeys.has() || !this.#sameKeys(storedKeys ?? [], validKeys)) {
143
+ this.pinnedMenuKeys.set(validKeys);
144
+ }
145
+ return validKeys;
146
+ }
147
+ writePinnedMenuKeys(keys) {
148
+ this.pinnedMenuKeys.set([...new Set(keys.filter(Boolean))]);
149
+ }
150
+ readRecentMenuKeys(menus, maximum = 5) {
151
+ const storedKeys = this.recentMenuKeys.get() ?? [];
152
+ const limit = Number.isFinite(maximum) && maximum > 0 ? Math.floor(maximum) : 5;
153
+ const validKeys = this.#resolveValidKeys(menus, storedKeys).slice(0, limit);
154
+ if (!this.#sameKeys(storedKeys, validKeys))
155
+ this.recentMenuKeys.set(validKeys);
156
+ return validKeys;
157
+ }
158
+ writeRecentMenuKeys(keys) {
159
+ this.recentMenuKeys.set([...new Set(keys.filter(Boolean))]);
160
+ }
161
+ readVersionState(version) {
162
+ return this.versionStates.get()?.[String(version)] ?? {};
163
+ }
164
+ patchVersionState(version, patch) {
165
+ const states = this.versionStates.get() ?? {};
166
+ const nextState = { ...(states[String(version)] ?? {}), ...patch };
167
+ this.versionStates.set({ ...states, [String(version)]: nextState });
168
+ return nextState;
169
+ }
170
+ #getLegacyPinnedKeys() {
171
+ const legacyGroup = this.pinnedMenuGroup.get();
172
+ if (!legacyGroup || !Array.isArray(legacyGroup.children))
173
+ return [];
174
+ return legacyGroup.children.map(menu => getMenuStableKey(menu)).filter(Boolean);
175
+ }
176
+ #resolveValidKeys(menus, keys) {
177
+ return resolveMenuKeys(menus, keys)
178
+ .map(menu => getMenuStableKey(menu))
179
+ .filter(Boolean);
180
+ }
181
+ #sameKeys(left, right) {
182
+ return left.length === right.length && left.every((key, index) => key === right[index]);
183
+ }
184
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
70
185
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, providedIn: 'root' });
71
186
  }
72
187
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutStorageService, decorators: [{
@@ -74,10 +189,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
74
189
  args: [{
75
190
  providedIn: 'root',
76
191
  }]
77
- }], ctorParameters: () => [{ type: i1.SdStorageService }] });
192
+ }] });
78
193
 
194
+ const DEFAULT_LAYOUT_MOBILE_BREAKPOINT = 1024;
195
+ function normalizeLayoutMobileBreakpoint(value) {
196
+ if (!Number.isFinite(value) || Number(value) <= 0) {
197
+ return DEFAULT_LAYOUT_MOBILE_BREAKPOINT;
198
+ }
199
+ return Math.floor(Number(value));
200
+ }
201
+ function resolveSidebarV2Interaction(sidebar) {
202
+ return sidebar.interaction === 'hover-lock' ? 'hover-lock' : 'click';
203
+ }
204
+ function resolveSidebarV3Recent(sidebar) {
205
+ const configuredLimit = sidebar.recent?.maxItems;
206
+ const maxItems = Number.isFinite(configuredLimit) && Number(configuredLimit) > 0 ? Math.floor(Number(configuredLimit)) : 5;
207
+ return {
208
+ enabled: sidebar.recent?.enabled ?? true,
209
+ maxItems,
210
+ };
211
+ }
212
+ function normalizeSidebarConfiguration(sidebar) {
213
+ if (sidebar.version === 2) {
214
+ return {
215
+ ...sidebar,
216
+ interaction: resolveSidebarV2Interaction(sidebar),
217
+ primaryMenuIds: [...new Set((sidebar.primaryMenuIds ?? []).filter(id => typeof id === 'string' && id.trim()))].slice(0, 3),
218
+ };
219
+ }
220
+ if (sidebar.version === 3) {
221
+ return {
222
+ ...sidebar,
223
+ defaultCollapsed: sidebar.defaultCollapsed ?? false,
224
+ recent: resolveSidebarV3Recent(sidebar),
225
+ };
226
+ }
227
+ return sidebar;
228
+ }
79
229
  const SD_LAYOUT_CONFIGURATION = new InjectionToken('sd.layout.configuration');
80
230
 
231
+ const SD_LAYOUT_VIEWPORT = SD_VIEWPORT;
232
+ /** @deprecated Prefer `SdViewportService` from `@sdcorejs/angular/services/viewport`. */
233
+ class SdLayoutResponsiveService {
234
+ #viewport = inject(SdViewportService);
235
+ viewportWidth = this.#viewport.width;
236
+ isMobile(breakpoint) {
237
+ return this.viewportWidth() < normalizeLayoutMobileBreakpoint(breakpoint);
238
+ }
239
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
240
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, providedIn: 'root' });
241
+ }
242
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutResponsiveService, decorators: [{
243
+ type: Injectable,
244
+ args: [{ providedIn: 'root' }]
245
+ }] });
246
+
247
+ class SdLayoutNavigationStateService {
248
+ #storage = inject(SdLayoutStorageService);
249
+ #menus = signal([], ...(ngDevMode ? [{ debugName: "#menus" }] : []));
250
+ pinnedKeys = signal([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : []));
251
+ recentKeys = signal([], ...(ngDevMode ? [{ debugName: "recentKeys" }] : []));
252
+ pinnedMenus = computed(() => resolveMenuKeys(this.#menus(), this.pinnedKeys()), ...(ngDevMode ? [{ debugName: "pinnedMenus" }] : []));
253
+ recentMenus = computed(() => resolveMenuKeys(this.#menus(), this.recentKeys()), ...(ngDevMode ? [{ debugName: "recentMenus" }] : []));
254
+ hydrate(menus, recentMaximum = 5) {
255
+ this.#menus.set(menus);
256
+ this.pinnedKeys.set(this.#storage.readPinnedMenuKeys(menus));
257
+ this.recentKeys.set(this.#storage.readRecentMenuKeys(menus, recentMaximum));
258
+ }
259
+ togglePinned(menu) {
260
+ const key = getMenuStableKey(menu);
261
+ if (!key)
262
+ return;
263
+ const nextKeys = this.pinnedKeys().includes(key)
264
+ ? this.pinnedKeys().filter(currentKey => currentKey !== key)
265
+ : [...this.pinnedKeys(), key];
266
+ this.pinnedKeys.set(nextKeys);
267
+ this.#storage.writePinnedMenuKeys(nextKeys);
268
+ }
269
+ recordRecent(menu, configuration) {
270
+ if (!configuration.enabled)
271
+ return;
272
+ const key = getMenuStableKey(menu);
273
+ if (!key)
274
+ return;
275
+ const maximum = Number.isFinite(configuration.maxItems) && configuration.maxItems > 0 ? Math.floor(configuration.maxItems) : 5;
276
+ const nextKeys = [key, ...this.recentKeys().filter(currentKey => currentKey !== key)].slice(0, maximum);
277
+ this.recentKeys.set(nextKeys);
278
+ this.#storage.writeRecentMenuKeys(nextKeys);
279
+ }
280
+ patchVersionState(version, patch) {
281
+ this.#storage.patchVersionState(version, patch);
282
+ }
283
+ versionState(version) {
284
+ return this.#storage.readVersionState(version);
285
+ }
286
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
287
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, providedIn: 'root' });
288
+ }
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutNavigationStateService, decorators: [{
290
+ type: Injectable,
291
+ args: [{ providedIn: 'root' }]
292
+ }] });
293
+
81
294
  class SdLayoutService {
82
295
  // ==========================================
83
296
  // INJECT CONFIGURATIONS
@@ -154,10 +367,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
154
367
 
155
368
  // End
156
369
  class MenuPipe {
157
- permissionService;
158
- constructor(permissionService) {
159
- this.permissionService = permissionService;
160
- }
370
+ permissionService = inject(SdPermissionService);
371
+ constructor() { }
161
372
  transform(menus) {
162
373
  const results = [];
163
374
  const check = (menu) => {
@@ -221,6 +432,9 @@ class MenuPipe {
221
432
  return results;
222
433
  }
223
434
  #getHashIdMenu = (menu) => {
435
+ const explicitId = typeof menu.id === 'string' ? menu.id.trim() : '';
436
+ if (explicitId)
437
+ return explicitId;
224
438
  const extendedMenu = menu;
225
439
  const hashKeys = [
226
440
  'title',
@@ -241,7 +455,7 @@ class MenuPipe {
241
455
  }, {});
242
456
  return Object.keys(hashData).length > 0 ? Utilities.hash(hashData) : Utilities.generateUuid();
243
457
  };
244
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, deps: [{ token: i1$1.SdPermissionService }], target: i0.ɵɵFactoryTarget.Pipe });
458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
245
459
  static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, isStandalone: true, name: "menu" });
246
460
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuPipe, providedIn: 'root' });
247
461
  }
@@ -255,7 +469,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
255
469
  args: [{
256
470
  providedIn: 'root',
257
471
  }]
258
- }], ctorParameters: () => [{ type: i1$1.SdPermissionService }] });
472
+ }], ctorParameters: () => [] });
259
473
 
260
474
  // End
261
475
  class MenuFocusPipe {
@@ -377,7 +591,7 @@ let LayoutUserComponent$1 = class LayoutUserComponent {
377
591
  this.toggleMenuLock.emit(event);
378
592
  };
379
593
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
380
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n<div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n @if (!isMobileOrTablet()) {\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n }\n</div>\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo.fullName || _userInfo.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>", styles: ["::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i1$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
594
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n<div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n @if (!isMobileOrTablet()) {\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n }\n</div>\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo.fullName || _userInfo.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>", styles: ["::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.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.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" }] });
381
595
  };
382
596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent$1, decorators: [{
383
597
  type: Component,
@@ -390,7 +604,9 @@ class SidebarComponent {
390
604
  // ==========================================
391
605
  #router = inject(Router);
392
606
  #layoutStorageService = inject(SdLayoutStorageService);
607
+ #navigationState = inject(SdLayoutNavigationStateService);
393
608
  #menuFocusPipe = inject(MenuFocusPipe);
609
+ #window = inject(DOCUMENT).defaultView;
394
610
  #destroyRef = inject(DestroyRef); // Dùng để unsubscribe RxJS tự động
395
611
  // ==========================================
396
612
  // SIGNAL INPUTS & OUTPUTS
@@ -399,6 +615,7 @@ class SidebarComponent {
399
615
  menus = input.required(...(ngDevMode ? [{ debugName: "menus" }] : []));
400
616
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
401
617
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
618
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
402
619
  expandSideBar = output();
403
620
  popupUserMenuClosed = output();
404
621
  popupUserMenuOpened = output();
@@ -406,21 +623,19 @@ class SidebarComponent {
406
623
  // ==========================================
407
624
  // STATE SIGNALS
408
625
  // ==========================================
409
- screenHeight = window.innerHeight;
410
- isMobileOrTablet = BrowserUtilities.isMobile();
411
626
  isMenuLock = signal(this.#layoutStorageService.menuLockStatus?.get() ?? true, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
412
- currentPath = signal(window.location.pathname, ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
627
+ currentPath = signal(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
413
628
  searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
414
629
  titleMenuGroup = signal('', ...(ngDevMode ? [{ debugName: "titleMenuGroup" }] : []));
415
630
  idMenuGroupActive = signal('', ...(ngDevMode ? [{ debugName: "idMenuGroupActive" }] : []));
416
631
  menusByGroup = signal([], ...(ngDevMode ? [{ debugName: "menusByGroup" }] : []));
417
632
  #hoveredMenuNodeKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredMenuNodeKey" }] : []));
418
633
  #pinIconHoverTimerId = signal(null, ...(ngDevMode ? [{ debugName: "#pinIconHoverTimerId" }] : []));
419
- pinnedMenuGroup = signal(this.#layoutStorageService.pinnedMenuGroup.get() ?? {
634
+ pinnedMenuGroup = computed(() => ({
420
635
  id: 'pinned-menu-group',
421
636
  title: 'Đã ghim',
422
- children: [],
423
- }, ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : []));
637
+ children: this.#navigationState.pinnedMenus(),
638
+ }), ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : []));
424
639
  // ==========================================
425
640
  // COMPUTED STATE
426
641
  // ==========================================
@@ -442,6 +657,7 @@ class SidebarComponent {
442
657
  effect(() => {
443
658
  const currentMenus = this.menus();
444
659
  untracked(() => {
660
+ this.#navigationState.hydrate(currentMenus);
445
661
  const lastActiveMenuGroupId = this.#layoutStorageService.lastActiveMenuGroupId.get();
446
662
  const pinnedGroup = this.pinnedMenuGroup();
447
663
  const isPinEnabled = this.sidebar()?.pin?.enabled;
@@ -459,8 +675,9 @@ class SidebarComponent {
459
675
  #setupSubscriptions() {
460
676
  this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
461
677
  if (event instanceof NavigationEnd) {
462
- if (this.currentPath() !== window.location.pathname) {
463
- this.currentPath.set(window.location.pathname);
678
+ const currentPath = event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '';
679
+ if (this.currentPath() !== currentPath) {
680
+ this.currentPath.set(currentPath);
464
681
  this.#bindingMenuGroupByCurrentPath(this.menus());
465
682
  }
466
683
  }
@@ -486,15 +703,7 @@ class SidebarComponent {
486
703
  };
487
704
  onTogglePin = (event, node) => {
488
705
  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
- });
706
+ this.#navigationState.togglePinned(node);
498
707
  };
499
708
  #onExpandAllMenuNodes = (menus) => {
500
709
  menus?.forEach(menu => {
@@ -519,12 +728,7 @@ class SidebarComponent {
519
728
  this.showSideBar.emit(this.isMenuLock());
520
729
  }
521
730
  openHomePage = () => {
522
- if (!this.isMobileOrTablet) {
523
- this.#router.navigateByUrl('/layout/home');
524
- }
525
- else {
526
- window.location.href = '/layout/home';
527
- }
731
+ this.#router.navigateByUrl('/layout/home');
528
732
  this.searchText.set('');
529
733
  this.#layoutStorageService.lastActiveMenuGroupId.set('');
530
734
  };
@@ -533,14 +737,14 @@ class SidebarComponent {
533
737
  navigate = (args) => {
534
738
  const { path, queryParams } = args;
535
739
  if (path.includes('http')) {
536
- window.open(path);
740
+ this.#window?.open(path, '_blank', 'noopener');
537
741
  return;
538
742
  }
539
743
  this.#router.navigate([path.split('?')[0]], {
540
744
  queryParams,
541
745
  state: { switchTab: true },
542
746
  });
543
- if (this.isMobileOrTablet) {
747
+ if (this.isMobile()) {
544
748
  this.#closeMenu();
545
749
  }
546
750
  };
@@ -571,7 +775,7 @@ class SidebarComponent {
571
775
  this.idMenuGroupActive.set(menuGroupNode?.id);
572
776
  this.#layoutStorageService.lastActiveMenuGroupId.set(menuGroupNode?.id || '');
573
777
  if (!this.currentPath()) {
574
- this.currentPath.set(window.location.pathname);
778
+ this.currentPath.set(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '');
575
779
  }
576
780
  };
577
781
  // ==========================================
@@ -900,7 +1104,7 @@ class SidebarComponent {
900
1104
  return count;
901
1105
  };
902
1106
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobileOrTablet ? screenHeight + 'px' : '100%'\">\n <div class=\"c-header\">\n @if (_sidebar.logoUrl) {\n <div class=\"c-logo\">\n <a href=\"javascript:;\" (click)=\"openHomePage()\">\n <img alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n </a>\n </div>\n }\n <div class=\"c-title-menu-group\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive() ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive() ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: idMenuGroupActive() === nodeMenuGroup?.id ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\" [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\">\n @if (totalMenuInMenusByGroup() >= 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input size=\"sm\" [placeholder]=\"'core.module.layout.sidebar.search' | translate\" [model]=\"searchText()\" (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor: (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color: (currentPath() | menuFocus: node) ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)' : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\"\n (click)=\"onTogglePin($event, node)\" name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon class=\"c-menu-node-description-icon-expand\" [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobileOrTablet\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:cover}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "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" }] });
1107
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n @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\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\">\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 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", "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$1.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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
904
1108
  }
905
1109
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarComponent, decorators: [{
906
1110
  type: Component,
@@ -919,8 +1123,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
919
1123
  SdSuffixDefDirective,
920
1124
  LayoutUserComponent$1,
921
1125
  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"] }] } });
1126
+ ], 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 @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\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\">\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 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"] }]
1127
+ }], 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
1128
 
925
1129
  class SidebarV1Component {
926
1130
  // ==========================================
@@ -934,14 +1138,14 @@ class SidebarV1Component {
934
1138
  menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
935
1139
  userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
936
1140
  sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1141
+ isMobile = input(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
937
1142
  // ==========================================
938
1143
  // STATE SIGNALS
939
1144
  // ==========================================
940
- isMobileOrTablet = signal(BrowserUtilities.isMobile(), ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
941
1145
  // Trạng thái khóa menu (luôn mở rộng hay không)
942
1146
  isMenuLock = signal(this.#layoutStorageService.menuLockStatus?.get() ?? true, ...(ngDevMode ? [{ debugName: "isMenuLock" }] : []));
943
1147
  // 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" }] : []));
1148
+ isShowSidebar = signal(this.isMobile() ? false : (this.#layoutStorageService.menuLockStatus?.get() ?? true), ...(ngDevMode ? [{ debugName: "isShowSidebar" }] : []));
945
1149
  onhover = signal(false, ...(ngDevMode ? [{ debugName: "onhover" }] : []));
946
1150
  // ==========================================
947
1151
  // PRIVATE TIMERS
@@ -983,7 +1187,7 @@ class SidebarV1Component {
983
1187
  }
984
1188
  };
985
1189
  openSidebar = () => {
986
- if (!this.isMobileOrTablet()) {
1190
+ if (!this.isMobile()) {
987
1191
  return;
988
1192
  }
989
1193
  if (!this.isShowSidebar()) {
@@ -1005,7 +1209,7 @@ class SidebarV1Component {
1005
1209
  }
1006
1210
  };
1007
1211
  onExpandSideBar = () => {
1008
- if (this.isMobileOrTablet()) {
1212
+ if (this.isMobile()) {
1009
1213
  return;
1010
1214
  }
1011
1215
  if (!this.isShowSidebar()) {
@@ -1014,7 +1218,7 @@ class SidebarV1Component {
1014
1218
  }
1015
1219
  };
1016
1220
  onMouseleaveSideBar = () => {
1017
- if (this.isMobileOrTablet()) {
1221
+ if (this.isMobile()) {
1018
1222
  return;
1019
1223
  }
1020
1224
  this.#mouseLeave();
@@ -1040,12 +1244,12 @@ class SidebarV1Component {
1040
1244
  }
1041
1245
  };
1042
1246
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV1Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1043
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type: SidebarV1Component, isStandalone: true, selector: "sidebar-v1", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "sidenav", first: true, predicate: ["sidenav"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"isShowSidebar()\"\n (openedChange)=\"isShowSidebar.set($event)\"\n class=\"c-layout-sidebar\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobileOrTablet() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobileOrTablet()\">\n <sidebar\n [menus]=\"menus()\"\n [userInfo]=\"userInfo()\"\n [sidebar]=\"sidebar()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (showSideBar)=\"onToggle($event)\"\n (expandSideBar)=\"onExpandSideBar()\"\n (popupUserMenuOpened)=\"onPopupOfSideBarOpened()\"\n (popupUserMenuClosed)=\"onPopupOfSideBarClosed()\">\n </sidebar>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:hidden!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.mat-drawer-opened{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i1$4.MatDrawerContainer, selector: "mat-drawer-container", inputs: ["autosize", "hasBackdrop"], outputs: ["backdropClick"], exportAs: ["matDrawerContainer"] }, { kind: "component", type: i1$4.MatDrawerContent, selector: "mat-drawer-content" }, { kind: "component", type: i1$4.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i1$4.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i1$4.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: SidebarComponent, selector: "sidebar", inputs: ["isShowSidebar", "menus", "userInfo", "sidebar"], outputs: ["expandSideBar", "popupUserMenuClosed", "popupUserMenuOpened", "showSideBar"] }] });
1247
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type: SidebarV1Component, isStandalone: true, selector: "sidebar-v1", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "sidenav", first: true, predicate: ["sidenav"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"isShowSidebar()\"\n (openedChange)=\"isShowSidebar.set($event)\"\n class=\"c-layout-sidebar\"\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: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$2.MatDrawerContainer, selector: "mat-drawer-container", inputs: ["autosize", "hasBackdrop"], outputs: ["backdropClick"], exportAs: ["matDrawerContainer"] }, { kind: "component", type: i1$2.MatDrawerContent, selector: "mat-drawer-content" }, { kind: "component", type: i1$2.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i1$2.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i1$2.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
1248
  }
1045
1249
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV1Component, decorators: [{
1046
1250
  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 }] }] } });
1251
+ 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]=\"isShowSidebar()\"\n (openedChange)=\"isShowSidebar.set($event)\"\n class=\"c-layout-sidebar\"\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: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"] }]
1252
+ }], 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
1253
 
1050
1254
  class LayoutUserComponent {
1051
1255
  // ==========================================
@@ -1098,7 +1302,7 @@ class LayoutUserComponent {
1098
1302
  this.toggleMenuLock.emit(event);
1099
1303
  };
1100
1304
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n@if (isMobileOrTablet()) {\n <!-- Mobile: accordion inline expand -->\n <div class=\"m-user-card\">\n <button class=\"m-user-row\" (click)=\"toggleExpanded()\">\n <sd-avatar [src]=\"avatar\" [size]=\"44\"></sd-avatar>\n <div class=\"m-user-info\">\n <span class=\"m-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n @if (_userInfo?.email) {\n <span class=\"m-user-email\">{{ _userInfo?.email }}</span>\n }\n </div>\n <sd-icon class=\"m-expand-icon\" [class.m-expand-icon--open]=\"isExpanded()\" name=\"keyboard_arrow_down\"></sd-icon>\n </button>\n\n @if (isExpanded()) {\n <div class=\"m-user-body\">\n @if (changePasswordLayoutConfig) {\n <button class=\"m-user-action\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n }\n <button class=\"m-user-action m-user-action--logout\" (click)=\"logout()\">\n <sd-icon name=\"logout\"></sd-icon>\n <span>{{ 'core.module.layout.user.logout' | translate }}</span>\n </button>\n </div>\n }\n </div>\n} @else {\n <!-- Desktop: ch\u1EC9 hi\u1EC3n th\u1ECB avatar + n\u00FAt toggle -->\n <div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n </div>\n}\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo?.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>\n", styles: ["@media(min-width:1024px){::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}}.m-user-card{width:100%}.m-user-row{display:flex;align-items:center;gap:12px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent}.m-user-info{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden}.m-user-name{font-size:15px;font-weight:600;color:#1f1f1f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-user-email{font-size:13px;color:#8c8c8c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-expand-icon{color:#8c8c8c;flex-shrink:0;transition:transform .2s ease}.m-expand-icon--open{transform:rotate(180deg)}.m-user-body{margin-top:8px;border-top:1px solid #EBEBEB;padding-top:4px;display:flex;flex-direction:column}.m-user-action{display:flex;align-items:center;gap:12px;width:100%;padding:12px 4px;border:none;background:transparent;cursor:pointer;font-size:15px;color:#1f1f1f;-webkit-tap-highlight-color:transparent}.m-user-action sd-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-user-action:active{background-color:#f5f5f5}.m-user-action--logout{color:#e53935}.m-user-action--logout sd-icon{color:#e53935}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i1$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1305
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let avatar = _userInfo?.avatar || _userInfo?.fullName || _userInfo?.username || _userInfo?.email;\n\n@if (isMobileOrTablet()) {\n <!-- Mobile: accordion inline expand -->\n <div class=\"m-user-card\">\n <button class=\"m-user-row\" (click)=\"toggleExpanded()\">\n <sd-avatar [src]=\"avatar\" [size]=\"44\"></sd-avatar>\n <div class=\"m-user-info\">\n <span class=\"m-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n @if (_userInfo?.email) {\n <span class=\"m-user-email\">{{ _userInfo?.email }}</span>\n }\n </div>\n <sd-icon class=\"m-expand-icon\" [class.m-expand-icon--open]=\"isExpanded()\" name=\"keyboard_arrow_down\"></sd-icon>\n </button>\n\n @if (isExpanded()) {\n <div class=\"m-user-body\">\n @if (changePasswordLayoutConfig) {\n <button class=\"m-user-action\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n }\n <button class=\"m-user-action m-user-action--logout\" (click)=\"logout()\">\n <sd-icon name=\"logout\"></sd-icon>\n <span>{{ 'core.module.layout.user.logout' | translate }}</span>\n </button>\n </div>\n }\n </div>\n} @else {\n <!-- Desktop: ch\u1EC9 hi\u1EC3n th\u1ECB avatar + n\u00FAt toggle -->\n <div class=\"c-layout-user-wrapper\">\n <div class=\"c-layout-user-avatar\" [matMenuTriggerFor]=\"userMenu\" (menuClosed)=\"onMenuClosed()\" (menuOpened)=\"onMenuOpened()\">\n <sd-avatar [src]=\"avatar\" [size]=\"32\"></sd-avatar>\n </div>\n\n <div aria-hidden=\"true\" mat-mini-fab class=\"c-layout-icon-toggle\" (click)=\"onToggleMenuLock($event)\">\n <sd-icon style=\"color: #8c8c8c\" [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </div>\n </div>\n}\n\n<mat-menu #userMenu=\"matMenu\" xPosition=\"before\" backdropClass=\"c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0\">\n <div aria-hidden=\"true\" class=\"c-user-menu-wrapper\" (click)=\"keepOpenWhenClickInsideMenu($event)\">\n <div class=\"c-user-info-wrapper\">\n <div class=\"c-user-avatar\">\n <sd-avatar [src]=\"avatar\" [size]=\"40\"></sd-avatar>\n </div>\n <span class=\"c-user-name\">{{ _userInfo?.fullName || _userInfo?.username }}</span>\n <span class=\"c-user-email\">{{ _userInfo?.email }}</span>\n </div>\n\n @if (changePasswordLayoutConfig) {\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-account-options\" (click)=\"changePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n <span>{{ 'core.module.layout.user.change-password' | translate }}</span>\n </button>\n </div>\n }\n\n <div class=\"c-sign-button-wrapper\">\n <button class=\"c-sign-button\" mat-flat-button (click)=\"logout()\">{{ 'core.module.layout.user.logout' | translate }}</button>\n </div>\n </div>\n</mat-menu>\n", styles: ["@media(min-width:1024px){::ng-deep .c-user-menu-backdrop-f90ce131-fa4c-4f71-a49b-ab7fa374fdd0+* .cdk-overlay-pane{transform:translate(277px,83px)}}.m-user-card{width:100%}.m-user-row{display:flex;align-items:center;gap:12px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent}.m-user-info{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden}.m-user-name{font-size:15px;font-weight:600;color:#1f1f1f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-user-email{font-size:13px;color:#8c8c8c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.m-expand-icon{color:#8c8c8c;flex-shrink:0;transition:transform .2s ease}.m-expand-icon--open{transform:rotate(180deg)}.m-user-body{margin-top:8px;border-top:1px solid #EBEBEB;padding-top:4px;display:flex;flex-direction:column}.m-user-action{display:flex;align-items:center;gap:12px;width:100%;padding:12px 4px;border:none;background:transparent;cursor:pointer;font-size:15px;color:#1f1f1f;-webkit-tap-highlight-color:transparent}.m-user-action sd-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-user-action:active{background-color:#f5f5f5}.m-user-action--logout{color:#e53935}.m-user-action--logout sd-icon{color:#e53935}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-wrapper .c-layout-user-avatar{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}.c-layout-user-wrapper .c-layout-icon-toggle{background-color:transparent;border-top:1px solid #f0f0f0;box-shadow:none;margin-top:10px;width:32px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer}.c-user-menu-wrapper .c-user-info-wrapper{width:250px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-avatar{width:40px;height:40px}.c-user-menu-wrapper .c-user-info-wrapper .c-user-name{font-size:16px;font-weight:700;text-align:center}.c-user-menu-wrapper .c-user-info-wrapper .c-user-email{font-size:14px;color:#7a7a7a;text-align:center}.c-user-menu-wrapper .c-sign-button-wrapper{text-align:center;padding:16px 16px 8px;width:100%;border-top:1px solid #dee2e6}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options{all:unset;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:8px}.c-user-menu-wrapper .c-sign-button-wrapper .c-account-options sd-icon{font-size:21px;width:21px;height:21px;color:#8c8c8c}.c-user-menu-wrapper .c-sign-button-wrapper .c-sign-button{width:100%;color:#000;padding:16px;font-weight:700;background-color:#eef0f2}.c-avatar{max-width:100%;object-fit:cover}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.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.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" }] });
1102
1306
  }
1103
1307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, decorators: [{
1104
1308
  type: Component,
@@ -1296,12 +1500,568 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1296
1500
  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
1501
  }], 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
1502
 
1503
+ class SdLayoutMenuTreeComponent {
1504
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1505
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : []));
1506
+ activePath = input('', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1507
+ pinnedKeys = input([], ...(ngDevMode ? [{ debugName: "pinnedKeys" }] : []));
1508
+ showPin = input(true, ...(ngDevMode ? [{ debugName: "showPin" }] : []));
1509
+ navigate = output();
1510
+ togglePinned = output();
1511
+ nodes = computed(() => {
1512
+ const pinnedKeys = new Set(this.pinnedKeys());
1513
+ const query = this.query().trim();
1514
+ const menus = query ? searchMenuLeaves(this.menus(), query) : this.menus();
1515
+ const nodes = [];
1516
+ const append = (items, depth, ancestors) => {
1517
+ for (const menu of items) {
1518
+ const key = getMenuStableKey(menu, ancestors);
1519
+ if (!key)
1520
+ continue;
1521
+ const isGroup = 'children' in menu && !!menu.children?.length;
1522
+ const path = 'path' in menu ? menu.path : '';
1523
+ nodes.push({
1524
+ menu,
1525
+ key,
1526
+ title: menu.title ?? menu.tooltipTitle ?? '',
1527
+ depth,
1528
+ paddingLeft: 12 + depth * 16,
1529
+ isGroup,
1530
+ isActive: !!path && this.#pathMatches(this.activePath(), path),
1531
+ isPinned: pinnedKeys.has(key),
1532
+ });
1533
+ if (isGroup && 'children' in menu)
1534
+ append(menu.children ?? [], depth + 1, [...ancestors, menu.title ?? key]);
1535
+ }
1536
+ };
1537
+ append(menus, 0, []);
1538
+ return nodes;
1539
+ }, ...(ngDevMode ? [{ debugName: "nodes" }] : []));
1540
+ onNavigate(menu) {
1541
+ if ('path' in menu)
1542
+ this.navigate.emit(menu);
1543
+ }
1544
+ onTogglePinned(event, menu) {
1545
+ event.stopPropagation();
1546
+ this.togglePinned.emit(menu);
1547
+ }
1548
+ #pathMatches(currentPath, menuPath) {
1549
+ const normalize = (path) => (path.endsWith('/') ? path : `${path}/`);
1550
+ return currentPath === menuPath || normalize(currentPath).startsWith(normalize(menuPath));
1551
+ }
1552
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1553
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutMenuTreeComponent, isStandalone: true, selector: "sd-layout-menu-tree", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, activePath: { classPropertyName: "activePath", publicName: "activePath", isSignal: true, isRequired: false, transformFunction: null }, pinnedKeys: { classPropertyName: "pinnedKeys", publicName: "pinnedKeys", isSignal: true, isRequired: false, transformFunction: null }, showPin: { classPropertyName: "showPin", publicName: "showPin", isSignal: true, isRequired: false, transformFunction: null } }, 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 class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\">\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 [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]=\"node.isPinned ? 'push_pin' : 'keep'\"></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);cursor:pointer;border-radius:8px}.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)}.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}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1554
+ }
1555
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, decorators: [{
1556
+ type: Component,
1557
+ 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 class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\">\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 [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]=\"node.isPinned ? 'push_pin' : 'keep'\"></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);cursor:pointer;border-radius:8px}.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)}.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}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}\n"] }]
1558
+ }], 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 }] }], navigate: [{ type: i0.Output, args: ["navigate"] }], togglePinned: [{ type: i0.Output, args: ["togglePinned"] }] } });
1559
+
1560
+ class SdLayoutUserMenuComponent {
1561
+ #configuration = inject(SD_LAYOUT_CONFIGURATION, { optional: true });
1562
+ #destroyRef = inject(DestroyRef);
1563
+ #document = inject(DOCUMENT);
1564
+ #host = inject(ElementRef);
1565
+ trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
1566
+ menu = viewChild('menu', ...(ngDevMode ? [{ debugName: "menu" }] : []));
1567
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1568
+ signout = input(...(ngDevMode ? [undefined, { debugName: "signout" }] : []));
1569
+ changePassword = input(...(ngDevMode ? [undefined, { debugName: "changePassword" }] : []));
1570
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : []));
1571
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1572
+ avatar = computed(() => {
1573
+ const user = this.userInfo();
1574
+ return user.avatar || user.fullName || user.username || user.email;
1575
+ }, ...(ngDevMode ? [{ debugName: "avatar" }] : []));
1576
+ displayName = computed(() => this.userInfo().fullName || this.userInfo().username || this.userInfo().email || 'User', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
1577
+ signoutAction = computed(() => this.signout() ?? this.#configuration?.signout, ...(ngDevMode ? [{ debugName: "signoutAction" }] : []));
1578
+ changePasswordAction = computed(() => this.changePassword() ?? this.#configuration?.changePassword, ...(ngDevMode ? [{ debugName: "changePasswordAction" }] : []));
1579
+ constructor() {
1580
+ effect(() => {
1581
+ if (!this.isOpen())
1582
+ return;
1583
+ this.menu()?.nativeElement.querySelector('[role="menuitem"]')?.focus();
1584
+ });
1585
+ const onDocumentKeydown = (event) => {
1586
+ if (!this.isOpen() || event.key !== 'Escape')
1587
+ return;
1588
+ event.preventDefault();
1589
+ this.close();
1590
+ };
1591
+ const onDocumentPointerDown = (event) => {
1592
+ if (!this.isOpen())
1593
+ return;
1594
+ const nodeConstructor = this.#document.defaultView?.Node;
1595
+ if (!nodeConstructor || !(event.target instanceof nodeConstructor) || this.#host.nativeElement.contains(event.target))
1596
+ return;
1597
+ this.close(false);
1598
+ };
1599
+ this.#document.addEventListener('keydown', onDocumentKeydown);
1600
+ this.#document.addEventListener('pointerdown', onDocumentPointerDown);
1601
+ this.#destroyRef.onDestroy(() => {
1602
+ this.#document.removeEventListener('keydown', onDocumentKeydown);
1603
+ this.#document.removeEventListener('pointerdown', onDocumentPointerDown);
1604
+ });
1605
+ }
1606
+ open() {
1607
+ this.isOpen.set(true);
1608
+ }
1609
+ toggle() {
1610
+ if (this.isOpen())
1611
+ this.close(false);
1612
+ else
1613
+ this.open();
1614
+ }
1615
+ close(restoreFocus = true) {
1616
+ this.isOpen.set(false);
1617
+ if (restoreFocus)
1618
+ this.trigger()?.nativeElement.focus();
1619
+ }
1620
+ onMenuKeydown(event) {
1621
+ if (event.key === 'Escape') {
1622
+ event.preventDefault();
1623
+ this.close();
1624
+ return;
1625
+ }
1626
+ if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) || !(event.currentTarget instanceof HTMLElement))
1627
+ return;
1628
+ const actions = [...event.currentTarget.querySelectorAll('[role="menuitem"]')];
1629
+ if (!actions.length)
1630
+ return;
1631
+ event.preventDefault();
1632
+ const currentIndex = actions.findIndex(action => action === this.#document.activeElement);
1633
+ const nextIndex = event.key === 'Home'
1634
+ ? 0
1635
+ : event.key === 'End'
1636
+ ? actions.length - 1
1637
+ : event.key === 'ArrowDown'
1638
+ ? (currentIndex + 1) % actions.length
1639
+ : (currentIndex - 1 + actions.length) % actions.length;
1640
+ actions[nextIndex].focus();
1641
+ }
1642
+ runChangePassword() {
1643
+ this.changePasswordAction()?.();
1644
+ this.close();
1645
+ }
1646
+ runSignout() {
1647
+ this.signoutAction()?.();
1648
+ this.close();
1649
+ }
1650
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1651
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutUserMenuComponent, isStandalone: true, selector: "sd-layout-user-menu", inputs: { userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, signout: { classPropertyName: "signout", publicName: "signout", isSignal: true, isRequired: false, transformFunction: null }, changePassword: { classPropertyName: "changePassword", publicName: "changePassword", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "menu", first: true, predicate: ["menu"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"sd-layout-user-menu position-relative\">\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center gap-8 w-full text-left\"\n aria-haspopup=\"menu\"\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 }\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\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 class=\"d-flex flex-column gap-4 px-8 py-4\">\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 </div>\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 \u0110\u1ED5i m\u1EADt kh\u1EA9u\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 \u0110\u0103ng xu\u1EA5t\n </button>\n }\n </div>\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500);cursor:pointer;border-radius:8px}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100)}.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__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:240px}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1652
+ }
1653
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, decorators: [{
1654
+ type: Component,
1655
+ args: [{ selector: 'sd-layout-user-menu', standalone: true, imports: [SdAvatar, SdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-layout-user-menu position-relative\">\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center gap-8 w-full text-left\"\n aria-haspopup=\"menu\"\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 }\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\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 class=\"d-flex flex-column gap-4 px-8 py-4\">\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 </div>\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 \u0110\u1ED5i m\u1EADt kh\u1EA9u\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 \u0110\u0103ng xu\u1EA5t\n </button>\n }\n </div>\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500);cursor:pointer;border-radius:8px}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100)}.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__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:240px}\n"] }]
1656
+ }], 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 }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }] } });
1657
+
1658
+ class SidebarV2Component {
1659
+ #router = inject(Router);
1660
+ #document = inject(DOCUMENT);
1661
+ #destroyRef = inject(DestroyRef);
1662
+ #navigationState = inject(SdLayoutNavigationStateService);
1663
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1664
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1665
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1666
+ activeGroupKey = signal('', ...(ngDevMode ? [{ debugName: "activeGroupKey" }] : []));
1667
+ isFlyoutOpen = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutOpen" }] : []));
1668
+ isFlyoutLocked = signal(false, ...(ngDevMode ? [{ debugName: "isFlyoutLocked" }] : []));
1669
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1670
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1671
+ interaction = computed(() => resolveSidebarV2Interaction(this.sidebar()), ...(ngDevMode ? [{ debugName: "interaction" }] : []));
1672
+ railItems = computed(() => this.menus()
1673
+ .map(menu => ({ menu, key: getMenuStableKey(menu), title: menu.tooltipTitle || menu.title || 'Menu' }))
1674
+ .filter(item => !!item.key), ...(ngDevMode ? [{ debugName: "railItems" }] : []));
1675
+ activeGroup = computed(() => this.railItems().find(item => item.key === this.activeGroupKey())?.menu, ...(ngDevMode ? [{ debugName: "activeGroup" }] : []));
1676
+ contextMenus = computed(() => {
1677
+ const activeGroup = this.activeGroup();
1678
+ if (!activeGroup)
1679
+ return [];
1680
+ return 'children' in activeGroup && activeGroup.children?.length ? activeGroup.children : [activeGroup];
1681
+ }, ...(ngDevMode ? [{ debugName: "contextMenus" }] : []));
1682
+ pinnedKeys = this.#navigationState.pinnedKeys;
1683
+ constructor() {
1684
+ effect(() => {
1685
+ const menus = this.menus();
1686
+ untracked(() => {
1687
+ this.#navigationState.hydrate(menus);
1688
+ const persistedState = this.#navigationState.versionState(2);
1689
+ if (persistedState.activeGroupKey && !this.activeGroupKey())
1690
+ this.activeGroupKey.set(persistedState.activeGroupKey);
1691
+ });
1692
+ });
1693
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1694
+ if (!(event instanceof NavigationEnd))
1695
+ return;
1696
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1697
+ this.closeFromNavigation();
1698
+ });
1699
+ }
1700
+ selectGroup(menu) {
1701
+ const key = getMenuStableKey(menu);
1702
+ if (!key)
1703
+ return;
1704
+ if (this.interaction() === 'click') {
1705
+ if (this.activeGroupKey() === key && this.isFlyoutOpen()) {
1706
+ this.#closeFlyout();
1707
+ return;
1708
+ }
1709
+ this.#openGroup(key, false);
1710
+ return;
1711
+ }
1712
+ if (this.activeGroupKey() === key && this.isFlyoutLocked()) {
1713
+ this.#closeFlyout();
1714
+ return;
1715
+ }
1716
+ this.#openGroup(key, true);
1717
+ }
1718
+ previewGroup(menu) {
1719
+ if (this.interaction() !== 'hover-lock' || this.isFlyoutLocked())
1720
+ return;
1721
+ const key = getMenuStableKey(menu);
1722
+ if (key)
1723
+ this.#openGroup(key, false, false);
1724
+ }
1725
+ leaveFlyout() {
1726
+ if (this.interaction() === 'hover-lock' && !this.isFlyoutLocked())
1727
+ this.#closeFlyout(false);
1728
+ }
1729
+ closeFromBackdrop() {
1730
+ this.#closeFlyout();
1731
+ }
1732
+ closeFromEscape() {
1733
+ this.#closeFlyout();
1734
+ }
1735
+ closeFromNavigation() {
1736
+ this.#closeFlyout();
1737
+ }
1738
+ onNavigate(menu) {
1739
+ if (!('path' in menu))
1740
+ return;
1741
+ this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
1742
+ if (menu.path.includes('http')) {
1743
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1744
+ this.closeFromNavigation();
1745
+ return;
1746
+ }
1747
+ void this.#router.navigate([menu.path.split('?')[0]], {
1748
+ queryParams: menu.queryParams ?? {},
1749
+ state: { switchTab: true },
1750
+ });
1751
+ }
1752
+ onTogglePinned(menu) {
1753
+ this.#navigationState.togglePinned(menu);
1754
+ }
1755
+ #openGroup(key, locked, persist = true) {
1756
+ this.activeGroupKey.set(key);
1757
+ this.isFlyoutOpen.set(true);
1758
+ this.isFlyoutLocked.set(locked);
1759
+ this.searchText.set('');
1760
+ if (persist)
1761
+ this.#navigationState.patchVersionState(2, { activeGroupKey: key, locked });
1762
+ }
1763
+ #closeFlyout(persist = true) {
1764
+ this.isFlyoutOpen.set(false);
1765
+ this.isFlyoutLocked.set(false);
1766
+ this.searchText.set('');
1767
+ if (persist)
1768
+ this.#navigationState.patchVersionState(2, { locked: false });
1769
+ }
1770
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1771
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarV2Component, isStandalone: true, selector: "sidebar-v2", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-v2\" (keydown.escape)=\"closeFromEscape()\">\n <aside class=\"sd-sidebar-v2__rail d-flex flex-column align-items-center gap-8 py-12 bg-white border-right border-black200\">\n <button type=\"button\" class=\"sd-sidebar-v2__brand d-flex align-items-center justify-content-center\" aria-label=\"Trang ch\u1EE7\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v2__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </button>\n\n <nav class=\"d-flex flex-column align-items-center gap-4 flex-1\" aria-label=\"Nh\u00F3m menu\">\n @for (item of railItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__rail-action d-flex flex-column align-items-center justify-content-center gap-4\"\n [attr.data-v2-group-key]=\"item.key\"\n [attr.aria-label]=\"item.title\"\n [attr.aria-expanded]=\"activeGroupKey() === item.key && isFlyoutOpen()\"\n [class.sd-sidebar-v2__rail-action--active]=\"activeGroupKey() === item.key\"\n (click)=\"selectGroup(item.menu)\"\n (mouseenter)=\"previewGroup(item.menu)\"\n (focus)=\"previewGroup(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v2__rail-icon\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-v2__rail-label text-ellipsis\">{{ item.title }}</span>\n </button>\n }\n </nav>\n\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"true\"></sd-layout-user-menu>\n </aside>\n\n @if (isFlyoutOpen()) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v2__backdrop\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\"\n (keydown.escape)=\"closeFromEscape()\"></button>\n <section\n class=\"sd-sidebar-v2__flyout d-flex flex-column bg-white border-right border-black200 mat-elevation-z4\"\n [attr.aria-label]=\"activeGroup()?.title || 'Menu'\"\n (mouseleave)=\"leaveFlyout()\"\n (keydown.escape)=\"closeFromEscape()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ activeGroup()?.title || sidebar().defaultTitle || 'Menu' }}</span>\n @if (isFlyoutLocked()) {\n <sd-icon name=\"lock\"></sd-icon>\n }\n </header>\n <div class=\"p-12\">\n <sd-input\n size=\"sm\"\n placeholder=\"T\u00ECm trong nh\u00F3m\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-input>\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: 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: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1772
+ }
1773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV2Component, decorators: [{
1774
+ type: Component,
1775
+ args: [{ selector: 'sidebar-v2', standalone: true, imports: [SdIcon, SdInput, 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-input\n size=\"sm\"\n placeholder=\"T\u00ECm trong nh\u00F3m\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-input>\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"] }]
1776
+ }], 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 }] }] } });
1777
+
1778
+ class SidebarMobileV2Component {
1779
+ #router = inject(Router);
1780
+ #document = inject(DOCUMENT);
1781
+ #destroyRef = inject(DestroyRef);
1782
+ #navigationState = inject(SdLayoutNavigationStateService);
1783
+ #restoreFocusElement = null;
1784
+ #previousBodyOverflow = '';
1785
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1786
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1787
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1788
+ primaryMenus = computed(() => selectPrimaryMenuGroups(this.menus(), this.sidebar().primaryMenuIds), ...(ngDevMode ? [{ debugName: "primaryMenus" }] : []));
1789
+ overflowMenus = computed(() => {
1790
+ const primaryKeys = new Set(this.primaryMenus().map(menu => getMenuStableKey(menu)));
1791
+ return this.menus().filter(menu => !primaryKeys.has(getMenuStableKey(menu)));
1792
+ }, ...(ngDevMode ? [{ debugName: "overflowMenus" }] : []));
1793
+ isSheetOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSheetOpen" }] : []));
1794
+ sheetGroupKey = signal('', ...(ngDevMode ? [{ debugName: "sheetGroupKey" }] : []));
1795
+ sheetTitle = signal('', ...(ngDevMode ? [{ debugName: "sheetTitle" }] : []));
1796
+ sheetMenus = signal([], ...(ngDevMode ? [{ debugName: "sheetMenus" }] : []));
1797
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1798
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1799
+ pinnedKeys = this.#navigationState.pinnedKeys;
1800
+ constructor() {
1801
+ effect(() => {
1802
+ const menus = this.menus();
1803
+ untracked(() => this.#navigationState.hydrate(menus));
1804
+ });
1805
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1806
+ if (!(event instanceof NavigationEnd))
1807
+ return;
1808
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1809
+ this.closeSheet(false);
1810
+ });
1811
+ this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
1812
+ }
1813
+ openMore(trigger) {
1814
+ this.#openSheet('more', 'Thêm', this.overflowMenus(), trigger);
1815
+ }
1816
+ activateMenu(menu, trigger) {
1817
+ if ('path' in menu) {
1818
+ this.#navigate(menu);
1819
+ return;
1820
+ }
1821
+ this.#openSheet(getMenuStableKey(menu), menu.title || 'Menu', menu.children ?? [], trigger);
1822
+ }
1823
+ closeSheet(restoreFocus = true) {
1824
+ if (!this.isSheetOpen())
1825
+ return;
1826
+ this.isSheetOpen.set(false);
1827
+ this.searchText.set('');
1828
+ this.#releaseBodyScroll();
1829
+ if (restoreFocus)
1830
+ this.#restoreFocusElement?.focus();
1831
+ this.#restoreFocusElement = null;
1832
+ }
1833
+ onSheetKeydown(event) {
1834
+ if (event.key !== 'Escape')
1835
+ return;
1836
+ event.preventDefault();
1837
+ this.closeSheet();
1838
+ }
1839
+ onNavigate(menu) {
1840
+ if ('path' in menu)
1841
+ this.#navigate(menu);
1842
+ }
1843
+ onTogglePinned(menu) {
1844
+ this.#navigationState.togglePinned(menu);
1845
+ }
1846
+ #openSheet(key, title, menus, trigger) {
1847
+ this.sheetGroupKey.set(key);
1848
+ this.sheetTitle.set(title);
1849
+ this.sheetMenus.set(menus);
1850
+ this.searchText.set('');
1851
+ this.#restoreFocusElement =
1852
+ trigger instanceof HTMLElement ? trigger : this.#document.activeElement instanceof HTMLElement ? this.#document.activeElement : null;
1853
+ if (!this.isSheetOpen()) {
1854
+ this.#previousBodyOverflow = this.#document.body.style.overflow;
1855
+ this.#document.body.style.overflow = 'hidden';
1856
+ }
1857
+ this.isSheetOpen.set(true);
1858
+ }
1859
+ #navigate(menu) {
1860
+ this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
1861
+ if (menu.path.includes('http')) {
1862
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1863
+ this.closeSheet();
1864
+ return;
1865
+ }
1866
+ void this.#router
1867
+ .navigate([menu.path.split('?')[0]], { queryParams: menu.queryParams ?? {}, state: { switchTab: true } })
1868
+ .then(navigated => {
1869
+ if (navigated)
1870
+ this.closeSheet();
1871
+ });
1872
+ }
1873
+ #releaseBodyScroll() {
1874
+ this.#document.body.style.overflow = this.#previousBodyOverflow;
1875
+ }
1876
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1877
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarMobileV2Component, isStandalone: true, selector: "sidebar-mobile-v2", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-mobile-v2 d-flex flex-column\">\n <main class=\"sd-sidebar-mobile-v2__content flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n <nav class=\"sd-sidebar-mobile-v2__bar d-flex align-items-stretch bg-white border-top border-black200\" aria-label=\"\u0110i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n @for (menu of primaryMenus(); track menu.id || menu.title) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n [attr.data-mobile-menu-key]=\"menu.id ? 'id:' + menu.id : null\"\n [attr.aria-label]=\"menu.tooltipTitle || menu.title || 'Menu'\"\n [attr.aria-expanded]=\"sheetGroupKey() === (menu.id ? 'id:' + menu.id : '') && isSheetOpen()\"\n (click)=\"activateMenu(menu, $event.currentTarget)\">\n @if (menu.iconUrl) {\n <img class=\"sd-sidebar-mobile-v2__icon\" [src]=\"menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"menu.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"sd-sidebar-mobile-v2__label text-ellipsis\">{{ menu.tooltipTitle || menu.title }}</span>\n </button>\n }\n @if (overflowMenus().length) {\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__bar-action d-flex flex-column align-items-center justify-content-center gap-4 flex-1\"\n aria-label=\"Th\u00EAm menu\"\n [attr.aria-expanded]=\"sheetGroupKey() === 'more' && isSheetOpen()\"\n (click)=\"openMore($event.currentTarget)\">\n <sd-icon name=\"more_horiz\"></sd-icon>\n <span class=\"sd-sidebar-mobile-v2__label\">Th\u00EAm</span>\n </button>\n }\n </nav>\n\n @if (isSheetOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v2__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeSheet()\"></button>\n <section\n data-v2-sheet\n class=\"sd-sidebar-mobile-v2__sheet d-flex flex-column bg-white rounded-top-16 mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"sheetTitle()\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onSheetKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ sheetTitle() }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v2__close d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng\"\n (click)=\"closeSheet()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"d-flex flex-column gap-12 p-12 overflow-auto\">\n <sd-layout-user-menu [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-input\n size=\"sm\"\n placeholder=\"T\u00ECm trong menu\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-input>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\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</div>\n", styles: [".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$3.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: 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: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1878
+ }
1879
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV2Component, decorators: [{
1880
+ type: Component,
1881
+ args: [{ selector: 'sidebar-mobile-v2', standalone: true, imports: [A11yModule, SdIcon, SdInput, 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 [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-input\n size=\"sm\"\n placeholder=\"T\u00ECm trong menu\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-input>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\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</div>\n", styles: [".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"] }]
1882
+ }], 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 }] }] } });
1883
+
1884
+ class SidebarV3Component {
1885
+ #router = inject(Router);
1886
+ #document = inject(DOCUMENT);
1887
+ #destroyRef = inject(DestroyRef);
1888
+ #navigationState = inject(SdLayoutNavigationStateService);
1889
+ #initialized = false;
1890
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1891
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1892
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1893
+ isCollapsed = signal(false, ...(ngDevMode ? [{ debugName: "isCollapsed" }] : []));
1894
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1895
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1896
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : []));
1897
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : []));
1898
+ pinnedKeys = this.#navigationState.pinnedKeys;
1899
+ pinnedMenus = this.#navigationState.pinnedMenus;
1900
+ recentMenus = this.#navigationState.recentMenus;
1901
+ collapsedItems = computed(() => this.menus().map(menu => ({ menu, key: getMenuStableKey(menu), title: menu.tooltipTitle || menu.title || 'Menu' })), ...(ngDevMode ? [{ debugName: "collapsedItems" }] : []));
1902
+ constructor() {
1903
+ effect(() => {
1904
+ const menus = this.menus();
1905
+ const sidebar = this.sidebar();
1906
+ const recentConfiguration = resolveSidebarV3Recent(sidebar);
1907
+ untracked(() => {
1908
+ this.#navigationState.hydrate(menus, recentConfiguration.maxItems);
1909
+ if (this.#initialized)
1910
+ return;
1911
+ const persistedCollapsed = this.#navigationState.versionState(3).collapsed;
1912
+ this.isCollapsed.set(typeof persistedCollapsed === 'boolean' ? persistedCollapsed : (sidebar.defaultCollapsed ?? false));
1913
+ this.#initialized = true;
1914
+ });
1915
+ });
1916
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1917
+ if (event instanceof NavigationEnd)
1918
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1919
+ });
1920
+ }
1921
+ toggleCollapsed() {
1922
+ this.isCollapsed.update(collapsed => !collapsed);
1923
+ this.#navigationState.patchVersionState(3, { collapsed: this.isCollapsed() });
1924
+ }
1925
+ activateCollapsedMenu(menu) {
1926
+ if ('path' in menu)
1927
+ this.navigateMenu(menu);
1928
+ else {
1929
+ this.isCollapsed.set(false);
1930
+ this.#navigationState.patchVersionState(3, { collapsed: false });
1931
+ }
1932
+ }
1933
+ navigateMenu(menu) {
1934
+ if (!('path' in menu))
1935
+ return;
1936
+ this.#navigationState.recordRecent(menu, this.recentConfiguration());
1937
+ if (menu.path.includes('http')) {
1938
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
1939
+ return;
1940
+ }
1941
+ void this.#router.navigate([menu.path.split('?')[0]], {
1942
+ queryParams: menu.queryParams ?? {},
1943
+ state: { switchTab: true },
1944
+ });
1945
+ }
1946
+ onTogglePinned(menu) {
1947
+ this.#navigationState.togglePinned(menu);
1948
+ }
1949
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
1950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarV3Component, isStandalone: true, selector: "sidebar-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 p-12 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-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n @if (!isCollapsed()) {\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n }\n </div>\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-input\n size=\"sm\"\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-input>\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: 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: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1951
+ }
1952
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarV3Component, decorators: [{
1953
+ type: Component,
1954
+ args: [{ selector: 'sidebar-v3', standalone: true, imports: [SdIcon, SdInput, 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 class=\"d-flex align-items-center justify-content-between gap-8 p-12 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-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n @if (!isCollapsed()) {\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n }\n </div>\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-input\n size=\"sm\"\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-input>\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"] }]
1955
+ }], 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 }] }] } });
1956
+
1957
+ class SidebarMobileV3Component {
1958
+ #router = inject(Router);
1959
+ #document = inject(DOCUMENT);
1960
+ #destroyRef = inject(DestroyRef);
1961
+ #navigationState = inject(SdLayoutNavigationStateService);
1962
+ trigger = viewChild('trigger', ...(ngDevMode ? [{ debugName: "trigger" }] : []));
1963
+ #restoreFocusElement = null;
1964
+ #previousBodyOverflow = '';
1965
+ menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
1966
+ userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
1967
+ sidebar = input.required(...(ngDevMode ? [{ debugName: "sidebar" }] : []));
1968
+ isDrawerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDrawerOpen" }] : []));
1969
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1970
+ activePath = signal(this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "activePath" }] : []));
1971
+ searchResults = computed(() => searchMenuLeaves(this.menus(), this.searchText()), ...(ngDevMode ? [{ debugName: "searchResults" }] : []));
1972
+ recentConfiguration = computed(() => resolveSidebarV3Recent(this.sidebar()), ...(ngDevMode ? [{ debugName: "recentConfiguration" }] : []));
1973
+ pinnedKeys = this.#navigationState.pinnedKeys;
1974
+ pinnedMenus = this.#navigationState.pinnedMenus;
1975
+ recentMenus = this.#navigationState.recentMenus;
1976
+ constructor() {
1977
+ effect(() => {
1978
+ const menus = this.menus();
1979
+ const maximum = resolveSidebarV3Recent(this.sidebar()).maxItems;
1980
+ untracked(() => this.#navigationState.hydrate(menus, maximum));
1981
+ });
1982
+ this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
1983
+ if (!(event instanceof NavigationEnd))
1984
+ return;
1985
+ this.activePath.set(event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '');
1986
+ this.closeFromNavigation();
1987
+ });
1988
+ this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
1989
+ }
1990
+ openDrawer(trigger) {
1991
+ this.#restoreFocusElement =
1992
+ trigger instanceof HTMLElement
1993
+ ? trigger
1994
+ : this.#document.activeElement instanceof HTMLElement
1995
+ ? this.#document.activeElement
1996
+ : (this.trigger()?.nativeElement ?? null);
1997
+ if (!this.isDrawerOpen()) {
1998
+ this.#previousBodyOverflow = this.#document.body.style.overflow;
1999
+ this.#document.body.style.overflow = 'hidden';
2000
+ }
2001
+ this.isDrawerOpen.set(true);
2002
+ }
2003
+ closeFromBackdrop() {
2004
+ this.#closeDrawer();
2005
+ }
2006
+ closeFromEscape() {
2007
+ this.#closeDrawer();
2008
+ }
2009
+ closeFromNavigation() {
2010
+ this.#closeDrawer();
2011
+ }
2012
+ onDrawerKeydown(event) {
2013
+ if (event.key !== 'Escape')
2014
+ return;
2015
+ event.preventDefault();
2016
+ this.closeFromEscape();
2017
+ }
2018
+ navigateMenu(menu) {
2019
+ if (!('path' in menu))
2020
+ return;
2021
+ this.#navigationState.recordRecent(menu, this.recentConfiguration());
2022
+ if (menu.path.includes('http')) {
2023
+ this.#document.defaultView?.open(menu.path, '_blank', 'noopener');
2024
+ this.closeFromNavigation();
2025
+ return;
2026
+ }
2027
+ void this.#router
2028
+ .navigate([menu.path.split('?')[0]], { queryParams: menu.queryParams ?? {}, state: { switchTab: true } })
2029
+ .then(navigated => {
2030
+ if (navigated)
2031
+ this.closeFromNavigation();
2032
+ });
2033
+ }
2034
+ onTogglePinned(menu) {
2035
+ this.#navigationState.togglePinned(menu);
2036
+ }
2037
+ #closeDrawer() {
2038
+ if (!this.isDrawerOpen())
2039
+ return;
2040
+ this.isDrawerOpen.set(false);
2041
+ this.searchText.set('');
2042
+ this.#releaseBodyScroll();
2043
+ (this.#restoreFocusElement ?? this.trigger()?.nativeElement)?.focus();
2044
+ this.#restoreFocusElement = null;
2045
+ }
2046
+ #releaseBodyScroll() {
2047
+ this.#document.body.style.overflow = this.#previousBodyOverflow;
2048
+ }
2049
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
2050
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarMobileV3Component, isStandalone: true, selector: "sidebar-mobile-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-input\n size=\"sm\"\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-input>\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 (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 (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [".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:0}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1$3.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: 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: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2051
+ }
2052
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SidebarMobileV3Component, decorators: [{
2053
+ type: Component,
2054
+ args: [{ selector: 'sidebar-mobile-v3', standalone: true, imports: [A11yModule, SdIcon, SdInput, 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 [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-input\n size=\"sm\"\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-input>\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 (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 (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [".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:0}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"] }]
2055
+ }], 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 }] }] } });
2056
+
1299
2057
  class SdLayoutComponent {
1300
2058
  // ==========================================
1301
2059
  // INJECT SERVICES
1302
2060
  // ==========================================
1303
2061
  #menuPipe = inject(MenuPipe);
1304
2062
  #layoutService = inject(SdLayoutService);
2063
+ #responsiveService = inject(SdLayoutResponsiveService);
2064
+ #layoutConfiguration = inject(SD_LAYOUT_CONFIGURATION, { optional: true });
1305
2065
  // ==========================================
1306
2066
  // SIGNAL INPUTS
1307
2067
  // ==========================================
@@ -1312,13 +2072,34 @@ class SdLayoutComponent {
1312
2072
  // ==========================================
1313
2073
  userInfo = this.#layoutService.userInfo;
1314
2074
  sidebar = this.#layoutService.sidebar;
1315
- isMobileOrTablet = signal(BrowserUtilities.isMobile(), ...(ngDevMode ? [{ debugName: "isMobileOrTablet" }] : []));
2075
+ sidebarV1 = computed(() => {
2076
+ const sidebar = this.sidebar();
2077
+ return sidebar?.version === 1 ? sidebar : undefined;
2078
+ }, ...(ngDevMode ? [{ debugName: "sidebarV1" }] : []));
2079
+ sidebarV2 = computed(() => {
2080
+ const sidebar = this.sidebar();
2081
+ return sidebar?.version === 2 ? sidebar : undefined;
2082
+ }, ...(ngDevMode ? [{ debugName: "sidebarV2" }] : []));
2083
+ sidebarV3 = computed(() => {
2084
+ const sidebar = this.sidebar();
2085
+ return sidebar?.version === 3 ? sidebar : undefined;
2086
+ }, ...(ngDevMode ? [{ debugName: "sidebarV3" }] : []));
2087
+ mobileBreakpoint = normalizeLayoutMobileBreakpoint(this.#layoutConfiguration?.mobileBreakpoint);
2088
+ isMobile = computed(() => this.#responsiveService.isMobile(this.mobileBreakpoint), ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
1316
2089
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1317
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutComponent, isStandalone: true, selector: "sd-layout", inputs: { menusInput: { classPropertyName: "menusInput", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar?.version === 1) {\n @if (!isMobileOrTablet()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebar!\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n", styles: [""], dependencies: [{ kind: "component", type: SidebarV1Component, selector: "sidebar-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SidebarMobileV1Component, selector: "sidebar-mobile-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2090
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutComponent, isStandalone: true, selector: "sd-layout", inputs: { menusInput: { classPropertyName: "menusInput", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [""], 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
2091
  }
1319
2092
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, decorators: [{
1320
2093
  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" }]
2094
+ args: [{ selector: 'sd-layout', imports: [
2095
+ SidebarV1Component,
2096
+ SidebarMobileV1Component,
2097
+ SidebarV2Component,
2098
+ SidebarMobileV2Component,
2099
+ SidebarV3Component,
2100
+ SidebarMobileV3Component,
2101
+ NgTemplateOutlet,
2102
+ ], 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" }]
1322
2103
  }], propDecorators: { menusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }] } });
1323
2104
 
1324
2105
  // End
@@ -1440,7 +2221,7 @@ const routes = [
1440
2221
  ];
1441
2222
  class HomeRoutingModule {
1442
2223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1443
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [i1$5.RouterModule], exports: [RouterModule] });
2224
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [i1$4.RouterModule], exports: [RouterModule] });
1444
2225
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
1445
2226
  }
1446
2227
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomeRoutingModule, decorators: [{
@@ -1515,7 +2296,7 @@ const Routes$2 = [{ path: '', component: RootComponent$1 }];
1515
2296
 
1516
2297
  class NotFoundModule {
1517
2298
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1518
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$5.RouterModule] });
2299
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, i1$4.RouterModule] });
1519
2300
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, imports: [CommonModule, RouterModule.forChild(Routes$2)] });
1520
2301
  }
1521
2302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NotFoundModule, decorators: [{
@@ -1577,7 +2358,7 @@ const Routes$1 = [{ path: '', component: RootComponent }];
1577
2358
 
1578
2359
  class ForbiddenModule {
1579
2360
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1580
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$5.RouterModule] });
2361
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, i1$4.RouterModule] });
1581
2362
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, imports: [CommonModule, RouterModule.forChild(Routes$1)] });
1582
2363
  }
1583
2364
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ForbiddenModule, decorators: [{
@@ -1615,7 +2396,7 @@ const Routes = [
1615
2396
 
1616
2397
  class SdLayoutModule {
1617
2398
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1618
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [i1$5.RouterModule] });
2399
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [i1$4.RouterModule] });
1619
2400
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, imports: [RouterModule.forChild(Routes)] });
1620
2401
  }
1621
2402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutModule, decorators: [{
@@ -1631,5 +2412,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1631
2412
  * Generated bundle index. Do not edit.
1632
2413
  */
1633
2414
 
1634
- export { ForbiddenModule, HomeModule, NotFoundModule, SD_LAYOUT_CONFIGURATION, SdLayoutComponent, SdLayoutModule, SdLayoutService, SdLayoutStorageService, SdPageComponent, SidebarMobileOverlayComponent, SidebarMobileV1Component };
2415
+ 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
2416
  //# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map