@sdcorejs/angular 20.2.0 → 20.2.2

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.
@@ -6,7 +6,7 @@ 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, SdTranslatePipe, I18N_STORAGE_KEY, I18N_MESSAGES } from '@sdcorejs/angular/i18n';
9
+ import { I18nService, I18N_STORAGE_KEY, I18N_MESSAGES, SdTranslatePipe } 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';
@@ -627,31 +627,104 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
627
627
  }]
628
628
  }], ctorParameters: () => [] });
629
629
 
630
+ // End
631
+ /** Thêm '/' ở cuối để so khớp theo segment: '/appointment' không khớp nhầm '/appointments'. */
632
+ const normalizeMenuPath = (path) => {
633
+ return path.endsWith('/') ? path : path + '/';
634
+ };
635
+ /** Route hiện tại có nằm trong nhánh của `path` không (khớp chính xác hoặc khớp phần đầu). */
636
+ const isMenuPathMatch = (routePath, path) => {
637
+ if (!routePath || !path) {
638
+ return false;
639
+ }
640
+ if (routePath === path) {
641
+ return true;
642
+ }
643
+ return normalizeMenuPath(routePath).startsWith(normalizeMenuPath(path));
644
+ };
645
+ /** Mọi path trong cây menu khớp với route hiện tại. */
646
+ const collectMatchedMenuPaths = (menus, routePath) => {
647
+ const matched = [];
648
+ const visit = (items) => {
649
+ for (const item of items) {
650
+ if ('path' in item && isMenuPathMatch(routePath, item.path)) {
651
+ matched.push(item.path);
652
+ }
653
+ if ('children' in item && item.children?.length) {
654
+ visit(item.children);
655
+ }
656
+ }
657
+ };
658
+ visit(menus ?? []);
659
+ return matched;
660
+ };
661
+ /**
662
+ * Path khớp sát nhất với route hiện tại trong cả cây menu.
663
+ * Khi cả '/appointment' và '/appointment/cs' cùng khớp route '/appointment/cs',
664
+ * chỉ path dài nhất được coi là active — path còn lại không được highlight.
665
+ * Hoà nhau thì lấy path xuất hiện trước theo thứ tự khai báo.
666
+ */
667
+ const resolveActiveMenuPath = (menus, routePath) => {
668
+ const matched = collectMatchedMenuPaths(menus, routePath);
669
+ if (!matched.length) {
670
+ return null;
671
+ }
672
+ return matched.reduce((best, path) => (normalizeMenuPath(path).length > normalizeMenuPath(best).length ? path : best));
673
+ };
674
+ /** Chính menu này, hoặc bất kỳ menu con nào ở mọi cấp, có đúng path đó. */
675
+ const containsMenuPath = (menuItem, path) => {
676
+ if ('path' in menuItem && menuItem.path === path) {
677
+ return true;
678
+ }
679
+ if ('children' in menuItem && menuItem.children?.length) {
680
+ return menuItem.children.some(child => containsMenuPath(child, path));
681
+ }
682
+ return false;
683
+ };
684
+
685
+ /**
686
+ * Resolve a translated tab name for `@SdTabComponent`.
687
+ *
688
+ * WHY not I18nService: the decorator runs at module-evaluation time, before
689
+ * Angular's DI exists, so the service cannot be injected. We read the language
690
+ * the app persisted and look the key up in the static catalog instead.
691
+ */
692
+ function resolveTabName(key) {
693
+ const lang = (() => {
694
+ try {
695
+ const stored = localStorage.getItem(I18N_STORAGE_KEY);
696
+ if (stored)
697
+ return stored;
698
+ }
699
+ catch {
700
+ // localStorage can throw (private mode, SSR shim) — fall back below.
701
+ }
702
+ return 'vi';
703
+ })();
704
+ return I18N_MESSAGES[lang]?.[key] ?? I18N_MESSAGES.vi[key] ?? key;
705
+ }
706
+
630
707
  // End
631
708
  class MenuFocusPipe {
632
- transform(routePath, menuItem) {
709
+ /**
710
+ * @param activeMenuPath Path khớp sát nhất với route hiện tại (xem `resolveActiveMenuPath`).
711
+ * Truyền vào thì chỉ menu chứa đúng path đó mới focus, nên '/appointment' không sáng khi đang ở
712
+ * '/appointment/cs'. Bỏ trống thì giữ cách khớp phần đầu cũ.
713
+ */
714
+ transform(routePath, menuItem, activeMenuPath) {
633
715
  if (!routePath) {
634
716
  return false;
635
717
  }
718
+ if (activeMenuPath !== undefined) {
719
+ return activeMenuPath ? containsMenuPath(menuItem, activeMenuPath) : false;
720
+ }
636
721
  if ('children' in menuItem && menuItem.children) {
637
722
  return menuItem.children.some(child => {
638
- return 'path' in child && child.path ? this.#match(routePath, child.path) : false;
723
+ return 'path' in child && child.path ? isMenuPathMatch(routePath, child.path) : false;
639
724
  });
640
725
  }
641
- return 'path' in menuItem && this.#match(routePath, menuItem.path);
726
+ return 'path' in menuItem && isMenuPathMatch(routePath, menuItem.path);
642
727
  }
643
- #match = (routePath, path) => {
644
- if (!path) {
645
- return false;
646
- }
647
- if (routePath === path) {
648
- return true;
649
- }
650
- return this.#normalizePath(routePath).startsWith(this.#normalizePath(path));
651
- };
652
- #normalizePath = (p) => {
653
- return p.endsWith('/') ? p : p + '/';
654
- };
655
728
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuFocusPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
656
729
  static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: MenuFocusPipe, isStandalone: true, name: "menuFocus" });
