@sdcorejs/angular 20.1.6 → 20.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/assets/scss/core/menu.scss +18 -0
- package/assets/scss/core/utilities/_misc.scss +15 -0
- package/assets/scss/sd-core.scss +1 -0
- package/components/anchor/index.d.ts +3 -1
- package/components/badge/index.d.ts +1 -0
- package/components/ckeditor-styles/index.d.ts +1 -1
- package/components/form-generic/index.d.ts +44 -33
- package/components/import-excel/index.d.ts +1 -0
- package/components/index.d.ts +6 -1
- package/components/mini-editor/index.d.ts +1 -1
- package/components/modal/index.d.ts +3 -2
- package/components/modal-resizable/index.d.ts +92 -0
- package/components/org-chart/index.d.ts +6 -0
- package/components/preview/index.d.ts +1 -0
- package/components/query-bar/index.d.ts +55 -17
- package/components/query-builder/index.d.ts +150 -98
- package/components/section/index.d.ts +3 -0
- package/components/tab-router/index.d.ts +19 -19
- package/components/table/index.d.ts +251 -109
- package/components/tree/index.d.ts +48 -30
- package/components/upload-file/index.d.ts +6 -3
- package/configurations/index.d.ts +1 -2
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +37 -13
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +15 -2
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-data-state.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +324 -192
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-history.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-history.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +13 -5
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +164 -8
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +30 -18
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs +60 -13
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +27 -17
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +192 -30
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +187 -127
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +16 -2
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +83 -24
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +57 -21
- package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-stepper.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +68 -20
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +3 -2
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +542 -255
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +80 -17
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +48 -13
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +6 -1
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +45 -8
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +40 -29
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +5 -5
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +67 -50
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +57 -43
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +232 -61
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +96 -24
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +131 -27
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +40 -16
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +16 -5
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +24 -9
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +58 -10
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs +227 -6
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +4 -4
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +136 -38
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +18 -3
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +51 -62
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs +57 -12
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time.mjs +16 -5
- package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +100 -9
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms.mjs +91 -46
- package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-handlers.mjs +18 -3
- package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +490 -68
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +87 -29
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-auth.mjs +25 -15
- package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-icon.mjs +2 -2
- package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +32 -3
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +332 -111
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-permission.mjs +177 -40
- package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +62 -28
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +123 -13
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +53 -3
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +25 -73
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-excel.mjs +12 -10
- package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +75 -84
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +46 -5
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-storage.mjs +4 -0
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services.mjs +0 -1
- package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +225 -80
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-models.mjs +1 -63
- package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular.mjs +17 -10
- package/fesm2022/sdcorejs-angular.mjs.map +1 -1
- package/forms/autocomplete/index.d.ts +5 -2
- package/forms/chip-calendar/index.d.ts +10 -0
- package/forms/date/index.d.ts +13 -2
- package/forms/date-range/index.d.ts +14 -2
- package/forms/datetime/index.d.ts +13 -2
- package/forms/entity-picker/index.d.ts +9 -3
- package/forms/index.d.ts +22 -13
- package/forms/input/index.d.ts +3 -0
- package/forms/input-color/index.d.ts +4 -0
- package/forms/input-number/index.d.ts +3 -0
- package/forms/models/index.d.ts +119 -4
- package/forms/select/index.d.ts +27 -4
- package/forms/switch/index.d.ts +7 -1
- package/forms/textarea/index.d.ts +5 -3
- package/forms/time-range/index.d.ts +22 -1
- package/forms/tree-select/index.d.ts +36 -5
- package/i18n/index.d.ts +95 -16
- package/index.d.ts +11 -2
- package/interceptors/index.d.ts +13 -3
- package/modules/auth/index.d.ts +1 -1
- package/modules/keycloak/index.d.ts +1 -0
- package/modules/layout/index.d.ts +94 -28
- package/modules/permission/index.d.ts +66 -4
- package/package.json +32 -25
- package/pipes/index.d.ts +27 -5
- package/services/api/index.d.ts +21 -1
- package/services/cache/index.d.ts +11 -1
- package/services/index.d.ts +0 -1
- package/services/notify/index.d.ts +1 -1
- package/utilities/extensions/index.d.ts +83 -61
- package/utilities/models/index.d.ts +2 -76
- package/components/base/index.d.ts +0 -11
- package/components/document-builder/index.d.ts +0 -252
- package/fesm2022/sdcorejs-angular-components-base.mjs +0 -23
- package/fesm2022/sdcorejs-angular-components-base.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +0 -5038
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-models.mjs +0 -6
- package/fesm2022/sdcorejs-angular-models.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +0 -291
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-services-license.mjs +0 -97
- package/fesm2022/sdcorejs-angular-services-license.mjs.map +0 -1
- package/models/index.d.ts +0 -2
- package/services/docx/index.d.ts +0 -27
- package/services/license/index.d.ts +0 -12
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, input, booleanAttribute, effect, Component,
|
|
2
|
+
import { inject, ElementRef, input, booleanAttribute, effect, Component, InjectionToken, Injectable, signal, computed, Pipe, DestroyRef, viewChild, output, isSignal, ChangeDetectionStrategy, untracked, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { DOCUMENT, CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
5
5
|
import * as i1$3 from '@angular/router';
|
|
6
6
|
import { Router, NavigationEnd, RouterModule, ActivatedRoute } from '@angular/router';
|
|
7
7
|
import { __decorate } from 'tslib';
|
|
8
8
|
import { SdAvatar, SdTabComponent, SdButton } from '@sdcorejs/angular/components';
|
|
9
|
-
import { I18nService,
|
|
9
|
+
import { I18nService, SdTranslatePipe, I18N_STORAGE_KEY, I18N_MESSAGES } from '@sdcorejs/angular/i18n';
|
|
10
10
|
import { SdStorageService } from '@sdcorejs/angular/services';
|
|
11
11
|
import { StringUtilities, Utilities } from '@sdcorejs/utils/fns';
|
|
12
12
|
import { SD_VIEWPORT, SdViewportService } from '@sdcorejs/angular/services/viewport';
|
|
13
13
|
import { resolveMaybeAsync } from '@sdcorejs/utils/models';
|
|
14
|
-
import { SdPermissionService } from '@sdcorejs/angular/modules/permission';
|
|
14
|
+
import { SdPermissionService, SD_PERMISSION_PUBLIC } from '@sdcorejs/angular/modules/permission';
|
|
15
|
+
import { sdParseUrl, sdIsExternalHttpUrl, sdOpenExternal } from '@sdcorejs/angular/utilities';
|
|
15
16
|
import * as i1$1 from '@angular/material/sidenav';
|
|
16
17
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
17
18
|
import { NestedTreeControl } from '@angular/cdk/tree';
|
|
@@ -124,15 +125,127 @@ function visitMenus(menus, ancestors, visitor) {
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Namespace do consumer cung cấp cho toàn bộ state layout được persist (thường là id user hoặc tenant).
|
|
130
|
+
*
|
|
131
|
+
* why: các key dưới đây là UUID CỐ ĐỊNH, không gắn với ai cả. Trên máy dùng chung, user A đăng xuất
|
|
132
|
+
* rồi user B đăng nhập thì `pinnedMenuKeys` / `recentMenuKeys` / `lastActiveMenuGroupId` của A vẫn
|
|
133
|
+
* còn nguyên trong localStorage và B nhìn thấy đúng những module A đã ghim và vừa truy cập. Cấp
|
|
134
|
+
* namespace theo user/tenant sẽ tách hẳn không gian lưu trữ.
|
|
135
|
+
*
|
|
136
|
+
* ⚠️ `SdLayoutStorageService` là singleton `providedIn: 'root'`, thường được khởi tạo TRƯỚC khi biết
|
|
137
|
+
* người dùng là ai. Vì vậy hãy cấp một **hàm** — nó được gọi lại ở mỗi lần truy cập handle, nên khi
|
|
138
|
+
* danh tính đến (hoặc đổi) thì cả bảy handle tự dựng lại trên namespace mới:
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* providers: [
|
|
142
|
+
* {
|
|
143
|
+
* provide: SD_LAYOUT_STORAGE_NAMESPACE,
|
|
144
|
+
* useFactory: () => {
|
|
145
|
+
* const auth = inject(SdAuthService);
|
|
146
|
+
* return () => auth.getAuthInfo?.()?.id;
|
|
147
|
+
* },
|
|
148
|
+
* },
|
|
149
|
+
* ];
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* Đây là biện pháp bổ sung chứ không thay thế `SdLayoutStorageService.clear()` lúc signout.
|
|
153
|
+
*/
|
|
154
|
+
const SD_LAYOUT_STORAGE_NAMESPACE = new InjectionToken('sd.layout.storage.namespace');
|
|
127
155
|
class SdLayoutStorageService {
|
|
128
156
|
#sdStorageService = inject(SdStorageService);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
#namespaceSource = inject(SD_LAYOUT_STORAGE_NAMESPACE, { optional: true });
|
|
158
|
+
#handles;
|
|
159
|
+
// why: đọc namespace bằng field initializer là vô nghĩa — service là singleton `providedIn: 'root'`
|
|
160
|
+
// nên nó chạy ở lần inject đầu tiên, thường TRƯỚC khi đăng nhập xong, và factory theo user trả về
|
|
161
|
+
// `undefined` cho cả phiên; biện pháp "pinned/recent của A không lộ sang B" quảng cáo trong doc khi
|
|
162
|
+
// đó không bao giờ có hiệu lực. Giải quyết bằng cách resolve LƯỜI ở mỗi lần truy cập handle: khi
|
|
163
|
+
// danh tính đến (hoặc đổi user trong cùng phiên SPA) thì bảy handle được dựng lại trên namespace mới.
|
|
164
|
+
get isShowSidebar() {
|
|
165
|
+
return this.#resolveHandles().isShowSidebar;
|
|
166
|
+
}
|
|
167
|
+
get menuLockStatus() {
|
|
168
|
+
return this.#resolveHandles().menuLockStatus;
|
|
169
|
+
}
|
|
170
|
+
get lastActiveMenuGroupId() {
|
|
171
|
+
return this.#resolveHandles().lastActiveMenuGroupId;
|
|
172
|
+
}
|
|
173
|
+
get pinnedMenuGroup() {
|
|
174
|
+
return this.#resolveHandles().pinnedMenuGroup;
|
|
175
|
+
}
|
|
176
|
+
get pinnedMenuKeys() {
|
|
177
|
+
return this.#resolveHandles().pinnedMenuKeys;
|
|
178
|
+
}
|
|
179
|
+
get recentMenuKeys() {
|
|
180
|
+
return this.#resolveHandles().recentMenuKeys;
|
|
181
|
+
}
|
|
182
|
+
get versionStates() {
|
|
183
|
+
return this.#resolveHandles().versionStates;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Xoá toàn bộ state layout đã persist.
|
|
187
|
+
*
|
|
188
|
+
* **Consumer PHẢI gọi hàm này trong luồng signout, TRƯỚC khi danh tính bị xoá.** Nếu không,
|
|
189
|
+
* pinned/recent/last-active của user vừa đăng xuất sẽ còn lại trên trình duyệt và hiện ra cho người
|
|
190
|
+
* đăng nhập kế tiếp — rò rỉ tên module nội bộ mà người đó có thể không có quyền thấy.
|
|
191
|
+
*
|
|
192
|
+
* why: khi đã cấu hình `SD_LAYOUT_STORAGE_NAMESPACE` theo user, namespace được resolve NGAY LÚC GỌI.
|
|
193
|
+
* Gọi `clear()` sau khi auth info về `undefined` sẽ xoá nhầm partition không-namespace, còn dữ liệu
|
|
194
|
+
* của user vừa thoát thì vẫn nguyên.
|
|
195
|
+
*/
|
|
196
|
+
clear() {
|
|
197
|
+
const handles = this.#resolveHandles();
|
|
198
|
+
for (const handle of this.#allHandles(handles))
|
|
199
|
+
handle.remove();
|
|
200
|
+
}
|
|
201
|
+
// why: namespace rỗng/space phải quy về undefined, nếu không mỗi chuỗi rác lại tạo ra một không
|
|
202
|
+
// gian lưu trữ khác và state của chính user đó biến mất sau khi reload.
|
|
203
|
+
#resolveNamespace() {
|
|
204
|
+
const source = this.#namespaceSource;
|
|
205
|
+
const raw = typeof source === 'function' ? source() : source;
|
|
206
|
+
return typeof raw === 'string' ? raw.trim() || undefined : undefined;
|
|
207
|
+
}
|
|
208
|
+
#resolveHandles() {
|
|
209
|
+
const namespace = this.#resolveNamespace();
|
|
210
|
+
const current = this.#handles;
|
|
211
|
+
if (current && current.namespace === namespace)
|
|
212
|
+
return current;
|
|
213
|
+
// why: handle cũ phải `destroy()` (KHÔNG phải `remove()`) — chỉ gỡ facade và complete subject,
|
|
214
|
+
// dữ liệu của namespace trước vẫn nằm nguyên để user đó quay lại còn đọc được.
|
|
215
|
+
if (current)
|
|
216
|
+
for (const handle of this.#allHandles(current))
|
|
217
|
+
handle.destroy();
|
|
218
|
+
const next = this.#createHandles(namespace);
|
|
219
|
+
this.#handles = next;
|
|
220
|
+
return next;
|
|
221
|
+
}
|
|
222
|
+
#createHandles(namespace) {
|
|
223
|
+
// why: `{ namespace: undefined }` cho ra đúng identity như khi không truyền option (`SdStorageService`
|
|
224
|
+
// không áp namespace mặc định nào), nên app không cấu hình namespace vẫn đọc được state đã persist
|
|
225
|
+
// từ trước — không mất pinned/recent khi nâng cấp thư viện.
|
|
226
|
+
const option = { namespace };
|
|
227
|
+
return {
|
|
228
|
+
namespace,
|
|
229
|
+
isShowSidebar: this.#sdStorageService.create('cb07c316-ed6d-4620-9e92-53dbef6aa983', option),
|
|
230
|
+
menuLockStatus: this.#sdStorageService.create('2c2f4816-18b3-4ec3-8177-7b90bff036c3', option),
|
|
231
|
+
lastActiveMenuGroupId: this.#sdStorageService.create('2e6961d0-3380-4a94-a6a1-38837560cd96', option),
|
|
232
|
+
pinnedMenuGroup: this.#sdStorageService.create('e81da122-5dab-4250-b309-64197fb19d44', option),
|
|
233
|
+
pinnedMenuKeys: this.#sdStorageService.create('4bdc7f80-9ff4-4fe0-908f-6c9f7fe4100a', option),
|
|
234
|
+
recentMenuKeys: this.#sdStorageService.create('fb989dc1-f6e7-4b62-a611-861a8f214a51', option),
|
|
235
|
+
versionStates: this.#sdStorageService.create('ca8e4261-fbd4-4209-af88-2dbe278c1a1b', option),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
#allHandles(handles) {
|
|
239
|
+
return [
|
|
240
|
+
handles.isShowSidebar,
|
|
241
|
+
handles.menuLockStatus,
|
|
242
|
+
handles.lastActiveMenuGroupId,
|
|
243
|
+
handles.pinnedMenuGroup,
|
|
244
|
+
handles.pinnedMenuKeys,
|
|
245
|
+
handles.recentMenuKeys,
|
|
246
|
+
handles.versionStates,
|
|
247
|
+
];
|
|
248
|
+
}
|
|
136
249
|
readPinnedMenuKeys(menus) {
|
|
137
250
|
const storedKeys = this.pinnedMenuKeys.has() ? this.pinnedMenuKeys.get() : this.#getLegacyPinnedKeys();
|
|
138
251
|
const validKeys = this.#resolveValidKeys(menus, storedKeys ?? []);
|
|
@@ -288,29 +401,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
288
401
|
args: [{ providedIn: 'root' }]
|
|
289
402
|
}] });
|
|
290
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Bật fallback demo của `SdLayoutService` khi KHÔNG provide `SD_LAYOUT_CONFIGURATION`.
|
|
406
|
+
*
|
|
407
|
+
* why: fallback cũ chạy ngầm sau mỗi `console.warn`, nên một app cấu hình thiếu vẫn ship được UI có
|
|
408
|
+
* user "demo@example.com" đang đăng nhập cùng nút signout không làm gì — người dùng cuối tin là đã
|
|
409
|
+
* đăng nhập thật. Giờ mặc định là throw; ai thực sự cần demo/playground phải khai báo ý định:
|
|
410
|
+
*
|
|
411
|
+
* ```ts
|
|
412
|
+
* providers: [{ provide: SD_LAYOUT_DEMO_FALLBACK, useValue: true }]
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* Cờ nằm ở token riêng chứ không phải field của `ISdLayoutConfiguration`, vì nó phải đọc được ĐÚNG
|
|
416
|
+
* lúc `SD_LAYOUT_CONFIGURATION` vắng mặt — khi đó không có object configuration nào để đọc field.
|
|
417
|
+
*/
|
|
418
|
+
const SD_LAYOUT_DEMO_FALLBACK = new InjectionToken('sd.layout.demo-fallback');
|
|
291
419
|
class SdLayoutService {
|
|
292
420
|
// ==========================================
|
|
293
421
|
// INJECT CONFIGURATIONS
|
|
294
422
|
// ==========================================
|
|
295
|
-
// WHY: optional=true
|
|
296
|
-
//
|
|
423
|
+
// WHY: optional=true để service tự phát hiện consumer thiếu config và báo lỗi rõ ràng,
|
|
424
|
+
// thay vì để Angular ném NullInjectorError khó đọc từ một component sâu trong cây.
|
|
297
425
|
#layoutConfiguration = inject(SD_LAYOUT_CONFIGURATION, { optional: true });
|
|
426
|
+
#demoFallback = inject(SD_LAYOUT_DEMO_FALLBACK, { optional: true }) ?? false;
|
|
298
427
|
#i18n = inject(I18nService);
|
|
299
428
|
// ==========================================
|
|
300
429
|
// SHARED SIGNALS (STATE)
|
|
301
430
|
// ==========================================
|
|
302
431
|
userInfo = signal(undefined, ...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
303
432
|
sidebar = signal(undefined, ...(ngDevMode ? [{ debugName: "sidebar" }] : []));
|
|
433
|
+
/**
|
|
434
|
+
* Route "về trang chủ" của consumer, lấy từ `ISdLayoutConfiguration.homeUrl`.
|
|
435
|
+
* why: các trang lỗi (forbidden / not-found) cần một đích điều hướng thật thay vì reload chính nó.
|
|
436
|
+
*/
|
|
437
|
+
homeUrl = this.#layoutConfiguration?.homeUrl?.trim() || '/';
|
|
304
438
|
constructor() {
|
|
305
439
|
if (!this.#layoutConfiguration) {
|
|
306
|
-
|
|
307
|
-
|
|
440
|
+
if (!this.#demoFallback) {
|
|
441
|
+
// why: fail loudly. Im lặng degrade sang mock user là ship ra production một UI nói dối
|
|
442
|
+
// rằng người dùng đã đăng nhập (email demo, signout no-op).
|
|
443
|
+
throw new Error(
|
|
444
|
+
// @i18n-ignore dev-facing error — không cần dịch
|
|
445
|
+
'[SdLayoutService] SD_LAYOUT_CONFIGURATION chưa được provide. ' +
|
|
446
|
+
// @i18n-ignore
|
|
447
|
+
'Provide token này (xem ISdLayoutConfiguration) để cấu hình user info / sidebar / signout. ' +
|
|
448
|
+
// @i18n-ignore
|
|
449
|
+
'Nếu đang chạy demo/playground và CHỦ ĐỘNG muốn mock data, provide SD_LAYOUT_DEMO_FALLBACK = true.');
|
|
450
|
+
}
|
|
451
|
+
// WHY: chỉ chạy khi consumer chủ động opt-in demo fallback.
|
|
308
452
|
// @i18n-ignore dev-facing console.warn — không cần dịch
|
|
309
453
|
console.warn(
|
|
310
454
|
// @i18n-ignore
|
|
311
|
-
'[SdLayoutService] SD_LAYOUT_CONFIGURATION chưa được inject — đang dùng mock data. ' +
|
|
455
|
+
'[SdLayoutService] SD_LAYOUT_CONFIGURATION chưa được inject — đang dùng mock data qua SD_LAYOUT_DEMO_FALLBACK. ' +
|
|
312
456
|
// @i18n-ignore
|
|
313
|
-
'
|
|
457
|
+
'Tuyệt đối không bật cờ này trên môi trường thật.');
|
|
314
458
|
this.userInfo.set(this.#mockUserInfo());
|
|
315
459
|
this.sidebar.set(this.#mockSidebar());
|
|
316
460
|
return;
|
|
@@ -387,34 +531,43 @@ class MenuPipe {
|
|
|
387
531
|
};
|
|
388
532
|
}
|
|
389
533
|
if ('permission' in menu) {
|
|
390
|
-
const path = menu?.path?.includes('http') ? menu.path : menu.path
|
|
391
|
-
|
|
534
|
+
// why: code cũ là `const path = menu?.path?.includes('http') ? menu.path : menu.path` — hai
|
|
535
|
+
// nhánh giống hệt nhau nên biến `path` không biến đổi gì, tức phần validate URL đã bị mất.
|
|
536
|
+
// `path` chảy thẳng xuống `window.open()` / `routerLink` của sidebar, nên ở đây validate thật
|
|
537
|
+
// và loại menu có scheme nguy hiểm (`javascript:`, `data:`, `vbscript:`…) — fail closed.
|
|
538
|
+
// Không cần biến trung gian nữa: `path` đi kèm nguyên vẹn trong `{ ...menu }`.
|
|
539
|
+
if (!menu.path || !this.#isSafeMenuPath(menu.path)) {
|
|
392
540
|
return null;
|
|
393
541
|
}
|
|
394
542
|
if ((typeof menu.permission === 'string' || Array.isArray(menu.permission)) &&
|
|
395
543
|
this.permissionService.hasPermission(menu.permission, menu.permissionKey)) {
|
|
396
544
|
return {
|
|
397
545
|
...menu,
|
|
398
|
-
path,
|
|
399
546
|
id: this.#getHashIdMenu(menu),
|
|
400
547
|
};
|
|
401
548
|
}
|
|
402
549
|
if (typeof menu.permission === 'boolean' && menu.permission) {
|
|
403
550
|
return {
|
|
404
551
|
...menu,
|
|
405
|
-
path,
|
|
406
552
|
id: this.#getHashIdMenu(menu),
|
|
407
553
|
};
|
|
408
554
|
}
|
|
409
555
|
if (typeof menu.permission === 'function' && menu.permission()) {
|
|
410
556
|
return {
|
|
411
557
|
...menu,
|
|
412
|
-
path,
|
|
413
558
|
id: this.#getHashIdMenu(menu),
|
|
414
559
|
};
|
|
415
560
|
}
|
|
416
561
|
return null;
|
|
417
562
|
}
|
|
563
|
+
// why: tới đây menu không có `children` và cũng KHÔNG có key `permission`. Nếu nó vẫn có `path`
|
|
564
|
+
// thì đó là một menu lá điều hướng được, và việc gõ sai key (`permision`, `permissions`) sẽ
|
|
565
|
+
// khiến item hiện với MỌI người — fail open. Chặn ở runtime (fail closed) thay vì siết type
|
|
566
|
+
// `SdLayoutMenu`: menu thường được dựng động từ API/JSON rồi ép `as SdLayoutMenu[]`, nên siết
|
|
567
|
+
// type không bắt được ca đó mà lại thành breaking change cho consumer đang build tĩnh.
|
|
568
|
+
if ('path' in menu) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
418
571
|
return {
|
|
419
572
|
...menu,
|
|
420
573
|
id: Utilities.generateUuid(),
|
|
@@ -428,6 +581,12 @@ class MenuPipe {
|
|
|
428
581
|
});
|
|
429
582
|
return results;
|
|
430
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* why: `sdParseUrl` không truyền base nên chỉ parse được URL TUYỆT ĐỐI. Không parse được ⇒ path
|
|
586
|
+
* tương đối ⇒ route nội bộ của app, an toàn. Parse được thì scheme bắt buộc phải là `http(s)`;
|
|
587
|
+
* mọi scheme khác bị loại vì `path` cuối cùng chảy vào `window.open()` của sidebar.
|
|
588
|
+
*/
|
|
589
|
+
#isSafeMenuPath = (path) => sdParseUrl(path) == null || sdIsExternalHttpUrl(path);
|
|
431
590
|
#getHashIdMenu = (menu) => {
|
|
432
591
|
const explicitId = typeof menu.id === 'string' ? menu.id.trim() : '';
|
|
433
592
|
if (explicitId)
|
|
@@ -733,11 +892,11 @@ class SdLayoutUserMenuComponent {
|
|
|
733
892
|
return Number.isFinite(value) && Number(value) > 0 ? Math.floor(Number(value)) : 0;
|
|
734
893
|
}
|
|
735
894
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
736
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutUserMenuComponent, isStandalone: true, selector: "sd-layout-user-menu", inputs: { userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, signout: { classPropertyName: "signout", publicName: "signout", isSignal: true, isRequired: false, transformFunction: null }, changePassword: { classPropertyName: "changePassword", publicName: "changePassword", isSignal: true, isRequired: false, transformFunction: null }, updateProfile: { classPropertyName: "updateProfile", publicName: "updateProfile", isSignal: true, isRequired: false, transformFunction: null }, setting: { classPropertyName: "setting", publicName: "setting", isSignal: true, isRequired: false, transformFunction: null }, notification: { classPropertyName: "notification", publicName: "notification", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, presentation: { classPropertyName: "presentation", publicName: "presentation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "menu", first: true, predicate: ["menu"], descendants: true, isSignal: true }], ngImport: i0, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"], dependencies: [{ kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
895
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutUserMenuComponent, isStandalone: true, selector: "sd-layout-user-menu", inputs: { userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, signout: { classPropertyName: "signout", publicName: "signout", isSignal: true, isRequired: false, transformFunction: null }, changePassword: { classPropertyName: "changePassword", publicName: "changePassword", isSignal: true, isRequired: false, transformFunction: null }, updateProfile: { classPropertyName: "updateProfile", publicName: "updateProfile", isSignal: true, isRequired: false, transformFunction: null }, setting: { classPropertyName: "setting", publicName: "setting", isSignal: true, isRequired: false, transformFunction: null }, notification: { classPropertyName: "notification", publicName: "notification", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, presentation: { classPropertyName: "presentation", publicName: "presentation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "menu", first: true, predicate: ["menu"], descendants: true, isSignal: true }], ngImport: i0, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | sdTranslate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | sdTranslate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | sdTranslate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | sdTranslate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | sdTranslate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | sdTranslate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | sdTranslate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | sdTranslate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | sdTranslate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | sdTranslate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"], dependencies: [{ kind: "component", type: SdAvatar, selector: "sd-avatar", inputs: ["src", "size"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
737
896
|
}
|
|
738
897
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutUserMenuComponent, decorators: [{
|
|
739
898
|
type: Component,
|
|
740
|
-
args: [{ selector: 'sd-layout-user-menu', standalone: true, imports: [SdAvatar, SdIcon, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | translate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | translate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | translate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | translate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | translate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"] }]
|
|
899
|
+
args: [{ selector: 'sd-layout-user-menu', standalone: true, imports: [SdAvatar, SdIcon, SdTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _presentation = presentation();\n@let _role = role();\n@let _notificationBadge = notificationBadge();\n<div class=\"sd-layout-user-menu position-relative\">\n @if (_presentation !== 'disclosure') {\n <div class=\"sd-layout-user-menu__mobile d-flex flex-column gap-8\">\n <div\n data-mobile-account-row\n class=\"sd-layout-user-menu__mobile-row sd-layout-user-menu__mobile--inline d-flex align-items-center gap-8\">\n <div data-user-summary class=\"sd-layout-user-menu__summary d-flex align-items-center gap-8 flex-1\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (signoutAction()) {\n <button\n type=\"button\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action sd-layout-user-menu__signout d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | sdTranslate }}\n </button>\n }\n </div>\n\n @if (updateProfileAction() || settingAction() || notificationAction() || changePasswordAction()) {\n <div data-mobile-account-actions class=\"sd-layout-user-menu__mobile-actions d-flex flex-column gap-4\">\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | sdTranslate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | sdTranslate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | sdTranslate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | sdTranslate }}\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <button\n #trigger\n type=\"button\"\n data-user-trigger\n class=\"sd-layout-user-menu__trigger d-flex align-items-center text-left\"\n [class.sd-layout-user-menu__trigger--compact]=\"compact()\"\n [class.w-full]=\"!compact()\"\n [class.gap-8]=\"!compact()\"\n [class.w-56]=\"compact()\"\n [class.gap-0]=\"compact()\"\n aria-haspopup=\"menu\"\n [attr.aria-label]=\"compact() ? displayName() : null\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggle()\">\n <sd-avatar [src]=\"avatar()\" [size]=\"compact() ? 32 : 40\"></sd-avatar>\n @if (!compact()) {\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n </span>\n <sd-icon [name]=\"isOpen() ? 'expand_less' : 'expand_more'\"></sd-icon>\n }\n </button>\n\n @if (isOpen()) {\n <div\n #menu\n data-user-menu\n class=\"sd-layout-user-menu__popup d-flex flex-column gap-4 p-8 bg-white border border-black200 rounded-8 mat-elevation-z4\"\n role=\"menu\"\n tabindex=\"-1\"\n (keydown)=\"onMenuKeydown($event)\">\n <div data-user-identity class=\"sd-layout-user-menu__identity d-flex align-items-center gap-8 px-8 py-4\">\n <sd-avatar [src]=\"avatar()\" [size]=\"40\"></sd-avatar>\n <span class=\"d-flex flex-column flex-1 overflow-hidden\">\n <span class=\"T14M text-ellipsis\">{{ displayName() }}</span>\n @if (userInfo().email) {\n <span class=\"T12R text-black400 text-ellipsis\">{{ userInfo().email }}</span>\n }\n @if (_role) {\n <span\n data-user-role\n class=\"sd-layout-user-menu__role d-flex align-items-center gap-4 T12R text-ellipsis\"\n [style.color]=\"_role.color\">\n @if (_role.icon) {\n <sd-icon [name]=\"_role.icon\"></sd-icon>\n }\n {{ _role.text }}\n </span>\n }\n </span>\n </div>\n @if (updateProfileAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"update-profile\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runUpdateProfile()\">\n <sd-icon name=\"edit\"></sd-icon>\n {{ 'core.module.layout.user.update-profile' | sdTranslate }}\n </button>\n }\n @if (settingAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"setting\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runSetting()\">\n <sd-icon name=\"settings\"></sd-icon>\n {{ 'core.module.layout.user.setting' | sdTranslate }}\n </button>\n }\n @if (notificationAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"notification\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runNotification()\">\n <sd-icon name=\"notifications\"></sd-icon>\n <span class=\"flex-1\">{{ 'core.module.layout.user.notification' | sdTranslate }}</span>\n @if (_notificationBadge) {\n <span data-notification-badge class=\"sd-layout-user-menu__badge\">{{ _notificationBadge }}</span>\n }\n </button>\n }\n @if (changePasswordAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"change-password\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left\"\n (click)=\"runChangePassword()\">\n <sd-icon name=\"lock\"></sd-icon>\n {{ 'core.module.layout.user.change-password' | sdTranslate }}\n </button>\n }\n @if (signoutAction()) {\n <button\n type=\"button\"\n role=\"menuitem\"\n data-user-action=\"signout\"\n class=\"sd-layout-user-menu__action d-flex align-items-center gap-8 px-8 py-8 T14R text-left text-error\"\n (click)=\"runSignout()\">\n <sd-icon name=\"logout\"></sd-icon>\n {{ 'core.module.layout.user.logout' | sdTranslate }}\n </button>\n }\n </div>\n }\n }\n</div>\n", styles: [".sd-layout-user-menu__trigger,.sd-layout-user-menu__action{border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-user-menu__trigger:hover,.sd-layout-user-menu__action:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3))}.sd-layout-user-menu__trigger:focus-visible,.sd-layout-user-menu__action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-user-menu__trigger--compact{width:56px;min-height:52px;justify-content:center;gap:0;padding:0}.sd-layout-user-menu__popup{position:absolute;z-index:40;left:0;bottom:calc(100% + 8px);width:272px}.sd-layout-user-menu__popup .sd-layout-user-menu__action,.sd-layout-user-menu__mobile-actions .sd-layout-user-menu__action{width:100%}.sd-layout-user-menu__summary{min-width:0;padding:0 8px}.sd-layout-user-menu__identity{min-width:0}.sd-layout-user-menu__signout{width:auto;flex:0 0 auto;min-height:40px;white-space:nowrap}.sd-layout-user-menu__role sd-icon{width:14px;height:14px;font-size:14px;flex:0 0 auto}.sd-layout-user-menu__badge{min-width:20px;padding:1px 6px;border-radius:999px;background:var(--sd-error, #ba1a1a);color:var(--sd-white, #ffffff);font-size:11px;line-height:18px;text-align:center}.sd-layout-user-menu__signout:hover{background:var(--sd-error-light, #ffdad6);color:var(--sd-error-dark, var(--sd-error, #ba1a1a))}@media(prefers-reduced-motion:reduce){.sd-layout-user-menu__trigger,.sd-layout-user-menu__action{transition:none}}\n"] }]
|
|
741
900
|
}], ctorParameters: () => [], propDecorators: { trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], menu: [{ type: i0.ViewChild, args: ['menu', { isSignal: true }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], signout: [{ type: i0.Input, args: [{ isSignal: true, alias: "signout", required: false }] }], changePassword: [{ type: i0.Input, args: [{ isSignal: true, alias: "changePassword", required: false }] }], updateProfile: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateProfile", required: false }] }], setting: [{ type: i0.Input, args: [{ isSignal: true, alias: "setting", required: false }] }], notification: [{ type: i0.Input, args: [{ isSignal: true, alias: "notification", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], presentation: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentation", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
742
901
|
|
|
743
902
|
let LayoutUserComponent$1 = class LayoutUserComponent {
|
|
@@ -758,14 +917,14 @@ let LayoutUserComponent$1 = class LayoutUserComponent {
|
|
|
758
917
|
this.toggleMenuLock.emit(event);
|
|
759
918
|
}
|
|
760
919
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
761
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "<div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n @if (!isMobileOrTablet()) {\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' |
|
|
920
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "<div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n @if (!isMobileOrTablet()) {\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | sdTranslate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n }\n</div>\n", styles: [".c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
762
921
|
};
|
|
763
922
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent$1, decorators: [{
|
|
764
923
|
type: Component,
|
|
765
|
-
args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent,
|
|
924
|
+
args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent, SdTranslatePipe], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n @if (!isMobileOrTablet()) {\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | sdTranslate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n }\n</div>\n", styles: [".c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"] }]
|
|
766
925
|
}], propDecorators: { isMobileOrTablet: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobileOrTablet", required: false }] }], isMenuLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMenuLock", required: false }] }], isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], menuClosed: [{ type: i0.Output, args: ["menuClosed"] }], menuOpened: [{ type: i0.Output, args: ["menuOpened"] }], toggleMenuLock: [{ type: i0.Output, args: ["toggleMenuLock"] }] } });
|
|
767
926
|
|
|
768
|
-
class
|
|
927
|
+
class SdSidebarV1Panel {
|
|
769
928
|
// ==========================================
|
|
770
929
|
// INJECT SERVICES (Thay thế Constructor rườm rà)
|
|
771
930
|
// ==========================================
|
|
@@ -773,6 +932,7 @@ class SidebarComponent {
|
|
|
773
932
|
#layoutStorageService = inject(SdLayoutStorageService);
|
|
774
933
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
775
934
|
#menuFocusPipe = inject(MenuFocusPipe);
|
|
935
|
+
#i18n = inject(I18nService);
|
|
776
936
|
#window = inject(DOCUMENT).defaultView;
|
|
777
937
|
#destroyRef = inject(DestroyRef); // Dùng để unsubscribe RxJS tự động
|
|
778
938
|
// ==========================================
|
|
@@ -798,9 +958,11 @@ class SidebarComponent {
|
|
|
798
958
|
menusByGroup = signal([], ...(ngDevMode ? [{ debugName: "menusByGroup" }] : []));
|
|
799
959
|
#hoveredMenuNodeKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredMenuNodeKey" }] : []));
|
|
800
960
|
#pinIconHoverTimerId = signal(null, ...(ngDevMode ? [{ debugName: "#pinIconHoverTimerId" }] : []));
|
|
961
|
+
// why: `title` của nhóm ghim là nhãn HIỂN THỊ (đổ vào `titleMenuGroup` trên header), không phải
|
|
962
|
+
// id — phải dịch. Tính trong computed nên đổi ngôn ngữ là tiêu đề nhóm đổi theo.
|
|
801
963
|
pinnedMenuGroup = computed(() => ({
|
|
802
964
|
id: 'pinned-menu-group',
|
|
803
|
-
title: '
|
|
965
|
+
title: this.#i18n.t('core.module.layout.sidebar.pinned'),
|
|
804
966
|
children: this.#navigationState.pinnedMenus(),
|
|
805
967
|
}), ...(ngDevMode ? [{ debugName: "pinnedMenuGroup" }] : []));
|
|
806
968
|
// ==========================================
|
|
@@ -858,6 +1020,22 @@ class SidebarComponent {
|
|
|
858
1020
|
onToggleMenuNode = (menu) => {
|
|
859
1021
|
this.treeControl.toggle(menu);
|
|
860
1022
|
};
|
|
1023
|
+
// why: mục menu nay là role="button" + tabindex="0" nên Enter/Space phải điều hướng đúng như
|
|
1024
|
+
// click. Lọc theo target để phím bấm trên nút ghim lồng bên trong không kéo theo điều hướng.
|
|
1025
|
+
onMenuNodeKeydown = (event, node) => {
|
|
1026
|
+
if (event.target !== event.currentTarget)
|
|
1027
|
+
return;
|
|
1028
|
+
// why: chặn Space cuộn trang.
|
|
1029
|
+
event.preventDefault();
|
|
1030
|
+
this.navigate({ path: node.path, queryParams: node.queryParams ?? {} });
|
|
1031
|
+
};
|
|
1032
|
+
// why: nhánh có con nay là role="button" + aria-expanded → Enter/Space phải gập/mở đúng như click.
|
|
1033
|
+
onToggleMenuNodeKeydown = (event, menu) => {
|
|
1034
|
+
if (event.target !== event.currentTarget)
|
|
1035
|
+
return;
|
|
1036
|
+
event.preventDefault();
|
|
1037
|
+
this.onToggleMenuNode(menu);
|
|
1038
|
+
};
|
|
861
1039
|
isPinnedNode = (node) => this.pinnedNodeKeys().has(this.#getMenuNodeKey(node));
|
|
862
1040
|
isHoveredNode = (node) => this.#hoveredMenuNodeKey() === this.#getMenuNodeKey(node);
|
|
863
1041
|
expandPinnedGroup = () => {
|
|
@@ -904,8 +1082,12 @@ class SidebarComponent {
|
|
|
904
1082
|
onUserMenuOpened = () => this.popupUserMenuOpened.emit();
|
|
905
1083
|
navigate = (args) => {
|
|
906
1084
|
const { path, queryParams } = args;
|
|
907
|
-
|
|
908
|
-
|
|
1085
|
+
// why: `path.includes('http')` là substring test chứ không phải scheme test — chuỗi
|
|
1086
|
+
// `javascript:fetch(...)//http` lọt qua rồi chạy như script trong chính origin của app.
|
|
1087
|
+
// `sdIsExternalHttpUrl` parse URL thật, `sdOpenExternal` từ chối scheme lạ và luôn gắn
|
|
1088
|
+
// `noopener,noreferrer` (chặn reverse tabnabbing qua `window.opener`).
|
|
1089
|
+
if (sdIsExternalHttpUrl(path)) {
|
|
1090
|
+
sdOpenExternal(path);
|
|
909
1091
|
return;
|
|
910
1092
|
}
|
|
911
1093
|
this.#router.navigate([path.split('?')[0]], {
|
|
@@ -1291,12 +1473,12 @@ class SidebarComponent {
|
|
|
1291
1473
|
}
|
|
1292
1474
|
return count;
|
|
1293
1475
|
};
|
|
1294
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1295
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarComponent, isStandalone: true, selector: "sidebar", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" aria-label=\"Trang ch\u1EE7\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n (click)=\"onTogglePin($event, node)\"\n name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i2.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i2.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1476
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1477
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarV1Panel, isStandalone: true, selector: "sd-sidebar-v1-panel", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | sdTranslate\"\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 <!-- why: role=button KH\u00D4NG \u0111\u01B0\u1EE3c b\u1ECDc ph\u1EA7n t\u1EED t\u01B0\u01A1ng t\u00E1c kh\u00E1c (n\u00FAt ghim b\u00EAn trong) \u2014\n AT coi c\u1EA3 c\u1EE5m l\u00E0 M\u1ED8T n\u00FAt v\u00E0 n\u00FAt ghim bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Nay\n role=button + tabindex + Enter/Space + aria-current + (click) n\u1EB1m C\u00D9NG tr\u00EAn\n ph\u1EA7n TI\u00CAU \u0110\u1EC0, n\u00FAt ghim l\u00E0 sibling \u0111\u1ED9c l\u1EADp; c\u1EA3 hai \u0111\u1EC1u focus \u0111\u01B0\u1EE3c. -->\n <div class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"navigate(node)\"\n (keydown.enter)=\"onMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onMenuNodeKeydown($event, node)\"\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 <!-- why: n\u00FAt ghim tr\u01B0\u1EDBc \u0111\u00E2y l\u00E0 <sd-icon (click)> \u2014 custom element kh\u00F4ng nh\u1EADn\n focus b\u00E0n ph\u00EDm v\u00E0 kh\u00F4ng c\u00F3 t\u00EAn kh\u1EA3 truy c\u1EADp, n\u00EAn kh\u00F4ng ghim \u0111\u01B0\u1EE3c b\u1EB1ng ph\u00EDm.\n \u0110\u1ED5i sang <button type=\"button\"> + aria-pressed, gi\u1ED1ng pattern \u0111\u00E3 d\u00F9ng \u1EDF\n `shared/menu-tree`. -->\n @if (_sidebar?.pin?.enabled) {\n <button\n type=\"button\"\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 [attr.aria-pressed]=\"isPinnedNode(node)\"\n [attr.aria-label]=\"\n (isPinnedNode(node) ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin')\n | sdTranslate: { title: node.title }\n \"\n (click)=\"onTogglePin($event, node)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 n\u00F3 \u1EA9n TO\u00C0N B\u1ED8 nh\u00E1nh menu c\u00F3 con (ti\u00EAu \u0111\u1EC1 nh\u00F3m l\u1EABn\n m\u1ECDi m\u1EE5c con b\u00EAn trong) kh\u1ECFi accessibility tree. -->\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\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 <!-- why: nh\u00E1nh c\u00F3 con l\u00E0 n\u00FAt G\u1EACP/M\u1EDE th\u1EADt nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 c\u00F3 (click) v\u00E0\n kh\u00F4ng khai tr\u1EA1ng th\u00E1i. Nay role=button + tabindex + aria-expanded +\n Enter/Space. -->\n <div\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\"\n (keydown.enter)=\"onToggleMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onToggleMenuNodeKeydown($event, node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;padding:0;font-size:18px;opacity:0}.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:focus-visible{opacity:1;outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i2.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i2.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1296
1478
|
}
|
|
1297
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1479
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, decorators: [{
|
|
1298
1480
|
type: Component,
|
|
1299
|
-
args: [{ selector: 'sidebar', standalone: true, imports: [
|
|
1481
|
+
args: [{ selector: 'sd-sidebar-v1-panel', standalone: true, imports: [
|
|
1300
1482
|
SdIcon,
|
|
1301
1483
|
SdInput,
|
|
1302
1484
|
FormsModule,
|
|
@@ -1310,11 +1492,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1310
1492
|
HighlightSearchPipe,
|
|
1311
1493
|
SdSuffixDefDirective,
|
|
1312
1494
|
LayoutUserComponent$1,
|
|
1313
|
-
|
|
1314
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" aria-label=\"Trang ch\u1EE7\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'\u0110\u00E3 ghim'\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | translate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <div aria-hidden=\"true\" class=\"c-menu-node-description\" (click)=\"navigate(node)\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n @if (_sidebar?.pin?.enabled) {\n <sd-icon\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n (click)=\"onTogglePin($event, node)\"\n name=\"push_pin\"></sd-icon>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n aria-hidden=\"true\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <div [class.d-none]=\"!isShowSidebar()\" class=\"c-menu-node-description\" (click)=\"onToggleMenuNode(node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:18px;opacity:0}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"] }]
|
|
1495
|
+
SdTranslatePipe,
|
|
1496
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | sdTranslate\"\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 <!-- why: role=button KH\u00D4NG \u0111\u01B0\u1EE3c b\u1ECDc ph\u1EA7n t\u1EED t\u01B0\u01A1ng t\u00E1c kh\u00E1c (n\u00FAt ghim b\u00EAn trong) \u2014\n AT coi c\u1EA3 c\u1EE5m l\u00E0 M\u1ED8T n\u00FAt v\u00E0 n\u00FAt ghim bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Nay\n role=button + tabindex + Enter/Space + aria-current + (click) n\u1EB1m C\u00D9NG tr\u00EAn\n ph\u1EA7n TI\u00CAU \u0110\u1EC0, n\u00FAt ghim l\u00E0 sibling \u0111\u1ED9c l\u1EADp; c\u1EA3 hai \u0111\u1EC1u focus \u0111\u01B0\u1EE3c. -->\n <div class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"navigate(node)\"\n (keydown.enter)=\"onMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onMenuNodeKeydown($event, node)\"\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 <!-- why: n\u00FAt ghim tr\u01B0\u1EDBc \u0111\u00E2y l\u00E0 <sd-icon (click)> \u2014 custom element kh\u00F4ng nh\u1EADn\n focus b\u00E0n ph\u00EDm v\u00E0 kh\u00F4ng c\u00F3 t\u00EAn kh\u1EA3 truy c\u1EADp, n\u00EAn kh\u00F4ng ghim \u0111\u01B0\u1EE3c b\u1EB1ng ph\u00EDm.\n \u0110\u1ED5i sang <button type=\"button\"> + aria-pressed, gi\u1ED1ng pattern \u0111\u00E3 d\u00F9ng \u1EDF\n `shared/menu-tree`. -->\n @if (_sidebar?.pin?.enabled) {\n <button\n type=\"button\"\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 [attr.aria-pressed]=\"isPinnedNode(node)\"\n [attr.aria-label]=\"\n (isPinnedNode(node) ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin')\n | sdTranslate: { title: node.title }\n \"\n (click)=\"onTogglePin($event, node)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 n\u00F3 \u1EA9n TO\u00C0N B\u1ED8 nh\u00E1nh menu c\u00F3 con (ti\u00EAu \u0111\u1EC1 nh\u00F3m l\u1EABn\n m\u1ECDi m\u1EE5c con b\u00EAn trong) kh\u1ECFi accessibility tree. -->\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\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 <!-- why: nh\u00E1nh c\u00F3 con l\u00E0 n\u00FAt G\u1EACP/M\u1EDE th\u1EADt nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 c\u00F3 (click) v\u00E0\n kh\u00F4ng khai tr\u1EA1ng th\u00E1i. Nay role=button + tabindex + aria-expanded +\n Enter/Space. -->\n <div\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\"\n (keydown.enter)=\"onToggleMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onToggleMenuNodeKeydown($event, node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;padding:0;font-size:18px;opacity:0}.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:focus-visible{opacity:1;outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"] }]
|
|
1315
1497
|
}], 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"] }] } });
|
|
1316
1498
|
|
|
1317
|
-
class
|
|
1499
|
+
class SdSidebarV1 {
|
|
1318
1500
|
// ==========================================
|
|
1319
1501
|
// INJECT SERVICES
|
|
1320
1502
|
// ==========================================
|
|
@@ -1438,12 +1620,12 @@ class SidebarV1Component {
|
|
|
1438
1620
|
}
|
|
1439
1621
|
}
|
|
1440
1622
|
};
|
|
1441
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1442
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type:
|
|
1623
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1624
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.25", type: SdSidebarV1, isStandalone: true, selector: "sd-sidebar-v1", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "sidenav", first: true, predicate: ["sidenav"], descendants: true, isSignal: true }], ngImport: i0, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"!isMobile() || isShowSidebar()\"\n (openedChange)=\"onSidenavOpenedChange($event)\"\n class=\"c-layout-sidebar\"\n [class.is-expanded]=\"isShowSidebar()\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobile() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobile()\">\n <sd-sidebar-v1-panel\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 </sd-sidebar-v1-panel>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>\n", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:clip!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.is-expanded{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i1$1.MatDrawerContainer, selector: "mat-drawer-container", inputs: ["autosize", "hasBackdrop"], outputs: ["backdropClick"], exportAs: ["matDrawerContainer"] }, { kind: "component", type: i1$1.MatDrawerContent, selector: "mat-drawer-content" }, { kind: "component", type: i1$1.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i1$1.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i1$1.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdSidebarV1Panel, selector: "sd-sidebar-v1-panel", inputs: ["isShowSidebar", "menus", "userInfo", "sidebar", "isMobile"], outputs: ["expandSideBar", "popupUserMenuClosed", "popupUserMenuOpened", "showSideBar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1443
1625
|
}
|
|
1444
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1626
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1, decorators: [{
|
|
1445
1627
|
type: Component,
|
|
1446
|
-
args: [{ selector: 'sidebar-v1', imports: [MatSidenavModule, CommonModule,
|
|
1628
|
+
args: [{ selector: 'sd-sidebar-v1', imports: [MatSidenavModule, CommonModule, SdSidebarV1Panel], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<mat-sidenav-container class=\"c-layout-wrapper\">\n <mat-sidenav\n #sidenav\n [opened]=\"!isMobile() || isShowSidebar()\"\n (openedChange)=\"onSidenavOpenedChange($event)\"\n class=\"c-layout-sidebar\"\n [class.is-expanded]=\"isShowSidebar()\"\n (click)=\"openSidebar()\"\n (mouseleave)=\"onMouseleaveSideBar()\"\n [mode]=\"isMobile() ? 'over' : 'side'\"\n [fixedInViewport]=\"isMobile()\">\n <sd-sidebar-v1-panel\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 </sd-sidebar-v1-panel>\n </mat-sidenav>\n\n <mat-sidenav-content class=\"c-layout-content\" [class.isShowSidebar]=\"isMenuLock()\">\n <mat-drawer-container class=\"c-mat-drawer-container\">\n <mat-drawer-content>\n <ng-content></ng-content>\n </mat-drawer-content>\n </mat-drawer-container>\n </mat-sidenav-content>\n</mat-sidenav-container>\n", styles: [":host ::ng-deep .c-layout-wrapper{width:100%}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar{margin-top:0;width:60px;height:100vh!important;transform:none!important;overflow:clip!important;visibility:visible!important;transition:width .15s!important;box-shadow:0 0 6px #0003!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar .mat-drawer-inner-container{position:relative;overflow:visible;display:block!important}:host ::ng-deep .c-layout-wrapper .c-layout-sidebar.is-expanded{width:290px;transition:width .15s}:host ::ng-deep .c-layout-wrapper .c-layout-content{background-color:#fff;box-sizing:border-box;height:100vh!important;margin-left:60px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content.isShowSidebar{margin-left:290px!important}:host ::ng-deep .c-layout-wrapper .c-layout-content .c-mat-drawer-container{height:100%}\n"] }]
|
|
1447
1629
|
}], 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 }] }] } });
|
|
1448
1630
|
|
|
1449
1631
|
class LayoutUserComponent {
|
|
@@ -1464,17 +1646,20 @@ class LayoutUserComponent {
|
|
|
1464
1646
|
this.toggleMenuLock.emit(event);
|
|
1465
1647
|
}
|
|
1466
1648
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1467
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@if (isMobileOrTablet()) {\n <sd-layout-user-menu [userInfo]=\"userInfo()\" presentation=\"mobile\"></sd-layout-user-menu>\n} @else {\n <div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' |
|
|
1649
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LayoutUserComponent, isStandalone: true, selector: "lib-layout-user", inputs: { isMobileOrTablet: { classPropertyName: "isMobileOrTablet", publicName: "isMobileOrTablet", isSignal: true, isRequired: false, transformFunction: null }, isMenuLock: { classPropertyName: "isMenuLock", publicName: "isMenuLock", isSignal: true, isRequired: false, transformFunction: null }, isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { menuClosed: "menuClosed", menuOpened: "menuOpened", toggleMenuLock: "toggleMenuLock" }, ngImport: i0, template: "@if (isMobileOrTablet()) {\n <sd-layout-user-menu [userInfo]=\"userInfo()\" presentation=\"mobile\"></sd-layout-user-menu>\n} @else {\n <div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | sdTranslate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n </div>\n}\n", styles: [":host{display:block;width:100%}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1468
1650
|
}
|
|
1469
1651
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, decorators: [{
|
|
1470
1652
|
type: Component,
|
|
1471
|
-
args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent,
|
|
1653
|
+
args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent, SdTranslatePipe], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isMobileOrTablet()) {\n <sd-layout-user-menu [userInfo]=\"userInfo()\" presentation=\"mobile\"></sd-layout-user-menu>\n} @else {\n <div class=\"c-layout-user-wrapper\">\n <sd-layout-user-menu\n class=\"c-layout-user-avatar\"\n [userInfo]=\"userInfo()\"\n [compact]=\"true\"\n (opened)=\"onMenuOpened()\"\n (closed)=\"onMenuClosed()\"></sd-layout-user-menu>\n\n <button\n type=\"button\"\n class=\"c-layout-icon-toggle\"\n [attr.aria-label]=\"'core.module.layout.sidebar.toggle' | sdTranslate\"\n (click)=\"onToggleMenuLock($event)\">\n <sd-icon [name]=\"isShowSidebar() ? 'keyboard_arrow_right' : 'keyboard_arrow_left'\"></sd-icon>\n </button>\n </div>\n}\n", styles: [":host{display:block;width:100%}.c-layout-user-wrapper{display:flex;flex-direction:column;align-items:center;width:60px;height:80px}.c-layout-user-avatar{display:block;width:56px;height:52px}.c-layout-icon-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:28px;margin:0;padding:0;border:0;border-top:1px solid var(--sd-black200, #f0f0f0);background:transparent;color:var(--sd-black400, #8c8c8c);cursor:pointer}.c-layout-icon-toggle:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}\n"] }]
|
|
1472
1654
|
}], propDecorators: { isMobileOrTablet: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobileOrTablet", required: false }] }], isMenuLock: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMenuLock", required: false }] }], isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: false }] }], userInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInfo", required: true }] }], menuClosed: [{ type: i0.Output, args: ["menuClosed"] }], menuOpened: [{ type: i0.Output, args: ["menuOpened"] }], toggleMenuLock: [{ type: i0.Output, args: ["toggleMenuLock"] }] } });
|
|
1473
1655
|
|
|
1474
|
-
class
|
|
1656
|
+
class SdSidebarMobileOverlay {
|
|
1475
1657
|
#router = inject(Router);
|
|
1476
1658
|
#destroyRef = inject(DestroyRef);
|
|
1477
1659
|
#layoutStorageService = inject(SdLayoutStorageService);
|
|
1660
|
+
// why: các sidebar anh em đều lấy window qua DOCUMENT.defaultView; đọc global `window` trực tiếp
|
|
1661
|
+
// sẽ throw khi render phía server (SSR) vì global đó không tồn tại.
|
|
1662
|
+
#window = inject(DOCUMENT).defaultView;
|
|
1478
1663
|
// ==========================================
|
|
1479
1664
|
// INPUTS & OUTPUTS
|
|
1480
1665
|
// ==========================================
|
|
@@ -1492,7 +1677,7 @@ class SidebarMobileOverlayComponent {
|
|
|
1492
1677
|
// STATE SIGNALS
|
|
1493
1678
|
// ==========================================
|
|
1494
1679
|
titleMenuGroup = signal('', ...(ngDevMode ? [{ debugName: "titleMenuGroup" }] : []));
|
|
1495
|
-
currentPath = signal(window
|
|
1680
|
+
currentPath = signal(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
|
|
1496
1681
|
// Lưu danh sách ID các menu group đang được mở
|
|
1497
1682
|
expandedMobileGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedMobileGroups" }] : []));
|
|
1498
1683
|
constructor() {
|
|
@@ -1510,8 +1695,11 @@ class SidebarMobileOverlayComponent {
|
|
|
1510
1695
|
// Bind title khi điều hướng
|
|
1511
1696
|
this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
|
|
1512
1697
|
if (event instanceof NavigationEnd) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1698
|
+
// why: giống sidebar v1 desktop — lấy path từ chính sự kiện router thay vì đọc global
|
|
1699
|
+
// `window.location`, để component chạy được cả khi không có global window (SSR).
|
|
1700
|
+
const currentPath = event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '';
|
|
1701
|
+
if (this.currentPath() !== currentPath) {
|
|
1702
|
+
this.currentPath.set(currentPath);
|
|
1515
1703
|
this.#bindingMenuGroupByCurrentPath(this.menus());
|
|
1516
1704
|
}
|
|
1517
1705
|
}
|
|
@@ -1543,8 +1731,12 @@ class SidebarMobileOverlayComponent {
|
|
|
1543
1731
|
const { path, queryParams } = args;
|
|
1544
1732
|
if (!path)
|
|
1545
1733
|
return;
|
|
1546
|
-
|
|
1547
|
-
|
|
1734
|
+
// why: `path.includes('http')` là substring test chứ không phải scheme test —
|
|
1735
|
+
// `javascript:fetch(...)//http` lọt qua rồi chạy như script trong chính origin của app. Ngoài ra
|
|
1736
|
+
// `window.open(path)` cũ không truyền `noopener` nên tab mới giữ được `window.opener` và có thể
|
|
1737
|
+
// điều hướng ngược tab gốc (reverse tabnabbing). `sdOpenExternal` luôn gắn `noopener,noreferrer`.
|
|
1738
|
+
if (sdIsExternalHttpUrl(path)) {
|
|
1739
|
+
sdOpenExternal(path);
|
|
1548
1740
|
return;
|
|
1549
1741
|
}
|
|
1550
1742
|
this.#router.navigate([path.split('?')[0]], {
|
|
@@ -1617,15 +1809,15 @@ class SidebarMobileOverlayComponent {
|
|
|
1617
1809
|
return this.#normalizePath(this.currentPath()).startsWith(this.#normalizePath(path));
|
|
1618
1810
|
};
|
|
1619
1811
|
#normalizePath = (p) => (p.endsWith('/') ? p : p + '/');
|
|
1620
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1621
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SidebarMobileOverlayComponent, isStandalone: true, selector: "sd-sidebar-mobile-overlay", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, 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: { showSideBar: "showSideBar", expandSideBar: "expandSideBar", popupUserMenuOpened: "popupUserMenuOpened", popupUserMenuClosed: "popupUserMenuClosed", titleMenuGroupChanged: "titleMenuGroupChanged" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div\n class=\"m-backdrop\"\n [class.m-backdrop--visible]=\"isShowSidebar()\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"onClose()\"\n (keydown.escape)=\"onClose()\">\n </div>\n\n <div class=\"m-wrapper\" [class.m-wrapper--open]=\"isShowSidebar()\">\n <div class=\"m-header\">\n <div class=\"m-title-group\">\n @if (_sidebar.logoUrl) {\n <img class=\"m-logo\" alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n }\n <span class=\"m-title\">{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n <button class=\"m-close-btn\" (click)=\"onClose()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </div>\n\n <div class=\"m-user-section\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"true\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\">\n </lib-layout-user>\n </div>\n\n <div class=\"m-menu-list\">\n @for (group of menus(); track group.id) {\n <div class=\"m-menu-card\">\n <button class=\"m-card-header\" (click)=\"toggleMobileGroup(group.id)\">\n <div class=\"m-card-header-left\">\n @if (group.iconUrl) {\n <img class=\"m-group-icon\" [src]=\"group.iconUrl\" [alt]=\"group.title\" />\n } @else {\n <sd-icon class=\"m-group-icon\" [name]=\"group.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"m-group-title\">{{ group.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(group.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n\n @if (expandedMobileGroups().has(group.id ?? '') && hasChildren(group)) {\n <div class=\"m-card-body\">\n @for (child of getChildren(group); track child.id) {\n @if (hasChildren(child)) {\n <div class=\"m-sub-group\">\n <button class=\"m-sub-group-header\" (click)=\"toggleMobileGroup(child.id)\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'folder'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(child.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n @if (expandedMobileGroups().has(child.id ?? '')) {\n @for (subChild of getChildren(child); track subChild.id) {\n <button class=\"m-menu-item m-menu-item--indented\"\n (click)=\"navigate({ path: getPath(subChild), queryParams: getQueryParams(subChild) })\">\n <div class=\"m-item-left\">\n @if (subChild.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"subChild.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"subChild.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ subChild.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n } @else {\n <button class=\"m-menu-item\" (click)=\"navigate({ path: getPath(child), queryParams: getQueryParams(child) })\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n}\n", styles: [":host{display:block;position:fixed;inset:0;z-index:1000;pointer-events:none}.m-backdrop{position:absolute;inset:0;background:#0006;opacity:0;pointer-events:none;transition:opacity .3s ease;cursor:pointer;outline:none}.m-backdrop--visible{opacity:1;pointer-events:auto}.m-wrapper{display:flex;flex-direction:column;position:absolute;inset:0;background-color:#f2f2f6;padding:16px;box-sizing:border-box;overflow-y:auto;pointer-events:none;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1)}.m-wrapper--open{transform:translate(0);pointer-events:auto}.m-wrapper .m-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.m-wrapper .m-header .m-title-group{display:flex;align-items:center;gap:12px}.m-wrapper .m-header .m-title-group .m-logo{width:32px;height:32px;object-fit:contain}.m-wrapper .m-header .m-title-group .m-title{font-size:18px;font-weight:600;color:#1f1f1f}.m-wrapper .m-header .m-close-btn{background:transparent;border:none;padding:0;color:#1f1f1f;display:flex;align-items:center;justify-content:center;cursor:pointer}.m-wrapper .m-header .m-close-btn sd-icon{font-size:28px;width:28px;height:28px}.m-wrapper .m-user-section{background-color:#fff;border-radius:12px;padding:12px 16px;margin-bottom:24px;box-shadow:0 1px 2px #00000005;border:1px solid #EBEBEB}.m-wrapper .m-user-section lib-layout-user{display:block;width:100%}.m-wrapper .m-menu-list{display:flex;flex-direction:column;gap:16px}.m-wrapper .m-menu-card{background-color:#fff;border-radius:12px;border:1px solid #EBEBEB;overflow:hidden}.m-wrapper .m-menu-card .m-card-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:16px;cursor:pointer;-webkit-user-select:none;user-select:none;border:none;background:transparent;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-header .m-card-header-left{display:flex;align-items:center;gap:16px}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-icon{font-size:24px;width:24px;height:24px;color:#1f1f1f;object-fit:contain}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-title{font-size:16px;font-weight:600;color:#1f1f1f}.m-wrapper .m-menu-card .m-card-header .m-expand-icon{color:#1f1f1f;transition:transform .2s ease}.m-wrapper .m-menu-card .m-card-body{display:flex;flex-direction:column;padding-bottom:8px}.m-wrapper .m-menu-card .m-card-body .m-menu-item{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:transparent;transition:background-color .2s;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-menu-item:active{background-color:#f5f5f5}.m-wrapper .m-menu-card .m-card-body .m-menu-item--indented{padding-left:48px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-title{font-size:15px;color:#333}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-nav-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-wrapper .m-menu-card .m-card-body .m-sub-group{border-top:1px solid #EBEBEB}.m-wrapper .m-menu-card .m-card-body .m-sub-group:first-child{border-top:none}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:#fafafa;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header:active{background-color:#f0f0f0}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-title{font-size:15px;font-weight:500;color:#333}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-expand-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: LayoutUserComponent, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }] });
|
|
1812
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileOverlay, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1813
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarMobileOverlay, isStandalone: true, selector: "sd-sidebar-mobile-overlay", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: false, 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: { showSideBar: "showSideBar", expandSideBar: "expandSideBar", popupUserMenuOpened: "popupUserMenuOpened", popupUserMenuClosed: "popupUserMenuClosed", titleMenuGroupChanged: "titleMenuGroupChanged" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div\n class=\"m-backdrop\"\n [class.m-backdrop--visible]=\"isShowSidebar()\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"onClose()\"\n (keydown.escape)=\"onClose()\">\n </div>\n\n <div class=\"m-wrapper\" [class.m-wrapper--open]=\"isShowSidebar()\">\n <div class=\"m-header\">\n <div class=\"m-title-group\">\n @if (_sidebar.logoUrl) {\n <img class=\"m-logo\" alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n }\n <span class=\"m-title\">{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n <button class=\"m-close-btn\" (click)=\"onClose()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </div>\n\n <div class=\"m-user-section\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"true\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\">\n </lib-layout-user>\n </div>\n\n <div class=\"m-menu-list\">\n @for (group of menus(); track group.id) {\n <div class=\"m-menu-card\">\n <button class=\"m-card-header\" (click)=\"toggleMobileGroup(group.id)\">\n <div class=\"m-card-header-left\">\n @if (group.iconUrl) {\n <img class=\"m-group-icon\" [src]=\"group.iconUrl\" [alt]=\"group.title\" />\n } @else {\n <sd-icon class=\"m-group-icon\" [name]=\"group.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"m-group-title\">{{ group.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(group.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n\n @if (expandedMobileGroups().has(group.id ?? '') && hasChildren(group)) {\n <div class=\"m-card-body\">\n @for (child of getChildren(group); track child.id) {\n @if (hasChildren(child)) {\n <div class=\"m-sub-group\">\n <button class=\"m-sub-group-header\" (click)=\"toggleMobileGroup(child.id)\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'folder'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(child.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n @if (expandedMobileGroups().has(child.id ?? '')) {\n @for (subChild of getChildren(child); track subChild.id) {\n <button class=\"m-menu-item m-menu-item--indented\"\n (click)=\"navigate({ path: getPath(subChild), queryParams: getQueryParams(subChild) })\">\n <div class=\"m-item-left\">\n @if (subChild.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"subChild.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"subChild.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ subChild.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n } @else {\n <button class=\"m-menu-item\" (click)=\"navigate({ path: getPath(child), queryParams: getQueryParams(child) })\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n}\n", styles: [":host{display:block;position:fixed;inset:0;z-index:1000;pointer-events:none}.m-backdrop{position:absolute;inset:0;background:#0006;opacity:0;pointer-events:none;transition:opacity .3s ease;cursor:pointer;outline:none}.m-backdrop--visible{opacity:1;pointer-events:auto}.m-wrapper{display:flex;flex-direction:column;position:absolute;inset:0;background-color:#f2f2f6;padding:16px;box-sizing:border-box;overflow-y:auto;pointer-events:none;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1)}.m-wrapper--open{transform:translate(0);pointer-events:auto}.m-wrapper .m-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.m-wrapper .m-header .m-title-group{display:flex;align-items:center;gap:12px}.m-wrapper .m-header .m-title-group .m-logo{width:32px;height:32px;object-fit:contain}.m-wrapper .m-header .m-title-group .m-title{font-size:18px;font-weight:600;color:#1f1f1f}.m-wrapper .m-header .m-close-btn{background:transparent;border:none;padding:0;color:#1f1f1f;display:flex;align-items:center;justify-content:center;cursor:pointer}.m-wrapper .m-header .m-close-btn sd-icon{font-size:28px;width:28px;height:28px}.m-wrapper .m-user-section{background-color:#fff;border-radius:12px;padding:12px 16px;margin-bottom:24px;box-shadow:0 1px 2px #00000005;border:1px solid #EBEBEB}.m-wrapper .m-user-section lib-layout-user{display:block;width:100%}.m-wrapper .m-menu-list{display:flex;flex-direction:column;gap:16px}.m-wrapper .m-menu-card{background-color:#fff;border-radius:12px;border:1px solid #EBEBEB;overflow:hidden}.m-wrapper .m-menu-card .m-card-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:16px;cursor:pointer;-webkit-user-select:none;user-select:none;border:none;background:transparent;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-header .m-card-header-left{display:flex;align-items:center;gap:16px}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-icon{font-size:24px;width:24px;height:24px;color:#1f1f1f;object-fit:contain}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-title{font-size:16px;font-weight:600;color:#1f1f1f}.m-wrapper .m-menu-card .m-card-header .m-expand-icon{color:#1f1f1f;transition:transform .2s ease}.m-wrapper .m-menu-card .m-card-body{display:flex;flex-direction:column;padding-bottom:8px}.m-wrapper .m-menu-card .m-card-body .m-menu-item{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:transparent;transition:background-color .2s;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-menu-item:active{background-color:#f5f5f5}.m-wrapper .m-menu-card .m-card-body .m-menu-item--indented{padding-left:48px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-title{font-size:15px;color:#333}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-nav-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-wrapper .m-menu-card .m-card-body .m-sub-group{border-top:1px solid #EBEBEB}.m-wrapper .m-menu-card .m-card-body .m-sub-group:first-child{border-top:none}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:#fafafa;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header:active{background-color:#f0f0f0}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-title{font-size:15px;font-weight:500;color:#333}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-expand-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: LayoutUserComponent, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }] });
|
|
1622
1814
|
}
|
|
1623
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1815
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileOverlay, decorators: [{
|
|
1624
1816
|
type: Component,
|
|
1625
1817
|
args: [{ selector: 'sd-sidebar-mobile-overlay', standalone: true, imports: [SdIcon, CommonModule, RouterModule, LayoutUserComponent], template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div\n class=\"m-backdrop\"\n [class.m-backdrop--visible]=\"isShowSidebar()\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"onClose()\"\n (keydown.escape)=\"onClose()\">\n </div>\n\n <div class=\"m-wrapper\" [class.m-wrapper--open]=\"isShowSidebar()\">\n <div class=\"m-header\">\n <div class=\"m-title-group\">\n @if (_sidebar.logoUrl) {\n <img class=\"m-logo\" alt=\"logo\" [src]=\"_sidebar.logoUrl\" />\n }\n <span class=\"m-title\">{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n <button class=\"m-close-btn\" (click)=\"onClose()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </div>\n\n <div class=\"m-user-section\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"true\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\">\n </lib-layout-user>\n </div>\n\n <div class=\"m-menu-list\">\n @for (group of menus(); track group.id) {\n <div class=\"m-menu-card\">\n <button class=\"m-card-header\" (click)=\"toggleMobileGroup(group.id)\">\n <div class=\"m-card-header-left\">\n @if (group.iconUrl) {\n <img class=\"m-group-icon\" [src]=\"group.iconUrl\" [alt]=\"group.title\" />\n } @else {\n <sd-icon class=\"m-group-icon\" [name]=\"group.icon || 'widgets'\"></sd-icon>\n }\n <span class=\"m-group-title\">{{ group.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(group.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n\n @if (expandedMobileGroups().has(group.id ?? '') && hasChildren(group)) {\n <div class=\"m-card-body\">\n @for (child of getChildren(group); track child.id) {\n @if (hasChildren(child)) {\n <div class=\"m-sub-group\">\n <button class=\"m-sub-group-header\" (click)=\"toggleMobileGroup(child.id)\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'folder'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-expand-icon\" [name]=\"expandedMobileGroups().has(child.id ?? '') ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n @if (expandedMobileGroups().has(child.id ?? '')) {\n @for (subChild of getChildren(child); track subChild.id) {\n <button class=\"m-menu-item m-menu-item--indented\"\n (click)=\"navigate({ path: getPath(subChild), queryParams: getQueryParams(subChild) })\">\n <div class=\"m-item-left\">\n @if (subChild.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"subChild.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"subChild.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ subChild.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n } @else {\n <button class=\"m-menu-item\" (click)=\"navigate({ path: getPath(child), queryParams: getQueryParams(child) })\">\n <div class=\"m-item-left\">\n @if (child.iconUrl) {\n <img class=\"m-item-icon-img\" [src]=\"child.iconUrl\" alt=\"icon\" />\n } @else {\n <sd-icon class=\"m-item-icon\" [name]=\"child.icon || 'insert_drive_file'\"></sd-icon>\n }\n <span class=\"m-item-title\">{{ child.title }}</span>\n </div>\n <sd-icon class=\"m-nav-icon\" name=\"chevron_right\"></sd-icon>\n </button>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n}\n", styles: [":host{display:block;position:fixed;inset:0;z-index:1000;pointer-events:none}.m-backdrop{position:absolute;inset:0;background:#0006;opacity:0;pointer-events:none;transition:opacity .3s ease;cursor:pointer;outline:none}.m-backdrop--visible{opacity:1;pointer-events:auto}.m-wrapper{display:flex;flex-direction:column;position:absolute;inset:0;background-color:#f2f2f6;padding:16px;box-sizing:border-box;overflow-y:auto;pointer-events:none;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));transform:translate(-100%);transition:transform .3s cubic-bezier(.4,0,.2,1)}.m-wrapper--open{transform:translate(0);pointer-events:auto}.m-wrapper .m-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.m-wrapper .m-header .m-title-group{display:flex;align-items:center;gap:12px}.m-wrapper .m-header .m-title-group .m-logo{width:32px;height:32px;object-fit:contain}.m-wrapper .m-header .m-title-group .m-title{font-size:18px;font-weight:600;color:#1f1f1f}.m-wrapper .m-header .m-close-btn{background:transparent;border:none;padding:0;color:#1f1f1f;display:flex;align-items:center;justify-content:center;cursor:pointer}.m-wrapper .m-header .m-close-btn sd-icon{font-size:28px;width:28px;height:28px}.m-wrapper .m-user-section{background-color:#fff;border-radius:12px;padding:12px 16px;margin-bottom:24px;box-shadow:0 1px 2px #00000005;border:1px solid #EBEBEB}.m-wrapper .m-user-section lib-layout-user{display:block;width:100%}.m-wrapper .m-menu-list{display:flex;flex-direction:column;gap:16px}.m-wrapper .m-menu-card{background-color:#fff;border-radius:12px;border:1px solid #EBEBEB;overflow:hidden}.m-wrapper .m-menu-card .m-card-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:16px;cursor:pointer;-webkit-user-select:none;user-select:none;border:none;background:transparent;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-header .m-card-header-left{display:flex;align-items:center;gap:16px}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-icon{font-size:24px;width:24px;height:24px;color:#1f1f1f;object-fit:contain}.m-wrapper .m-menu-card .m-card-header .m-card-header-left .m-group-title{font-size:16px;font-weight:600;color:#1f1f1f}.m-wrapper .m-menu-card .m-card-header .m-expand-icon{color:#1f1f1f;transition:transform .2s ease}.m-wrapper .m-menu-card .m-card-body{display:flex;flex-direction:column;padding-bottom:8px}.m-wrapper .m-menu-card .m-card-body .m-menu-item{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:transparent;transition:background-color .2s;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-menu-item:active{background-color:#f5f5f5}.m-wrapper .m-menu-card .m-card-body .m-menu-item--indented{padding-left:48px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-item-left .m-item-title{font-size:15px;color:#333}.m-wrapper .m-menu-card .m-card-body .m-menu-item .m-nav-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}.m-wrapper .m-menu-card .m-card-body .m-sub-group{border-top:1px solid #EBEBEB}.m-wrapper .m-menu-card .m-card-body .m-sub-group:first-child{border-top:none}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px 12px 24px;cursor:pointer;border:none;background:#fafafa;-webkit-tap-highlight-color:transparent}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header:active{background-color:#f0f0f0}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left{display:flex;align-items:center;gap:12px}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon{font-size:20px;width:20px;height:20px;color:#8c8c8c}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-icon-img{width:20px;height:20px;object-fit:contain;opacity:.7}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-item-left .m-item-title{font-size:15px;font-weight:500;color:#333}.m-wrapper .m-menu-card .m-card-body .m-sub-group .m-sub-group-header .m-expand-icon{color:#8c8c8c;font-size:20px;width:20px;height:20px}\n"] }]
|
|
1626
1818
|
}], ctorParameters: () => [], propDecorators: { isShowSidebar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isShowSidebar", required: false }] }], 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 }] }], showSideBar: [{ type: i0.Output, args: ["showSideBar"] }], expandSideBar: [{ type: i0.Output, args: ["expandSideBar"] }], popupUserMenuOpened: [{ type: i0.Output, args: ["popupUserMenuOpened"] }], popupUserMenuClosed: [{ type: i0.Output, args: ["popupUserMenuClosed"] }], titleMenuGroupChanged: [{ type: i0.Output, args: ["titleMenuGroupChanged"] }] } });
|
|
1627
1819
|
|
|
1628
|
-
class
|
|
1820
|
+
class SdSidebarMobileV1 {
|
|
1629
1821
|
#layoutStorageService = inject(SdLayoutStorageService);
|
|
1630
1822
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
1631
1823
|
userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
@@ -1654,16 +1846,17 @@ class SidebarMobileV1Component {
|
|
|
1654
1846
|
onTitleMenuGroupChanged = (title) => {
|
|
1655
1847
|
this.titleMenuGroup.set(title);
|
|
1656
1848
|
};
|
|
1657
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1658
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type:
|
|
1849
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV1, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarMobileV1, isStandalone: true, selector: "sd-sidebar-mobile-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 } }, ngImport: i0, 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"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdSidebarMobileOverlay, selector: "sd-sidebar-mobile-overlay", inputs: ["isShowSidebar", "menus", "userInfo", "sidebar"], outputs: ["showSideBar", "expandSideBar", "popupUserMenuOpened", "popupUserMenuClosed", "titleMenuGroupChanged"] }] });
|
|
1659
1851
|
}
|
|
1660
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1852
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV1, decorators: [{
|
|
1661
1853
|
type: Component,
|
|
1662
|
-
args: [{ selector: 'sidebar-mobile-v1', imports: [SdIcon, CommonModule,
|
|
1854
|
+
args: [{ selector: 'sd-sidebar-mobile-v1', imports: [SdIcon, CommonModule, SdSidebarMobileOverlay], 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"] }]
|
|
1663
1855
|
}], 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 }] }] } });
|
|
1664
1856
|
|
|
1665
1857
|
class SdLayoutMenuTreeComponent {
|
|
1666
1858
|
#destroyRef = inject(DestroyRef);
|
|
1859
|
+
#i18n = inject(I18nService);
|
|
1667
1860
|
#hoveredPinKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredPinKey" }] : []));
|
|
1668
1861
|
#pinHoverTimerId;
|
|
1669
1862
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
@@ -1688,16 +1881,22 @@ class SdLayoutMenuTreeComponent {
|
|
|
1688
1881
|
continue;
|
|
1689
1882
|
const isGroup = 'children' in menu && !!menu.children?.length;
|
|
1690
1883
|
const path = 'path' in menu ? menu.path : '';
|
|
1884
|
+
const title = menu.title ?? menu.tooltipTitle ?? '';
|
|
1885
|
+
const isPinned = pinnedKeys.has(key);
|
|
1691
1886
|
nodes.push({
|
|
1692
1887
|
menu,
|
|
1693
1888
|
key,
|
|
1694
|
-
title
|
|
1889
|
+
title,
|
|
1695
1890
|
depth,
|
|
1696
1891
|
paddingLeft: 12 + depth * 16,
|
|
1697
1892
|
isGroup,
|
|
1698
1893
|
isActive: !!path && this.#pathMatches(this.activePath(), path),
|
|
1699
|
-
isPinned
|
|
1700
|
-
isPinVisible: alwaysShowPin ||
|
|
1894
|
+
isPinned,
|
|
1895
|
+
isPinVisible: alwaysShowPin || isPinned || hoveredPinKey === key,
|
|
1896
|
+
// why: template cũ nối `'Pin ' + node.title` — chuỗi tiếng Anh cứng và ép trật tự
|
|
1897
|
+
// "động từ trước tên" của tiếng Anh lên mọi ngôn ngữ. Nội suy `{title}` để catalog tự
|
|
1898
|
+
// quyết định vị trí. Tính trong `nodes` computed nên tự cập nhật khi đổi ngôn ngữ.
|
|
1899
|
+
pinLabel: this.#i18n.t(isPinned ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin', { title }),
|
|
1701
1900
|
});
|
|
1702
1901
|
if (isGroup && 'children' in menu)
|
|
1703
1902
|
append(menu.children ?? [], depth + 1, [...ancestors, menu.title ?? key]);
|
|
@@ -1742,11 +1941,11 @@ class SdLayoutMenuTreeComponent {
|
|
|
1742
1941
|
return currentPath === menuPath || normalize(currentPath).startsWith(normalize(menuPath));
|
|
1743
1942
|
}
|
|
1744
1943
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1745
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutMenuTreeComponent, isStandalone: true, selector: "sd-layout-menu-tree", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, activePath: { classPropertyName: "activePath", publicName: "activePath", isSignal: true, isRequired: false, transformFunction: null }, pinnedKeys: { classPropertyName: "pinnedKeys", publicName: "pinnedKeys", isSignal: true, isRequired: false, transformFunction: null }, showPin: { classPropertyName: "showPin", publicName: "showPin", isSignal: true, isRequired: false, transformFunction: null }, pinVisibility: { classPropertyName: "pinVisibility", publicName: "pinVisibility", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate", togglePinned: "togglePinned" }, ngImport: i0, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.
|
|
1944
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutMenuTreeComponent, isStandalone: true, selector: "sd-layout-menu-tree", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, activePath: { classPropertyName: "activePath", publicName: "activePath", isSignal: true, isRequired: false, transformFunction: null }, pinnedKeys: { classPropertyName: "pinnedKeys", publicName: "pinnedKeys", isSignal: true, isRequired: false, transformFunction: null }, showPin: { classPropertyName: "showPin", publicName: "showPin", isSignal: true, isRequired: false, transformFunction: null }, pinVisibility: { classPropertyName: "pinVisibility", publicName: "pinVisibility", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate", togglePinned: "togglePinned" }, ngImport: i0, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.pinLabel\"\n [attr.aria-pressed]=\"node.isPinned\"\n (click)=\"onTogglePinned($event, node.menu)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n }\n } @empty {\n <div class=\"d-flex flex-column align-items-center justify-content-center gap-12 p-24 text-black400 T14R\">\n Kh\u00F4ng t\u00ECm th\u1EA5y menu ph\u00F9 h\u1EE3p\n </div>\n }\n</nav>\n", styles: [".sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{min-height:40px;border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-menu-tree__route-button--active{color:var(--sd-primary);background:var(--sd-primary-light);font-weight:500}.sd-layout-menu-tree__route-button:hover,.sd-layout-menu-tree__pin:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3));color:var(--sd-primary, #005cbb)}.sd-layout-menu-tree__route-button--active:hover{background:var(--sd-primary-light, #d7e3ff)}.sd-layout-menu-tree__route-button:focus-visible,.sd-layout-menu-tree__pin:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-menu-tree__pin{width:36px;flex:0 0 36px;opacity:0;pointer-events:none;transition:opacity .12s ease,background-color .12s ease,color .12s ease}.sd-layout-menu-tree__pin--visible,.sd-layout-menu-tree__pin:focus-visible{opacity:1;pointer-events:auto}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1746
1945
|
}
|
|
1747
1946
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, decorators: [{
|
|
1748
1947
|
type: Component,
|
|
1749
|
-
args: [{ selector: 'sd-layout-menu-tree', standalone: true, imports: [SdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.
|
|
1948
|
+
args: [{ selector: 'sd-layout-menu-tree', standalone: true, imports: [SdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"d-flex flex-column gap-4\" aria-label=\"Navigation menu\">\n @for (node of nodes(); track node.key) {\n @if (node.isGroup) {\n <div class=\"sd-layout-menu-tree__group d-flex align-items-center gap-8 T12M text-black400\" [style.padding-left.px]=\"node.paddingLeft\">\n <sd-icon [name]=\"node.menu.icon || 'folder'\"></sd-icon>\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </div>\n } @else {\n <div\n class=\"sd-layout-menu-tree__route d-flex align-items-center gap-4\"\n (mouseenter)=\"onPinHoverStart(node.key)\"\n (mouseleave)=\"onPinHoverEnd(node.key)\">\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__route-button d-flex align-items-center gap-8 flex-1 text-left T14R\"\n data-menu-route\n [attr.data-menu-key]=\"node.key\"\n [attr.aria-current]=\"node.isActive ? 'page' : null\"\n [class.sd-layout-menu-tree__route-button--active]=\"node.isActive\"\n [style.padding-left.px]=\"node.paddingLeft\"\n (click)=\"onNavigate(node.menu)\">\n @if (node.menu.iconUrl) {\n <img class=\"sd-layout-menu-tree__icon-image\" [src]=\"node.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"node.menu.icon || 'chevron_right'\"></sd-icon>\n }\n <span class=\"text-ellipsis\">{{ node.title }}</span>\n </button>\n @if (showPin()) {\n <button\n type=\"button\"\n class=\"sd-layout-menu-tree__pin d-flex align-items-center justify-content-center\"\n [class.sd-layout-menu-tree__pin--visible]=\"node.isPinVisible\"\n [attr.data-pin-key]=\"node.key\"\n [attr.aria-label]=\"node.pinLabel\"\n [attr.aria-pressed]=\"node.isPinned\"\n (click)=\"onTogglePinned($event, node.menu)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n }\n } @empty {\n <div class=\"d-flex flex-column align-items-center justify-content-center gap-12 p-24 text-black400 T14R\">\n Kh\u00F4ng t\u00ECm th\u1EA5y menu ph\u00F9 h\u1EE3p\n </div>\n }\n</nav>\n", styles: [".sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{min-height:40px;border:0;background:transparent;color:var(--sd-black500, var(--sd-text, #1a1b1f));cursor:pointer;border-radius:8px;transition:background-color .12s ease,color .12s ease}.sd-layout-menu-tree__route-button--active{color:var(--sd-primary);background:var(--sd-primary-light);font-weight:500}.sd-layout-menu-tree__route-button:hover,.sd-layout-menu-tree__pin:hover{background:var(--sd-black100, var(--sd-surface-muted, #f3f3f3));color:var(--sd-primary, #005cbb)}.sd-layout-menu-tree__route-button--active:hover{background:var(--sd-primary-light, #d7e3ff)}.sd-layout-menu-tree__route-button:focus-visible,.sd-layout-menu-tree__pin:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-layout-menu-tree__pin{width:36px;flex:0 0 36px;opacity:0;pointer-events:none;transition:opacity .12s ease,background-color .12s ease,color .12s ease}.sd-layout-menu-tree__pin--visible,.sd-layout-menu-tree__pin:focus-visible{opacity:1;pointer-events:auto}.sd-layout-menu-tree__group{min-height:32px}.sd-layout-menu-tree__icon-image{width:20px;height:20px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-layout-menu-tree__route-button,.sd-layout-menu-tree__pin{transition:none}}\n"] }]
|
|
1750
1949
|
}], ctorParameters: () => [], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], activePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "activePath", required: false }] }], pinnedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedKeys", required: false }] }], showPin: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPin", required: false }] }], pinVisibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinVisibility", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }], togglePinned: [{ type: i0.Output, args: ["togglePinned"] }] } });
|
|
1751
1950
|
|
|
1752
1951
|
class SdLayoutSearchFieldComponent {
|
|
@@ -1762,9 +1961,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1762
1961
|
args: [{ selector: 'sd-layout-search-field', standalone: true, imports: [SdIcon, SdInput], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n data-layout-search\n class=\"sd-layout-search-field sd-layout-search-field--soft-pill d-flex align-items-center gap-8\">\n <sd-icon class=\"sd-layout-search-field__icon\" name=\"search\"></sd-icon>\n <sd-input\n class=\"sd-layout-search-field__control\"\n size=\"sm\"\n hideInlineError\n [placeholder]=\"placeholder()\"\n [autoId]=\"autoId()\"\n [model]=\"model()\"\n (sdChange)=\"sdChange.emit($event)\">\n </sd-input>\n</div>\n", styles: [":host{display:block;min-width:0}.sd-layout-search-field{min-height:38px;padding:0 12px;border:0;border-radius:9999px;outline:1px solid transparent;outline-offset:1px;background:var(--sd-black100, var(--sd-surface-muted));color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field:focus-within{outline-color:var(--sd-primary)}.sd-layout-search-field__icon{flex:0 0 auto;color:var(--sd-black400, var(--sd-text-muted))}.sd-layout-search-field__control{flex:1;min-width:0;--sd-border: transparent;--mdc-outlined-text-field-outline-color: transparent;--mdc-outlined-text-field-hover-outline-color: transparent;--mdc-outlined-text-field-focus-outline-color: transparent;--mdc-outlined-text-field-input-text-placeholder-color: var(--sd-black400, var(--sd-text-muted));--mdc-outlined-text-field-container-shape: 0px}:host ::ng-deep .sd-layout-search-field__control input:focus-visible{outline:0}\n"] }]
|
|
1763
1962
|
}], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: true }] }], autoId: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoId", required: true }] }], sdChange: [{ type: i0.Output, args: ["sdChange"] }] } });
|
|
1764
1963
|
|
|
1765
|
-
class
|
|
1964
|
+
class SdSidebarV2 {
|
|
1766
1965
|
#router = inject(Router);
|
|
1767
|
-
#document = inject(DOCUMENT);
|
|
1768
1966
|
#destroyRef = inject(DestroyRef);
|
|
1769
1967
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
1770
1968
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
@@ -1846,8 +2044,10 @@ class SidebarV2Component {
|
|
|
1846
2044
|
if (!('path' in menu))
|
|
1847
2045
|
return;
|
|
1848
2046
|
this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
|
|
1849
|
-
|
|
1850
|
-
|
|
2047
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2048
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2049
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2050
|
+
sdOpenExternal(menu.path);
|
|
1851
2051
|
this.closeFromNavigation();
|
|
1852
2052
|
return;
|
|
1853
2053
|
}
|
|
@@ -1874,16 +2074,19 @@ class SidebarV2Component {
|
|
|
1874
2074
|
if (persist)
|
|
1875
2075
|
this.#navigationState.patchVersionState(2, { locked: false });
|
|
1876
2076
|
}
|
|
1877
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1878
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type:
|
|
2077
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV2, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2078
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarV2, isStandalone: true, selector: "sd-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 } }, host: { listeners: { "keydown.escape": "closeFromEscape()" } }, ngImport: i0, template: "<!-- why: `(keydown.escape)` \u0111\u01B0\u1EE3c gom l\u00EAn HOST c\u1EE7a component (xem `host` trong main.component.ts).\n Tr\u01B0\u1EDBc \u0111\u00E2y n\u00F3 n\u1EB1m tr\u00EAn div b\u1ECDc v\u00E0 tr\u00EAn <section> flyout \u2014 c\u1EA3 hai \u0111\u1EC1u KH\u00D4NG focusable n\u00EAn ch\u1EC9\n \"ch\u1EA1y nh\u1EDD\" event n\u1ED5i b\u1ECDt t\u1EEB con, \u0111\u1ED3ng th\u1EDDi khi\u1EBFn markup khai b\u00E1o interaction handler tr\u00EAn\n ph\u1EA7n t\u1EED kh\u00F4ng nh\u1EADn \u0111\u01B0\u1EE3c focus. Host l\u00E0 t\u1ED5 ti\u00EAn c\u1EE7a t\u1EA5t c\u1EA3 n\u00EAn h\u00E0nh vi gi\u1EEF nguy\u00EAn. -->\n<div class=\"sd-sidebar-v2\">\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\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\">\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\" [attr.aria-label]=\"'core.module.layout.sidebar.menu-groups' | sdTranslate\">\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 [attr.aria-label]=\"'core.module.layout.sidebar.close-menu' | sdTranslate\"\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 role=\"region\"\n [attr.aria-label]=\"activeGroup()?.title || 'Menu'\"\n (mouseleave)=\"leaveFlyout()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ activeGroup()?.title || sidebar().defaultTitle || 'Menu' }}</span>\n @if (isFlyoutLocked()) {\n <sd-icon name=\"lock\"></sd-icon>\n }\n </header>\n <div class=\"p-12\">\n <sd-layout-search-field\n [placeholder]=\"'core.module.layout.sidebar.search-in-group' | sdTranslate\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"flex-1 overflow-auto px-8 pb-12\">\n <sd-layout-menu-tree\n [menus]=\"contextMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n\n <main class=\"sd-sidebar-v2__content\" data-v2-content data-content-offset=\"rail\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v2{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v2__rail{position:fixed;z-index:30;inset:0 auto 0 0;width:72px}.sd-sidebar-v2__brand,.sd-sidebar-v2__rail-action{width:56px;min-height:52px;border:0;border-radius:10px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v2__rail-action:hover,.sd-sidebar-v2__rail-action--active{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v2__brand:focus-visible,.sd-sidebar-v2__rail-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v2__rail-label{width:52px;font-size:10px;line-height:12px;text-align:center}.sd-sidebar-v2__logo,.sd-sidebar-v2__rail-icon{width:24px;height:24px;object-fit:contain}.sd-sidebar-v2__backdrop{position:fixed;z-index:10;inset:0;border:0;background:transparent}.sd-sidebar-v2__flyout{position:fixed;z-index:20;inset:0 auto 0 72px;width:320px}.sd-sidebar-v2__content{min-height:100vh;margin-left:72px}@media(prefers-reduced-motion:reduce){.sd-sidebar-v2__flyout{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1879
2079
|
}
|
|
1880
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2080
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV2, decorators: [{
|
|
1881
2081
|
type: Component,
|
|
1882
|
-
args: [{ selector: 'sidebar-v2', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2082
|
+
args: [{ selector: 'sd-sidebar-v2', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent, SdTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
2083
|
+
'(keydown.escape)': 'closeFromEscape()',
|
|
2084
|
+
}, template: "<!-- why: `(keydown.escape)` \u0111\u01B0\u1EE3c gom l\u00EAn HOST c\u1EE7a component (xem `host` trong main.component.ts).\n Tr\u01B0\u1EDBc \u0111\u00E2y n\u00F3 n\u1EB1m tr\u00EAn div b\u1ECDc v\u00E0 tr\u00EAn <section> flyout \u2014 c\u1EA3 hai \u0111\u1EC1u KH\u00D4NG focusable n\u00EAn ch\u1EC9\n \"ch\u1EA1y nh\u1EDD\" event n\u1ED5i b\u1ECDt t\u1EEB con, \u0111\u1ED3ng th\u1EDDi khi\u1EBFn markup khai b\u00E1o interaction handler tr\u00EAn\n ph\u1EA7n t\u1EED kh\u00F4ng nh\u1EADn \u0111\u01B0\u1EE3c focus. Host l\u00E0 t\u1ED5 ti\u00EAn c\u1EE7a t\u1EA5t c\u1EA3 n\u00EAn h\u00E0nh vi gi\u1EEF nguy\u00EAn. -->\n<div class=\"sd-sidebar-v2\">\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\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\">\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\" [attr.aria-label]=\"'core.module.layout.sidebar.menu-groups' | sdTranslate\">\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 [attr.aria-label]=\"'core.module.layout.sidebar.close-menu' | sdTranslate\"\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 role=\"region\"\n [attr.aria-label]=\"activeGroup()?.title || 'Menu'\"\n (mouseleave)=\"leaveFlyout()\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T16M text-ellipsis\">{{ activeGroup()?.title || sidebar().defaultTitle || 'Menu' }}</span>\n @if (isFlyoutLocked()) {\n <sd-icon name=\"lock\"></sd-icon>\n }\n </header>\n <div class=\"p-12\">\n <sd-layout-search-field\n [placeholder]=\"'core.module.layout.sidebar.search-in-group' | sdTranslate\"\n [autoId]=\"'layout-v2-context-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"flex-1 overflow-auto px-8 pb-12\">\n <sd-layout-menu-tree\n [menus]=\"contextMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n\n <main class=\"sd-sidebar-v2__content\" data-v2-content data-content-offset=\"rail\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v2{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v2__rail{position:fixed;z-index:30;inset:0 auto 0 0;width:72px}.sd-sidebar-v2__brand,.sd-sidebar-v2__rail-action{width:56px;min-height:52px;border:0;border-radius:10px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v2__rail-action:hover,.sd-sidebar-v2__rail-action--active{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v2__brand:focus-visible,.sd-sidebar-v2__rail-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v2__rail-label{width:52px;font-size:10px;line-height:12px;text-align:center}.sd-sidebar-v2__logo,.sd-sidebar-v2__rail-icon{width:24px;height:24px;object-fit:contain}.sd-sidebar-v2__backdrop{position:fixed;z-index:10;inset:0;border:0;background:transparent}.sd-sidebar-v2__flyout{position:fixed;z-index:20;inset:0 auto 0 72px;width:320px}.sd-sidebar-v2__content{min-height:100vh;margin-left:72px}@media(prefers-reduced-motion:reduce){.sd-sidebar-v2__flyout{transition:none}}\n"] }]
|
|
1883
2085
|
}], 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 }] }] } });
|
|
1884
2086
|
|
|
1885
|
-
class
|
|
2087
|
+
class SdSidebarMobileV2 {
|
|
1886
2088
|
#router = inject(Router);
|
|
2089
|
+
#i18n = inject(I18nService);
|
|
1887
2090
|
#document = inject(DOCUMENT);
|
|
1888
2091
|
#destroyRef = inject(DestroyRef);
|
|
1889
2092
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
@@ -1918,7 +2121,7 @@ class SidebarMobileV2Component {
|
|
|
1918
2121
|
this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
|
|
1919
2122
|
}
|
|
1920
2123
|
openMore(trigger) {
|
|
1921
|
-
this.#openSheet('more', '
|
|
2124
|
+
this.#openSheet('more', this.#i18n.t('core.module.layout.sidebar.more'), this.overflowMenus(), trigger);
|
|
1922
2125
|
}
|
|
1923
2126
|
activateMenu(menu, trigger) {
|
|
1924
2127
|
if ('path' in menu) {
|
|
@@ -1965,8 +2168,10 @@ class SidebarMobileV2Component {
|
|
|
1965
2168
|
}
|
|
1966
2169
|
#navigate(menu) {
|
|
1967
2170
|
this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
|
|
1968
|
-
|
|
1969
|
-
|
|
2171
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2172
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2173
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2174
|
+
sdOpenExternal(menu.path);
|
|
1970
2175
|
this.closeSheet();
|
|
1971
2176
|
return;
|
|
1972
2177
|
}
|
|
@@ -1980,17 +2185,16 @@ class SidebarMobileV2Component {
|
|
|
1980
2185
|
#releaseBodyScroll() {
|
|
1981
2186
|
this.#document.body.style.overflow = this.#previousBodyOverflow;
|
|
1982
2187
|
}
|
|
1983
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1984
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type:
|
|
2188
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV2, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2189
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarMobileV2, isStandalone: true, selector: "sd-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\" [attr.aria-label]=\"'core.module.layout.sidebar.primary-nav' | sdTranslate\">\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 [attr.aria-label]=\"'core.module.layout.sidebar.more-menu' | sdTranslate\"\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\">{{ 'core.module.layout.sidebar.more' | sdTranslate }}</span>\n </button>\n }\n </nav>\n\n @if (isSheetOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v2__backdrop\" [attr.aria-label]=\"'core.module.layout.sidebar.close-menu' | sdTranslate\" (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 [attr.aria-label]=\"'core.common.close' | sdTranslate\"\n (click)=\"closeSheet()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"d-flex flex-column gap-12 p-12 overflow-auto\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-layout-search-field\n [placeholder]=\"'core.module.layout.sidebar.search-in-menu' | sdTranslate\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v2{min-height:100dvh;padding-bottom:calc(64px + env(safe-area-inset-bottom));background:var(--sd-light)}.sd-sidebar-mobile-v2__bar{position:fixed;z-index:30;inset:auto 0 0;min-height:64px;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v2__bar-action,.sd-sidebar-mobile-v2__close{min-width:0;min-height:48px;border:0;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-mobile-v2__bar-action:hover,.sd-sidebar-mobile-v2__close:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v2__bar-action:focus-visible,.sd-sidebar-mobile-v2__close:focus-visible{outline:2px solid var(--sd-primary);outline-offset:-2px}.sd-sidebar-mobile-v2__label{max-width:72px;font-size:11px;line-height:14px}.sd-sidebar-mobile-v2__icon{width:22px;height:22px;object-fit:contain}.sd-sidebar-mobile-v2__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v2__sheet{position:fixed;z-index:50;inset:auto 0 0;max-height:min(78dvh,720px);padding-bottom:env(safe-area-inset-bottom);overflow:hidden}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v2__sheet{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1985
2190
|
}
|
|
1986
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2191
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV2, decorators: [{
|
|
1987
2192
|
type: Component,
|
|
1988
|
-
args: [{ selector: 'sidebar-mobile-v2', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-mobile-v2 d-flex flex-column\">\n <main class=\"sd-sidebar-mobile-v2__content flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n <nav class=\"sd-sidebar-mobile-v2__bar d-flex align-items-stretch bg-white border-top border-black200\" aria-label=\"
|
|
2193
|
+
args: [{ selector: 'sd-sidebar-mobile-v2', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent, SdTranslatePipe], 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\" [attr.aria-label]=\"'core.module.layout.sidebar.primary-nav' | sdTranslate\">\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 [attr.aria-label]=\"'core.module.layout.sidebar.more-menu' | sdTranslate\"\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\">{{ 'core.module.layout.sidebar.more' | sdTranslate }}</span>\n </button>\n }\n </nav>\n\n @if (isSheetOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v2__backdrop\" [attr.aria-label]=\"'core.module.layout.sidebar.close-menu' | sdTranslate\" (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 [attr.aria-label]=\"'core.common.close' | sdTranslate\"\n (click)=\"closeSheet()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"d-flex flex-column gap-12 p-12 overflow-auto\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <sd-layout-search-field\n [placeholder]=\"'core.module.layout.sidebar.search-in-menu' | sdTranslate\"\n [autoId]=\"'layout-v2-mobile-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n <sd-layout-menu-tree\n [menus]=\"sheetMenus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"onNavigate($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v2{min-height:100dvh;padding-bottom:calc(64px + env(safe-area-inset-bottom));background:var(--sd-light)}.sd-sidebar-mobile-v2__bar{position:fixed;z-index:30;inset:auto 0 0;min-height:64px;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v2__bar-action,.sd-sidebar-mobile-v2__close{min-width:0;min-height:48px;border:0;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-mobile-v2__bar-action:hover,.sd-sidebar-mobile-v2__close:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v2__bar-action:focus-visible,.sd-sidebar-mobile-v2__close:focus-visible{outline:2px solid var(--sd-primary);outline-offset:-2px}.sd-sidebar-mobile-v2__label{max-width:72px;font-size:11px;line-height:14px}.sd-sidebar-mobile-v2__icon{width:22px;height:22px;object-fit:contain}.sd-sidebar-mobile-v2__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v2__sheet{position:fixed;z-index:50;inset:auto 0 0;max-height:min(78dvh,720px);padding-bottom:env(safe-area-inset-bottom);overflow:hidden}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v2__sheet{transition:none}}\n"] }]
|
|
1989
2194
|
}], 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 }] }] } });
|
|
1990
2195
|
|
|
1991
|
-
class
|
|
2196
|
+
class SdSidebarV3 {
|
|
1992
2197
|
#router = inject(Router);
|
|
1993
|
-
#document = inject(DOCUMENT);
|
|
1994
2198
|
#destroyRef = inject(DestroyRef);
|
|
1995
2199
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
1996
2200
|
#initialized = false;
|
|
@@ -2041,8 +2245,10 @@ class SidebarV3Component {
|
|
|
2041
2245
|
if (!('path' in menu))
|
|
2042
2246
|
return;
|
|
2043
2247
|
this.#navigationState.recordRecent(menu, this.recentConfiguration());
|
|
2044
|
-
|
|
2045
|
-
|
|
2248
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2249
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2250
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2251
|
+
sdOpenExternal(menu.path);
|
|
2046
2252
|
return;
|
|
2047
2253
|
}
|
|
2048
2254
|
void this.#router.navigate([menu.path.split('?')[0]], {
|
|
@@ -2053,15 +2259,15 @@ class SidebarV3Component {
|
|
|
2053
2259
|
onTogglePinned(menu) {
|
|
2054
2260
|
this.#navigationState.togglePinned(menu);
|
|
2055
2261
|
}
|
|
2056
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2057
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type:
|
|
2262
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV3, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2263
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarV3, isStandalone: true, selector: "sd-sidebar-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header\n data-v3-header\n class=\"sd-sidebar-v3__header d-flex align-items-center gap-8 p-12 border-bottom border-black200\"\n [class.sd-sidebar-v3__header--collapsed]=\"isCollapsed()\"\n [class.justify-content-between]=\"!isCollapsed()\"\n [class.justify-content-center]=\"isCollapsed()\">\n @if (!isCollapsed()) {\n <div data-v3-brand class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n }\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__icon-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"isCollapsed() ? 'M\u1EDF r\u1ED9ng sidebar' : 'Thu g\u1ECDn sidebar'\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapsed()\">\n <sd-icon [name]=\"isCollapsed() ? 'chevron_right' : 'chevron_left'\"></sd-icon>\n </button>\n </header>\n\n @if (isCollapsed()) {\n <nav class=\"d-flex flex-column align-items-center gap-4 p-8 flex-1\" aria-label=\"Menu thu g\u1ECDn\">\n @for (item of collapsedItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__collapsed-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"item.title\"\n (click)=\"activateCollapsedMenu(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </nav>\n } @else {\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"sd-sidebar-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto px-8 pb-12\">\n @if (pinnedMenus().length) {\n <section data-v3-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.pinned' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.recent' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.all-menu' | sdTranslate }}</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n }\n\n <div class=\"p-8 border-top border-black200\">\n <sd-layout-user-menu [userInfo]=\"userInfo()\" [compact]=\"isCollapsed()\"></sd-layout-user-menu>\n </div>\n </aside>\n\n <main class=\"sd-sidebar-v3__content\">\n <ng-content></ng-content>\n </main>\n</div>\n", styles: [".sd-sidebar-v3{min-height:100vh;background:var(--sd-light)}.sd-sidebar-v3__drawer{position:fixed;z-index:20;inset:0 auto 0 0;width:304px;transition:width .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__drawer{width:72px}.sd-sidebar-v3__content{min-height:100vh;margin-left:304px;transition:margin-left .16s ease}.sd-sidebar-v3--collapsed .sd-sidebar-v3__content{margin-left:72px}.sd-sidebar-v3__icon-action,.sd-sidebar-v3__collapsed-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black400);cursor:pointer}.sd-sidebar-v3__icon-action:hover,.sd-sidebar-v3__collapsed-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-v3__icon-action:focus-visible,.sd-sidebar-v3__collapsed-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-v3__logo{width:28px;height:28px;object-fit:contain}@media(prefers-reduced-motion:reduce){.sd-sidebar-v3__drawer,.sd-sidebar-v3__content{transition:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2058
2264
|
}
|
|
2059
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2265
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV3, decorators: [{
|
|
2060
2266
|
type: Component,
|
|
2061
|
-
args: [{ selector: 'sidebar-v3', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header\n data-v3-header\n class=\"sd-sidebar-v3__header d-flex align-items-center gap-8 p-12 border-bottom border-black200\"\n [class.sd-sidebar-v3__header--collapsed]=\"isCollapsed()\"\n [class.justify-content-between]=\"!isCollapsed()\"\n [class.justify-content-center]=\"isCollapsed()\">\n @if (!isCollapsed()) {\n <div data-v3-brand class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n }\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__icon-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"isCollapsed() ? 'M\u1EDF r\u1ED9ng sidebar' : 'Thu g\u1ECDn sidebar'\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapsed()\">\n <sd-icon [name]=\"isCollapsed() ? 'chevron_right' : 'chevron_left'\"></sd-icon>\n </button>\n </header>\n\n @if (isCollapsed()) {\n <nav class=\"d-flex flex-column align-items-center gap-4 p-8 flex-1\" aria-label=\"Menu thu g\u1ECDn\">\n @for (item of collapsedItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__collapsed-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"item.title\"\n (click)=\"activateCollapsedMenu(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </nav>\n } @else {\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"sd-sidebar-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto px-8 pb-12\">\n @if (pinnedMenus().length) {\n <section data-v3-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\"
|
|
2267
|
+
args: [{ selector: 'sd-sidebar-v3', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent, SdTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-v3\" [class.sd-sidebar-v3--collapsed]=\"isCollapsed()\">\n <aside\n class=\"sd-sidebar-v3__drawer d-flex flex-column bg-white border-right border-black200\"\n data-v3-sidebar\n [attr.data-collapsed]=\"isCollapsed()\">\n <header\n data-v3-header\n class=\"sd-sidebar-v3__header d-flex align-items-center gap-8 p-12 border-bottom border-black200\"\n [class.sd-sidebar-v3__header--collapsed]=\"isCollapsed()\"\n [class.justify-content-between]=\"!isCollapsed()\"\n [class.justify-content-center]=\"isCollapsed()\">\n @if (!isCollapsed()) {\n <div data-v3-brand class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n }\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__icon-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"isCollapsed() ? 'M\u1EDF r\u1ED9ng sidebar' : 'Thu g\u1ECDn sidebar'\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapsed()\">\n <sd-icon [name]=\"isCollapsed() ? 'chevron_right' : 'chevron_left'\"></sd-icon>\n </button>\n </header>\n\n @if (isCollapsed()) {\n <nav class=\"d-flex flex-column align-items-center gap-4 p-8 flex-1\" aria-label=\"Menu thu g\u1ECDn\">\n @for (item of collapsedItems(); track item.key) {\n <button\n type=\"button\"\n class=\"sd-sidebar-v3__collapsed-action d-flex align-items-center justify-content-center\"\n [attr.aria-label]=\"item.title\"\n (click)=\"activateCollapsedMenu(item.menu)\">\n @if (item.menu.iconUrl) {\n <img class=\"sd-sidebar-v3__logo\" [src]=\"item.menu.iconUrl\" alt=\"\" />\n } @else {\n <sd-icon [name]=\"item.menu.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </nav>\n } @else {\n <div class=\"p-12\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n <div class=\"sd-sidebar-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto px-8 pb-12\">\n @if (pinnedMenus().length) {\n <section data-v3-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.pinned' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.recent' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.all-menu' | sdTranslate }}</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"] }]
|
|
2062
2268
|
}], 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 }] }] } });
|
|
2063
2269
|
|
|
2064
|
-
class
|
|
2270
|
+
class SdSidebarMobileV3 {
|
|
2065
2271
|
#router = inject(Router);
|
|
2066
2272
|
#document = inject(DOCUMENT);
|
|
2067
2273
|
#destroyRef = inject(DestroyRef);
|
|
@@ -2126,8 +2332,10 @@ class SidebarMobileV3Component {
|
|
|
2126
2332
|
if (!('path' in menu))
|
|
2127
2333
|
return;
|
|
2128
2334
|
this.#navigationState.recordRecent(menu, this.recentConfiguration());
|
|
2129
|
-
|
|
2130
|
-
|
|
2335
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2336
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2337
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2338
|
+
sdOpenExternal(menu.path);
|
|
2131
2339
|
this.closeFromNavigation();
|
|
2132
2340
|
return;
|
|
2133
2341
|
}
|
|
@@ -2153,12 +2361,12 @@ class SidebarMobileV3Component {
|
|
|
2153
2361
|
#releaseBodyScroll() {
|
|
2154
2362
|
this.#document.body.style.overflow = this.#previousBodyOverflow;
|
|
2155
2363
|
}
|
|
2156
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2157
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type:
|
|
2364
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV3, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2365
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarMobileV3, isStandalone: true, selector: "sd-sidebar-mobile-v3", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-mobile-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n @if (pinnedMenus().length) {\n <section data-v3-mobile-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.pinned' | sdTranslate }}</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-mobile-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.recent' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.all-menu' | sdTranslate }}</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v3{min-height:100dvh;background:var(--sd-light)}.sd-sidebar-mobile-v3__topbar{min-height:56px}.sd-sidebar-mobile-v3__icon-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black500);cursor:pointer}.sd-sidebar-mobile-v3__icon-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v3__icon-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-mobile-v3__logo{width:28px;height:28px;object-fit:contain}.sd-sidebar-mobile-v3__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v3__drawer{position:fixed;z-index:50;inset:0 auto 0 0;width:min(88vw,360px);max-width:100%;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v3__search{position:sticky;z-index:2;top:-12px;margin:-12px;padding:12px;background:var(--sd-white, #fff)}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdLayoutSearchFieldComponent, selector: "sd-layout-search-field", inputs: ["model", "placeholder", "autoId"], outputs: ["sdChange"] }, { kind: "component", type: SdLayoutMenuTreeComponent, selector: "sd-layout-menu-tree", inputs: ["menus", "query", "activePath", "pinnedKeys", "showPin", "pinVisibility"], outputs: ["navigate", "togglePinned"] }, { kind: "component", type: SdLayoutUserMenuComponent, selector: "sd-layout-user-menu", inputs: ["userInfo", "signout", "changePassword", "updateProfile", "setting", "notification", "compact", "presentation"], outputs: ["opened", "closed"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2158
2366
|
}
|
|
2159
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2367
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV3, decorators: [{
|
|
2160
2368
|
type: Component,
|
|
2161
|
-
args: [{ selector: 'sidebar-mobile-v3', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-mobile-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n @if (pinnedMenus().length) {\n <section data-v3-mobile-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\"
|
|
2369
|
+
args: [{ selector: 'sd-sidebar-mobile-v3', standalone: true, imports: [A11yModule, SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent, SdTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-sidebar-mobile-v3 d-flex flex-column\">\n <header\n class=\"sd-sidebar-mobile-v3__topbar d-flex align-items-center justify-content-between gap-8 px-16 bg-white border-bottom border-black200\">\n <div class=\"d-flex align-items-center gap-8 overflow-hidden\">\n @if (sidebar().logoUrl) {\n <img class=\"sd-sidebar-mobile-v3__logo\" [src]=\"sidebar().logoUrl\" alt=\"\" />\n }\n <span class=\"T16M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n </div>\n <button\n #trigger\n type=\"button\"\n data-v3-mobile-trigger\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"M\u1EDF menu\"\n [attr.aria-expanded]=\"isDrawerOpen()\"\n (click)=\"openDrawer($event.currentTarget)\">\n <sd-icon name=\"menu\"></sd-icon>\n </button>\n </header>\n\n <main class=\"flex-1 overflow-auto\">\n <ng-content></ng-content>\n </main>\n\n @if (isDrawerOpen()) {\n <button type=\"button\" class=\"sd-sidebar-mobile-v3__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeFromBackdrop()\"></button>\n <aside\n class=\"sd-sidebar-mobile-v3__drawer d-flex flex-column bg-white mat-elevation-z8\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menu ch\u00EDnh\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onDrawerKeydown($event)\">\n <header class=\"d-flex align-items-center justify-content-between gap-8 px-16 py-12 border-bottom border-black200\">\n <span class=\"T18M text-ellipsis\">{{ sidebar().defaultTitle || 'Back Office' }}</span>\n <button\n type=\"button\"\n class=\"sd-sidebar-mobile-v3__icon-action d-flex align-items-center justify-content-center\"\n aria-label=\"\u0110\u00F3ng menu\"\n (click)=\"closeFromBackdrop()\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n </header>\n <div class=\"sd-sidebar-mobile-v3__navigation d-flex flex-column gap-16 flex-1 overflow-auto p-12\">\n <sd-layout-user-menu presentation=\"mobile-inline\" [userInfo]=\"userInfo()\"></sd-layout-user-menu>\n <div class=\"sd-sidebar-mobile-v3__search bg-white\">\n <sd-layout-search-field\n placeholder=\"T\u00ECm trong t\u1EA5t c\u1EA3 menu\"\n [autoId]=\"'layout-v3-mobile-global-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"searchText.set($event)\">\n </sd-layout-search-field>\n </div>\n @if (pinnedMenus().length) {\n <section data-v3-mobile-pinned class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.pinned' | sdTranslate }}</h2>\n <sd-layout-menu-tree\n [menus]=\"pinnedMenus()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n }\n @if (recentConfiguration().enabled && recentMenus().length) {\n <section data-v3-mobile-recent class=\"d-flex flex-column gap-4\">\n <h2 class=\"T12M text-black400 px-12 m-0\">{{ 'core.module.layout.sidebar.recent' | sdTranslate }}</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\">{{ 'core.module.layout.sidebar.all-menu' | sdTranslate }}</h2>\n <sd-layout-menu-tree\n [menus]=\"menus()\"\n [query]=\"searchText()\"\n [activePath]=\"activePath()\"\n [pinnedKeys]=\"pinnedKeys()\"\n pinVisibility=\"always\"\n (navigate)=\"navigateMenu($event)\"\n (togglePinned)=\"onTogglePinned($event)\">\n </sd-layout-menu-tree>\n </section>\n </div>\n </aside>\n }\n</div>\n", styles: [":host{display:block}.sd-sidebar-mobile-v3{min-height:100dvh;background:var(--sd-light)}.sd-sidebar-mobile-v3__topbar{min-height:56px}.sd-sidebar-mobile-v3__icon-action{width:40px;height:40px;border:0;border-radius:8px;background:transparent;color:var(--sd-black500);cursor:pointer}.sd-sidebar-mobile-v3__icon-action:hover{color:var(--sd-primary);background:var(--sd-primary-light)}.sd-sidebar-mobile-v3__icon-action:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-sidebar-mobile-v3__logo{width:28px;height:28px;object-fit:contain}.sd-sidebar-mobile-v3__backdrop{position:fixed;z-index:40;inset:0;border:0;background:color-mix(in srgb,var(--sd-black) 40%,transparent)}.sd-sidebar-mobile-v3__drawer{position:fixed;z-index:50;inset:0 auto 0 0;width:min(88vw,360px);max-width:100%;padding-bottom:env(safe-area-inset-bottom)}.sd-sidebar-mobile-v3__search{position:sticky;z-index:2;top:-12px;margin:-12px;padding:12px;background:var(--sd-white, #fff)}@media(prefers-reduced-motion:reduce){.sd-sidebar-mobile-v3__drawer{transition:none}}\n"] }]
|
|
2162
2370
|
}], 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 }] }] } });
|
|
2163
2371
|
|
|
2164
2372
|
class SdLayoutComponent {
|
|
@@ -2194,19 +2402,11 @@ class SdLayoutComponent {
|
|
|
2194
2402
|
mobileBreakpoint = normalizeLayoutMobileBreakpoint(this.#layoutConfiguration?.mobileBreakpoint);
|
|
2195
2403
|
isMobile = computed(() => this.#responsiveService.isMobile(this.mobileBreakpoint), ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
|
|
2196
2404
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2197
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdLayoutComponent, isStandalone: true, selector: "sd-layout", inputs: { menusInput: { classPropertyName: "menusInput", publicName: "menus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type:
|
|
2405
|
+
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 <sd-sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v1>\n } @else {\n <sd-sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sd-sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v3>\n } @else {\n <sd-sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sd-sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v2>\n } @else {\n <sd-sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: SdSidebarV1, selector: "sd-sidebar-v1", inputs: ["menus", "userInfo", "sidebar", "isMobile"] }, { kind: "component", type: SdSidebarMobileV1, selector: "sd-sidebar-mobile-v1", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SdSidebarV2, selector: "sd-sidebar-v2", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SdSidebarMobileV2, selector: "sd-sidebar-mobile-v2", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SdSidebarV3, selector: "sd-sidebar-v3", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "component", type: SdSidebarMobileV3, selector: "sd-sidebar-mobile-v3", inputs: ["menus", "userInfo", "sidebar"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2198
2406
|
}
|
|
2199
2407
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, decorators: [{
|
|
2200
2408
|
type: Component,
|
|
2201
|
-
args: [{ selector: 'sd-layout', imports: [
|
|
2202
|
-
SidebarV1Component,
|
|
2203
|
-
SidebarMobileV1Component,
|
|
2204
|
-
SidebarV2Component,
|
|
2205
|
-
SidebarMobileV2Component,
|
|
2206
|
-
SidebarV3Component,
|
|
2207
|
-
SidebarMobileV3Component,
|
|
2208
|
-
NgTemplateOutlet,
|
|
2209
|
-
], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"] }]
|
|
2409
|
+
args: [{ selector: 'sd-layout', imports: [SdSidebarV1, SdSidebarMobileV1, SdSidebarV2, SdSidebarMobileV2, SdSidebarV3, SdSidebarMobileV3, NgTemplateOutlet], 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 <sd-sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v1>\n } @else {\n <sd-sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sd-sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v3>\n } @else {\n <sd-sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sd-sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-v2>\n } @else {\n <sd-sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sd-sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"] }]
|
|
2210
2410
|
}], propDecorators: { menusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }] } });
|
|
2211
2411
|
|
|
2212
2412
|
/**
|
|
@@ -2288,7 +2488,7 @@ let HomePageComponent = class HomePageComponent {
|
|
|
2288
2488
|
return `${formatted} (${this.#getTimezone()})`;
|
|
2289
2489
|
}
|
|
2290
2490
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2291
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: HomePageComponent, isStandalone: true, selector: "app-home-page", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">\n {{ 'core.module.layout.greeting.hello' |
|
|
2491
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: HomePageComponent, isStandalone: true, selector: "app-home-page", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">\n {{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}\n </div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"wrapper\">\n <div class=\"bento\">\n <!-- Hero -->\n <div class=\"card hero-card\">\n <div class=\"hero-ring ring-3\"></div>\n <div class=\"hero-ring ring-2\"></div>\n <div class=\"hero-ring ring-1\"></div>\n <div class=\"hero-core\">\n <sd-icon class=\"hero-icon\" name=\"space_dashboard\"></sd-icon>\n </div>\n <div class=\"hero-decoration\">\n <span class=\"dot d1\"></span>\n <span class=\"dot d2\"></span>\n <span class=\"dot d3\"></span>\n </div>\n </div>\n\n <!-- Features -->\n @for (feature of features; track feature.key; let i = $index) {\n <div\n class=\"card feature-card\"\n [class.feature-wide]=\"feature.area === 'audit' || feature.area === 'integ'\"\n [style.gridArea]=\"feature.area\"\n [style.animation-delay.ms]=\"(i + 1) * 80\"\n >\n <div class=\"feature-accent\"></div>\n <div class=\"feature-content\">\n <div class=\"feature-icon-wrap\">\n <sd-icon class=\"feature-icon\" [name]=\"feature.icon\"></sd-icon>\n </div>\n <span class=\"feature-label\">{{ feature.key | sdTranslate }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n</sd-page>\n", styles: [":host{display:contents}.wrapper{display:flex;align-items:center;justify-content:center;padding:32px 24px;height:100%;width:100%;background:radial-gradient(circle at 15% 25%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 14%,transparent),transparent 55%),radial-gradient(circle at 85% 75%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 10%,transparent),transparent 55%),linear-gradient(135deg,#fafbfc,#f4f6fa);overflow:auto}.bento{display:grid;width:100%;max-width:1080px;gap:16px;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(3,minmax(140px,1fr));grid-template-areas:\"hero hero data report\" \"hero hero users flow\" \"audit audit integ integ\"}.card{position:relative;background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:20px;overflow:hidden;opacity:0;transform:translateY(8px);animation:card-in .5s ease-out forwards;transition:transform .3s ease,box-shadow .3s ease}.card:hover{transform:translateY(-4px);box-shadow:0 16px 40px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 18%,transparent),0 4px 12px #0000000f}.hero-card{grid-area:hero;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle at 30% 30%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 18%,white),#fff 60%),#fff;border:1px solid color-mix(in srgb,var(--mat-sys-primary, #005cbb) 15%,transparent)}.hero-card .hero-ring{position:absolute;border-radius:50%;border:1px solid color-mix(in srgb,var(--mat-sys-primary, #005cbb) 25%,transparent);animation:hero-pulse 3s ease-in-out infinite;pointer-events:none}.hero-card .hero-ring.ring-1{inset:calc(50% - 70px);animation-delay:0s}.hero-card .hero-ring.ring-2{inset:calc(50% - 105px);animation-delay:.6s}.hero-card .hero-ring.ring-3{inset:calc(50% - 140px);animation-delay:1.2s}.hero-card .hero-core{position:relative;z-index:1;width:140px;height:140px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 22%,white),#fff);box-shadow:0 0 60px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 30%,transparent),0 10px 30px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 20%,transparent)}.hero-card .hero-icon{font-size:72px;width:72px;height:72px;color:var(--mat-sys-primary, #005cbb)}.hero-card .hero-decoration{position:absolute;inset:0;pointer-events:none}.hero-card .hero-decoration .dot{position:absolute;width:8px;height:8px;border-radius:50%;background:color-mix(in srgb,var(--mat-sys-primary, #005cbb) 50%,transparent);animation:dot-float 4s ease-in-out infinite}.hero-card .hero-decoration .d1{top:16%;left:12%;animation-delay:0s}.hero-card .hero-decoration .d2{bottom:20%;right:14%;animation-delay:1s}.hero-card .hero-decoration .d3{top:60%;left:80%;animation-delay:2s;width:5px;height:5px}.feature-card{display:flex;align-items:stretch;cursor:default}.feature-card .feature-accent{position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 60%,transparent),var(--mat-sys-primary, #005cbb),color-mix(in srgb,var(--mat-sys-primary, #005cbb) 60%,transparent))}.feature-card .feature-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 18px;gap:10px}.feature-card .feature-icon-wrap{width:56px;height:56px;border-radius:50%;background:color-mix(in srgb,var(--mat-sys-primary, #005cbb) 12%,white);display:flex;align-items:center;justify-content:center}.feature-card .feature-icon{font-size:28px;width:28px;height:28px;color:var(--mat-sys-primary, #005cbb)}.feature-card .feature-label{font-size:14px;font-weight:500;text-align:center;color:#000000c7;line-height:20px}.feature-card.feature-wide .feature-content{flex-direction:row;justify-content:flex-start;padding:18px 24px;gap:18px}.feature-card.feature-wide .feature-icon-wrap{width:64px;height:64px}.feature-card.feature-wide .feature-icon{font-size:32px;width:32px;height:32px}.feature-card.feature-wide .feature-label{text-align:left;font-size:16px}@keyframes card-in{to{opacity:1;transform:translateY(0)}}@keyframes hero-pulse{0%,to{transform:scale(1);opacity:.7}50%{transform:scale(1.06);opacity:.3}}@keyframes dot-float{0%,to{transform:translateY(0);opacity:.8}50%{transform:translateY(-8px);opacity:.4}}@media(max-width:1024px){.bento{grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(4,minmax(120px,1fr));grid-template-areas:\"hero hero data\" \"hero hero report\" \"users flow audit\" \"integ integ integ\"}.hero-card .hero-core{width:120px;height:120px}.hero-card .hero-icon{font-size:60px;width:60px;height:60px}}@media(max-width:640px){.wrapper{padding:16px 12px}.bento{grid-template-columns:1fr;grid-template-rows:auto;grid-template-areas:\"hero\" \"data\" \"report\" \"users\" \"flow\" \"audit\" \"integ\";gap:12px}.hero-card{min-height:220px}.hero-card .hero-core{width:100px;height:100px}.hero-card .hero-icon{font-size:52px;width:52px;height:52px}}@media(prefers-reduced-motion:reduce){.card{animation:none;opacity:1;transform:none}.hero-card .hero-ring{animation:none;opacity:.4}.hero-card .hero-decoration .dot{animation:none}}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdPageComponent, selector: "sd-page", inputs: ["title", "description", "noHeader"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
2292
2492
|
};
|
|
2293
2493
|
HomePageComponent = __decorate([
|
|
2294
2494
|
SdTabComponent({
|
|
@@ -2300,7 +2500,7 @@ HomePageComponent = __decorate([
|
|
|
2300
2500
|
], HomePageComponent);
|
|
2301
2501
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomePageComponent, decorators: [{
|
|
2302
2502
|
type: Component,
|
|
2303
|
-
args: [{ selector: 'app-home-page', imports: [SdIcon, SdPageComponent,
|
|
2503
|
+
args: [{ selector: 'app-home-page', imports: [SdIcon, SdPageComponent, SdTranslatePipe], standalone: true, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">\n {{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}\n </div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"wrapper\">\n <div class=\"bento\">\n <!-- Hero -->\n <div class=\"card hero-card\">\n <div class=\"hero-ring ring-3\"></div>\n <div class=\"hero-ring ring-2\"></div>\n <div class=\"hero-ring ring-1\"></div>\n <div class=\"hero-core\">\n <sd-icon class=\"hero-icon\" name=\"space_dashboard\"></sd-icon>\n </div>\n <div class=\"hero-decoration\">\n <span class=\"dot d1\"></span>\n <span class=\"dot d2\"></span>\n <span class=\"dot d3\"></span>\n </div>\n </div>\n\n <!-- Features -->\n @for (feature of features; track feature.key; let i = $index) {\n <div\n class=\"card feature-card\"\n [class.feature-wide]=\"feature.area === 'audit' || feature.area === 'integ'\"\n [style.gridArea]=\"feature.area\"\n [style.animation-delay.ms]=\"(i + 1) * 80\"\n >\n <div class=\"feature-accent\"></div>\n <div class=\"feature-content\">\n <div class=\"feature-icon-wrap\">\n <sd-icon class=\"feature-icon\" [name]=\"feature.icon\"></sd-icon>\n </div>\n <span class=\"feature-label\">{{ feature.key | sdTranslate }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n</sd-page>\n", styles: [":host{display:contents}.wrapper{display:flex;align-items:center;justify-content:center;padding:32px 24px;height:100%;width:100%;background:radial-gradient(circle at 15% 25%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 14%,transparent),transparent 55%),radial-gradient(circle at 85% 75%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 10%,transparent),transparent 55%),linear-gradient(135deg,#fafbfc,#f4f6fa);overflow:auto}.bento{display:grid;width:100%;max-width:1080px;gap:16px;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(3,minmax(140px,1fr));grid-template-areas:\"hero hero data report\" \"hero hero users flow\" \"audit audit integ integ\"}.card{position:relative;background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:20px;overflow:hidden;opacity:0;transform:translateY(8px);animation:card-in .5s ease-out forwards;transition:transform .3s ease,box-shadow .3s ease}.card:hover{transform:translateY(-4px);box-shadow:0 16px 40px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 18%,transparent),0 4px 12px #0000000f}.hero-card{grid-area:hero;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle at 30% 30%,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 18%,white),#fff 60%),#fff;border:1px solid color-mix(in srgb,var(--mat-sys-primary, #005cbb) 15%,transparent)}.hero-card .hero-ring{position:absolute;border-radius:50%;border:1px solid color-mix(in srgb,var(--mat-sys-primary, #005cbb) 25%,transparent);animation:hero-pulse 3s ease-in-out infinite;pointer-events:none}.hero-card .hero-ring.ring-1{inset:calc(50% - 70px);animation-delay:0s}.hero-card .hero-ring.ring-2{inset:calc(50% - 105px);animation-delay:.6s}.hero-card .hero-ring.ring-3{inset:calc(50% - 140px);animation-delay:1.2s}.hero-card .hero-core{position:relative;z-index:1;width:140px;height:140px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 22%,white),#fff);box-shadow:0 0 60px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 30%,transparent),0 10px 30px color-mix(in srgb,var(--mat-sys-primary, #005cbb) 20%,transparent)}.hero-card .hero-icon{font-size:72px;width:72px;height:72px;color:var(--mat-sys-primary, #005cbb)}.hero-card .hero-decoration{position:absolute;inset:0;pointer-events:none}.hero-card .hero-decoration .dot{position:absolute;width:8px;height:8px;border-radius:50%;background:color-mix(in srgb,var(--mat-sys-primary, #005cbb) 50%,transparent);animation:dot-float 4s ease-in-out infinite}.hero-card .hero-decoration .d1{top:16%;left:12%;animation-delay:0s}.hero-card .hero-decoration .d2{bottom:20%;right:14%;animation-delay:1s}.hero-card .hero-decoration .d3{top:60%;left:80%;animation-delay:2s;width:5px;height:5px}.feature-card{display:flex;align-items:stretch;cursor:default}.feature-card .feature-accent{position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,color-mix(in srgb,var(--mat-sys-primary, #005cbb) 60%,transparent),var(--mat-sys-primary, #005cbb),color-mix(in srgb,var(--mat-sys-primary, #005cbb) 60%,transparent))}.feature-card .feature-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 18px;gap:10px}.feature-card .feature-icon-wrap{width:56px;height:56px;border-radius:50%;background:color-mix(in srgb,var(--mat-sys-primary, #005cbb) 12%,white);display:flex;align-items:center;justify-content:center}.feature-card .feature-icon{font-size:28px;width:28px;height:28px;color:var(--mat-sys-primary, #005cbb)}.feature-card .feature-label{font-size:14px;font-weight:500;text-align:center;color:#000000c7;line-height:20px}.feature-card.feature-wide .feature-content{flex-direction:row;justify-content:flex-start;padding:18px 24px;gap:18px}.feature-card.feature-wide .feature-icon-wrap{width:64px;height:64px}.feature-card.feature-wide .feature-icon{font-size:32px;width:32px;height:32px}.feature-card.feature-wide .feature-label{text-align:left;font-size:16px}@keyframes card-in{to{opacity:1;transform:translateY(0)}}@keyframes hero-pulse{0%,to{transform:scale(1);opacity:.7}50%{transform:scale(1.06);opacity:.3}}@keyframes dot-float{0%,to{transform:translateY(0);opacity:.8}50%{transform:translateY(-8px);opacity:.4}}@media(max-width:1024px){.bento{grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(4,minmax(120px,1fr));grid-template-areas:\"hero hero data\" \"hero hero report\" \"users flow audit\" \"integ integ integ\"}.hero-card .hero-core{width:120px;height:120px}.hero-card .hero-icon{font-size:60px;width:60px;height:60px}}@media(max-width:640px){.wrapper{padding:16px 12px}.bento{grid-template-columns:1fr;grid-template-rows:auto;grid-template-areas:\"hero\" \"data\" \"report\" \"users\" \"flow\" \"audit\" \"integ\";gap:12px}.hero-card{min-height:220px}.hero-card .hero-core{width:100px;height:100px}.hero-card .hero-icon{font-size:52px;width:52px;height:52px}}@media(prefers-reduced-motion:reduce){.card{animation:none;opacity:1;transform:none}.hero-card .hero-ring{animation:none;opacity:.4}.hero-card .hero-decoration .dot{animation:none}}\n"] }]
|
|
2304
2504
|
}] });
|
|
2305
2505
|
|
|
2306
2506
|
class HomeRedirectGuard {
|
|
@@ -2370,6 +2570,7 @@ let RootComponent$1 = class RootComponent {
|
|
|
2370
2570
|
// INJECT SERVICES (Modern Angular)
|
|
2371
2571
|
// ==========================================
|
|
2372
2572
|
#route = inject(ActivatedRoute);
|
|
2573
|
+
#router = inject(Router);
|
|
2373
2574
|
#layoutService = inject(SdLayoutService);
|
|
2374
2575
|
#i18n = inject(I18nService);
|
|
2375
2576
|
// ==========================================
|
|
@@ -2395,11 +2596,16 @@ let RootComponent$1 = class RootComponent {
|
|
|
2395
2596
|
// ==========================================
|
|
2396
2597
|
// PUBLIC METHODS
|
|
2397
2598
|
// ==========================================
|
|
2599
|
+
/**
|
|
2600
|
+
* why: `window.location.href = ''` vừa là global thô (throw khi SSR vì không có `window`), vừa chỉ
|
|
2601
|
+
* reload lại đúng trang lỗi này thay vì rời khỏi nó — trong khi nút mang nhãn "Quay lại trang chủ".
|
|
2602
|
+
* Điều hướng bằng Router tới `homeUrl` mà consumer đã khai trong SD_LAYOUT_CONFIGURATION.
|
|
2603
|
+
*/
|
|
2398
2604
|
reload() {
|
|
2399
|
-
|
|
2605
|
+
void this.#router.navigateByUrl(this.#layoutService.homeUrl);
|
|
2400
2606
|
}
|
|
2401
2607
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2402
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' |
|
|
2608
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"not-found\" class=\"sd-image-not-found\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.not-found.title' | sdTranslate }}</span>\n <span class=\"T16R text-secondary mt-4\">\n {{ 'core.module.layout.not-found.message' | sdTranslate }}\n </span>\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.not-found.back' | sdTranslate\" prefixIcon=\"arrow_back\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdPageComponent, selector: "sd-page", inputs: ["title", "description", "noHeader"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
2403
2609
|
};
|
|
2404
2610
|
RootComponent$1 = __decorate([
|
|
2405
2611
|
SdTabComponent({
|
|
@@ -2411,7 +2617,7 @@ RootComponent$1 = __decorate([
|
|
|
2411
2617
|
], RootComponent$1);
|
|
2412
2618
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent$1, decorators: [{
|
|
2413
2619
|
type: Component,
|
|
2414
|
-
args: [{ imports: [SdButton, SdPageComponent,
|
|
2620
|
+
args: [{ imports: [SdButton, SdPageComponent, SdTranslatePipe], template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"not-found\" class=\"sd-image-not-found\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.not-found.title' | sdTranslate }}</span>\n <span class=\"T16R text-secondary mt-4\">\n {{ 'core.module.layout.not-found.message' | sdTranslate }}\n </span>\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.not-found.back' | sdTranslate\" prefixIcon=\"arrow_back\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"] }]
|
|
2415
2621
|
}] });
|
|
2416
2622
|
|
|
2417
2623
|
const Routes$2 = [{ path: '', component: RootComponent$1 }];
|
|
@@ -2440,6 +2646,7 @@ let RootComponent = class RootComponent {
|
|
|
2440
2646
|
// INJECT SERVICES (Modern Angular)
|
|
2441
2647
|
// ==========================================
|
|
2442
2648
|
#route = inject(ActivatedRoute);
|
|
2649
|
+
#router = inject(Router);
|
|
2443
2650
|
#layoutService = inject(SdLayoutService);
|
|
2444
2651
|
#i18n = inject(I18nService);
|
|
2445
2652
|
// ==========================================
|
|
@@ -2465,11 +2672,16 @@ let RootComponent = class RootComponent {
|
|
|
2465
2672
|
// ==========================================
|
|
2466
2673
|
// PUBLIC METHODS
|
|
2467
2674
|
// ==========================================
|
|
2675
|
+
/**
|
|
2676
|
+
* why: `window.location.href = ''` vừa là global thô (throw khi SSR vì không có `window`), vừa chỉ
|
|
2677
|
+
* reload lại đúng trang lỗi này thay vì rời khỏi nó — trong khi nút mang nhãn "Về trang chủ".
|
|
2678
|
+
* Điều hướng bằng Router tới `homeUrl` mà consumer đã khai trong SD_LAYOUT_CONFIGURATION.
|
|
2679
|
+
*/
|
|
2468
2680
|
reload() {
|
|
2469
|
-
|
|
2681
|
+
void this.#router.navigateByUrl(this.#layoutService.homeUrl);
|
|
2470
2682
|
}
|
|
2471
2683
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2472
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' |
|
|
2684
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"forbidden\" class=\"sd-image-forbidden\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.forbidden.title' | sdTranslate }}</span>\n <span class=\"T14R text-secondary mt-4\"\n >{{ 'core.module.layout.forbidden.message' | sdTranslate }}</span\n >\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.forbidden.back-home' | sdTranslate\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdPageComponent, selector: "sd-page", inputs: ["title", "description", "noHeader"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
2473
2685
|
};
|
|
2474
2686
|
RootComponent = __decorate([
|
|
2475
2687
|
SdTabComponent({
|
|
@@ -2481,7 +2693,7 @@ RootComponent = __decorate([
|
|
|
2481
2693
|
], RootComponent);
|
|
2482
2694
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent, decorators: [{
|
|
2483
2695
|
type: Component,
|
|
2484
|
-
args: [{ imports: [SdButton, SdPageComponent,
|
|
2696
|
+
args: [{ imports: [SdButton, SdPageComponent, SdTranslatePipe], template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | sdTranslate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"forbidden\" class=\"sd-image-forbidden\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.forbidden.title' | sdTranslate }}</span>\n <span class=\"T14R text-secondary mt-4\"\n >{{ 'core.module.layout.forbidden.message' | sdTranslate }}</span\n >\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.forbidden.back-home' | sdTranslate\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"] }]
|
|
2485
2697
|
}] });
|
|
2486
2698
|
|
|
2487
2699
|
const Routes$1 = [{ path: '', component: RootComponent }];
|
|
@@ -2504,6 +2716,12 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
2504
2716
|
ForbiddenModule: ForbiddenModule
|
|
2505
2717
|
});
|
|
2506
2718
|
|
|
2719
|
+
// why: `SdPermissionGuard.canActivateChild` từ chối mọi route không khai báo `data.permission`. Ba
|
|
2720
|
+
// trang built-in dưới đây là hạ tầng của layout (home / 404 / 403), ai đăng nhập cũng phải vào được —
|
|
2721
|
+
// đặc biệt `forbidden`, vì `onForbiden` thường điều hướng chính vào nó: thiếu khai báo ở đây thì
|
|
2722
|
+
// trang 403 tự bị chặn và app rơi vào vòng lặp redirect. Nói tường minh bằng `SD_PERMISSION_PUBLIC`.
|
|
2723
|
+
// Route con `path: ''` trong từng module kế thừa `data` này (chiến lược `emptyOnly` mặc định).
|
|
2724
|
+
const PUBLIC_ROUTE_DATA = { permission: SD_PERMISSION_PUBLIC };
|
|
2507
2725
|
const Routes = [
|
|
2508
2726
|
{
|
|
2509
2727
|
path: '',
|
|
@@ -2512,14 +2730,17 @@ const Routes = [
|
|
|
2512
2730
|
},
|
|
2513
2731
|
{
|
|
2514
2732
|
path: 'home',
|
|
2733
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2515
2734
|
loadChildren: () => Promise.resolve().then(function () { return index$2; }).then(m => m.HomeModule),
|
|
2516
2735
|
},
|
|
2517
2736
|
{
|
|
2518
2737
|
path: 'not-found',
|
|
2738
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2519
2739
|
loadChildren: () => Promise.resolve().then(function () { return index$1; }).then(m => m.NotFoundModule),
|
|
2520
2740
|
},
|
|
2521
2741
|
{
|
|
2522
2742
|
path: 'forbidden',
|
|
2743
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2523
2744
|
loadChildren: () => Promise.resolve().then(function () { return index; }).then(m => m.ForbiddenModule),
|
|
2524
2745
|
},
|
|
2525
2746
|
];
|
|
@@ -2542,5 +2763,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2542
2763
|
* Generated bundle index. Do not edit.
|
|
2543
2764
|
*/
|
|
2544
2765
|
|
|
2545
|
-
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,
|
|
2766
|
+
export { DEFAULT_LAYOUT_MOBILE_BREAKPOINT, ForbiddenModule, HighlightSearchPipe, HomeModule, MenuFocusPipe, MenuPipe, NotFoundModule, SD_LAYOUT_CONFIGURATION, SD_LAYOUT_DEMO_FALLBACK, SD_LAYOUT_STORAGE_NAMESPACE, SD_LAYOUT_VIEWPORT, SdLayoutComponent, SdLayoutMenuTreeComponent, SdLayoutModule, SdLayoutNavigationStateService, SdLayoutResponsiveService, SdLayoutService, SdLayoutStorageService, SdLayoutUserMenuComponent, SdPageComponent, SdSidebarMobileOverlay, SdSidebarMobileV1, SdSidebarMobileV2, SdSidebarMobileV3, SdSidebarV2, SdSidebarV3, flattenMenuLeaves, getMenuStableKey, normalizeLayoutMobileBreakpoint, normalizeSidebarConfiguration, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, resolveTabName, searchMenuLeaves, selectPrimaryMenuGroups };
|
|
2546
2767
|
//# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map
|