@sdcorejs/angular 19.1.5 → 19.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +2 -4
  2. package/components/preview/src/preview-pdf/pdf-worker-inline.generated.d.ts +7 -0
  3. package/components/table/src/services/table-export/table-export.service.d.ts +3 -1
  4. package/components/table/src/table.component.d.ts +1 -1
  5. package/fesm2022/sdcorejs-angular-components-preview.mjs +31 -2
  6. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  7. package/fesm2022/sdcorejs-angular-components-table.mjs +18 -3
  8. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  9. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +3 -4
  10. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  11. package/fesm2022/sdcorejs-angular-forms-date.mjs +122 -27
  12. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  13. package/fesm2022/sdcorejs-angular-forms-models.mjs +54 -2
  14. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  15. package/fesm2022/sdcorejs-angular-i18n.mjs +20 -0
  16. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  17. package/fesm2022/sdcorejs-angular-modules-layout.mjs +110 -25
  18. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  19. package/forms/date/src/date-input.util.d.ts +18 -0
  20. package/forms/date/src/date.component.d.ts +8 -1
  21. package/forms/models/index.d.ts +1 -0
  22. package/forms/models/src/sd-strict-date-adapter.d.ts +26 -0
  23. package/i18n/src/en.d.ts +4 -0
  24. package/modules/layout/index.d.ts +1 -0
  25. package/modules/layout/utils/index.d.ts +1 -0
  26. package/modules/layout/utils/tab-name.util.d.ts +8 -0
  27. package/package.json +13 -5
@@ -515,19 +515,61 @@ class HighlightSearchPipe {
515
515
  return value;
516
516
  }
517
517
  value = value?.trim() || '';
518
- const regex = new RegExp(StringUtilities.changeAliasLowerCase(keyword), 'gi'); //'gi' for case insensitive and can use 'g' if you want the search to be case sensitive.
518
+ const aliasKeyword = StringUtilities.changeAliasLowerCase(keyword);
519
519
  const aliasLowerCaseStr = StringUtilities.changeAliasLowerCase(value);
520
+ // Ưu tiên highlight theo chuỗi con liền nhau (khớp trực tiếp với tiêu đề/route)
521
+ const substringHighlight = this.#highlightSubstring(value, aliasLowerCaseStr, aliasKeyword, color);
522
+ if (substringHighlight !== null) {
523
+ return substringHighlight;
524
+ }
525
+ // Không có chuỗi con nào khớp -> thử highlight theo ký tự đầu (viết tắt), vd "sp" -> "Sản phẩm"
526
+ return this.#highlightOrderedInitials(value, aliasLowerCaseStr, aliasKeyword, color) ?? value;
527
+ }
528
+ #highlightSubstring = (value, aliasLowerCaseStr, aliasKeyword, color) => {
529
+ const regex = new RegExp(aliasKeyword, 'gi'); //'gi' for case insensitive and can use 'g' if you want the search to be case sensitive.
520
530
  const strs = [];
521
531
  let previousOffset = 0;
532
+ let hasMatch = false;
522
533
  aliasLowerCaseStr.replace(regex, (_, offset) => {
534
+ hasMatch = true;
523
535
  strs.push(value.substring(previousOffset, offset));
524
- strs.push(`<mark style="background-color: ${color}">${value.substring(offset, offset + keyword.length)}</mark>`);
525
- previousOffset = offset + keyword.length;
526
- return `<mark style="background-color: ${color}">${value.substring(offset, offset + keyword.length)}</mark>`;
536
+ strs.push(`<mark style="background-color: ${color}">${value.substring(offset, offset + aliasKeyword.length)}</mark>`);
537
+ previousOffset = offset + aliasKeyword.length;
538
+ return `<mark style="background-color: ${color}">${value.substring(offset, offset + aliasKeyword.length)}</mark>`;
527
539
  });
540
+ if (!hasMatch) {
541
+ return null;
542
+ }
528
543
  strs.push(value.substring(previousOffset, value.length));
529
544
  return strs.join('');
