@sdcorejs/angular 20.1.6 → 20.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/api-contract-builder/index.d.ts +694 -0
- package/components/badge/index.d.ts +2 -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 +7 -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 +5 -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-api-contract-builder.mjs +3417 -0
- package/fesm2022/sdcorejs-angular-components-api-contract-builder.mjs.map +1 -0
- 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 +27 -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-operator.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-operator.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 +33 -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 +556 -268
- 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 +7 -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 +895 -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 +342 -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 +175 -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 +36 -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,32 @@ class SidebarComponent {
|
|
|
858
1020
|
onToggleMenuNode = (menu) => {
|
|
859
1021
|
this.treeControl.toggle(menu);
|
|
860
1022
|
};
|
|
1023
|
+
menuNodeHref = (node) => {
|
|
1024
|
+
if (sdIsExternalHttpUrl(node.path))
|
|
1025
|
+
return node.path;
|
|
1026
|
+
return this.#router.serializeUrl(this.#router.createUrlTree([node.path.split('?')[0]], {
|
|
1027
|
+
queryParams: node.queryParams ?? {},
|
|
1028
|
+
}));
|
|
1029
|
+
};
|
|
1030
|
+
onMenuNodeClick = (event, node) => {
|
|
1031
|
+
event.preventDefault();
|
|
1032
|
+
this.navigate({ path: node.path, queryParams: node.queryParams ?? {} });
|
|
1033
|
+
};
|
|
1034
|
+
// why: giữ public keyboard handler cũ để không làm thay đổi declaration API.
|
|
1035
|
+
onMenuNodeKeydown = (event, node) => {
|
|
1036
|
+
if (event.target !== event.currentTarget)
|
|
1037
|
+
return;
|
|
1038
|
+
// why: chặn Space cuộn trang.
|
|
1039
|
+
event.preventDefault();
|
|
1040
|
+
this.navigate({ path: node.path, queryParams: node.queryParams ?? {} });
|
|
1041
|
+
};
|
|
1042
|
+
// why: giữ public keyboard handler cũ; native branch button now owns keyboard activation.
|
|
1043
|
+
onToggleMenuNodeKeydown = (event, menu) => {
|
|
1044
|
+
if (event.target !== event.currentTarget)
|
|
1045
|
+
return;
|
|
1046
|
+
event.preventDefault();
|
|
1047
|
+
this.onToggleMenuNode(menu);
|
|
1048
|
+
};
|
|
861
1049
|
isPinnedNode = (node) => this.pinnedNodeKeys().has(this.#getMenuNodeKey(node));
|
|
862
1050
|
isHoveredNode = (node) => this.#hoveredMenuNodeKey() === this.#getMenuNodeKey(node);
|
|
863
1051
|
expandPinnedGroup = () => {
|
|
@@ -904,8 +1092,12 @@ class SidebarComponent {
|
|
|
904
1092
|
onUserMenuOpened = () => this.popupUserMenuOpened.emit();
|
|
905
1093
|
navigate = (args) => {
|
|
906
1094
|
const { path, queryParams } = args;
|
|
907
|
-
|
|
908
|
-
|
|
1095
|
+
// why: `path.includes('http')` là substring test chứ không phải scheme test — chuỗi
|
|
1096
|
+
// `javascript:fetch(...)//http` lọt qua rồi chạy như script trong chính origin của app.
|
|
1097
|
+
// `sdIsExternalHttpUrl` parse URL thật, `sdOpenExternal` từ chối scheme lạ và luôn gắn
|
|
1098
|
+
// `noopener,noreferrer` (chặn reverse tabnabbing qua `window.opener`).
|
|
1099
|
+
if (sdIsExternalHttpUrl(path)) {
|
|
1100
|
+
sdOpenExternal(path);
|
|
909
1101
|
return;
|
|
910
1102
|
}
|
|
911
1103
|
this.#router.navigate([path.split('?')[0]], {
|
|
@@ -1291,12 +1483,12 @@ class SidebarComponent {
|
|
|
1291
1483
|
}
|
|
1292
1484
|
return count;
|
|
1293
1485
|
};
|
|
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 });
|
|
1486
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1487
|
+
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\n href=\"/layout/home\"\n [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\"\n (click)=\"$event.preventDefault(); 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 type=\"button\"\n [attr.aria-label]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\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 type=\"button\"\n [attr.aria-label]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\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 <div\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 class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <a\n class=\"c-menu-node-description-content\"\n [href]=\"menuNodeHref(node)\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"onMenuNodeClick($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\"></a>\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 </div>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <div class=\"c-menu-node\" (mouseenter)=\"onMouseOverMenuNode($event, node)\" (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <button\n type=\"button\"\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\">\n <span\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></span>\n <sd-icon\n aria-hidden=\"true\"\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n </div>\n </div>\n <div class=\"c-menu-node-group p-0\" role=\"group\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\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 .c-menu-node-group .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .c-menu-node-description-content{margin-left:48px!important}.c-menu-node-group{margin-top:0;margin-bottom:0}.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 button.c-menu-node-description{width:100%;padding:0;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.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 a.c-menu-node-description-content{color:inherit;text-decoration:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description-content:focus-visible,.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node button.c-menu-node-description: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
1488
|
}
|
|
1297
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1489
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, decorators: [{
|
|
1298
1490
|
type: Component,
|
|
1299
|
-
args: [{ selector: 'sidebar', standalone: true, imports: [
|
|
1491
|
+
args: [{ selector: 'sd-sidebar-v1-panel', standalone: true, imports: [
|
|
1300
1492
|
SdIcon,
|
|
1301
1493
|
SdInput,
|
|
1302
1494
|
FormsModule,
|
|
@@ -1310,11 +1502,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1310
1502
|
HighlightSearchPipe,
|
|
1311
1503
|
SdSuffixDefDirective,
|
|
1312
1504
|
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"] }]
|
|
1505
|
+
SdTranslatePipe,
|
|
1506
|
+
], 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\n href=\"/layout/home\"\n [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\"\n (click)=\"$event.preventDefault(); 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 type=\"button\"\n [attr.aria-label]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\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 type=\"button\"\n [attr.aria-label]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\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 <div\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 class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <a\n class=\"c-menu-node-description-content\"\n [href]=\"menuNodeHref(node)\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"onMenuNodeClick($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\"></a>\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 </div>\n </mat-nested-tree-node>\n\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <div class=\"c-menu-node\" (mouseenter)=\"onMouseOverMenuNode($event, node)\" (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <button\n type=\"button\"\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\">\n <span\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></span>\n <sd-icon\n aria-hidden=\"true\"\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </button>\n </div>\n </div>\n <div class=\"c-menu-node-group p-0\" role=\"group\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\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 .c-menu-node-group .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .mat-nested-tree-node .c-menu-node-group .c-menu-node-description-content{margin-left:48px!important}.c-menu-node-group{margin-top:0;margin-bottom:0}.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 button.c-menu-node-description{width:100%;padding:0;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.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 a.c-menu-node-description-content{color:inherit;text-decoration:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description-content:focus-visible,.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node button.c-menu-node-description: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
1507
|
}], 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
1508
|
|
|
1317
|
-
class
|
|
1509
|
+
class SdSidebarV1 {
|
|
1318
1510
|
// ==========================================
|
|
1319
1511
|
// INJECT SERVICES
|
|
1320
1512
|
// ==========================================
|
|
@@ -1438,12 +1630,12 @@ class SidebarV1Component {
|
|
|
1438
1630
|
}
|
|
1439
1631
|
}
|
|
1440
1632
|
};
|
|
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:
|
|
1633
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1634
|
+
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
1635
|
}
|
|
1444
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1636
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1, decorators: [{
|
|
1445
1637
|
type: Component,
|
|
1446
|
-
args: [{ selector: 'sidebar-v1', imports: [MatSidenavModule, CommonModule,
|
|
1638
|
+
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
1639
|
}], 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
1640
|
|
|
1449
1641
|
class LayoutUserComponent {
|
|
@@ -1464,17 +1656,20 @@ class LayoutUserComponent {
|
|
|
1464
1656
|
this.toggleMenuLock.emit(event);
|
|
1465
1657
|
}
|
|
1466
1658
|
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' |
|
|
1659
|
+
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
1660
|
}
|
|
1469
1661
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LayoutUserComponent, decorators: [{
|
|
1470
1662
|
type: Component,
|
|
1471
|
-
args: [{ selector: 'lib-layout-user', imports: [SdIcon, SdLayoutUserMenuComponent,
|
|
1663
|
+
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
1664
|
}], 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
1665
|
|
|
1474
|
-
class
|
|
1666
|
+
class SdSidebarMobileOverlay {
|
|
1475
1667
|
#router = inject(Router);
|
|
1476
1668
|
#destroyRef = inject(DestroyRef);
|
|
1477
1669
|
#layoutStorageService = inject(SdLayoutStorageService);
|
|
1670
|
+
// why: các sidebar anh em đều lấy window qua DOCUMENT.defaultView; đọc global `window` trực tiếp
|
|
1671
|
+
// sẽ throw khi render phía server (SSR) vì global đó không tồn tại.
|
|
1672
|
+
#window = inject(DOCUMENT).defaultView;
|
|
1478
1673
|
// ==========================================
|
|
1479
1674
|
// INPUTS & OUTPUTS
|
|
1480
1675
|
// ==========================================
|
|
@@ -1492,7 +1687,7 @@ class SidebarMobileOverlayComponent {
|
|
|
1492
1687
|
// STATE SIGNALS
|
|
1493
1688
|
// ==========================================
|
|
1494
1689
|
titleMenuGroup = signal('', ...(ngDevMode ? [{ debugName: "titleMenuGroup" }] : []));
|
|
1495
|
-
currentPath = signal(window
|
|
1690
|
+
currentPath = signal(this.#window?.location.pathname ?? this.#router.url.split(/[?#]/, 1)[0] ?? '', ...(ngDevMode ? [{ debugName: "currentPath" }] : []));
|
|
1496
1691
|
// Lưu danh sách ID các menu group đang được mở
|
|
1497
1692
|
expandedMobileGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedMobileGroups" }] : []));
|
|
1498
1693
|
constructor() {
|
|
@@ -1510,8 +1705,11 @@ class SidebarMobileOverlayComponent {
|
|
|
1510
1705
|
// Bind title khi điều hướng
|
|
1511
1706
|
this.#router.events.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(event => {
|
|
1512
1707
|
if (event instanceof NavigationEnd) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1708
|
+
// why: giống sidebar v1 desktop — lấy path từ chính sự kiện router thay vì đọc global
|
|
1709
|
+
// `window.location`, để component chạy được cả khi không có global window (SSR).
|
|
1710
|
+
const currentPath = event.urlAfterRedirects.split(/[?#]/, 1)[0] ?? '';
|
|
1711
|
+
if (this.currentPath() !== currentPath) {
|
|
1712
|
+
this.currentPath.set(currentPath);
|
|
1515
1713
|
this.#bindingMenuGroupByCurrentPath(this.menus());
|
|
1516
1714
|
}
|
|
1517
1715
|
}
|
|
@@ -1543,8 +1741,12 @@ class SidebarMobileOverlayComponent {
|
|
|
1543
1741
|
const { path, queryParams } = args;
|
|
1544
1742
|
if (!path)
|
|
1545
1743
|
return;
|
|
1546
|
-
|
|
1547
|
-
|
|
1744
|
+
// why: `path.includes('http')` là substring test chứ không phải scheme test —
|
|
1745
|
+
// `javascript:fetch(...)//http` lọt qua rồi chạy như script trong chính origin của app. Ngoài ra
|
|
1746
|
+
// `window.open(path)` cũ không truyền `noopener` nên tab mới giữ được `window.opener` và có thể
|
|
1747
|
+
// điều hướng ngược tab gốc (reverse tabnabbing). `sdOpenExternal` luôn gắn `noopener,noreferrer`.
|
|
1748
|
+
if (sdIsExternalHttpUrl(path)) {
|
|
1749
|
+
sdOpenExternal(path);
|
|
1548
1750
|
return;
|
|
1549
1751
|
}
|
|
1550
1752
|
this.#router.navigate([path.split('?')[0]], {
|
|
@@ -1617,15 +1819,15 @@ class SidebarMobileOverlayComponent {
|
|
|
1617
1819
|
return this.#normalizePath(this.currentPath()).startsWith(this.#normalizePath(path));
|
|
1618
1820
|
};
|
|
1619
1821
|
#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"] }] });
|
|
1822
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileOverlay, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1823
|
+
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
1824
|
}
|
|
1623
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1825
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileOverlay, decorators: [{
|
|
1624
1826
|
type: Component,
|
|
1625
1827
|
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
1828
|
}], 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
1829
|
|
|
1628
|
-
class
|
|
1830
|
+
class SdSidebarMobileV1 {
|
|
1629
1831
|
#layoutStorageService = inject(SdLayoutStorageService);
|
|
1630
1832
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
1631
1833
|
userInfo = input.required(...(ngDevMode ? [{ debugName: "userInfo" }] : []));
|
|
@@ -1654,16 +1856,17 @@ class SidebarMobileV1Component {
|
|
|
1654
1856
|
onTitleMenuGroupChanged = (title) => {
|
|
1655
1857
|
this.titleMenuGroup.set(title);
|
|
1656
1858
|
};
|
|
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:
|
|
1859
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV1, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1860
|
+
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
1861
|
}
|
|
1660
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
1862
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV1, decorators: [{
|
|
1661
1863
|
type: Component,
|
|
1662
|
-
args: [{ selector: 'sidebar-mobile-v1', imports: [SdIcon, CommonModule,
|
|
1864
|
+
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
1865
|
}], 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
1866
|
|
|
1665
1867
|
class SdLayoutMenuTreeComponent {
|
|
1666
1868
|
#destroyRef = inject(DestroyRef);
|
|
1869
|
+
#i18n = inject(I18nService);
|
|
1667
1870
|
#hoveredPinKey = signal(null, ...(ngDevMode ? [{ debugName: "#hoveredPinKey" }] : []));
|
|
1668
1871
|
#pinHoverTimerId;
|
|
1669
1872
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
@@ -1688,16 +1891,22 @@ class SdLayoutMenuTreeComponent {
|
|
|
1688
1891
|
continue;
|
|
1689
1892
|
const isGroup = 'children' in menu && !!menu.children?.length;
|
|
1690
1893
|
const path = 'path' in menu ? menu.path : '';
|
|
1894
|
+
const title = menu.title ?? menu.tooltipTitle ?? '';
|
|
1895
|
+
const isPinned = pinnedKeys.has(key);
|
|
1691
1896
|
nodes.push({
|
|
1692
1897
|
menu,
|
|
1693
1898
|
key,
|
|
1694
|
-
title
|
|
1899
|
+
title,
|
|
1695
1900
|
depth,
|
|
1696
1901
|
paddingLeft: 12 + depth * 16,
|
|
1697
1902
|
isGroup,
|
|
1698
1903
|
isActive: !!path && this.#pathMatches(this.activePath(), path),
|
|
1699
|
-
isPinned
|
|
1700
|
-
isPinVisible: alwaysShowPin ||
|
|
1904
|
+
isPinned,
|
|
1905
|
+
isPinVisible: alwaysShowPin || isPinned || hoveredPinKey === key,
|
|
1906
|
+
// why: template cũ nối `'Pin ' + node.title` — chuỗi tiếng Anh cứng và ép trật tự
|
|
1907
|
+
// "động từ trước tên" của tiếng Anh lên mọi ngôn ngữ. Nội suy `{title}` để catalog tự
|
|
1908
|
+
// quyết định vị trí. Tính trong `nodes` computed nên tự cập nhật khi đổi ngôn ngữ.
|
|
1909
|
+
pinLabel: this.#i18n.t(isPinned ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin', { title }),
|
|
1701
1910
|
});
|
|
1702
1911
|
if (isGroup && 'children' in menu)
|
|
1703
1912
|
append(menu.children ?? [], depth + 1, [...ancestors, menu.title ?? key]);
|
|
@@ -1742,11 +1951,11 @@ class SdLayoutMenuTreeComponent {
|
|
|
1742
1951
|
return currentPath === menuPath || normalize(currentPath).startsWith(normalize(menuPath));
|
|
1743
1952
|
}
|
|
1744
1953
|
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.
|
|
1954
|
+
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
1955
|
}
|
|
1747
1956
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, decorators: [{
|
|
1748
1957
|
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.
|
|
1958
|
+
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
1959
|
}], 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
1960
|
|
|
1752
1961
|
class SdLayoutSearchFieldComponent {
|
|
@@ -1762,9 +1971,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1762
1971
|
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
1972
|
}], 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
1973
|
|
|
1765
|
-
class
|
|
1974
|
+
class SdSidebarV2 {
|
|
1766
1975
|
#router = inject(Router);
|
|
1767
|
-
#document = inject(DOCUMENT);
|
|
1768
1976
|
#destroyRef = inject(DestroyRef);
|
|
1769
1977
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
1770
1978
|
menus = input([], ...(ngDevMode ? [{ debugName: "menus" }] : []));
|
|
@@ -1846,8 +2054,10 @@ class SidebarV2Component {
|
|
|
1846
2054
|
if (!('path' in menu))
|
|
1847
2055
|
return;
|
|
1848
2056
|
this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
|
|
1849
|
-
|
|
1850
|
-
|
|
2057
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2058
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2059
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2060
|
+
sdOpenExternal(menu.path);
|
|
1851
2061
|
this.closeFromNavigation();
|
|
1852
2062
|
return;
|
|
1853
2063
|
}
|
|
@@ -1874,16 +2084,19 @@ class SidebarV2Component {
|
|
|
1874
2084
|
if (persist)
|
|
1875
2085
|
this.#navigationState.patchVersionState(2, { locked: false });
|
|
1876
2086
|
}
|
|
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:
|
|
2087
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV2, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2088
|
+
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
2089
|
}
|
|
1880
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2090
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV2, decorators: [{
|
|
1881
2091
|
type: Component,
|
|
1882
|
-
args: [{ selector: 'sidebar-v2', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2092
|
+
args: [{ selector: 'sd-sidebar-v2', standalone: true, imports: [SdIcon, SdLayoutSearchFieldComponent, SdLayoutMenuTreeComponent, SdLayoutUserMenuComponent, SdTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
2093
|
+
'(keydown.escape)': 'closeFromEscape()',
|
|
2094
|
+
}, 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
2095
|
}], 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
2096
|
|
|
1885
|
-
class
|
|
2097
|
+
class SdSidebarMobileV2 {
|
|
1886
2098
|
#router = inject(Router);
|
|
2099
|
+
#i18n = inject(I18nService);
|
|
1887
2100
|
#document = inject(DOCUMENT);
|
|
1888
2101
|
#destroyRef = inject(DestroyRef);
|
|
1889
2102
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
@@ -1918,7 +2131,7 @@ class SidebarMobileV2Component {
|
|
|
1918
2131
|
this.#destroyRef.onDestroy(() => this.#releaseBodyScroll());
|
|
1919
2132
|
}
|
|
1920
2133
|
openMore(trigger) {
|
|
1921
|
-
this.#openSheet('more', '
|
|
2134
|
+
this.#openSheet('more', this.#i18n.t('core.module.layout.sidebar.more'), this.overflowMenus(), trigger);
|
|
1922
2135
|
}
|
|
1923
2136
|
activateMenu(menu, trigger) {
|
|
1924
2137
|
if ('path' in menu) {
|
|
@@ -1965,8 +2178,10 @@ class SidebarMobileV2Component {
|
|
|
1965
2178
|
}
|
|
1966
2179
|
#navigate(menu) {
|
|
1967
2180
|
this.#navigationState.recordRecent(menu, { enabled: true, maxItems: 5 });
|
|
1968
|
-
|
|
1969
|
-
|
|
2181
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2182
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2183
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2184
|
+
sdOpenExternal(menu.path);
|
|
1970
2185
|
this.closeSheet();
|
|
1971
2186
|
return;
|
|
1972
2187
|
}
|
|
@@ -1980,17 +2195,16 @@ class SidebarMobileV2Component {
|
|
|
1980
2195
|
#releaseBodyScroll() {
|
|
1981
2196
|
this.#document.body.style.overflow = this.#previousBodyOverflow;
|
|
1982
2197
|
}
|
|
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:
|
|
2198
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV2, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2199
|
+
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
2200
|
}
|
|
1986
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV2, decorators: [{
|
|
1987
2202
|
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=\"
|
|
2203
|
+
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
2204
|
}], 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
2205
|
|
|
1991
|
-
class
|
|
2206
|
+
class SdSidebarV3 {
|
|
1992
2207
|
#router = inject(Router);
|
|
1993
|
-
#document = inject(DOCUMENT);
|
|
1994
2208
|
#destroyRef = inject(DestroyRef);
|
|
1995
2209
|
#navigationState = inject(SdLayoutNavigationStateService);
|
|
1996
2210
|
#initialized = false;
|
|
@@ -2041,8 +2255,10 @@ class SidebarV3Component {
|
|
|
2041
2255
|
if (!('path' in menu))
|
|
2042
2256
|
return;
|
|
2043
2257
|
this.#navigationState.recordRecent(menu, this.recentConfiguration());
|
|
2044
|
-
|
|
2045
|
-
|
|
2258
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2259
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2260
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2261
|
+
sdOpenExternal(menu.path);
|
|
2046
2262
|
return;
|
|
2047
2263
|
}
|
|
2048
2264
|
void this.#router.navigate([menu.path.split('?')[0]], {
|
|
@@ -2053,15 +2269,15 @@ class SidebarV3Component {
|
|
|
2053
2269
|
onTogglePinned(menu) {
|
|
2054
2270
|
this.#navigationState.togglePinned(menu);
|
|
2055
2271
|
}
|
|
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:
|
|
2272
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV3, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2273
|
+
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
2274
|
}
|
|
2059
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2275
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV3, decorators: [{
|
|
2060
2276
|
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\"
|
|
2277
|
+
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
2278
|
}], 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
2279
|
|
|
2064
|
-
class
|
|
2280
|
+
class SdSidebarMobileV3 {
|
|
2065
2281
|
#router = inject(Router);
|
|
2066
2282
|
#document = inject(DOCUMENT);
|
|
2067
2283
|
#destroyRef = inject(DestroyRef);
|
|
@@ -2126,8 +2342,10 @@ class SidebarMobileV3Component {
|
|
|
2126
2342
|
if (!('path' in menu))
|
|
2127
2343
|
return;
|
|
2128
2344
|
this.#navigationState.recordRecent(menu, this.recentConfiguration());
|
|
2129
|
-
|
|
2130
|
-
|
|
2345
|
+
// why: `includes('http')` là substring test — `javascript:fetch(...)//http` lọt qua và chạy như
|
|
2346
|
+
// script trong origin của app. Parse URL thật rồi mới mở, kèm `noopener,noreferrer`.
|
|
2347
|
+
if (sdIsExternalHttpUrl(menu.path)) {
|
|
2348
|
+
sdOpenExternal(menu.path);
|
|
2131
2349
|
this.closeFromNavigation();
|
|
2132
2350
|
return;
|
|
2133
2351
|
}
|
|
@@ -2153,12 +2371,12 @@ class SidebarMobileV3Component {
|
|
|
2153
2371
|
#releaseBodyScroll() {
|
|
2154
2372
|
this.#document.body.style.overflow = this.#previousBodyOverflow;
|
|
2155
2373
|
}
|
|
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:
|
|
2374
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV3, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2375
|
+
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
2376
|
}
|
|
2159
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type:
|
|
2377
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileV3, decorators: [{
|
|
2160
2378
|
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\"
|
|
2379
|
+
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
2380
|
}], 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
2381
|
|
|
2164
2382
|
class SdLayoutComponent {
|
|
@@ -2194,19 +2412,11 @@ class SdLayoutComponent {
|
|
|
2194
2412
|
mobileBreakpoint = normalizeLayoutMobileBreakpoint(this.#layoutConfiguration?.mobileBreakpoint);
|
|
2195
2413
|
isMobile = computed(() => this.#responsiveService.isMobile(this.mobileBreakpoint), ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
|
|
2196
2414
|
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:
|
|
2415
|
+
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
2416
|
}
|
|
2199
2417
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutComponent, decorators: [{
|
|
2200
2418
|
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"] }]
|
|
2419
|
+
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
2420
|
}], propDecorators: { menusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }] } });
|
|
2211
2421
|
|
|
2212
2422
|
/**
|
|
@@ -2288,7 +2498,7 @@ let HomePageComponent = class HomePageComponent {
|
|
|
2288
2498
|
return `${formatted} (${this.#getTimezone()})`;
|
|
2289
2499
|
}
|
|
2290
2500
|
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' |
|
|
2501
|
+
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
2502
|
};
|
|
2293
2503
|
HomePageComponent = __decorate([
|
|
2294
2504
|
SdTabComponent({
|
|
@@ -2300,7 +2510,7 @@ HomePageComponent = __decorate([
|
|
|
2300
2510
|
], HomePageComponent);
|
|
2301
2511
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HomePageComponent, decorators: [{
|
|
2302
2512
|
type: Component,
|
|
2303
|
-
args: [{ selector: 'app-home-page', imports: [SdIcon, SdPageComponent,
|
|
2513
|
+
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
2514
|
}] });
|
|
2305
2515
|
|
|
2306
2516
|
class HomeRedirectGuard {
|
|
@@ -2370,6 +2580,7 @@ let RootComponent$1 = class RootComponent {
|
|
|
2370
2580
|
// INJECT SERVICES (Modern Angular)
|
|
2371
2581
|
// ==========================================
|
|
2372
2582
|
#route = inject(ActivatedRoute);
|
|
2583
|
+
#router = inject(Router);
|
|
2373
2584
|
#layoutService = inject(SdLayoutService);
|
|
2374
2585
|
#i18n = inject(I18nService);
|
|
2375
2586
|
// ==========================================
|
|
@@ -2395,11 +2606,16 @@ let RootComponent$1 = class RootComponent {
|
|
|
2395
2606
|
// ==========================================
|
|
2396
2607
|
// PUBLIC METHODS
|
|
2397
2608
|
// ==========================================
|
|
2609
|
+
/**
|
|
2610
|
+
* why: `window.location.href = ''` vừa là global thô (throw khi SSR vì không có `window`), vừa chỉ
|
|
2611
|
+
* 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ủ".
|
|
2612
|
+
* Điều hướng bằng Router tới `homeUrl` mà consumer đã khai trong SD_LAYOUT_CONFIGURATION.
|
|
2613
|
+
*/
|
|
2398
2614
|
reload() {
|
|
2399
|
-
|
|
2615
|
+
void this.#router.navigateByUrl(this.#layoutService.homeUrl);
|
|
2400
2616
|
}
|
|
2401
2617
|
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' |
|
|
2618
|
+
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
2619
|
};
|
|
2404
2620
|
RootComponent$1 = __decorate([
|
|
2405
2621
|
SdTabComponent({
|
|
@@ -2411,7 +2627,7 @@ RootComponent$1 = __decorate([
|
|
|
2411
2627
|
], RootComponent$1);
|
|
2412
2628
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent$1, decorators: [{
|
|
2413
2629
|
type: Component,
|
|
2414
|
-
args: [{ imports: [SdButton, SdPageComponent,
|
|
2630
|
+
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
2631
|
}] });
|
|
2416
2632
|
|
|
2417
2633
|
const Routes$2 = [{ path: '', component: RootComponent$1 }];
|
|
@@ -2440,6 +2656,7 @@ let RootComponent = class RootComponent {
|
|
|
2440
2656
|
// INJECT SERVICES (Modern Angular)
|
|
2441
2657
|
// ==========================================
|
|
2442
2658
|
#route = inject(ActivatedRoute);
|
|
2659
|
+
#router = inject(Router);
|
|
2443
2660
|
#layoutService = inject(SdLayoutService);
|
|
2444
2661
|
#i18n = inject(I18nService);
|
|
2445
2662
|
// ==========================================
|
|
@@ -2465,11 +2682,16 @@ let RootComponent = class RootComponent {
|
|
|
2465
2682
|
// ==========================================
|
|
2466
2683
|
// PUBLIC METHODS
|
|
2467
2684
|
// ==========================================
|
|
2685
|
+
/**
|
|
2686
|
+
* why: `window.location.href = ''` vừa là global thô (throw khi SSR vì không có `window`), vừa chỉ
|
|
2687
|
+
* 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ủ".
|
|
2688
|
+
* Điều hướng bằng Router tới `homeUrl` mà consumer đã khai trong SD_LAYOUT_CONFIGURATION.
|
|
2689
|
+
*/
|
|
2468
2690
|
reload() {
|
|
2469
|
-
|
|
2691
|
+
void this.#router.navigateByUrl(this.#layoutService.homeUrl);
|
|
2470
2692
|
}
|
|
2471
2693
|
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' |
|
|
2694
|
+
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
2695
|
};
|
|
2474
2696
|
RootComponent = __decorate([
|
|
2475
2697
|
SdTabComponent({
|
|
@@ -2481,7 +2703,7 @@ RootComponent = __decorate([
|
|
|
2481
2703
|
], RootComponent);
|
|
2482
2704
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RootComponent, decorators: [{
|
|
2483
2705
|
type: Component,
|
|
2484
|
-
args: [{ imports: [SdButton, SdPageComponent,
|
|
2706
|
+
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
2707
|
}] });
|
|
2486
2708
|
|
|
2487
2709
|
const Routes$1 = [{ path: '', component: RootComponent }];
|
|
@@ -2504,6 +2726,12 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
2504
2726
|
ForbiddenModule: ForbiddenModule
|
|
2505
2727
|
});
|
|
2506
2728
|
|
|
2729
|
+
// why: `SdPermissionGuard.canActivateChild` từ chối mọi route không khai báo `data.permission`. Ba
|
|
2730
|
+
// 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 —
|
|
2731
|
+
// đặc biệt `forbidden`, vì `onForbiden` thường điều hướng chính vào nó: thiếu khai báo ở đây thì
|
|
2732
|
+
// 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`.
|
|
2733
|
+
// Route con `path: ''` trong từng module kế thừa `data` này (chiến lược `emptyOnly` mặc định).
|
|
2734
|
+
const PUBLIC_ROUTE_DATA = { permission: SD_PERMISSION_PUBLIC };
|
|
2507
2735
|
const Routes = [
|
|
2508
2736
|
{
|
|
2509
2737
|
path: '',
|
|
@@ -2512,14 +2740,17 @@ const Routes = [
|
|
|
2512
2740
|
},
|
|
2513
2741
|
{
|
|
2514
2742
|
path: 'home',
|
|
2743
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2515
2744
|
loadChildren: () => Promise.resolve().then(function () { return index$2; }).then(m => m.HomeModule),
|
|
2516
2745
|
},
|
|
2517
2746
|
{
|
|
2518
2747
|
path: 'not-found',
|
|
2748
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2519
2749
|
loadChildren: () => Promise.resolve().then(function () { return index$1; }).then(m => m.NotFoundModule),
|
|
2520
2750
|
},
|
|
2521
2751
|
{
|
|
2522
2752
|
path: 'forbidden',
|
|
2753
|
+
data: PUBLIC_ROUTE_DATA,
|
|
2523
2754
|
loadChildren: () => Promise.resolve().then(function () { return index; }).then(m => m.ForbiddenModule),
|
|
2524
2755
|
},
|
|
2525
2756
|
];
|
|
@@ -2542,5 +2773,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2542
2773
|
* Generated bundle index. Do not edit.
|
|
2543
2774
|
*/
|
|
2544
2775
|
|
|
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,
|
|
2776
|
+
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
2777
|
//# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map
|