@sdcorejs/angular 19.0.9 → 19.0.10

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 (223) hide show
  1. package/README.md +270 -140
  2. package/assets/scss/core/utilities/_grid.scss +25 -19
  3. package/components/anchor/src/models/sd-anchor.model.d.ts +1 -1
  4. package/components/document-builder/src/document-builder.component.d.ts +2 -2
  5. package/components/document-builder/src/plugins/paste-handler/filters/parse.d.ts +1 -1
  6. package/components/editor/src/plugins/image-upload/utils/batch.utils.d.ts +3 -3
  7. package/components/form-generic/src/components/form-builder/components/attribute-expression/attribute-expression.component.d.ts +10 -6
  8. package/components/form-generic/src/components/form-builder/components/attribute-expression/form-expression-query-adapter.d.ts +6 -0
  9. package/components/form-generic/src/components/form-builder/components/attribute-parameter/attribute-parameter.component.d.ts +2 -2
  10. package/components/form-generic/src/components/form-builder/components/attribute-selection/attribute-selection.component.d.ts +2 -2
  11. package/components/form-generic/src/components/form-builder/components/attribute-selection/components/build-queries/build-queries.component.d.ts +2 -2
  12. package/components/form-generic/src/components/form-builder/components/attribute-selection/components/build-variables/build-variables.component.d.ts +2 -2
  13. package/components/form-generic/src/components/form-builder/components/attribute-table/attribute-table.component.d.ts +2 -4
  14. package/components/form-generic/src/components/form-builder/components/attribute-template/attribute-template.component.d.ts +2 -4
  15. package/components/form-generic/src/components/form-builder/components/checkbox/attribute/checkbox-attribute.component.d.ts +2 -2
  16. package/components/form-generic/src/components/form-builder/components/chip-calendar/attribute/chip-calendar-attribute.component.d.ts +2 -2
  17. package/components/form-generic/src/components/form-builder/components/chip-calendar/control/chip-calendar-control.component.d.ts +2 -2
  18. package/components/form-generic/src/components/form-builder/components/chip-string/attribute/chip-string-attribute.component.d.ts +2 -2
  19. package/components/form-generic/src/components/form-builder/components/chip-string/control/chip-string-control.component.d.ts +2 -2
  20. package/components/form-generic/src/components/form-builder/components/configure-validation/configure-validation.component.d.ts +6 -7
  21. package/components/form-generic/src/components/form-builder/components/datetime/attribute/datetime-attribute.component.d.ts +2 -2
  22. package/components/form-generic/src/components/form-builder/components/datetime/control/datetime-control.component.d.ts +2 -2
  23. package/components/form-generic/src/components/form-builder/components/expression-builder/expression-builder.component.d.ts +2 -2
  24. package/components/form-generic/src/components/form-builder/components/html/attribute/components/build-queries/build-queries.component.d.ts +2 -2
  25. package/components/form-generic/src/components/form-builder/components/html/control/html-control.component.d.ts +2 -2
  26. package/components/form-generic/src/components/form-builder/components/number/attribute/number-attribute.component.d.ts +2 -2
  27. package/components/form-generic/src/components/form-builder/components/number/control/number-control.component.d.ts +2 -2
  28. package/components/form-generic/src/components/form-builder/components/radio/attribute/radio-attribute.component.d.ts +2 -2
  29. package/components/form-generic/src/components/form-builder/components/radio/control/radio-control.component.d.ts +2 -2
  30. package/components/form-generic/src/components/form-builder/components/select/attribute/select-attribute.component.d.ts +2 -2
  31. package/components/form-generic/src/components/form-builder/components/select/control/select-control.component.d.ts +2 -2
  32. package/components/form-generic/src/components/form-builder/components/table/attribute/table-attribute.component.d.ts +2 -2
  33. package/components/form-generic/src/components/form-builder/components/table/control/table-control.component.d.ts +2 -2
  34. package/components/form-generic/src/components/form-builder/components/textarea/attribute/textarea-attribute.component.d.ts +2 -2
  35. package/components/form-generic/src/components/form-builder/components/textarea/control/textarea-control.component.d.ts +2 -2
  36. package/components/form-generic/src/components/form-builder/components/textfield/attribute/textfield-attribute.component.d.ts +2 -2
  37. package/components/form-generic/src/components/form-builder/components/textfield/control/textfield-control.component.d.ts +2 -2
  38. package/components/form-generic/src/components/form-builder/components/upload/attribute/upload-attribute.component.d.ts +2 -2
  39. package/components/form-generic/src/components/form-builder/components/upload/control/upload-control.component.d.ts +2 -2
  40. package/components/form-generic/src/components/form-builder/form-builder-layout.d.ts +22 -0
  41. package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +24 -5
  42. package/components/form-generic/src/components/form-render/components/item/components/chip-calendar/chip-calendar.component.d.ts +2 -2
  43. package/components/form-generic/src/components/form-render/components/item/components/chip-string/chip-string.component.d.ts +2 -2
  44. package/components/form-generic/src/components/form-render/components/item/components/datetime/datetime.component.d.ts +2 -2
  45. package/components/form-generic/src/components/form-render/components/item/components/html/html.component.d.ts +2 -2
  46. package/components/form-generic/src/components/form-render/components/item/components/number/number.component.d.ts +2 -2
  47. package/components/form-generic/src/components/form-render/components/item/components/radio/radio.component.d.ts +2 -2
  48. package/components/form-generic/src/components/form-render/components/item/components/select/select.component.d.ts +2 -2
  49. package/components/form-generic/src/components/form-render/components/item/components/table/table.component.d.ts +2 -2
  50. package/components/form-generic/src/components/form-render/components/item/components/textarea/textarea.component.d.ts +2 -2
  51. package/components/form-generic/src/components/form-render/components/item/components/textfield/textfield.component.d.ts +2 -2
  52. package/components/form-generic/src/components/form-render/components/item/components/upload/upload.component.d.ts +2 -2
  53. package/components/form-generic/src/components/form-render/components/variable/variable.component.d.ts +2 -2
  54. package/components/form-generic/src/components/form-render/form-render.component.d.ts +4 -6
  55. package/components/form-generic/src/components/sd-feel-expression/sd-feel-expression.component.d.ts +2 -2
  56. package/components/form-generic/src/configurations/form.configuration.d.ts +1 -1
  57. package/components/form-generic/src/models/form-generic-component.model.d.ts +2 -2
  58. package/components/form-generic/src/services/form-generic.service.d.ts +1 -3
  59. package/components/import-excel/src/import-excel.model.d.ts +2 -2
  60. package/components/mini-editor/src/mini-editor.component.d.ts +4 -4
  61. package/components/modal/src/modal.component.d.ts +1 -1
  62. package/components/query-builder/src/query-builder.model.d.ts +4 -4
  63. package/components/splitter/src/splitter-state.service.d.ts +1 -1
  64. package/components/tab-router/src/models/tab-router.model.d.ts +2 -1
  65. package/components/tab-router/src/services/tab-router.service.d.ts +0 -1
  66. package/components/table/src/components/command/desktop-command.component.d.ts +8 -7
  67. package/components/table/src/components/command/pipes/command.pipe.d.ts +1 -12
  68. package/components/table/src/components/command/pipes/filter.pipe.d.ts +1 -8
  69. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +1 -0
  70. package/components/table/src/models/table-column.model.d.ts +3 -2
  71. package/components/table/src/pipes/selection-disabled.pipe.d.ts +1 -1
  72. package/components/table/src/services/config.service.d.ts +4 -5
  73. package/components/view/src/view.component.d.ts +1 -1
  74. package/configurations/src/sd-core.configuration.d.ts +2 -2
  75. package/fesm2022/sdcorejs-angular-components-anchor.mjs +15 -18
  76. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  77. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-components-avatar.mjs.map +1 -1
  79. package/fesm2022/sdcorejs-angular-components-badge.mjs +0 -1
  80. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  81. package/fesm2022/sdcorejs-angular-components-button.mjs +6 -7
  82. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  83. package/fesm2022/sdcorejs-angular-components-chart.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
  85. package/fesm2022/sdcorejs-angular-components-code-editor.mjs +1 -1
  86. package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
  87. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +17 -26
  88. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  89. package/fesm2022/sdcorejs-angular-components-editor.mjs +7 -2
  90. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  91. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +711 -426
  92. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  93. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +35 -18
  94. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  95. package/fesm2022/sdcorejs-angular-components-inform.mjs +0 -1
  96. package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
  97. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
  99. package/fesm2022/sdcorejs-angular-components-modal.mjs +25 -14
  100. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  101. package/fesm2022/sdcorejs-angular-components-operator.mjs +1 -1
  102. package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
  103. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
  104. package/fesm2022/sdcorejs-angular-components-preview.mjs +33 -29
  105. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +15 -28
  107. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +2 -3
  109. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-components-quick-action.mjs.map +1 -1
  111. package/fesm2022/sdcorejs-angular-components-section.mjs +0 -1
  112. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  113. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +3 -3
  114. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  115. package/fesm2022/sdcorejs-angular-components-splitter.mjs +4 -4
  116. package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
  117. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +31 -31
  119. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  121. package/fesm2022/sdcorejs-angular-components-table.mjs +60 -78
  122. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  123. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +6 -6
  125. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-components-view.mjs +3 -9
  127. package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
  128. package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
  129. package/fesm2022/sdcorejs-angular-directives.mjs +2 -7
  130. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  131. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +0 -3
  132. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  133. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  134. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +2 -2
  135. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  136. package/fesm2022/sdcorejs-angular-forms-chip.mjs +2 -2
  137. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  138. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +1 -3
  139. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  140. package/fesm2022/sdcorejs-angular-forms-date.mjs +2 -4
  141. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  142. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +2 -6
  143. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  144. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +3 -3
  145. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  146. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +1 -1
  147. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  148. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  149. package/fesm2022/sdcorejs-angular-forms-label.mjs +0 -1
  150. package/fesm2022/sdcorejs-angular-forms-label.mjs.map +1 -1
  151. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  152. package/fesm2022/sdcorejs-angular-forms-radio.mjs +12 -2
  153. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  154. package/fesm2022/sdcorejs-angular-forms-select.mjs +45 -14
  155. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  156. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  157. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +2 -2
  158. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  159. package/fesm2022/sdcorejs-angular-handlers.mjs +3 -6
  160. package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
  161. package/fesm2022/sdcorejs-angular-i18n.mjs +11 -7
  162. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  163. package/fesm2022/sdcorejs-angular-interceptors.mjs +0 -2
  164. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  165. package/fesm2022/sdcorejs-angular-modules-auth.mjs +14 -38
  166. package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
  167. package/fesm2022/sdcorejs-angular-modules-authom.mjs +14 -14
  168. package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +1 -1
  169. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +22 -11
  170. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
  171. package/fesm2022/sdcorejs-angular-modules-layout.mjs +30 -27
  172. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  173. package/fesm2022/sdcorejs-angular-modules-permission.mjs +5 -12
  174. package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
  175. package/fesm2022/sdcorejs-angular-pipes.mjs +71 -17
  176. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  177. package/fesm2022/sdcorejs-angular-services-api.mjs +0 -2
  178. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  179. package/fesm2022/sdcorejs-angular-services-cache.mjs +5 -11
  180. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  181. package/fesm2022/sdcorejs-angular-services-confirm.mjs +16 -21
  182. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  183. package/fesm2022/sdcorejs-angular-services-docx.mjs +2 -8
  184. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  185. package/fesm2022/sdcorejs-angular-services-excel.mjs +2 -2
  186. package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
  187. package/fesm2022/sdcorejs-angular-services-license.mjs +5 -12
  188. package/fesm2022/sdcorejs-angular-services-license.mjs.map +1 -1
  189. package/fesm2022/sdcorejs-angular-services-notify.mjs +70 -87
  190. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  191. package/fesm2022/sdcorejs-angular-services-storage.mjs +6 -15
  192. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  193. package/fesm2022/sdcorejs-angular-utilities-data-state.mjs.map +1 -1
  194. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +47 -5
  195. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
  196. package/fesm2022/sdcorejs-angular-utilities-models.mjs +18 -3
  197. package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
  198. package/fesm2022/sdcorejs-angular.mjs.map +1 -1
  199. package/forms/chip/src/chip.component.d.ts +2 -2
  200. package/forms/chip-calendar/src/chip-calendar.component.d.ts +2 -2
  201. package/forms/input-color/src/input-color.component.d.ts +2 -1
  202. package/forms/select/src/select-footer-action.directive.d.ts +5 -1
  203. package/forms/select/src/select.component.d.ts +4 -0
  204. package/modules/auth/guards/auth.guard.d.ts +2 -4
  205. package/modules/auth/guards/portal.guard.d.ts +2 -4
  206. package/modules/auth/services/auth.service.d.ts +3 -4
  207. package/modules/layout/modules/home/guards/home-redirect.guard.d.ts +3 -5
  208. package/modules/permission/src/configurations/permission.configuration.d.ts +3 -3
  209. package/modules/permission/src/guards/permission.guard.d.ts +2 -5
  210. package/package.json +2 -2
  211. package/pipes/index.d.ts +3 -0
  212. package/pipes/src/format-date.pipe.d.ts +8 -0
  213. package/pipes/src/format-datetime.pipe.d.ts +8 -0
  214. package/pipes/src/format-number.pipe.d.ts +1 -3
  215. package/pipes/src/view.pipe.d.ts +7 -0
  216. package/services/confirm/src/lib/components/dialog-confirm/dialog-confirm.component.d.ts +3 -3
  217. package/services/license/src/license.service.d.ts +2 -3
  218. package/services/notify/src/components/toast/toast.component.d.ts +9 -13
  219. package/services/notify/src/notify.service.d.ts +4 -5
  220. package/services/storage/src/storage.service.d.ts +3 -4
  221. package/utilities/extensions/index.d.ts +1 -0
  222. package/utilities/extensions/src/object.extension.d.ts +8 -0
  223. package/components/document-builder/src/plugins/paste-handler/filters/replacetabswithinprewithspaces.d.ts +0 -24
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services-license.mjs","sources":["../../../projects/sdcorejs-angular/services/license/src/license.service.ts","../../../projects/sdcorejs-angular/services/license/sdcorejs-angular-services-license.ts"],"sourcesContent":["import { Inject, Injectable, Optional } from '@angular/core';\nimport { ISdCoreConfiguration, SD_CORE_CONFIGURATION } from '@sdcorejs/angular/configurations';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdLicenseService {\n readonly #SALT = 'angular-core-976e2fa6f8b44dadbc63f87b057a331f';\n #isValid = false;\n\n constructor(\n @Inject(SD_CORE_CONFIGURATION)\n @Optional()\n private readonly coreConfiguration: ISdCoreConfiguration | undefined\n ) {\n this.#verify();\n }\n\n enforceLicense = () => {\n if (!this.#isValid) {\n this.#throwSecurityError();\n }\n };\n\n #verify = () => {\n const hostname = window.location.hostname; // Ví dụ: store.uat.nexa.mobi\n\n // 1. Bypass Localhost\n if (this.#isLocalhost(hostname)) {\n console.log('%c [SdAngularCore] Dev Mode ', 'background: #222; color: #bada55');\n this.#isValid = true;\n return;\n }\n\n const configKeys = this.#getConfiguredKeys();\n if (configKeys.length === 0) {\n this.#isValid = false;\n this.#throwSecurityError();\n return;\n }\n\n // 2. CHECK EXACT MATCH (Ưu tiên khớp chính xác 100%)\n // Hash chuỗi: \"store.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(hostname, configKeys)) {\n this.#isValid = true;\n return;\n }\n\n // 3. CHECK WILDCARD MATCH (Thử các trường hợp có dấu *)\n const parts = hostname.split('.'); // [\"store\", \"uat\", \"nexa\", \"mobi\"]\n\n // Lặp để cắt dần subdomain bên trái và thêm \"*.\" vào\n // Điều kiện > 2 để đảm bảo không check các case vô nghĩa như *.mobi hay *.com\n while (parts.length > 2) {\n parts.shift(); // Bỏ phần đầu: [\"uat\", \"nexa\", \"mobi\"]\n \n // Tạo domain wildcard: \"*.uat.nexa.mobi\"\n const wildcardDomain = '*.' + parts.join('.'); \n \n // Hash chuỗi: \"*.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(wildcardDomain, configKeys)) {\n this.#isValid = true;\n return;\n }\n }\n\n // Nếu chạy hết vòng lặp mà không khớp cái nào\n this.#isValid = false;\n this.#throwSecurityError();\n };\n\n // Helper để check xem hash của input có nằm trong list key không\n #checkMatch = (inputString: string, validKeys: string[]): boolean => {\n const hash = this.#generateHash(inputString + this.#SALT);\n return validKeys.includes(hash);\n };\n\n #getConfiguredKeys = (): string[] => {\n const keyOrKeys = this.coreConfiguration?.licenseKey;\n if (!keyOrKeys) return [];\n return Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys];\n };\n\n #isLocalhost = (domain: string) => {\n return domain === 'localhost' || domain === '127.0.0.1' || domain.includes('localhost');\n };\n\n #generateHash = (input: string): string => {\n let hash = 0;\n if (input.length === 0) return hash.toString();\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return btoa(hash.toString() + 'signed');\n };\n\n #throwSecurityError = () => {\n throw new Error(`[Security] Unauthorized usage of UI Lib on domain: ${window.location.hostname}`);\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-services-license.mjs","sources":["../../../projects/sdcorejs-angular/services/license/src/license.service.ts","../../../projects/sdcorejs-angular/services/license/sdcorejs-angular-services-license.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { ISdCoreConfiguration, SD_CORE_CONFIGURATION } from '@sdcorejs/angular/configurations';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdLicenseService {\n readonly #SALT = 'angular-core-976e2fa6f8b44dadbc63f87b057a331f';\n #isValid = false;\n private readonly coreConfiguration: ISdCoreConfiguration | null = inject(SD_CORE_CONFIGURATION, { optional: true });\n\n constructor() {\n this.#verify();\n }\n\n enforceLicense = () => {\n if (!this.#isValid) {\n this.#throwSecurityError();\n }\n };\n\n #verify = () => {\n const hostname = window.location.hostname; // Ví dụ: store.uat.nexa.mobi\n\n // 1. Bypass Localhost\n if (this.#isLocalhost(hostname)) {\n this.#isValid = true;\n return;\n }\n\n const configKeys = this.#getConfiguredKeys();\n if (configKeys.length === 0) {\n this.#isValid = false;\n this.#throwSecurityError();\n return;\n }\n\n // 2. CHECK EXACT MATCH (Ưu tiên khớp chính xác 100%)\n // Hash chuỗi: \"store.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(hostname, configKeys)) {\n this.#isValid = true;\n return;\n }\n\n // 3. CHECK WILDCARD MATCH (Thử các trường hợp có dấu *)\n const parts = hostname.split('.'); // [\"store\", \"uat\", \"nexa\", \"mobi\"]\n\n // Lặp để cắt dần subdomain bên trái và thêm \"*.\" vào\n // Điều kiện > 2 để đảm bảo không check các case vô nghĩa như *.mobi hay *.com\n while (parts.length > 2) {\n parts.shift(); // Bỏ phần đầu: [\"uat\", \"nexa\", \"mobi\"]\n\n // Tạo domain wildcard: \"*.uat.nexa.mobi\"\n const wildcardDomain = '*.' + parts.join('.');\n\n // Hash chuỗi: \"*.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(wildcardDomain, configKeys)) {\n this.#isValid = true;\n return;\n }\n }\n\n // Nếu chạy hết vòng lặp mà không khớp cái nào\n this.#isValid = false;\n this.#throwSecurityError();\n };\n\n // Helper để check xem hash của input có nằm trong list key không\n #checkMatch = (inputString: string, validKeys: string[]): boolean => {\n const hash = this.#generateHash(inputString + this.#SALT);\n return validKeys.includes(hash);\n };\n\n #getConfiguredKeys = (): string[] => {\n const keyOrKeys = this.coreConfiguration?.licenseKey;\n if (!keyOrKeys) return [];\n return Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys];\n };\n\n #isLocalhost = (domain: string) => {\n return domain === 'localhost' || domain === '127.0.0.1' || domain.includes('localhost');\n };\n\n #generateHash = (input: string): string => {\n let hash = 0;\n if (input.length === 0) return hash.toString();\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return btoa(hash.toString() + 'signed');\n };\n\n #throwSecurityError = () => {\n throw new Error(`[Security] Unauthorized usage of UI Lib on domain: ${window.location.hostname}`);\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,69 +1,62 @@
1
1
  import { CommonModule, DOCUMENT } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { signal, SecurityContext, HostListener, Input, Component, createComponent, Inject, Injectable } from '@angular/core';
3
+ import { signal, computed, SecurityContext, HostListener, Input, ChangeDetectionStrategy, Component, inject, ApplicationRef, EnvironmentInjector, createComponent, Injectable } from '@angular/core';
4
4
  import { Utilities } from '@sdcorejs/utils/fns';
5
- import { trigger, transition, style, animate } from '@angular/animations';
6
- import { TranslatePipe } from '@sdcorejs/angular/i18n';
5
+ import { TranslatePipe, I18nService } from '@sdcorejs/angular/i18n';
7
6
  import * as i2 from '@angular/platform-browser';
8
7
 
8
+ const TOAST_EXIT_ANIMATION_MS = 200;
9
9
  class ToastComponent {
10
10
  notifyService;
11
11
  sanitizer;
12
12
  data;
13
13
  isExpanded = signal(false);
14
+ isClosing = signal(false);
15
+ hostClasses = computed(() => (this.isClosing() ? 'bg-white sd-toast sd-toast--closing' : 'bg-white sd-toast'));
14
16
  MAX_SHOW = 2;
15
- // --- Logic Timer ---
16
- timer;
17
- start;
18
- remaining;
17
+ timer = null;
18
+ closeTimer = null;
19
+ start = 0;
20
+ remaining = 0;
19
21
  constructor(notifyService, sanitizer) {
20
22
  this.notifyService = notifyService;
21
23
  this.sanitizer = sanitizer;
22
24
  }
23
- /**
24
- * Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip
25
- * `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh
26
- * `data.html === true`; nhánh mặc định render text (auto-escape) nên không
27
- * có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.
28
- */
29
- sanitizeHtml(value) {
30
- return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
31
- }
32
- /** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */
33
- get singleMessage() {
34
- return this.data.message;
35
- }
36
25
  ngOnInit() {
37
- // Khởi tạo thời gian còn lại bằng duration ban đầu
38
26
  this.remaining = this.data.duration;
39
- // Bắt đầu đếm ngược ngay khi hiện ra
40
27
  this.resumeTimer();
41
28
  }
42
29
  ngOnDestroy() {
43
- clearTimeout(this.timer);
30
+ this.clearAutoDismissTimer();
31
+ this.clearCloseTimer();
44
32
  }
45
- // --- Logic Pause/Resume ---
46
33
  pauseTimer() {
47
- if (this.timer) {
48
- // Xóa timer hiện tại
49
- clearTimeout(this.timer);
50
- this.timer = null;
51
- // Tính toán thời gian đã trôi qua để trừ đi
52
- this.remaining -= Date.now() - this.start;
34
+ if (!this.timer || this.isClosing()) {
35
+ return;
53
36
  }
37
+ clearTimeout(this.timer);
38
+ this.timer = null;
39
+ this.remaining = Math.max(this.remaining - (Date.now() - this.start), 0);
54
40
  }
55
41
  resumeTimer() {
56
- // Chỉ chạy tiếp nếu còn thời gian và timer chưa chạy
57
- if (this.remaining > 0 && !this.timer) {
58
- this.start = Date.now();
59
- this.timer = setTimeout(() => {
60
- this.close();
61
- }, this.remaining);
42
+ if (this.remaining <= 0 || this.timer || this.isClosing()) {
43
+ return;
62
44
  }
45
+ this.start = Date.now();
46
+ this.timer = setTimeout(() => {
47
+ this.close();
48
+ }, this.remaining);
63
49
  }
64
- // --- Logic cũ ---
65
50
  close() {
66
- this.notifyService.remove(this.data.id);
51
+ if (this.isClosing()) {
52
+ return;
53
+ }
54
+ this.clearAutoDismissTimer();
55
+ this.isClosing.set(true);
56
+ this.closeTimer = setTimeout(() => {
57
+ this.closeTimer = null;
58
+ this.notifyService.remove(this.data.id);
59
+ }, TOAST_EXIT_ANIMATION_MS);
67
60
  }
68
61
  onActionClick() {
69
62
  this.data.onAction?.();
@@ -71,6 +64,12 @@ class ToastComponent {
71
64
  toggleExpand() {
72
65
  this.isExpanded.update(v => !v);
73
66
  }
67
+ sanitizeHtml(value) {
68
+ return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
69
+ }
70
+ get singleMessage() {
71
+ return this.data.message;
72
+ }
74
73
  get isMultiMessage() {
75
74
  return Array.isArray(this.data.message);
76
75
  }
@@ -89,48 +88,38 @@ class ToastComponent {
89
88
  get hasMore() {
90
89
  return this.isMultiMessage && this.messages.length > this.MAX_SHOW;
91
90
  }
92
- /** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */
93
91
  get autoId() {
94
92
  return `services-notify-toast-${this.data.type}`;
95
93
  }
96
- /** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */
97
94
  get dataMessage() {
98
95
  return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;
99
96
  }
97
+ clearAutoDismissTimer() {
98
+ if (!this.timer) {
99
+ return;
100
+ }
101
+ clearTimeout(this.timer);
102
+ this.timer = null;
103
+ }
104
+ clearCloseTimer() {
105
+ if (!this.closeTimer) {
106
+ return;
107
+ }
108
+ clearTimeout(this.closeTimer);
109
+ this.closeTimer = null;
110
+ }
100
111
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ToastComponent, deps: [{ token: SdNotifyService }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "@toastAnimation": "true", "class": "\"bg-white sd-toast\"", "attr.data-autoid": "autoId", "attr.data-type": "data.type", "attr.data-title": "data.title ?? null", "attr.data-message": "dataMessage" } }, ngImport: i0, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: m\u1EB7c \u0111\u1ECBnh render TEXT (auto-escape) \u2014 an to\u00E0n. Ch\u1EC9 render HTML\n (\u0111\u00E3 sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], animations: [
102
- trigger('toastAnimation', [
103
- transition(':enter', [
104
- style({ transform: 'translateX(100%)', opacity: 0 }),
105
- animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
106
- ]),
107
- transition(':leave', [
108
- animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))
109
- ])
110
- ])
111
- ] });
112
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "class": "hostClasses()", "attr.data-autoid": "autoId", "attr.data-type": "data.type", "attr.data-title": "data.title ?? null", "attr.data-message": "dataMessage" } }, ngImport: i0, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">&times;</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto;animation:sd-toast-enter .3s ease-out}:host(.sd-toast--closing){pointer-events:none;animation:sd-toast-leave .2s ease-in forwards}@keyframes sd-toast-enter{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes sd-toast-leave{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){:host,:host(.sd-toast--closing){animation:none}}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
112
113
  }