657
730
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: MenuFocusPipe, providedIn: 'root' });
@@ -972,6 +1045,8 @@ class SdSidebarV1Panel {
972
1045
  logoUrl = computed(() => this.sidebar().logoUrl?.trim() || undefined, ...(ngDevMode ? [{ debugName: "logoUrl" }] : []));
973
1046
  pinnedNodeKeys = computed(() => new Set(this.pinnedMenuGroup().children?.map(m => this.#getMenuNodeKey(m)) ?? []), ...(ngDevMode ? [{ debugName: "pinnedNodeKeys" }] : []));
974
1047
  isPinnedMenuGroupActive = computed(() => this.idMenuGroupActive() === this.pinnedMenuGroup().id, ...(ngDevMode ? [{ debugName: "isPinnedMenuGroupActive" }] : []));
1048
+ // Path khớp sát nhất trong nhánh menu đang hiển thị. Nhờ nó '/appointment' hết sáng khi đang ở '/appointment/cs'.
1049
+ activeMenuPath = computed(() => resolveActiveMenuPath(this.menusByGroup(), this.currentPath()), ...(ngDevMode ? [{ debugName: "activeMenuPath" }] : []));
975
1050
  // ==========================================
976
1051
  // DATA STRUCTURES
977
1052
  // ==========================================
@@ -1020,8 +1095,18 @@ class SdSidebarV1Panel {
1020
1095
  onToggleMenuNode = (menu) => {
1021
1096
  this.treeControl.toggle(menu);
1022
1097
  };
1023
- // why: mục menu nay là role="button" + tabindex="0" nên Enter/Space phải điều hướng đúng như
1024
- // click. Lọc theo target để phím bấm trên nút ghim lồng bên trong không kéo theo điều hướng.
1098
+ menuNodeHref = (node) => {
1099
+ if (sdIsExternalHttpUrl(node.path))
1100
+ return node.path;
1101
+ return this.#router.serializeUrl(this.#router.createUrlTree([node.path.split('?')[0]], {
1102
+ queryParams: node.queryParams ?? {},
1103
+ }));
1104
+ };
1105
+ onMenuNodeClick = (event, node) => {
1106
+ event.preventDefault();
1107
+ this.navigate({ path: node.path, queryParams: node.queryParams ?? {} });
1108
+ };
1109
+ // why: giữ public keyboard handler cũ để không làm thay đổi declaration API.
1025
1110
  onMenuNodeKeydown = (event, node) => {
1026
1111
  if (event.target !== event.currentTarget)
1027
1112
  return;
@@ -1029,7 +1114,7 @@ class SdSidebarV1Panel {
1029
1114
  event.preventDefault();
1030
1115
  this.navigate({ path: node.path, queryParams: node.queryParams ?? {} });
1031
1116
  };
1032
- // why: nhánh con nay role="button" + aria-expanded Enter/Space phải gập/mở đúng như click.
1117
+ // why: giữ public keyboard handler cũ; native branch button now owns keyboard activation.
1033
1118
  onToggleMenuNodeKeydown = (event, menu) => {
1034
1119
  if (event.target !== event.currentTarget)
1035
1120
  return;
@@ -1182,7 +1267,7 @@ class SdSidebarV1Panel {
1182
1267
  }, this.#pinIconHoverTimerDelay));
1183
1268
  }
1184
1269
  }
1185
- if (!this.#menuFocusPipe.transform(this.currentPath(), menuItem)) {
1270
+ if (!this.#menuFocusPipe.transform(this.currentPath(), menuItem, this.activeMenuPath())) {
1186
1271
  const iconMenu = menuNode.querySelector('.c-menu-node-icon');
1187
1272
  const content = menuNode.querySelector('.c-menu-node-description-content');
1188
1273
  const iconExpand = menuNode.querySelector('.c-menu-node-description-icon-expand');
@@ -1223,7 +1308,7 @@ class SdSidebarV1Panel {
1223
1308
  }
1224
1309
  }
1225
1310
  }
1226
- if (!this.#menuFocusPipe.transform(this.currentPath(), menuItem)) {
1311
+ if (!this.#menuFocusPipe.transform(this.currentPath(), menuItem, this.activeMenuPath())) {
1227
1312
  const iconMenu = menuNode.querySelector('.c-menu-node-icon');
1228
1313
  const content = menuNode.querySelector('.c-menu-node-description-content');
1229
1314
  const iconExpand = menuNode.querySelector('.c-menu-node-description-icon-expand');
@@ -1261,19 +1346,27 @@ class SdSidebarV1Panel {
1261
1346
  }
1262
1347
  return node.title || '';
1263
1348
  };
1264
- #getMenuGroupByCurrentPath = (menus, menuGroup) => {
1349
+ #getMenuGroupByCurrentPath = (menus) => {
1350
+ const activePath = resolveActiveMenuPath(menus, this.currentPath());
1351
+ if (!activePath) {
1352
+ return [];
1353
+ }
1354
+ const menuGroup = this.#findMenuGroupByPath(menus, activePath);
1355
+ return menuGroup ? [menuGroup] : [];
1356
+ };
1357
+ #findMenuGroupByPath = (menus, activePath, menuGroup) => {
1265
1358
  for (const menu of menus) {
1266
- if ('path' in menu && this.#isMenuPathMatchByCurrentPath(menu.path)) {
1267
- return [menuGroup ?? menu];
1359
+ if ('path' in menu && menu.path === activePath) {
1360
+ return menuGroup ?? menu;
1268
1361
  }
1269
1362
  if ('children' in menu && menu.children?.length) {
1270
- const result = this.#getMenuGroupByCurrentPath(menu.children, menuGroup ?? menu);
1271
- if (result?.length) {
1363
+ const result = this.#findMenuGroupByPath(menu.children, activePath, menuGroup ?? menu);
1364
+ if (result) {
1272
1365
  return result;
1273
1366
  }
1274
1367
  }
1275
1368
  }
1276
- return [];
1369
+ return null;
1277
1370
  };
1278
1371
  #bindingMenuGroupByCurrentPath = (menus) => {
1279
1372
  // Chỉ bindingGroup mới khi người dùng không searchText
@@ -1341,13 +1434,17 @@ class SdSidebarV1Panel {
1341
1434
  }
1342
1435
  };
1343
1436
  #expandParentNodesByCurrentPath(menus) {
1437
+ const activePath = resolveActiveMenuPath(menus, this.currentPath());
1438
+ return activePath ? this.#expandParentNodesByPath(menus, activePath) : false;
1439
+ }
1440
+ #expandParentNodesByPath(menus, activePath) {
1344
1441
  let shouldPropagate = false;
1345
1442
  for (const menu of menus) {
1346
- if ('path' in menu && this.#isMenuPathMatchByCurrentPath(menu.path)) {
1443
+ if ('path' in menu && menu.path === activePath) {
1347
1444
  shouldPropagate = true;
1348
1445
  }
1349
1446
  if ('children' in menu && menu.children?.length) {
1350
- const childHasMatch = this.#expandParentNodesByCurrentPath(menu.children);
1447
+ const childHasMatch = this.#expandParentNodesByPath(menu.children, activePath);
1351
1448
  if (childHasMatch) {
1352
1449
  this.treeControl.expand(menu);
1353
1450
  shouldPropagate = true;
@@ -1409,16 +1506,6 @@ class SdSidebarV1Panel {
1409
1506
  }
1410
1507
  return matchedCount === needle.length;
1411
1508
  };
1412
- #isMenuPathMatchByCurrentPath = (path) => {
1413
- if (!path) {
1414
- return false;
1415
- }
1416
- if (this.currentPath() === path) {
1417
- return true;
1418
- }
1419
- return this.#normalizePath(this.currentPath()).startsWith(this.#normalizePath(path));
1420
- };
1421
- #normalizePath = (p) => (p.endsWith('/') ? p : p + '/');
1422
1509
  #closeMenu() {
1423
1510
  this.showSideBar.emit(null);
1424
1511
  }
@@ -1474,7 +1561,7 @@ class SdSidebarV1Panel {
1474
1561
  return count;
1475
1562
  };
1476
1563
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
1477
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdSidebarV1Panel, isStandalone: true, selector: "sd-sidebar-v1-panel", inputs: { isShowSidebar: { classPropertyName: "isShowSidebar", publicName: "isShowSidebar", isSignal: true, isRequired: true, transformFunction: null }, menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, userInfo: { classPropertyName: "userInfo", publicName: "userInfo", isSignal: true, isRequired: true, transformFunction: null }, sidebar: { classPropertyName: "sidebar", publicName: "sidebar", isSignal: true, isRequired: true, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandSideBar: "expandSideBar", popupUserMenuClosed: "popupUserMenuClosed", popupUserMenuOpened: "popupUserMenuOpened", showSideBar: "showSideBar" }, ngImport: i0, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | sdTranslate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <!-- why: role=button KH\u00D4NG \u0111\u01B0\u1EE3c b\u1ECDc ph\u1EA7n t\u1EED t\u01B0\u01A1ng t\u00E1c kh\u00E1c (n\u00FAt ghim b\u00EAn trong) \u2014\n AT coi c\u1EA3 c\u1EE5m l\u00E0 M\u1ED8T n\u00FAt v\u00E0 n\u00FAt ghim bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Nay\n role=button + tabindex + Enter/Space + aria-current + (click) n\u1EB1m C\u00D9NG tr\u00EAn\n ph\u1EA7n TI\u00CAU \u0110\u1EC0, n\u00FAt ghim l\u00E0 sibling \u0111\u1ED9c l\u1EADp; c\u1EA3 hai \u0111\u1EC1u focus \u0111\u01B0\u1EE3c. -->\n <div class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"navigate(node)\"\n (keydown.enter)=\"onMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onMenuNodeKeydown($event, node)\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n <!-- why: n\u00FAt ghim tr\u01B0\u1EDBc \u0111\u00E2y l\u00E0 <sd-icon (click)> \u2014 custom element kh\u00F4ng nh\u1EADn\n focus b\u00E0n ph\u00EDm v\u00E0 kh\u00F4ng c\u00F3 t\u00EAn kh\u1EA3 truy c\u1EADp, n\u00EAn kh\u00F4ng ghim \u0111\u01B0\u1EE3c b\u1EB1ng ph\u00EDm.\n \u0110\u1ED5i sang <button type=\"button\"> + aria-pressed, gi\u1ED1ng pattern \u0111\u00E3 d\u00F9ng \u1EDF\n `shared/menu-tree`. -->\n @if (_sidebar?.pin?.enabled) {\n <button\n type=\"button\"\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n [attr.aria-pressed]=\"isPinnedNode(node)\"\n [attr.aria-label]=\"\n (isPinnedNode(node) ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin')\n | sdTranslate: { title: node.title }\n \"\n (click)=\"onTogglePin($event, node)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 n\u00F3 \u1EA9n TO\u00C0N B\u1ED8 nh\u00E1nh menu c\u00F3 con (ti\u00EAu \u0111\u1EC1 nh\u00F3m l\u1EABn\n m\u1ECDi m\u1EE5c con b\u00EAn trong) kh\u1ECFi accessibility tree. -->\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <!-- why: nh\u00E1nh c\u00F3 con l\u00E0 n\u00FAt G\u1EACP/M\u1EDE th\u1EADt nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 c\u00F3 (click) v\u00E0\n kh\u00F4ng khai tr\u1EA1ng th\u00E1i. Nay role=button + tabindex + aria-expanded +\n Enter/Space. -->\n <div\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\"\n (keydown.enter)=\"onToggleMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onToggleMenuNodeKeydown($event, node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;padding:0;font-size:18px;opacity:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin:focus-visible{opacity:1;outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i2.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i2.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: SdSuffixDefDirective, selector: "[sdSuffixDef]" }, { kind: "component", type: LayoutUserComponent$1, selector: "lib-layout-user", inputs: ["isMobileOrTablet", "isMenuLock", "isShowSidebar", "userInfo"], outputs: ["menuClosed", "menuOpened", "toggleMenuLock"] }, { kind: "pipe", type: SdSafeHtmlPipe, name: "sdSafeHtml" }, { kind: "pipe", type: MenuFocusPipe, name: "menuFocus" }, { kind: "pipe", type: HighlightSearchPipe, name: "highLightSearch" }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1564
+ 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 : activeMenuPath()) ? _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 : activeMenuPath()) ? 'page' : null\"\n (click)=\"onMenuNodeClick($event, node)\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node : activeMenuPath())\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 : activeMenuPath() }\">\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 });
1478
1565
  }
