@sdcorejs/angular 20.1.5 → 20.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +12 -4
  2. package/assets/scss/core/menu.scss +18 -0
  3. package/assets/scss/core/utilities/_misc.scss +15 -0
  4. package/assets/scss/sd-core.scss +1 -0
  5. package/components/anchor/index.d.ts +3 -1
  6. package/components/badge/index.d.ts +1 -0
  7. package/components/ckeditor-styles/index.d.ts +1 -1
  8. package/components/form-generic/index.d.ts +44 -33
  9. package/components/import-excel/index.d.ts +1 -0
  10. package/components/index.d.ts +6 -1
  11. package/components/mini-editor/index.d.ts +1 -1
  12. package/components/modal/index.d.ts +3 -2
  13. package/components/modal-resizable/index.d.ts +92 -0
  14. package/components/org-chart/index.d.ts +6 -0
  15. package/components/preview/index.d.ts +1 -0
  16. package/components/query-bar/index.d.ts +55 -17
  17. package/components/query-builder/index.d.ts +150 -98
  18. package/components/section/index.d.ts +3 -0
  19. package/components/tab-router/index.d.ts +19 -19
  20. package/components/table/index.d.ts +252 -110
  21. package/components/tree/index.d.ts +48 -30
  22. package/components/upload-file/index.d.ts +6 -3
  23. package/configurations/index.d.ts +1 -2
  24. package/fesm2022/sdcorejs-angular-components-anchor.mjs +37 -13
  25. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +2 -2
  27. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-components-badge.mjs +15 -2
  29. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
  31. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs +1 -1
  33. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-code-editor.mjs +3 -3
  35. package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-data-state.mjs +3 -3
  37. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-editor.mjs +2 -2
  39. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  40. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +324 -192
  41. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-history.mjs +4 -4
  43. package/fesm2022/sdcorejs-angular-components-history.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +13 -5
  45. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-inform.mjs +3 -3
  47. package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +164 -8
  49. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-modal.mjs +30 -18
  51. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-org-chart.mjs +60 -13
  53. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-preview.mjs +58 -19
  55. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +192 -30
  57. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +187 -127
  59. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-section.mjs +16 -2
  61. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +83 -24
  63. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-components-splitter.mjs +57 -21
  65. package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-components-stepper.mjs +2 -2
  67. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +68 -20
  69. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-components-tab.mjs +3 -2
  71. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-components-table.mjs +557 -255
  73. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-components-tree.mjs +80 -17
  75. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +48 -13
  77. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-components.mjs +6 -1
  79. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
  81. package/fesm2022/sdcorejs-angular-directives.mjs +45 -8
  82. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  83. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +40 -29
  84. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  85. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +5 -5
  86. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  87. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +67 -50
  88. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  89. package/fesm2022/sdcorejs-angular-forms-chip.mjs +57 -43
  90. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  91. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +234 -64
  92. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  93. package/fesm2022/sdcorejs-angular-forms-date.mjs +209 -42
  94. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  95. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +131 -27
  96. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  97. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +40 -16
  98. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -1
  99. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +16 -5
  100. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  101. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +24 -9
  102. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  103. package/fesm2022/sdcorejs-angular-forms-input.mjs +58 -10
  104. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  105. package/fesm2022/sdcorejs-angular-forms-models.mjs +279 -6
  106. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  107. package/fesm2022/sdcorejs-angular-forms-radio.mjs +4 -4
  108. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  109. package/fesm2022/sdcorejs-angular-forms-select.mjs +136 -38
  110. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  111. package/fesm2022/sdcorejs-angular-forms-switch.mjs +18 -3
  112. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  113. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +51 -62
  114. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  115. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +57 -12
  116. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -1
  117. package/fesm2022/sdcorejs-angular-forms-time.mjs +16 -5
  118. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -1
  119. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +100 -9
  120. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -1
  121. package/fesm2022/sdcorejs-angular-forms.mjs +91 -46
  122. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  123. package/fesm2022/sdcorejs-angular-handlers.mjs +18 -3
  124. package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
  125. package/fesm2022/sdcorejs-angular-i18n.mjs +510 -68
  126. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  127. package/fesm2022/sdcorejs-angular-interceptors.mjs +87 -29
  128. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  129. package/fesm2022/sdcorejs-angular-modules-auth.mjs +25 -15
  130. package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
  131. package/fesm2022/sdcorejs-angular-modules-icon.mjs +2 -2
  132. package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
  133. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +32 -3
  134. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
  135. package/fesm2022/sdcorejs-angular-modules-layout.mjs +441 -135
  136. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  137. package/fesm2022/sdcorejs-angular-modules-permission.mjs +177 -40
  138. package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
  139. package/fesm2022/sdcorejs-angular-pipes.mjs +62 -28
  140. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  141. package/fesm2022/sdcorejs-angular-services-api.mjs +123 -13
  142. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  143. package/fesm2022/sdcorejs-angular-services-cache.mjs +53 -3
  144. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  145. package/fesm2022/sdcorejs-angular-services-confirm.mjs +25 -73
  146. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  147. package/fesm2022/sdcorejs-angular-services-excel.mjs +12 -10
  148. package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
  149. package/fesm2022/sdcorejs-angular-services-loading.mjs +75 -84
  150. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  151. package/fesm2022/sdcorejs-angular-services-notify.mjs +46 -5
  152. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  153. package/fesm2022/sdcorejs-angular-services-storage.mjs +4 -0
  154. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  155. package/fesm2022/sdcorejs-angular-services.mjs +0 -1
  156. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  157. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +225 -80
  158. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
  159. package/fesm2022/sdcorejs-angular-utilities-models.mjs +1 -63
  160. package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
  161. package/fesm2022/sdcorejs-angular.mjs +17 -10
  162. package/fesm2022/sdcorejs-angular.mjs.map +1 -1
  163. package/forms/autocomplete/index.d.ts +5 -2
  164. package/forms/chip-calendar/index.d.ts +10 -0
  165. package/forms/date/index.d.ts +21 -3
  166. package/forms/date-range/index.d.ts +14 -2
  167. package/forms/datetime/index.d.ts +13 -2
  168. package/forms/entity-picker/index.d.ts +9 -3
  169. package/forms/index.d.ts +22 -13
  170. package/forms/input/index.d.ts +3 -0
  171. package/forms/input-color/index.d.ts +4 -0
  172. package/forms/input-number/index.d.ts +3 -0
  173. package/forms/models/index.d.ts +146 -5
  174. package/forms/select/index.d.ts +27 -4
  175. package/forms/switch/index.d.ts +7 -1
  176. package/forms/textarea/index.d.ts +5 -3
  177. package/forms/time-range/index.d.ts +22 -1
  178. package/forms/tree-select/index.d.ts +36 -5
  179. package/i18n/index.d.ts +99 -16
  180. package/index.d.ts +11 -2
  181. package/interceptors/index.d.ts +13 -3
  182. package/modules/auth/index.d.ts +1 -1
  183. package/modules/keycloak/index.d.ts +1 -0
  184. package/modules/layout/index.d.ts +103 -28
  185. package/modules/permission/index.d.ts +66 -4
  186. package/package.json +40 -25
  187. package/pipes/index.d.ts +27 -5
  188. package/services/api/index.d.ts +21 -1
  189. package/services/cache/index.d.ts +11 -1
  190. package/services/index.d.ts +0 -1
  191. package/services/notify/index.d.ts +1 -1
  192. package/utilities/extensions/index.d.ts +83 -61
  193. package/utilities/models/index.d.ts +2 -76
  194. package/components/base/index.d.ts +0 -11
  195. package/components/document-builder/index.d.ts +0 -252
  196. package/fesm2022/sdcorejs-angular-components-base.mjs +0 -23
  197. package/fesm2022/sdcorejs-angular-components-base.mjs.map +0 -1
  198. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +0 -5038
  199. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +0 -1
  200. package/fesm2022/sdcorejs-angular-models.mjs +0 -6
  201. package/fesm2022/sdcorejs-angular-models.mjs.map +0 -1
  202. package/fesm2022/sdcorejs-angular-services-docx.mjs +0 -291
  203. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +0 -1
  204. package/fesm2022/sdcorejs-angular-services-license.mjs +0 -97
  205. package/fesm2022/sdcorejs-angular-services-license.mjs.map +0 -1
  206. package/models/index.d.ts +0 -2
  207. package/services/docx/index.d.ts +0 -27
  208. package/services/license/index.d.ts +0 -12
@@ -1,41 +1,59 @@
1
- import { HttpClient } from '@angular/common/http';
1
+ import { HttpBackend, HttpRequest, HttpHeaders, HttpResponse } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, Injector, Injectable } from '@angular/core';
3
+ import { InjectionToken, inject, DestroyRef, Injectable } from '@angular/core';
4
4
  import { MatSnackBar } from '@angular/material/snack-bar';
5
5
  import { I18nService } from '@sdcorejs/angular/i18n';
6
- import { throwError } from 'rxjs';
7
- import { catchError, switchMap } from 'rxjs/operators';
6
+ import { throwError, of } from 'rxjs';
7
+ import { catchError, switchMap, filter, map } from 'rxjs/operators';
8
8
  import { SdAuthService } from '@sdcorejs/angular/modules';
9
9
 
