@sdcorejs/angular 21.0.9 → 21.0.11

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 (174) hide show
  1. package/README.md +270 -140
  2. package/assets/STYLE-GUIDE.md +2 -2
  3. package/assets/scss/core/_inline-edit.scss +1 -1
  4. package/assets/scss/core/form.scss +1 -1
  5. package/assets/scss/core/utilities/_grid.scss +25 -19
  6. package/assets/scss/themes/default.scss +2 -2
  7. package/fesm2022/sdcorejs-angular-components-anchor.mjs +20 -24
  8. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  9. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +4 -5
  10. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  11. package/fesm2022/sdcorejs-angular-components-avatar.mjs.map +1 -1
  12. package/fesm2022/sdcorejs-angular-components-badge.mjs +13 -12
  13. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  14. package/fesm2022/sdcorejs-angular-components-button.mjs +14 -16
  15. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  16. package/fesm2022/sdcorejs-angular-components-chart.mjs.map +1 -1
  17. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
  18. package/fesm2022/sdcorejs-angular-components-code-editor.mjs +5 -6
  19. package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +17 -26
  21. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-editor.mjs +11 -7
  23. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  24. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +716 -437
  25. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-history.mjs +2 -2
  27. package/fesm2022/sdcorejs-angular-components-history.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +38 -23
  29. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-components-inform.mjs +4 -15
  31. package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  33. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +5 -6
  34. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
  35. package/fesm2022/sdcorejs-angular-components-modal.mjs +24 -18
  36. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  37. package/fesm2022/sdcorejs-angular-components-operator.mjs +1 -1
  38. package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
  39. package/fesm2022/sdcorejs-angular-components-org-chart.mjs +4 -5
  40. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
  41. package/fesm2022/sdcorejs-angular-components-preview.mjs +40 -37
  42. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  43. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +38 -55
  44. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  45. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +5 -7
  46. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  47. package/fesm2022/sdcorejs-angular-components-quick-action.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-section.mjs +3 -5
  49. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +6 -7
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-splitter.mjs +4 -4
  53. package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-stepper.mjs +3 -4
  55. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +36 -37
  57. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-tab.mjs +3 -4
  59. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-table.mjs +80 -102
  61. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components-tree.mjs +6 -8
  63. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +14 -15
  65. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-components-view.mjs +3 -9
  67. package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
  69. package/fesm2022/sdcorejs-angular-directives.mjs +2 -7
  70. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  71. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +5 -10
  72. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  73. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +9 -11
  75. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-chip.mjs +7 -9
  77. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +8 -12
  79. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-date.mjs +9 -13
  81. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  82. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +8 -14
  83. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs +4 -5
  85. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +4 -4
  87. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +6 -8
  89. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-forms-input.mjs +5 -7
  91. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-forms-label.mjs +4 -6
  93. package/fesm2022/sdcorejs-angular-forms-label.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  95. package/fesm2022/sdcorejs-angular-forms-radio.mjs +12 -4
  96. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  97. package/fesm2022/sdcorejs-angular-forms-select.mjs +51 -22
  98. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  99. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  100. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +7 -9
  101. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  102. package/fesm2022/sdcorejs-angular-handlers.mjs +3 -6
  103. package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
  104. package/fesm2022/sdcorejs-angular-i18n.mjs +11 -7
  105. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-interceptors.mjs +0 -2
  107. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-modules-auth.mjs +14 -38
  109. package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-modules-authom.mjs +14 -14
  111. package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-modules-icon.mjs +505 -0
  113. package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -0
  114. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +22 -11
  115. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
  116. package/fesm2022/sdcorejs-angular-modules-layout.mjs +53 -52
  117. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-modules-permission.mjs +5 -12
  119. package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-modules.mjs +1 -0
  121. package/fesm2022/sdcorejs-angular-modules.mjs.map +1 -1
  122. package/fesm2022/sdcorejs-angular-pipes.mjs +71 -17
  123. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-services-api.mjs +0 -2
  125. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-services-cache.mjs +5 -11
  127. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  128. package/fesm2022/sdcorejs-angular-services-confirm.mjs +19 -25
  129. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  130. package/fesm2022/sdcorejs-angular-services-docx.mjs +2 -8
  131. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  132. package/fesm2022/sdcorejs-angular-services-excel.mjs +24 -16
  133. package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
  134. package/fesm2022/sdcorejs-angular-services-license.mjs +5 -12
  135. package/fesm2022/sdcorejs-angular-services-license.mjs.map +1 -1
  136. package/fesm2022/sdcorejs-angular-services-notify.mjs +70 -87
  137. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  138. package/fesm2022/sdcorejs-angular-services-storage.mjs +6 -15
  139. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  140. package/fesm2022/sdcorejs-angular-utilities-data-state.mjs.map +1 -1
  141. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +47 -5
  142. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
  143. package/fesm2022/sdcorejs-angular-utilities-models.mjs +21 -5
  144. package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
  145. package/fesm2022/sdcorejs-angular.mjs.map +1 -1
  146. package/package.json +7 -2
  147. package/types/sdcorejs-angular-components-badge.d.ts +3 -14
  148. package/types/sdcorejs-angular-components-button.d.ts +6 -4
  149. package/types/sdcorejs-angular-components-document-builder.d.ts +2 -2
  150. package/types/sdcorejs-angular-components-form-generic.d.ts +45 -23
  151. package/types/sdcorejs-angular-components-import-excel.d.ts +2 -2
  152. package/types/sdcorejs-angular-components-inform.d.ts +2 -8
  153. package/types/sdcorejs-angular-components-mini-editor.d.ts +4 -4
  154. package/types/sdcorejs-angular-components-modal.d.ts +1 -1
  155. package/types/sdcorejs-angular-components-query-builder.d.ts +7 -7
  156. package/types/sdcorejs-angular-components-tab-router.d.ts +3 -3
  157. package/types/sdcorejs-angular-components-table.d.ts +12 -10
  158. package/types/sdcorejs-angular-components-tree.d.ts +3 -2
  159. package/types/sdcorejs-angular-components-view.d.ts +1 -1
  160. package/types/sdcorejs-angular-forms-chip-calendar.d.ts +2 -2
  161. package/types/sdcorejs-angular-forms-chip.d.ts +2 -2
  162. package/types/sdcorejs-angular-forms-input-color.d.ts +2 -1
  163. package/types/sdcorejs-angular-forms-select.d.ts +10 -2
  164. package/types/sdcorejs-angular-modules-auth.d.ts +7 -9
  165. package/types/sdcorejs-angular-modules-icon.d.ts +191 -0
  166. package/types/sdcorejs-angular-modules-permission.d.ts +5 -6
  167. package/types/sdcorejs-angular-modules.d.ts +1 -0
  168. package/types/sdcorejs-angular-pipes.d.ts +22 -4
  169. package/types/sdcorejs-angular-services-confirm.d.ts +3 -3
  170. package/types/sdcorejs-angular-services-license.d.ts +2 -3
  171. package/types/sdcorejs-angular-services-notify.d.ts +4 -5
  172. package/types/sdcorejs-angular-services-storage.d.ts +2 -3
  173. package/types/sdcorejs-angular-utilities-extensions.d.ts +9 -1
  174. package/types/sdcorejs-angular-utilities-models.d.ts +6 -4
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services-excel.mjs","sources":["../../../projects/sdcorejs-angular/services/excel/src/lib/excel.service.ts","../../../projects/sdcorejs-angular/services/excel/sdcorejs-angular-services-excel.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport type { CellValue, Style } from 'exceljs';\n// import hash from 'object-hash';\nimport { DateUtilities } from '@sdcorejs/angular/utilities/extensions';\nimport { BrowserUtilities } from '@sdcorejs/utils/fns';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { SdExcelExportOption, SdExcelTemplate } from './excel.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdExcelService {\n readonly #i18n = inject(I18nService);\n #fieldStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n bold: true,\n size: 11,\n color: { argb: '000000' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'FAFAFA',\n },\n },\n };\n #titleColor = 'FFFFFF';\n #titleFgColor = '143180';\n #titleStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n bold: true,\n size: 11,\n color: { argb: this.#titleColor },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: this.#titleFgColor,\n },\n },\n };\n\n #requiredStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n italic: true,\n size: 11,\n color: { argb: 'FFFFFF' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'FF1744',\n },\n },\n };\n\n #descriptionStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n wrapText: true,\n },\n font: {\n italic: true,\n size: 9,\n color: { argb: '000000' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'CFD8DC',\n },\n },\n };\n\n #cellStyle: Partial<Style> = {\n alignment: {\n vertical: 'middle',\n wrapText: true,\n },\n };\n\n constructor() {}\n\n // exceljs là CJS module — khi dynamic import từ ESM, named export `Workbook`\n // có thể nằm trên `mod.Workbook` hoặc bị bọc trong `mod.default.Workbook` tuỳ bundler.\n // Helper này chuẩn hoá cả hai trường hợp.\n async #loadWorkbook(): Promise<new () => import('exceljs').Workbook> {\n const mod: any = await import('exceljs');\n return mod.Workbook ?? mod.default?.Workbook;\n }\n\n generateTemplate = async (template: SdExcelTemplate) => {\n const { fileName, columns, sheets } = template;\n if (!Array.isArray(columns)) {\n throw new Error('Excel template columns must be an array');\n }\n for (const [idx, column] of columns.entries()) {\n if (!column.field) {\n throw new Error(`Column ${idx + 1}: Field is required`);\n }\n if (!column.title) {\n throw new Error(`Column ${idx + 1}: Title is required`);\n }\n }\n const hasDescription = columns.some(column => column.description);\n const Workbook = await this.#loadWorkbook();\n const workbook = new Workbook();\n const firstSheet = workbook.addWorksheet('template'); // Lấy ra sheet đầu tiên\n columns.forEach((column, index) => {\n const { required, fill, fontColor } = column;\n const cellField = firstSheet.getCell(1, index + 1);\n const cellTitle = firstSheet.getCell(2, index + 1);\n const cellDescription = firstSheet.getCell(3, index + 1);\n let width = 120;\n if (column.width && column.width.endsWith('px')) {\n width = +column.width.replace('px', '');\n }\n firstSheet.getColumn(index + 1).width = width / 7 || 20;\n cellField.style = this.#fieldStyle;\n if (required) {\n cellField.style = this.#requiredStyle;\n } else {\n cellField.style = this.#fieldStyle;\n }\n cellTitle.style = {\n ...this.#titleStyle,\n font: {\n ...this.#titleStyle?.font,\n color: {\n ...this.#titleStyle?.font?.color,\n argb: fontColor || this.#titleStyle?.font?.color?.argb,\n },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: fill || this.#titleFgColor,\n },\n },\n };\n cellField.value = column.field;\n cellTitle.value = column.title;\n if (hasDescription) {\n cellDescription.style = this.#descriptionStyle;\n cellDescription.value = column.description || '';\n }\n });\n if (sheets?.length) {\n for (const sheet of sheets) {\n if (sheet.name && Array.isArray(sheet.items) && Array.isArray(sheet.headers)) {\n const newSheet = workbook.addWorksheet(sheet.name);\n sheet.headers.forEach((header, index) => {\n newSheet.getColumn(index + 1).width = 30;\n newSheet.getCell(1, index + 1).style = this.#titleStyle;\n newSheet.getCell(1, index + 1).value = header.value;\n newSheet.getCell(2, index + 1).style = this.#titleStyle;\n newSheet.getCell(2, index + 1).value = header.display || header.value;\n });\n sheet.items.forEach((item, idx1) => {\n sheet.headers.forEach((header, idx2) => {\n newSheet.getCell(3 + idx1, 1 + idx2).value = item[header.value];\n newSheet.getCell(3 + idx1, 1 + idx2).style = this.#cellStyle;\n });\n });\n }\n }\n }\n const file = await workbook.xlsx.writeBuffer();\n BrowserUtilities.downloadBlob(\n new Blob([file], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n }),\n fileName\n );\n };\n\n exportCSV = async (option: SdExcelExportOption): Promise<void> => {\n const { columns, items, fileName } = option;\n const filename = `${fileName || 'CSV'}_${DateUtilities.toFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss')}.csv`;\n\n // Escape CSV cell: bọc trong dấu \" nếu chứa dấu phẩy / xuống dòng / dấu ngoặc kép.\n // Dấu \" bên trong được nhân đôi theo chuẩn RFC 4180.\n const escape = (v: unknown): string => {\n if (v == null) return '';\n const s = String(v);\n return /[\",\\n\\r]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const headerLine = columns.map(c => escape(c.title)).join(',');\n const dataLines = items.map(item =>\n columns.map(c => escape((item as Record<string, unknown>)[c.field])).join(','),\n );\n\n // Prepend UTF-8 BOM () để Excel mở đúng encoding cho tiếng Việt có dấu.\n // Dùng CRLF vì Excel trên Windows ưu tiên, macOS Excel cũng chấp nhận.\n const csv = '' + [headerLine, ...dataLines].join('\\r\\n');\n\n BrowserUtilities.downloadBlob(\n new Blob([csv], { type: 'text/csv;charset=utf-8;' }),\n filename,\n );\n };\n\n export = async (option: SdExcelExportOption) => {\n const { columns, items, fileName, sheets } = option;\n let hasDescription = false;\n for (const [idx, column] of columns.entries()) {\n if (!column.field) {\n throw new Error(`Column ${idx + 1}: Field is required`);\n }\n if (!column.title) {\n throw new Error(`Column ${idx + 1}: Title is required`);\n }\n if (column.description) {\n hasDescription = true;\n }\n }\n const Workbook = await this.#loadWorkbook();\n const workbook = new Workbook(); //await XlsxPopulate.fromBlankAsync(); // Đọc file sau khi đã download\n const firstSheet = workbook.addWorksheet('data'); // Lấy ra sheet đầu tiên\n columns.forEach((column, index) => {\n let width = 120;\n if (column.width && column.width.endsWith('px')) {\n width = +column.width.replace('px', '');\n }\n firstSheet.getColumn(index + 1).width = width / 7 || 20;\n firstSheet.getCell(1, index + 1).style = this.#fieldStyle;\n if (column.required) {\n firstSheet.getCell(2, index + 1).style = this.#requiredStyle;\n } else {\n firstSheet.getCell(2, index + 1).style = this.#titleStyle;\n }\n firstSheet.getCell(1, index + 1).value = column.field;\n firstSheet.getCell(2, index + 1).value = column.title;\n if (hasDescription) {\n firstSheet.getCell(3, index + 1).style = this.#descriptionStyle;\n firstSheet.getCell(3, index + 1).value = column.description || '';\n }\n });\n if (sheets?.length) {\n for (const sheet of sheets) {\n if (sheet.name && Array.isArray(sheet.items) && Array.isArray(sheet.headers)) {\n const newSheet = workbook.addWorksheet(sheet.name);\n sheet.headers.forEach((header, index) => {\n newSheet.getColumn(index + 1).width = 30;\n newSheet.getCell(1, index + 1).style = this.#titleStyle;\n newSheet.getCell(1, index + 1).value = header.value;\n newSheet.getCell(2, index + 1).style = this.#titleStyle;\n newSheet.getCell(2, index + 1).value = header.display || header.value;\n });\n sheet.items.forEach((item, idx1) => {\n sheet.headers.forEach((header, idx2) => {\n newSheet.getCell(3 + idx1, 1 + idx2).value = item[header.value];\n newSheet.getCell(3 + idx1, 1 + idx2).style = this.#cellStyle;\n });\n });\n }\n }\n }\n\n const fromRow = hasDescription ? 4 : 3;\n items.forEach((e, idx1) => {\n columns.forEach((column, idx2) => {\n if (typeof e[column.field] === 'number') {\n // Format mặc định với kiểu số\n firstSheet.getCell(fromRow + idx1, 1 + idx2).value = +e[column.field];\n firstSheet.getCell(fromRow + idx1, 1 + idx2).numFmt = '#';\n } else {\n firstSheet.getCell(fromRow + idx1, 1 + idx2).value = e[column.field];\n }\n firstSheet.getCell(fromRow + idx1, 1 + idx2).style = {\n ...this.#cellStyle,\n };\n });\n });\n const file = await workbook.xlsx.writeBuffer();\n BrowserUtilities.downloadBlob(\n new Blob([file], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n }),\n fileName\n );\n };\n\n // Helper để lấy giá trị text chuẩn từ ô Excel (xử lý RichText, Formula, Hyperlink)\n #getCellValue = (value: CellValue): string | number | null | undefined | boolean | Date => {\n if (value && typeof value === 'object') {\n // Xử lý Rich Text (vd: ô bôi đậm)\n if ('richText' in value) {\n return value.richText.map(t => t.text).join('');\n }\n // Xử lý Hyperlink\n if ('text' in value) {\n return value.text as string; // hoặc value.result nếu là công thức\n }\n // Xử lý Formula\n if ('result' in value) {\n return value.result as any;\n }\n }\n return value as any;\n };\n upload = async (): Promise<{ items: Record<string, any>[]; file: File | null }> => {\n try {\n const fileResult = await BrowserUtilities.upload({\n extensions: ['xlsx'],\n maxSizeInMb: 10,\n });\n\n // Chuẩn hóa file đầu vào (nếu upload trả về mảng thì lấy phần tử đầu)\n const file = Array.isArray(fileResult) ? fileResult[0] : fileResult;\n\n if (!file) {\n return { items: [], file: null };\n }\n\n return await this.parse(file);\n } catch (error) {\n console.error('Upload error:', error);\n throw error; // Hoặc return { items: [], file: null } tùy logic nghiệp vụ\n }\n };\n parse = async (file: File): Promise<{ items: Record<string, any>[]; file: File | null }> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = async e => {\n try {\n const buffer = e.target?.result as ArrayBuffer;\n if (!buffer) {\n throw new Error(this.#i18n.t('core.excel.cannot-read-file'));\n }\n\n const Workbook = await this.#loadWorkbook();\n const wb = new Workbook();\n await wb.xlsx.load(buffer);\n\n const sheet = wb.worksheets[0];\n if (!sheet) {\n throw new Error(this.#i18n.t('core.excel.no-sheet'));\n }\n\n const items: Record<string, any>[] = [];\n\n // Lấy row Header (Row 1)\n const headerRow = sheet.getRow(1);\n // Map: Column Index (bắt đầu từ 1) -> Tên trường (Key)\n const headerMap: Record<number, string> = {};\n\n // Duyệt qua các ô có dữ liệu thực tế của dòng Header\n headerRow.eachCell((cell, colNumber) => {\n const cellValue = this.#getCellValue(cell.value);\n if (cellValue) {\n // Ép kiểu về string và trim để làm key\n headerMap[colNumber] = String(cellValue).trim();\n }\n });\n\n // Duyệt dữ liệu từ dòng 2 trở đi\n sheet.eachRow((row, rowIndex) => {\n if (rowIndex <= 1 || !row.hasValues) return;\n\n const item: Record<string, any> = {};\n\n // Chỉ duyệt qua các cột đã định nghĩa trong headerMap\n // Cách này an toàn hơn là duyệt mảng values\n Object.keys(headerMap).forEach(colKey => {\n const colNumber = Number(colKey);\n const fieldKey = headerMap[colNumber];\n\n // Lấy giá trị ô tại cột tương ứng\n const rawValue = this.#getCellValue(row.getCell(colNumber).value);\n\n let finalValue = rawValue;\n\n // Xử lý string: trim\n if (typeof finalValue === 'string') {\n finalValue = finalValue.trim();\n }\n\n // Xử lý các keyword đặc biệt\n if (finalValue === '' || finalValue === undefined) {\n finalValue = null;\n } else if (finalValue === 'SET_NULL') {\n finalValue = null;\n } else if (finalValue === 'SET_EMPTY') {\n finalValue = '';\n }\n\n item[fieldKey] = finalValue;\n });\n\n // Chỉ push nếu item có dữ liệu (tùy chọn)\n if (Object.keys(item).length > 0) {\n items.push(item);\n }\n });\n\n resolve({ items, file });\n } catch (err) {\n reject(err);\n }\n };\n\n reader.onerror = error => reject(error);\n reader.readAsArrayBuffer(file);\n });\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-services-excel.mjs","sources":["../../../projects/sdcorejs-angular/services/excel/src/lib/spreadsheet-formula-injection.util.ts","../../../projects/sdcorejs-angular/services/excel/src/lib/excel.service.ts","../../../projects/sdcorejs-angular/services/excel/sdcorejs-angular-services-excel.ts"],"sourcesContent":["const DANGEROUS_SPREADSHEET_FORMULA_PREFIX = /^[=+\\-@\\t\\r\\n]/;\n\n/**\n * Prefix spreadsheet formula-like strings so exported files treat them as text.\n */\nexport function neutralizeSpreadsheetFormula(value: string): string;\nexport function neutralizeSpreadsheetFormula<T>(value: T): T;\nexport function neutralizeSpreadsheetFormula(value: unknown): unknown {\n if (typeof value !== 'string' || value === '' || value.startsWith(\"'\")) {\n return value;\n }\n\n return DANGEROUS_SPREADSHEET_FORMULA_PREFIX.test(value) ? `'${value}` : value;\n}\n","import { inject, Injectable } from '@angular/core';\nimport type { CellValue, Style } from 'exceljs';\n// import hash from 'object-hash';\nimport { DateUtilities } from '@sdcorejs/angular/utilities/extensions';\nimport { BrowserUtilities } from '@sdcorejs/utils/fns';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { SdExcelExportOption, SdExcelTemplate } from './excel.model';\nimport { neutralizeSpreadsheetFormula } from './spreadsheet-formula-injection.util';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdExcelService {\n readonly #i18n = inject(I18nService);\n #fieldStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n bold: true,\n size: 11,\n color: { argb: '000000' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'FAFAFA',\n },\n },\n };\n #titleColor = 'FFFFFF';\n #titleFgColor = '143180';\n #titleStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n bold: true,\n size: 11,\n color: { argb: this.#titleColor },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: this.#titleFgColor,\n },\n },\n };\n\n #requiredStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n vertical: 'middle',\n horizontal: 'center',\n wrapText: true,\n },\n font: {\n italic: true,\n size: 11,\n color: { argb: 'FFFFFF' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'FF1744',\n },\n },\n };\n\n #descriptionStyle: Partial<Style> = {\n border: {\n bottom: { style: 'thin' },\n left: { style: 'thin' },\n right: { style: 'thin' },\n },\n alignment: {\n wrapText: true,\n },\n font: {\n italic: true,\n size: 9,\n color: { argb: '000000' },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: 'CFD8DC',\n },\n },\n };\n\n #cellStyle: Partial<Style> = {\n alignment: {\n vertical: 'middle',\n wrapText: true,\n },\n };\n\n constructor() {}\n\n // exceljs là CJS module — khi dynamic import từ ESM, named export `Workbook`\n // có thể nằm trên `mod.Workbook` hoặc bị bọc trong `mod.default.Workbook` tuỳ bundler.\n // Helper này chuẩn hoá cả hai trường hợp.\n async #loadWorkbook(): Promise<new () => import('exceljs').Workbook> {\n const mod: any = await import('exceljs');\n return mod.Workbook ?? mod.default?.Workbook;\n }\n\n generateTemplate = async (template: SdExcelTemplate) => {\n const { fileName, columns, sheets } = template;\n if (!Array.isArray(columns)) {\n throw new Error('Excel template columns must be an array');\n }\n for (const [idx, column] of columns.entries()) {\n if (!column.field) {\n throw new Error(`Column ${idx + 1}: Field is required`);\n }\n if (!column.title) {\n throw new Error(`Column ${idx + 1}: Title is required`);\n }\n }\n const hasDescription = columns.some(column => column.description);\n const Workbook = await this.#loadWorkbook();\n const workbook = new Workbook();\n const firstSheet = workbook.addWorksheet('template'); // Lấy ra sheet đầu tiên\n columns.forEach((column, index) => {\n const { required, fill, fontColor } = column;\n const cellField = firstSheet.getCell(1, index + 1);\n const cellTitle = firstSheet.getCell(2, index + 1);\n const cellDescription = firstSheet.getCell(3, index + 1);\n let width = 120;\n if (column.width && column.width.endsWith('px')) {\n width = +column.width.replace('px', '');\n }\n firstSheet.getColumn(index + 1).width = width / 7 || 20;\n cellField.style = this.#fieldStyle;\n if (required) {\n cellField.style = this.#requiredStyle;\n } else {\n cellField.style = this.#fieldStyle;\n }\n cellTitle.style = {\n ...this.#titleStyle,\n font: {\n ...this.#titleStyle?.font,\n color: {\n ...this.#titleStyle?.font?.color,\n argb: fontColor || this.#titleStyle?.font?.color?.argb,\n },\n },\n fill: {\n type: 'pattern',\n pattern: 'solid',\n fgColor: {\n argb: fill || this.#titleFgColor,\n },\n },\n };\n cellField.value = neutralizeSpreadsheetFormula(column.field);\n cellTitle.value = neutralizeSpreadsheetFormula(column.title);\n if (hasDescription) {\n cellDescription.style = this.#descriptionStyle;\n cellDescription.value = neutralizeSpreadsheetFormula(column.description || '');\n }\n });\n if (sheets?.length) {\n for (const sheet of sheets) {\n if (sheet.name && Array.isArray(sheet.items) && Array.isArray(sheet.headers)) {\n const newSheet = workbook.addWorksheet(sheet.name);\n sheet.headers.forEach((header, index) => {\n newSheet.getColumn(index + 1).width = 30;\n newSheet.getCell(1, index + 1).style = this.#titleStyle;\n newSheet.getCell(1, index + 1).value = neutralizeSpreadsheetFormula(header.value);\n newSheet.getCell(2, index + 1).style = this.#titleStyle;\n newSheet.getCell(2, index + 1).value = neutralizeSpreadsheetFormula(header.display || header.value);\n });\n sheet.items.forEach((item, idx1) => {\n sheet.headers.forEach((header, idx2) => {\n newSheet.getCell(3 + idx1, 1 + idx2).value = neutralizeSpreadsheetFormula(item[header.value]) as CellValue;\n newSheet.getCell(3 + idx1, 1 + idx2).style = this.#cellStyle;\n });\n });\n }\n }\n }\n const file = await workbook.xlsx.writeBuffer();\n BrowserUtilities.downloadBlob(\n new Blob([file], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n }),\n fileName\n );\n };\n\n exportCSV = async (option: SdExcelExportOption): Promise<void> => {\n const { columns, items, fileName } = option;\n const filename = `${fileName || 'CSV'}_${DateUtilities.toFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss')}.csv`;\n\n // Escape CSV cell: bọc trong dấu \" nếu chứa dấu phẩy / xuống dòng / dấu ngoặc kép.\n // Dấu \" bên trong được nhân đôi theo chuẩn RFC 4180.\n const escape = (v: unknown): string => {\n if (v == null) return '';\n const s = typeof v === 'string' ? neutralizeSpreadsheetFormula(v) : String(v);\n return /[\",\\n\\r]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n\n const headerLine = columns.map(c => escape(c.title)).join(',');\n const dataLines = items.map(item => columns.map(c => escape((item as Record<string, unknown>)[c.field])).join(','));\n\n // Prepend UTF-8 BOM so Excel opens Vietnamese text with the correct encoding.\n // Dùng CRLF vì Excel trên Windows ưu tiên, macOS Excel cũng chấp nhận.\n const csv = '\\uFEFF' + [headerLine, ...dataLines].join('\\r\\n');\n\n BrowserUtilities.downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), filename);\n };\n\n export = async (option: SdExcelExportOption) => {\n const { columns, items, fileName, sheets } = option;\n let hasDescription = false;\n for (const [idx, column] of columns.entries()) {\n if (!column.field) {\n throw new Error(`Column ${idx + 1}: Field is required`);\n }\n if (!column.title) {\n throw new Error(`Column ${idx + 1}: Title is required`);\n }\n if (column.description) {\n hasDescription = true;\n }\n }\n const Workbook = await this.#loadWorkbook();\n const workbook = new Workbook(); //await XlsxPopulate.fromBlankAsync(); // Đọc file sau khi đã download\n const firstSheet = workbook.addWorksheet('data'); // Lấy ra sheet đầu tiên\n columns.forEach((column, index) => {\n let width = 120;\n if (column.width && column.width.endsWith('px')) {\n width = +column.width.replace('px', '');\n }\n firstSheet.getColumn(index + 1).width = width / 7 || 20;\n firstSheet.getCell(1, index + 1).style = this.#fieldStyle;\n if (column.required) {\n firstSheet.getCell(2, index + 1).style = this.#requiredStyle;\n } else {\n firstSheet.getCell(2, index + 1).style = this.#titleStyle;\n }\n firstSheet.getCell(1, index + 1).value = neutralizeSpreadsheetFormula(column.field);\n firstSheet.getCell(2, index + 1).value = neutralizeSpreadsheetFormula(column.title);\n if (hasDescription) {\n firstSheet.getCell(3, index + 1).style = this.#descriptionStyle;\n firstSheet.getCell(3, index + 1).value = neutralizeSpreadsheetFormula(column.description || '');\n }\n });\n if (sheets?.length) {\n for (const sheet of sheets) {\n if (sheet.name && Array.isArray(sheet.items) && Array.isArray(sheet.headers)) {\n const newSheet = workbook.addWorksheet(sheet.name);\n sheet.headers.forEach((header, index) => {\n newSheet.getColumn(index + 1).width = 30;\n newSheet.getCell(1, index + 1).style = this.#titleStyle;\n newSheet.getCell(1, index + 1).value = neutralizeSpreadsheetFormula(header.value);\n newSheet.getCell(2, index + 1).style = this.#titleStyle;\n newSheet.getCell(2, index + 1).value = neutralizeSpreadsheetFormula(header.display || header.value);\n });\n sheet.items.forEach((item, idx1) => {\n sheet.headers.forEach((header, idx2) => {\n newSheet.getCell(3 + idx1, 1 + idx2).value = neutralizeSpreadsheetFormula(item[header.value]) as CellValue;\n newSheet.getCell(3 + idx1, 1 + idx2).style = this.#cellStyle;\n });\n });\n }\n }\n }\n\n const fromRow = hasDescription ? 4 : 3;\n items.forEach((e, idx1) => {\n columns.forEach((column, idx2) => {\n if (typeof e[column.field] === 'number') {\n // Format mặc định với kiểu số\n firstSheet.getCell(fromRow + idx1, 1 + idx2).value = +e[column.field];\n firstSheet.getCell(fromRow + idx1, 1 + idx2).numFmt = '#';\n } else {\n firstSheet.getCell(fromRow + idx1, 1 + idx2).value = neutralizeSpreadsheetFormula(e[column.field]) as CellValue;\n }\n firstSheet.getCell(fromRow + idx1, 1 + idx2).style = {\n ...this.#cellStyle,\n };\n });\n });\n const file = await workbook.xlsx.writeBuffer();\n BrowserUtilities.downloadBlob(\n new Blob([file], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n }),\n fileName\n );\n };\n\n // Helper để lấy giá trị text chuẩn từ ô Excel (xử lý RichText, Formula, Hyperlink)\n #getCellValue = (value: CellValue): string | number | null | undefined | boolean | Date => {\n if (value && typeof value === 'object') {\n // Xử lý Rich Text (vd: ô bôi đậm)\n if ('richText' in value) {\n return value.richText.map(t => t.text).join('');\n }\n // Xử lý Hyperlink\n if ('text' in value) {\n return value.text as string; // hoặc value.result nếu là công thức\n }\n // Xử lý Formula\n if ('result' in value) {\n return value.result as any;\n }\n }\n return value as any;\n };\n upload = async (): Promise<{ items: Record<string, any>[]; file: File | null }> => {\n try {\n const fileResult = await BrowserUtilities.upload({\n extensions: ['xlsx'],\n maxSizeInMb: 10,\n });\n\n // Chuẩn hóa file đầu vào (nếu upload trả về mảng thì lấy phần tử đầu)\n const file = Array.isArray(fileResult) ? fileResult[0] : fileResult;\n\n if (!file) {\n return { items: [], file: null };\n }\n\n return await this.parse(file);\n } catch (error) {\n console.error('Upload error:', error);\n throw error; // Hoặc return { items: [], file: null } tùy logic nghiệp vụ\n }\n };\n parse = async (file: File): Promise<{ items: Record<string, any>[]; file: File | null }> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = async e => {\n try {\n const buffer = e.target?.result as ArrayBuffer;\n if (!buffer) {\n throw new Error(this.#i18n.t('core.excel.cannot-read-file'));\n }\n\n const Workbook = await this.#loadWorkbook();\n const wb = new Workbook();\n await wb.xlsx.load(buffer);\n\n const sheet = wb.worksheets[0];\n if (!sheet) {\n throw new Error(this.#i18n.t('core.excel.no-sheet'));\n }\n\n const items: Record<string, any>[] = [];\n\n // Lấy row Header (Row 1)\n const headerRow = sheet.getRow(1);\n // Map: Column Index (bắt đầu từ 1) -> Tên trường (Key)\n const headerMap: Record<number, string> = {};\n\n // Duyệt qua các ô có dữ liệu thực tế của dòng Header\n headerRow.eachCell((cell, colNumber) => {\n const cellValue = this.#getCellValue(cell.value);\n if (cellValue) {\n // Ép kiểu về string và trim để làm key\n headerMap[colNumber] = String(cellValue).trim();\n }\n });\n\n // Duyệt dữ liệu từ dòng 2 trở đi\n sheet.eachRow((row, rowIndex) => {\n if (rowIndex <= 1 || !row.hasValues) return;\n\n const item: Record<string, any> = {};\n\n // Chỉ duyệt qua các cột đã định nghĩa trong headerMap\n // Cách này an toàn hơn là duyệt mảng values\n Object.keys(headerMap).forEach(colKey => {\n const colNumber = Number(colKey);\n const fieldKey = headerMap[colNumber];\n\n // Lấy giá trị ô tại cột tương ứng\n const rawValue = this.#getCellValue(row.getCell(colNumber).value);\n\n let finalValue = rawValue;\n\n // Xử lý string: trim\n if (typeof finalValue === 'string') {\n finalValue = finalValue.trim();\n }\n\n // Xử lý các keyword đặc biệt\n if (finalValue === '' || finalValue === undefined) {\n finalValue = null;\n } else if (finalValue === 'SET_NULL') {\n finalValue = null;\n } else if (finalValue === 'SET_EMPTY') {\n finalValue = '';\n }\n\n item[fieldKey] = finalValue;\n });\n\n // Chỉ push nếu item có dữ liệu (tùy chọn)\n if (Object.keys(item).length > 0) {\n items.push(item);\n }\n });\n\n resolve({ items, file });\n } catch (err) {\n reject(err);\n }\n };\n\n reader.onerror = error => reject(error);\n reader.readAsArrayBuffer(file);\n });\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Inject, Optional, Injectable } from '@angular/core';
2
+ import { inject, Injectable } from '@angular/core';
3
3
  import { SD_CORE_CONFIGURATION } from '@sdcorejs/angular/configurations';
