@sd-angular/core 19.0.0-beta.8 → 19.0.0-beta.81

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 (306) hide show
  1. package/README.md +686 -33
  2. package/assets/scss/ckeditor5.scss +61 -4
  3. package/assets/scss/core/bootstrap.scss +17 -0
  4. package/assets/scss/core/form.scss +32 -6
  5. package/assets/scss/core/grid.scss +40 -0
  6. package/assets/scss/sd-core.scss +5 -0
  7. package/assets/scss/themes/material-theme.scss +82 -40
  8. package/components/anchor-v2/src/components/anchor-item-v2/anchor-item-v2.component.d.ts +5 -5
  9. package/components/anchor-v2/src/components/anchor-v2/anchor-v2.component.d.ts +12 -18
  10. package/components/anchor-v2/src/components/anchor-vertical-v2/anchor-vertical-list-v2.component.d.ts +9 -10
  11. package/components/anchor-v2/src/models/sd-anchor-v2.model.d.ts +3 -3
  12. package/components/avatar/index.d.ts +1 -0
  13. package/components/avatar/src/avatar.component.d.ts +19 -0
  14. package/components/badge/src/badge.component.d.ts +77 -19
  15. package/components/button/src/button.component.d.ts +30 -28
  16. package/components/chart/index.d.ts +4 -0
  17. package/components/chart/src/bar-chart.component.d.ts +18 -0
  18. package/components/chart/src/doughnut-chart.component.d.ts +16 -0
  19. package/components/chart/src/line-chart.component.d.ts +18 -0
  20. package/components/chart/src/pie-chart.component.d.ts +16 -0
  21. package/components/code-editor/index.d.ts +1 -0
  22. package/components/code-editor/src/code-editor.component.d.ts +25 -0
  23. package/components/document-builder/index.d.ts +1 -0
  24. package/components/document-builder/src/document-builder.component.d.ts +13 -42
  25. package/components/document-builder/src/document-builder.model.d.ts +15 -12
  26. package/components/document-builder/src/plugins/block-space/block-space.plugin.d.ts +9 -0
  27. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.d.ts +44 -0
  28. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +57 -0
  29. package/components/document-builder/src/plugins/heading/heading.plugin.d.ts +1 -0
  30. package/components/document-builder/src/plugins/highlight-range/highlight-range.plugin.d.ts +4 -0
  31. package/components/document-builder/src/plugins/image-custom/image-custom.plugin.d.ts +31 -0
  32. package/components/document-builder/src/plugins/index.d.ts +7 -2
  33. package/components/document-builder/src/plugins/page-orientation/page-orientation.plugin.d.ts +2 -2
  34. package/components/document-builder/src/plugins/paste-handler/filters/bookmark.d.ts +14 -0
  35. package/components/document-builder/src/plugins/paste-handler/filters/br.d.ts +15 -0
  36. package/components/document-builder/src/plugins/paste-handler/filters/image.d.ts +25 -0
  37. package/components/document-builder/src/plugins/paste-handler/filters/list.d.ts +29 -0
  38. package/components/document-builder/src/plugins/paste-handler/filters/parse.d.ts +35 -0
  39. package/components/document-builder/src/plugins/paste-handler/filters/removeboldwrapper.d.ts +15 -0
  40. package/components/document-builder/src/plugins/paste-handler/filters/removegooglesheetstag.d.ts +15 -0
  41. package/components/document-builder/src/plugins/paste-handler/filters/removeinvalidtablewidth.d.ts +15 -0
  42. package/components/document-builder/src/plugins/paste-handler/filters/removemsattributes.d.ts +15 -0
  43. package/components/document-builder/src/plugins/paste-handler/filters/removestyleblock.d.ts +15 -0
  44. package/components/document-builder/src/plugins/paste-handler/filters/removexmlns.d.ts +15 -0
  45. package/components/document-builder/src/plugins/paste-handler/filters/replacemsfootnotes.d.ts +54 -0
  46. package/components/document-builder/src/plugins/paste-handler/filters/replacetabswithinprewithspaces.d.ts +24 -0
  47. package/components/document-builder/src/plugins/paste-handler/filters/space.d.ts +27 -0
  48. package/components/document-builder/src/plugins/paste-handler/filters/table.d.ts +16 -0
  49. package/components/document-builder/src/plugins/paste-handler/filters/utils.d.ts +25 -0
  50. package/components/document-builder/src/plugins/paste-handler/index.d.ts +35 -0
  51. package/components/document-builder/src/plugins/paste-handler/normalizers/googledocsnormalizer.d.ts +31 -0
  52. package/components/document-builder/src/plugins/paste-handler/normalizers/googlesheetsnormalizer.d.ts +31 -0
  53. package/components/document-builder/src/plugins/paste-handler/normalizers/mswordnormalizer.d.ts +29 -0
  54. package/components/document-builder/src/plugins/paste-handler/types.d.ts +30 -0
  55. package/components/document-builder/src/plugins/table-custom/index.d.ts +34 -0
  56. package/components/document-builder/src/plugins/variable/variable.plugin.d.ts +39 -0
  57. package/components/editor/index.d.ts +3 -0
  58. package/components/editor/src/configurations/editor.configuration.d.ts +12 -0
  59. package/components/editor/src/configurations/index.d.ts +1 -0
  60. package/components/editor/src/editor.component.d.ts +42 -0
  61. package/components/editor/src/models/editor.model.d.ts +8 -0
  62. package/components/editor/src/models/image-upload.plugin.model.d.ts +20 -0
  63. package/components/editor/src/models/index.d.ts +2 -0
  64. package/components/editor/src/plugins/image-upload/image-upload.plugin.d.ts +22 -0
  65. package/components/editor/src/plugins/image-upload/utils/batch.utils.d.ts +14 -0
  66. package/components/editor/src/plugins/image-upload/utils/index.d.ts +3 -0
  67. package/components/editor/src/plugins/image-upload/utils/style.utils.d.ts +2 -0
  68. package/components/editor/src/plugins/image-upload/utils/validate.utils.d.ts +3 -0
  69. package/components/index.d.ts +6 -0
  70. package/components/mini-editor/index.d.ts +2 -0
  71. package/components/mini-editor/src/mini-editor.component.d.ts +91 -0
  72. package/components/mini-editor/src/mini-editor.model.d.ts +44 -0
  73. package/components/modal/index.d.ts +1 -1
  74. package/components/modal/src/modal.component.d.ts +26 -0
  75. package/components/section/index.d.ts +1 -0
  76. package/components/section/src/section-item/section-item.component.d.ts +7 -0
  77. package/components/section/src/section.component.d.ts +11 -11
  78. package/components/side-drawer/src/side-drawer.component.d.ts +11 -24
  79. package/components/tab-router/src/components/tab-router-item/tab-router-item.component.d.ts +4 -1
  80. package/components/tab-router/src/components/tab-router-outlet/tab-router-outlet.component.d.ts +3 -15
  81. package/components/table/index.d.ts +2 -0
  82. package/components/table/src/components/column-filter/column-filter.component.d.ts +3 -3
  83. package/components/table/src/components/column-title/column-title.component.d.ts +10 -0
  84. package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +18 -11
  85. package/components/table/src/components/desktop-cell/view/view.component.d.ts +24 -0
  86. package/components/table/src/components/external-filter/external-filter.component.d.ts +1 -1
  87. package/components/table/src/components/index.d.ts +1 -0
  88. package/components/table/src/components/selector-action/action-filter.pipe.d.ts +11 -10
  89. package/components/table/src/components/selector-action/selector-action.component.d.ts +5 -3
  90. package/components/table/src/directives/index.d.ts +4 -0
  91. package/components/table/src/directives/sd-table-cell-def.directive.d.ts +2 -3
  92. package/components/table/src/directives/sd-table-column-filter-def.directive.d.ts +8 -0
  93. package/components/table/src/directives/sd-table-expand-def.directive.d.ts +0 -1
  94. package/components/table/src/directives/sd-table-filter-def.directive.d.ts +4 -6
  95. package/components/table/src/directives/sd-table-footer-def.directive.d.ts +2 -3
  96. package/components/table/src/directives/sd-table-title-def.directive.d.ts +8 -0
  97. package/components/table/src/directives/sticky-shadow.directive.d.ts +17 -0
  98. package/components/table/src/models/table-column.model.d.ts +49 -40
  99. package/components/table/src/models/table-command.model.d.ts +7 -3
  100. package/components/table/src/models/table-item.model.d.ts +5 -4
  101. package/components/table/src/models/table-option-config.model.d.ts +3 -0
  102. package/components/table/src/models/table-option-export.model.d.ts +3 -2
  103. package/components/table/src/models/table-option-selector.model.d.ts +17 -10
  104. package/components/table/src/models/table-option.model.d.ts +15 -8
  105. package/components/table/src/services/index.d.ts +3 -0
  106. package/components/table/src/services/table-export/table-export.service.d.ts +26 -0
  107. package/components/table/src/services/table-filter/table-filter.model.d.ts +6 -5
  108. package/components/table/src/services/table-format/table-format.service.d.ts +16 -0
  109. package/components/table/src/table.component.d.ts +46 -53
  110. package/components/upload-file/src/configurations/upload-file.configuration.d.ts +34 -1
  111. package/components/upload-file/src/services/upload-file.service.d.ts +0 -1
  112. package/components/upload-file/src/upload-file.component.d.ts +52 -54
  113. package/components/view/index.d.ts +1 -0
  114. package/components/view/src/view.component.d.ts +16 -0
  115. package/components/workflow/src/models/form-generic-component.model.d.ts +5 -4
  116. package/components/workflow/src/models/form-generic-expression.model.d.ts +1 -0
  117. package/components/workflow/src/models/index.d.ts +1 -0
  118. package/components/workflow/src/pipes/html.pipe.d.ts +4 -4
  119. package/directives/index.d.ts +2 -0
  120. package/directives/src/sd-href.directive.d.ts +9 -0
  121. package/directives/src/sd-tooltip.directive.d.ts +26 -0
  122. package/fesm2022/sd-angular-core-components-anchor-v2.mjs +79 -154
  123. package/fesm2022/sd-angular-core-components-anchor-v2.mjs.map +1 -1
  124. package/fesm2022/sd-angular-core-components-avatar.mjs +103 -0
  125. package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -0
  126. package/fesm2022/sd-angular-core-components-badge.mjs +101 -91
  127. package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
  128. package/fesm2022/sd-angular-core-components-button.mjs +70 -96
  129. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  130. package/fesm2022/sd-angular-core-components-chart.mjs +290 -0
  131. package/fesm2022/sd-angular-core-components-chart.mjs.map +1 -0
  132. package/fesm2022/sd-angular-core-components-code-editor.mjs +127 -0
  133. package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -0
  134. package/fesm2022/sd-angular-core-components-document-builder.mjs +4005 -611
  135. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  136. package/fesm2022/sd-angular-core-components-editor.mjs +933 -0
  137. package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -0
  138. package/fesm2022/sd-angular-core-components-history.mjs +1 -1
  139. package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
  140. package/fesm2022/sd-angular-core-components-import-excel.mjs +1 -1
  141. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  142. package/fesm2022/sd-angular-core-components-mini-editor.mjs +332 -0
  143. package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -0
  144. package/fesm2022/sd-angular-core-components-modal.mjs +63 -92
  145. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  146. package/fesm2022/sd-angular-core-components-preview.mjs +1 -1
  147. package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
  148. package/fesm2022/sd-angular-core-components-quick-action.mjs +2 -2
  149. package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
  150. package/fesm2022/sd-angular-core-components-section.mjs +41 -43
  151. package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
  152. package/fesm2022/sd-angular-core-components-side-drawer.mjs +78 -84
  153. package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
  154. package/fesm2022/sd-angular-core-components-tab-router.mjs +151 -241
  155. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  156. package/fesm2022/sd-angular-core-components-table.mjs +1394 -1254
  157. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  158. package/fesm2022/sd-angular-core-components-upload-file.mjs +390 -443
  159. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  160. package/fesm2022/sd-angular-core-components-view.mjs +45 -0
  161. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -0
  162. package/fesm2022/sd-angular-core-components-workflow.mjs +165 -168
  163. package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
  164. package/fesm2022/sd-angular-core-components.mjs +6 -0
  165. package/fesm2022/sd-angular-core-components.mjs.map +1 -1
  166. package/fesm2022/sd-angular-core-directives.mjs +286 -27
  167. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  168. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +289 -363
  169. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  170. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +170 -189
  171. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  172. package/fesm2022/sd-angular-core-forms-chip.mjs +184 -194
  173. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  174. package/fesm2022/sd-angular-core-forms-date-range.mjs +180 -242
  175. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  176. package/fesm2022/sd-angular-core-forms-date.mjs +178 -270
  177. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  178. package/fesm2022/sd-angular-core-forms-datetime.mjs +177 -285
  179. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  180. package/fesm2022/sd-angular-core-forms-input-number.mjs +210 -337
  181. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  182. package/fesm2022/sd-angular-core-forms-input.mjs +169 -286
  183. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  184. package/fesm2022/sd-angular-core-forms-radio.mjs +3 -2
  185. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  186. package/fesm2022/sd-angular-core-forms-select.mjs +390 -447
  187. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  188. package/fesm2022/sd-angular-core-forms-textarea.mjs +167 -226
  189. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  190. package/fesm2022/sd-angular-core-modules-authom.mjs +359 -0
  191. package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -0
  192. package/fesm2022/sd-angular-core-modules-keycloak.mjs +126 -0
  193. package/fesm2022/sd-angular-core-modules-keycloak.mjs.map +1 -0
  194. package/fesm2022/sd-angular-core-modules-layout.mjs +709 -456
  195. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  196. package/fesm2022/sd-angular-core-modules-permission.mjs +160 -74
  197. package/fesm2022/sd-angular-core-modules-permission.mjs.map +1 -1
  198. package/fesm2022/sd-angular-core-modules.mjs +2 -1
  199. package/fesm2022/sd-angular-core-modules.mjs.map +1 -1
  200. package/fesm2022/sd-angular-core-services-api.mjs +5 -10
  201. package/fesm2022/sd-angular-core-services-api.mjs.map +1 -1
  202. package/fesm2022/sd-angular-core-services-confirm.mjs +9 -7
  203. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  204. package/fesm2022/sd-angular-core-services-docx.mjs +173 -0
  205. package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -0
  206. package/fesm2022/sd-angular-core-services-notify.mjs +2 -2
  207. package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
  208. package/fesm2022/sd-angular-core-services.mjs +1 -0
  209. package/fesm2022/sd-angular-core-services.mjs.map +1 -1
  210. package/fesm2022/sd-angular-core-utilities-extensions.mjs +58 -80
  211. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  212. package/fesm2022/sd-angular-core-utilities-models.mjs +15 -1
  213. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  214. package/fesm2022/sd-angular-core.mjs +0 -1
  215. package/fesm2022/sd-angular-core.mjs.map +1 -1
  216. package/forms/autocomplete/src/autocomplete.component.d.ts +55 -55
  217. package/forms/chip/src/chip.component.d.ts +37 -40
  218. package/forms/chip-calendar/src/chip-calendar.component.d.ts +37 -38
  219. package/forms/date/src/date.component.d.ts +48 -46
  220. package/forms/date-range/src/date-range.component.d.ts +35 -34
  221. package/forms/datetime/src/datetime.component.d.ts +48 -49
  222. package/forms/input/src/input.component.d.ts +54 -57
  223. package/forms/input-number/src/input-number.component.d.ts +53 -54
  224. package/forms/select/src/select.component.d.ts +67 -64
  225. package/forms/textarea/src/textarea.component.d.ts +40 -43
  226. package/modules/authom/authom.configuration.d.ts +17 -0
  227. package/modules/authom/authom.interceptor.d.ts +3 -0
  228. package/modules/authom/authom.module.d.ts +16 -0
  229. package/modules/authom/authom.service.d.ts +32 -0
  230. package/modules/authom/index.d.ts +35 -0
  231. package/modules/index.d.ts +2 -1
  232. package/modules/keycloak/index.d.ts +4 -0
  233. package/modules/keycloak/keycloak.configuration.d.ts +11 -0
  234. package/modules/keycloak/keycloak.interceptor.d.ts +2 -0
  235. package/modules/keycloak/keycloak.module.d.ts +18 -0
  236. package/modules/keycloak/keycloak.service.d.ts +14 -0
  237. package/modules/layout/components/index.d.ts +2 -0
  238. package/modules/layout/components/layout-main/layout-main.component.d.ts +8 -12
  239. package/modules/layout/components/page/page.component.d.ts +5 -7
  240. package/modules/layout/components/sidebar-mobile-v1/components/sidebar/sidebar.component.d.ts +35 -0
  241. package/modules/layout/components/sidebar-mobile-v1/components/user/user.component.d.ts +24 -0
  242. package/modules/layout/components/sidebar-mobile-v1/main.component.d.ts +21 -0
  243. package/modules/layout/components/sidebar-v1/components/sidebar/sidebar.component.d.ts +22 -29
  244. package/modules/layout/components/sidebar-v1/components/user/user.component.d.ts +11 -17
  245. package/modules/layout/components/sidebar-v1/main.component.d.ts +14 -14
  246. package/modules/layout/configurations/layout.configuration.d.ts +46 -3
  247. package/modules/layout/modules/forbidden/pages/root/root.component.d.ts +3 -8
  248. package/modules/layout/modules/home/components/home-page/home-page.component.d.ts +2 -5
  249. package/modules/layout/modules/not-found/pages/root/root.component.d.ts +3 -8
  250. package/modules/layout/pipes/high-light-search.pipe.d.ts +1 -1
  251. package/modules/layout/services/index.d.ts +1 -0
  252. package/modules/layout/services/layout.service.d.ts +10 -0
  253. package/modules/layout/services/menu/menu.model.d.ts +4 -1
  254. package/modules/layout/services/storage/storage.service.d.ts +0 -3
  255. package/modules/permission/src/configurations/permission.configuration.d.ts +56 -2
  256. package/modules/permission/src/directives/permission.directive.d.ts +5 -8
  257. package/modules/permission/src/guards/permission.guard.d.ts +2 -1
  258. package/modules/permission/src/services/permission.service.d.ts +6 -9
  259. package/package.json +107 -79
  260. package/public-api.d.ts +0 -1
  261. package/sd-angular-core-19.0.0-beta.81.tgz +0 -0
  262. package/services/api/src/api.model.d.ts +6 -1
  263. package/services/confirm/src/lib/confirm.service.d.ts +5 -0
  264. package/services/docx/index.d.ts +1 -0
  265. package/services/docx/src/lib/docx.model.d.ts +9 -0
  266. package/services/docx/src/lib/docx.service.d.ts +13 -0
  267. package/services/docx/src/public-api.d.ts +2 -0
  268. package/services/index.d.ts +1 -0
  269. package/services/notify/index.d.ts +1 -0
  270. package/services/notify/src/notify.model.d.ts +1 -1
  271. package/services/notify/src/notify.service.d.ts +5 -5
  272. package/utilities/extensions/src/string.extension.d.ts +3 -0
  273. package/utilities/extensions/src/utility.extension.d.ts +1 -0
  274. package/utilities/models/index.d.ts +3 -0
  275. package/utilities/models/src/filter.model.d.ts +17 -4
  276. package/utilities/models/src/icon.model.d.ts +2 -0
  277. package/utilities/models/src/nested-key-of.model.d.ts +5 -0
  278. package/utilities/models/src/order.model.d.ts +2 -1
  279. package/utilities/models/src/paging.model.d.ts +2 -1
  280. package/utilities/models/src/pattern.model.d.ts +1 -1
  281. package/utilities/models/src/unwrap-signal.model.d.ts +6 -0
  282. package/components/document-builder/src/plugins/comment/comment.plugin.d.ts +0 -4
  283. package/components/document-builder/src/plugins/table-fit/table-fit.plugin.d.ts +0 -4
  284. package/components/modal/src/modal/modal.component.d.ts +0 -31
  285. package/components/table/src/components/desktop-cell-view/desktop-cell-view.component.d.ts +0 -14
  286. package/fesm2022/sd-angular-core-guards-permission.mjs +0 -155
  287. package/fesm2022/sd-angular-core-guards-permission.mjs.map +0 -1
  288. package/fesm2022/sd-angular-core-guards.mjs +0 -6
  289. package/fesm2022/sd-angular-core-guards.mjs.map +0 -1
  290. package/fesm2022/sd-angular-core-modules-oidc.mjs +0 -127
  291. package/fesm2022/sd-angular-core-modules-oidc.mjs.map +0 -1
  292. package/guards/index.d.ts +0 -1
  293. package/guards/permission/index.d.ts +0 -4
  294. package/guards/permission/src/configurations/index.d.ts +0 -1
  295. package/guards/permission/src/configurations/permission.configuration.d.ts +0 -8
  296. package/guards/permission/src/directives/index.d.ts +0 -1
  297. package/guards/permission/src/directives/permission.directive.d.ts +0 -12
  298. package/guards/permission/src/guards/index.d.ts +0 -1
  299. package/guards/permission/src/guards/permission.guard.d.ts +0 -13
  300. package/guards/permission/src/services/index.d.ts +0 -1
  301. package/guards/permission/src/services/permission.service.d.ts +0 -15
  302. package/modules/oidc/dynamic-sts.loader.d.ts +0 -11
  303. package/modules/oidc/index.d.ts +0 -2
  304. package/modules/oidc/oidc.configuration.d.ts +0 -11
  305. package/modules/oidc/oidc.module.d.ts +0 -14
  306. package/sd-angular-core-19.0.0-beta.8.tgz +0 -0