10
+ /**
11
+ * URL dùng để dò kết nối khi một request trả về status 0.
12
+ *
13
+ * why: bản cũ hard-code `https://jsonplaceholder.typicode.com/todos/1`, không override được. Mọi
14
+ * app tiêu dùng thư viện đều ping một bên thứ ba lạ mỗi 3 giây khi mất mạng, và vì probe đi qua
15
+ * NGUYÊN chuỗi interceptor của app nên bất kỳ interceptor nào gắn header auth vô điều kiện đều
16
+ * rò token ra ngoài origin. Mặc định giờ là một đường dẫn same-origin; app đổi bằng cách provide
17
+ * lại token này (nên trỏ tới endpoint tĩnh, rẻ, không cần auth).
18
+ */
19
+ const SD_NO_INTERNET_PROBE_URL = new InjectionToken('sd.no-internet.probe-url', {
20
+ providedIn: 'root',
21
+ factory: () => '/favicon.ico',
22
+ });
10
23
  class SdNoInternetInterceptor {
11
24
  snackBar = inject(MatSnackBar);
12
- injector = inject(Injector);
13
25
  #i18n = inject(I18nService);
26
+ // why: HttpBackend là tầng cuối, KHÔNG đi qua chuỗi interceptor — probe vì thế không kéo theo
27
+ // header do interceptor của app gắn, và cũng không tự đệ quy vào chính interceptor này.
28
+ // Dùng backend cũng loại luôn lý do phải lazy-resolve HttpClient qua Injector (circular DI).
29
+ #backend = inject(HttpBackend);
30
+ #probeUrl = inject(SD_NO_INTERNET_PROBE_URL);
31
+ #destroyRef = inject(DestroyRef);
14
32
  // Trạng thái offline để tránh spam request check hoặc hiển thị nhiều snackbar
15
33
  #isOffline = false;
16
34
  // Giữ tham chiếu để có thể đóng/thao tác snackbar
17
35
  #snackBarRef = null;
18
36
  // Giữ tham chiếu interval để clear khi có mạng lại
19
37
  #pollInterval = null;
20
- // Lazy load HttpClient
21
- #http = null;
22
- // Endpoint kiểm tra mạng (Nên dùng 1 file tĩnh nhẹ hoặc API ping public uy tín)
23
- #healthCheckUrl = 'https://jsonplaceholder.typicode.com/todos/1';
38
+ // Giữ tham chiếu request probe đang bay để huỷ được khi dừng polling
39
+ #pollSubscription = null;
24
40
  // Thời gian lặp lại việc kiểm tra mạng (ms)
25
41
  #checkIntervalDuration = 3000;
26
- constructor() { }
42
+ constructor() {
43
+ // why: interceptor sống ở root injector nhưng vòng polling không có teardown nào — nó chỉ
44
+ // dừng khi mạng phục hồi. App/microfrontend bị destroy giữa lúc offline sẽ bỏ lại một timer
45
+ // 3s cùng request HTTP của nó chạy mãi.
46
+ this.#destroyRef.onDestroy(() => this.#stopPolling());
47
+ }
27
48
  intercept(request, next) {
28
49
  return next.handle(request).pipe(catchError((error) => {
29
50
  // --- TRƯỜNG HỢP 1: Lỗi mất kết nối (Status 0) ---
30
51
  if (error.status === 0 && !this.#isOffline) {
31
52
  this.#isOffline = true;
32
- // Lazy load HttpClient để tránh lỗi Circular Dependency
33
- if (!this.#http) {
34
- this.#http = this.injector.get(HttpClient);
35
- }
36
- // Gọi thử một API public để xác minh xem là Mất mạng thật hay do CORS/Server chặn
37
- return this.#http.get(this.#healthCheckUrl).pipe(
38
- // 1. Ưu tiên bắt lỗi kết nối trước (Đây là logic MẤT MẠNG THẬT)
53
+ // Gọi thử probe để xác minh xem là Mất mạng thật hay do CORS/SSL/Server chặn
54
+ return this.#probe().pipe(
55
+ // 1. Ưu tiên bắt lỗi kết nối trước (MẤT MẠNG THẬT — probe cũng không mở nổi kết nối,
56
+ // tức status 0; mọi status HTTP khác đã được #probe quy về "có mạng")
39
57
  catchError(_checkError => {
40
58
  // Hiển thị thông báo và bắt đầu polling chờ mạng
41
59
  this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.offline'), { isSticky: true });
@@ -69,6 +87,28 @@ class SdNoInternetInterceptor {
69
87
  return throwError(() => error);
70
88
  }));
71
89
  }
90
+ /**
91
+ * Bắn một request dò kết nối đi thẳng qua HttpBackend.
92
+ *
93
+ * Phát `true` khi CÓ mạng; chỉ ném lỗi khi thật sự không mở được kết nối (`status === 0`).
94
+ */
95
+ #probe = () => {
96
+ const request = new HttpRequest('GET', this.#probeUrl, {
97
+ responseType: 'text',
98
+ // why: probe mà đọc được từ HTTP cache thì lúc offline vẫn "thành công" và interceptor sẽ
99
+ // kết luận nhầm là đã có mạng. no-cache buộc trình duyệt phải đi hỏi lại server.
100
+ headers: new HttpHeaders({ 'Cache-Control': 'no-cache', Pragma: 'no-cache' }),
101
+ });
102
+ // why: HttpBackend phát HttpEvent thô (có thể gồm sent/progress), chỉ HttpResponse mới là
103
+ // tín hiệu "đã có mạng".
104
+ return this.#backend.handle(request).pipe(filter((event) => event instanceof HttpResponse), map(() => true),
105
+ // why: HttpBackend ném HttpErrorResponse cho MỌI status không phải 2xx, nên coi "probe lỗi"
106
+ // = "mất mạng thật" là sai. Một `404` rất phổ biến (app xoá favicon mặc định của CLI, static
107
+ // host trả 404/403 cho đường dẫn không tồn tại) và chính nó ĐÃ chứng minh là có mạng — nhưng
108
+ // vẫn bị phân loại thành offline, dựng snackbar offline dính cứng cùng vòng poll 3 giây không
109
+ // bao giờ thoát được. Chỉ status 0 (không thiết lập nổi kết nối) mới là offline.
110
+ catchError((error) => (error.status === 0 ? throwError(() => error) : of(true))));
111
+ };
72
112
  /**
73
113
  * Hiển thị Snackbar thông báo trạng thái mạng
74
114
  */
@@ -94,23 +134,19 @@ class SdNoInternetInterceptor {
94
134
  */
95
135
  #startPolling = () => {
96
136
  this.#stopPolling(); // Clear cũ nếu có
97
- // @i18n-ignore — dev console log
98
137
  this.#pollInterval = setInterval(() => {
99
- if (!this.#http) {
100
- this.#http = this.injector.get(HttpClient);
101
- }
102
138
  // Check nhẹ
103
- this.#http.get(this.#healthCheckUrl).subscribe({
139
+ this.#pollSubscription?.unsubscribe();
140
+ this.#pollSubscription = this.#probe().subscribe({
104
141
  next: () => {
105
- // --> ĐÃ CÓ MẠNG LẠI
106
- // @i18n-ignore — dev console log
142
+ // --> ĐÃ CÓ MẠNG LẠI (2xx, hoặc bất kỳ status HTTP nào — xem #probe)
107
143
  this.#stopPolling();
108
144
  this.#isOffline = false;
109
145
  // Thông báo thành công (tự tắt sau 5s)
110
146
  this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.restored'), { duration: 5000, isSticky: false });
111
147
  },
112
148
  error: () => {
113
- // --> VẪN MẤT MẠNG: Không làm gì cả, chờ lần check tiếp theo
149
+ // --> VẪN MẤT MẠNG (status 0): Không làm gì cả, chờ lần check tiếp theo
114
150
  },
115
151
  });
116
152
  }, this.#checkIntervalDuration);
@@ -123,6 +159,8 @@ class SdNoInternetInterceptor {
123
159
  clearInterval(this.#pollInterval);
124
160
  this.#pollInterval = null;
125
161
  }
162
+ this.#pollSubscription?.unsubscribe();
163
+ this.#pollSubscription = null;
126
164
  };
127
165
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdNoInternetInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
128
166
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdNoInternetInterceptor });
@@ -133,17 +171,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
133
171
 
134
172
  class SdUnauthorizedInterceptor {
135
173
  authService = inject(SdAuthService);
136
- #unauthorizedHandled = false;
174
+ /**
175
+ * why: bản cũ dùng một boolean `#unauthorizedHandled` set một lần rồi KHÔNG bao giờ mở lại.
176
+ * Hệ quả: sau lần 401 đầu tiên, mọi 401 còn lại trong vòng đời trang đều bị nuốt — người dùng
177
+ * đăng nhập lại xong, phiên hết hạn lần hai thì không còn bị ép signout nữa. Tệ hơn: nếu
178
+ * `SD_AUTH_CONFIGURATION.action.signout` không được cấu hình thì `SdAuthService.signout()` là
179
+ * no-op, nên lần 401 đầu tiên đốt latch mà chẳng làm gì cả.
180
+ *
181
+ * Thay bằng một cửa sổ debounce ngắn: 401 song song trong cùng cửa sổ vẫn chỉ gọi signout một
182
+ * lần (đúng mục đích ban đầu của latch), nhưng hết cửa sổ là latch tự mở lại.
183
+ *
184
+ * why: latch CHỈ mở lại theo thời gian. Bản trước còn reset `#lastSignoutAt` ở mỗi `HttpResponse`
185
+ * thành công, nhưng một 2xx KHÔNG chứng minh phiên còn sống — endpoint công khai vẫn trả 200 khi
186
+ * token đã hết hạn. Đúng kịch bản latch sinh ra để chặn (một burst request song song, vài cái
187
+ * trúng endpoint công khai trả 2xx xen giữa những cái trả 401) sẽ gọi signout một lần cho MỖI 200
188
+ * xen vào.
189
+ */
190
+ #signoutDebounceMs = 3000;
191
+ #lastSignoutAt = null;
137
192
  constructor() { }
138
193
  intercept(request, next) {
139
194
  return next.handle(request).pipe(catchError((error) => {
140
- if (error.status === 401 && !this.#unauthorizedHandled) {
141
- this.#unauthorizedHandled = true;
195
+ if (error.status === 401 && this.#shouldSignout()) {
196
+ this.#lastSignoutAt = Date.now();
142
197
  this.authService.signout();
143
198
  }
144
199
  return throwError(() => error);
145
200
  }));
146
201
  }
202
+ #shouldSignout = () => {
203
+ return this.#lastSignoutAt === null || Date.now() - this.#lastSignoutAt >= this.#signoutDebounceMs;
204
+ };
147
205
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdUnauthorizedInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
148
206
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdUnauthorizedInterceptor });
149
207
  }
@@ -155,5 +213,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
155
213
  * Generated bundle index. Do not edit.
156
214
  */
157
215
 
