@sdcorejs/angular 19.1.5 → 19.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +12 -4
  2. package/assets/scss/core/menu.scss +18 -0
  3. package/assets/scss/core/utilities/_misc.scss +15 -0
  4. package/assets/scss/sd-core.scss +1 -0
  5. package/components/anchor/src/components/anchor/anchor.component.d.ts +3 -1
  6. package/components/badge/src/badge.component.d.ts +1 -0
  7. package/components/ckeditor-styles/src/ckeditor-styles.component.d.ts +1 -1
  8. package/components/form-generic/src/components/form-builder/components/expression-builder/expression-builder.component.d.ts +2 -1
  9. package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +3 -1
  10. package/components/form-generic/src/components/form-render/components/item/components/checkbox/checkbox.component.d.ts +34 -0
  11. package/components/form-generic/src/components/form-render/components/item/components/index.d.ts +1 -0
  12. package/components/form-generic/src/components/form-render/components/item/components/table/table.component.d.ts +6 -1
  13. package/components/form-generic/src/components/sd-feel-expression/sd-feel-expression.component.d.ts +1 -1
  14. package/components/form-generic/src/configurations/form.configuration.d.ts +1 -1
  15. package/components/form-generic/src/models/form-generic-component.model.d.ts +8 -8
  16. package/components/form-generic/src/models/form-generic-definition-selection.model.d.ts +5 -5
  17. package/components/form-generic/src/models/form-generic-expression.model.d.ts +12 -12
  18. package/components/form-generic/src/models/index.d.ts +1 -0
  19. package/components/form-generic/src/services/form-generic.service.d.ts +2 -2
  20. package/components/import-excel/src/import-excel.component.d.ts +1 -0
  21. package/components/index.d.ts +6 -1
  22. package/components/mini-editor/src/mini-editor.model.d.ts +1 -1
  23. package/components/modal/src/modal.component.d.ts +3 -2
  24. package/components/modal-resizable/src/modal-resizable.component.d.ts +92 -0
  25. package/components/modal-resizable/src/modal-resizable.registry.d.ts +22 -0
  26. package/components/org-chart/src/org-chart.component.d.ts +6 -0
  27. package/components/preview/src/preview-image/preview-image.component.d.ts +1 -0
  28. package/components/preview/src/preview-pdf/pdf-worker-inline.generated.d.ts +7 -0
  29. package/components/query-bar/src/components/actions-bar/actions-bar.component.d.ts +6 -2
  30. package/components/query-bar/src/components/build-chip/build-chip.component.d.ts +4 -1
  31. package/components/query-bar/src/components/chip-popover/chip-popover.component.d.ts +8 -0
  32. package/components/query-bar/src/components/field-picker/field-picker.component.d.ts +2 -0
  33. package/components/query-bar/src/components/inline-chip/inline-chip.component.d.ts +2 -0
  34. package/components/query-bar/src/components/inline-value-chip/inline-value-chip.component.d.ts +3 -3
  35. package/components/query-bar/src/components/saved-filters-menu/saved-filters-menu.component.d.ts +9 -4
  36. package/components/query-bar/src/query-bar.component.d.ts +32 -5
  37. package/components/query-bar/src/query-bar.model.d.ts +15 -12
  38. package/components/query-builder/src/query-builder.component.d.ts +76 -39
  39. package/components/query-builder/src/query-builder.model.d.ts +59 -52
  40. package/components/query-builder/src/query-builder.serializer.d.ts +15 -6
  41. package/components/section/src/section.component.d.ts +3 -0
  42. package/components/side-drawer/src/body-scroll-lock.service.d.ts +10 -0
  43. package/components/splitter/src/splitter-handle/splitter-handle.component.d.ts +1 -0
  44. package/components/tab-router/index.d.ts +2 -1
  45. package/components/tab-router/src/actions/tab-router.action.d.ts +2 -2
  46. package/components/tab-router/src/components/tab-router-item/tab-router-item.component.d.ts +2 -2
  47. package/components/tab-router/src/components/tab-router-nav/tab-router-nav.component.d.ts +3 -3
  48. package/components/tab-router/src/components/tab-router-outlet/tab-router-outlet.component.d.ts +3 -3
  49. package/components/tab-router/src/decorators/tab.decorator.d.ts +13 -1
  50. package/components/tab-router/src/events/tab-router.event.d.ts +3 -3
  51. package/components/tab-router/src/models/tab-router.model.d.ts +2 -2
  52. package/components/tab-router/src/pipes/tab-info.pipe.d.ts +2 -2
  53. package/components/tab-router/src/services/tab-router.service.d.ts +9 -9
  54. package/components/table/index.d.ts +1 -0
  55. package/components/table/src/components/config/config.component.d.ts +6 -1
  56. package/components/table/src/components/desktop-cell/view/view.component.d.ts +1 -0
  57. package/components/table/src/components/filter/external-filter/external-filter.component.d.ts +1 -1
  58. package/components/table/src/components/filter/mobile-filter/mobile-filter.component.d.ts +1 -1
  59. package/components/table/src/components/selector-action/action-filter.pipe.d.ts +8 -1
  60. package/components/table/src/components/selector-action/selector-action.component.d.ts +2 -0
  61. package/components/table/src/directives/index.d.ts +1 -0
  62. package/components/table/src/directives/sd-table-command-header-def.directive.d.ts +24 -0
  63. package/components/table/src/models/table-column.model.d.ts +30 -13
  64. package/components/table/src/models/table-item.model.d.ts +30 -1
  65. package/components/table/src/models/table-option-selector.model.d.ts +5 -4
  66. package/components/table/src/models/table-option.model.d.ts +12 -0
  67. package/components/table/src/pipes/sd-group.pipe.d.ts +6 -2
  68. package/components/table/src/services/table-export/table-export.service.d.ts +3 -1
  69. package/components/table/src/services/table-filter/table-filter.model.d.ts +15 -15
  70. package/components/table/src/services/table-filter/table-filter.service.d.ts +2 -2
  71. package/components/table/src/services/table-format/table-format.service.d.ts +1 -1
  72. package/components/table/src/services/table-selection/selection-action.util.d.ts +24 -0
  73. package/components/table/src/services/table-selection/table-selection.util.d.ts +31 -0
  74. package/components/table/src/services/tree/tree.util.d.ts +3 -2
  75. package/components/table/src/table.component.d.ts +52 -6
  76. package/components/tree/src/tree-item-def.directive.d.ts +1 -1
  77. package/components/tree/src/tree.component.d.ts +16 -4
  78. package/components/tree/src/tree.model.d.ts +31 -25
  79. package/components/upload-file/src/configurations/upload-file.configuration.d.ts +2 -2
  80. package/components/upload-file/src/upload-file.component.d.ts +4 -1
  81. package/configurations/src/sd-core.configuration.d.ts +1 -2
  82. package/fesm2022/sdcorejs-angular-components-anchor.mjs +36 -12
  83. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +2 -2
  85. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-components-badge.mjs +15 -2
  87. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
  89. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs +1 -1
  91. package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-components-code-editor.mjs +3 -3
  93. package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-components-data-state.mjs +3 -3
  95. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-components-editor.mjs +2 -2
  97. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +324 -192
  99. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  100. package/fesm2022/sdcorejs-angular-components-history.mjs +4 -4
  101. package/fesm2022/sdcorejs-angular-components-history.mjs.map +1 -1
  102. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +13 -5
  103. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  104. package/fesm2022/sdcorejs-angular-components-inform.mjs +3 -3
  105. package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +164 -8
  107. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-components-modal.mjs +30 -18
  109. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-components-org-chart.mjs +60 -13
  111. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-components-preview.mjs +58 -19
  113. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  114. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +192 -30
  115. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  116. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +187 -127
  117. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-components-section.mjs +16 -2
  119. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +83 -24
  121. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  122. package/fesm2022/sdcorejs-angular-components-splitter.mjs +57 -21
  123. package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-components-stepper.mjs +2 -2
  125. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +68 -20
  127. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  128. package/fesm2022/sdcorejs-angular-components-tab.mjs +3 -2
  129. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  130. package/fesm2022/sdcorejs-angular-components-table.mjs +556 -254
  131. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  132. package/fesm2022/sdcorejs-angular-components-tree.mjs +80 -17
  133. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  134. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +48 -13
  135. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  136. package/fesm2022/sdcorejs-angular-components.mjs +6 -1
  137. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  138. package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
  139. package/fesm2022/sdcorejs-angular-directives.mjs +45 -8
  140. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  141. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +40 -29
  142. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  143. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +5 -5
  144. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  145. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +67 -50
  146. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  147. package/fesm2022/sdcorejs-angular-forms-chip.mjs +57 -43
  148. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  149. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +233 -63
  150. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  151. package/fesm2022/sdcorejs-angular-forms-date.mjs +208 -41
  152. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  153. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +130 -26
  154. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  155. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +39 -15
  156. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -1
  157. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +16 -5
  158. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  159. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +24 -9
  160. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  161. package/fesm2022/sdcorejs-angular-forms-input.mjs +58 -10
  162. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  163. package/fesm2022/sdcorejs-angular-forms-models.mjs +279 -6
  164. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  165. package/fesm2022/sdcorejs-angular-forms-radio.mjs +4 -4
  166. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  167. package/fesm2022/sdcorejs-angular-forms-select.mjs +135 -37
  168. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  169. package/fesm2022/sdcorejs-angular-forms-switch.mjs +18 -3
  170. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  171. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +51 -62
  172. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  173. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +57 -12
  174. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -1
  175. package/fesm2022/sdcorejs-angular-forms-time.mjs +16 -5
  176. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -1
  177. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +100 -9
  178. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -1
  179. package/fesm2022/sdcorejs-angular-forms.mjs +91 -46
  180. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  181. package/fesm2022/sdcorejs-angular-handlers.mjs +18 -3
  182. package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
  183. package/fesm2022/sdcorejs-angular-i18n.mjs +510 -68
  184. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  185. package/fesm2022/sdcorejs-angular-interceptors.mjs +87 -29
  186. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  187. package/fesm2022/sdcorejs-angular-modules-auth.mjs +25 -15
  188. package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
  189. package/fesm2022/sdcorejs-angular-modules-icon.mjs +2 -2
  190. package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
  191. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +32 -3
  192. package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
  193. package/fesm2022/sdcorejs-angular-modules-layout.mjs +441 -135
  194. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  195. package/fesm2022/sdcorejs-angular-modules-permission.mjs +177 -40
  196. package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
  197. package/fesm2022/sdcorejs-angular-pipes.mjs +62 -28
  198. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  199. package/fesm2022/sdcorejs-angular-services-api.mjs +123 -13
  200. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  201. package/fesm2022/sdcorejs-angular-services-cache.mjs +53 -3
  202. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  203. package/fesm2022/sdcorejs-angular-services-confirm.mjs +25 -73
  204. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  205. package/fesm2022/sdcorejs-angular-services-excel.mjs +12 -10
  206. package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
  207. package/fesm2022/sdcorejs-angular-services-loading.mjs +75 -84
  208. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  209. package/fesm2022/sdcorejs-angular-services-notify.mjs +46 -5
  210. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  211. package/fesm2022/sdcorejs-angular-services-storage.mjs +4 -0
  212. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  213. package/fesm2022/sdcorejs-angular-services.mjs +0 -1
  214. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  215. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +225 -80
  216. package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
  217. package/fesm2022/sdcorejs-angular-utilities-models.mjs +1 -63
  218. package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
  219. package/fesm2022/sdcorejs-angular.mjs +17 -10
  220. package/fesm2022/sdcorejs-angular.mjs.map +1 -1
  221. package/forms/autocomplete/src/autocomplete.component.d.ts +5 -2
  222. package/forms/chip-calendar/src/chip-calendar.component.d.ts +10 -0
  223. package/forms/date/src/date-input.util.d.ts +18 -0
  224. package/forms/date/src/date.component.d.ts +21 -3
  225. package/forms/date-range/src/date-range.component.d.ts +13 -2
  226. package/forms/datetime/src/datetime.component.d.ts +13 -2
  227. package/forms/entity-picker/src/entity-picker.component.d.ts +9 -3
  228. package/forms/input/src/input.component.d.ts +3 -0
  229. package/forms/input-color/src/input-color.component.d.ts +4 -0
  230. package/forms/input-number/src/input-number.component.d.ts +3 -0
  231. package/forms/models/index.d.ts +4 -0
  232. package/forms/models/src/sd-form-control-connector.d.ts +12 -0
  233. package/forms/models/src/sd-model-facing-control.d.ts +43 -0
  234. package/forms/models/src/sd-search.model.d.ts +1 -1
  235. package/forms/models/src/sd-selection-data.model.d.ts +1 -1
  236. package/forms/models/src/sd-strict-date-adapter.d.ts +26 -0
  237. package/forms/models/src/sd-temporal-transform.d.ts +31 -0
  238. package/forms/models/src/sd-timer-scope.d.ts +29 -0
  239. package/forms/select/src/select.component.d.ts +27 -4
  240. package/forms/src/forms.module.d.ts +22 -13
  241. package/forms/switch/src/switch.component.d.ts +5 -0
  242. package/forms/textarea/src/textarea.component.d.ts +5 -3
  243. package/forms/time-range/src/time-range.component.d.ts +22 -1
  244. package/forms/tree-select/src/tree-select.component.d.ts +36 -5
  245. package/i18n/src/en.d.ts +94 -10
  246. package/i18n/src/i18n.messages.d.ts +1 -1
  247. package/i18n/src/i18n.pipe.d.ts +3 -3
  248. package/i18n/src/i18n.service.d.ts +2 -1
  249. package/i18n/src/i18n.types.d.ts +0 -3
  250. package/interceptors/no-internet/no-internet.interceptor.d.ts +11 -1
  251. package/modules/auth/services/auth.model.d.ts +1 -1
  252. package/modules/keycloak/keycloak.service.d.ts +1 -0
  253. package/modules/layout/components/shared/menu-tree/menu-tree.component.d.ts +2 -0
  254. package/modules/layout/components/sidebar-mobile-v1/components/sidebar/sidebar.component.d.ts +3 -3
  255. package/modules/layout/components/sidebar-mobile-v1/main.component.d.ts +3 -3
  256. package/modules/layout/components/sidebar-mobile-v2/main.component.d.ts +3 -3
  257. package/modules/layout/components/sidebar-mobile-v3/main.component.d.ts +3 -3
  258. package/modules/layout/components/sidebar-v1/components/sidebar/sidebar.component.d.ts +8 -3
  259. package/modules/layout/components/sidebar-v1/main.component.d.ts +3 -3
  260. package/modules/layout/components/sidebar-v2/main.component.d.ts +3 -3
  261. package/modules/layout/components/sidebar-v3/main.component.d.ts +3 -3
  262. package/modules/layout/index.d.ts +1 -0
  263. package/modules/layout/modules/forbidden/pages/root/root.component.d.ts +5 -0
  264. package/modules/layout/modules/not-found/pages/root/root.component.d.ts +5 -0
  265. package/modules/layout/services/layout.service.d.ts +21 -0
  266. package/modules/layout/services/storage/storage.service.d.ts +52 -7
  267. package/modules/layout/utils/index.d.ts +1 -0
  268. package/modules/layout/utils/tab-name.util.d.ts +8 -0
  269. package/modules/permission/src/configurations/permission.configuration.d.ts +11 -0
  270. package/modules/permission/src/directives/permission.directive.d.ts +2 -1
  271. package/modules/permission/src/services/permission.service.d.ts +52 -1
  272. package/package.json +48 -33
  273. package/pipes/src/safe-html.pipe.d.ts +26 -4
  274. package/pipes/src/time-different.pipe.d.ts +1 -0
  275. package/public-api.d.ts +11 -10
  276. package/services/api/src/api-host.d.ts +15 -0
  277. package/services/api/src/api.model.d.ts +14 -0
  278. package/services/api/src/api.module.d.ts +6 -0
  279. package/services/cache/src/cache.model.d.ts +10 -0
  280. package/services/index.d.ts +0 -1
  281. package/services/notify/index.d.ts +1 -1
  282. package/utilities/extensions/index.d.ts +1 -5
  283. package/utilities/extensions/src/url-safety.d.ts +72 -0
  284. package/utilities/extensions/src/utility.extension.d.ts +9 -5
  285. package/utilities/models/index.d.ts +0 -10
  286. package/components/base/index.d.ts +0 -1
  287. package/components/base/src/base-secure.component.d.ts +0 -8
  288. package/components/document-builder/index.d.ts +0 -4
  289. package/components/document-builder/src/components/header-footer-builder/header-footer-builder.component.d.ts +0 -15
  290. package/components/document-builder/src/components/header-footer-builder/plugins/page-number.plugin.d.ts +0 -4
  291. package/components/document-builder/src/components/index.d.ts +0 -1
  292. package/components/document-builder/src/document-builder.component.d.ts +0 -53
  293. package/components/document-builder/src/document-builder.config.d.ts +0 -21
  294. package/components/document-builder/src/document-builder.model.d.ts +0 -41
  295. package/components/document-builder/src/document-builder.utils.d.ts +0 -10
  296. package/components/document-builder/src/plugins/block-space/block-space.plugin.d.ts +0 -9
  297. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.d.ts +0 -44
  298. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +0 -57
  299. package/components/document-builder/src/plugins/heading/heading.plugin.d.ts +0 -5
  300. package/components/document-builder/src/plugins/highlight-range/highlight-range.plugin.d.ts +0 -4
  301. package/components/document-builder/src/plugins/image-custom/image-custom.plugin.d.ts +0 -31
  302. package/components/document-builder/src/plugins/image-upload/image-upload.plugin.d.ts +0 -5
  303. package/components/document-builder/src/plugins/index.d.ts +0 -11
  304. package/components/document-builder/src/plugins/page-orientation/page-orientation.plugin.d.ts +0 -24
  305. package/components/document-builder/src/plugins/paste-handler/filters/bookmark.d.ts +0 -14
  306. package/components/document-builder/src/plugins/paste-handler/filters/br.d.ts +0 -15
  307. package/components/document-builder/src/plugins/paste-handler/filters/image.d.ts +0 -25
  308. package/components/document-builder/src/plugins/paste-handler/filters/list.d.ts +0 -29
  309. package/components/document-builder/src/plugins/paste-handler/filters/parse.d.ts +0 -35
  310. package/components/document-builder/src/plugins/paste-handler/filters/removeboldwrapper.d.ts +0 -15
  311. package/components/document-builder/src/plugins/paste-handler/filters/removegooglesheetstag.d.ts +0 -15
  312. package/components/document-builder/src/plugins/paste-handler/filters/removeinvalidtablewidth.d.ts +0 -15
  313. package/components/document-builder/src/plugins/paste-handler/filters/removemsattributes.d.ts +0 -15
  314. package/components/document-builder/src/plugins/paste-handler/filters/removestyleblock.d.ts +0 -15
  315. package/components/document-builder/src/plugins/paste-handler/filters/removexmlns.d.ts +0 -15
  316. package/components/document-builder/src/plugins/paste-handler/filters/replacemsfootnotes.d.ts +0 -54
  317. package/components/document-builder/src/plugins/paste-handler/filters/space.d.ts +0 -27
  318. package/components/document-builder/src/plugins/paste-handler/filters/table.d.ts +0 -16
  319. package/components/document-builder/src/plugins/paste-handler/filters/utils.d.ts +0 -25
  320. package/components/document-builder/src/plugins/paste-handler/index.d.ts +0 -35
  321. package/components/document-builder/src/plugins/paste-handler/normalizers/googledocsnormalizer.d.ts +0 -31
  322. package/components/document-builder/src/plugins/paste-handler/normalizers/googlesheetsnormalizer.d.ts +0 -31
  323. package/components/document-builder/src/plugins/paste-handler/normalizers/mswordnormalizer.d.ts +0 -29
  324. package/components/document-builder/src/plugins/paste-handler/types.d.ts +0 -30
  325. package/components/document-builder/src/plugins/table-custom/index.d.ts +0 -34
  326. package/components/document-builder/src/plugins/variable/variable.plugin.d.ts +0 -51
  327. package/components/table/src/pipes/selection-visible-select-all.pipe.d.ts +0 -9
  328. package/fesm2022/sdcorejs-angular-components-base.mjs +0 -23
  329. package/fesm2022/sdcorejs-angular-components-base.mjs.map +0 -1
  330. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +0 -5038
  331. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +0 -1
  332. package/fesm2022/sdcorejs-angular-models.mjs +0 -6
  333. package/fesm2022/sdcorejs-angular-models.mjs.map +0 -1
  334. package/fesm2022/sdcorejs-angular-services-docx.mjs +0 -291
  335. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +0 -1
  336. package/fesm2022/sdcorejs-angular-services-license.mjs +0 -97
  337. package/fesm2022/sdcorejs-angular-services-license.mjs.map +0 -1
  338. package/models/index.d.ts +0 -1
  339. package/models/src/language.model.d.ts +0 -2
  340. package/services/docx/index.d.ts +0 -1
  341. package/services/docx/src/lib/docx.model.d.ts +0 -9
  342. package/services/docx/src/lib/docx.service.d.ts +0 -14
  343. package/services/docx/src/lib/pandoc-core.d.ts +0 -11
  344. package/services/docx/src/public-api.d.ts +0 -2
  345. package/services/license/index.d.ts +0 -1
  346. package/services/license/src/license.service.d.ts +0 -9
  347. package/utilities/extensions/src/array.extension.d.ts +0 -1
  348. package/utilities/extensions/src/color.extension.d.ts +0 -5
  349. package/utilities/extensions/src/date.extension.d.ts +0 -1
  350. package/utilities/extensions/src/number.extension.d.ts +0 -1
  351. package/utilities/extensions/src/string.extension.d.ts +0 -50
  352. package/utilities/models/src/color.model.d.ts +0 -4
  353. package/utilities/models/src/empty.model.d.ts +0 -4
  354. package/utilities/models/src/filter.model.d.ts +0 -12
  355. package/utilities/models/src/maybe-async.model.d.ts +0 -10
  356. package/utilities/models/src/nested-key-of.model.d.ts +0 -4
  357. package/utilities/models/src/operator.model.d.ts +0 -13
  358. package/utilities/models/src/order.model.d.ts +0 -4
  359. package/utilities/models/src/paging.model.d.ts +0 -8
  360. package/utilities/models/src/pattern.model.d.ts +0 -21
  361. package/utilities/models/src/size.model.d.ts +0 -4