@@ -0,0 +1,933 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, DestroyRef, ChangeDetectorRef, input, booleanAttribute, computed, model, output, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { NgForm, FormGroup } from '@angular/forms';
5
+ import * as i2 from '@angular/material/form-field';
6
+ import { MatFormFieldModule } from '@angular/material/form-field';
7
+ import * as i3 from '@angular/material/icon';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import * as i4 from '@angular/material/tooltip';
10
+ import { MatTooltipModule } from '@angular/material/tooltip';
11
+ import * as i1 from '@ckeditor/ckeditor5-angular';
12
+ import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
13
+ import { Plugin, FileRepository, Image, ImageResize, ImageUpload, ImageStyle, ImageToolbar, ClassicEditor, Essentials, FontColor, FontSize, Paragraph, Bold, Italic, Underline, List, Undo, Widget, Alignment } from 'ckeditor5';
14
+ import { Subject } from 'rxjs';
15
+ import { debounceTime } from 'rxjs/operators';
16
+ import * as uuid from 'uuid';
17
+ import { SdNotifyService } from '@sd-angular/core/services';
18
+ import { SdFormControl, HandleSdCustomValidator } from '@sd-angular/core/forms/models';
19
+ import { SdLabel } from '@sd-angular/core/forms/label';
20
+
21
+ const SD_EDITOR_CONFIGURATION = new InjectionToken('sd.editor.configuration');
22
+
23
+ // Dựa vào file để detect định dạng thay vì chỉ check tên
24
+ // Tránh user gửi file không đúng, ví dụ file docx nhưng đổi tên thành ảnh docx.jpg
25
+ const detectFormatFromBytes = async (file) => {
26
+ const buffer = await file.slice(0, 12).arrayBuffer();
27
+ const b = new Uint8Array(buffer);
28
+ if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
29
+ return 'jpg';
30
+ if (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47)
31
+ return 'png';
32
+ if (b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 && b[8] === 0x57)
33
+ return 'webp';
34
+ if (b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38)
35
+ return 'gif';
36
+ if (b[4] === 0x66 && b[5] === 0x74 && b[6] === 0x79 && b[7] === 0x70) {
37
+ const brand = String.fromCharCode(b[8], b[9], b[10], b[11]);
38
+ if (brand === 'avif')
39
+ return 'avif';
40
+ if (brand.startsWith('hei'))
41
+ return 'heic';
42
+ }
43
+ return '';
44
+ };
45
+ const getImageInfo = async (file) => {
46
+ const sizeMB = file.size / (1024 * 1024);
47
+ const loadDimensions = new Promise(resolve => {
48
+ const url = URL.createObjectURL(file);
49
+ const img = new window.Image();
50
+ img.onload = () => {
51
+ URL.revokeObjectURL(url);
52
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
53
+ };
54
+ img.onerror = () => {
55
+ URL.revokeObjectURL(url);
56
+ resolve({ width: 0, height: 0 });
57
+ };
58
+ img.src = url;
59
+ });
60
+ const [format, { width, height }] = await Promise.all([detectFormatFromBytes(file), loadDimensions]);
61
+ return { width, height, sizeMB, format };
62
+ };
63
+ const validateImageFile = async (file, validation, onWarning) => {
64
+ const { width, height, sizeMB, format } = await getImageInfo(file);
65
+ if (validation.allowedFormats !== undefined) {
66
+ const normalizedFormats = validation.allowedFormats.map((f) => {
67
+ const fmt = f.replace(/^\./, '').toLowerCase();
68
+ return fmt === 'jpeg' ? 'jpg' : fmt;
69
+ });
70
+ if (!normalizedFormats.includes(format)) {
71
+ onWarning?.(`Định dạng file ".${format}" không được phép. Các định dạng hợp lệ: ${normalizedFormats.join(', ')}`);
72
+ return false;
73
+ }
74
+ }
75
+ if (validation.maxSizeMB !== undefined && sizeMB > validation.maxSizeMB) {
76
+ onWarning?.(`Dung lượng file ${sizeMB.toFixed(2)}MB vượt quá giới hạn ${validation.maxSizeMB}MB`);
77
+ return false;
78
+ }
79
+ if (validation.minWidth !== undefined && width < validation.minWidth) {
80
+ onWarning?.(`Chiều rộng ảnh ${width}px nhỏ hơn giới hạn tối thiểu ${validation.minWidth}px`);
81
+ return false;
82
+ }
83
+ if (validation.minHeight !== undefined && height < validation.minHeight) {
84
+ onWarning?.(`Chiều cao ảnh ${height}px nhỏ hơn giới hạn tối thiểu ${validation.minHeight}px`);
85
+ return false;
86
+ }
87
+ if (validation.maxWidth !== undefined && width > validation.maxWidth) {
88
+ onWarning?.(`Chiều rộng ảnh ${width}px vượt quá giới hạn ${validation.maxWidth}px`);
89
+ return false;
90
+ }
91
+ if (validation.maxHeight !== undefined && height > validation.maxHeight) {
92
+ onWarning?.(`Chiều cao ảnh ${height}px vượt quá giới hạn ${validation.maxHeight}px`);
93
+ return false;
94
+ }
95
+ return true;
96
+ };
97
+ const validateAndGetFile = async (loader, validation, onWarning) => {
98
+ const file = (await loader.file);
99
+ if (!file) {
100
+ throw new Error('No file found');
101
+ }
102
+ if (validation) {
103
+ const valid = await validateImageFile(file, validation, onWarning);
104
+ if (!valid) {
105
+ throw new Error('Image validation failed');
106
+ }
107
+ }
108
+ return file;
109
+ };
110
+
111
+ const getBatchConfig = (option) => {
112
+ const imageConfig = option?.imageConfig;
113
+ return {
114
+ batchSize: Math.max(1, imageConfig?.batchSize ?? 2),
115
+ maxConcurrent: Math.max(1, imageConfig?.maxConcurrentUploads ?? 2),
116
+ };
117
+ };
118
+ const getActiveBlobUrls = (editor) => {
119
+ const urls = new Set();
120
+ const root = editor.model.document.getRoot();
121
+ if (!root)
122
+ return urls;
123
+ for (const item of editor.model.createRangeIn(root).getItems()) {
124
+ if (item.is('element', 'imageBlock') || item.is('element', 'imageInline')) {
125
+ const src = item.getAttribute('src');
126
+ if (src?.startsWith('blob:'))
127
+ urls.add(src);
128
+ }
129
+ }
130
+ return urls;
131
+ };
132
+ const filterActivePendingFiles = (pendingFiles, activeBlobUrls) => {
133
+ const toUpload = [];
134
+ for (const [blobUrl, file] of pendingFiles) {
135
+ if (activeBlobUrls.has(blobUrl)) {
136
+ toUpload.push([blobUrl, file]);
137
+ }
138
+ else {
139
+ URL.revokeObjectURL(blobUrl);
140
+ pendingFiles.delete(blobUrl);
141
+ }
142
+ }
143
+ return toUpload;
144
+ };
145
+ const runBatchUploads = async (toUpload, uploadFn, batchSize, maxConcurrent) => {
146
+ const batches = [];
147
+ for (let i = 0; i < toUpload.length; i += batchSize) {
148
+ batches.push(toUpload.slice(i, i + batchSize));
149
+ }
150
+ const replacements = new Map();
151
+ const failedBatches = [];
152
+ let head = 0;
153
+ const runWorker = async () => {
154
+ while (head < batches.length) {
155
+ const batch = batches[head++];
156
+ try {
157
+ const details = await uploadFn(batch.map(([, file]) => file));
158
+ if (details.length !== batch.length) {
159
+ throw new Error(`API returned ${details.length} results for ${batch.length} files`);
160
+ }
161
+ batch.forEach(([blobUrl], idx) => {
162
+ replacements.set(blobUrl, details[idx]);
163
+ URL.revokeObjectURL(blobUrl);
164
+ });
165
+ }
166
+ catch {
167
+ failedBatches.push(batch);
168
+ }
169
+ }
170
+ };
171
+ await Promise.all(Array.from({ length: Math.min(maxConcurrent, batches.length) }, runWorker));
172
+ return { replacements, failedBatches };
173
+ };
174
+ const applyReplacementsToEditor = (editor, replacements) => {
175
+ if (replacements.size === 0)
176
+ return;
177
+ editor.model.change(writer => {
178
+ const root = editor.model.document.getRoot();
179
+ for (const item of editor.model.createRangeIn(root).getItems()) {
180
+ if (!item.is('element', 'imageBlock') && !item.is('element', 'imageInline'))
181
+ continue;
182
+ const detail = replacements.get(item.getAttribute('src'));
183
+ if (detail)
184
+ writer.setAttribute('src', detail.cdn, item);
185
+ }
186
+ });
187
+ };
188
+
189
+ const parseStyle = (style) => {
190
+ const map = new Map();
191
+ for (const decl of style.split(';')) {
192
+ const colon = decl.indexOf(':');
193
+ if (colon === -1)
194
+ continue;
195
+ const key = decl.slice(0, colon).trim();
196
+ const value = decl.slice(colon + 1).trim();
197
+ if (key && value)
198
+ map.set(key, value);
199
+ }
200
+ return map;
201
+ };
202
+ const serializeStyle = (map) => [...map.entries()].map(([k, v]) => `${k}:${v}`).join(';');
203
+ const countTextLength = (content) => {
204
+ if (!content)
205
+ return 0;
206
+ const doc = new DOMParser().parseFromString(content, 'text/html');
207
+ doc.querySelectorAll('img, figure').forEach(el => el.remove());
208
+ return doc.body.textContent?.length ?? 0;
209
+ };
210
+ const imageClassesToInlineStyles = (html) => {
211
+ if (!html)
212
+ return html;
213
+ const doc = new DOMParser().parseFromString(html, 'text/html');
214
+ doc.querySelectorAll('figure.image').forEach(figure => {
215
+ const styleMap = parseStyle(figure.getAttribute('style') || '');
216
+ const classList = figure.classList;
217
+ const hasResizeWidth = styleMap.has('width') && /\d/.test(styleMap.get('width'));
218
+ styleMap.set('display', 'block');
219
+ styleMap.set('max-width', '100%');
220
+ if (classList.contains('image-style-align-left')) {
221
+ if (hasResizeWidth && styleMap.get('width') === '100%') {
222
+ styleMap.delete('float');
223
+ styleMap.set('margin', '0 auto 0 0');
224
+ }
225
+ else {
226
+ styleMap.set('float', 'left');
227
+ styleMap.set('margin', '0 1em 0 0');
228
+ }
229
+ }
230
+ else if (classList.contains('image-style-align-right')) {
231
+ if (hasResizeWidth && styleMap.get('width') === '100%') {
232
+ styleMap.delete('float');
233
+ styleMap.set('margin', '0 0 0 auto');
234
+ }
235
+ else {
236
+ styleMap.set('float', 'right');
237
+ styleMap.set('margin', '0 0 0 1em');
238
+ }
239
+ }
240
+ else if (classList.contains('image-style-block-align-left')) {
241
+ styleMap.delete('float');
242
+ styleMap.set('margin', '0 auto 0 0');
243
+ }
244
+ else if (classList.contains('image-style-block-align-right')) {
245
+ styleMap.delete('float');
246
+ styleMap.set('margin', '0 0 0 auto');
247
+ }
248
+ else {
249
+ styleMap.delete('float');
250
+ styleMap.set('margin', '0 auto');
251
+ }
252
+ if (!hasResizeWidth)
253
+ styleMap.set('width', 'fit-content');
254
+ figure.setAttribute('style', serializeStyle(styleMap));
255
+ if (hasResizeWidth) {
256
+ const img = figure.querySelector('img');
257
+ if (img) {
258
+ const imgStyle = parseStyle(img.getAttribute('style') || '');
259
+ imgStyle.set('width', '100%');
260
+ imgStyle.set('height', 'auto');
261
+ img.setAttribute('style', serializeStyle(imgStyle));
262
+ }
263
+ }
264
+ });
265
+ return doc.body.innerHTML;
266
+ };
267
+
268
+ class UploadQueue {
269
+ #queue = [];
270
+ #running = 0;
271
+ maxConcurrent;
272
+ constructor(maxConcurrent) {
273
+ this.maxConcurrent = maxConcurrent;
274
+ }
275
+ async run(task) {
276
+ if (this.#running >= this.maxConcurrent) {
277
+ await new Promise(resolve => {
278
+ this.#queue.push(resolve);
279
+ });
280
+ }
281
+ this.#running++;
282
+ try {
283
+ return await task();
284
+ }
285
+ finally {
286
+ this.#running--;
287
+ this.#queue.shift()?.();
288
+ }
289
+ }
290
+ }
291
+ class BatchUploadScheduler {
292
+ #pending = [];
293
+ #timer = null;
294
+ #batchSize;
295
+ #uploadFn;
296
+ #queue;
297
+ constructor(batchSize, uploadFn, queue) {
298
+ this.#batchSize = batchSize;
299
+ this.#uploadFn = uploadFn;
300
+ this.#queue = queue;
301
+ }
302
+ enqueue(file) {
303
+ return new Promise((resolve, reject) => {
304
+ this.#pending.push({ file, resolve, reject });
305
+ if (this.#timer) {
306
+ clearTimeout(this.#timer);
307
+ }
308
+ if (this.#pending.length >= this.#batchSize) {
309
+ this.#timer = null;
310
+ this.#flush();
311
+ }
312
+ else {
313
+ this.#timer = setTimeout(() => this.#flush(), 50);
314
+ }
315
+ });
316
+ }
317
+ destroy() {
318
+ if (this.#timer !== null) {
319
+ clearTimeout(this.#timer);
320
+ this.#timer = null;
321
+ }
322
+ this.#pending.splice(0).forEach(p => p.reject(new Error('Upload cancelled')));
323
+ }
324
+ #flush() {
325
+ this.#timer = null;
326
+ const all = this.#pending.splice(0);
327
+ if (all.length === 0) {
328
+ return;
329
+ }
330
+ for (let i = 0; i < all.length; i += this.#batchSize) {
331
+ const batch = all.slice(i, i + this.#batchSize);
332
+ this.#queue
333
+ .run(() => this.#uploadFn(batch.map(p => p.file)))
334
+ .then(details => {
335
+ batch.forEach((p, idx) => {
336
+ if (details && details[idx]) {
337
+ p.resolve(details[idx]);
338
+ }
339
+ else {
340
+ p.reject(new Error('Upload failed: Missing details from server'));
341
+ }
342
+ });
343
+ })
344
+ .catch(err => {
345
+ batch.forEach(p => p.reject(err));
346
+ });
347
+ }
348
+ }
349
+ }
350
+ class DeferredImageUploadAdapter {
351
+ #loader;
352
+ #pendingFiles;
353
+ #imageMetaMap;
354
+ #validation;
355
+ #onWarning;
356
+ #lazyLoad;
357
+ #currentBlobUrl = null;
358
+ constructor(loader, pendingFiles, imageMetaMap, options) {
359
+ this.#loader = loader;
360
+ this.#pendingFiles = pendingFiles;
361
+ this.#imageMetaMap = imageMetaMap;
362
+ this.#validation = options.validation;
363
+ this.#onWarning = options.onWarning;
364
+ this.#lazyLoad = options.lazyLoad;
365
+ }
366
+ upload = async () => {
367
+ const file = await validateAndGetFile(this.#loader, this.#validation, this.#onWarning);
368
+ if (!file)
369
+ throw new Error('Image validation failed');
370
+ this.#currentBlobUrl = URL.createObjectURL(file);
371
+ this.#pendingFiles.set(this.#currentBlobUrl, file);
372
+ this.#imageMetaMap.set(this.#currentBlobUrl, { fileName: file.name, lazyLoad: this.#lazyLoad });
373
+ return { default: this.#currentBlobUrl };
374
+ };
375
+ abort() {
376
+ if (this.#currentBlobUrl) {
377
+ URL.revokeObjectURL(this.#currentBlobUrl);
378
+ this.#pendingFiles.delete(this.#currentBlobUrl);
379
+ this.#imageMetaMap.delete(this.#currentBlobUrl);
380
+ }
381
+ }
382
+ }
383
+ class ImmediateImageUploadAdapter {
384
+ #loader;
385
+ #validation;
386
+ #onWarning;
387
+ #scheduler;
388
+ #imageMetaMap;
389
+ #lazyLoad;
390
+ constructor(loader, scheduler, imageMetaMap, options) {
391
+ this.#loader = loader;
392
+ this.#scheduler = scheduler;
393
+ this.#imageMetaMap = imageMetaMap;
394
+ this.#validation = options.validation;
395
+ this.#onWarning = options.onWarning;
396
+ this.#lazyLoad = options.lazyLoad;
397
+ }
398
+ upload = async () => {
399
+ const file = await validateAndGetFile(this.#loader, this.#validation, this.#onWarning);
400
+ if (!file)
401
+ throw new Error('Image validation failed');
402
+ const detail = await this.#scheduler.enqueue(file);
403
+ this.#imageMetaMap.set(detail.cdn, {
404
+ name: detail.name,
405
+ fileName: file.name,
406
+ idOrKey: detail.idOrKey,
407
+ lazyLoad: this.#lazyLoad,
408
+ });
409
+ return { default: detail.cdn };
410
+ };
411
+ abort() { }
412
+ }
413
+ class EditorImageUploadPlugin extends Plugin {
414
+ pendingFiles = new Map();
415
+ uploadQueue;
416
+ #batchScheduler;
417
+ #imageMetaMap = new Map();
418
+ setMeta(cdnUrl, meta) {
419
+ this.#imageMetaMap.set(cdnUrl, { name: meta.name, idOrKey: meta.idOrKey, lazyLoad: meta.lazyLoad });
420
+ }
421
+ static get pluginName() {
422
+ return 'EditorImageUploadPlugin';
423
+ }
424
+ static get requires() {
425
+ return [FileRepository, Image, ImageResize, ImageUpload, ImageStyle, ImageToolbar];
426
+ }
427
+ init() {
428
+ const editor = this.editor;
429
+ const getOption = editor.config.get('getOption');
430
+ const maxConcurrent = getOption?.()?.imageConfig?.maxConcurrentUploads ?? 2;
431
+ this.uploadQueue = new UploadQueue(maxConcurrent);
432
+ this.#registerSchemaAndConverters();
433
+ this.#listenForSrcChanges();
434
+ editor.editing.view.document.on('clipboardInput', (evt, data) => {
435
+ const dataTransfer = data.dataTransfer;
436
+ if (!dataTransfer)
437
+ return;
438
+ const hasImageFiles = Array.from(dataTransfer.files ?? []).some(f => f.type.startsWith('image/'));
439
+ const hasImageInHtml = /<img[\s>]/i.test(dataTransfer.getData('text/html') ?? '');
440
+ if (hasImageFiles || hasImageInHtml) {
441
+ evt.stop();
442
+ }
443
+ }, { priority: 'highest' });
444
+ const uploadImageCommand = editor.commands.get('uploadImage');
445
+ if (uploadImageCommand) {
446
+ uploadImageCommand.on('execute', (evt, args) => {
447
+ const options = args[0] || {};
448
+ const option = getOption?.();
449
+ const maxPerSelection = option?.imageConfig?.maxImagesPerSelection;
450
+ if (maxPerSelection !== undefined) {
451
+ const files = Array.isArray(options?.file) ? options.file : options?.file ? [options.file] : [];
452
+ if (files.length > maxPerSelection) {
453
+ option?.onWarning?.(`Chỉ được phép chọn tối đa ${maxPerSelection} ảnh mỗi lần`);
454
+ evt.stop();
455
+ }
456
+ }
457
+ }, { priority: 'high' });
458
+ }
459
+ editor.once('ready', () => {
460
+ const notification = editor.plugins.get('Notification');
461
+ notification?.on('show:warning', (evt) => {
462
+ evt.stop();
463
+ }, { priority: 'highest' });
464
+ });
465
+ editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
466
+ const option = getOption?.();
467
+ const imageConfig = option?.imageConfig;
468
+ const uploadMode = imageConfig?.uploadMode ?? 'deferred';
469
+ const validation = imageConfig?.validation;
470
+ const onWarning = option?.onWarning;
471
+ const lazyLoad = imageConfig?.lazyLoad ?? true;
472
+ if (uploadMode === 'deferred') {
473
+ return new DeferredImageUploadAdapter(loader, this.pendingFiles, this.#imageMetaMap, { validation, onWarning, lazyLoad });
474
+ }
475
+ if (!this.#batchScheduler) {
476
+ const uploadFn = option?.uploadFn;
477
+ if (uploadFn) {
478
+ const batchSize = imageConfig?.batchSize ?? 2;
479
+ this.#batchScheduler = new BatchUploadScheduler(batchSize, uploadFn, this.uploadQueue);
480
+ }
481
+ }
482
+ if (!this.#batchScheduler) {
483
+ throw new Error('No upload function configured');
484
+ }
485
+ return new ImmediateImageUploadAdapter(loader, this.#batchScheduler, this.#imageMetaMap, { validation, onWarning, lazyLoad });
486
+ };
487
+ }
488
+ destroy() {
489
+ super.destroy();
490
+ this.#batchScheduler?.destroy();
491
+ for (const blobUrl of this.pendingFiles.keys()) {
492
+ URL.revokeObjectURL(blobUrl);
493
+ }
494
+ this.pendingFiles.clear();
495
+ this.#imageMetaMap.clear();
496
+ }
497
+ #registerSchemaAndConverters() {
498
+ const editor = this.editor;
499
+ const schema = editor.model.schema;
500
+ for (const type of ['imageBlock', 'imageInline']) {
501
+ if (schema.isRegistered(type)) {
502
+ schema.extend(type, { allowAttributes: ['loading', 'imageId'] });
503
+ }
504
+ }
505
+ // Downcast: model → HTML view
506
+ editor.conversion.for('downcast').add((dispatcher) => {
507
+ for (const [modelAttr, htmlAttr] of [
508
+ ['loading', 'loading'],
509
+ ['imageId', 'id'],
510
+ ]) {
511
+ const handleAttribute = (isBlock) => (evt, data, conversionApi) => {
512
+ if (!conversionApi.consumable.consume(data.item, evt.name))
513
+ return;
514
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
515
+ const viewImg = isBlock ? this.#findImg(viewElement) : viewElement;
516
+ if (!viewImg)
517
+ return;
518
+ if (data.attributeNewValue != null) {
519
+ conversionApi.writer.setAttribute(htmlAttr, String(data.attributeNewValue), viewImg);
520
+ }
521
+ else {
522
+ conversionApi.writer.removeAttribute(htmlAttr, viewImg);
523
+ }
524
+ };
525
+ dispatcher.on(`attribute:${modelAttr}:imageBlock`, handleAttribute(true));
526
+ dispatcher.on(`attribute:${modelAttr}:imageInline`, handleAttribute(false));
527
+ }
528
+ });
529
+ // Upcast: HTML → model
530
+ editor.conversion.for('upcast').add((dispatcher) => {
531
+ dispatcher.on('element:figure', (_evt, data, conversionApi) => {
532
+ if (!data.viewItem.hasClass('image'))
533
+ return;
534
+ const modelElement = data.modelRange?.start?.nodeAfter;
535
+ if (!modelElement)
536
+ return;
537
+ const viewImg = this.#findImg(data.viewItem);
538
+ if (!viewImg)
539
+ return;
540
+ this.#transferHtmlAttrsToModel(viewImg, modelElement, conversionApi);
541
+ }, { priority: 'low' });
542
+ dispatcher.on('element:img', (_evt, data, conversionApi) => {
543
+ const modelElement = data.modelRange?.start?.nodeAfter;
544
+ if (!modelElement)
545
+ return;
546
+ this.#transferHtmlAttrsToModel(data.viewItem, modelElement, conversionApi);
547
+ }, { priority: 'low' });
548
+ });
549
+ }
550
+ #listenForSrcChanges() {
551
+ const editor = this.editor;
552
+ editor.model.document.on('change', () => {
553
+ const changes = editor.model.document.differ.getChanges();
554
+ const updates = [];
555
+ for (const change of changes) {
556
+ if (change.type !== 'attribute' || change.attributeKey !== 'src')
557
+ continue;
558
+ const newSrc = change.attributeNewValue;
559
+ const meta = this.#imageMetaMap.get(newSrc);
560
+ if (!meta)
561
+ continue;
562
+ const node = change.range?.start?.nodeAfter;
563
+ if (node?.is('element')) {
564
+ updates.push({ element: node, meta });
565
+ this.#imageMetaMap.delete(newSrc);
566
+ }
567
+ }
568
+ if (updates.length === 0)
569
+ return;
570
+ editor.model.enqueueChange('transparent', (writer) => {
571
+ for (const { element, meta } of updates) {
572
+ if (meta.name || meta.fileName) {
573
+ writer.setAttribute('alt', meta.name || this.#generateAlt(meta.fileName), element);
574
+ }
575
+ if (meta.lazyLoad) {
576
+ writer.setAttribute('loading', 'lazy', element);
577
+ }
578
+ if (meta.idOrKey) {
579
+ writer.setAttribute('imageId', meta.idOrKey, element);
580
+ }
581
+ }
582
+ });
583
+ });
584
+ }
585
+ #findImg(viewElement) {
586
+ if (!viewElement)
587
+ return null;
588
+ if (viewElement.is?.('element', 'img'))
589
+ return viewElement;
590
+ for (const child of viewElement.getChildren?.() ?? []) {
591
+ if (child.is?.('element', 'img'))
592
+ return child;
593
+ }
594
+ return null;
595
+ }
596
+ #transferHtmlAttrsToModel(viewImg, modelElement, conversionApi) {
597
+ const schema = this.editor.model.schema;
598
+ if (viewImg.hasAttribute('loading') && schema.checkAttribute(modelElement, 'loading')) {
599
+ conversionApi.writer.setAttribute('loading', viewImg.getAttribute('loading'), modelElement);
600
+ }
601
+ if (viewImg.hasAttribute('id') && schema.checkAttribute(modelElement, 'imageId')) {
602
+ conversionApi.writer.setAttribute('imageId', viewImg.getAttribute('id'), modelElement);
603
+ }
604
+ }
605
+ #generateAlt(fileName) {
606
+ if (!fileName)
607
+ return 'Image';
608
+ const nameWithoutExt = fileName.replace(/\.[^.]+$/, '');
609
+ const result = nameWithoutExt
610
+ .replace(/[_\-\.]+/g, ' ')
611
+ .replace(/\s+/g, ' ')
612
+ .trim();
613
+ return result || 'Image';
614
+ }
615
+ }
616
+
617
+ class SdEditor {
618
+ // Injected
619
+ #destroyRef = inject(DestroyRef);
620
+ #cdRef = inject(ChangeDetectorRef);
621
+ #uploadConfig = inject(SD_EDITOR_CONFIGURATION, { optional: true });
622
+ #notifyService = inject(SdNotifyService);
623
+ // Input
624
+ option = input({});
625
+ height = input('250px');
626
+ maxHeight = input('250px');
627
+ maxlength = input(undefined);
628
+ label = input();
629
+ helperText = input();
630
+ required = input(false, { transform: booleanAttribute });
631
+ disabled = input(false, { transform: booleanAttribute });
632
+ readonly = input(false, { transform: booleanAttribute });
633
+ hideInlineError = input(false, { transform: booleanAttribute });
634
+ inlineError = input();
635
+ placeholder = input();
636
+ validator = input();
637
+ form = input(undefined, {
638
+ transform: (val) => {
639
+ if (!val)
640
+ return undefined;
641
+ if (val instanceof NgForm)
642
+ return val.form;
643
+ if (val instanceof FormGroup)
644
+ return val;
645
+ if (val?.form instanceof FormGroup)
646
+ return val.form;
647
+ return undefined;
648
+ },
649
+ });
650
+ key = input(undefined);
651
+ autoIdInput = input(undefined, { alias: 'autoId' });
652
+ autoId = computed(() => (this.autoIdInput() ? `forms-editor-${this.autoIdInput()}` : undefined));
653
+ name = input(uuid.v4());
654
+ valueModel = model('', { alias: 'model' });
655
+ // Output
656
+ sdChange = output();
657
+ sdBlur = output();
658
+ sdFocus = output();
659
+ // State
660
+ _textLength = signal(0);
661
+ formControl = new SdFormControl();
662
+ isOverLimit = computed(() => {
663
+ const max = this.maxlength();
664
+ return max !== undefined && this._textLength() > max;
665
+ });
666
+ get errorMessage() {
667
+ const errors = this.formControl.errors;
668
+ if (!errors)
669
+ return undefined;
670
+ if (errors['required'])
671
+ return 'Vui lòng nhập nội dung';
672
+ if (errors['maxlength'])
673
+ return `Số ký tự tối đa: ${this.maxlength()}`;
674
+ if (errors['customValidator'])
675
+ return errors['customValidator'];
676
+ if (errors['inlineError'])
677
+ return this.inlineError();
678
+ return undefined;
679
+ }
680
+ Editor = ClassicEditor;
681
+ #editor;
682
+ #contentChangeSubject = new Subject();
683
+ editorConfig = computed(() => {
684
+ const opt = this.option();
685
+ const uploadFn = this.#buildUploadFn();
686
+ const enableImageUpload = !!uploadFn;
687
+ const plugins = [Essentials, FontColor, FontSize, Paragraph, Bold, Italic, Underline, List, Undo, Widget, Alignment];
688
+ if (enableImageUpload)
689
+ plugins.push(EditorImageUploadPlugin);
690
+ const toolbarItems = [
691
+ 'bold',
692
+ 'italic',
693
+ 'underline',
694
+ '|',
695
+ 'fontSize',
696
+ 'fontColor',
697
+ '|',
698
+ 'alignment:left',
699
+ 'alignment:center',
700
+ 'alignment:right',
701
+ 'alignment:justify',
702
+ '|',
703
+ 'bulletedList',
704
+ 'numberedList',
705
+ ];
706
+ if (enableImageUpload)
707
+ toolbarItems.push('|', 'uploadImage');
708
+ const config = {
709
+ licenseKey: 'GPL',
710
+ getOption: () => ({
711
+ ...opt,
712
+ uploadFn,
713
+ onWarning: (msg) => this.#notifyService.warning(msg),
714
+ }),
715
+ plugins,
716
+ toolbar: { items: toolbarItems, shouldNotGroupWhenFull: true },
717
+ placeholder: this.placeholder(),
718
+ fontColor: { columns: 5, documentColors: 10, colorPicker: { format: 'hex' } },
719
+ fontSize: { options: [10, 11, 12, 14, 16, 18, 20], supportAllValues: true },
720
+ };
721
+ if (enableImageUpload) {
722
+ config.image = {
723
+ resizeUnit: '%',
724
+ resizeOptions: [
725
+ { name: 'resizeImage:100', value: '100', label: '100%' },
726
+ { name: 'resizeImage:75', value: '75', label: '75%' },
727
+ { name: 'resizeImage:50', value: '50', label: '50%' },
728
+ { name: 'resizeImage:25', value: '25', label: '25%' },
729
+ { name: 'resizeImage:original', value: null, label: 'Original' },
730
+ ],
731
+ toolbar: ['resizeImage', '|', 'imageStyle:alignBlockLeft', 'imageStyle:alignCenter', 'imageStyle:alignBlockRight'],
732
+ };
733
+ }
734
+ return config;
735
+ });
736
+ constructor() {
737
+ this.#validateDuplicateConfigKeys();
738
+ this.#setupEffects();
739
+ this.#setupSubscriptions();
740
+ this.#setupDestroy();
741
+ }
742
+ // Public API
743
+ onReady = (editor) => {
744
+ this.#editor = editor;
745
+ const currentValue = this.valueModel();
746
+ if (currentValue)
747
+ this.#setContent(currentValue);
748
+ if (this.formControl.disabled)
749
+ editor.enableReadOnlyMode('sd-editor');
750
+ if (this.readonly())
751
+ editor.enableReadOnlyMode('sd-editor-readonly');
752
+ editor.model.document.registerPostFixer(writer => {
753
+ let changed = false;
754
+ for (const change of editor.model.document.differ.getChanges()) {
755
+ if (change.type === 'insert' && (change.name === 'imageBlock' || change.name === 'imageInline')) {
756
+ const element = change.position.nodeAfter;
757
+ if (element && !element.hasAttribute('resizedWidth')) {
758
+ writer.setAttribute('resizedWidth', '100%', element);
759
+ changed = true;
760
+ }
761
+ }
762
+ }
763
+ return changed;
764
+ });
765
+ editor.model.document.on('change:data', () => this.#contentChangeSubject.next(editor.getData()));
766
+ editor.editing.view.document.on('focus', evt => this.sdFocus.emit(evt.domEvent));
767
+ editor.editing.view.document.on('blur', evt => {
768
+ this.sdBlur.emit(evt.domEvent);
769
+ this.formControl.markAsTouched();
770
+ this.#cdRef.markForCheck();
771
+ });
772
+ };
773
+ focusEditor = () => this.#editor?.editing?.view?.focus?.();
774
+ #uploadImages = async () => {
775
+ const uploadMode = this.option()?.imageConfig?.uploadMode;
776
+ if (uploadMode !== 'deferred')
777
+ return;
778
+ const uploadFn = this.#buildUploadFn();
779
+ if (!uploadFn) {
780
+ throw new Error('No upload function configured for image upload');
781
+ }
782
+ const plugin = this.#editor?.plugins?.get(EditorImageUploadPlugin);
783
+ if (!plugin || plugin.pendingFiles.size === 0)
784
+ return;
785
+ const { batchSize, maxConcurrent } = getBatchConfig(this.option());
786
+ const activeBlobUrls = this.#editor ? getActiveBlobUrls(this.#editor) : new Set();
787
+ const toUpload = filterActivePendingFiles(plugin.pendingFiles, activeBlobUrls);
788
+ // Capture trước await để tránh race condition: ảnh được chèn trong lúc chờ API
789
+ const processedBlobUrls = new Set(toUpload.map(([blobUrl]) => blobUrl));
790
+ const { replacements, failedBatches } = await runBatchUploads(toUpload, uploadFn, batchSize, maxConcurrent);
791
+ if (this.#editor) {
792
+ const lazyLoad = this.option()?.imageConfig?.lazyLoad ?? true;
793
+ for (const detail of replacements.values()) {
794
+ plugin.setMeta(detail.cdn, { name: detail.name, idOrKey: detail.idOrKey, lazyLoad });
795
+ }
796
+ applyReplacementsToEditor(this.#editor, replacements);
797
+ }
798
+ // Chỉ xóa đúng các entries đã được xử lý
799
+ for (const blobUrl of processedBlobUrls) {
800
+ plugin.pendingFiles.delete(blobUrl);
801
+ }
802
+ for (const [blobUrl, file] of failedBatches.flat()) {
803
+ plugin.pendingFiles.set(blobUrl, file);
804
+ }
805
+ if (failedBatches.length > 0) {
806
+ throw new Error(`Upload failed for ${failedBatches.flat().length} image(s)`);
807
+ }
808
+ };
809
+ upload = async () => {
810
+ await this.#uploadImages();
811
+ return this.#getContent();
812
+ };
813
+ // Setup
814
+ #setupEffects = () => {
815
+ effect(() => {
816
+ this.disabled() ? this.formControl.disable({ emitEvent: false }) : this.formControl.enable({ emitEvent: false });
817
+ });
818
+ effect(() => {
819
+ if (!this.#editor)
820
+ return;
821
+ this.readonly() ? this.#editor.enableReadOnlyMode('sd-editor-readonly') : this.#editor.disableReadOnlyMode('sd-editor-readonly');
822
+ });
823
+ effect(() => {
824
+ const val = this.valueModel();
825
+ if (this.formControl.value !== val) {
826
+ this.formControl.setValue(val, { emitEvent: false });
827
+ if (this.#editor)
828
+ this.#setContent(val);
829
+ }
830
+ });
831
+ effect(() => this.form()?.addControl(this.name(), this.formControl));
832
+ effect(() => {
833
+ this.required();
834
+ this.maxlength();
835
+ this.inlineError();
836
+ this.validator();
837
+ this.#updateValidators();
838
+ });
839
+ };
840
+ #setupSubscriptions = () => {
841
+ this.formControl.valueChanges.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((val) => {
842
+ const v = val ?? '';
843
+ this.valueModel.set(v);
844
+ this._textLength.set(countTextLength(v));
845
+ if (this.#editor) {
846
+ this.#setContent(v);
847
+ }
848
+ });
849
+ this.formControl.sdChanges.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe(() => this.#cdRef.markForCheck());
850
+ this.#contentChangeSubject.pipe(debounceTime(100), takeUntilDestroyed(this.#destroyRef)).subscribe(content => {
851
+ const out = imageClassesToInlineStyles(content);
852
+ this._textLength.set(countTextLength(out));
853
+ if (this.formControl.value !== out)
854
+ this.formControl.setValue(out, { emitEvent: false });
855
+ this.formControl.updateValueAndValidity({ emitEvent: false });
856
+ this.formControl.sdChanges.next(true);
857
+ this.valueModel.set(out);
858
+ this.sdChange.emit(out);
859
+ });
860
+ };
861
+ #setupDestroy = () => {
862
+ this.#destroyRef.onDestroy(() => {
863
+ this.form()?.removeControl(this.name());
864
+ this.#editor?.destroy?.();
865
+ });
866
+ };
867
+ // Helpers
868
+ #setContent = (content) => this.#editor?.setData?.(content);
869
+ #getContent = () => (this.#editor ? imageClassesToInlineStyles(this.#editor.getData()) : '');
870
+ #getConfigurations = () => {
871
+ const config = this.#uploadConfig;
872
+ if (!config)
873
+ return [];
874
+ return Array.isArray(config) ? config : [config];
875
+ };
876
+ #validateDuplicateConfigKeys() {
877
+ const config = this.#uploadConfig;
878
+ if (!config)
879
+ return;
880
+ const configurations = Array.isArray(config) ? config : [config];
881
+ const seen = new Set();
882
+ for (const cfg of configurations) {
883
+ const normalizedKey = cfg.key === undefined ? '__undefined__' : cfg.key;
884
+ if (seen.has(normalizedKey)) {
885
+ const label = cfg.key === undefined ? 'undefined' : cfg.key;
886
+ throw new Error(`[sd-editor] Duplicate upload configuration key detected: ${label}`);
887
+ }
888
+ seen.add(normalizedKey);
889
+ }
890
+ }
891
+ #getSelectedConfig = () => {
892
+ const configurations = this.#getConfigurations();
893
+ if (!configurations.length)
894
+ return undefined;
895
+ return configurations.find(cfg => cfg.key === this.key());
896
+ };
897
+ #buildUploadFn = () => this.option()?.imageConfig?.uploadFn ?? this.#getSelectedConfig()?.upload;
898
+ #updateValidators = () => {
899
+ const validators = [];
900
+ const asyncValidators = [];
901
+ if (this.required()) {
902
+ validators.push(() => (this._textLength() ? null : { required: true }));
903
+ }
904
+ const max = this.maxlength();
905
+ if (max !== undefined) {
906
+ validators.push(() => (this._textLength() > max ? { maxlength: { requiredLength: max, actualLength: this._textLength() } } : null));
907
+ }
908
+ const inl = this.inlineError();
909
+ if (inl) {
910
+ validators.push(() => ({ inlineError: true }));
911
+ }
912
+ const val = this.validator();
913
+ if (val) {
914
+ asyncValidators.push(HandleSdCustomValidator(val));
915
+ }
916
+ this.formControl.setValidators(validators.length ? validators : null);
917
+ this.formControl.setAsyncValidators(asyncValidators.length ? asyncValidators : null);
918
+ this.formControl.updateValueAndValidity({ emitEvent: false });
919
+ };
920
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
921
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SdEditor, isStandalone: true, selector: "sd-editor", inputs: { option: { classPropertyName: "option", publicName: "option", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, hideInlineError: { classPropertyName: "hideInlineError", publicName: "hideInlineError", isSignal: true, isRequired: false, transformFunction: null }, inlineError: { classPropertyName: "inlineError", publicName: "inlineError", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, validator: { classPropertyName: "validator", publicName: "validator", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, valueModel: { classPropertyName: "valueModel", publicName: "model", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueModel: "modelChange", sdChange: "sdChange", sdBlur: "sdBlur", sdFocus: "sdFocus" }, ngImport: i0, template: "@let lbl = label();\r\n@let hText = helperText();\r\n@let hideErr = hideInlineError();\r\n@let errMsg = errorMessage;\r\n@let req = required();\r\n@let maxLen = maxlength();\r\n@let showCounter = !!maxLen && !formControl.disabled && !hideErr;\r\n\r\n@if (lbl) {\r\n <sd-label [label]=\"lbl\" [required]=\"req\" [helperText]=\"hText\"></sd-label>\r\n}\r\n\r\n<div\r\n class=\"sd-editor\"\r\n [class.sd-editor--readonly]=\"readonly()\"\r\n [class.sd-editor--error]=\"formControl.invalid && formControl.touched\"\r\n [class.has-counter]=\"showCounter\"\r\n [style.--sd-editor-content-min-height]=\"height()\"\r\n [style.--sd-editor-content-max-height]=\"maxHeight()\"\r\n [attr.data-autoId]=\"autoId()\">\r\n <ckeditor [editor]=\"Editor\" [config]=\"editorConfig()\" [disabled]=\"formControl.disabled\" (ready)=\"onReady($event)\"></ckeditor>\r\n\r\n @if (showCounter) {\r\n <div class=\"sd-editor-counter-row\">\r\n <span class=\"sd-editor-counter\" [class.sd-editor-counter--over]=\"isOverLimit()\"> {{ _textLength() }}/{{ maxLen }} </span>\r\n </div>\r\n }\r\n\r\n @if (hideErr && errMsg && formControl.touched) {\r\n <mat-icon class=\"sd-error-icon\" [matTooltip]=\"errMsg\" matTooltipPosition=\"above\">error</mat-icon>\r\n }\r\n</div>\r\n\r\n<div class=\"sd-editor-footer\">\r\n @if (!hideErr && errMsg && formControl.touched) {\r\n <mat-error>{{ errMsg }}</mat-error>\r\n }\r\n</div>\r\n", styles: [".sd-editor{display:flex;flex-direction:column;width:100%;position:relative}.sd-editor-counter-row{display:flex;justify-content:flex-end;align-items:center;padding:4px 12px;border:1px solid var(--ck-color-base-border);border-top:none}.sd-editor-footer{min-height:20px;margin-top:4px}.sd-editor-footer mat-error{font-size:12px;color:var(--mdc-theme-error, #f44336)}.sd-error-icon{position:absolute;right:8px;bottom:8px;z-index:2;color:var(--mdc-theme-error, #f44336);height:20px;width:20px;font-size:20px;cursor:default}.sd-editor--readonly :host ::ng-deep .ck-editor__editable{background:#0000000a;cursor:default}.sd-editor-counter{font-size:12px;color:#0009}.sd-editor-counter--over{color:var(--mdc-theme-error, #f44336)}::ng-deep .ck-powered-by{display:none}:host ::ng-deep ckeditor,:host ::ng-deep .ck-editor{display:flex;flex-direction:column}:host ::ng-deep .ck-editor{--ck-content-font-size: 14px;--ck-content-line-height: 1.5}:host ::ng-deep .ck-editor__main{overflow:visible}:host ::ng-deep .ck-editor__editable{min-height:var(--sd-editor-content-min-height, 200px)!important;max-height:var(--sd-editor-content-max-height, none)!important;padding:24px!important;overflow-y:auto!important;border:1px solid var(--ck-color-base-border)!important}:host ::ng-deep .sd-editor.has-counter .ck-editor__editable{border-bottom:none!important}:host ::ng-deep .sd-editor--error .ck-editor__editable,:host ::ng-deep .sd-editor--error .ck-toolbar{border-color:var(--mdc-theme-error, #f44336)!important}:host ::ng-deep .sd-editor--error.has-counter .sd-editor-counter-row{border-color:var(--mdc-theme-error, #f44336)}:host ::ng-deep .ck-content p{margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{margin-left:0!important;padding-left:20px!important}\n", ":host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content figure.image{position:relative;display:block;width:fit-content;max-width:100%;margin:0 auto;overflow:visible}:host ::ng-deep .ck-content figure.image:hover,:host ::ng-deep .ck-content figure.image.ck-widget_selected{z-index:99999!important}:host ::ng-deep .ck-content figure.image.image-style-align-left{float:left;margin:0 1em 0 0}:host ::ng-deep .ck-content figure.image.image-style-align-left[style*=\"width:100\"]{float:none;margin:0 auto 0 0}:host ::ng-deep .ck-content figure.image.image-style-align-right{float:right;margin:0 0 0 1em}:host ::ng-deep .ck-content figure.image.image-style-align-right[style*=\"width:100\"]{float:none;margin:0 0 0 auto}:host ::ng-deep .ck-content figure.image.image-style-block-align-left{margin:0 auto 0 0}:host ::ng-deep .ck-content figure.image.image-style-block-align-right{margin:0 0 0 auto}:host ::ng-deep .ck-content figure.image .ck-progress-bar{position:absolute;bottom:0;left:0;width:30%!important;height:3px;background:var(--sd-color-primary, #1565c0);border-radius:2px;z-index:10;pointer-events:none;animation:sd-progress-indeterminate 1.4s ease-in-out infinite}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar){overflow:hidden}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar) img{filter:blur(1px) brightness(.85);transition:filter .3s ease}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar):after{content:\"\";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:9;background:#ffffff40;border-radius:inherit}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar):before{content:\"\";position:absolute;top:50%;left:50%;width:28px;height:28px;margin:-14px 0 0 -14px;border:3px solid rgba(255,255,255,.5);border-top-color:var(--sd-color-primary, #1565c0);border-radius:50%;animation:sd-spinner .75s linear infinite;z-index:11;pointer-events:none}@keyframes sd-spinner{to{transform:rotate(360deg)}}@keyframes sd-progress-indeterminate{0%{left:-30%}to{left:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableWatchdog", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: SdLabel, selector: "sd-label", inputs: ["label", "description", "required", "helperText"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
922
+ }
923
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdEditor, decorators: [{
924
+ type: Component,
925
+ args: [{ selector: 'sd-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CKEditorModule, SdLabel, MatFormFieldModule, MatIconModule, MatTooltipModule], template: "@let lbl = label();\r\n@let hText = helperText();\r\n@let hideErr = hideInlineError();\r\n@let errMsg = errorMessage;\r\n@let req = required();\r\n@let maxLen = maxlength();\r\n@let showCounter = !!maxLen && !formControl.disabled && !hideErr;\r\n\r\n@if (lbl) {\r\n <sd-label [label]=\"lbl\" [required]=\"req\" [helperText]=\"hText\"></sd-label>\r\n}\r\n\r\n<div\r\n class=\"sd-editor\"\r\n [class.sd-editor--readonly]=\"readonly()\"\r\n [class.sd-editor--error]=\"formControl.invalid && formControl.touched\"\r\n [class.has-counter]=\"showCounter\"\r\n [style.--sd-editor-content-min-height]=\"height()\"\r\n [style.--sd-editor-content-max-height]=\"maxHeight()\"\r\n [attr.data-autoId]=\"autoId()\">\r\n <ckeditor [editor]=\"Editor\" [config]=\"editorConfig()\" [disabled]=\"formControl.disabled\" (ready)=\"onReady($event)\"></ckeditor>\r\n\r\n @if (showCounter) {\r\n <div class=\"sd-editor-counter-row\">\r\n <span class=\"sd-editor-counter\" [class.sd-editor-counter--over]=\"isOverLimit()\"> {{ _textLength() }}/{{ maxLen }} </span>\r\n </div>\r\n }\r\n\r\n @if (hideErr && errMsg && formControl.touched) {\r\n <mat-icon class=\"sd-error-icon\" [matTooltip]=\"errMsg\" matTooltipPosition=\"above\">error</mat-icon>\r\n }\r\n</div>\r\n\r\n<div class=\"sd-editor-footer\">\r\n @if (!hideErr && errMsg && formControl.touched) {\r\n <mat-error>{{ errMsg }}</mat-error>\r\n }\r\n</div>\r\n", styles: [".sd-editor{display:flex;flex-direction:column;width:100%;position:relative}.sd-editor-counter-row{display:flex;justify-content:flex-end;align-items:center;padding:4px 12px;border:1px solid var(--ck-color-base-border);border-top:none}.sd-editor-footer{min-height:20px;margin-top:4px}.sd-editor-footer mat-error{font-size:12px;color:var(--mdc-theme-error, #f44336)}.sd-error-icon{position:absolute;right:8px;bottom:8px;z-index:2;color:var(--mdc-theme-error, #f44336);height:20px;width:20px;font-size:20px;cursor:default}.sd-editor--readonly :host ::ng-deep .ck-editor__editable{background:#0000000a;cursor:default}.sd-editor-counter{font-size:12px;color:#0009}.sd-editor-counter--over{color:var(--mdc-theme-error, #f44336)}::ng-deep .ck-powered-by{display:none}:host ::ng-deep ckeditor,:host ::ng-deep .ck-editor{display:flex;flex-direction:column}:host ::ng-deep .ck-editor{--ck-content-font-size: 14px;--ck-content-line-height: 1.5}:host ::ng-deep .ck-editor__main{overflow:visible}:host ::ng-deep .ck-editor__editable{min-height:var(--sd-editor-content-min-height, 200px)!important;max-height:var(--sd-editor-content-max-height, none)!important;padding:24px!important;overflow-y:auto!important;border:1px solid var(--ck-color-base-border)!important}:host ::ng-deep .sd-editor.has-counter .ck-editor__editable{border-bottom:none!important}:host ::ng-deep .sd-editor--error .ck-editor__editable,:host ::ng-deep .sd-editor--error .ck-toolbar{border-color:var(--mdc-theme-error, #f44336)!important}:host ::ng-deep .sd-editor--error.has-counter .sd-editor-counter-row{border-color:var(--mdc-theme-error, #f44336)}:host ::ng-deep .ck-content p{margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{margin-left:0!important;padding-left:20px!important}\n", ":host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content figure.image{position:relative;display:block;width:fit-content;max-width:100%;margin:0 auto;overflow:visible}:host ::ng-deep .ck-content figure.image:hover,:host ::ng-deep .ck-content figure.image.ck-widget_selected{z-index:99999!important}:host ::ng-deep .ck-content figure.image.image-style-align-left{float:left;margin:0 1em 0 0}:host ::ng-deep .ck-content figure.image.image-style-align-left[style*=\"width:100\"]{float:none;margin:0 auto 0 0}:host ::ng-deep .ck-content figure.image.image-style-align-right{float:right;margin:0 0 0 1em}:host ::ng-deep .ck-content figure.image.image-style-align-right[style*=\"width:100\"]{float:none;margin:0 0 0 auto}:host ::ng-deep .ck-content figure.image.image-style-block-align-left{margin:0 auto 0 0}:host ::ng-deep .ck-content figure.image.image-style-block-align-right{margin:0 0 0 auto}:host ::ng-deep .ck-content figure.image .ck-progress-bar{position:absolute;bottom:0;left:0;width:30%!important;height:3px;background:var(--sd-color-primary, #1565c0);border-radius:2px;z-index:10;pointer-events:none;animation:sd-progress-indeterminate 1.4s ease-in-out infinite}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar){overflow:hidden}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar) img{filter:blur(1px) brightness(.85);transition:filter .3s ease}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar):after{content:\"\";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;z-index:9;background:#ffffff40;border-radius:inherit}:host ::ng-deep .ck-content figure.image:has(.ck-progress-bar):before{content:\"\";position:absolute;top:50%;left:50%;width:28px;height:28px;margin:-14px 0 0 -14px;border:3px solid rgba(255,255,255,.5);border-top-color:var(--sd-color-primary, #1565c0);border-radius:50%;animation:sd-spinner .75s linear infinite;z-index:11;pointer-events:none}@keyframes sd-spinner{to{transform:rotate(360deg)}}@keyframes sd-progress-indeterminate{0%{left:-30%}to{left:100%}}\n"] }]
926
+ }], ctorParameters: () => [] });
927
+
928
+ /**
929
+ * Generated bundle index. Do not edit.
930
+ */
931
+
932
+ export { SD_EDITOR_CONFIGURATION, SdEditor };
933
+ //# sourceMappingURL=sd-angular-core-components-editor.mjs.map