158
- export { SdNoInternetInterceptor, SdUnauthorizedInterceptor };
216
+ export { SD_NO_INTERNET_PROBE_URL, SdNoInternetInterceptor, SdUnauthorizedInterceptor };
159
217
  //# sourceMappingURL=sdcorejs-angular-interceptors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-interceptors.mjs","sources":["../../../projects/sdcorejs-angular/interceptors/no-internet/no-internet.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/unauthorized/unauthorized.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/sdcorejs-angular-interceptors.ts"],"sourcesContent":["import { HttpClient, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { inject, Injectable, Injector } from '@angular/core';\nimport { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class SdNoInternetInterceptor implements HttpInterceptor {\n private snackBar = inject(MatSnackBar);\n private injector = inject(Injector);\n\n readonly #i18n = inject(I18nService);\n\n // Trạng thái offline để tránh spam request check hoặc hiển thị nhiều snackbar\n #isOffline = false;\n\n // Giữ tham chiếu để có thể đóng/thao tác snackbar\n #snackBarRef: MatSnackBarRef<any> | null = null;\n\n // Giữ tham chiếu interval để clear khi có mạng lại\n #pollInterval: ReturnType<typeof setInterval> | null = null;\n\n // Lazy load HttpClient\n #http: HttpClient | null = null;\n\n // Endpoint kiểm tra mạng (Nên dùng 1 file tĩnh nhẹ hoặc API ping public uy tín)\n readonly #healthCheckUrl = 'https://jsonplaceholder.typicode.com/todos/1';\n\n // Thời gian lặp lại việc kiểm tra mạng (ms)\n readonly #checkIntervalDuration = 3000;\n\n /** Inserted by Angular inject() migration for backwards compatibility */\n constructor(...args: unknown[]);\n\n constructor() {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n // --- TRƯỜNG HỢP 1: Lỗi mất kết nối (Status 0) ---\n if (error.status === 0 && !this.#isOffline) {\n this.#isOffline = true;\n\n // Lazy load HttpClient để tránh lỗi Circular Dependency\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Gọi thử một API public để xác minh xem là Mất mạng thật hay do CORS/Server chặn\n return this.#http.get(this.#healthCheckUrl).pipe(\n // 1. Ưu tiên bắt lỗi kết nối trước (Đây là logic MẤT MẠNG THẬT)\n catchError(_checkError => {\n // Hiển thị thông báo và bắt đầu polling chờ mạng\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.offline'), { isSticky: true });\n this.#startPolling();\n\n // Ném lại lỗi gốc để Component biết request thất bại\n return throwError(() => error);\n }),\n\n // 2. Nếu không vào catchError ở trên -> Check thành công -> CÓ MẠNG\n // Lỗi status 0 ban đầu là do CORS, SSL, hoặc Server chặn connection\n switchMap(() => {\n this.#isOffline = false; // Reset cờ\n\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.cors-error'), this.#i18n.t('core.common.close'), {\n duration: 5000,\n horizontalPosition: 'center',\n verticalPosition: 'top',\n });\n\n // Ném lại lỗi gốc\n return throwError(() => error);\n })\n );\n }\n\n // --- TRƯỜNG HỢP 2: Server bảo trì (503) ---\n else if (error.status === 503) {\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.maintenance'), this.#i18n.t('core.common.close'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n duration: 5000,\n });\n }\n\n // --- TRƯỜNG HỢP KHÁC: 401, 404, 500... ---\n // Ném lỗi ra để component xử lý bình thường\n return throwError(() => error);\n })\n );\n }\n\n /**\n * Hiển thị Snackbar thông báo trạng thái mạng\n */\n #showReloadSnackbar = (message: string, options?: { duration?: number; isSticky?: boolean }): void => {\n // Đóng snackbar cũ nếu đang hiện\n if (this.#snackBarRef) {\n this.#snackBarRef.dismiss();\n }\n\n this.#snackBarRef = this.snackBar.open(message, this.#i18n.t('core.common.reload'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n // Nếu isSticky = true (mất mạng) -> Không tự tắt. Nếu có duration -> tự tắt.\n duration: options?.duration,\n panelClass: options?.isSticky ? ['offline-snackbar'] : undefined, // Class CSS tùy chọn\n });\n\n // Xử lý sự kiện bấm nút \"Tải lại trang\"\n this.#snackBarRef.onAction().subscribe(() => {\n window.location.reload();\n });\n };\n\n /**\n * Bắt đầu vòng lặp kiểm tra kết nối mạng\n */\n #startPolling = (): void => {\n this.#stopPolling(); // Clear cũ nếu có\n\n // @i18n-ignore — dev console log\n\n this.#pollInterval = setInterval(() => {\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Check nhẹ\n this.#http.get(this.#healthCheckUrl).subscribe({\n next: () => {\n // --> ĐÃ CÓ MẠNG LẠI\n // @i18n-ignore — dev console log\n\n this.#stopPolling();\n this.#isOffline = false;\n\n // Thông báo thành công (tự tắt sau 5s)\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.restored'), { duration: 5000, isSticky: false });\n },\n error: () => {\n // --> VẪN MẤT MẠNG: Không làm gì cả, chờ lần check tiếp theo\n },\n });\n }, this.#checkIntervalDuration);\n };\n\n /**\n * Dừng vòng lặp kiểm tra\n */\n #stopPolling = (): void => {\n if (this.#pollInterval) {\n clearInterval(this.#pollInterval);\n this.#pollInterval = null;\n }\n };\n}\n","import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { SdAuthService } from '@sdcorejs/angular/modules';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\n\n@Injectable()\nexport class SdUnauthorizedInterceptor implements HttpInterceptor {\n private authService = inject(SdAuthService);\n\n #unauthorizedHandled = false;\n\n /** Inserted by Angular inject() migration for backwards compatibility */\n constructor(...args: unknown[]);\n\n constructor() {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n if (error.status === 401 && !this.#unauthorizedHandled) {\n this.#unauthorizedHandled = true;\n this.authService.signout();\n }\n\n return throwError(() => error);\n })\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-interceptors.mjs","sources":["../../../projects/sdcorejs-angular/interceptors/no-internet/no-internet.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/unauthorized/unauthorized.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/sdcorejs-angular-interceptors.ts"],"sourcesContent":["import {\n HttpBackend,\n HttpErrorResponse,\n HttpEvent,\n HttpHandler,\n HttpHeaders,\n HttpInterceptor,\n HttpRequest,\n HttpResponse,\n} from '@angular/common/http';\nimport { DestroyRef, inject, Injectable, InjectionToken } from '@angular/core';\nimport { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { Observable, of, Subscription, throwError } from 'rxjs';\nimport { catchError, filter, map, switchMap } from 'rxjs/operators';\n\n/**\n * URL dùng để dò kết nối khi một request trả về status 0.\n *\n * why: bản cũ hard-code `https://jsonplaceholder.typicode.com/todos/1`, không override được. Mọi\n * app tiêu dùng thư viện đều ping một bên thứ ba lạ mỗi 3 giây khi mất mạng, và vì probe đi qua\n * NGUYÊN chuỗi interceptor của app nên bất kỳ interceptor nào gắn header auth vô điều kiện đều\n * rò token ra ngoài origin. Mặc định giờ là một đường dẫn same-origin; app đổi bằng cách provide\n * lại token này (nên trỏ tới endpoint tĩnh, rẻ, không cần auth).\n */\nexport const SD_NO_INTERNET_PROBE_URL = new InjectionToken<string>('sd.no-internet.probe-url', {\n providedIn: 'root',\n factory: () => '/favicon.ico',\n});\n\n@Injectable()\nexport class SdNoInternetInterceptor implements HttpInterceptor {\n private snackBar = inject(MatSnackBar);\n\n readonly #i18n = inject(I18nService);\n\n // why: HttpBackend là tầng cuối, KHÔNG đi qua chuỗi interceptor — probe vì thế không kéo theo\n // header do interceptor của app gắn, và cũng không tự đệ quy vào chính interceptor này.\n // Dùng backend cũng loại luôn lý do phải lazy-resolve HttpClient qua Injector (circular DI).\n readonly #backend = inject(HttpBackend);\n\n readonly #probeUrl = inject(SD_NO_INTERNET_PROBE_URL);\n\n readonly #destroyRef = inject(DestroyRef);\n\n // Trạng thái offline để tránh spam request check hoặc hiển thị nhiều snackbar\n #isOffline = false;\n\n // Giữ tham chiếu để có thể đóng/thao tác snackbar\n #snackBarRef: MatSnackBarRef<any> | null = null;\n\n // Giữ tham chiếu interval để clear khi có mạng lại\n #pollInterval: ReturnType<typeof setInterval> | null = null;\n\n // Giữ tham chiếu request probe đang bay để huỷ được khi dừng polling\n #pollSubscription: Subscription | null = null;\n\n // Thời gian lặp lại việc kiểm tra mạng (ms)\n readonly #checkIntervalDuration = 3000;\n\n /** Inserted by Angular inject() migration for backwards compatibility */\n constructor(...args: unknown[]);\n\n constructor() {\n // why: interceptor sống ở root injector nhưng vòng polling không có teardown nào — nó chỉ\n // dừng khi mạng phục hồi. App/microfrontend bị destroy giữa lúc offline sẽ bỏ lại một timer\n // 3s cùng request HTTP của nó chạy mãi.\n this.#destroyRef.onDestroy(() => this.#stopPolling());\n }\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n // --- TRƯỜNG HỢP 1: Lỗi mất kết nối (Status 0) ---\n if (error.status === 0 && !this.#isOffline) {\n this.#isOffline = true;\n\n // Gọi thử probe để xác minh xem là Mất mạng thật hay do CORS/SSL/Server chặn\n return this.#probe().pipe(\n // 1. Ưu tiên bắt lỗi kết nối trước (MẤT MẠNG THẬT — probe cũng không mở nổi kết nối,\n // tức status 0; mọi status HTTP khác đã được #probe quy về \"có mạng\")\n catchError(_checkError => {\n // Hiển thị thông báo và bắt đầu polling chờ mạng\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.offline'), { isSticky: true });\n this.#startPolling();\n\n // Ném lại lỗi gốc để Component biết request thất bại\n return throwError(() => error);\n }),\n\n // 2. Nếu không vào catchError ở trên -> Check thành công -> CÓ MẠNG\n // Lỗi status 0 ban đầu là do CORS, SSL, hoặc Server chặn connection\n switchMap(() => {\n this.#isOffline = false; // Reset cờ\n\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.cors-error'), this.#i18n.t('core.common.close'), {\n duration: 5000,\n horizontalPosition: 'center',\n verticalPosition: 'top',\n });\n\n // Ném lại lỗi gốc\n return throwError(() => error);\n })\n );\n }\n\n // --- TRƯỜNG HỢP 2: Server bảo trì (503) ---\n else if (error.status === 503) {\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.maintenance'), this.#i18n.t('core.common.close'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n duration: 5000,\n });\n }\n\n // --- TRƯỜNG HỢP KHÁC: 401, 404, 500... ---\n // Ném lỗi ra để component xử lý bình thường\n return throwError(() => error);\n })\n );\n }\n\n /**\n * Bắn một request dò kết nối đi thẳng qua HttpBackend.\n *\n * Phát `true` khi CÓ mạng; chỉ ném lỗi khi thật sự không mở được kết nối (`status === 0`).\n */\n #probe = (): Observable<boolean> => {\n const request = new HttpRequest<null>('GET', this.#probeUrl, {\n responseType: 'text',\n // why: probe mà đọc được từ HTTP cache thì lúc offline vẫn \"thành công\" và interceptor sẽ\n // kết luận nhầm là đã có mạng. no-cache buộc trình duyệt phải đi hỏi lại server.\n headers: new HttpHeaders({ 'Cache-Control': 'no-cache', Pragma: 'no-cache' }),\n });\n\n // why: HttpBackend phát HttpEvent thô (có thể gồm sent/progress), chỉ HttpResponse mới là\n // tín hiệu \"đã có mạng\".\n return this.#backend.handle(request).pipe(\n filter((event): event is HttpResponse<unknown> => event instanceof HttpResponse),\n map(() => true),\n // why: HttpBackend ném HttpErrorResponse cho MỌI status không phải 2xx, nên coi \"probe lỗi\"\n // = \"mất mạng thật\" là sai. Một `404` rất phổ biến (app xoá favicon mặc định của CLI, static\n // host trả 404/403 cho đường dẫn không tồn tại) và chính nó ĐÃ chứng minh là có mạng — nhưng\n // vẫn bị phân loại thành offline, dựng snackbar offline dính cứng cùng vòng poll 3 giây không\n // bao giờ thoát được. Chỉ status 0 (không thiết lập nổi kết nối) mới là offline.\n catchError((error: HttpErrorResponse) => (error.status === 0 ? throwError(() => error) : of(true)))\n );\n };\n\n /**\n * Hiển thị Snackbar thông báo trạng thái mạng\n */\n #showReloadSnackbar = (message: string, options?: { duration?: number; isSticky?: boolean }): void => {\n // Đóng snackbar cũ nếu đang hiện\n if (this.#snackBarRef) {\n this.#snackBarRef.dismiss();\n }\n\n this.#snackBarRef = this.snackBar.open(message, this.#i18n.t('core.common.reload'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n // Nếu isSticky = true (mất mạng) -> Không tự tắt. Nếu có duration -> tự tắt.\n duration: options?.duration,\n panelClass: options?.isSticky ? ['offline-snackbar'] : undefined, // Class CSS tùy chọn\n });\n\n // Xử lý sự kiện bấm nút \"Tải lại trang\"\n this.#snackBarRef.onAction().subscribe(() => {\n window.location.reload();\n });\n };\n\n /**\n * Bắt đầu vòng lặp kiểm tra kết nối mạng\n */\n #startPolling = (): void => {\n this.#stopPolling(); // Clear cũ nếu có\n\n this.#pollInterval = setInterval(() => {\n // Check nhẹ\n this.#pollSubscription?.unsubscribe();\n this.#pollSubscription = this.#probe().subscribe({\n next: () => {\n // --> ĐÃ CÓ MẠNG LẠI (2xx, hoặc bất kỳ status HTTP nào — xem #probe)\n this.#stopPolling();\n this.#isOffline = false;\n\n // Thông báo thành công (tự tắt sau 5s)\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.restored'), { duration: 5000, isSticky: false });\n },\n error: () => {\n // --> VẪN MẤT MẠNG (status 0): Không làm gì cả, chờ lần check tiếp theo\n },\n });\n }, this.#checkIntervalDuration);\n };\n\n /**\n * Dừng vòng lặp kiểm tra\n */\n #stopPolling = (): void => {\n if (this.#pollInterval) {\n clearInterval(this.#pollInterval);\n this.#pollInterval = null;\n }\n this.#pollSubscription?.unsubscribe();\n this.#pollSubscription = null;\n };\n}\n","import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { SdAuthService } from '@sdcorejs/angular/modules';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\n\n@Injectable()\nexport class SdUnauthorizedInterceptor implements HttpInterceptor {\n private authService = inject(SdAuthService);\n\n /**\n * why: bản cũ dùng một boolean `#unauthorizedHandled` set một lần rồi KHÔNG bao giờ mở lại.\n * Hệ quả: sau lần 401 đầu tiên, mọi 401 còn lại trong vòng đời trang đều bị nuốt — người dùng\n * đăng nhập lại xong, phiên hết hạn lần hai thì không còn bị ép signout nữa. Tệ hơn: nếu\n * `SD_AUTH_CONFIGURATION.action.signout` không được cấu hình thì `SdAuthService.signout()` là\n * no-op, nên lần 401 đầu tiên đốt latch mà chẳng làm gì cả.\n *\n * Thay bằng một cửa sổ debounce ngắn: 401 song song trong cùng cửa sổ vẫn chỉ gọi signout một\n * lần (đúng mục đích ban đầu của latch), nhưng hết cửa sổ là latch tự mở lại.\n *\n * why: latch CHỈ mở lại theo thời gian. Bản trước còn reset `#lastSignoutAt` ở mỗi `HttpResponse`\n * thành công, nhưng một 2xx KHÔNG chứng minh phiên còn sống — endpoint công khai vẫn trả 200 khi\n * token đã hết hạn. Đúng kịch bản latch sinh ra để chặn (một burst request song song, vài cái\n * trúng endpoint công khai trả 2xx xen giữa những cái trả 401) sẽ gọi signout một lần cho MỖI 200\n * xen vào.\n */\n readonly #signoutDebounceMs = 3000;\n #lastSignoutAt: number | null = null;\n\n /** Inserted by Angular inject() migration for backwards compatibility */\n constructor(...args: unknown[]);\n\n constructor() {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n if (error.status === 401 && this.#shouldSignout()) {\n this.#lastSignoutAt = Date.now();\n this.authService.signout();\n }\n\n return throwError(() => error);\n })\n );\n }\n\n #shouldSignout = (): boolean => {\n return this.#lastSignoutAt === null || Date.now() - this.#lastSignoutAt >= this.#signoutDebounceMs;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, Injectable } from '@angular/core';
2
+ import { InjectionToken, inject, signal, Injectable, isDevMode } from '@angular/core';
3
3
  import { Subject } from 'rxjs';