4
4
 
5
5
  class SdLicenseService {
6
- coreConfiguration;
7
6
  #SALT = 'angular-core-976e2fa6f8b44dadbc63f87b057a331f';
8
7
  #isValid = false;
9
- constructor(coreConfiguration) {
10
- this.coreConfiguration = coreConfiguration;
8
+ coreConfiguration = inject(SD_CORE_CONFIGURATION, { optional: true });
9
+ constructor() {
11
10
  this.#verify();
12
11
  }
13
12
  enforceLicense = () => {
@@ -19,7 +18,6 @@ class SdLicenseService {
19
18
  const hostname = window.location.hostname; // Ví dụ: store.uat.nexa.mobi
20
19
  // 1. Bypass Localhost
21
20
  if (this.#isLocalhost(hostname)) {
22
- console.log('%c [SdAngularCore] Dev Mode ', 'background: #222; color: #bada55');
23
21
  this.#isValid = true;
24
22
  return;
25
23
  }
@@ -81,7 +79,7 @@ class SdLicenseService {
81
79
  #throwSecurityError = () => {
82
80
  throw new Error(`[Security] Unauthorized usage of UI Lib on domain: ${window.location.hostname}`);
83
81
  };
84
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLicenseService, deps: [{ token: SD_CORE_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
82
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLicenseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
85
83
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLicenseService, providedIn: 'root' });
86
84
  }
87
85
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdLicenseService, decorators: [{
@@ -89,12 +87,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
89
87
  args: [{
90
88
  providedIn: 'root',
91
89
  }]
92
- }], ctorParameters: () => [{ type: undefined, decorators: [{
93
- type: Inject,
94
- args: [SD_CORE_CONFIGURATION]
95
- }, {
96
- type: Optional
97
- }] }] });
90
+ }], ctorParameters: () => [] });
98
91
 
99
92
  /**
100
93
  * Generated bundle index. Do not edit.
@@ -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, ...(ngDevMode ? [{ debugName: "isExpanded" }] : /* istanbul ignore next */ []));
14
+ isClosing = signal(false, ...(ngDevMode ? [{ debugName: "isClosing" }] : /* istanbul ignore next */ []));
15
+ hostClasses = computed(() => (this.isClosing() ? 'bg-white sd-toast sd-toast--closing' : 'bg-white sd-toast'), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
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: "21.2.17", ngImport: i0, type: ToastComponent, deps: [{ token: SdNotifyService }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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: "21.2.17", 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: "21.2.17", 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: "21.2.17", ngImpo
165
154
  }] });
166
155
 
167
156
  class SdNotifyService {
168
- appRef;
169
- injector;
170
- document;
171
157
  toasts = signal([], ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
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: "21.2.17", 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: "21.2.17", ngImport: i0, type: SdNotifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
282
268
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdNotifyService, providedIn: 'root' });
283
269
  }
284
270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdNotifyService, decorators: [{
@@ -286,10 +272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", 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.