1479
1566
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarV1Panel, decorators: [{
1480
1567
  type: Component,
@@ -1493,7 +1580,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1493
1580
  SdSuffixDefDirective,
1494
1581
  LayoutUserComponent$1,
1495
1582
  SdTranslatePipe,
1496
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let _userInfo = userInfo();\n@let _sidebar = sidebar();\n\n@if (_userInfo && _sidebar) {\n <div class=\"wrapper\" [style.height]=\"isMobile() ? '100dvh' : '100%'\">\n <div class=\"c-header\">\n <div class=\"c-logo\">\n <a href=\"javascript:;\" [attr.aria-label]=\"'core.module.layout.home.tab-name' | sdTranslate\" (click)=\"openHomePage()\">\n @if (logoUrl(); as _logoUrl) {\n <img alt=\"\" [src]=\"_logoUrl\" />\n } @else {\n <sd-icon name=\"apps\"></sd-icon>\n }\n </a>\n </div>\n <div class=\"c-title-menu-group\" [class.d-none]=\"!isShowSidebar()\">\n <span>{{ titleMenuGroup() || _sidebar.defaultTitle || 'Back Office' }}</span>\n </div>\n </div>\n <div class=\"c-body\">\n <div class=\"c-menu\">\n <div class=\"c-menu-group\">\n @if (_sidebar?.pin?.enabled && pinnedMenuGroup().children?.length) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, pinnedMenuGroup())\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, pinnedMenuGroup())\"\n (click)=\"expandPinnedGroup()\"\n [matTooltip]=\"'core.module.layout.sidebar.pinned' | sdTranslate\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color: isPinnedMenuGroupActive()\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor: isPinnedMenuGroupActive()\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n name=\"push_pin\"></sd-icon>\n </button>\n }\n @for (nodeMenuGroup of menus(); track nodeMenuGroup.id) {\n <button\n (mouseenter)=\"onMouseOverMenuGroupNode($event, nodeMenuGroup)\"\n (mouseleave)=\"onMouseLeaveMenuGroupNode($event, nodeMenuGroup)\"\n (click)=\"expandMenuGroup(nodeMenuGroup)\"\n [matTooltip]=\"nodeMenuGroup.tooltipTitle || nodeMenuGroup.title\"\n [matTooltipClass]=\"'c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140'\"\n [matTooltipPosition]=\"'right'\"\n style=\"padding: 0; margin: 0; border: none; background-color: transparent\">\n @if (nodeMenuGroup.iconUrl) {\n <span\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\">\n <img width=\"24px\" height=\"24px\" [src]=\"nodeMenuGroup.iconUrl\" [alt]=\"nodeMenuGroup.title\" />\n </span>\n } @else {\n <sd-icon\n class=\"c-menu-group-icon\"\n [ngStyle]=\"{\n color:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text-secondary, #44474f)',\n backgroundColor:\n idMenuGroupActive() === nodeMenuGroup?.id\n ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)'\n : 'transparent',\n }\"\n [name]=\"nodeMenuGroup.icon || 'widgets'\"></sd-icon>\n }\n </button>\n }\n </div>\n <div class=\"c-menu-tree\" [class.d-none]=\"!isShowSidebar()\" [attr.inert]=\"!isShowSidebar() ? '' : null\">\n @if (totalMenuInMenusByGroup() > 10) {\n <div style=\"padding: 0px 4px\" class=\"c-menu-tree-search\">\n <sd-input\n size=\"sm\"\n [placeholder]=\"'core.module.layout.sidebar.search' | sdTranslate\"\n [autoId]=\"'layout-v1-menu-search'\"\n [model]=\"searchText()\"\n (sdChange)=\"onFilterSearchText($event)\">\n <ng-template sdSuffixDef>\n @if (searchText()) {\n <sd-icon class=\"c-search-prefix-icon cancel\" (click)=\"onClearSearchText()\" name=\"cancel\"></sd-icon>\n } @else {\n <sd-icon class=\"c-search-prefix-icon search\" name=\"search\"></sd-icon>\n }\n </ng-template>\n </sd-input>\n </div>\n }\n <div class=\"c-menu-tree-container\">\n <mat-tree [dataSource]=\"dataSource\" [treeControl]=\"treeControl\" [style.backgroundColor]=\"'transparent'\">\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: !hasChild\">\n <li\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\"\n [ngStyle]=\"{\n backgroundColor:\n (currentPath() | menuFocus: node) ? _sidebar.brandLightColor || 'var(--sd-primary-light, #d7e3ff)' : 'transparent',\n }\">\n <!-- why: role=button KH\u00D4NG \u0111\u01B0\u1EE3c b\u1ECDc ph\u1EA7n t\u1EED t\u01B0\u01A1ng t\u00E1c kh\u00E1c (n\u00FAt ghim b\u00EAn trong) \u2014\n AT coi c\u1EA3 c\u1EE5m l\u00E0 M\u1ED8T n\u00FAt v\u00E0 n\u00FAt ghim bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Nay\n role=button + tabindex + Enter/Space + aria-current + (click) n\u1EB1m C\u00D9NG tr\u00EAn\n ph\u1EA7n TI\u00CAU \u0110\u1EC0, n\u00FAt ghim l\u00E0 sibling \u0111\u1ED9c l\u1EADp; c\u1EA3 hai \u0111\u1EC1u focus \u0111\u01B0\u1EE3c. -->\n <div class=\"c-menu-node-description\" [class.d-none]=\"!isShowSidebar()\">\n <div\n class=\"c-menu-node-description-content\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-current]=\"(currentPath() | menuFocus: node) ? 'page' : null\"\n (click)=\"navigate(node)\"\n (keydown.enter)=\"onMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onMenuNodeKeydown($event, node)\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node)\n ? _sidebar.brandColor || 'var(--sd-primary, #005cbb)'\n : 'var(--sd-text, #1a1b1f)',\n }\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n\n <!-- why: n\u00FAt ghim tr\u01B0\u1EDBc \u0111\u00E2y l\u00E0 <sd-icon (click)> \u2014 custom element kh\u00F4ng nh\u1EADn\n focus b\u00E0n ph\u00EDm v\u00E0 kh\u00F4ng c\u00F3 t\u00EAn kh\u1EA3 truy c\u1EADp, n\u00EAn kh\u00F4ng ghim \u0111\u01B0\u1EE3c b\u1EB1ng ph\u00EDm.\n \u0110\u1ED5i sang <button type=\"button\"> + aria-pressed, gi\u1ED1ng pattern \u0111\u00E3 d\u00F9ng \u1EDF\n `shared/menu-tree`. -->\n @if (_sidebar?.pin?.enabled) {\n <button\n type=\"button\"\n class=\"c-menu-node-description-icon-pin\"\n [style.opacity]=\"isPinnedNode(node) || isHoveredNode(node) ? '1' : null\"\n [style.color]=\"\n isPinnedNode(node) || isHoveredNode(node) ? sidebar().brandColor || 'var(--sd-primary, #005cbb)' : null\n \"\n [attr.aria-pressed]=\"isPinnedNode(node)\"\n [attr.aria-label]=\"\n (isPinnedNode(node) ? 'core.module.layout.menu.unpin' : 'core.module.layout.menu.pin')\n | sdTranslate: { title: node.title }\n \"\n (click)=\"onTogglePin($event, node)\">\n <sd-icon name=\"push_pin\"></sd-icon>\n </button>\n }\n </div>\n </li>\n </mat-nested-tree-node>\n\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 n\u00F3 \u1EA9n TO\u00C0N B\u1ED8 nh\u00E1nh menu c\u00F3 con (ti\u00EAu \u0111\u1EC1 nh\u00F3m l\u1EABn\n m\u1ECDi m\u1EE5c con b\u00EAn trong) kh\u1ECFi accessibility tree. -->\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n [class]=\"{ expanded: treeControl.isExpanded(node), isfocus: currentPath() | menuFocus: node }\">\n <li>\n <div\n class=\"c-menu-node\"\n (mouseenter)=\"onMouseOverMenuNode($event, node)\"\n (mouseleave)=\"onMouseLeaveMenuNode($event, node)\">\n <div class=\"d-flex align-items-center\" style=\"gap: 10px; width: 100%\">\n <!-- why: nh\u00E1nh c\u00F3 con l\u00E0 n\u00FAt G\u1EACP/M\u1EDE th\u1EADt nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 c\u00F3 (click) v\u00E0\n kh\u00F4ng khai tr\u1EA1ng th\u00E1i. Nay role=button + tabindex + aria-expanded +\n Enter/Space. -->\n <div\n [class.d-none]=\"!isShowSidebar()\"\n class=\"c-menu-node-description\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"treeControl.isExpanded(node)\"\n (click)=\"onToggleMenuNode(node)\"\n (keydown.enter)=\"onToggleMenuNodeKeydown($event, node)\"\n (keydown.space)=\"onToggleMenuNodeKeydown($event, node)\">\n <div\n class=\"c-menu-node-description-content\"\n [innerHTML]=\"node.title | highLightSearch: searchText() | sdSafeHtml\"></div>\n <sd-icon\n class=\"c-menu-node-description-icon-expand\"\n [name]=\"treeControl.isExpanded(node) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\"></sd-icon>\n </div>\n </div>\n </div>\n <ul class=\"p-0\" [class.d-none]=\"!treeControl.isExpanded(node)\">\n <ng-container matTreeNodeOutlet></ng-container>\n </ul>\n </li>\n </mat-nested-tree-node>\n </mat-tree>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"c-footer\">\n <lib-layout-user\n [userInfo]=\"_userInfo\"\n [isMobileOrTablet]=\"isMobile()\"\n (menuOpened)=\"onUserMenuOpened()\"\n (menuClosed)=\"onUserMenuClosed()\"\n [isShowSidebar]=\"isShowSidebar()\"\n (toggleMenuLock)=\"toggleMenuLock($event)\">\n </lib-layout-user>\n </div>\n\n <div class=\"c-vertical\"></div>\n </div>\n}\n", styles: ["::ng-deep .mat-mdc-tooltip-panel:has(.c-tooltip-menu-group-7a22ab15-0083-4d4c-9c0c-00a30bc8c140){pointer-events:none}:host ::ng-deep .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:16px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:32px!important}:host ::ng-deep .mat-nested-tree-node ul .mat-nested-tree-node ul .mat-nested-tree-node ul .c-menu-node-description-content{margin-left:48px!important}ul,li{margin-top:0;margin-bottom:0;list-style-type:none}.wrapper{display:flex;flex-direction:column;width:290px;background-color:#fff}.wrapper .c-header{display:flex;align-items:center;height:52px;padding:3px;gap:16px}.wrapper .c-header .c-logo{display:flex;justify-content:center;align-items:center;width:52px;height:52px}.wrapper .c-header .c-logo a{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--sd-text-secondary, #44474f)}.wrapper .c-header .c-logo img{width:32px;height:32px;object-fit:contain}.wrapper .c-header .c-title-menu-group{display:flex;align-items:center;font-size:18px;font-weight:500;flex:1}.wrapper .c-body{flex:1;overflow-y:hidden}.wrapper .c-body .c-menu{height:100%;display:flex}.wrapper .c-body .c-menu .c-menu-group{display:flex;flex-direction:column;align-items:center;flex:0 0 60px;min-width:60px;width:60px;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon{display:flex;justify-content:center;align-items:center;min-height:50px;width:52px;border-radius:8px;transition:all .15s}.wrapper .c-body .c-menu .c-menu-group .c-menu-group-icon img{height:24px;width:24px;object-fit:contain}.wrapper .c-body .c-menu .c-menu-tree{flex:1;display:flex;flex-direction:column;padding:3px 4px 3px 3px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon{cursor:pointer;color:#757575;padding:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.search{width:20px;height:20px;font-size:18px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-search .c-search-prefix-icon.cancel{width:16px;height:16px;font-size:16px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container{flex:1;overflow-y:scroll;scrollbar-width:none}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node{width:100%;display:flex;cursor:pointer;min-height:44px;padding:8px;border-radius:8px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-icon{display:flex;justify-content:center;align-items:center;height:100%}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description{flex:1;display:flex;align-items:center}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-content{flex:1;display:flex;align-items:center;flex-wrap:wrap;white-space:pre-wrap;font-size:15px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-expand{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;font-size:20px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin{display:flex;align-items:center;justify-content:start;background-color:transparent;border:none;padding:0;font-size:18px;opacity:0}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description .c-menu-node-description-icon-pin:focus-visible{opacity:1;outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.wrapper .c-body .c-menu .c-menu-tree .c-menu-tree-container .c-menu-node .c-menu-node-description[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px}.wrapper .c-footer{height:80px}.wrapper .c-vertical{position:fixed;height:100vh;left:58px;width:2px;background-color:#f8f9fa;pointer-events:none}:host ::ng-deep .c-menu-tree-search input:focus-visible{outline:1px solid var(--sd-primary, #005cbb);outline-offset:1px}\n"] }]
1583
+ ], 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 : activeMenuPath()) ? _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 : activeMenuPath()) ? 'page' : null\"\n (click)=\"onMenuNodeClick($event, node)\"\n [ngStyle]=\"{\n color:\n (currentPath() | menuFocus: node : activeMenuPath())\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 : activeMenuPath() }\">\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"] }]
1497
1584
  }], 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"] }] } });