4
4
  import { normalizeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';
5
5
  import { toSignal } from '@angular/core/rxjs-interop';
@@ -23,19 +23,16 @@ class SdAuthService {
23
23
  // this.#handleSignin();
24
24
  this.#handleSignout();
25
25
  this.#handleChangePassword();
26
- const defaultUser = {
27
- id: 'guest-id',
28
- username: 'guest',
29
- firstName: 'Guest',
30
- email: 'guest@gmail.com',
31
- };
26
+ // why: code cũ dựng sẵn một identity giả (`guest` / `guest@gmail.com`) và dùng nó vừa làm giá trị
27
+ // khi CHƯA cấu hình `guard.authInfo`, vừa làm `initialValue` trong lúc lookup thật còn pending.
28
+ // Hệ quả: template thấy một user "đã đăng nhập" hợp lệ và render UI của người dùng đã xác thực,
29
+ // dù thực tế chưa ai đăng nhập. `undefined` là trạng thái trung thực duy nhất cho cả hai ca —
30
+ // template bắt buộc phải xử lý nhánh chưa xác thực (`@if (user(); as u)`) thay vì tin vào user giả.
32
31
  if (this.authConfiguration?.guard?.authInfo) {
33
- this.getAuthInfo = toSignal(normalizeAsync(this.authConfiguration.guard?.authInfo()), {
34
- initialValue: defaultUser,
35
- });
32
+ this.getAuthInfo = toSignal(normalizeAsync(this.authConfiguration.guard?.authInfo()));
36
33
  }
37
34
  else {
38
- this.getAuthInfo = signal(defaultUser, ...(ngDevMode ? [{ debugName: "getAuthInfo" }] : []));
35
+ this.getAuthInfo = signal(undefined, ...(ngDevMode ? [{ debugName: "getAuthInfo" }] : []));
39
36
  }
40
37
  }
41
38
  // #handleSignin = () => {
@@ -76,8 +73,15 @@ class SdAuthGuard {
76
73
  if (this.authConfiguration?.guard?.auth) {
77
74
  return this.authConfiguration.guard.auth(route, state);
78
75
  }
79
- // Nếu chưa tích hợp Auth Guard thì pass canActive
80
- return true;
76
+ // why: đặt `SdAuthGuard` vào route table tức consumer đã tuyên bố route ĐÓ cần bảo vệ. Code cũ
77
+ // trả `true` khi thiếu `SD_AUTH_CONFIGURATION.guard.auth`, nên chỉ cần quên một provider là toàn bộ
78
+ // route "được bảo vệ" trở thành công khai mà giao diện vẫn trông đúng — không có tín hiệu nào cả.
79
+ // Vì vậy mặc định là TỪ CHỐI (fail closed); ở dev mode log thật to để lỗi cấu hình lộ ra ngay.
80
+ if (isDevMode()) {
81
+ console.error('[SdAuthGuard] Navigation denied: SD_AUTH_CONFIGURATION.guard.auth is not configured. ' +
82
+ 'Provide SD_AUTH_CONFIGURATION with a guard.auth callback, or remove SdAuthGuard from this route if it is meant to be public.');
83
+ }
84
+ return false;
81
85
  };
82
86
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdAuthGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
83
87
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdAuthGuard, providedIn: 'root' });
@@ -94,8 +98,14 @@ class SdPortalGuard {
94
98
  if (this.authConfiguration?.guard?.portal) {
95
99
  return this.authConfiguration.guard.portal(route, state);
96
100
  }
97
- // Nếu chưa tích hợp Portal Guard thì pass canActive
98
- return true;
101
+ // why: giống `SdAuthGuard` guard mặt trong route table là lời tuyên bố "route này cần kiểm
102
+ // tra". Trả `true` khi thiếu `SD_AUTH_CONFIGURATION.guard.portal` biến hàng rào portal thành vô
103
+ // hiệu một cách âm thầm. Fail closed + log to ở dev mode để phát hiện thiếu provider ngay lập tức.
104
+ if (isDevMode()) {
105
+ console.error('[SdPortalGuard] Navigation denied: SD_AUTH_CONFIGURATION.guard.portal is not configured. ' +
106
+ 'Provide SD_AUTH_CONFIGURATION with a guard.portal callback, or remove SdPortalGuard from this route if it is meant to be public.');
107
+ }
108
+ return false;
99
109
  };