113
114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ToastComponent, decorators: [{
114
115
  type: Component,
115
- args: [{ selector: 'toast', standalone: true, imports: [CommonModule, TranslatePipe], animations: [
116
- trigger('toastAnimation', [
117
- transition(':enter', [
118
- style({ transform: 'translateX(100%)', opacity: 0 }),
119
- animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
120
- ]),
121
- transition(':leave', [
122
- animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))
123
- ])
124
- ])
125
- ], host: {
126
- '[@toastAnimation]': 'true',
127
- '[class]': '"bg-white sd-toast"',
128
- // E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.
116
+ args: [{ selector: 'toast', standalone: true, imports: [CommonModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
117
+ '[class]': 'hostClasses()',
129
118
  '[attr.data-autoid]': 'autoId',
130
119
  '[attr.data-type]': 'data.type',
131
120
  '[attr.data-title]': 'data.title ?? null',
132
- '[attr.data-message]': 'dataMessage'
133
- }, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">\u2715</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: m\u1EB7c \u0111\u1ECBnh render TEXT (auto-escape) \u2014 an to\u00E0n. Ch\u1EC9 render HTML\n (\u0111\u00E3 sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"] }]
121
+ '[attr.data-message]': 'dataMessage',
122
+ }, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">&times;</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto;animation:sd-toast-enter .3s ease-out}:host(.sd-toast--closing){pointer-events:none;animation:sd-toast-leave .2s ease-in forwards}@keyframes sd-toast-enter{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes sd-toast-leave{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){:host,:host(.sd-toast--closing){animation:none}}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"] }]
134
123
  }], ctorParameters: () => [{ type: SdNotifyService }, { type: i2.DomSanitizer }], propDecorators: { data: [{
135
124
  type: Input,
136
125
  args: [{ required: true }]
@@ -165,9 +154,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
165
154
  }] });