530
- }
545
+ };
546
+ #highlightOrderedInitials = (value, aliasLowerCaseStr, aliasKeyword, color) => {
547
+ const wordRegex = /\S+/g;
548
+ const matchedPositions = [];
549
+ let matchedCount = 0;
550
+ let wordMatch;
551
+ while ((wordMatch = wordRegex.exec(aliasLowerCaseStr))) {
552
+ if (matchedCount >= aliasKeyword.length) {
553
+ break;
554
+ }
555
+ if (wordMatch[0][0] === aliasKeyword[matchedCount]) {
556
+ matchedPositions.push(wordMatch.index);
557
+ matchedCount++;
558
+ }
559
+ }
560
+ if (matchedCount !== aliasKeyword.length) {
561
+ return null;
562
+ }
563
+ let result = '';
564
+ let cursor = 0;
565
+ for (const position of matchedPositions) {
566
+ result += value.substring(cursor, position);
567
+ result += `<mark style="background-color: ${color}">${value.charAt(position)}</mark>`;
568
+ cursor = position + 1;
569
+ }
570
+ result += value.substring(cursor);
571
+ return result;
572
+ };
531
573
  #normalizeValue = (keyword) => {
532
574
  let str = keyword?.toString() ?? '';
533
575
  /* eslint-disable no-useless-escape */
@@ -1151,7 +1193,10 @@ class SidebarComponent {
1151
1193
  const result = [];
1152
1194
  for (const menu of menus) {
1153
1195
  const aliasTitle = StringUtilities.changeAliasLowerCase(menu.title);
1154
- if (aliasTitle.includes(aliasSearchText)) {
1196
+ // why: hỗ trợ search theo route (path) và search theo ký tự đầu (viết tắt) bên cạnh search theo tiêu đề
1197
+ const matchByRoutePath = 'path' in menu && !!menu.path && menu.path.toLowerCase().includes(aliasSearchText);
1198
+ const matchByInitials = this.#matchesTitleInitials(aliasTitle, aliasSearchText);
1199
+ if (aliasTitle.includes(aliasSearchText) || matchByRoutePath || matchByInitials) {
1155
1200
  result.push(menu);
1156
1201
  continue;
1157
1202
  }
@@ -1165,6 +1210,23 @@ class SidebarComponent {
1165
1210
  }
1166
1211
  return result;
1167
1212
  };
1213
+ #matchesTitleInitials = (aliasTitle, aliasSearchText) => {
1214
+ const initials = aliasTitle
1215
+ .split(' ')
1216
+ .filter(word => word)
1217
+ .map(word => word[0])
1218
+ .join('');
1219
+ return this.#isOrderedSubsequence(aliasSearchText, initials);
1220
+ };
1221
+ #isOrderedSubsequence = (needle, haystack) => {
1222
+ let matchedCount = 0;
1223
+ for (const char of haystack) {
1224
+ if (char === needle[matchedCount]) {
1225
+ matchedCount++;
1226
+ }
1227
+ }
1228
+ return matchedCount === needle.length;
1229
+ };
1168
1230
  #isMenuPathMatchByCurrentPath = (path) => {
1169
1231
  if (!path) {
1170
1232
  return false;
@@ -2147,6 +2209,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
2147
2209
  ], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #projectedContent>\n <ng-content></ng-content>\n</ng-template>\n\n@let _userInfo = userInfo();\n@let _sidebarV1 = sidebarV1();\n@let _sidebarV2 = sidebarV2();\n@let _sidebarV3 = sidebarV3();\n\n@if (_userInfo && _sidebarV1) {\n @if (!isMobile()) {\n <sidebar-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\" [isMobile]=\"false\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v1>\n } @else {\n <sidebar-mobile-v1 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV1\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v1>\n }\n}\n\n@if (_userInfo && _sidebarV3) {\n @if (!isMobile()) {\n <sidebar-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v3>\n } @else {\n <sidebar-mobile-v3 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV3\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v3>\n }\n}\n\n@if (_userInfo && _sidebarV2) {\n @if (!isMobile()) {\n <sidebar-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-v2>\n } @else {\n <sidebar-mobile-v2 [menus]=\"menus()\" [userInfo]=\"_userInfo\" [sidebar]=\"_sidebarV2\">\n <ng-container [ngTemplateOutlet]=\"projectedContent\"></ng-container>\n </sidebar-mobile-v2>\n }\n}\n", styles: [":host{display:block}\n"] }]
2148
2210
  }] });