@@ -2,6 +2,8 @@ import * as i0 from '@angular/core';
2
2
  import { inject, ElementRef, input, model, booleanAttribute, effect, Component } from '@angular/core';
3
3
  import { SdIcon } from '@sdcorejs/angular/modules/icon';
4
4
 
5
+ // why: id tăng dần để nối aria-controls từ header sang body — mỗi instance cần một id duy nhất.
6
+ let sectionBodyIdSeq = 0;
5
7
  class SdSection {
6
8
  #el = inject(ElementRef);
7
9
  title = input(undefined);
@@ -11,6 +13,8 @@ class SdSection {
11
13
  collapsed = model(false, { alias: 'collapsed' });
12
14
  collapsible = input(false, { transform: booleanAttribute });
13
15
  hideHeader = input(false, { transform: booleanAttribute });
16
+ /** Id của vùng body, để header `aria-controls` trỏ tới khi section collapsible. */
17
+ bodyId = `sd-section-body-${++sectionBodyIdSeq}`;
14
18
  constructor() {
15
19
  effect(() => {
16
20
  if (this.title()) {
@@ -28,12 +32,22 @@ class SdSection {
28
32
  }
29
33
  }
30
34
  };
35
+ // why: header là div[role=button] nhưng consumer chiếu nội dung tuỳ ý vào [sdHeaderRight]
36
+ // (thường là nút bấm). Nếu không lọc theo target thì Enter/Space trên nút của consumer vừa
37
+ // kích hoạt nút vừa gập section. Chỉ xử lý khi chính header đang giữ focus.
38
+ onHeaderKeydown = (event) => {
39
+ if (event.target !== event.currentTarget)
40
+ return;
41
+ // why: chặn Space cuộn trang trước khi toggle.
42
+ event.preventDefault();
43
+ this.toggleCollapse();
44
+ };
31
45
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
32
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSection, isStandalone: true, selector: "sd-section", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subTitle: { classPropertyName: "subTitle", publicName: "subTitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconColor: { classPropertyName: "iconColor", publicName: "iconColor", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, ngImport: i0, template: "@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <div\n class=\"sd-section-header cursor-pointer\"\n (click)=\"toggleCollapse()\"\n aria-hidden=\"true\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n", styles: [":host{width:100%;display:block}.sd-section{border-radius:8px;background-color:#fff}.sd-section-header,.sd-section-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;padding:8px 16px}.sd-section-header-left,.sd-section-header-right,.sd-section-footer-left,.sd-section-footer-right{display:flex;align-items:center;min-width:0}.sd-section-header-left,.sd-section-footer-left{flex:1 1 auto}.sd-section-header-right,.sd-section-footer-right{flex:0 0 auto;justify-content:flex-end;gap:8px}.sd-section-footer-right{margin-left:auto}.sd-section-title-block,.sd-section-title,.sd-section-subtitle{min-width:0}.sd-section-body{padding:0}.sd-section-body--with-header,.sd-section-footer{border-top:1px solid #e6e6e6}.sd-section-footer-left:empty,.sd-section-footer-right:empty{display:none}.sd-section-footer:has(.sd-section-footer-left:empty):has(.sd-section-footer-right:empty){display:none}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }] });
46
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSection, isStandalone: true, selector: "sd-section", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subTitle: { classPropertyName: "subTitle", publicName: "subTitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconColor: { classPropertyName: "iconColor", publicName: "iconColor", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, ngImport: i0, template: "@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 header ch\u1EE9a title/subTitle v\u00E0 to\u00E0n b\u1ED9 n\u1ED9i dung chi\u1EBFu\n [sdHeaderLeft]/[sdHeaderRight] (th\u01B0\u1EDDng c\u00F3 n\u00FAt b\u1EA5m). aria-hidden xo\u00E1 t\u1EA5t c\u1EA3 kh\u1ECFi\n accessibility tree trong khi v\u00F9ng n\u00E0y v\u1EABn nh\u1EADn click. Khi collapsible, header tr\u1EDF th\u00E0nh\n n\u00FAt th\u1EADt: role=button + tabindex=0 + aria-expanded/aria-controls + Enter/Space. -->\n <div\n class=\"sd-section-header cursor-pointer\"\n [attr.role]=\"_collapsible ? 'button' : null\"\n [attr.tabindex]=\"_collapsible ? 0 : null\"\n [attr.aria-expanded]=\"_collapsible ? !_collapsed : null\"\n [attr.aria-controls]=\"_collapsible ? bodyId : null\"\n (click)=\"toggleCollapse()\"\n (keydown.enter)=\"onHeaderKeydown($event)\"\n (keydown.space)=\"onHeaderKeydown($event)\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [id]=\"bodyId\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n", styles: [":host{width:100%;display:block}.sd-section{border-radius:8px;background-color:#fff}.sd-section-header,.sd-section-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;padding:8px 16px}.sd-section-header[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px;border-radius:8px}.sd-section-header-left,.sd-section-header-right,.sd-section-footer-left,.sd-section-footer-right{display:flex;align-items:center;min-width:0}.sd-section-header-left,.sd-section-footer-left{flex:1 1 auto}.sd-section-header-right,.sd-section-footer-right{flex:0 0 auto;justify-content:flex-end;gap:8px}.sd-section-footer-right{margin-left:auto}.sd-section-title-block,.sd-section-title,.sd-section-subtitle{min-width:0}.sd-section-body{padding:0}.sd-section-body--with-header,.sd-section-footer{border-top:1px solid #e6e6e6}.sd-section-footer-left:empty,.sd-section-footer-right:empty{display:none}.sd-section-footer:has(.sd-section-footer-left:empty):has(.sd-section-footer-right:empty){display:none}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }] });
33
47
  }
34
48
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSection, decorators: [{
35
49
  type: Component,
36
- args: [{ selector: 'sd-section', imports: [SdIcon], template: "@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <div\n class=\"sd-section-header cursor-pointer\"\n (click)=\"toggleCollapse()\"\n aria-hidden=\"true\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n", styles: [":host{width:100%;display:block}.sd-section{border-radius:8px;background-color:#fff}.sd-section-header,.sd-section-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;padding:8px 16px}.sd-section-header-left,.sd-section-header-right,.sd-section-footer-left,.sd-section-footer-right{display:flex;align-items:center;min-width:0}.sd-section-header-left,.sd-section-footer-left{flex:1 1 auto}.sd-section-header-right,.sd-section-footer-right{flex:0 0 auto;justify-content:flex-end;gap:8px}.sd-section-footer-right{margin-left:auto}.sd-section-title-block,.sd-section-title,.sd-section-subtitle{min-width:0}.sd-section-body{padding:0}.sd-section-body--with-header,.sd-section-footer{border-top:1px solid #e6e6e6}.sd-section-footer-left:empty,.sd-section-footer-right:empty{display:none}.sd-section-footer:has(.sd-section-footer-left:empty):has(.sd-section-footer-right:empty){display:none}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"] }]
50
+ args: [{ selector: 'sd-section', imports: [SdIcon], template: "@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 header ch\u1EE9a title/subTitle v\u00E0 to\u00E0n b\u1ED9 n\u1ED9i dung chi\u1EBFu\n [sdHeaderLeft]/[sdHeaderRight] (th\u01B0\u1EDDng c\u00F3 n\u00FAt b\u1EA5m). aria-hidden xo\u00E1 t\u1EA5t c\u1EA3 kh\u1ECFi\n accessibility tree trong khi v\u00F9ng n\u00E0y v\u1EABn nh\u1EADn click. Khi collapsible, header tr\u1EDF th\u00E0nh\n n\u00FAt th\u1EADt: role=button + tabindex=0 + aria-expanded/aria-controls + Enter/Space. -->\n <div\n class=\"sd-section-header cursor-pointer\"\n [attr.role]=\"_collapsible ? 'button' : null\"\n [attr.tabindex]=\"_collapsible ? 0 : null\"\n [attr.aria-expanded]=\"_collapsible ? !_collapsed : null\"\n [attr.aria-controls]=\"_collapsible ? bodyId : null\"\n (click)=\"toggleCollapse()\"\n (keydown.enter)=\"onHeaderKeydown($event)\"\n (keydown.space)=\"onHeaderKeydown($event)\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [id]=\"bodyId\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n", styles: [":host{width:100%;display:block}.sd-section{border-radius:8px;background-color:#fff}.sd-section-header,.sd-section-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;padding:8px 16px}.sd-section-header[role=button]:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:-2px;border-radius:8px}.sd-section-header-left,.sd-section-header-right,.sd-section-footer-left,.sd-section-footer-right{display:flex;align-items:center;min-width:0}.sd-section-header-left,.sd-section-footer-left{flex:1 1 auto}.sd-section-header-right,.sd-section-footer-right{flex:0 0 auto;justify-content:flex-end;gap:8px}.sd-section-footer-right{margin-left:auto}.sd-section-title-block,.sd-section-title,.sd-section-subtitle{min-width:0}.sd-section-body{padding:0}.sd-section-body--with-header,.sd-section-footer{border-top:1px solid #e6e6e6}.sd-section-footer-left:empty,.sd-section-footer-right:empty{display:none}.sd-section-footer:has(.sd-section-footer-left:empty):has(.sd-section-footer-right:empty){display:none}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"] }]
37
51
  }], ctorParameters: () => [] });
38
52
 
39
53
  class SdSectionItem {
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-components-section.mjs","sources":["../../../projects/sdcorejs-angular/components/section/src/section.component.ts","../../../projects/sdcorejs-angular/components/section/src/section.component.html","../../../projects/sdcorejs-angular/components/section/src/section-item/section-item.component.ts","../../../projects/sdcorejs-angular/components/section/src/section-item/section-item.component.html","../../../projects/sdcorejs-angular/components/section/sdcorejs-angular-components-section.ts"],"sourcesContent":["import { booleanAttribute, Component, effect, ElementRef, inject, input, model } from '@angular/core';\nimport { Color } from '@sdcorejs/utils/models';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\n\n@Component({\n selector: 'sd-section',\n templateUrl: './section.component.html',\n styleUrl: './section.component.scss',\n imports: [SdIcon],\n})\nexport class SdSection {\n #el = inject(ElementRef);\n\n title = input<string | undefined | null>(undefined);\n subTitle = input<string | undefined | null>(undefined);\n icon = input<string | undefined | null>(undefined);\n iconColor = input<Color>('primary', { alias: 'iconColor' });\n\n collapsed = model<boolean>(false, { alias: 'collapsed' });\n collapsible = input(false, { transform: booleanAttribute });\n\n hideHeader = input(false, { transform: booleanAttribute });\n\n constructor() {\n effect(() => {\n if (this.title()) {\n this.#el.nativeElement.removeAttribute('title');\n }\n });\n }\n\n toggleCollapse = () => {\n if (this.collapsible()) {\n this.collapsed.set(!this.collapsed());\n } else {\n if (this.collapsed()) {\n this.collapsed.set(false);\n }\n }\n };\n}\n","@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <div\n class=\"sd-section-header cursor-pointer\"\n (click)=\"toggleCollapse()\"\n aria-hidden=\"true\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n","import { Component, input } from '@angular/core';\n\n@Component({\n selector: 'sd-section-item',\n templateUrl: './section-item.component.html',\n styleUrl: 'section-item.component.scss',\n standalone: true,\n})\nexport class SdSectionItem {\n label = input.required<string>();\n labelWidth = input<string, string | null | undefined>('150px', {\n transform: (val: any): string => {\n return val || '150px';\n },\n });\n}\n","<div class=\"c-item\">\n <div class=\"c-item-label T14R text-secondary\" [style.width]=\"labelWidth()\">{{ label() }}</div>\n <div class=\"c-item-content\"><ng-content></ng-content></div>\n</div>\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-components-section.mjs","sources":["../../../projects/sdcorejs-angular/components/section/src/section.component.ts","../../../projects/sdcorejs-angular/components/section/src/section.component.html","../../../projects/sdcorejs-angular/components/section/src/section-item/section-item.component.ts","../../../projects/sdcorejs-angular/components/section/src/section-item/section-item.component.html","../../../projects/sdcorejs-angular/components/section/sdcorejs-angular-components-section.ts"],"sourcesContent":["import { booleanAttribute, Component, effect, ElementRef, inject, input, model } from '@angular/core';\nimport { Color } from '@sdcorejs/utils/models';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\n\n// why: id tăng dần để nối aria-controls từ header sang body — mỗi instance cần một id duy nhất.\nlet sectionBodyIdSeq = 0;\n\n@Component({\n selector: 'sd-section',\n templateUrl: './section.component.html',\n styleUrl: './section.component.scss',\n imports: [SdIcon],\n})\nexport class SdSection {\n #el = inject(ElementRef);\n\n title = input<string | undefined | null>(undefined);\n subTitle = input<string | undefined | null>(undefined);\n icon = input<string | undefined | null>(undefined);\n iconColor = input<Color>('primary', { alias: 'iconColor' });\n\n collapsed = model<boolean>(false, { alias: 'collapsed' });\n collapsible = input(false, { transform: booleanAttribute });\n\n hideHeader = input(false, { transform: booleanAttribute });\n\n /** Id của vùng body, để header `aria-controls` trỏ tới khi section collapsible. */\n readonly bodyId = `sd-section-body-${++sectionBodyIdSeq}`;\n\n constructor() {\n effect(() => {\n if (this.title()) {\n this.#el.nativeElement.removeAttribute('title');\n }\n });\n }\n\n toggleCollapse = () => {\n if (this.collapsible()) {\n this.collapsed.set(!this.collapsed());\n } else {\n if (this.collapsed()) {\n this.collapsed.set(false);\n }\n }\n };\n\n // why: header là div[role=button] nhưng consumer chiếu nội dung tuỳ ý vào [sdHeaderRight]\n // (thường là nút bấm). Nếu không lọc theo target thì Enter/Space trên nút của consumer vừa\n // kích hoạt nút vừa gập section. Chỉ xử lý khi chính header đang giữ focus.\n onHeaderKeydown = (event: KeyboardEvent) => {\n if (event.target !== event.currentTarget) return;\n // why: chặn Space cuộn trang trước khi toggle.\n event.preventDefault();\n this.toggleCollapse();\n };\n}\n","@let _hideHeader = hideHeader();\n@let _collapsible = collapsible();\n@let _collapsed = collapsed();\n@let _title = title();\n@let _subTitle = subTitle();\n@let _icon = icon();\n@let _iconColor = iconColor();\n<div class=\"sd-section c-shadow-section\">\n @if (!_hideHeader) {\n <!-- why: bỏ aria-hidden=\"true\" — header chứa title/subTitle và toàn bộ nội dung chiếu\n [sdHeaderLeft]/[sdHeaderRight] (thường có nút bấm). aria-hidden xoá tất cả khỏi\n accessibility tree trong khi vùng này vẫn nhận click. Khi collapsible, header trở thành\n nút thật: role=button + tabindex=0 + aria-expanded/aria-controls + Enter/Space. -->\n <div\n class=\"sd-section-header cursor-pointer\"\n [attr.role]=\"_collapsible ? 'button' : null\"\n [attr.tabindex]=\"_collapsible ? 0 : null\"\n [attr.aria-expanded]=\"_collapsible ? !_collapsed : null\"\n [attr.aria-controls]=\"_collapsible ? bodyId : null\"\n (click)=\"toggleCollapse()\"\n (keydown.enter)=\"onHeaderKeydown($event)\"\n (keydown.space)=\"onHeaderKeydown($event)\">\n <div class=\"sd-section-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n @if (_icon) {\n <span\n class=\"mr-8 material-icons-outlined\"\n [class.text-primary]=\"_iconColor === 'primary'\"\n [class.text-secondary]=\"_iconColor === 'secondary'\"\n [class.text-error]=\"_iconColor === 'error'\"\n [class.text-warning]=\"_iconColor === 'warning'\"\n [class.text-success]=\"_iconColor === 'success'\">\n {{ _icon }}\n </span>\n }\n <div class=\"sd-section-title-block\">\n @if (_title) {\n <div class=\"sd-section-title T16M\">{{ _title }}</div>\n }\n @if (_subTitle) {\n <div class=\"sd-section-subtitle T12R text-secondary\">{{ _subTitle }}</div>\n }\n </div>\n </ng-content>\n </div>\n <div class=\"sd-section-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (_collapsible) {\n <div class=\"d-flex align-items-center\">\n @if (_collapsed) {\n <sd-icon name=\"expand_more\"></sd-icon>\n } @else {\n <sd-icon name=\"expand_less\"></sd-icon>\n }\n </div>\n }\n </div>\n </div>\n }\n @if (!_collapsed || _hideHeader) {\n <div class=\"sd-section-body\" [id]=\"bodyId\" [class.sd-section-body--with-header]=\"!_hideHeader\">\n <ng-content></ng-content>\n </div>\n }\n <div class=\"sd-section-footer\">\n <div class=\"sd-section-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-section-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n</div>\n","import { Component, input } from '@angular/core';\n\n@Component({\n selector: 'sd-section-item',\n templateUrl: './section-item.component.html',\n styleUrl: 'section-item.component.scss',\n standalone: true,\n})\nexport class SdSectionItem {\n label = input.required<string>();\n labelWidth = input<string, string | null | undefined>('150px', {\n transform: (val: any): string => {\n return val || '150px';\n },\n });\n}\n","<div class=\"c-item\">\n <div class=\"c-item-label T14R text-secondary\" [style.width]=\"labelWidth()\">{{ label() }}</div>\n <div class=\"c-item-content\"><ng-content></ng-content></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,14 +1,71 @@
1
1
  import * as i2 from '@angular/cdk/portal';
2
2
  import { CdkPortal, DomPortalOutlet, PortalModule } from '@angular/cdk/portal';
3
3
  import * as i1 from '@angular/common';
4
- import { CommonModule } from '@angular/common';
4
+ import { DOCUMENT, CommonModule } from '@angular/common';
5
5
  import * as i0 from '@angular/core';
6
- import { viewChild, input, booleanAttribute, computed, output, signal, inject, ViewContainerRef, ApplicationRef, Injector, ChangeDetectorRef, DestroyRef, afterNextRender, ChangeDetectionStrategy, Component } from '@angular/core';
6
+ import { inject, Injectable, viewChild, input, booleanAttribute, computed, output, signal, ViewContainerRef, ApplicationRef, Injector, ChangeDetectorRef, DestroyRef, afterNextRender, ChangeDetectionStrategy, Component } from '@angular/core';
7
7
  import { SdLoadingService } from '@sdcorejs/angular/services';
8
8
  import { Utilities } from '@sdcorejs/utils/fns';
9
9
  import { Subject, fromEvent, merge } from 'rxjs';
10
10
  import { map, startWith, distinctUntilChanged, takeUntil } from 'rxjs/operators';
11
11
  import { SdIcon } from '@sdcorejs/angular/modules/icon';
12
+ import { SdTranslatePipe } from '@sdcorejs/angular/i18n';
13
+
14
+ /**
15
+ * why: state nằm ở MODULE, khoá theo `Document`, KHÔNG nằm trong instance service.
16
+ * `providedIn: 'root'` cho một instance mỗi *ứng dụng Angular*, không phải mỗi *document*. Hai app
17
+ * bootstrap độc lập trên cùng một trang (micro-frontend, widget nhúng) sẽ có hai bộ đếm riêng, mỗi
18
+ * bên chụp lại `overflow` mà bên kia vừa ghi — tái lập đúng bug khoá-scroll-vĩnh-viễn mà service
19
+ * này sinh ra để sửa, chỉ dịch lên một tầng. Khoá theo `Document` thì mọi instance trong cùng
20
+ * document dùng chung một bộ đếm, bất kể injector nào tạo ra chúng.
21
+ */
22
+ const scrollLockStates = new WeakMap();
23
+ const stateFor = (doc) => {
24
+ let state = scrollLockStates.get(doc);
25
+ if (!state) {
26
+ state = { count: 0, previousOverflow: null };
27
+ scrollLockStates.set(doc, state);
28
+ }
29
+ return state;
30
+ };
31
+ class SdBodyScrollLockService {
32
+ #document = inject(DOCUMENT);
33
+ /** Số lượng khoá đang giữ trên document này — phục vụ chẩn đoán/test. */
34
+ get count() {
35
+ return stateFor(this.#document).count;
36
+ }
37
+ lock() {
38
+ const state = stateFor(this.#document);
39
+ const body = this.#document.body;
40
+ if (!body)
41
+ return;
42
+ if (state.count === 0) {
43
+ state.previousOverflow = body.style.overflow;
44
+ body.style.overflow = 'hidden';
45
+ }
46
+ state.count++;
47
+ }
48
+ release() {
49
+ const state = stateFor(this.#document);
50
+ // Nhả nhiều hơn khoá (double close, destroy sau close) không được đẩy count xuống âm,
51
+ // nếu không lần khoá kế tiếp sẽ không ghi DOM.
52
+ if (state.count === 0)
53
+ return;
54
+ state.count--;
55
+ if (state.count > 0)
56
+ return;
57
+ const body = this.#document.body;
58
+ if (body)
59
+ body.style.overflow = state.previousOverflow ?? '';
60
+ state.previousOverflow = null;
61
+ }
62
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdBodyScrollLockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
63
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdBodyScrollLockService, providedIn: 'root' });
64
+ }
65
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdBodyScrollLockService, decorators: [{
66
+ type: Injectable,
67
+ args: [{ providedIn: 'root' }]
68
+ }] });
12
69
 
13
70
  class SdSideDrawer {
14
71
  id = `I${Utilities.generateUuid()}`;
@@ -33,7 +90,9 @@ class SdSideDrawer {
33
90
  dataLoading = computed(() => (this.#isLoadingSignal() ? 'true' : 'false'));
34
91
  isHovered$;
35
92
  #destroy$ = new Subject();
36
- #previousBodyOverflow = null;
93
+ // Instance này có đang giữ 1 khoá scroll hay không — giữ cho lock/release luôn cân bằng
94
+ // dù open()/close() bị gọi lặp.
95
+ #holdsScrollLock = false;
37
96
  #closeRequest;
38
97
  #viewContainerRef = inject(ViewContainerRef);
39
98
  #ar = inject(ApplicationRef);
@@ -41,6 +100,7 @@ class SdSideDrawer {
41
100
  #ref = inject(ChangeDetectorRef);
42
101
  #loadingService = inject(SdLoadingService);
43
102
  #destroyRef = inject(DestroyRef);
103
+ #scrollLock = inject(SdBodyScrollLockService);
44
104
  constructor() {
45
105
  // Thay thế ngAfterViewInit, tự động chạy nội dung này khi DOM sẵn sàng để render
46
106
  afterNextRender(() => {
@@ -57,22 +117,15 @@ class SdSideDrawer {
57
117
  if (this.#embeddedViewRef) {
58
118
  this.#embeddedViewRef.destroy();
59
119
  }
60
- if (this.#isOpenedSignal()) {
61
- if (this.#previousBodyOverflow !== null) {
62
- document.body.style.overflow = this.#previousBodyOverflow;
63
- }
64
- else {
65
- document.body.style.overflow = '';
66
- }
67
- }
120
+ // Destroy khi đang mở cũng phải nhả khoá, nếu không ref-count kẹt > 0 và trang khoá scroll mãi.
121
+ this.#releaseScrollLock();
68
122
  });
69
123
  }
70
124
  open = () => {
71
125
  this.#ref.markForCheck();
72
126
  this.#isOpenedSignal.set(true);
73
- // Chặn scroll ở document body
74
- this.#previousBodyOverflow = document.body.style.overflow;
75
- document.body.style.overflow = 'hidden';
127
+ // Chặn scroll ở document body qua khoá ref-count dùng chung (stack-safe với drawer lồng nhau)
128
+ this.#acquireScrollLock();
76
129
  };
77
130
  close = () => {
78
131
  if (this.beforeClose()) {
@@ -113,15 +166,21 @@ class SdSideDrawer {
113
166
  this.#isOpenedSignal.set(false);
114
167
  this.sdClosed.emit();
115
168
  this.stopLoading();
116
- // Khôi phục lại scroll ở document body
117
- if (this.#previousBodyOverflow !== null) {
118
- document.body.style.overflow = this.#previousBodyOverflow;
119
- this.#previousBodyOverflow = null;
120
- }
121
- else {
122
- document.body.style.overflow = '';
123
- }
169
+ // Khôi phục lại scroll ở document body (chỉ thật sự khôi phục khi drawer cuối cùng nhả khoá)
170
+ this.#releaseScrollLock();
124
171
  };
172
+ #acquireScrollLock() {
173
+ if (this.#holdsScrollLock)
174
+ return;
175
+ this.#holdsScrollLock = true;
176
+ this.#scrollLock.lock();
177
+ }
178
+ #releaseScrollLock() {
179
+ if (!this.#holdsScrollLock)
180
+ return;
181
+ this.#holdsScrollLock = false;
182
+ this.#scrollLock.release();
183
+ }
125
184
  startLoading = () => {
126
185
  this.#isLoadingSignal.set(true);
127
186
  this.#loadingService.stop(`#${this.id}`);
@@ -150,11 +209,11 @@ class SdSideDrawer {
150
209
  this.isHovered$ = merge(mouseEnter$, mouseLeave$).pipe(startWith(false), distinctUntilChanged(), takeUntil(this.#destroy$));
151
210
  }
152
211
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSideDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
153
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSideDrawer, isStandalone: true, selector: "sd-side-drawer", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hideClose: { classPropertyName: "hideClose", publicName: "hideClose", isSignal: true, isRequired: false, transformFunction: null }, disableBackdropClose: { classPropertyName: "disableBackdropClose", publicName: "disableBackdropClose", isSignal: true, isRequired: false, transformFunction: null }, beforeClose: { classPropertyName: "beforeClose", publicName: "beforeClose", isSignal: true, isRequired: false, transformFunction: null }, drawerClass: { classPropertyName: "drawerClass", publicName: "drawerClass", isSignal: true, isRequired: false, transformFunction: null }, autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sdClosed: "sdClosed", sdCloseError: "sdCloseError" }, viewQueries: [{ propertyName: "portal", first: true, predicate: CdkPortal, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <button type=\"button\" class=\"sd-side-drawer-close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n @if (_isOpened) {\n <div\n aria-hidden=\"true\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\n", styles: [".sd-side-drawer{--sd-side-drawer-inset: 10px;--sd-side-drawer-radius: var(--sd-overlay-radius, 24px);--sd-side-drawer-surface: var(--sd-surface, #fff);--sd-side-drawer-border: var(--sd-border, #dde0e5);--sd-side-drawer-shadow: 0 6px 18px rgba(15, 23, 42, .18), 0 1px 3px rgba(15, 23, 42, .12);position:fixed;right:var(--sd-side-drawer-inset);top:var(--sd-side-drawer-inset);bottom:var(--sd-side-drawer-inset);max-width:calc(100vw - var(--sd-side-drawer-inset) - var(--sd-side-drawer-inset));background-color:var(--sd-side-drawer-surface);color:var(--sd-text);z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(calc(100% + var(--sd-side-drawer-inset)),0,0);transition:all .3s ease-in-out;pointer-events:none;overflow:hidden;border-radius:var(--sd-side-drawer-radius);box-shadow:var(--sd-side-drawer-shadow)}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:16px;display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-header-left{display:flex;align-items:center;flex:1 1 auto;min-width:0}.sd-side-drawer-header-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;min-width:0}.sd-side-drawer-close-btn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--sd-text-secondary);line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:background-color 80ms ease,color 80ms ease}.sd-side-drawer-close-btn:hover{background:var(--sd-surface-muted);color:var(--sd-text)}.sd-side-drawer-close-btn:active{background:var(--sd-border)}.sd-side-drawer-close-btn:focus{outline:none}.sd-side-drawer-close-btn:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-side-drawer-close-btn sd-icon{display:block;width:20px;height:20px;font-size:20px;line-height:20px}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid var(--sd-side-drawer-border);padding:16px;background-color:var(--sd-side-drawer-surface);display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-footer-left,.sd-side-drawer-footer-right{display:flex;align-items:center;gap:8px;min-width:0}.sd-side-drawer-footer-left{flex:1 1 auto}.sd-side-drawer-footer-right{flex:0 0 auto;margin-left:auto;justify-content:flex-end}.sd-side-drawer-footer-left:empty,.sd-side-drawer-footer-right:empty{display:none}.sd-side-drawer-footer:has(.sd-side-drawer-footer-left:empty):has(.sd-side-drawer-footer-right:empty){display:none}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i2.CdkPortal, selector: "[cdkPortal]", exportAs: ["cdkPortal"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
212
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSideDrawer, isStandalone: true, selector: "sd-side-drawer", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hideClose: { classPropertyName: "hideClose", publicName: "hideClose", isSignal: true, isRequired: false, transformFunction: null }, disableBackdropClose: { classPropertyName: "disableBackdropClose", publicName: "disableBackdropClose", isSignal: true, isRequired: false, transformFunction: null }, beforeClose: { classPropertyName: "beforeClose", publicName: "beforeClose", isSignal: true, isRequired: false, transformFunction: null }, drawerClass: { classPropertyName: "drawerClass", publicName: "drawerClass", isSignal: true, isRequired: false, transformFunction: null }, autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sdClosed: "sdClosed", sdCloseError: "sdCloseError" }, viewQueries: [{ propertyName: "portal", first: true, predicate: CdkPortal, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"title() || null\"\n (keydown.escape)=\"disableBackdropClose() ? null : close()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <!-- why: aria-label=\"Close\" l\u00E0 chu\u1ED7i ti\u1EBFng Anh c\u1EE9ng trong m\u1ED9t th\u01B0 vi\u1EC7n \u0111\u00E3 c\u00F3 i18n. -->\n <button\n type=\"button\"\n class=\"sd-side-drawer-close-btn\"\n (click)=\"close()\"\n [attr.aria-label]=\"'core.common.close' | sdTranslate\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n <!-- why: backdrop l\u00E0 l\u1EDBp ph\u1EE7 b\u1EAFt click thu\u1EA7n trang tr\u00ED, kh\u00F4ng c\u00F3 n\u1ED9i dung \u2014 nh\u01B0ng aria-hidden\n kh\u00F4ng ph\u1EA3i l\u00E0 c\u00E1ch khai b\u00E1o \u0111\u00FAng cho n\u00F3. `role=\"presentation\"` m\u1EDBi l\u00E0 t\u00EDn hi\u1EC7u ch\u00EDnh x\u00E1c\n (ph\u1EA7n t\u1EED n\u00E0y kh\u00F4ng mang ng\u1EEF ngh\u0129a), v\u00E0 quan tr\u1ECDng h\u01A1n: tr\u01B0\u1EDBc \u0111\u00E2y \"\u0111\u00F3ng b\u1EB1ng c\u00E1ch click ra\n ngo\u00E0i\" KH\u00D4NG c\u00F3 t\u01B0\u01A1ng \u0111\u01B0\u01A1ng b\u00E0n ph\u00EDm n\u00E0o c\u1EA3. Escape \u0111\u01B0\u1EE3c th\u00EAm \u1EDF drawer container. -->\n @if (_isOpened) {\n <div\n role=\"presentation\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\n", styles: [".sd-side-drawer{--sd-side-drawer-inset: 10px;--sd-side-drawer-radius: var(--sd-overlay-radius, 24px);--sd-side-drawer-surface: var(--sd-surface, #fff);--sd-side-drawer-border: var(--sd-border, #dde0e5);--sd-side-drawer-shadow: 0 6px 18px rgba(15, 23, 42, .18), 0 1px 3px rgba(15, 23, 42, .12);position:fixed;right:var(--sd-side-drawer-inset);top:var(--sd-side-drawer-inset);bottom:var(--sd-side-drawer-inset);max-width:calc(100vw - var(--sd-side-drawer-inset) - var(--sd-side-drawer-inset));background-color:var(--sd-side-drawer-surface);color:var(--sd-text);z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(calc(100% + var(--sd-side-drawer-inset)),0,0);transition:all .3s ease-in-out;pointer-events:none;overflow:hidden;border-radius:var(--sd-side-drawer-radius);box-shadow:var(--sd-side-drawer-shadow)}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:16px;display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-header-left{display:flex;align-items:center;flex:1 1 auto;min-width:0}.sd-side-drawer-header-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;min-width:0}.sd-side-drawer-close-btn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--sd-text-secondary);line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:background-color 80ms ease,color 80ms ease}.sd-side-drawer-close-btn:hover{background:var(--sd-surface-muted);color:var(--sd-text)}.sd-side-drawer-close-btn:active{background:var(--sd-border)}.sd-side-drawer-close-btn:focus{outline:none}.sd-side-drawer-close-btn:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-side-drawer-close-btn sd-icon{display:block;width:20px;height:20px;font-size:20px;line-height:20px}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid var(--sd-side-drawer-border);padding:16px;background-color:var(--sd-side-drawer-surface);display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-footer-left,.sd-side-drawer-footer-right{display:flex;align-items:center;gap:8px;min-width:0}.sd-side-drawer-footer-left{flex:1 1 auto}.sd-side-drawer-footer-right{flex:0 0 auto;margin-left:auto;justify-content:flex-end}.sd-side-drawer-footer-left:empty,.sd-side-drawer-footer-right:empty{display:none}.sd-side-drawer-footer:has(.sd-side-drawer-footer-left:empty):has(.sd-side-drawer-footer-right:empty){display:none}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i2.CdkPortal, selector: "[cdkPortal]", exportAs: ["cdkPortal"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
154
213
  }
155
214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSideDrawer, decorators: [{
156
215
  type: Component,
157
- args: [{ selector: 'sd-side-drawer', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [SdIcon, CommonModule, PortalModule], template: "<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <button type=\"button\" class=\"sd-side-drawer-close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n @if (_isOpened) {\n <div\n aria-hidden=\"true\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\n", styles: [".sd-side-drawer{--sd-side-drawer-inset: 10px;--sd-side-drawer-radius: var(--sd-overlay-radius, 24px);--sd-side-drawer-surface: var(--sd-surface, #fff);--sd-side-drawer-border: var(--sd-border, #dde0e5);--sd-side-drawer-shadow: 0 6px 18px rgba(15, 23, 42, .18), 0 1px 3px rgba(15, 23, 42, .12);position:fixed;right:var(--sd-side-drawer-inset);top:var(--sd-side-drawer-inset);bottom:var(--sd-side-drawer-inset);max-width:calc(100vw - var(--sd-side-drawer-inset) - var(--sd-side-drawer-inset));background-color:var(--sd-side-drawer-surface);color:var(--sd-text);z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(calc(100% + var(--sd-side-drawer-inset)),0,0);transition:all .3s ease-in-out;pointer-events:none;overflow:hidden;border-radius:var(--sd-side-drawer-radius);box-shadow:var(--sd-side-drawer-shadow)}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:16px;display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-header-left{display:flex;align-items:center;flex:1 1 auto;min-width:0}.sd-side-drawer-header-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;min-width:0}.sd-side-drawer-close-btn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--sd-text-secondary);line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:background-color 80ms ease,color 80ms ease}.sd-side-drawer-close-btn:hover{background:var(--sd-surface-muted);color:var(--sd-text)}.sd-side-drawer-close-btn:active{background:var(--sd-border)}.sd-side-drawer-close-btn:focus{outline:none}.sd-side-drawer-close-btn:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-side-drawer-close-btn sd-icon{display:block;width:20px;height:20px;font-size:20px;line-height:20px}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid var(--sd-side-drawer-border);padding:16px;background-color:var(--sd-side-drawer-surface);display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-footer-left,.sd-side-drawer-footer-right{display:flex;align-items:center;gap:8px;min-width:0}.sd-side-drawer-footer-left{flex:1 1 auto}.sd-side-drawer-footer-right{flex:0 0 auto;margin-left:auto;justify-content:flex-end}.sd-side-drawer-footer-left:empty,.sd-side-drawer-footer-right:empty{display:none}.sd-side-drawer-footer:has(.sd-side-drawer-footer-left:empty):has(.sd-side-drawer-footer-right:empty){display:none}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"] }]
216
+ args: [{ selector: 'sd-side-drawer', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [SdIcon, CommonModule, PortalModule, SdTranslatePipe], template: "<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"title() || null\"\n (keydown.escape)=\"disableBackdropClose() ? null : close()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <!-- why: aria-label=\"Close\" l\u00E0 chu\u1ED7i ti\u1EBFng Anh c\u1EE9ng trong m\u1ED9t th\u01B0 vi\u1EC7n \u0111\u00E3 c\u00F3 i18n. -->\n <button\n type=\"button\"\n class=\"sd-side-drawer-close-btn\"\n (click)=\"close()\"\n [attr.aria-label]=\"'core.common.close' | sdTranslate\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n <!-- why: backdrop l\u00E0 l\u1EDBp ph\u1EE7 b\u1EAFt click thu\u1EA7n trang tr\u00ED, kh\u00F4ng c\u00F3 n\u1ED9i dung \u2014 nh\u01B0ng aria-hidden\n kh\u00F4ng ph\u1EA3i l\u00E0 c\u00E1ch khai b\u00E1o \u0111\u00FAng cho n\u00F3. `role=\"presentation\"` m\u1EDBi l\u00E0 t\u00EDn hi\u1EC7u ch\u00EDnh x\u00E1c\n (ph\u1EA7n t\u1EED n\u00E0y kh\u00F4ng mang ng\u1EEF ngh\u0129a), v\u00E0 quan tr\u1ECDng h\u01A1n: tr\u01B0\u1EDBc \u0111\u00E2y \"\u0111\u00F3ng b\u1EB1ng c\u00E1ch click ra\n ngo\u00E0i\" KH\u00D4NG c\u00F3 t\u01B0\u01A1ng \u0111\u01B0\u01A1ng b\u00E0n ph\u00EDm n\u00E0o c\u1EA3. Escape \u0111\u01B0\u1EE3c th\u00EAm \u1EDF drawer container. -->\n @if (_isOpened) {\n <div\n role=\"presentation\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\n", styles: [".sd-side-drawer{--sd-side-drawer-inset: 10px;--sd-side-drawer-radius: var(--sd-overlay-radius, 24px);--sd-side-drawer-surface: var(--sd-surface, #fff);--sd-side-drawer-border: var(--sd-border, #dde0e5);--sd-side-drawer-shadow: 0 6px 18px rgba(15, 23, 42, .18), 0 1px 3px rgba(15, 23, 42, .12);position:fixed;right:var(--sd-side-drawer-inset);top:var(--sd-side-drawer-inset);bottom:var(--sd-side-drawer-inset);max-width:calc(100vw - var(--sd-side-drawer-inset) - var(--sd-side-drawer-inset));background-color:var(--sd-side-drawer-surface);color:var(--sd-text);z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(calc(100% + var(--sd-side-drawer-inset)),0,0);transition:all .3s ease-in-out;pointer-events:none;overflow:hidden;border-radius:var(--sd-side-drawer-radius);box-shadow:var(--sd-side-drawer-shadow)}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:16px;display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-header-left{display:flex;align-items:center;flex:1 1 auto;min-width:0}.sd-side-drawer-header-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;min-width:0}.sd-side-drawer-close-btn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--sd-text-secondary);line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:background-color 80ms ease,color 80ms ease}.sd-side-drawer-close-btn:hover{background:var(--sd-surface-muted);color:var(--sd-text)}.sd-side-drawer-close-btn:active{background:var(--sd-border)}.sd-side-drawer-close-btn:focus{outline:none}.sd-side-drawer-close-btn:focus-visible{outline:2px solid var(--sd-primary);outline-offset:2px}.sd-side-drawer-close-btn sd-icon{display:block;width:20px;height:20px;font-size:20px;line-height:20px}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid var(--sd-side-drawer-border);padding:16px;background-color:var(--sd-side-drawer-surface);display:flex;align-items:center;justify-content:space-between;gap:16px}.sd-side-drawer-footer-left,.sd-side-drawer-footer-right{display:flex;align-items:center;gap:8px;min-width:0}.sd-side-drawer-footer-left{flex:1 1 auto}.sd-side-drawer-footer-right{flex:0 0 auto;margin-left:auto;justify-content:flex-end}.sd-side-drawer-footer-left:empty,.sd-side-drawer-footer-right:empty{display:none}.sd-side-drawer-footer:has(.sd-side-drawer-footer-left:empty):has(.sd-side-drawer-footer-right:empty){display:none}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"] }]
158
217
  }], ctorParameters: () => [] });
159
218
 
160
219
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-components-side-drawer.mjs","sources":["../../../projects/sdcorejs-angular/components/side-drawer/src/side-drawer.component.ts","../../../projects/sdcorejs-angular/components/side-drawer/src/side-drawer.component.html","../../../projects/sdcorejs-angular/components/side-drawer/sdcorejs-angular-components-side-drawer.ts"],"sourcesContent":["import { CdkPortal, DomPortalOutlet, PortalModule } from '@angular/cdk/portal';\nimport { CommonModule } from '@angular/common';\nimport {\n ApplicationRef,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n EmbeddedViewRef,\n Injector,\n ViewContainerRef,\n afterNextRender,\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { SdLoadingService } from '@sdcorejs/angular/services';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { fromEvent, merge, Observable, Subject } from 'rxjs';\nimport { map, takeUntil, startWith, distinctUntilChanged } from 'rxjs/operators';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\n\nexport type SdSideDrawerBeforeClose = () => boolean | Promise<boolean>;\n\n@Component({\n selector: 'sd-side-drawer',\n templateUrl: './side-drawer.component.html',\n styleUrl: './side-drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [SdIcon, CommonModule, PortalModule],\n})\nexport class SdSideDrawer {\n id = `I${Utilities.generateUuid()}`;\n\n portal = viewChild.required(CdkPortal);\n\n title = input<string>('');\n width = input<string>('480px');\n hideClose = input<boolean, boolean | ''>(false, { transform: booleanAttribute });\n disableBackdropClose = input<boolean, boolean | ''>(false, { transform: booleanAttribute });\n beforeClose = input<SdSideDrawerBeforeClose | undefined>(undefined);\n\n // Custom CSS class added to the root side-drawer container\n drawerClass = input<any>('');\n\n readonly autoIdInput = input<string | undefined | null>(undefined, { alias: 'autoId' });\n readonly autoId = computed(() => (this.autoIdInput() ? `components-side-drawer-${this.autoIdInput()}` : undefined));\n\n sdClosed = output<void>();\n sdCloseError = output<unknown>();\n\n #embeddedViewRef!: EmbeddedViewRef<any>;\n\n readonly #isOpenedSignal = signal(false);\n readonly #isLoadingSignal = signal(false);\n readonly isOpened = this.#isOpenedSignal.asReadonly();\n readonly isLoading = this.#isLoadingSignal.asReadonly();\n readonly dataOpened = computed(() => (this.#isOpenedSignal() ? 'true' : 'false'));\n readonly dataLoading = computed(() => (this.#isLoadingSignal() ? 'true' : 'false'));\n\n isHovered$!: Observable<boolean>;\n #destroy$ = new Subject<void>();\n #previousBodyOverflow: string | null = null;\n #closeRequest?: Promise<boolean>;\n\n #viewContainerRef = inject(ViewContainerRef);\n #ar = inject(ApplicationRef);\n #injector = inject(Injector);\n #ref = inject(ChangeDetectorRef);\n #loadingService = inject(SdLoadingService);\n #destroyRef = inject(DestroyRef);\n\n constructor() {\n // Thay thế ngAfterViewInit, tự động chạy nội dung này khi DOM sẵn sàng để render\n afterNextRender(() => {\n // 1. Gắn portal vào body và lưu lại EmbeddedViewRef\n const outlet = new DomPortalOutlet(document.body, this.#viewContainerRef, this.#ar, this.#injector);\n this.#embeddedViewRef = outlet.attachTemplatePortal(this.portal());\n\n // 2. Setup sự kiện hover ngay sau khi DOM thật đã được in ra\n this.#setupHoverSubscription();\n });\n\n // Thay thế ngOnDestroy bằng logic destroy trực tiếp\n this.#destroyRef.onDestroy(() => {\n this.#destroy$.next();\n this.#destroy$.complete();\n\n if (this.#embeddedViewRef) {\n this.#embeddedViewRef.destroy();\n }\n\n if (this.#isOpenedSignal()) {\n if (this.#previousBodyOverflow !== null) {\n document.body.style.overflow = this.#previousBodyOverflow;\n } else {\n document.body.style.overflow = '';\n }\n }\n });\n }\n\n open = () => {\n this.#ref.markForCheck();\n this.#isOpenedSignal.set(true);\n\n // Chặn scroll ở document body\n this.#previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n };\n\n close = () => {\n if (this.beforeClose()) {\n void this.requestClose();\n return;\n }\n this.forceClose();\n };\n\n requestClose = (): Promise<boolean> => {\n const guard = this.beforeClose();\n if (!guard) {\n this.forceClose();\n return Promise.resolve(true);\n }\n if (this.#closeRequest) return this.#closeRequest;\n\n const request = Promise.resolve()\n .then(() => guard())\n .then(canClose => {\n if (!canClose) return false;\n this.forceClose();\n return true;\n })\n .catch((error: unknown) => {\n this.sdCloseError.emit(error);\n return false;\n });\n\n this.#closeRequest = request;\n void request.finally(() => {\n if (this.#closeRequest === request) this.#closeRequest = undefined;\n });\n return request;\n };\n\n forceClose = () => {\n this.#ref.markForCheck();\n this.#isOpenedSignal.set(false);\n this.sdClosed.emit();\n this.stopLoading();\n\n // Khôi phục lại scroll ở document body\n if (this.#previousBodyOverflow !== null) {\n document.body.style.overflow = this.#previousBodyOverflow;\n this.#previousBodyOverflow = null;\n } else {\n document.body.style.overflow = '';\n }\n };\n\n startLoading = () => {\n this.#isLoadingSignal.set(true);\n this.#loadingService.stop(`#${this.id}`);\n this.#loadingService.start(`#${this.id}`);\n };\n\n stopLoading = () => {\n this.#isLoadingSignal.set(false);\n this.#loadingService.stop(`#${this.id}`);\n };\n\n preventScroll = (event: Event) => {\n event.preventDefault();\n };\n\n #setupHoverSubscription(): void {\n if (!this.#embeddedViewRef) return;\n\n // 3. Lấy DOM element trực tiếp từ rootNodes của EmbeddedViewRef\n const rootNodes = this.#embeddedViewRef.rootNodes;\n const element = rootNodes.find(\n node => node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).classList?.contains('sd-side-drawer')\n );\n\n if (!element) {\n console.warn('SdSideDrawer: Cannot find sd-side-drawer element to attach hover event');\n return;\n }\n\n // 4. Gắn event listeners trực tiếp lên element thật\n const mouseEnter$ = fromEvent(element, 'mouseenter').pipe(map(() => true));\n const mouseLeave$ = fromEvent(element, 'mouseleave').pipe(map(() => false));\n\n this.isHovered$ = merge(mouseEnter$, mouseLeave$).pipe(startWith(false), distinctUntilChanged(), takeUntil(this.#destroy$));\n }\n}\n","<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <button type=\"button\" class=\"sd-side-drawer-close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n @if (_isOpened) {\n <div\n aria-hidden=\"true\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\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-components-side-drawer.mjs","sources":["../../../projects/sdcorejs-angular/components/side-drawer/src/body-scroll-lock.service.ts","../../../projects/sdcorejs-angular/components/side-drawer/src/side-drawer.component.ts","../../../projects/sdcorejs-angular/components/side-drawer/src/side-drawer.component.html","../../../projects/sdcorejs-angular/components/side-drawer/sdcorejs-angular-components-side-drawer.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport { Injectable, inject } from '@angular/core';\n\n/**\n * Khoá scroll của `document.body` theo kiểu đếm tham chiếu (ref-count).\n *\n * why: mỗi drawer tự lưu/khôi phục `document.body.style.overflow` là KHÔNG stack-safe. Với hai\n * drawer chồng nhau: drawer ngoài lưu `''` rồi set `'hidden'`; drawer trong lưu `'hidden'` (giá trị\n * do drawer ngoài vừa ghi) rồi cũng set `'hidden'`. Đóng drawer ngoài trước → khôi phục `''` →\n * trang scroll lại được dù drawer trong vẫn mở; đóng drawer trong sau → khôi phục `'hidden'` →\n * trang KHOÁ SCROLL VĨNH VIỄN dù không còn drawer nào. Ref-count chỉ ghi DOM ở lần khoá đầu và\n * lần nhả cuối, nên thứ tự đóng không còn ảnh hưởng.\n */\ninterface SdBodyScrollLockState {\n count: number;\n previousOverflow: string | null;\n}\n\n/**\n * why: state nằm ở MODULE, khoá theo `Document`, KHÔNG nằm trong instance service.\n * `providedIn: 'root'` cho một instance mỗi *ứng dụng Angular*, không phải mỗi *document*. Hai app\n * bootstrap độc lập trên cùng một trang (micro-frontend, widget nhúng) sẽ có hai bộ đếm riêng, mỗi\n * bên chụp lại `overflow` mà bên kia vừa ghi — tái lập đúng bug khoá-scroll-vĩnh-viễn mà service\n * này sinh ra để sửa, chỉ dịch lên một tầng. Khoá theo `Document` thì mọi instance trong cùng\n * document dùng chung một bộ đếm, bất kể injector nào tạo ra chúng.\n */\nconst scrollLockStates = new WeakMap<Document, SdBodyScrollLockState>();\n\nconst stateFor = (doc: Document): SdBodyScrollLockState => {\n let state = scrollLockStates.get(doc);\n if (!state) {\n state = { count: 0, previousOverflow: null };\n scrollLockStates.set(doc, state);\n }\n return state;\n};\n\n@Injectable({ providedIn: 'root' })\nexport class SdBodyScrollLockService {\n readonly #document = inject(DOCUMENT);\n\n /** Số lượng khoá đang giữ trên document này — phục vụ chẩn đoán/test. */\n get count(): number {\n return stateFor(this.#document).count;\n }\n\n lock(): void {\n const state = stateFor(this.#document);\n const body = this.#document.body;\n if (!body) return;\n if (state.count === 0) {\n state.previousOverflow = body.style.overflow;\n body.style.overflow = 'hidden';\n }\n state.count++;\n }\n\n release(): void {\n const state = stateFor(this.#document);\n // Nhả nhiều hơn khoá (double close, destroy sau close) không được đẩy count xuống âm,\n // nếu không lần khoá kế tiếp sẽ không ghi DOM.\n if (state.count === 0) return;\n state.count--;\n if (state.count > 0) return;\n const body = this.#document.body;\n if (body) body.style.overflow = state.previousOverflow ?? '';\n state.previousOverflow = null;\n }\n}\n","import { CdkPortal, DomPortalOutlet, PortalModule } from '@angular/cdk/portal';\nimport { CommonModule } from '@angular/common';\nimport {\n ApplicationRef,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n EmbeddedViewRef,\n Injector,\n ViewContainerRef,\n afterNextRender,\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { SdLoadingService } from '@sdcorejs/angular/services';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { fromEvent, merge, Observable, Subject } from 'rxjs';\nimport { map, takeUntil, startWith, distinctUntilChanged } from 'rxjs/operators';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\nimport { SdTranslatePipe } from '@sdcorejs/angular/i18n';\nimport { SdBodyScrollLockService } from './body-scroll-lock.service';\n\nexport type SdSideDrawerBeforeClose = () => boolean | Promise<boolean>;\n\n@Component({\n selector: 'sd-side-drawer',\n templateUrl: './side-drawer.component.html',\n styleUrl: './side-drawer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [SdIcon, CommonModule, PortalModule, SdTranslatePipe],\n})\nexport class SdSideDrawer {\n id = `I${Utilities.generateUuid()}`;\n\n portal = viewChild.required(CdkPortal);\n\n title = input<string>('');\n width = input<string>('480px');\n hideClose = input<boolean, boolean | ''>(false, { transform: booleanAttribute });\n disableBackdropClose = input<boolean, boolean | ''>(false, { transform: booleanAttribute });\n beforeClose = input<SdSideDrawerBeforeClose | undefined>(undefined);\n\n // Custom CSS class added to the root side-drawer container\n drawerClass = input<any>('');\n\n readonly autoIdInput = input<string | undefined | null>(undefined, { alias: 'autoId' });\n readonly autoId = computed(() => (this.autoIdInput() ? `components-side-drawer-${this.autoIdInput()}` : undefined));\n\n sdClosed = output<void>();\n sdCloseError = output<unknown>();\n\n #embeddedViewRef!: EmbeddedViewRef<any>;\n\n readonly #isOpenedSignal = signal(false);\n readonly #isLoadingSignal = signal(false);\n readonly isOpened = this.#isOpenedSignal.asReadonly();\n readonly isLoading = this.#isLoadingSignal.asReadonly();\n readonly dataOpened = computed(() => (this.#isOpenedSignal() ? 'true' : 'false'));\n readonly dataLoading = computed(() => (this.#isLoadingSignal() ? 'true' : 'false'));\n\n isHovered$!: Observable<boolean>;\n #destroy$ = new Subject<void>();\n // Instance này có đang giữ 1 khoá scroll hay không — giữ cho lock/release luôn cân bằng\n // dù open()/close() bị gọi lặp.\n #holdsScrollLock = false;\n #closeRequest?: Promise<boolean>;\n\n #viewContainerRef = inject(ViewContainerRef);\n #ar = inject(ApplicationRef);\n #injector = inject(Injector);\n #ref = inject(ChangeDetectorRef);\n #loadingService = inject(SdLoadingService);\n #destroyRef = inject(DestroyRef);\n #scrollLock = inject(SdBodyScrollLockService);\n\n constructor() {\n // Thay thế ngAfterViewInit, tự động chạy nội dung này khi DOM sẵn sàng để render\n afterNextRender(() => {\n // 1. Gắn portal vào body và lưu lại EmbeddedViewRef\n const outlet = new DomPortalOutlet(document.body, this.#viewContainerRef, this.#ar, this.#injector);\n this.#embeddedViewRef = outlet.attachTemplatePortal(this.portal());\n\n // 2. Setup sự kiện hover ngay sau khi DOM thật đã được in ra\n this.#setupHoverSubscription();\n });\n\n // Thay thế ngOnDestroy bằng logic destroy trực tiếp\n this.#destroyRef.onDestroy(() => {\n this.#destroy$.next();\n this.#destroy$.complete();\n\n if (this.#embeddedViewRef) {\n this.#embeddedViewRef.destroy();\n }\n\n // Destroy khi đang mở cũng phải nhả khoá, nếu không ref-count kẹt > 0 và trang khoá scroll mãi.\n this.#releaseScrollLock();\n });\n }\n\n open = () => {\n this.#ref.markForCheck();\n this.#isOpenedSignal.set(true);\n\n // Chặn scroll ở document body qua khoá ref-count dùng chung (stack-safe với drawer lồng nhau)\n this.#acquireScrollLock();\n };\n\n close = () => {\n if (this.beforeClose()) {\n void this.requestClose();\n return;\n }\n this.forceClose();\n };\n\n requestClose = (): Promise<boolean> => {\n const guard = this.beforeClose();\n if (!guard) {\n this.forceClose();\n return Promise.resolve(true);\n }\n if (this.#closeRequest) return this.#closeRequest;\n\n const request = Promise.resolve()\n .then(() => guard())\n .then(canClose => {\n if (!canClose) return false;\n this.forceClose();\n return true;\n })\n .catch((error: unknown) => {\n this.sdCloseError.emit(error);\n return false;\n });\n\n this.#closeRequest = request;\n void request.finally(() => {\n if (this.#closeRequest === request) this.#closeRequest = undefined;\n });\n return request;\n };\n\n forceClose = () => {\n this.#ref.markForCheck();\n this.#isOpenedSignal.set(false);\n this.sdClosed.emit();\n this.stopLoading();\n\n // Khôi phục lại scroll ở document body (chỉ thật sự khôi phục khi drawer cuối cùng nhả khoá)\n this.#releaseScrollLock();\n };\n\n #acquireScrollLock(): void {\n if (this.#holdsScrollLock) return;\n this.#holdsScrollLock = true;\n this.#scrollLock.lock();\n }\n\n #releaseScrollLock(): void {\n if (!this.#holdsScrollLock) return;\n this.#holdsScrollLock = false;\n this.#scrollLock.release();\n }\n\n startLoading = () => {\n this.#isLoadingSignal.set(true);\n this.#loadingService.stop(`#${this.id}`);\n this.#loadingService.start(`#${this.id}`);\n };\n\n stopLoading = () => {\n this.#isLoadingSignal.set(false);\n this.#loadingService.stop(`#${this.id}`);\n };\n\n preventScroll = (event: Event) => {\n event.preventDefault();\n };\n\n #setupHoverSubscription(): void {\n if (!this.#embeddedViewRef) return;\n\n // 3. Lấy DOM element trực tiếp từ rootNodes của EmbeddedViewRef\n const rootNodes = this.#embeddedViewRef.rootNodes;\n const element = rootNodes.find(\n node => node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).classList?.contains('sd-side-drawer')\n );\n\n if (!element) {\n console.warn('SdSideDrawer: Cannot find sd-side-drawer element to attach hover event');\n return;\n }\n\n // 4. Gắn event listeners trực tiếp lên element thật\n const mouseEnter$ = fromEvent(element, 'mouseenter').pipe(map(() => true));\n const mouseLeave$ = fromEvent(element, 'mouseleave').pipe(map(() => false));\n\n this.isHovered$ = merge(mouseEnter$, mouseLeave$).pipe(startWith(false), distinctUntilChanged(), takeUntil(this.#destroy$));\n }\n}\n","<ng-template cdkPortal>\n @let _isOpened = isOpened();\n <div\n [id]=\"id\"\n class=\"sd-side-drawer\"\n [ngStyle]=\"{ width: width() }\"\n [ngClass]=\"drawerClass()\"\n [class.sd-side-drawer-active]=\"_isOpened\"\n [attr.data-autoId]=\"autoId()\"\n [attr.data-opened]=\"dataOpened()\"\n [attr.data-loading]=\"dataLoading()\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"title() || null\"\n (keydown.escape)=\"disableBackdropClose() ? null : close()\"\n #drawerContainer>\n @if (_isOpened) {\n <ng-container>\n <div class=\"sd-side-drawer-header\">\n <div class=\"sd-side-drawer-header-left\">\n <ng-content select=\"[sdHeaderLeft]\">\n <div class=\"sd-side-drawer-title\">{{ title() }}</div>\n </ng-content>\n </div>\n <div class=\"sd-side-drawer-header-right\">\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\n @if (!hideClose()) {\n <!-- why: aria-label=\"Close\" là chuỗi tiếng Anh cứng trong một thư viện đã có i18n. -->\n <button\n type=\"button\"\n class=\"sd-side-drawer-close-btn\"\n (click)=\"close()\"\n [attr.aria-label]=\"'core.common.close' | sdTranslate\">\n <sd-icon name=\"close\"></sd-icon>\n </button>\n }\n </div>\n </div>\n @if (_isOpened) {\n <div class=\"sd-side-drawer-body\">\n <div class=\"sd-side-drawer-content\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n <div class=\"sd-side-drawer-footer\">\n <div class=\"sd-side-drawer-footer-left\"><ng-content select=\"[sdFooterLeft]\"></ng-content></div>\n <div class=\"sd-side-drawer-footer-right\"><ng-content select=\"[sdFooterRight]\"></ng-content></div>\n </div>\n </ng-container>\n }\n </div>\n <!-- why: backdrop là lớp phủ bắt click thuần trang trí, không có nội dung — nhưng aria-hidden\n không phải là cách khai báo đúng cho nó. `role=\"presentation\"` mới là tín hiệu chính xác\n (phần tử này không mang ngữ nghĩa), và quan trọng hơn: trước đây \"đóng bằng cách click ra\n ngoài\" KHÔNG có tương đương bàn phím nào cả. Escape được thêm ở drawer container. -->\n @if (_isOpened) {\n <div\n role=\"presentation\"\n class=\"sd-side-drawer-backdrop\"\n (click)=\"disableBackdropClose() ? null : close()\"\n (wheel)=\"preventScroll($event)\"\n (touchmove)=\"preventScroll($event)\"></div>\n }\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, input, booleanAttribute, numberAttribute, output, HostListener, Component, model, signal, Injectable, EnvironmentInjector, Injector, DestroyRef, computed, contentChildren, effect, afterNextRender, createComponent } from '@angular/core';
2
+ import { inject, ElementRef, DestroyRef, input, booleanAttribute, numberAttribute, output, HostListener, Component, model, signal, Injectable, EnvironmentInjector, Injector, computed, contentChildren, effect, afterNextRender, untracked, createComponent } from '@angular/core';
3
3
  import { SdStorageService } from '@sdcorejs/angular/services/storage';
4
4
 
5
5
  class SdSplitterHandleComponent {
6
6
  elementRef = inject(ElementRef);
7
+ #destroyRef = inject(DestroyRef);
8
+ constructor() {
9
+ // why: destroy giữa lúc drag (splitter re-sync handle, panel bị @if gỡ…) thì pointerup
10
+ // KHÔNG bao giờ tới → frame đang chờ vẫn chạy và emit dragMove từ component đã tháo.
11
+ // Huỷ frame ở cả destroy chứ không chỉ ở pointerup.
12
+ this.#destroyRef.onDestroy(() => this.#cancelPendingFrame());
13
+ }
7
14
  orientation = input('horizontal');
8
15
  disabled = input(false, { transform: booleanAttribute });
9
16
  keyboardStep = input(10, { transform: numberAttribute });
@@ -92,12 +99,15 @@ class SdSplitterHandleComponent {
92
99
  this.elementRef.nativeElement.releasePointerCapture(ev.pointerId);
93
100
  this.#pointerId = null;
94
101
  // Hủy rAF đang chờ để tránh emit dragMove sau khi drag kết thúc
95
- if (this.#rafPending != null) {
96
- cancelAnimationFrame(this.#rafPending);
97
- this.#rafPending = null;
98
- }
102
+ this.#cancelPendingFrame();
99
103
  this.dragEnd.emit();
100
104
  }
105
+ #cancelPendingFrame() {
106
+ if (this.#rafPending == null)
107
+ return;
108
+ cancelAnimationFrame(this.#rafPending);
109
+ this.#rafPending = null;
110
+ }
101
111
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSplitterHandleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
102
112
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.21", type: SdSplitterHandleComponent, isStandalone: true, selector: "sd-splitter-handle", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null }, ariaValueMin: { classPropertyName: "ariaValueMin", publicName: "ariaValueMin", isSignal: true, isRequired: false, transformFunction: null }, ariaValueMax: { classPropertyName: "ariaValueMax", publicName: "ariaValueMax", isSignal: true, isRequired: false, transformFunction: null }, ariaValueNow: { classPropertyName: "ariaValueNow", publicName: "ariaValueNow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dragStart: "dragStart", dragMove: "dragMove", dragEnd: "dragEnd", toggleRequest: "toggleRequest" }, host: { listeners: { "dblclick": "onDblClick()", "keydown": "onKeyDown($event)", "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp($event)", "pointercancel": "onPointerUp($event)" }, properties: { "class.sd-splitter__handle--horizontal": "orientation() === \"horizontal\"", "class.sd-splitter__handle--vertical": "orientation() === \"vertical\"", "class.sd-splitter__handle--disabled": "disabled()", "attr.tabindex": "disabled() ? -1 : 0", "attr.role": "\"separator\"", "attr.aria-orientation": "orientation() === \"horizontal\" ? \"vertical\" : \"horizontal\"", "attr.aria-disabled": "disabled() ? \"true\" : null", "attr.aria-valuemin": "ariaValueMin() ?? null", "attr.aria-valuemax": "ariaValueMax() ?? null", "attr.aria-valuenow": "ariaValueNow() ?? null" }, classAttribute: "sd-splitter__handle" }, ngImport: i0, template: "<span class=\"sd-splitter__handle-bar\"></span>\n", styles: [":host{--sd-splitter-handle-size: 4px;--sd-splitter-handle-color: var(--sd-primary-light, #d7e3ff);--sd-splitter-handle-hover-color: var(--sd-primary, #005cbb);--sd-splitter-handle-active-color: var(--sd-primary, #005cbb);--sd-splitter-handle-hit-area: 8px;--sd-splitter-handle-radius: 0;--sd-splitter-disabled-opacity: .5;display:flex;align-items:center;justify-content:center;flex:0 0 var(--sd-splitter-handle-hit-area);-webkit-user-select:none;user-select:none;outline:none}:host.sd-splitter__handle--horizontal{cursor:col-resize}:host.sd-splitter__handle--horizontal .sd-splitter__handle-bar{width:var(--sd-splitter-handle-size);height:100%}:host.sd-splitter__handle--vertical{cursor:row-resize;flex-direction:column}:host.sd-splitter__handle--vertical .sd-splitter__handle-bar{width:100%;height:var(--sd-splitter-handle-size)}:host .sd-splitter__handle-bar{background:var(--sd-splitter-handle-color);border-radius:var(--sd-splitter-handle-radius);transition:background-color .12s ease}:host:hover .sd-splitter__handle-bar{background:var(--sd-splitter-handle-hover-color)}:host:focus-visible{outline:2px solid var(--sd-splitter-handle-active-color);outline-offset:1px}:host.sd-splitter__handle--disabled{cursor:default;opacity:var(--sd-splitter-disabled-opacity);pointer-events:none}\n"] });
103
113
  }
@@ -116,7 +126,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
116
126
  '[attr.aria-valuemax]': 'ariaValueMax() ?? null',
117
127
  '[attr.aria-valuenow]': 'ariaValueNow() ?? null',
118
128
  }, template: "<span class=\"sd-splitter__handle-bar\"></span>\n", styles: [":host{--sd-splitter-handle-size: 4px;--sd-splitter-handle-color: var(--sd-primary-light, #d7e3ff);--sd-splitter-handle-hover-color: var(--sd-primary, #005cbb);--sd-splitter-handle-active-color: var(--sd-primary, #005cbb);--sd-splitter-handle-hit-area: 8px;--sd-splitter-handle-radius: 0;--sd-splitter-disabled-opacity: .5;display:flex;align-items:center;justify-content:center;flex:0 0 var(--sd-splitter-handle-hit-area);-webkit-user-select:none;user-select:none;outline:none}:host.sd-splitter__handle--horizontal{cursor:col-resize}:host.sd-splitter__handle--horizontal .sd-splitter__handle-bar{width:var(--sd-splitter-handle-size);height:100%}:host.sd-splitter__handle--vertical{cursor:row-resize;flex-direction:column}:host.sd-splitter__handle--vertical .sd-splitter__handle-bar{width:100%;height:var(--sd-splitter-handle-size)}:host .sd-splitter__handle-bar{background:var(--sd-splitter-handle-color);border-radius:var(--sd-splitter-handle-radius);transition:background-color .12s ease}:host:hover .sd-splitter__handle-bar{background:var(--sd-splitter-handle-hover-color)}:host:focus-visible{outline:2px solid var(--sd-splitter-handle-active-color);outline-offset:1px}:host.sd-splitter__handle--disabled{cursor:default;opacity:var(--sd-splitter-disabled-opacity);pointer-events:none}\n"] }]
119
- }], propDecorators: { onDblClick: [{
129
+ }], ctorParameters: () => [], propDecorators: { onDblClick: [{
120
130
  type: HostListener,
121
131
  args: ['dblclick']
122
132
  }], onKeyDown: [{
@@ -405,6 +415,8 @@ class SdSplitterComponent {
405
415
  #dragStartSize = null;
406
416
  #dragLastDelta = 0;
407
417
  #prevCollapsedMap = new Map();
418
+ // Lật true sau lần render đầu → effect sync handle mới được phép chạm DOM.
419
+ #firstRenderDone = signal(false);
408
420
  constructor() {
409
421
  // 1. Reconcile state khi panels signal đổi (panel add/remove qua @if/@for)
410
422
  effect(() => {
@@ -476,14 +488,26 @@ class SdSplitterComponent {
476
488
  panel.elementRef.nativeElement.style.flex = flex;
477
489
  }
478
490
  });
479
- // Sync handles sau khi DOM render xong (panels đã projected vào host)
491
+ // Sync handles sau khi DOM render xong (panels đã projected vào host).
492
+ // why: afterNextRender đăng ký BÊN TRONG effect() sẽ tạo MỘT hook one-shot mới cho MỖI lần
493
+ // panels/orientation/disabled/keyboardStep tick — cùng với đó là một sequence + một
494
+ // DestroyRef.onDestroy mới xếp hàng trong AfterRenderManager. Đăng ký ĐÚNG MỘT lần ở
495
+ // constructor (chỉ để mở cổng sau lần render đầu), rồi đọc reactive value trong effect.
496
+ afterNextRender(() => this.#firstRenderDone.set(true), { injector: this.#injector });
480
497
  effect(() => {
481
- const panelCount = this.panels().length;
498
+ const panels = this.panels();
482
499
  const orientation = this.resolvedOrientation();
483
500
  const disabled = this.resolvedDisabled();
484
501
  const keyboardStep = this.resolvedKeyboardStep();
485
- afterNextRender(() => this.#syncHandles(panelCount, orientation, disabled, keyboardStep), { injector: this.#injector } // component-scoped auto-cancel khi component destroy
486
- );
502
+ // why: #syncHandles đọc panel.resizable() để tính handleDisabled. Khi còn chạy TRỰC TIẾP
503
+ // trong effect thì mỗi resizable() là một dependency ngầm — không rõ ràng, và trộn lẫn với
504
+ // các signal mà #syncHandles tình cờ đọc (panels(), state…). Đọc TƯỜNG MINH ở đây rồi gọi
505
+ // #syncHandles trong untracked(): dependency set của effect là đúng 5 dòng này, không hơn.
506
+ const resizables = panels.map(panel => panel.resizable());
507
+ // Lần CD đầu tiên panel chưa render xong → chờ afterNextRender mở cổng rồi effect tự chạy lại.
508
+ if (!this.#firstRenderDone())
509
+ return;
510
+ untracked(() => this.#syncHandles(panels, orientation, disabled, keyboardStep, resizables));
487
511
  });
488
512
  // Destroy handle ComponentRef khi container bị destroy (tránh leak)
489
513
  this.#destroyRef.onDestroy(() => {
@@ -505,9 +529,8 @@ class SdSplitterComponent {
505
529
  lastSize: panel.size(),
506
530
  };
507
531
  }
508
- #syncHandles(panelCount, orientation, disabled, keyboardStep) {
509
- const panels = this.panels();
510
- const needed = Math.max(0, panelCount - 1);
532
+ #syncHandles(panels, orientation, disabled, keyboardStep, resizables) {
533
+ const needed = Math.max(0, panels.length - 1);
511
534
  // Remove excess
512
535
  while (this.#handleRefs.length > needed) {
513
536
  this.#handleRefs.pop().destroy();
@@ -522,24 +545,37 @@ class SdSplitterComponent {
522
545
  ref.instance.toggleRequest.subscribe(() => this.#onHandleToggle(handleIndex));
523
546
  this.#handleRefs.push(ref);
524
547
  }
525
- // Apply inputs với disabled tính theo per-panel resizable
548
+ // Apply inputs với disabled tính theo per-panel resizable (đã đọc sẵn ở effect)
526
549
  for (let i = 0; i < this.#handleRefs.length; i++) {
527
550
  const ref = this.#handleRefs[i];
528
- const prev = panels[i];
529
- const next = panels[i + 1];
530
- const handleDisabled = disabled || !prev.resizable() || !next.resizable();
551
+ const handleDisabled = disabled || !resizables[i] || !resizables[i + 1];
531
552
  ref.setInput('orientation', orientation);
532
553
  ref.setInput('disabled', handleDisabled);
533
554
  ref.setInput('keyboardStep', keyboardStep);
534
555
  ref.changeDetectorRef.detectChanges();
535
556
  }
536
- // Re-arrange DOM: panel0, handle0, panel1, handle1, ..., panelN
537
- const host = this.#host.nativeElement;
557
+ // Thứ tự DOM mong muốn: panel0, handle0, panel1, handle1, ..., panelN
558
+ const desired = [];
538
559
  for (let i = 0; i < panels.length; i++) {
539
- host.appendChild(panels[i].elementRef.nativeElement);
560
+ desired.push(panels[i].elementRef.nativeElement);
540
561
  if (i < this.#handleRefs.length)
541
- host.appendChild(this.#handleRefs[i].location.nativeElement);
562
+ desired.push(this.#handleRefs[i].location.nativeElement);
542
563
  }
564
+ // why: appendChild trên một node ĐANG ở đúng vị trí vẫn là một move thật — trình duyệt tháo
565
+ // rồi gắn lại element, nên iframe/video/audio bên trong reload từ đầu và focus/scroll trong
566
+ // panel bị mất. Effect này chạy lại mỗi khi orientation/disabled/keyboardStep/resizable đổi,
567
+ // tức đổi [resizable] của MỘT panel cũng đủ để xáo tung DOM sống. Chỉ sắp xếp lại khi trình
568
+ // tự panel/handle THỰC SỰ khác với DOM hiện tại.
569
+ const host = this.#host.nativeElement;
570
+ if (this.#matchesDomOrder(host, desired))
571
+ return;
572
+ for (const node of desired)
573
+ host.appendChild(node);
574
+ }
575
+ #matchesDomOrder(host, desired) {
576
+ const tracked = new Set(desired);
577
+ const actual = Array.from(host.children).filter(child => tracked.has(child));
578
+ return actual.length === desired.length && desired.every((node, index) => actual[index] === node);
543
579
  }
544
580
  #onDragStart(handleIndex) {
545
581
  const rect = this.#host.nativeElement.getBoundingClientRect();