166
155
 
167
156
  class SdNotifyService {
168
- appRef;
169
- injector;
170
- document;
171
157
  toasts = signal([]);
172
158
  // Constants
173
159
  #DEBOUNCE_TIME = 500;
@@ -177,10 +163,11 @@ class SdNotifyService {
177
163
  // State
178
164
  #buffer = {};
179
165
  #timers = {};
180
- constructor(appRef, injector, document) {
181
- this.appRef = appRef;
182
- this.injector = injector;
183
- this.document = document;
166
+ appRef = inject(ApplicationRef);
167
+ injector = inject(EnvironmentInjector);
168
+ document = inject(DOCUMENT);
169
+ #i18n = inject(I18nService);
170
+ constructor() {
184
171
  this.#initContainer();
185
172
  }
186
173
  #initContainer() {
@@ -206,14 +193,14 @@ class SdNotifyService {
206
193
  this.#addToBuffer('error', message, option);
207
194
  }
208
195
  remove(id) {
209
- this.toasts.update((current) => current.filter((t) => t.id !== id));
196
+ this.toasts.update(current => current.filter(t => t.id !== id));
210
197
  }
211
198
  clearAll() {
212
199
  this.toasts.set([]);
213
200
  this.#clearAllTimers();
214
201
  }
215
202
  clearByType(type) {
216
- this.toasts.update((current) => current.filter((t) => t.type !== type));
203
+ this.toasts.update(current => current.filter(t => t.type !== type));
217
204
  }
218
205
  // Private helpers
219
206
  #addImmediate(type, message, option) {
@@ -225,9 +212,9 @@ class SdNotifyService {
225
212
  duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,
226
213
  actionLabel: option?.actionLabel,
227
214
  onAction: option?.onAction,
228
- html: option?.html
215
+ html: option?.html,
229
216
  };
230
- this.toasts.update((current) => {
217
+ this.toasts.update(current => {
231
218
  const updated = [newToast, ...current];
232
219
  return updated.slice(0, this.#MAX_TOASTS);
233
220
  });
@@ -253,8 +240,7 @@ class SdNotifyService {
253
240
  this.#timers[type] = null;
254
241
  if (messages.length === 0)
255
242
  return;
256
- const typeLabel = type === 'error' ? 'Error' : 'Warning';
257
- const title = option?.title ?? typeLabel;
243
+ const title = option?.title ?? this.#i18n.t(`core.notify.type.${type}`);
258
244
  const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;
259
245
  const newToast = {
260
246
  id: Utilities.generateUuid(),
@@ -264,9 +250,9 @@ class SdNotifyService {
264
250
  duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,
265
251
  actionLabel: option?.actionLabel,
266
252
  onAction: option?.onAction,
267
- html: option?.html
253
+ html: option?.html,
268
254
  };
269
- this.toasts.update((current) => {
255
+ this.toasts.update(current => {
270
256
  const updated = [newToast, ...current];
271
257
  return updated.slice(0, this.#MAX_TOASTS);
272
258
  });
@@ -278,7 +264,7 @@ class SdNotifyService {
278
264
  });
279
265
  this.#timers = {};
280
266
  }
281
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, deps: [{ token: i0.ApplicationRef }, { token: i0.EnvironmentInjector }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
267
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
282
268
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, providedIn: 'root' });
283
269
  }
284
270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, decorators: [{
@@ -286,10 +272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
286
272
  args: [{
287
273
  providedIn: 'root',
288
274
  }]
289
- }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.EnvironmentInjector }, { type: Document, decorators: [{
290
- type: Inject,
291
- args: [DOCUMENT]
292
- }] }] });
275
+ }], ctorParameters: () => [] });
293
276
 