2149
2211
 
2212
+ /**
2213
+ * Resolve a translated tab name for `@SdTabComponent`.
2214
+ *
2215
+ * WHY not I18nService: the decorator runs at module-evaluation time, before
2216
+ * Angular's DI exists, so the service cannot be injected. We read the language
2217
+ * the app persisted and look the key up in the static catalog instead.
2218
+ */
2219
+ function resolveTabName(key) {
2220
+ const lang = (() => {
2221
+ try {
2222
+ const stored = localStorage.getItem(I18N_STORAGE_KEY);
2223
+ if (stored)
2224
+ return stored;
2225
+ }
2226
+ catch {
2227
+ // localStorage can throw (private mode, SSR shim) — fall back below.
2228
+ }
2229
+ return 'vi';
2230
+ })();
2231
+ return I18N_MESSAGES[lang]?.[key] ?? I18N_MESSAGES.vi[key] ?? key;
2232
+ }
2233
+
2150
2234
  // End
2151
2235
  let HomePageComponent = class HomePageComponent {
2152
2236
  // ==========================================
@@ -2209,22 +2293,7 @@ let HomePageComponent = class HomePageComponent {
2209
2293
  HomePageComponent = __decorate([
2210
2294
  SdTabComponent({
2211
2295
  component: HomePageComponent,
2212
- // WHY: decorator chạy trước Angular DI, nên không inject được I18nService.
2213
- // Đọc ngôn ngữ hiện tại trực tiếp từ localStorage + I18N_MESSAGES để có giá trị đã dịch.
2214
- name: () => {
2215
- const lang = (() => {
2216
- try {
2217
- const stored = localStorage.getItem(I18N_STORAGE_KEY);
2218
- if (stored)
2219
- return stored;
2220
- }
2221
- catch {
2222
- /* ignore */
2223
- }
2224
- return 'vi';
2225
- })();
2226
- return I18N_MESSAGES[lang]?.['core.module.layout.home.tab-name'] ?? I18N_MESSAGES.vi['core.module.layout.home.tab-name'];
2227
- },
2296
+ name: () => resolveTabName('core.module.layout.home.tab-name'),
2228
2297
  icon: 'home',
2229
2298
  color: 'primary',
2230
2299
  })
@@ -2332,6 +2401,14 @@ let RootComponent$1 = class RootComponent {
2332
2401
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RootComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2333
2402
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | translate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"not-found\" class=\"sd-image-not-found\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.not-found.title' | translate }}</span>\n <span class=\"T16R text-secondary mt-4\">\n {{ 'core.module.layout.not-found.message' | translate }}\n </span>\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.not-found.back' | translate\" prefixIcon=\"arrow_back\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdPageComponent, selector: "sd-page", inputs: ["title", "description", "noHeader"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2334
2403
  };
2404
+ RootComponent$1 = __decorate([
2405
+ SdTabComponent({
2406
+ component: RootComponent$1,
2407
+ name: () => resolveTabName('core.module.layout.not-found.tab-name'),
2408
+ icon: 'search_off',
2409
+ color: 'warning',
2410
+ })
2411
+ ], RootComponent$1);
2335
2412
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RootComponent$1, decorators: [{
2336
2413
  type: Component,
2337
2414
  args: [{ imports: [SdButton, SdPageComponent, TranslatePipe], template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | translate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"not-found\" class=\"sd-image-not-found\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.not-found.title' | translate }}</span>\n <span class=\"T16R text-secondary mt-4\">\n {{ 'core.module.layout.not-found.message' | translate }}\n </span>\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.not-found.back' | translate\" prefixIcon=\"arrow_back\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"] }]
@@ -2358,7 +2435,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
2358
2435
  });
2359
2436
 
2360
2437
  // End
2361
- class RootComponent {
2438
+ let RootComponent = class RootComponent {
2362
2439
  // ==========================================
2363
2440
  // INJECT SERVICES (Modern Angular)
2364
2441
  // ==========================================
@@ -2393,7 +2470,15 @@ class RootComponent {
2393
2470
  }
2394
2471
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RootComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2395
2472
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: RootComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | translate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"forbidden\" class=\"sd-image-forbidden\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.forbidden.title' | translate }}</span>\n <span class=\"T14R text-secondary mt-4\"\n >{{ 'core.module.layout.forbidden.message' | translate }}</span\n >\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.forbidden.back-home' | translate\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdPageComponent, selector: "sd-page", inputs: ["title", "description", "noHeader"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2396
- }
2473
+ };
2474
+ RootComponent = __decorate([
2475
+ SdTabComponent({
2476
+ component: RootComponent,
2477
+ name: () => resolveTabName('core.module.layout.forbidden.tab-name'),
2478
+ icon: 'block',
2479
+ color: 'error',
2480
+ })
2481
+ ], RootComponent);
2397
2482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RootComponent, decorators: [{
2398
2483
  type: Component,
2399
2484
  args: [{ imports: [SdButton, SdPageComponent, TranslatePipe], template: "@let _userInfo = userInfo();\n<sd-page>\n <div class=\"d-flex flex-column\" headerLeft>\n <div class=\"font-weight-medium fs-20\">{{ 'core.module.layout.greeting.hello' | translate: { name: _userInfo?.fullName ?? _userInfo?.email ?? _userInfo?.username ?? '' } }}</div>\n <span class=\"T12R text-secondary\">{{ todayInfo() }}</span>\n </div>\n <div class=\"h-full d-flex flex-column align-items-center\">\n <div class=\"h-full d-flex flex-column align-items-center justify-content-center text-center wrapper\">\n <img alt=\"forbidden\" class=\"sd-image-forbidden\" />\n <span class=\"T16M mt-24\">{{ 'core.module.layout.forbidden.title' | translate }}</span>\n <span class=\"T14R text-secondary mt-4\"\n >{{ 'core.module.layout.forbidden.message' | translate }}</span\n >\n <sd-button class=\"mt-16\" type=\"fill\" color=\"primary\" (click)=\"reload()\" [title]=\"'core.module.layout.forbidden.back-home' | translate\"> </sd-button>\n </div>\n </div>\n</sd-page>\n", styles: [".wrapper{width:300px}\n"] }]
@@ -2457,5 +2542,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
2457
2542
  * Generated bundle index. Do not edit.
2458
2543
  */
2459
2544
 
2460
- export { DEFAULT_LAYOUT_MOBILE_BREAKPOINT, ForbiddenModule, HighlightSearchPipe, HomeModule, MenuFocusPipe, MenuPipe, NotFoundModule, SD_LAYOUT_CONFIGURATION, SD_LAYOUT_VIEWPORT, SdLayoutComponent, SdLayoutMenuTreeComponent, SdLayoutModule, SdLayoutNavigationStateService, SdLayoutResponsiveService, SdLayoutService, SdLayoutStorageService, SdLayoutUserMenuComponent, SdPageComponent, SidebarMobileOverlayComponent, SidebarMobileV1Component, SidebarMobileV2Component, SidebarMobileV3Component, SidebarV2Component, SidebarV3Component, flattenMenuLeaves, getMenuStableKey, normalizeLayoutMobileBreakpoint, normalizeSidebarConfiguration, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, searchMenuLeaves, selectPrimaryMenuGroups };
2545
+ export { DEFAULT_LAYOUT_MOBILE_BREAKPOINT, ForbiddenModule, HighlightSearchPipe, HomeModule, MenuFocusPipe, MenuPipe, NotFoundModule, SD_LAYOUT_CONFIGURATION, SD_LAYOUT_VIEWPORT, SdLayoutComponent, SdLayoutMenuTreeComponent, SdLayoutModule, SdLayoutNavigationStateService, SdLayoutResponsiveService, SdLayoutService, SdLayoutStorageService, SdLayoutUserMenuComponent, SdPageComponent, SidebarMobileOverlayComponent, SidebarMobileV1Component, SidebarMobileV2Component, SidebarMobileV3Component, SidebarV2Component, SidebarV3Component, flattenMenuLeaves, getMenuStableKey, normalizeLayoutMobileBreakpoint, normalizeSidebarConfiguration, resolveMenuKeys, resolveSidebarV2Interaction, resolveSidebarV3Recent, resolveTabName, searchMenuLeaves, selectPrimaryMenuGroups };
2461
2546
  //# sourceMappingURL=sdcorejs-angular-modules-layout.mjs.map