1498
1585
 
1499
1586
  class SdSidebarV1 {
@@ -1788,27 +1875,27 @@ class SdSidebarMobileOverlay {
1788
1875
  this.titleMenuGroupChanged.emit('');
1789
1876
  }
1790
1877
  };
1791
- #getMenuGroupByCurrentPath = (menus, menuGroup) => {
1878
+ #getMenuGroupByCurrentPath = (menus) => {
1879
+ // Nhiều menu cùng khớp route thì chỉ path sát nhất được chọn, tránh nhận nhầm group của path cha
1880
+ const activePath = resolveActiveMenuPath(menus, this.currentPath());
1881
+ if (!activePath)
1882
+ return [];
1883
+ const menuGroup = this.#findMenuGroupByPath(menus, activePath);
1884
+ return menuGroup ? [menuGroup] : [];
1885
+ };
1886
+ #findMenuGroupByPath = (menus, activePath, menuGroup) => {
1792
1887
  for (const menu of menus) {
1793
- if ('path' in menu && this.#isMenuPathMatchByCurrentPath(menu.path)) {
1794
- return [menuGroup ?? menu];
1888
+ if ('path' in menu && menu.path === activePath) {
1889
+ return menuGroup ?? menu;
1795
1890
  }
1796
1891
  if ('children' in menu && menu.children?.length) {
1797
- const result = this.#getMenuGroupByCurrentPath(menu.children, menuGroup ?? menu);
1798
- if (result?.length)
1892
+ const result = this.#findMenuGroupByPath(menu.children, activePath, menuGroup ?? menu);
1893
+ if (result)
1799
1894
  return result;
1800
1895
  }
1801
1896
  }
1802
- return [];
1897
+ return null;
1803
1898
  };