294
277
  /**
295
278
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { Component, Input, signal, HostListener, OnInit, OnDestroy, SecurityContext } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { animate, style, transition, trigger } from '@angular/animations';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n animations: [\n trigger('toastAnimation', [\n transition(':enter', [\n style({ transform: 'translateX(100%)', opacity: 0 }),\n animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))\n ]),\n transition(':leave', [\n animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))\n ])\n ])\n ],\n host: {\n '[@toastAnimation]': 'true',\n '[class]': '\"bg-white sd-toast\"',\n // E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.\n '[attr.data-autoid]': 'autoId',\n '[attr.data-type]': 'data.type',\n '[attr.data-title]': 'data.title ?? null',\n '[attr.data-message]': 'dataMessage'\n }\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n \n isExpanded = signal(false);\n readonly MAX_SHOW = 2;\n\n // --- Logic Timer ---\n private timer: any;\n private start!: number;\n private remaining!: number;\n\n constructor(\n private notifyService: SdNotifyService,\n private sanitizer: DomSanitizer\n ) {}\n\n /**\n * Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip\n * `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh\n * `data.html === true`; nhánh mặc định render text (auto-escape) nên không\n * có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.\n */\n sanitizeHtml(value: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';\n }\n\n /** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */\n get singleMessage(): string {\n return this.data.message as string;\n }\n\n ngOnInit() {\n // Khởi tạo thời gian còn lại bằng duration ban đầu\n this.remaining = this.data.duration;\n // Bắt đầu đếm ngược ngay khi hiện ra\n this.resumeTimer();\n }\n\n ngOnDestroy() {\n clearTimeout(this.timer);\n }\n\n // --- Logic Pause/Resume ---\n\n @HostListener('mouseenter')\n pauseTimer() {\n if (this.timer) {\n // Xóa timer hiện tại\n clearTimeout(this.timer);\n this.timer = null;\n // Tính toán thời gian đã trôi qua để trừ đi\n this.remaining -= Date.now() - this.start;\n }\n }\n\n @HostListener('mouseleave')\n resumeTimer() {\n // Chỉ chạy tiếp nếu còn thời gian và timer chưa chạy\n if (this.remaining > 0 && !this.timer) {\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n }\n\n // --- Logic cũ ---\n\n close() {\n this.notifyService.remove(this.data.id);\n }\n\n onActionClick() {\n this.data.onAction?.();\n }\n\n toggleExpand() {\n this.isExpanded.update(v => !v);\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n\n /** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */\n get autoId(): string {\n return `services-notify-toast-${this.data.type}`;\n }\n\n /** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */\n get dataMessage(): string {\n return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;\n }\n}","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">✕</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: mặc định render TEXT (auto-escape) — an toàn. Chỉ render HTML\n (đã sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n ApplicationRef,\n createComponent,\n EnvironmentInjector,\n Inject,\n Injectable,\n signal\n} from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n\n constructor(\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n @Inject(DOCUMENT) private document: Document\n ) {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update((current) => current.filter((t) => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update((current) => current.filter((t) => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n \n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n \n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const typeLabel = type === 'error' ? 'Error' : 'Warning';\n const title = option?.title ?? typeLabel;\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages, \n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n HostListener,\n Input,\n OnDestroy,\n OnInit,\n SecurityContext,\n computed,\n signal,\n} from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\n\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\nconst TOAST_EXIT_ANIMATION_MS = 200;\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrl: './toast.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.data-autoid]': 'autoId',\n '[attr.data-type]': 'data.type',\n '[attr.data-title]': 'data.title ?? null',\n '[attr.data-message]': 'dataMessage',\n },\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n\n readonly isExpanded = signal(false);\n readonly isClosing = signal(false);\n readonly hostClasses = computed(() => (this.isClosing() ? 'bg-white sd-toast sd-toast--closing' : 'bg-white sd-toast'));\n readonly MAX_SHOW = 2;\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private start = 0;\n private remaining = 0;\n\n constructor(\n private notifyService: SdNotifyService,\n private sanitizer: DomSanitizer\n ) {}\n\n ngOnInit(): void {\n this.remaining = this.data.duration;\n this.resumeTimer();\n }\n\n ngOnDestroy(): void {\n this.clearAutoDismissTimer();\n this.clearCloseTimer();\n }\n\n @HostListener('mouseenter')\n pauseTimer(): void {\n if (!this.timer || this.isClosing()) {\n return;\n }\n\n clearTimeout(this.timer);\n this.timer = null;\n this.remaining = Math.max(this.remaining - (Date.now() - this.start), 0);\n }\n\n @HostListener('mouseleave')\n resumeTimer(): void {\n if (this.remaining <= 0 || this.timer || this.isClosing()) {\n return;\n }\n\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n\n close(): void {\n if (this.isClosing()) {\n return;\n }\n\n this.clearAutoDismissTimer();\n this.isClosing.set(true);\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.notifyService.remove(this.data.id);\n }, TOAST_EXIT_ANIMATION_MS);\n }\n\n onActionClick(): void {\n this.data.onAction?.();\n }\n\n toggleExpand(): void {\n this.isExpanded.update(v => !v);\n }\n\n sanitizeHtml(value: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';\n }\n\n get singleMessage(): string {\n return this.data.message as string;\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n\n get autoId(): string {\n return `services-notify-toast-${this.data.type}`;\n }\n\n get dataMessage(): string {\n return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;\n }\n\n private clearAutoDismissTimer(): void {\n if (!this.timer) {\n return;\n }\n\n clearTimeout(this.timer);\n this.timer = null;\n }\n\n private clearCloseTimer(): void {\n if (!this.closeTimer) {\n return;\n }\n\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n}\n","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">&times;</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport { ApplicationRef, createComponent, EnvironmentInjector, inject, Injectable, signal } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\n\nimport { I18nService } from '@sdcorejs/angular/i18n';\n\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n private readonly appRef = inject(ApplicationRef);\n private readonly injector = inject(EnvironmentInjector);\n private readonly document = inject(DOCUMENT);\n readonly #i18n = inject(I18nService);\n\n constructor() {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update(current => current.filter(t => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update(current => current.filter(t => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html,\n };\n\n this.toasts.update(current => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n\n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n\n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const title = option?.title ?? this.#i18n.t(`core.notify.type.${type}`);\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages,\n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html,\n };\n\n this.toasts.update(current => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":"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"}
@@ -1,20 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Inject, Optional, Injectable } from '@angular/core';
2
+ import { InjectionToken, inject, Injectable } from '@angular/core';
3
3
  import { Utilities } from '@sdcorejs/utils/fns';