100
110
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdPortalGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
101
111
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdPortalGuard, providedIn: 'root' });
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-modules-auth.mjs","sources":["../../../projects/sdcorejs-angular/modules/auth/configurations/auth.configuration.ts","../../../projects/sdcorejs-angular/modules/auth/services/auth.service.ts","../../../projects/sdcorejs-angular/modules/auth/guards/auth.guard.ts","../../../projects/sdcorejs-angular/modules/auth/guards/portal.guard.ts","../../../projects/sdcorejs-angular/modules/auth/sdcorejs-angular-modules-auth.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { CanActivate } from '@angular/router';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from '../services';\n\nexport interface ISdAuthConfiguration {\n action?: IAuthConfigurationAction;\n guard?: IAuthConfigurationGuard;\n}\n\nexport const SD_AUTH_CONFIGURATION = new InjectionToken<ISdAuthConfiguration>('sd.auth.configuration');\n\ninterface IAuthConfigurationAction {\n signout: () => MaybeAsync<void>;\n changePassword?: () => MaybeAsync<void>;\n}\n\ninterface IAuthConfigurationGuard {\n auth?: CanActivate['canActivate'];\n portal?: CanActivate['canActivate'];\n authInfo: () => MaybeAsync<SdAuthInfo>;\n}\n","import { inject, Injectable, signal, Signal } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\nimport { normalizeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from './auth.model';\nimport { toSignal } from '@angular/core/rxjs-interop';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthService {\n // Đăng nhập\n // #signin = new Subject<void>();\n // signin$?: Observable<void>;\n getAuthInfo?: Signal<SdAuthInfo | undefined>;\n\n // Đăng xuất\n #signout = new Subject<void>();\n signout$?: Observable<void>;\n\n // Thay đổi mật khẩu\n #changePassword = new Subject<void>();\n changePassword$?: Observable<void>;\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n\n constructor() {\n // this.#handleSignin();\n this.#handleSignout();\n this.#handleChangePassword();\n const defaultUser = {\n id: 'guest-id',\n username: 'guest',\n firstName: 'Guest',\n email: 'guest@gmail.com',\n };\n if (this.authConfiguration?.guard?.authInfo) {\n this.getAuthInfo = toSignal(normalizeAsync(this.authConfiguration.guard?.authInfo()), {\n initialValue: defaultUser,\n });\n } else {\n this.getAuthInfo = signal(defaultUser);\n }\n }\n\n // #handleSignin = () => {\n // this.signin$ = this.#signin.asObservable();\n // };\n\n #handleSignout = () => {\n this.signout$ = this.#signout.asObservable();\n };\n\n #handleChangePassword = () => {\n this.changePassword$ = this.#changePassword.asObservable();\n };\n signout = () => {\n if (this.authConfiguration?.action?.signout) {\n resolveMaybeAsync(this.authConfiguration.action.signout()).then(() => {\n this.#signout.next();\n });\n }\n };\n\n changePassword = () => {\n if (this.authConfiguration?.action?.changePassword) {\n resolveMaybeAsync(this.authConfiguration.action.changePassword()).then(() => {\n this.#changePassword.next();\n });\n }\n };\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.auth) {\n return this.authConfiguration.guard.auth(route, state);\n }\n // Nếu chưa tích hợp Auth Guard thì pass canActive\n return true;\n };\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPortalGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.portal) {\n return this.authConfiguration.guard.portal(route, state);\n }\n // Nếu chưa tích hợp Portal Guard thì pass canActive\n return true;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-modules-auth.mjs","sources":["../../../projects/sdcorejs-angular/modules/auth/configurations/auth.configuration.ts","../../../projects/sdcorejs-angular/modules/auth/services/auth.service.ts","../../../projects/sdcorejs-angular/modules/auth/guards/auth.guard.ts","../../../projects/sdcorejs-angular/modules/auth/guards/portal.guard.ts","../../../projects/sdcorejs-angular/modules/auth/sdcorejs-angular-modules-auth.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { CanActivate } from '@angular/router';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from '../services';\n\nexport interface ISdAuthConfiguration {\n action?: IAuthConfigurationAction;\n guard?: IAuthConfigurationGuard;\n}\n\nexport const SD_AUTH_CONFIGURATION = new InjectionToken<ISdAuthConfiguration>('sd.auth.configuration');\n\ninterface IAuthConfigurationAction {\n signout: () => MaybeAsync<void>;\n changePassword?: () => MaybeAsync<void>;\n}\n\ninterface IAuthConfigurationGuard {\n auth?: CanActivate['canActivate'];\n portal?: CanActivate['canActivate'];\n authInfo: () => MaybeAsync<SdAuthInfo>;\n}\n","import { inject, Injectable, signal, Signal } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\nimport { normalizeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from './auth.model';\nimport { toSignal } from '@angular/core/rxjs-interop';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthService {\n // Đăng nhập\n // #signin = new Subject<void>();\n // signin$?: Observable<void>;\n getAuthInfo?: Signal<SdAuthInfo | undefined>;\n\n // Đăng xuất\n #signout = new Subject<void>();\n signout$?: Observable<void>;\n\n // Thay đổi mật khẩu\n #changePassword = new Subject<void>();\n changePassword$?: Observable<void>;\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n\n constructor() {\n // this.#handleSignin();\n this.#handleSignout();\n this.#handleChangePassword();\n // why: code dựng sẵn một identity giả (`guest` / `guest@gmail.com`) và dùng nó vừa làm giá trị\n // khi CHƯA cấu hình `guard.authInfo`, vừa làm `initialValue` trong lúc lookup thật còn pending.\n // Hệ quả: template thấy một user \"đã đăng nhập\" hợp lệ và render UI của người dùng đã xác thực,\n // dù thực tế chưa ai đăng nhập. `undefined` là trạng thái trung thực duy nhất cho cả hai ca —\n // template bắt buộc phải xử lý nhánh chưa xác thực (`@if (user(); as u)`) thay vì tin vào user giả.\n if (this.authConfiguration?.guard?.authInfo) {\n this.getAuthInfo = toSignal(normalizeAsync(this.authConfiguration.guard?.authInfo()));\n } else {\n this.getAuthInfo = signal<SdAuthInfo | undefined>(undefined);\n }\n }\n\n // #handleSignin = () => {\n // this.signin$ = this.#signin.asObservable();\n // };\n\n #handleSignout = () => {\n this.signout$ = this.#signout.asObservable();\n };\n\n #handleChangePassword = () => {\n this.changePassword$ = this.#changePassword.asObservable();\n };\n signout = () => {\n if (this.authConfiguration?.action?.signout) {\n resolveMaybeAsync(this.authConfiguration.action.signout()).then(() => {\n this.#signout.next();\n });\n }\n };\n\n changePassword = () => {\n if (this.authConfiguration?.action?.changePassword) {\n resolveMaybeAsync(this.authConfiguration.action.changePassword()).then(() => {\n this.#changePassword.next();\n });\n }\n };\n}\n","import { inject, Injectable, isDevMode } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.auth) {\n return this.authConfiguration.guard.auth(route, state);\n }\n\n // why: đặt `SdAuthGuard` vào route table tức consumer đã tuyên bố route ĐÓ cần bảo vệ. Code cũ\n // trả `true` khi thiếu `SD_AUTH_CONFIGURATION.guard.auth`, nên chỉ cần quên một provider là toàn bộ\n // route \"được bảo vệ\" trở thành công khai mà giao diện vẫn trông đúng — không có tín hiệu nào cả.\n // Vì vậy mặc định là TỪ CHỐI (fail closed); ở dev mode log thật to để lỗi cấu hình lộ ra ngay.\n if (isDevMode()) {\n console.error(\n '[SdAuthGuard] Navigation denied: SD_AUTH_CONFIGURATION.guard.auth is not configured. ' +\n 'Provide SD_AUTH_CONFIGURATION with a guard.auth callback, or remove SdAuthGuard from this route if it is meant to be public.'\n );\n }\n return false;\n };\n}\n","import { inject, Injectable, isDevMode } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPortalGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.portal) {\n return this.authConfiguration.guard.portal(route, state);\n }\n\n // why: giống `SdAuthGuard` guard mặt trong route table là lời tuyên bố \"route này cần kiểm\n // tra\". Trả `true` khi thiếu `SD_AUTH_CONFIGURATION.guard.portal` biến hàng rào portal thành vô\n // hiệu một cách âm thầm. Fail closed + log to ở dev mode để phát hiện thiếu provider ngay lập tức.\n if (isDevMode()) {\n console.error(\n '[SdPortalGuard] Navigation denied: SD_AUTH_CONFIGURATION.guard.portal is not configured. ' +\n 'Provide SD_AUTH_CONFIGURATION with a guard.portal callback, or remove SdPortalGuard from this route if it is meant to be public.'\n );\n }\n return false;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;MAUa,qBAAqB,GAAG,IAAI,cAAc,CAAuB,uBAAuB;;MCFxF,aAAa,CAAA;;;;AAIxB,IAAA,WAAW;;AAGX,IAAA,QAAQ,GAAG,IAAI,OAAO,EAAQ;AAC9B,IAAA,QAAQ;;AAGR,IAAA,eAAe,GAAG,IAAI,OAAO,EAAQ;AACrC,IAAA,eAAe;IACE,iBAAiB,GAAgC,MAAM,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAEnH,IAAA,WAAA,GAAA;;QAEE,IAAI,CAAC,cAAc,EAAE;QACrB,IAAI,CAAC,qBAAqB,EAAE;;;;;;QAM5B,IAAI,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE;AAC3C,YAAA,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvF;aAAO;AACL,YAAA,IAAI,CAAC,WAAW,GAAG,MAAM,CAAyB,SAAS,uDAAC;QAC9D;IACF;;;;IAMA,cAAc,GAAG,MAAK;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE;AAC9C,IAAA,CAAC;IAED,qBAAqB,GAAG,MAAK;QAC3B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE;AAC5D,IAAA,CAAC;IACD,OAAO,GAAG,MAAK;QACb,IAAI,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,OAAO,EAAE;AAC3C,YAAA,iBAAiB,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,MAAK;AACnE,gBAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AACtB,YAAA,CAAC,CAAC;QACJ;AACF,IAAA,CAAC;IAED,cAAc,GAAG,MAAK;QACpB,IAAI,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,cAAc,EAAE;AAClD,YAAA,iBAAiB,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,MAAK;AAC1E,gBAAA,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE;AAC7B,YAAA,CAAC,CAAC;QACJ;AACF,IAAA,CAAC;wGAxDU,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAb,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,cADA,MAAM,EAAA,CAAA;;4FACnB,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;MCFrB,WAAW,CAAA;IACL,iBAAiB,GAAgC,MAAM,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAChG,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAE1C,IAAA,WAAW,GAAG,CAAC,KAA6B,EAAE,KAA0B,KAAI;QAC1E,IAAI,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,IAAI,EAAE;AACvC,YAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC;QACxD;;;;;QAMA,IAAI,SAAS,EAAE,EAAE;YACf,OAAO,CAAC,KAAK,CACX,uFAAuF;AACrF,gBAAA,8HAA8H,CACjI;QACH;AACA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;wGApBU,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAX,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAW,cADE,MAAM,EAAA,CAAA;;4FACnB,WAAW,EAAA,UAAA,EAAA,CAAA;kBADvB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;MCCrB,aAAa,CAAA;IACP,iBAAiB,GAAgC,MAAM,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAChG,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAE1C,IAAA,WAAW,GAAG,CAAC,KAA6B,EAAE,KAA0B,KAAI;QAC1E,IAAI,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE;AACzC,YAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC;QAC1D;;;;QAKA,IAAI,SAAS,EAAE,EAAE;YACf,OAAO,CAAC,KAAK,CACX,2FAA2F;AACzF,gBAAA,kIAAkI,CACrI;QACH;AACA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;wGAnBU,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAb,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,cADA,MAAM,EAAA,CAAA;;4FACnB,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACJlC;;AAEG;;;;"}
@@ -460,7 +460,7 @@ class SdIcon {
460
460
  </mat-icon>
461
461
  }
462
462
  }
463
- `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:var(--sd-icon-size);height:var(--sd-icon-size);min-width:var(--sd-icon-size);color:currentColor;font-size:var(--sd-icon-size);line-height:1;vertical-align:middle;flex:0 0 auto}.sd-icon__material{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%;color:currentColor;font-size:inherit;line-height:1;overflow:hidden}.sd-icon__svg{display:block;width:100%;height:100%;color:currentColor;stroke:currentColor;flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
463
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:var(--sd-icon-size);height:var(--sd-icon-size);min-width:var(--sd-icon-size);color:currentColor;font-size:var(--sd-icon-size);line-height:1;vertical-align:middle;flex:0 0 auto}:host .sd-icon__material{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%;margin:0;color:currentColor;font-size:inherit;line-height:1;overflow:hidden}.sd-icon__svg{display:block;width:100%;height:100%;color:currentColor;stroke:currentColor;flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
464
464
  }
465
465
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdIcon, decorators: [{
466
466
  type: Component,
@@ -493,7 +493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
493
493
  '[attr.aria-label]': 'ariaLabel()',
494
494
  '[attr.aria-hidden]': 'ariaLabel() ? null : "true"',
495
495
  '[attr.color]': 'color()',
496
- }, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:var(--sd-icon-size);height:var(--sd-icon-size);min-width:var(--sd-icon-size);color:currentColor;font-size:var(--sd-icon-size);line-height:1;vertical-align:middle;flex:0 0 auto}.sd-icon__material{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%;color:currentColor;font-size:inherit;line-height:1;overflow:hidden}.sd-icon__svg{display:block;width:100%;height:100%;color:currentColor;stroke:currentColor;flex:0 0 auto}\n"] }]
496
+ }, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:var(--sd-icon-size);height:var(--sd-icon-size);min-width:var(--sd-icon-size);color:currentColor;font-size:var(--sd-icon-size);line-height:1;vertical-align:middle;flex:0 0 auto}:host .sd-icon__material{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%;margin:0;color:currentColor;font-size:inherit;line-height:1;overflow:hidden}.sd-icon__svg{display:block;width:100%;height:100%;color:currentColor;stroke:currentColor;flex:0 0 auto}\n"] }]
497
497
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], fontIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontIcon", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], set: [{ type: i0.Input, args: [{ isSignal: true, alias: "set", required: false }] }], fontSet: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontSet", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], absoluteStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "absoluteStrokeWidth", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
498
498
  /**
499
499
  * Chuẩn hóa string input rỗng thành `undefined` để fallback provider hoạt động nhất quán.
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-modules-icon.mjs","sources":["../../../projects/sdcorejs-angular/modules/icon/src/icon.model.ts","../../../projects/sdcorejs-angular/modules/icon/src/icon.provider.ts","../../../projects/sdcorejs-angular/modules/icon/src/icon.component.ts","../../../projects/sdcorejs-angular/modules/icon/sdcorejs-angular-modules-icon.ts"],"sourcesContent":["import { type LucideConfig, type LucideIcon, type LucideIconData } from '@lucide/angular';\nimport { type Size } from '@sdcorejs/utils/models';\nimport { DefaultSdIconSet, type SdIconSet, type SdMaterialIconSet } from '@sdcorejs/angular/utilities/models';\n\n/**\n * Chọn renderer icon chính cho `SdIcon`.\n *\n * Material dùng đúng tên font set để tránh thêm mapping nội bộ; `lucide` chuyển sang renderer SVG của `@lucide/angular`.\n */\nexport type { SdIconSet, SdMaterialIconSet };\n\n/** @deprecated Use `SdIconSet` instead. */\nexport type SdIconFontSet = SdIconSet;\n\n/**\n * Một icon Lucide có thể đăng ký bằng class icon Angular hoặc dữ liệu path thuần.\n */\nexport type SdLucideIconRegistration = LucideIcon | LucideIconData;\n\n/**\n * Danh sách đăng ký Lucide cho provider, hỗ trợ cả array và object map để app có thể giữ cấu trúc import riêng.\n */\nexport type SdLucideIconRegistrations = SdLucideIconRegistration[] | Record<string, SdLucideIconRegistration>;\n\n/**\n * Cấu hình app-level cho `SdIcon`.\n *\n * Các alias tách riêng theo renderer để app có thể giữ tên Material trong template trong lúc chuyển dần sang Lucide.\n */\nexport interface ISdIconConfiguration {\n /** Default icon font set when component does not pass `fontSet`. */\n defaultFontSet?: SdIconSet;\n /** Alias áp dụng khi renderer hiện tại là Material. */\n materialAliases?: Record<string, string>;\n /** Alias áp dụng khi renderer hiện tại là Lucide. */\n lucideAliases?: Record<string, string>;\n /** Cấu hình stroke toàn cục cho Lucide SVG. */\n lucideConfig?: Partial<LucideConfig>;\n /** Icon Lucide cần đăng ký vào `@lucide/angular`. */\n lucideIcons?: SdLucideIconRegistrations;\n}\n\n/**\n * Cấu hình đã resolve đầy đủ để component không phải tự xử lý fallback nhiều lần.\n */\nexport type ISdIconResolvedConfiguration = Required<Omit<ISdIconConfiguration, 'lucideIcons'>>;\n\n/**\n * Quy đổi token `Size` chung của SDCoreJS sang pixel cho icon.\n */\nexport const SD_ICON_SIZE_MAP: Record<Size, number> = {\n sm: 16,\n md: 20,\n lg: 24,\n};\n\n/**\n * Alias mặc định giúp template giữ tên Material trong khi renderer có thể là Lucide.\n */\nexport const SD_DEFAULT_LUCIDE_ALIASES: Record<string, string> = {\n account_balance: 'landmark',\n account_circle: 'circle-user-round',\n account_tree: 'workflow',\n add: 'plus',\n add_circle: 'circle-plus',\n arrow_back: 'arrow-left',\n arrow_downward: 'arrow-down',\n arrow_drop_down: 'chevron-down',\n arrow_drop_up: 'chevron-up',\n arrow_forward: 'arrow-right',\n arrow_upward: 'arrow-up',\n bar_chart: 'chart-column',\n block: 'ban',\n bookmark_add: 'bookmark-plus',\n bookmark_border: 'bookmark',\n build: 'wrench',\n calendar_month: 'calendar-days',\n campaign: 'megaphone',\n cancel: 'circle-x',\n celebration: 'party-popper',\n chevron_left: 'chevron-left',\n chevron_right: 'chevron-right',\n check: 'check',\n check_circle: 'circle-check',\n close: 'x',\n cloud_download: 'cloud-download',\n contact_page: 'contact',\n content_copy: 'copy',\n dashboard: 'layout-dashboard',\n delete: 'trash-2',\n delete_sweep: 'trash-2',\n description: 'file-text',\n done: 'check',\n done_all: 'check-check',\n drag_indicator: 'grip-vertical',\n edit: 'pencil',\n edit_note: 'file-pen-line',\n error: 'circle-alert',\n error_outline: 'circle-alert',\n expand_less: 'chevron-up',\n expand_more: 'chevron-down',\n filter_alt: 'list-filter',\n file_download: 'download',\n file_upload: 'upload',\n fiber_manual_record: 'circle',\n fit_screen: 'scan',\n folder: 'folder',\n first_page: 'chevrons-left',\n format_indent_decrease: 'list-indent-decrease',\n format_indent_increase: 'list-indent-increase',\n fullscreen: 'maximize',\n fullscreen_exit: 'minimize',\n get_app: 'download',\n group: 'users',\n hourglass_empty: 'hourglass',\n hourglass_top: 'hourglass',\n image: 'image',\n image_not_supported: 'image-off',\n info: 'info',\n info_outline: 'info',\n insert_drive_file: 'file',\n inventory_2: 'package',\n keyboard_arrow_down: 'chevron-down',\n keyboard_arrow_left: 'chevron-left',\n keyboard_arrow_right: 'chevron-right',\n keyboard_arrow_up: 'chevron-up',\n last_page: 'chevrons-right',\n list: 'list',\n local_offer: 'tag',\n lock: 'lock-keyhole',\n lock_outline: 'lock-keyhole',\n login: 'log-in',\n logout: 'log-out',\n match_case: 'case-sensitive',\n menu: 'menu',\n menu_open: 'panel-left-close',\n more_horiz: 'ellipsis',\n more_vert: 'ellipsis-vertical',\n notifications: 'bell',\n open_in_new: 'external-link',\n open_in_full: 'maximize',\n person: 'user',\n picture_as_pdf: 'file-text',\n play_arrow: 'play',\n print: 'printer',\n refresh: 'refresh-cw',\n remove: 'minus',\n remove_circle: 'circle-minus',\n restart_alt: 'rotate-ccw',\n rotate_right: 'rotate-cw',\n search_off: 'search-x',\n settings: 'settings',\n share: 'share-2',\n smart_toy: 'bot',\n space_dashboard: 'layout-dashboard',\n storefront: 'store',\n swap_horiz: 'arrow-left-right',\n table_chart: 'table',\n table_view: 'table-2',\n task_alt: 'circle-check',\n text_fields: 'text',\n today: 'calendar',\n unfold_more: 'chevrons-up-down',\n upload_file: 'upload',\n view_carousel: 'gallery-horizontal',\n view_day: 'panel-top',\n view_list: 'list',\n visibility: 'eye',\n visibility_off: 'eye-off',\n warning: 'triangle-alert',\n warning_amber: 'triangle-alert',\n widgets: 'layout-grid',\n zoom_in: 'zoom-in',\n};\n\n/**\n * Alias mac dinh giup template dung ten Lucide van render duoc khi quay ve Material font.\n */\nexport const SD_DEFAULT_MATERIAL_ALIASES: Record<string, string> = {\n 'arrow-down': 'arrow_downward',\n 'arrow-left': 'arrow_back',\n 'arrow-right': 'arrow_forward',\n 'arrow-up': 'arrow_upward',\n 'calendar-days': 'calendar_month',\n 'case-sensitive': 'match_case',\n 'chart-column': 'bar_chart',\n 'check-check': 'done_all',\n 'chevron-down': 'expand_more',\n 'chevron-left': 'chevron_left',\n 'chevron-right': 'chevron_right',\n 'chevron-up': 'expand_less',\n 'chevrons-left': 'first_page',\n 'chevrons-right': 'last_page',\n 'circle-alert': 'error',\n 'circle-check': 'check_circle',\n 'circle-minus': 'remove_circle',\n 'circle-plus': 'add_circle',\n 'circle-user-round': 'account_circle',\n 'circle-x': 'cancel',\n 'cloud-download': 'cloud_download',\n 'file-down': 'file_download',\n 'file-pen-line': 'edit_note',\n 'file-text': 'description',\n 'file-up': 'upload_file',\n 'gallery-horizontal': 'view_carousel',\n get_app: 'file_download',\n 'grip-vertical': 'drag_indicator',\n 'layout-dashboard': 'dashboard',\n 'layout-grid': 'widgets',\n 'list-indent-decrease': 'format_indent_decrease',\n 'list-indent-increase': 'format_indent_increase',\n 'list-filter': 'filter_alt',\n 'lock-keyhole': 'lock',\n 'log-in': 'login',\n 'log-out': 'logout',\n 'panel-left-close': 'menu_open',\n 'party-popper': 'celebration',\n 'refresh-cw': 'refresh',\n 'rotate-ccw': 'restart_alt',\n 'rotate-cw': 'rotate_right',\n 'share-2': 'share',\n 'square-pen': 'edit',\n 'trash-2': 'delete',\n 'triangle-alert': 'warning',\n 'zoom-in': 'zoom_in',\n ban: 'block',\n contact: 'contact_page',\n copy: 'content_copy',\n circle: 'fiber_manual_record',\n download: 'file_download',\n ellipsis: 'more_horiz',\n 'ellipsis-vertical': 'more_vert',\n error_outline: 'error',\n eye: 'visibility',\n 'eye-off': 'visibility_off',\n file: 'insert_drive_file',\n folder: 'folder',\n fullscreen: 'fullscreen',\n hourglass: 'hourglass_top',\n image: 'image',\n 'image-off': 'image_not_supported',\n info_outline: 'info',\n landmark: 'account_balance',\n list: 'list',\n lock_outline: 'lock',\n maximize: 'fullscreen',\n megaphone: 'campaign',\n menu: 'menu',\n minimize: 'fullscreen_exit',\n minus: 'remove',\n package: 'inventory_2',\n pencil: 'edit',\n plus: 'add',\n play: 'play_arrow',\n printer: 'print',\n scan: 'fit_screen',\n 'search-x': 'search_off',\n settings: 'settings',\n 'save-all': 'done_all',\n store: 'storefront',\n tag: 'local_offer',\n text: 'text_fields',\n upload: 'upload_file',\n user: 'person',\n users: 'group',\n workflow: 'account_tree',\n wrench: 'build',\n x: 'close',\n};\n\n/**\n * Cấu hình mặc định của Core UI icon.\n *\n * `material-icons-outlined` được giữ làm mặc định để không đổi visual của các component Material hiện có.\n */\nexport const SD_ICON_DEFAULT_CONFIG: ISdIconResolvedConfiguration = {\n defaultFontSet: DefaultSdIconSet,\n materialAliases: SD_DEFAULT_MATERIAL_ALIASES,\n lucideAliases: SD_DEFAULT_LUCIDE_ALIASES,\n lucideConfig: {},\n};\n","import { InjectionToken, makeEnvironmentProviders, type EnvironmentProviders, type Provider } from '@angular/core';\nimport { provideLucideConfig, provideLucideIcons } from '@lucide/angular';\nimport {\n SD_ICON_DEFAULT_CONFIG,\n type ISdIconConfiguration,\n type ISdIconResolvedConfiguration,\n type SdLucideIconRegistration,\n type SdLucideIconRegistrations,\n} from './icon.model';\n\n/**\n * Injection token chứa cấu hình icon đã resolve cho toàn bộ app.\n *\n * Token có factory mặc định để `SdIcon` vẫn chạy khi app chưa gọi `provideSdIcon`.\n */\nexport const SD_ICON_CONFIGURATION = new InjectionToken<ISdIconResolvedConfiguration>('SD_ICON_CONFIGURATION', {\n providedIn: 'root',\n factory: () => resolveSdIconConfig(),\n});\n\n/**\n * Đăng ký cấu hình icon Core UI ở app-level.\n *\n * Provider này gom cả cấu hình `SdIcon` và registry của `@lucide/angular` để consumer chỉ cần gọi một hàm.\n *\n * @param config - cấu hình app-level cho Material/Lucide icon.\n * @returns environment providers dùng được trong `ApplicationConfig.providers`.\n */\nexport function provideSdIcon(config: ISdIconConfiguration = {}): EnvironmentProviders {\n const providers: Provider[] = [\n {\n provide: SD_ICON_CONFIGURATION,\n useValue: resolveSdIconConfig(config),\n },\n ];\n\n if (config.lucideConfig) {\n providers.push(provideLucideConfig(config.lucideConfig));\n }\n\n const lucideIcons = normalizeSdLucideIcons(config.lucideIcons);\n if (lucideIcons.length) {\n // why: @lucide/angular chỉ render dynamic icon đã được đăng ký trước qua provider\n providers.push(provideLucideIcons(...lucideIcons));\n }\n\n return makeEnvironmentProviders(providers);\n}\n\n/**\n * Resolve cấu hình `SdIcon` thành object đầy đủ, không còn field optional.\n *\n * Alias mặc định đứng trước để app có thể override từng key mà không phải copy toàn bộ bảng alias.\n *\n * @param config - cấu hình rời rạc từ consumer hoặc mặc định rỗng.\n * @returns cấu hình đầy đủ dùng trực tiếp trong component.\n */\nexport function resolveSdIconConfig(config: ISdIconConfiguration = {}): ISdIconResolvedConfiguration {\n return {\n defaultFontSet: config.defaultFontSet ?? SD_ICON_DEFAULT_CONFIG.defaultFontSet,\n materialAliases: {\n ...SD_ICON_DEFAULT_CONFIG.materialAliases,\n ...config.materialAliases,\n },\n lucideAliases: {\n ...SD_ICON_DEFAULT_CONFIG.lucideAliases,\n ...config.lucideAliases,\n },\n lucideConfig: {\n ...SD_ICON_DEFAULT_CONFIG.lucideConfig,\n ...config.lucideConfig,\n },\n };\n}\n\n/**\n * Chuẩn hóa nhiều kiểu khai báo Lucide icon về array cho `provideLucideIcons`.\n *\n * @param registrations - array hoặc object map icon Lucide từ consumer.\n * @returns danh sách icon Lucide đã sẵn sàng để spread vào provider của Lucide.\n */\nexport function normalizeSdLucideIcons(registrations?: SdLucideIconRegistrations): SdLucideIconRegistration[] {\n if (!registrations) {\n return [];\n }\n\n return Array.isArray(registrations) ? registrations : Object.values(registrations);\n}\n","import { ChangeDetectionStrategy, Component, booleanAttribute, computed, inject, input } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { LucideDynamicIcon } from '@lucide/angular';\nimport { type Size } from '@sdcorejs/utils/models';\nimport { DefaultSdMaterialIconSet } from '@sdcorejs/angular/utilities/models';\nimport { SD_ICON_SIZE_MAP, type SdIconSet, type SdMaterialIconSet } from './icon.model';\nimport { SD_ICON_CONFIGURATION } from './icon.provider';\n\n/**\n * Facade icon dùng chung cho Core UI.\n *\n * Component này giữ template app theo tên icon ổn định, còn renderer có thể đổi giữa Material font và Lucide SVG qua provider hoặc input.\n */\n@Component({\n selector: 'sd-icon',\n standalone: true,\n imports: [MatIconModule, LucideDynamicIcon],\n template: `\n @if (resolvedName(); as _name) {\n @if (resolvedFontSet() === 'lucide') {\n <svg\n class=\"sd-icon__svg\"\n [lucideIcon]=\"_name\"\n [size]=\"resolvedSize()\"\n [strokeWidth]=\"resolvedStrokeWidth()\"\n [absoluteStrokeWidth]=\"resolvedAbsoluteStrokeWidth()\"\n [title]=\"ariaLabel()\"></svg>\n } @else {\n <mat-icon\n class=\"sd-icon__material\"\n [fontSet]=\"resolvedMaterialFontSet()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\">\n {{ _name }}\n </mat-icon>\n }\n }\n `,\n styles: [\n `\n :host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--sd-icon-size);\n height: var(--sd-icon-size);\n min-width: var(--sd-icon-size);\n color: currentColor;\n font-size: var(--sd-icon-size);\n line-height: 1;\n vertical-align: middle;\n flex: 0 0 auto;\n }\n\n .sd-icon__material {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: currentColor;\n font-size: inherit;\n line-height: 1;\n overflow: hidden;\n }\n\n .sd-icon__svg {\n display: block;\n width: 100%;\n height: 100%;\n color: currentColor;\n stroke: currentColor;\n flex: 0 0 auto;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'sd-icon',\n '[class.sd-icon--material]': 'resolvedFontSet() !== \"lucide\"',\n '[class.sd-icon--material-icons-outlined]': 'resolvedFontSet() === \"material-icons-outlined\"',\n '[class.sd-icon--lucide]': 'resolvedFontSet() === \"lucide\"',\n '[style.--sd-icon-size]': 'resolvedCssSize()',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-hidden]': 'ariaLabel() ? null : \"true\"',\n '[attr.color]': 'color()',\n },\n})\nexport class SdIcon {\n // why: InjectionToken cho phép app đổi default/alias mà không buộc từng component nhận thêm input cấu hình\n private readonly config = inject(SD_ICON_CONFIGURATION);\n\n /**\n * Tên icon theo namespace hiện tại.\n *\n * Khi `set` là `lucide`, tên này sẽ đi qua alias để app có thể tiếp tục dùng tên Material trong template.\n */\n name = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n fontIcon = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n color = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n /**\n * Renderer icon của riêng instance này.\n *\n * Deprecated alias for `fontSet`.\n */\n set = input<SdIconSet | undefined, SdIconSet | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Escape hatch cho Material icon font set.\n *\n * Prefer this input for both Material font families and Lucide SVG icons.\n */\n fontSet = input<SdIconSet | undefined, SdIconSet | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Kích thước icon theo token SDCoreJS hoặc CSS size tùy biến.\n *\n * Token `sm | md | lg` giữ icon đồng bộ với các component Core UI; CSS string xử lý các ngoại lệ layout.\n */\n size = input<Size | (string & {}), Size | (string & {}) | undefined | null>('md', {\n transform: value => value ?? 'md',\n });\n\n /**\n * Độ dày stroke riêng cho Lucide SVG.\n *\n * Bỏ trống để nhận giá trị từ `lucideConfig` app-level.\n */\n strokeWidth = input<number | string | undefined, number | string | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Giữ stroke width tuyệt đối cho Lucide khi SVG đổi kích thước.\n *\n * Input này đi qua `booleanAttribute` để hỗ trợ cả attribute trần trong template.\n */\n absoluteStrokeWidth = input<boolean | undefined, boolean | string | undefined | null>(undefined, {\n transform: value => (value == null ? undefined : booleanAttribute(value)),\n });\n\n /**\n * Nhãn truy cập cho icon có ý nghĩa.\n *\n * Khi không có nhãn, component tự đánh dấu decorative bằng `aria-hidden`.\n */\n ariaLabel = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n /**\n * Set icon cuối cùng sau khi áp dụng input và provider.\n *\n * @returns renderer hiện tại của component.\n */\n resolvedFontSet = computed<SdIconSet>(() => this.fontSet() ?? this.set() ?? this.config.defaultFontSet);\n\n /** @deprecated Use `resolvedFontSet` instead. */\n resolvedSet = computed<SdIconSet>(() => this.resolvedFontSet());\n\n /**\n * Tên icon cuối cùng theo renderer hiện tại.\n *\n * @returns tên icon đã áp alias nếu renderer yêu cầu.\n */\n resolvedName = computed(() => {\n const name = this.name() ?? this.fontIcon();\n if (!name) {\n return undefined;\n }\n\n const aliases = this.resolvedFontSet() === 'lucide' ? this.config.lucideAliases : this.config.materialAliases;\n return aliases[name] ?? name;\n });\n\n /**\n * Font set Material cuối cùng.\n *\n * @returns font set dùng cho `mat-icon`; với Lucide trả về fallback Material để computed luôn có type ổn định.\n */\n resolvedMaterialFontSet = computed<SdMaterialIconSet>(() => {\n const fontSet = this.resolvedFontSet();\n return fontSet === 'lucide' ? DefaultSdMaterialIconSet : fontSet;\n });\n\n /**\n * Kích thước runtime cho renderer hiện tại.\n *\n * @returns số pixel cho token chuẩn hoặc CSS string nguyên bản cho custom size.\n */\n resolvedSize = computed<number | string>(() => {\n const size = this.size();\n return isSizeToken(size) ? SD_ICON_SIZE_MAP[size] : size;\n });\n\n /**\n * Kích thước CSS cuối cùng cho host element.\n *\n * @returns CSS length dùng cho custom property `--sd-icon-size`.\n */\n resolvedCssSize = computed(() => {\n const size = this.resolvedSize();\n return typeof size === 'number' ? `${size}px` : size;\n });\n\n /**\n * Stroke width cuối cùng cho Lucide SVG.\n *\n * @returns giá trị input instance hoặc fallback từ provider.\n */\n resolvedStrokeWidth = computed(() => this.strokeWidth() ?? this.config.lucideConfig.strokeWidth);\n\n /**\n * Absolute stroke width cuối cùng cho Lucide SVG.\n *\n * @returns giá trị input instance hoặc fallback từ provider.\n */\n resolvedAbsoluteStrokeWidth = computed(() => this.absoluteStrokeWidth() ?? this.config.lucideConfig.absoluteStrokeWidth);\n}\n\n/**\n * Chuẩn hóa string input rỗng thành `undefined` để fallback provider hoạt động nhất quán.\n *\n * @param value - giá trị string từ input Angular.\n * @returns string đã trim hoặc `undefined`.\n */\nfunction coerceStringInput(value: string | undefined | null): string | undefined {\n const trimmed = value?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Kiểm tra một size có phải token chuẩn của SDCoreJS hay không.\n *\n * @param value - token size hoặc CSS size string.\n * @returns `true` khi value có mapping pixel trong `SD_ICON_SIZE_MAP`.\n */\nfunction isSizeToken(value: Size | (string & {})): value is Size {\n return value in SD_ICON_SIZE_MAP;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-modules-icon.mjs","sources":["../../../projects/sdcorejs-angular/modules/icon/src/icon.model.ts","../../../projects/sdcorejs-angular/modules/icon/src/icon.provider.ts","../../../projects/sdcorejs-angular/modules/icon/src/icon.component.ts","../../../projects/sdcorejs-angular/modules/icon/sdcorejs-angular-modules-icon.ts"],"sourcesContent":["import { type LucideConfig, type LucideIcon, type LucideIconData } from '@lucide/angular';\nimport { type Size } from '@sdcorejs/utils/models';\nimport { DefaultSdIconSet, type SdIconSet, type SdMaterialIconSet } from '@sdcorejs/angular/utilities/models';\n\n/**\n * Chọn renderer icon chính cho `SdIcon`.\n *\n * Material dùng đúng tên font set để tránh thêm mapping nội bộ; `lucide` chuyển sang renderer SVG của `@lucide/angular`.\n */\nexport type { SdIconSet, SdMaterialIconSet };\n\n/** @deprecated Use `SdIconSet` instead. */\nexport type SdIconFontSet = SdIconSet;\n\n/**\n * Một icon Lucide có thể đăng ký bằng class icon Angular hoặc dữ liệu path thuần.\n */\nexport type SdLucideIconRegistration = LucideIcon | LucideIconData;\n\n/**\n * Danh sách đăng ký Lucide cho provider, hỗ trợ cả array và object map để app có thể giữ cấu trúc import riêng.\n */\nexport type SdLucideIconRegistrations = SdLucideIconRegistration[] | Record<string, SdLucideIconRegistration>;\n\n/**\n * Cấu hình app-level cho `SdIcon`.\n *\n * Các alias tách riêng theo renderer để app có thể giữ tên Material trong template trong lúc chuyển dần sang Lucide.\n */\nexport interface ISdIconConfiguration {\n /** Default icon font set when component does not pass `fontSet`. */\n defaultFontSet?: SdIconSet;\n /** Alias áp dụng khi renderer hiện tại là Material. */\n materialAliases?: Record<string, string>;\n /** Alias áp dụng khi renderer hiện tại là Lucide. */\n lucideAliases?: Record<string, string>;\n /** Cấu hình stroke toàn cục cho Lucide SVG. */\n lucideConfig?: Partial<LucideConfig>;\n /** Icon Lucide cần đăng ký vào `@lucide/angular`. */\n lucideIcons?: SdLucideIconRegistrations;\n}\n\n/**\n * Cấu hình đã resolve đầy đủ để component không phải tự xử lý fallback nhiều lần.\n */\nexport type ISdIconResolvedConfiguration = Required<Omit<ISdIconConfiguration, 'lucideIcons'>>;\n\n/**\n * Quy đổi token `Size` chung của SDCoreJS sang pixel cho icon.\n */\nexport const SD_ICON_SIZE_MAP: Record<Size, number> = {\n sm: 16,\n md: 20,\n lg: 24,\n};\n\n/**\n * Alias mặc định giúp template giữ tên Material trong khi renderer có thể là Lucide.\n */\nexport const SD_DEFAULT_LUCIDE_ALIASES: Record<string, string> = {\n account_balance: 'landmark',\n account_circle: 'circle-user-round',\n account_tree: 'workflow',\n add: 'plus',\n add_circle: 'circle-plus',\n arrow_back: 'arrow-left',\n arrow_downward: 'arrow-down',\n arrow_drop_down: 'chevron-down',\n arrow_drop_up: 'chevron-up',\n arrow_forward: 'arrow-right',\n arrow_upward: 'arrow-up',\n bar_chart: 'chart-column',\n block: 'ban',\n bookmark_add: 'bookmark-plus',\n bookmark_border: 'bookmark',\n build: 'wrench',\n calendar_month: 'calendar-days',\n campaign: 'megaphone',\n cancel: 'circle-x',\n celebration: 'party-popper',\n chevron_left: 'chevron-left',\n chevron_right: 'chevron-right',\n check: 'check',\n check_circle: 'circle-check',\n close: 'x',\n cloud_download: 'cloud-download',\n contact_page: 'contact',\n content_copy: 'copy',\n dashboard: 'layout-dashboard',\n delete: 'trash-2',\n delete_sweep: 'trash-2',\n description: 'file-text',\n done: 'check',\n done_all: 'check-check',\n drag_indicator: 'grip-vertical',\n edit: 'pencil',\n edit_note: 'file-pen-line',\n error: 'circle-alert',\n error_outline: 'circle-alert',\n expand_less: 'chevron-up',\n expand_more: 'chevron-down',\n filter_alt: 'list-filter',\n file_download: 'download',\n file_upload: 'upload',\n fiber_manual_record: 'circle',\n fit_screen: 'scan',\n folder: 'folder',\n first_page: 'chevrons-left',\n format_indent_decrease: 'list-indent-decrease',\n format_indent_increase: 'list-indent-increase',\n fullscreen: 'maximize',\n fullscreen_exit: 'minimize',\n get_app: 'download',\n group: 'users',\n hourglass_empty: 'hourglass',\n hourglass_top: 'hourglass',\n image: 'image',\n image_not_supported: 'image-off',\n info: 'info',\n info_outline: 'info',\n insert_drive_file: 'file',\n inventory_2: 'package',\n keyboard_arrow_down: 'chevron-down',\n keyboard_arrow_left: 'chevron-left',\n keyboard_arrow_right: 'chevron-right',\n keyboard_arrow_up: 'chevron-up',\n last_page: 'chevrons-right',\n list: 'list',\n local_offer: 'tag',\n lock: 'lock-keyhole',\n lock_outline: 'lock-keyhole',\n login: 'log-in',\n logout: 'log-out',\n match_case: 'case-sensitive',\n menu: 'menu',\n menu_open: 'panel-left-close',\n more_horiz: 'ellipsis',\n more_vert: 'ellipsis-vertical',\n notifications: 'bell',\n open_in_new: 'external-link',\n open_in_full: 'maximize',\n person: 'user',\n picture_as_pdf: 'file-text',\n play_arrow: 'play',\n print: 'printer',\n refresh: 'refresh-cw',\n remove: 'minus',\n remove_circle: 'circle-minus',\n restart_alt: 'rotate-ccw',\n rotate_right: 'rotate-cw',\n search_off: 'search-x',\n settings: 'settings',\n share: 'share-2',\n smart_toy: 'bot',\n space_dashboard: 'layout-dashboard',\n storefront: 'store',\n swap_horiz: 'arrow-left-right',\n table_chart: 'table',\n table_view: 'table-2',\n task_alt: 'circle-check',\n text_fields: 'text',\n today: 'calendar',\n unfold_more: 'chevrons-up-down',\n upload_file: 'upload',\n view_carousel: 'gallery-horizontal',\n view_day: 'panel-top',\n view_list: 'list',\n visibility: 'eye',\n visibility_off: 'eye-off',\n warning: 'triangle-alert',\n warning_amber: 'triangle-alert',\n widgets: 'layout-grid',\n zoom_in: 'zoom-in',\n};\n\n/**\n * Alias mac dinh giup template dung ten Lucide van render duoc khi quay ve Material font.\n */\nexport const SD_DEFAULT_MATERIAL_ALIASES: Record<string, string> = {\n 'arrow-down': 'arrow_downward',\n 'arrow-left': 'arrow_back',\n 'arrow-right': 'arrow_forward',\n 'arrow-up': 'arrow_upward',\n 'calendar-days': 'calendar_month',\n 'case-sensitive': 'match_case',\n 'chart-column': 'bar_chart',\n 'check-check': 'done_all',\n 'chevron-down': 'expand_more',\n 'chevron-left': 'chevron_left',\n 'chevron-right': 'chevron_right',\n 'chevron-up': 'expand_less',\n 'chevrons-left': 'first_page',\n 'chevrons-right': 'last_page',\n 'circle-alert': 'error',\n 'circle-check': 'check_circle',\n 'circle-minus': 'remove_circle',\n 'circle-plus': 'add_circle',\n 'circle-user-round': 'account_circle',\n 'circle-x': 'cancel',\n 'cloud-download': 'cloud_download',\n 'file-down': 'file_download',\n 'file-pen-line': 'edit_note',\n 'file-text': 'description',\n 'file-up': 'upload_file',\n 'gallery-horizontal': 'view_carousel',\n get_app: 'file_download',\n 'grip-vertical': 'drag_indicator',\n 'layout-dashboard': 'dashboard',\n 'layout-grid': 'widgets',\n 'list-indent-decrease': 'format_indent_decrease',\n 'list-indent-increase': 'format_indent_increase',\n 'list-filter': 'filter_alt',\n 'lock-keyhole': 'lock',\n 'log-in': 'login',\n 'log-out': 'logout',\n 'panel-left-close': 'menu_open',\n 'party-popper': 'celebration',\n 'refresh-cw': 'refresh',\n 'rotate-ccw': 'restart_alt',\n 'rotate-cw': 'rotate_right',\n 'share-2': 'share',\n 'square-pen': 'edit',\n 'trash-2': 'delete',\n 'triangle-alert': 'warning',\n 'zoom-in': 'zoom_in',\n ban: 'block',\n contact: 'contact_page',\n copy: 'content_copy',\n circle: 'fiber_manual_record',\n download: 'file_download',\n ellipsis: 'more_horiz',\n 'ellipsis-vertical': 'more_vert',\n error_outline: 'error',\n eye: 'visibility',\n 'eye-off': 'visibility_off',\n file: 'insert_drive_file',\n folder: 'folder',\n fullscreen: 'fullscreen',\n hourglass: 'hourglass_top',\n image: 'image',\n 'image-off': 'image_not_supported',\n info_outline: 'info',\n landmark: 'account_balance',\n list: 'list',\n lock_outline: 'lock',\n maximize: 'fullscreen',\n megaphone: 'campaign',\n menu: 'menu',\n minimize: 'fullscreen_exit',\n minus: 'remove',\n package: 'inventory_2',\n pencil: 'edit',\n plus: 'add',\n play: 'play_arrow',\n printer: 'print',\n scan: 'fit_screen',\n 'search-x': 'search_off',\n settings: 'settings',\n 'save-all': 'done_all',\n store: 'storefront',\n tag: 'local_offer',\n text: 'text_fields',\n upload: 'upload_file',\n user: 'person',\n users: 'group',\n workflow: 'account_tree',\n wrench: 'build',\n x: 'close',\n};\n\n/**\n * Cấu hình mặc định của Core UI icon.\n *\n * `material-icons-outlined` được giữ làm mặc định để không đổi visual của các component Material hiện có.\n */\nexport const SD_ICON_DEFAULT_CONFIG: ISdIconResolvedConfiguration = {\n defaultFontSet: DefaultSdIconSet,\n materialAliases: SD_DEFAULT_MATERIAL_ALIASES,\n lucideAliases: SD_DEFAULT_LUCIDE_ALIASES,\n lucideConfig: {},\n};\n","import { InjectionToken, makeEnvironmentProviders, type EnvironmentProviders, type Provider } from '@angular/core';\nimport { provideLucideConfig, provideLucideIcons } from '@lucide/angular';\nimport {\n SD_ICON_DEFAULT_CONFIG,\n type ISdIconConfiguration,\n type ISdIconResolvedConfiguration,\n type SdLucideIconRegistration,\n type SdLucideIconRegistrations,\n} from './icon.model';\n\n/**\n * Injection token chứa cấu hình icon đã resolve cho toàn bộ app.\n *\n * Token có factory mặc định để `SdIcon` vẫn chạy khi app chưa gọi `provideSdIcon`.\n */\nexport const SD_ICON_CONFIGURATION = new InjectionToken<ISdIconResolvedConfiguration>('SD_ICON_CONFIGURATION', {\n providedIn: 'root',\n factory: () => resolveSdIconConfig(),\n});\n\n/**\n * Đăng ký cấu hình icon Core UI ở app-level.\n *\n * Provider này gom cả cấu hình `SdIcon` và registry của `@lucide/angular` để consumer chỉ cần gọi một hàm.\n *\n * @param config - cấu hình app-level cho Material/Lucide icon.\n * @returns environment providers dùng được trong `ApplicationConfig.providers`.\n */\nexport function provideSdIcon(config: ISdIconConfiguration = {}): EnvironmentProviders {\n const providers: Provider[] = [\n {\n provide: SD_ICON_CONFIGURATION,\n useValue: resolveSdIconConfig(config),\n },\n ];\n\n if (config.lucideConfig) {\n providers.push(provideLucideConfig(config.lucideConfig));\n }\n\n const lucideIcons = normalizeSdLucideIcons(config.lucideIcons);\n if (lucideIcons.length) {\n // why: @lucide/angular chỉ render dynamic icon đã được đăng ký trước qua provider\n providers.push(provideLucideIcons(...lucideIcons));\n }\n\n return makeEnvironmentProviders(providers);\n}\n\n/**\n * Resolve cấu hình `SdIcon` thành object đầy đủ, không còn field optional.\n *\n * Alias mặc định đứng trước để app có thể override từng key mà không phải copy toàn bộ bảng alias.\n *\n * @param config - cấu hình rời rạc từ consumer hoặc mặc định rỗng.\n * @returns cấu hình đầy đủ dùng trực tiếp trong component.\n */\nexport function resolveSdIconConfig(config: ISdIconConfiguration = {}): ISdIconResolvedConfiguration {\n return {\n defaultFontSet: config.defaultFontSet ?? SD_ICON_DEFAULT_CONFIG.defaultFontSet,\n materialAliases: {\n ...SD_ICON_DEFAULT_CONFIG.materialAliases,\n ...config.materialAliases,\n },\n lucideAliases: {\n ...SD_ICON_DEFAULT_CONFIG.lucideAliases,\n ...config.lucideAliases,\n },\n lucideConfig: {\n ...SD_ICON_DEFAULT_CONFIG.lucideConfig,\n ...config.lucideConfig,\n },\n };\n}\n\n/**\n * Chuẩn hóa nhiều kiểu khai báo Lucide icon về array cho `provideLucideIcons`.\n *\n * @param registrations - array hoặc object map icon Lucide từ consumer.\n * @returns danh sách icon Lucide đã sẵn sàng để spread vào provider của Lucide.\n */\nexport function normalizeSdLucideIcons(registrations?: SdLucideIconRegistrations): SdLucideIconRegistration[] {\n if (!registrations) {\n return [];\n }\n\n return Array.isArray(registrations) ? registrations : Object.values(registrations);\n}\n","import { ChangeDetectionStrategy, Component, booleanAttribute, computed, inject, input } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { LucideDynamicIcon } from '@lucide/angular';\nimport { type Size } from '@sdcorejs/utils/models';\nimport { DefaultSdMaterialIconSet } from '@sdcorejs/angular/utilities/models';\nimport { SD_ICON_SIZE_MAP, type SdIconSet, type SdMaterialIconSet } from './icon.model';\nimport { SD_ICON_CONFIGURATION } from './icon.provider';\n\n/**\n * Facade icon dùng chung cho Core UI.\n *\n * Component này giữ template app theo tên icon ổn định, còn renderer có thể đổi giữa Material font và Lucide SVG qua provider hoặc input.\n */\n@Component({\n selector: 'sd-icon',\n standalone: true,\n imports: [MatIconModule, LucideDynamicIcon],\n template: `\n @if (resolvedName(); as _name) {\n @if (resolvedFontSet() === 'lucide') {\n <svg\n class=\"sd-icon__svg\"\n [lucideIcon]=\"_name\"\n [size]=\"resolvedSize()\"\n [strokeWidth]=\"resolvedStrokeWidth()\"\n [absoluteStrokeWidth]=\"resolvedAbsoluteStrokeWidth()\"\n [title]=\"ariaLabel()\"></svg>\n } @else {\n <mat-icon\n class=\"sd-icon__material\"\n [fontSet]=\"resolvedMaterialFontSet()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"ariaLabel() ? null : 'true'\">\n {{ _name }}\n </mat-icon>\n }\n }\n `,\n styles: [\n `\n :host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--sd-icon-size);\n height: var(--sd-icon-size);\n min-width: var(--sd-icon-size);\n color: currentColor;\n font-size: var(--sd-icon-size);\n line-height: 1;\n vertical-align: middle;\n flex: 0 0 auto;\n }\n\n /* why: base CSS của Material nhắm thẳng .mat-icon con trong menu/list/button item và ép\n width/height 24px + margin-right 12px lên nó. Glyph khi đó to hơn host --sd-icon-size,\n bị overflow hidden xén mất và lệch sang trái — lỗi \"icon bị cắt đè\" trong mat-menu. Selector\n đi qua :host (0,3,0) để luôn thắng .mat-mdc-menu-item .mat-icon (0,2,0) bất kể thứ tự nạp\n stylesheet; khoảng cách với nhãn để menu/list lo ở cấp host, không nhét vào trong glyph. */\n :host .sd-icon__material {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n margin: 0;\n color: currentColor;\n font-size: inherit;\n line-height: 1;\n overflow: hidden;\n }\n\n .sd-icon__svg {\n display: block;\n width: 100%;\n height: 100%;\n color: currentColor;\n stroke: currentColor;\n flex: 0 0 auto;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'sd-icon',\n '[class.sd-icon--material]': 'resolvedFontSet() !== \"lucide\"',\n '[class.sd-icon--material-icons-outlined]': 'resolvedFontSet() === \"material-icons-outlined\"',\n '[class.sd-icon--lucide]': 'resolvedFontSet() === \"lucide\"',\n '[style.--sd-icon-size]': 'resolvedCssSize()',\n '[attr.aria-label]': 'ariaLabel()',\n '[attr.aria-hidden]': 'ariaLabel() ? null : \"true\"',\n '[attr.color]': 'color()',\n },\n})\nexport class SdIcon {\n // why: InjectionToken cho phép app đổi default/alias mà không buộc từng component nhận thêm input cấu hình\n private readonly config = inject(SD_ICON_CONFIGURATION);\n\n /**\n * Tên icon theo namespace hiện tại.\n *\n * Khi `set` là `lucide`, tên này sẽ đi qua alias để app có thể tiếp tục dùng tên Material trong template.\n */\n name = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n fontIcon = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n color = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n /**\n * Renderer icon của riêng instance này.\n *\n * Deprecated alias for `fontSet`.\n */\n set = input<SdIconSet | undefined, SdIconSet | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Escape hatch cho Material icon font set.\n *\n * Prefer this input for both Material font families and Lucide SVG icons.\n */\n fontSet = input<SdIconSet | undefined, SdIconSet | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Kích thước icon theo token SDCoreJS hoặc CSS size tùy biến.\n *\n * Token `sm | md | lg` giữ icon đồng bộ với các component Core UI; CSS string xử lý các ngoại lệ layout.\n */\n size = input<Size | (string & {}), Size | (string & {}) | undefined | null>('md', {\n transform: value => value ?? 'md',\n });\n\n /**\n * Độ dày stroke riêng cho Lucide SVG.\n *\n * Bỏ trống để nhận giá trị từ `lucideConfig` app-level.\n */\n strokeWidth = input<number | string | undefined, number | string | undefined | null>(undefined, {\n transform: value => value ?? undefined,\n });\n\n /**\n * Giữ stroke width tuyệt đối cho Lucide khi SVG đổi kích thước.\n *\n * Input này đi qua `booleanAttribute` để hỗ trợ cả attribute trần trong template.\n */\n absoluteStrokeWidth = input<boolean | undefined, boolean | string | undefined | null>(undefined, {\n transform: value => (value == null ? undefined : booleanAttribute(value)),\n });\n\n /**\n * Nhãn truy cập cho icon có ý nghĩa.\n *\n * Khi không có nhãn, component tự đánh dấu decorative bằng `aria-hidden`.\n */\n ariaLabel = input<string | undefined, string | undefined | null>(undefined, {\n transform: coerceStringInput,\n });\n\n /**\n * Set icon cuối cùng sau khi áp dụng input và provider.\n *\n * @returns renderer hiện tại của component.\n */\n resolvedFontSet = computed<SdIconSet>(() => this.fontSet() ?? this.set() ?? this.config.defaultFontSet);\n\n /** @deprecated Use `resolvedFontSet` instead. */\n resolvedSet = computed<SdIconSet>(() => this.resolvedFontSet());\n\n /**\n * Tên icon cuối cùng theo renderer hiện tại.\n *\n * @returns tên icon đã áp alias nếu renderer yêu cầu.\n */\n resolvedName = computed(() => {\n const name = this.name() ?? this.fontIcon();\n if (!name) {\n return undefined;\n }\n\n const aliases = this.resolvedFontSet() === 'lucide' ? this.config.lucideAliases : this.config.materialAliases;\n return aliases[name] ?? name;\n });\n\n /**\n * Font set Material cuối cùng.\n *\n * @returns font set dùng cho `mat-icon`; với Lucide trả về fallback Material để computed luôn có type ổn định.\n */\n resolvedMaterialFontSet = computed<SdMaterialIconSet>(() => {\n const fontSet = this.resolvedFontSet();\n return fontSet === 'lucide' ? DefaultSdMaterialIconSet : fontSet;\n });\n\n /**\n * Kích thước runtime cho renderer hiện tại.\n *\n * @returns số pixel cho token chuẩn hoặc CSS string nguyên bản cho custom size.\n */\n resolvedSize = computed<number | string>(() => {\n const size = this.size();\n return isSizeToken(size) ? SD_ICON_SIZE_MAP[size] : size;\n });\n\n /**\n * Kích thước CSS cuối cùng cho host element.\n *\n * @returns CSS length dùng cho custom property `--sd-icon-size`.\n */\n resolvedCssSize = computed(() => {\n const size = this.resolvedSize();\n return typeof size === 'number' ? `${size}px` : size;\n });\n\n /**\n * Stroke width cuối cùng cho Lucide SVG.\n *\n * @returns giá trị input instance hoặc fallback từ provider.\n */\n resolvedStrokeWidth = computed(() => this.strokeWidth() ?? this.config.lucideConfig.strokeWidth);\n\n /**\n * Absolute stroke width cuối cùng cho Lucide SVG.\n *\n * @returns giá trị input instance hoặc fallback từ provider.\n */\n resolvedAbsoluteStrokeWidth = computed(() => this.absoluteStrokeWidth() ?? this.config.lucideConfig.absoluteStrokeWidth);\n}\n\n/**\n * Chuẩn hóa string input rỗng thành `undefined` để fallback provider hoạt động nhất quán.\n *\n * @param value - giá trị string từ input Angular.\n * @returns string đã trim hoặc `undefined`.\n */\nfunction coerceStringInput(value: string | undefined | null): string | undefined {\n const trimmed = value?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Kiểm tra một size có phải token chuẩn của SDCoreJS hay không.\n *\n * @param value - token size hoặc CSS size string.\n * @returns `true` khi value có mapping pixel trong `SD_ICON_SIZE_MAP`.\n */\nfunction isSizeToken(value: Size | (string & {})): value is Size {\n return value in SD_ICON_SIZE_MAP;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}