1804
- #isMenuPathMatchByCurrentPath = (path) => {
1805
- if (!path)
1806
- return false;
1807
- if (this.currentPath() === path)
1808
- return true;
1809
- return this.#normalizePath(this.currentPath()).startsWith(this.#normalizePath(path));
1810
- };
1811
- #normalizePath = (p) => (p.endsWith('/') ? p : p + '/');
1812
1899
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdSidebarMobileOverlay, deps: [], target: i0.ɵɵFactoryTarget.Component });
1813
1900
  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"] }] });
1814
1901
  }
@@ -1873,6 +1960,8 @@ class SdLayoutMenuTreeComponent {
1873
1960
  const alwaysShowPin = this.pinVisibility() === 'always';
1874
1961
  const query = this.query().trim();
1875
1962
  const menus = query ? searchMenuLeaves(this.menus(), query) : this.menus();
1963
+ // Nhiều menu cùng khớp route thì chỉ path sát nhất sáng: ở '/appointment/cs' thì '/appointment' không sáng nữa
1964
+ const activeMenuPath = resolveActiveMenuPath(menus, this.activePath());
1876
1965
  const nodes = [];
1877
1966
  const append = (items, depth, ancestors) => {
1878
1967
  for (const menu of items) {
@@ -1890,7 +1979,7 @@ class SdLayoutMenuTreeComponent {
1890
1979
  depth,
1891
1980
  paddingLeft: 12 + depth * 16,
1892
1981
  isGroup,
1893
- isActive: !!path && this.#pathMatches(this.activePath(), path),
1982
+ isActive: !!path && path === activeMenuPath,
1894
1983
  isPinned,
1895
1984
  isPinVisible: alwaysShowPin || isPinned || hoveredPinKey === key,
1896
1985
  // why: template cũ nối `'Pin ' + node.title` — chuỗi tiếng Anh cứng và ép trật tự
@@ -1936,10 +2025,6 @@ class SdLayoutMenuTreeComponent {
1936
2025
  clearTimeout(this.#pinHoverTimerId);
1937
2026
  this.#pinHoverTimerId = undefined;
1938
2027
  }
1939
- #pathMatches(currentPath, menuPath) {
1940
- const normalize = (path) => (path.endsWith('/') ? path : `${path}/`);
1941
- return currentPath === menuPath || normalize(currentPath).startsWith(normalize(menuPath));
1942
- }
1943
2028
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdLayoutMenuTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1944
2029
  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 });
1945
2030
  }
@@ -2409,28 +2494,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2409
2494
  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"] }]
2410
2495
  }], propDecorators: { menusInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: false }] }] } });