4
4
  import { BehaviorSubject } from 'rxjs';
5
5
  import { map } from 'rxjs/operators';
6
6
 
7
7
  const SD_STORAGE_CONFIG = new InjectionToken('storage.configuration');
8
8
 
9
- /* eslint-disable @typescript-eslint/no-explicit-any */
10
9
  class SdStorageService {
11
- configuration;
12
10
  // Dùng Map để quản lý bộ nhớ tốt hơn Record
13
11
  #memoryCache = new Map();
14
12
  #subjects = new Map();
15
- constructor(configuration) {
16
- this.configuration = configuration;
17
- }
13
+ configuration = inject(SD_STORAGE_CONFIG, { optional: true });
18
14
  create(key, option) {
19
15
  if (!key)
20
16
  throw new Error('Key is required');
@@ -71,7 +67,7 @@ class SdStorageService {
71
67
  setSilent,
72
68
  has,
73
69
  remove,
74
- // @ts-ignore: Bổ sung vào interface nếu cần
70
+ // @ts-expect-error: Bổ sung vào interface nếu cần
75
71
  destroy,
76
72
  subject: subject,
77
73
  observer: subject.asObservable().pipe(map(() => get())),
@@ -100,7 +96,7 @@ class SdStorageService {
100
96
  // Convert string date back to Date object nếu cần
101
97
  entry = {
102
98
  data: parsed.data,
103
- createdOn: new Date(parsed.createdOn)
99
+ createdOn: new Date(parsed.createdOn),
104
100
  };
105
101
  // Sync ngược vào RAM
106
102
  this.#memoryCache.set(key, entry);
@@ -151,7 +147,7 @@ class SdStorageService {
151
147
  return val;
152
148
  return JSON.parse(JSON.stringify(val));
153
149
  }
154
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, deps: [{ token: SD_STORAGE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
150
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
155
151
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, providedIn: 'root' });
156
152
  }
157
153
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, decorators: [{
@@ -159,12 +155,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
159
155
  args: [{
160
156
  providedIn: 'root',
161
157
  }]
162
- }], ctorParameters: () => [{ type: undefined, decorators: [{
163
- type: Inject,
164
- args: [SD_STORAGE_CONFIG]
165
- }, {
166
- type: Optional
167
- }] }] });
158
+ }] });
168
159
 
169
160
  /**
170
161
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services-storage.mjs","sources":["../../../projects/sdcorejs-angular/services/storage/src/storage.model.ts","../../../projects/sdcorejs-angular/services/storage/src/storage.service.ts","../../../projects/sdcorejs-angular/services/storage/sdcorejs-angular-services-storage.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface SdStorageOption<T = any> {\n type?: 'session';\n default?: T;\n args?: Record<string, any>;\n}\n\nexport interface SdStorage<T = any> {\n get: () => T;\n set: (data: T) => void;\n // Ghi vào storage nhưng KHÔNG emit subject. Dùng cho thay đổi UI-only\n // (vd: column width) để tránh re-trigger các subscriber gây reload data.\n setSilent: (data: T) => void;\n has: () => boolean;\n remove: () => void;\n subject: BehaviorSubject<T>;\n observer: Observable<T>;\n}\n\nexport interface ISdStorageConfiguration {\n key?: (key: string) => string;\n}\n\nexport const SD_STORAGE_CONFIG = new InjectionToken<ISdStorageConfiguration>('storage.configuration');\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Inject, Injectable, Optional } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { BehaviorSubject } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { ISdStorageConfiguration, SD_STORAGE_CONFIG, SdStorage, SdStorageOption } from './storage.model';\n\ninterface StorageCacheEntry<T> {\n data: T;\n createdOn: Date;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdStorageService {\n // Dùng Map để quản lý bộ nhớ tốt hơn Record\n #memoryCache = new Map<string, StorageCacheEntry<any>>();\n #subjects = new Map<string, BehaviorSubject<any>>();\n\n constructor(\n @Inject(SD_STORAGE_CONFIG)\n @Optional()\n private configuration: ISdStorageConfiguration\n ) {}\n\n create<T = any>(key: string | object, option?: SdStorageOption<T>): SdStorage<T> {\n if (!key) throw new Error('Key is required');\n\n // 1. Xử lý Key\n let hashKey: string;\n if (typeof key === 'string') {\n hashKey = key;\n } else if (typeof key === 'object') {\n hashKey = Utilities.hash(key);\n } else {\n throw new Error('Invalid key type');\n }\n\n if (this.configuration?.key) {\n hashKey = this.configuration.key(hashKey);\n }\n\n // 2. Init Subject: Đọc dữ liệu từ Storage ngay lập tức để Subject có giá trị đúng\n if (!this.#subjects.has(hashKey)) {\n const existingData = this.#internalGet<T>(hashKey, option);\n // Fix Type: as T | undefined\n this.#subjects.set(hashKey, new BehaviorSubject<T | undefined>(existingData ?? option?.default));\n }\n\n const subject = this.#subjects.get(hashKey)!;\n\n // 3. Define Helpers\n const get = () => {\n const val = this.#internalGet<T>(hashKey, option);\n return (val ?? option?.default) as T;\n };\n\n const set = (data: T) => {\n this.#internalSet(hashKey, data, option);\n subject.next(data);\n };\n\n const setSilent = (data: T) => {\n this.#internalSet(hashKey, data, option);\n // Cố tình KHÔNG gọi subject.next — consumer dùng kênh riêng để thông báo\n };\n\n const has = () => {\n return this.#internalGet<T>(hashKey, option) !== undefined;\n };\n\n const remove = () => {\n this.#internalRemove(hashKey, option);\n subject.next(undefined);\n };\n\n // Hàm dọn dẹp bộ nhớ (quan trọng)\n const destroy = () => {\n subject.complete();\n this.#subjects.delete(hashKey);\n this.#memoryCache.delete(hashKey);\n };\n\n return {\n get,\n set,\n setSilent,\n has,\n remove,\n // @ts-ignore: Bổ sung vào interface nếu cần\n destroy,\n subject: subject,\n observer: subject.asObservable().pipe(map(() => get())),\n };\n }\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Helper xác định loại Storage đang dùng\n */\n #getStorage(option?: SdStorageOption<any>): Storage {\n return option?.type === 'session' ? sessionStorage : localStorage;\n }\n\n /**\n * Đọc dữ liệu: Memory -> Storage -> Parse\n */\n #internalGet<T>(key: string, option?: SdStorageOption<T>): T | undefined {\n // 1. Check Memory (Nhanh nhất)\n let entry = this.#memoryCache.get(key);\n\n // 2. Check Storage (Nếu memory chưa có hoặc mới F5)\n if (!entry) {\n const storage = this.#getStorage(option);\n const raw = storage.getItem(key);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n // Convert string date back to Date object nếu cần\n entry = {\n data: parsed.data,\n createdOn: new Date(parsed.createdOn)\n };\n // Sync ngược vào RAM\n this.#memoryCache.set(key, entry);\n } catch (e) {\n console.warn('Storage parse error', e);\n this.#internalRemove(key, option); // Dữ liệu lỗi -> Xóa\n }\n }\n }\n\n if (!entry) return undefined;\n\n return this.#deepClone(entry.data);\n }\n\n /**\n * Ghi dữ liệu: Memory -> Storage\n */\n #internalSet<T>(key: string, data: T, option?: SdStorageOption<T>): void {\n const clonedData = this.#deepClone(data);\n\n const entry: StorageCacheEntry<T> = {\n data: clonedData,\n createdOn: new Date(),\n };\n\n // 1. Lưu Memory\n this.#memoryCache.set(key, entry);\n\n // 2. Lưu Storage\n const storage = this.#getStorage(option);\n try {\n storage.setItem(key, JSON.stringify(entry));\n } catch (e) {\n console.error('Storage quota exceeded', e);\n }\n }\n\n /**\n * Xóa dữ liệu\n */\n #internalRemove(key: string, option?: SdStorageOption<any>): void {\n this.#memoryCache.delete(key);\n const storage = this.#getStorage(option);\n storage.removeItem(key);\n }\n\n /**\n * Deep Clone an toàn\n */\n #deepClone<T>(val: T): T {\n if (val === undefined || val === null) return val;\n return JSON.parse(JSON.stringify(val));\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-services-storage.mjs","sources":["../../../projects/sdcorejs-angular/services/storage/src/storage.model.ts","../../../projects/sdcorejs-angular/services/storage/src/storage.service.ts","../../../projects/sdcorejs-angular/services/storage/sdcorejs-angular-services-storage.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface SdStorageOption<T = any> {\n type?: 'session';\n default?: T;\n args?: Record<string, any>;\n}\n\nexport interface SdStorage<T = any> {\n get: () => T;\n set: (data: T) => void;\n // Ghi vào storage nhưng KHÔNG emit subject. Dùng cho thay đổi UI-only\n // (vd: column width) để tránh re-trigger các subscriber gây reload data.\n setSilent: (data: T) => void;\n has: () => boolean;\n remove: () => void;\n subject: BehaviorSubject<T>;\n observer: Observable<T>;\n}\n\nexport interface ISdStorageConfiguration {\n key?: (key: string) => string;\n}\n\nexport const SD_STORAGE_CONFIG = new InjectionToken<ISdStorageConfiguration>('storage.configuration');\n","import { inject, Injectable } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { BehaviorSubject } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { ISdStorageConfiguration, SD_STORAGE_CONFIG, SdStorage, SdStorageOption } from './storage.model';\n\ninterface StorageCacheEntry<T> {\n data: T;\n createdOn: Date;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdStorageService {\n // Dùng Map để quản lý bộ nhớ tốt hơn Record\n #memoryCache = new Map<string, StorageCacheEntry<any>>();\n #subjects = new Map<string, BehaviorSubject<any>>();\n private readonly configuration: ISdStorageConfiguration | null = inject(SD_STORAGE_CONFIG, { optional: true });\n\n create<T = any>(key: string | object, option?: SdStorageOption<T>): SdStorage<T> {\n if (!key) throw new Error('Key is required');\n\n // 1. Xử lý Key\n let hashKey: string;\n if (typeof key === 'string') {\n hashKey = key;\n } else if (typeof key === 'object') {\n hashKey = Utilities.hash(key);\n } else {\n throw new Error('Invalid key type');\n }\n\n if (this.configuration?.key) {\n hashKey = this.configuration.key(hashKey);\n }\n\n // 2. Init Subject: Đọc dữ liệu từ Storage ngay lập tức để Subject có giá trị đúng\n if (!this.#subjects.has(hashKey)) {\n const existingData = this.#internalGet<T>(hashKey, option);\n // Fix Type: as T | undefined\n this.#subjects.set(hashKey, new BehaviorSubject<T | undefined>(existingData ?? option?.default));\n }\n\n const subject = this.#subjects.get(hashKey)!;\n\n // 3. Define Helpers\n const get = () => {\n const val = this.#internalGet<T>(hashKey, option);\n return (val ?? option?.default) as T;\n };\n\n const set = (data: T) => {\n this.#internalSet(hashKey, data, option);\n subject.next(data);\n };\n\n const setSilent = (data: T) => {\n this.#internalSet(hashKey, data, option);\n // Cố tình KHÔNG gọi subject.next — consumer dùng kênh riêng để thông báo\n };\n\n const has = () => {\n return this.#internalGet<T>(hashKey, option) !== undefined;\n };\n\n const remove = () => {\n this.#internalRemove(hashKey, option);\n subject.next(undefined);\n };\n\n // Hàm dọn dẹp bộ nhớ (quan trọng)\n const destroy = () => {\n subject.complete();\n this.#subjects.delete(hashKey);\n this.#memoryCache.delete(hashKey);\n };\n\n return {\n get,\n set,\n setSilent,\n has,\n remove,\n // @ts-expect-error: Bổ sung vào interface nếu cần\n destroy,\n subject: subject,\n observer: subject.asObservable().pipe(map(() => get())),\n };\n }\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Helper xác định loại Storage đang dùng\n */\n #getStorage(option?: SdStorageOption<any>): Storage {\n return option?.type === 'session' ? sessionStorage : localStorage;\n }\n\n /**\n * Đọc dữ liệu: Memory -> Storage -> Parse\n */\n #internalGet<T>(key: string, option?: SdStorageOption<T>): T | undefined {\n // 1. Check Memory (Nhanh nhất)\n let entry = this.#memoryCache.get(key);\n\n // 2. Check Storage (Nếu memory chưa có hoặc mới F5)\n if (!entry) {\n const storage = this.#getStorage(option);\n const raw = storage.getItem(key);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n // Convert string date back to Date object nếu cần\n entry = {\n data: parsed.data,\n createdOn: new Date(parsed.createdOn),\n };\n // Sync ngược vào RAM\n this.#memoryCache.set(key, entry);\n } catch (e) {\n console.warn('Storage parse error', e);\n this.#internalRemove(key, option); // Dữ liệu lỗi -> Xóa\n }\n }\n }\n\n if (!entry) return undefined;\n\n return this.#deepClone(entry.data);\n }\n\n /**\n * Ghi dữ liệu: Memory -> Storage\n */\n #internalSet<T>(key: string, data: T, option?: SdStorageOption<T>): void {\n const clonedData = this.#deepClone(data);\n\n const entry: StorageCacheEntry<T> = {\n data: clonedData,\n createdOn: new Date(),\n };\n\n // 1. Lưu Memory\n this.#memoryCache.set(key, entry);\n\n // 2. Lưu Storage\n const storage = this.#getStorage(option);\n try {\n storage.setItem(key, JSON.stringify(entry));\n } catch (e) {\n console.error('Storage quota exceeded', e);\n }\n }\n\n /**\n * Xóa dữ liệu\n */\n #internalRemove(key: string, option?: SdStorageOption<any>): void {\n this.#memoryCache.delete(key);\n const storage = this.#getStorage(option);\n storage.removeItem(key);\n }\n\n /**\n * Deep Clone an toàn\n */\n #deepClone<T>(val: T): T {\n if (val === undefined || val === null) return val;\n return JSON.parse(JSON.stringify(val));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-utilities-data-state.mjs","sources":["../../../projects/sdcorejs-angular/utilities/data-state/src/data-state.ts","../../../projects/sdcorejs-angular/utilities/data-state/sdcorejs-angular-utilities-data-state.ts"],"sourcesContent":["/**\n * Serialize a value for the `data-value` attribute consumed by QA automation.\n *\n * null/undefined/empty-string → ''.\n * Date → ISO string.\n * Array / object → JSON.stringify, '' on failure (circular refs).\n * Primitives → String().\n */\nexport function sdSerializeDataValue(value: unknown): string {\n if (value === null || value === undefined || value === '') return '';\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {\n try { return JSON.stringify(value); } catch { return ''; }\n }\n return String(value);\n}\n\n/** True when the value should drive `data-empty=\"true\"` on a form control. */\nexport function sdIsEmpty(value: unknown): boolean {\n if (value === null || value === undefined || value === '') return true;\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAAA;;;;;;;AAOG;AACG,SAAU,oBAAoB,CAAC,KAAc,EAAA;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,EAAE;IACpE,IAAI,KAAK,YAAY,IAAI;AAAE,QAAA,OAAO,KAAK,CAAC,WAAW,EAAE;AACrD,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE;AACzE,QAAA,IAAI;AAAE,YAAA,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE;AAAE,QAAA,MAAM;AAAE,YAAA,OAAO,EAAE;QAAE;IAC3D;AACA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB;AAEA;AACM,SAAU,SAAS,CAAC,KAAc,EAAA;IACtC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,IAAI;AACtE,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACnD,IAAA,OAAO,KAAK;AACd;;ACtBA;;AAEG;;;;"}
1
+ {"version":3,"file":"sdcorejs-angular-utilities-data-state.mjs","sources":["../../../projects/sdcorejs-angular/utilities/data-state/src/data-state.ts","../../../projects/sdcorejs-angular/utilities/data-state/sdcorejs-angular-utilities-data-state.ts"],"sourcesContent":["/**\n * Serialize a value for the `data-value` attribute consumed by QA automation.\n *\n * null/undefined/empty-string → ''.\n * Date → ISO string.\n * Array / object → JSON.stringify, '' on failure (circular refs).\n * Primitives → String().\n */\nexport function sdSerializeDataValue(value: unknown): string {\n if (value === null || value === undefined || value === '') return '';\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n }\n return String(value);\n}\n\n/** True when the value should drive `data-empty=\"true\"` on a form control. */\nexport function sdIsEmpty(value: unknown): boolean {\n if (value === null || value === undefined || value === '') return true;\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAAA;;;;;;;AAOG;AACG,SAAU,oBAAoB,CAAC,KAAc,EAAA;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,EAAE;IACpE,IAAI,KAAK,YAAY,IAAI;AAAE,QAAA,OAAO,KAAK,CAAC,WAAW,EAAE;AACrD,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE;AACzE,QAAA,IAAI;AACF,YAAA,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAC9B;AAAE,QAAA,MAAM;AACN,YAAA,OAAO,EAAE;QACX;IACF;AACA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB;AAEA;AACM,SAAU,SAAS,CAAC,KAAc,EAAA;IACtC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,IAAI;AACtE,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACnD,IAAA,OAAO,KAAK;AACd;;AC1BA;;AAEG;;;;"}