2411
2496
 
2412
- /**
2413
- * Resolve a translated tab name for `@SdTabComponent`.
2414
- *
2415
- * WHY not I18nService: the decorator runs at module-evaluation time, before
2416
- * Angular's DI exists, so the service cannot be injected. We read the language
2417
- * the app persisted and look the key up in the static catalog instead.
2418
- */
2419
- function resolveTabName(key) {
2420
- const lang = (() => {
2421
- try {
2422
- const stored = localStorage.getItem(I18N_STORAGE_KEY);
2423
- if (stored)
2424
- return stored;
2425
- }
2426
- catch {
2427
- // localStorage can throw (private mode, SSR shim) — fall back below.
2428
- }
2429
- return 'vi';
2430
- })();
2431
- return I18N_MESSAGES[lang]?.[key] ?? I18N_MESSAGES.vi[key] ?? key;
2432
- }
2433
-
2434
2497
  // End
2435
2498
  let HomePageComponent = class HomePageComponent {
2436
2499
  // ==========================================
@@ -2763,5 +2826,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
2763
2826
  * Generated bundle index. Do not edit.
2764
2827
  */
2765
2828
 
2766
- export { DEFAULT_LAYOUT_MOBILE_BREAKPOINT, ForbiddenModule, HighlightSearchPipe, HomeModule, MenuFocusPipe, MenuPipe, NotFoundModule, SD_LAYOUT_CONFIGURATION, SD_LAYOUT_DEMO_FALLBACK, SD_LAYOUT_STORAGE_NAMESPACE, SD_LAYOUT_VIEWPORT, SdLayoutComponent, SdLayoutMenuTreeComponent, SdLayoutModule, SdLayoutNavigationStateService, SdLayoutResponsiveService, SdLayoutService, SdLayoutStorageService, SdLayoutUserMenuComponent, SdPageComponent, SdSidebarMobileOverlay, SdSidebarMobileV1, SdSidebarMobileV2, SdSidebarMobileV3, SdSidebarV2, SdSidebarV3, flattenMenuLeaves, getMenuStableKey, normalizeLayoutMobileBreakpoint, normalizeSidebarConfiguration, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, resolveTabName, searchMenuLeaves, selectPrimaryMenuGroups };
2829
+ 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, collectMatchedMenuPaths, containsMenuPath, flattenMenuLeaves, getMenuStableKey, isMenuPathMatch, normalizeLayoutMobileBreakpoint, normalizeMenuPath, normalizeSidebarConfiguration, resolveActiveMenuPath, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, resolveTabName, searchMenuLeaves, selectPrimaryMenuGroups };
2767
2830
  //# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map