@heartlandone/vega 2.97.0 → 2.99.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 (628) hide show
  1. package/dist/cjs/{action-color-accessibility-validator-24498816.js → action-color-accessibility-validator-b8a9ef0f.js} +2 -2
  2. package/dist/cjs/{app-globals-3f57f7b9.js → app-globals-a306dce6.js} +10 -10
  3. package/dist/cjs/{aria-dialog-focusable-trap-slimmer-73b4c0cb.js → aria-dialog-focusable-trap-slimmer-62147489.js} +1 -1
  4. package/dist/cjs/{code-block-28a7732e.js → code-block-ea1f5eb3.js} +58 -14
  5. package/dist/cjs/{component-global-style-slimmer-93c5fb50.js → component-global-style-slimmer-ba588eb4.js} +1 -1
  6. package/dist/cjs/{component-value-history-controller-slimmer.abstract-50cdba6e.js → component-value-history-controller-slimmer.abstract-7071b064.js} +25 -6
  7. package/dist/cjs/constants-907d7d89.js +16 -0
  8. package/dist/cjs/{content-state-e2ab0f06.js → content-state-8d07d530.js} +61 -9
  9. package/dist/cjs/{dark-mode-style-controller-32d4054a.js → dark-mode-style-controller-f3da3342.js} +2 -2
  10. package/dist/cjs/{date-required-rule-5250f26d.js → date-required-rule-c0c498ad.js} +1 -1
  11. package/dist/cjs/{design-token-74674ee7.js → design-token-58bf7fe8.js} +3 -3
  12. package/dist/cjs/{element-appender-slimmer-ccb2e3c2.js → element-appender-slimmer-a0269315.js} +3 -3
  13. package/dist/cjs/{form-field-controller-slimmer-58621b5e.js → form-field-controller-slimmer-f73f200e.js} +4 -4
  14. package/dist/cjs/{image-annotation-action-b54dc669.js → image-annotation-action-2a28038e.js} +3 -3
  15. package/dist/cjs/{image-extension-1e3eeaa3.js → image-extension-9659708b.js} +2 -2
  16. package/dist/cjs/{index-592b4052.js → index-5d1adcd1.js} +86 -0
  17. package/dist/cjs/index.cjs.js +20 -20
  18. package/dist/cjs/{inject-keyboard-manager-6a6f5c9f.js → inject-keyboard-manager-4fa45f9c.js} +1 -1
  19. package/dist/cjs/{internal-calendar-period-factory-dfcdd3f6.js → internal-calendar-period-factory-6196bc80.js} +1 -1
  20. package/dist/cjs/{internal-translation-controller-1dac2df8.js → internal-translation-controller-51f7f865.js} +2 -0
  21. package/dist/cjs/{keyboard-manager-c56033b1.js → keyboard-manager-482fe257.js} +1 -1
  22. package/dist/cjs/{keyboard-manager-slimmer-7043a106.js → keyboard-manager-slimmer-a19430dd.js} +1 -1
  23. package/dist/cjs/{link-extension-71c27ff3.js → link-extension-11340e1c.js} +26 -7
  24. package/dist/cjs/loader.cjs.js +15 -15
  25. package/dist/cjs/{month-view-generator-7b704a80.js → month-view-generator-1a534451.js} +1 -1
  26. package/dist/cjs/{public-rules-55e34d2e.js → public-rules-6c77b545.js} +10 -10
  27. package/dist/cjs/{range-eebe0e3c.js → range-22ddd452.js} +1 -1
  28. package/dist/cjs/{responsive-format-facade-493d1763.js → responsive-format-facade-74e6d791.js} +1 -1
  29. package/dist/cjs/{rich-text-editor-required-rule-4fdee376.js → rich-text-editor-required-rule-09d1093a.js} +1 -1
  30. package/dist/cjs/{split-cell-operation-51097409.js → split-cell-operation-11bfb305.js} +14 -4
  31. package/dist/cjs/{state-border-formatter-711c7aaa.js → state-border-formatter-bf4f9f1c.js} +1 -1
  32. package/dist/cjs/{string-format-strategy.abstract-b61bdcbd.js → string-format-strategy.abstract-bf37b14c.js} +1 -1
  33. package/dist/cjs/{string-input-formatter-slimmer-18d45942.js → string-input-formatter-slimmer-ffeb18a6.js} +3 -3
  34. package/dist/cjs/{string-mask-strategy-ad3fa0ac.js → string-mask-strategy-7f394726.js} +2 -2
  35. package/dist/cjs/{style-formatter-8f711cb0.js → style-formatter-4ac63910.js} +2 -2
  36. package/dist/cjs/{sub-state-notify-slimmer-18891389.js → sub-state-notify-slimmer-e264a120.js} +1 -1
  37. package/dist/cjs/{sub-state-observer-slimmer-eee2f0ae.js → sub-state-observer-slimmer-6033b824.js} +1 -1
  38. package/dist/cjs/{time-required-rule-87417359.js → time-required-rule-e2a50003.js} +1 -1
  39. package/dist/cjs/{token-extension-608bbf7b.js → token-extension-4686cb3b.js} +2921 -158
  40. package/dist/cjs/{translation-slimmer-77c7f37d.js → translation-slimmer-e4997f8f.js} +1 -1
  41. package/dist/cjs/{type-guard-a57ef3ac.js → type-guard-b8d7bb3f.js} +1 -1
  42. package/dist/cjs/{valid-credit-card-number-rule-4c0a3040.js → valid-credit-card-number-rule-f5a6f009.js} +1 -1
  43. package/dist/cjs/vega-accordion.cjs.entry.js +6 -6
  44. package/dist/cjs/vega-app-footer.cjs.entry.js +2 -2
  45. package/dist/cjs/vega-app-header-button.cjs.entry.js +5 -5
  46. package/dist/cjs/vega-box.cjs.entry.js +7 -7
  47. package/dist/cjs/vega-brand-logo.cjs.entry.js +2 -2
  48. package/dist/cjs/vega-breadcrumb.cjs.entry.js +2 -2
  49. package/dist/cjs/vega-button-circle.cjs.entry.js +7 -7
  50. package/dist/cjs/vega-button-group_2.cjs.entry.js +3 -3
  51. package/dist/cjs/vega-button-link.cjs.entry.js +1 -1
  52. package/dist/cjs/vega-button.cjs.entry.js +5 -5
  53. package/dist/cjs/vega-calendar_5.cjs.entry.js +24 -13
  54. package/dist/cjs/vega-card.cjs.entry.js +5 -5
  55. package/dist/cjs/vega-carousel.cjs.entry.js +3 -3
  56. package/dist/cjs/vega-checkbox_2.cjs.entry.js +5 -5
  57. package/dist/cjs/vega-chip.cjs.entry.js +5 -5
  58. package/dist/cjs/vega-code-block.cjs.entry.js +16 -29
  59. package/dist/cjs/vega-color-picker.cjs.entry.js +5 -5
  60. package/dist/cjs/vega-color-swatch-picker.cjs.entry.js +6 -6
  61. package/dist/cjs/vega-color-swatch.cjs.entry.js +2 -2
  62. package/dist/cjs/vega-combo-box.cjs.entry.js +11 -11
  63. package/dist/cjs/vega-counter-badge.cjs.entry.js +2 -2
  64. package/dist/cjs/vega-date-picker_2.cjs.entry.js +18 -18
  65. package/dist/cjs/vega-dialog_2.cjs.entry.js +7 -7
  66. package/dist/cjs/vega-divider.cjs.entry.js +5 -5
  67. package/dist/cjs/vega-dropdown_5.cjs.entry.js +46 -31
  68. package/dist/cjs/vega-env-manager-23b8b23c.js +2 -2
  69. package/dist/cjs/vega-field-label.cjs.entry.js +3 -3
  70. package/dist/cjs/vega-file-uploader.cjs.entry.js +41 -8
  71. package/dist/cjs/vega-flag-icon.cjs.entry.js +5 -5
  72. package/dist/cjs/vega-flex.cjs.entry.js +6 -6
  73. package/dist/cjs/vega-font.cjs.entry.js +5 -5
  74. package/dist/cjs/vega-form.cjs.entry.js +5 -5
  75. package/dist/cjs/vega-grid.cjs.entry.js +5 -5
  76. package/dist/cjs/vega-icon.cjs.entry.js +6 -6
  77. package/dist/cjs/vega-image-uploader.cjs.entry.js +9 -9
  78. package/dist/cjs/vega-input-credit-card.cjs.entry.js +9 -9
  79. package/dist/cjs/vega-input-numeric.cjs.entry.js +9 -9
  80. package/dist/cjs/vega-input-passcode.cjs.entry.js +8 -8
  81. package/dist/cjs/vega-input-phone-number.cjs.entry.js +7 -7
  82. package/dist/cjs/vega-input-range.cjs.entry.js +6 -6
  83. package/dist/cjs/vega-input-select.cjs.entry.js +8 -8
  84. package/dist/cjs/vega-input.cjs.entry.js +17 -12
  85. package/dist/cjs/{vega-internal-event-id-ecc5b87a.js → vega-internal-event-id-f4d33037.js} +2 -0
  86. package/dist/cjs/vega-left-nav_5.cjs.entry.js +13 -13
  87. package/dist/cjs/vega-loader-wrapper_2.cjs.entry.js +3 -3
  88. package/dist/cjs/vega-pagination-page-selector-mobile.cjs.entry.js +1 -1
  89. package/dist/cjs/vega-pagination-page-size-selector-mobile.cjs.entry.js +3 -3
  90. package/dist/cjs/vega-pagination.cjs.entry.js +5 -5
  91. package/dist/cjs/vega-popover_2.cjs.entry.js +10 -10
  92. package/dist/cjs/vega-progress-tracker_2.cjs.entry.js +6 -6
  93. package/dist/cjs/vega-radio_2.cjs.entry.js +8 -8
  94. package/dist/cjs/vega-rich-text-content.cjs.entry.js +42 -14
  95. package/dist/cjs/vega-rich-text-editor_4.cjs.entry.js +83 -26
  96. package/dist/cjs/vega-rich-text-find-replace-panel.cjs.entry.js +1 -1
  97. package/dist/cjs/vega-rich-text-special-characters-panel.cjs.entry.js +5 -5
  98. package/dist/cjs/vega-rich-text-table-properties_3.cjs.entry.js +11 -11
  99. package/dist/cjs/vega-selection-chip_2.cjs.entry.js +8 -8
  100. package/dist/cjs/vega-selection-tile_2.cjs.entry.js +6 -6
  101. package/dist/cjs/vega-sidenav_3.cjs.entry.js +6 -6
  102. package/dist/cjs/vega-signature-capture.cjs.entry.js +10 -10
  103. package/dist/cjs/vega-stepper.cjs.entry.js +5 -5
  104. package/dist/cjs/vega-tab-group_2.cjs.entry.js +21 -8
  105. package/dist/cjs/vega-table_11.cjs.entry.js +1219 -489
  106. package/dist/cjs/vega-textarea.cjs.entry.js +5 -5
  107. package/dist/cjs/vega-time-picker_2.cjs.entry.js +12 -12
  108. package/dist/cjs/vega-toggle-switch.cjs.entry.js +5 -5
  109. package/dist/cjs/vega-tooltip_2.cjs.entry.js +7 -7
  110. package/dist/cjs/vega.cjs.js +15 -15
  111. package/dist/cjs/{wait-for-component-did-render-02ca4bee.js → wait-for-component-did-render-44d006ee.js} +1 -1
  112. package/dist/collection/components/vega-accordion/vega-accordion.js +2 -0
  113. package/dist/collection/components/vega-app-footer/vega-app-footer.js +2 -0
  114. package/dist/collection/components/vega-app-header-button/vega-app-header-button.js +2 -0
  115. package/dist/collection/components/vega-banner/vega-banner.js +2 -0
  116. package/dist/collection/components/vega-box/vega-box.js +2 -0
  117. package/dist/collection/components/vega-brand-logo/vega-brand-logo.js +2 -0
  118. package/dist/collection/components/vega-breadcrumb/vega-breadcrumb.js +2 -0
  119. package/dist/collection/components/vega-button/vega-button.js +2 -0
  120. package/dist/collection/components/vega-button-circle/vega-button-circle.js +2 -0
  121. package/dist/collection/components/vega-button-group/vega-button-group.js +2 -0
  122. package/dist/collection/components/vega-button-link/vega-button-link.js +2 -0
  123. package/dist/collection/components/vega-calendar/slimmers/common/renderers/vega-calendar-event-item-renderer.js +5 -2
  124. package/dist/collection/components/vega-calendar/vega-calendar-event-preview/slimmers/renderers/vega-calendar-event-preview-detail-renderer.js +9 -1
  125. package/dist/collection/components/vega-calendar/vega-calendar.js +2 -0
  126. package/dist/collection/components/vega-card/vega-card.js +2 -0
  127. package/dist/collection/components/vega-carousel/vega-carousel.js +2 -0
  128. package/dist/collection/components/vega-checkbox/vega-checkbox.js +2 -0
  129. package/dist/collection/components/vega-chip/vega-chip.js +2 -0
  130. package/dist/collection/components/vega-code-block/slimmers/controllers/code-highlight-parser.abstract.js +4 -17
  131. package/dist/collection/components/vega-color-picker/vega-color-picker.js +2 -0
  132. package/dist/collection/components/vega-combo-box/vega-combo-box.js +2 -0
  133. package/dist/collection/components/vega-counter-badge/vega-counter-badge.js +4 -2
  134. package/dist/collection/components/vega-date-picker/vega-date-picker.js +2 -0
  135. package/dist/collection/components/vega-divider/vega-divider.js +2 -0
  136. package/dist/collection/components/vega-dropdown/constants.js +10 -0
  137. package/dist/collection/components/vega-dropdown/vega-dropdown-group/slimmers/renderers/vega-dropdown-group-renderer.js +6 -4
  138. package/dist/collection/components/vega-dropdown/vega-dropdown-item/slimmers/renderers/vega-dropdown-item-renderer.js +10 -2
  139. package/dist/collection/components/vega-dropdown/vega-dropdown.js +45 -14
  140. package/dist/collection/components/vega-field-error/vega-field-error.js +2 -0
  141. package/dist/collection/components/vega-field-label/vega-field-label.css +1 -0
  142. package/dist/collection/components/vega-file-uploader/slimmers/renderers/vega-file-uploader-file-list-renderer.js +22 -2
  143. package/dist/collection/components/vega-file-uploader/vega-file-uploader.js +63 -6
  144. package/dist/collection/components/vega-flag-icon/vega-flag-icon.js +2 -0
  145. package/dist/collection/components/vega-flex/vega-flex.js +2 -0
  146. package/dist/collection/components/vega-font/vega-font.js +2 -0
  147. package/dist/collection/components/vega-form/vega-form.js +2 -0
  148. package/dist/collection/components/vega-grid/vega-grid.js +2 -0
  149. package/dist/collection/components/vega-icon/vega-icon.js +2 -0
  150. package/dist/collection/components/vega-image-uploader/vega-image-uploader.js +2 -0
  151. package/dist/collection/components/vega-input/slimmers/renderers/vega-input-input-suggestions-renderer.js +7 -2
  152. package/dist/collection/components/vega-input/vega-input.js +2 -0
  153. package/dist/collection/components/vega-input-credit-card/vega-input-credit-card.js +2 -0
  154. package/dist/collection/components/vega-input-numeric/vega-input-numeric.js +2 -0
  155. package/dist/collection/components/vega-input-passcode/vega-input-passcode.js +2 -0
  156. package/dist/collection/components/vega-input-phone-number/vega-input-phone-number.js +2 -0
  157. package/dist/collection/components/vega-input-range/vega-input-range.js +2 -0
  158. package/dist/collection/components/vega-input-select/vega-input-select.js +2 -0
  159. package/dist/collection/components/vega-item-toggle/vega-item-toggle.js +2 -0
  160. package/dist/collection/components/vega-loading-indicator/vega-loading-indicator.js +2 -0
  161. package/dist/collection/components/vega-modal/vega-modal.js +2 -0
  162. package/dist/collection/components/vega-page-notification/vega-page-notification.js +2 -0
  163. package/dist/collection/components/vega-pagination/vega-pagination.js +2 -0
  164. package/dist/collection/components/vega-popover/vega-popover.js +2 -0
  165. package/dist/collection/components/vega-progress-tracker/vega-progress-tracker.js +2 -0
  166. package/dist/collection/components/vega-radio/vega-radio.js +2 -0
  167. package/dist/collection/components/vega-rich-text-editor/constants/constant.js +16 -0
  168. package/dist/collection/components/vega-rich-text-editor/dto/annotations/text-style-default-styles.js +2 -1
  169. package/dist/collection/components/vega-rich-text-editor/dto/blocks/block.abstract.js +11 -0
  170. package/dist/collection/components/vega-rich-text-editor/dto/blocks/text-block.js +3 -0
  171. package/dist/collection/components/vega-rich-text-editor/dto/filter-styles-strategies/filter-styles-strategy-registry.js +2 -1
  172. package/dist/collection/components/vega-rich-text-editor/dto/filter-styles-strategies/filter-styles-strategy.abstract.js +5 -2
  173. package/dist/collection/components/vega-rich-text-editor/dto/filter-styles-strategies/text-node/title-heading-node-filter-styles-strategy.js +36 -2
  174. package/dist/collection/components/vega-rich-text-editor/dto/nodes/code-block-node.js +2 -1
  175. package/dist/collection/components/vega-rich-text-editor/dto/renderers/blocks/block-text-nodes-renderer.abstract.js +20 -3
  176. package/dist/collection/components/vega-rich-text-editor/dto/renderers/nodes/text-node-renderer.js +12 -0
  177. package/dist/collection/components/vega-rich-text-editor/extensions/block-boundary-registry.js +34 -0
  178. package/dist/collection/components/vega-rich-text-editor/extensions/extension.abstract.js +20 -0
  179. package/dist/collection/components/vega-rich-text-editor/extensions/functions/action-handler-strategies/insert-function-block-strategy.js +3 -2
  180. package/dist/collection/components/vega-rich-text-editor/extensions/functions/constants.js +8 -0
  181. package/dist/collection/components/vega-rich-text-editor/extensions/functions/dto/function-block.js +14 -3
  182. package/dist/collection/components/vega-rich-text-editor/extensions/functions/dto/logic-control-block.js +2 -1
  183. package/dist/collection/components/vega-rich-text-editor/extensions/functions/element-to-dto-strategies/div-containing-function-block-strategy.js +19 -0
  184. package/dist/collection/components/vega-rich-text-editor/extensions/functions/element-to-dto-strategies/div-to-function-block-strategy.js +9 -5
  185. package/dist/collection/components/vega-rich-text-editor/extensions/functions/element-to-dto-strategies/div-to-function-content-strategy.js +3 -3
  186. package/dist/collection/components/vega-rich-text-editor/extensions/functions/element-to-dto-strategies/div-to-logic-control-block-strategy.js +4 -2
  187. package/dist/collection/components/vega-rich-text-editor/extensions/functions/function-extension.js +18 -0
  188. package/dist/collection/components/vega-rich-text-editor/extensions/functions/renderers/function-block-renderer.js +5 -2
  189. package/dist/collection/components/vega-rich-text-editor/extensions/functions/renderers/logic-control-block-renderer.js +2 -1
  190. package/dist/collection/components/vega-rich-text-editor/extensions/functions/utils.js +36 -0
  191. package/dist/collection/components/vega-rich-text-editor/extensions/languages/language-extension.js +1 -0
  192. package/dist/collection/components/vega-rich-text-editor/extensions/paste/action-handler-strategies/image-replace-src-strategy.js +21 -0
  193. package/dist/collection/components/vega-rich-text-editor/extensions/paste/actions/image-replace-src-action.js +13 -0
  194. package/dist/collection/components/vega-rich-text-editor/extensions/paste/parse-local-images-helper/paste-image-parser.js +295 -0
  195. package/dist/collection/components/vega-rich-text-editor/extensions/paste/parse-local-images-helper/pending-image-registry.js +116 -0
  196. package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-chooser-ui.js +13 -4
  197. package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-extension.js +70 -7
  198. package/dist/collection/components/vega-rich-text-editor/extensions/paste/strategies/default-paste-strategy.js +38 -0
  199. package/dist/collection/components/vega-rich-text-editor/extensions/paste/strategies/paste-processing-strategy.abstract.js +55 -0
  200. package/dist/collection/components/vega-rich-text-editor/extensions/paste/strategies/paste-strategy-selector.js +59 -0
  201. package/dist/collection/components/vega-rich-text-editor/extensions/paste/strategies/word-paste-strategy.js +416 -0
  202. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-code-block-transformer.js +106 -0
  203. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-image-transformer.js +198 -0
  204. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-list-transformer.js +591 -0
  205. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-paragraph-transformer.js +112 -0
  206. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-table-transformer.js +551 -0
  207. package/dist/collection/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-transformer.js +7 -0
  208. package/dist/collection/components/vega-rich-text-editor/extensions/table/dto/table-block.js +10 -0
  209. package/dist/collection/components/vega-rich-text-editor/extensions/tokens/span-to-token-node-strategy.js +4 -2
  210. package/dist/collection/components/vega-rich-text-editor/extensions/tokens/token-node.js +44 -0
  211. package/dist/collection/components/vega-rich-text-editor/helpers/utils.js +10 -6
  212. package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/extension-controller.js +2 -0
  213. package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/element-to-dto-strategy/annotation-handler/text-style-annotation-handler.js +7 -4
  214. package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/element-to-dto-strategy/continue-inline-to-rte-text-block-strategy.js +17 -0
  215. package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/insert-paste-handler.js +18 -4
  216. package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/selection-controller.js +21 -2
  217. package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/source-edit-toolbar-button-slimmer.js +7 -0
  218. package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/vega-rich-text-editor-source-view-renderer.js +5 -2
  219. package/dist/collection/components/vega-rich-text-editor/vega-rich-text-editor.js +2 -0
  220. package/dist/collection/components/vega-section-title/vega-section-title.js +2 -0
  221. package/dist/collection/components/vega-segment-control/vega-segment-control.js +2 -0
  222. package/dist/collection/components/vega-selection-chip/vega-selection-chip.js +2 -0
  223. package/dist/collection/components/vega-selection-tile/vega-selection-tile.js +2 -0
  224. package/dist/collection/components/vega-sidenav/vega-sidenav.js +2 -0
  225. package/dist/collection/components/vega-signature-capture/vega-signature-capture.js +2 -0
  226. package/dist/collection/components/vega-skeleton/vega-skeleton.js +2 -0
  227. package/dist/collection/components/vega-stepper/vega-stepper.js +2 -0
  228. package/dist/collection/components/vega-tab-group/slimmers/vega-tab-group/renderers/vega-tab-group-tab-list-renderer.js +14 -7
  229. package/dist/collection/components/vega-tab-group/vega-tab-group.css +6 -0
  230. package/dist/collection/components/vega-tab-group/vega-tab-group.js +7 -0
  231. package/dist/collection/components/vega-table/slimmers/controllers/vega-table-body-col-span-controller.js +31 -1
  232. package/dist/collection/components/vega-table/slimmers/controllers/vega-table-column-structure-model-controller.js +17 -7
  233. package/dist/collection/components/vega-table/slimmers/controllers/vega-table-head-template-structure-controller.js +440 -0
  234. package/dist/collection/components/vega-table/slimmers/controllers/vega-table-render-input-controller.js +11 -3
  235. package/dist/collection/components/vega-table/slimmers/renderers/vega-table-body-col-span-resolver.js +18 -11
  236. package/dist/collection/components/vega-table/slimmers/renderers/vega-table-pinned-renderer.js +207 -17
  237. package/dist/collection/components/vega-table/vega-table-foot/vega-table-foot.css +1 -0
  238. package/dist/collection/components/vega-table/vega-table-foot-cell/slimmers/renderers/vega-table-foot-cell-renderer.js +4 -1
  239. package/dist/collection/components/vega-table/vega-table-foot-cell/vega-table-foot-cell.css +15 -0
  240. package/dist/collection/components/vega-table/vega-table-foot-cell/vega-table-foot-cell.js +1 -1
  241. package/dist/collection/components/vega-table/vega-table-head-cell/slimmers/controllers/vega-table-head-cell-template-structure-controller.js +28 -0
  242. package/dist/collection/components/vega-table/vega-table-head-cell/vega-table-head-cell.css +1 -0
  243. package/dist/collection/components/vega-table/vega-table-head-cell/vega-table-head-cell.js +68 -4
  244. package/dist/collection/components/vega-table/vega-table.js +15 -0
  245. package/dist/collection/components/vega-textarea/vega-textarea.js +2 -0
  246. package/dist/collection/components/vega-time-picker/vega-time-picker.js +2 -0
  247. package/dist/collection/components/vega-toggle-switch/vega-toggle-switch.js +2 -0
  248. package/dist/collection/components/vega-tooltip/vega-tooltip.js +2 -0
  249. package/dist/collection/helpers/event-manager/event-id/vega-internal-event-id.js +1 -0
  250. package/dist/collection/helpers/translation/locales/en.js +2 -0
  251. package/dist/collection/utils/sanitize/index.js +3 -2
  252. package/dist/collection/utils/sanitize/strategies/index.js +1 -0
  253. package/dist/collection/utils/sanitize/strategies/label-sanitize-strategy.js +14 -0
  254. package/dist/collection/utils/sanitize/strategies/plain-text-sanitize-strategy.js +70 -0
  255. package/dist/esm/{action-color-accessibility-validator-ce55cb07.js → action-color-accessibility-validator-08877515.js} +2 -2
  256. package/dist/esm/{app-globals-d3991e53.js → app-globals-c59364c2.js} +10 -10
  257. package/dist/esm/{aria-dialog-focusable-trap-slimmer-9eae86c6.js → aria-dialog-focusable-trap-slimmer-f069f54d.js} +1 -1
  258. package/dist/esm/{code-block-8df86434.js → code-block-e6443949.js} +54 -15
  259. package/dist/esm/{component-global-style-slimmer-ac703085.js → component-global-style-slimmer-7d60a04a.js} +1 -1
  260. package/dist/esm/{component-value-history-controller-slimmer.abstract-e4b95997.js → component-value-history-controller-slimmer.abstract-ee393213.js} +25 -6
  261. package/dist/esm/constants-4725f97f.js +13 -0
  262. package/dist/esm/{content-state-c63fbeda.js → content-state-43cdacff.js} +61 -10
  263. package/dist/esm/{dark-mode-style-controller-ca5afd2d.js → dark-mode-style-controller-352f15ff.js} +2 -2
  264. package/dist/esm/{date-required-rule-85334d0c.js → date-required-rule-b5b67498.js} +1 -1
  265. package/dist/esm/{design-token-10ff79b5.js → design-token-8319bb59.js} +3 -3
  266. package/dist/esm/{element-appender-slimmer-24be8e94.js → element-appender-slimmer-c81d1fe3.js} +3 -3
  267. package/dist/esm/{form-field-controller-slimmer-d8628837.js → form-field-controller-slimmer-511b7ac5.js} +4 -4
  268. package/dist/esm/{image-annotation-action-fcceff95.js → image-annotation-action-d7cb80aa.js} +3 -3
  269. package/dist/esm/{image-extension-3280bdb0.js → image-extension-9b8abd0d.js} +2 -2
  270. package/dist/esm/{index-3b327f29.js → index-35451d12.js} +86 -1
  271. package/dist/esm/index.js +20 -20
  272. package/dist/esm/{inject-keyboard-manager-ea1aad5b.js → inject-keyboard-manager-dd368d56.js} +1 -1
  273. package/dist/esm/{internal-calendar-period-factory-7b294fb6.js → internal-calendar-period-factory-d58f35c6.js} +1 -1
  274. package/dist/esm/{internal-translation-controller-fac22598.js → internal-translation-controller-b33430c6.js} +2 -0
  275. package/dist/esm/{keyboard-manager-50da85d7.js → keyboard-manager-b4a0d97d.js} +1 -1
  276. package/dist/esm/{keyboard-manager-slimmer-1f7c8bfb.js → keyboard-manager-slimmer-0517fc70.js} +1 -1
  277. package/dist/esm/{link-extension-f92c06ef.js → link-extension-230bae8d.js} +26 -7
  278. package/dist/esm/loader.js +15 -15
  279. package/dist/esm/{month-view-generator-a1a9a4ec.js → month-view-generator-f470caac.js} +1 -1
  280. package/dist/esm/{public-rules-3695aaf7.js → public-rules-28ef32d3.js} +10 -10
  281. package/dist/esm/{range-717d552d.js → range-efad2578.js} +1 -1
  282. package/dist/esm/{responsive-format-facade-f19b601c.js → responsive-format-facade-dec87e46.js} +1 -1
  283. package/dist/esm/{rich-text-editor-required-rule-2e39264a.js → rich-text-editor-required-rule-a697e5bd.js} +1 -1
  284. package/dist/esm/{split-cell-operation-6c26af91.js → split-cell-operation-89b91a38.js} +14 -4
  285. package/dist/esm/{state-border-formatter-bfda33aa.js → state-border-formatter-1e900a2f.js} +1 -1
  286. package/dist/esm/{string-format-strategy.abstract-1f4ff1b3.js → string-format-strategy.abstract-d0ce3f65.js} +1 -1
  287. package/dist/esm/{string-input-formatter-slimmer-68b2af39.js → string-input-formatter-slimmer-6546cfc7.js} +3 -3
  288. package/dist/esm/{string-mask-strategy-bbbcdbef.js → string-mask-strategy-dd13ae21.js} +2 -2
  289. package/dist/esm/{style-formatter-e9adc8f4.js → style-formatter-057d7120.js} +2 -2
  290. package/dist/esm/{sub-state-notify-slimmer-0f72ce74.js → sub-state-notify-slimmer-23b7d0e8.js} +1 -1
  291. package/dist/esm/{sub-state-observer-slimmer-64532d94.js → sub-state-observer-slimmer-722f6b92.js} +1 -1
  292. package/dist/esm/{time-required-rule-1ce2d0a0.js → time-required-rule-73753a54.js} +1 -1
  293. package/dist/esm/{token-extension-5082a4d2.js → token-extension-74a0eeb1.js} +2919 -156
  294. package/dist/esm/{translation-slimmer-ee0f0e76.js → translation-slimmer-ef970ffc.js} +1 -1
  295. package/dist/esm/{type-guard-f2624334.js → type-guard-6fc96870.js} +1 -1
  296. package/dist/esm/{valid-credit-card-number-rule-60212b1d.js → valid-credit-card-number-rule-b54a31e7.js} +1 -1
  297. package/dist/esm/vega-accordion.entry.js +6 -6
  298. package/dist/esm/vega-app-footer.entry.js +2 -2
  299. package/dist/esm/vega-app-header-button.entry.js +5 -5
  300. package/dist/esm/vega-box.entry.js +7 -7
  301. package/dist/esm/vega-brand-logo.entry.js +2 -2
  302. package/dist/esm/vega-breadcrumb.entry.js +2 -2
  303. package/dist/esm/vega-button-circle.entry.js +7 -7
  304. package/dist/esm/vega-button-group_2.entry.js +3 -3
  305. package/dist/esm/vega-button-link.entry.js +1 -1
  306. package/dist/esm/vega-button.entry.js +5 -5
  307. package/dist/esm/vega-calendar_5.entry.js +24 -13
  308. package/dist/esm/vega-card.entry.js +5 -5
  309. package/dist/esm/vega-carousel.entry.js +3 -3
  310. package/dist/esm/vega-checkbox_2.entry.js +5 -5
  311. package/dist/esm/vega-chip.entry.js +5 -5
  312. package/dist/esm/vega-code-block.entry.js +16 -29
  313. package/dist/esm/vega-color-picker.entry.js +5 -5
  314. package/dist/esm/vega-color-swatch-picker.entry.js +6 -6
  315. package/dist/esm/vega-color-swatch.entry.js +2 -2
  316. package/dist/esm/vega-combo-box.entry.js +11 -11
  317. package/dist/esm/vega-counter-badge.entry.js +2 -2
  318. package/dist/esm/vega-date-picker_2.entry.js +18 -18
  319. package/dist/esm/vega-dialog_2.entry.js +7 -7
  320. package/dist/esm/vega-divider.entry.js +5 -5
  321. package/dist/esm/vega-dropdown_5.entry.js +42 -27
  322. package/dist/esm/vega-env-manager-8f8dc473.js +2 -2
  323. package/dist/esm/vega-field-label.entry.js +3 -3
  324. package/dist/esm/vega-file-uploader.entry.js +41 -8
  325. package/dist/esm/vega-flag-icon.entry.js +5 -5
  326. package/dist/esm/vega-flex.entry.js +6 -6
  327. package/dist/esm/vega-font.entry.js +5 -5
  328. package/dist/esm/vega-form.entry.js +5 -5
  329. package/dist/esm/vega-grid.entry.js +5 -5
  330. package/dist/esm/vega-icon.entry.js +6 -6
  331. package/dist/esm/vega-image-uploader.entry.js +9 -9
  332. package/dist/esm/vega-input-credit-card.entry.js +9 -9
  333. package/dist/esm/vega-input-numeric.entry.js +9 -9
  334. package/dist/esm/vega-input-passcode.entry.js +8 -8
  335. package/dist/esm/vega-input-phone-number.entry.js +7 -7
  336. package/dist/esm/vega-input-range.entry.js +6 -6
  337. package/dist/esm/vega-input-select.entry.js +8 -8
  338. package/dist/esm/vega-input.entry.js +17 -12
  339. package/dist/esm/{vega-internal-event-id-827eb1ce.js → vega-internal-event-id-86fce049.js} +2 -1
  340. package/dist/esm/vega-left-nav_5.entry.js +13 -13
  341. package/dist/esm/vega-loader-wrapper_2.entry.js +3 -3
  342. package/dist/esm/vega-pagination-page-selector-mobile.entry.js +1 -1
  343. package/dist/esm/vega-pagination-page-size-selector-mobile.entry.js +3 -3
  344. package/dist/esm/vega-pagination.entry.js +5 -5
  345. package/dist/esm/vega-popover_2.entry.js +10 -10
  346. package/dist/esm/vega-progress-tracker_2.entry.js +6 -6
  347. package/dist/esm/vega-radio_2.entry.js +8 -8
  348. package/dist/esm/vega-rich-text-content.entry.js +42 -14
  349. package/dist/esm/vega-rich-text-editor_4.entry.js +83 -26
  350. package/dist/esm/vega-rich-text-find-replace-panel.entry.js +1 -1
  351. package/dist/esm/vega-rich-text-special-characters-panel.entry.js +5 -5
  352. package/dist/esm/vega-rich-text-table-properties_3.entry.js +11 -11
  353. package/dist/esm/vega-selection-chip_2.entry.js +8 -8
  354. package/dist/esm/vega-selection-tile_2.entry.js +6 -6
  355. package/dist/esm/vega-sidenav_3.entry.js +6 -6
  356. package/dist/esm/vega-signature-capture.entry.js +10 -10
  357. package/dist/esm/vega-stepper.entry.js +5 -5
  358. package/dist/esm/vega-tab-group_2.entry.js +21 -8
  359. package/dist/esm/vega-table_11.entry.js +1194 -464
  360. package/dist/esm/vega-textarea.entry.js +5 -5
  361. package/dist/esm/vega-time-picker_2.entry.js +12 -12
  362. package/dist/esm/vega-toggle-switch.entry.js +5 -5
  363. package/dist/esm/vega-tooltip_2.entry.js +7 -7
  364. package/dist/esm/vega.js +15 -15
  365. package/dist/esm/{wait-for-component-did-render-0c237cb8.js → wait-for-component-did-render-ff7062aa.js} +1 -1
  366. package/dist/sri/vega-sri-manifest.json +371 -367
  367. package/dist/types/components/vega-accordion/vega-accordion.d.ts +2 -0
  368. package/dist/types/components/vega-app-footer/vega-app-footer.d.ts +2 -0
  369. package/dist/types/components/vega-app-header-button/vega-app-header-button.d.ts +2 -0
  370. package/dist/types/components/vega-banner/vega-banner.d.ts +2 -0
  371. package/dist/types/components/vega-box/vega-box.d.ts +2 -0
  372. package/dist/types/components/vega-brand-logo/vega-brand-logo.d.ts +2 -0
  373. package/dist/types/components/vega-breadcrumb/vega-breadcrumb.d.ts +2 -0
  374. package/dist/types/components/vega-button/vega-button.d.ts +2 -0
  375. package/dist/types/components/vega-button-circle/vega-button-circle.d.ts +2 -0
  376. package/dist/types/components/vega-button-group/vega-button-group.d.ts +2 -0
  377. package/dist/types/components/vega-button-link/vega-button-link.d.ts +2 -0
  378. package/dist/types/components/vega-calendar/vega-calendar-event-preview/slimmers/renderers/vega-calendar-event-preview-detail-renderer.d.ts +2 -0
  379. package/dist/types/components/vega-calendar/vega-calendar.d.ts +2 -0
  380. package/dist/types/components/vega-card/vega-card.d.ts +2 -0
  381. package/dist/types/components/vega-carousel/vega-carousel.d.ts +2 -0
  382. package/dist/types/components/vega-checkbox/vega-checkbox.d.ts +2 -0
  383. package/dist/types/components/vega-chip/vega-chip.d.ts +2 -0
  384. package/dist/types/components/vega-code-block/slimmers/controllers/code-highlight-parser.abstract.d.ts +2 -1
  385. package/dist/types/components/vega-color-picker/vega-color-picker.d.ts +2 -0
  386. package/dist/types/components/vega-combo-box/vega-combo-box.d.ts +2 -0
  387. package/dist/types/components/vega-counter-badge/vega-counter-badge.d.ts +2 -0
  388. package/dist/types/components/vega-date-picker/vega-date-picker.d.ts +2 -0
  389. package/dist/types/components/vega-divider/vega-divider.d.ts +2 -0
  390. package/dist/types/components/vega-dropdown/constants.d.ts +12 -0
  391. package/dist/types/components/vega-dropdown/vega-dropdown-item/slimmers/renderers/vega-dropdown-item-renderer.d.ts +1 -0
  392. package/dist/types/components/vega-dropdown/vega-dropdown.d.ts +9 -0
  393. package/dist/types/components/vega-field-error/vega-field-error.d.ts +2 -0
  394. package/dist/types/components/vega-file-uploader/slimmers/renderers/vega-file-uploader-file-list-renderer.d.ts +10 -0
  395. package/dist/types/components/vega-file-uploader/types.d.ts +4 -0
  396. package/dist/types/components/vega-file-uploader/vega-file-uploader.d.ts +15 -0
  397. package/dist/types/components/vega-flag-icon/vega-flag-icon.d.ts +2 -0
  398. package/dist/types/components/vega-flex/vega-flex.d.ts +2 -0
  399. package/dist/types/components/vega-font/vega-font.d.ts +2 -0
  400. package/dist/types/components/vega-form/vega-form.d.ts +2 -0
  401. package/dist/types/components/vega-grid/vega-grid.d.ts +2 -0
  402. package/dist/types/components/vega-icon/vega-icon.d.ts +2 -0
  403. package/dist/types/components/vega-image-uploader/vega-image-uploader.d.ts +2 -0
  404. package/dist/types/components/vega-input/types.d.ts +11 -1
  405. package/dist/types/components/vega-input/vega-input.d.ts +2 -0
  406. package/dist/types/components/vega-input-credit-card/vega-input-credit-card.d.ts +2 -0
  407. package/dist/types/components/vega-input-numeric/vega-input-numeric.d.ts +2 -0
  408. package/dist/types/components/vega-input-passcode/vega-input-passcode.d.ts +2 -0
  409. package/dist/types/components/vega-input-phone-number/vega-input-phone-number.d.ts +2 -0
  410. package/dist/types/components/vega-input-range/vega-input-range.d.ts +2 -0
  411. package/dist/types/components/vega-input-select/vega-input-select.d.ts +2 -0
  412. package/dist/types/components/vega-item-toggle/vega-item-toggle.d.ts +2 -0
  413. package/dist/types/components/vega-loading-indicator/vega-loading-indicator.d.ts +2 -0
  414. package/dist/types/components/vega-modal/vega-modal.d.ts +2 -0
  415. package/dist/types/components/vega-page-notification/vega-page-notification.d.ts +2 -0
  416. package/dist/types/components/vega-pagination/vega-pagination.d.ts +2 -0
  417. package/dist/types/components/vega-popover/vega-popover.d.ts +2 -0
  418. package/dist/types/components/vega-progress-tracker/vega-progress-tracker.d.ts +2 -0
  419. package/dist/types/components/vega-radio/vega-radio.d.ts +2 -0
  420. package/dist/types/components/vega-rich-text-editor/constants/constant.d.ts +16 -0
  421. package/dist/types/components/vega-rich-text-editor/dto/annotations/text-style-default-styles.d.ts +1 -0
  422. package/dist/types/components/vega-rich-text-editor/dto/blocks/block.abstract.d.ts +9 -0
  423. package/dist/types/components/vega-rich-text-editor/dto/filter-styles-strategies/filter-styles-strategy-registry.d.ts +1 -0
  424. package/dist/types/components/vega-rich-text-editor/dto/filter-styles-strategies/filter-styles-strategy.abstract.d.ts +3 -2
  425. package/dist/types/components/vega-rich-text-editor/dto/filter-styles-strategies/text-node/title-heading-node-filter-styles-strategy.d.ts +25 -3
  426. package/dist/types/components/vega-rich-text-editor/dto/renderers/blocks/block-text-nodes-renderer.abstract.d.ts +7 -0
  427. package/dist/types/components/vega-rich-text-editor/dto/renderers/nodes/text-node-renderer.d.ts +11 -0
  428. package/dist/types/components/vega-rich-text-editor/extensions/block-boundary-registry.d.ts +21 -0
  429. package/dist/types/components/vega-rich-text-editor/extensions/extension.abstract.d.ts +15 -0
  430. package/dist/types/components/vega-rich-text-editor/extensions/functions/constants.d.ts +4 -0
  431. package/dist/types/components/vega-rich-text-editor/extensions/functions/dto/function-block.d.ts +7 -0
  432. package/dist/types/components/vega-rich-text-editor/extensions/functions/element-to-dto-strategies/div-containing-function-block-strategy.d.ts +8 -0
  433. package/dist/types/components/vega-rich-text-editor/extensions/functions/function-extension.d.ts +6 -0
  434. package/dist/types/components/vega-rich-text-editor/extensions/functions/utils.d.ts +29 -0
  435. package/dist/types/components/vega-rich-text-editor/extensions/paste/action-handler-strategies/image-replace-src-strategy.d.ts +12 -0
  436. package/dist/types/components/vega-rich-text-editor/extensions/paste/actions/image-replace-src-action.d.ts +11 -0
  437. package/dist/types/components/vega-rich-text-editor/extensions/paste/parse-local-images-helper/paste-image-parser.d.ts +136 -0
  438. package/dist/types/components/vega-rich-text-editor/extensions/paste/parse-local-images-helper/pending-image-registry.d.ts +68 -0
  439. package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-chooser-ui.d.ts +9 -4
  440. package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-extension.d.ts +39 -2
  441. package/dist/types/components/vega-rich-text-editor/extensions/paste/strategies/default-paste-strategy.d.ts +29 -0
  442. package/dist/types/components/vega-rich-text-editor/extensions/paste/strategies/paste-processing-strategy.abstract.d.ts +61 -0
  443. package/dist/types/components/vega-rich-text-editor/extensions/paste/strategies/paste-strategy-selector.d.ts +45 -0
  444. package/dist/types/components/vega-rich-text-editor/extensions/paste/strategies/word-paste-strategy.d.ts +186 -0
  445. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-code-block-transformer.d.ts +51 -0
  446. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-image-transformer.d.ts +86 -0
  447. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-list-transformer.d.ts +271 -0
  448. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-paragraph-transformer.d.ts +25 -0
  449. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-table-transformer.d.ts +240 -0
  450. package/dist/types/components/vega-rich-text-editor/extensions/paste/word-paste-transformers/word-structure-transformer.d.ts +36 -0
  451. package/dist/types/components/vega-rich-text-editor/extensions/table/dto/table-block.d.ts +8 -0
  452. package/dist/types/components/vega-rich-text-editor/extensions/tokens/span-to-token-node-strategy.d.ts +2 -2
  453. package/dist/types/components/vega-rich-text-editor/extensions/tokens/token-node.d.ts +19 -1
  454. package/dist/types/components/vega-rich-text-editor/helpers/utils.d.ts +7 -0
  455. package/dist/types/components/vega-rich-text-editor/interface.d.ts +10 -0
  456. package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/element-to-dto-strategy/annotation-handler/text-style-annotation-handler.d.ts +4 -1
  457. package/dist/types/components/vega-rich-text-editor/slimmers/controllers/helper/element-to-dto-strategy/continue-inline-to-rte-text-block-strategy.d.ts +8 -0
  458. package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/source-edit-toolbar-button-slimmer.d.ts +1 -0
  459. package/dist/types/components/vega-rich-text-editor/vega-rich-text-editor.d.ts +2 -0
  460. package/dist/types/components/vega-section-title/vega-section-title.d.ts +2 -0
  461. package/dist/types/components/vega-segment-control/vega-segment-control.d.ts +2 -0
  462. package/dist/types/components/vega-selection-chip/vega-selection-chip.d.ts +2 -0
  463. package/dist/types/components/vega-selection-tile/vega-selection-tile.d.ts +2 -0
  464. package/dist/types/components/vega-sidenav/vega-sidenav.d.ts +2 -0
  465. package/dist/types/components/vega-signature-capture/vega-signature-capture.d.ts +2 -0
  466. package/dist/types/components/vega-skeleton/vega-skeleton.d.ts +2 -0
  467. package/dist/types/components/vega-stepper/vega-stepper.d.ts +2 -0
  468. package/dist/types/components/vega-tab-group/slimmers/vega-tab-group/renderers/vega-tab-group-tab-list-renderer.d.ts +1 -0
  469. package/dist/types/components/vega-tab-group/types.d.ts +3 -1
  470. package/dist/types/components/vega-tab-group/vega-tab-group.d.ts +4 -0
  471. package/dist/types/components/vega-table/interface.d.ts +4 -4
  472. package/dist/types/components/vega-table/slimmers/controllers/vega-table-body-col-span-controller.d.ts +16 -0
  473. package/dist/types/components/vega-table/slimmers/controllers/vega-table-column-structure-model-controller.d.ts +8 -4
  474. package/dist/types/components/vega-table/slimmers/controllers/vega-table-head-template-structure-controller.d.ts +168 -0
  475. package/dist/types/components/vega-table/slimmers/controllers/vega-table-render-input-controller.d.ts +8 -1
  476. package/dist/types/components/vega-table/slimmers/renderers/vega-table-body-col-span-resolver.d.ts +8 -10
  477. package/dist/types/components/vega-table/slimmers/renderers/vega-table-pinned-renderer.d.ts +76 -0
  478. package/dist/types/components/vega-table/vega-table-foot-cell/slimmers/renderers/vega-table-foot-cell-renderer.d.ts +1 -0
  479. package/dist/types/components/vega-table/vega-table-head-cell/slimmers/controllers/vega-table-head-cell-template-structure-controller.d.ts +12 -0
  480. package/dist/types/components/vega-table/vega-table-head-cell/vega-table-head-cell.d.ts +19 -0
  481. package/dist/types/components/vega-table/vega-table.d.ts +6 -0
  482. package/dist/types/components/vega-textarea/vega-textarea.d.ts +2 -0
  483. package/dist/types/components/vega-time-picker/vega-time-picker.d.ts +2 -0
  484. package/dist/types/components/vega-toggle-switch/vega-toggle-switch.d.ts +2 -0
  485. package/dist/types/components/vega-tooltip/vega-tooltip.d.ts +2 -0
  486. package/dist/types/components.d.ts +54 -0
  487. package/dist/types/helpers/event-manager/event-id/vega-internal-event-id.d.ts +1 -0
  488. package/dist/types/helpers/translation/interface.d.ts +3 -1
  489. package/dist/types/types/public-api.d.ts +1 -0
  490. package/dist/types/utils/sanitize/index.d.ts +3 -2
  491. package/dist/types/utils/sanitize/strategies/index.d.ts +1 -0
  492. package/dist/types/utils/sanitize/strategies/label-sanitize-strategy.d.ts +9 -0
  493. package/dist/types/utils/sanitize/strategies/plain-text-sanitize-strategy.d.ts +36 -0
  494. package/dist/vega/index.esm.js +1 -1
  495. package/dist/vega/{p-36c89520.entry.js → p-024db952.entry.js} +1 -1
  496. package/dist/vega/p-0b2c4c0b.js +1 -0
  497. package/dist/vega/{p-c2277448.entry.js → p-0ca3b4bf.entry.js} +1 -1
  498. package/dist/vega/{p-363ee8a5.entry.js → p-1a37d63c.entry.js} +1 -1
  499. package/dist/vega/{p-b622c455.js → p-1fa22fda.js} +1 -1
  500. package/dist/vega/{p-d609c3fd.js → p-2044eba9.js} +1 -1
  501. package/dist/vega/{p-e1029acc.js → p-22edd3cc.js} +1 -1
  502. package/dist/vega/{p-5f96e741.entry.js → p-23c1f378.entry.js} +1 -1
  503. package/dist/vega/{p-6c95a6a3.js → p-2529820e.js} +1 -1
  504. package/dist/vega/{p-8d96f0e8.entry.js → p-2af1d8b8.entry.js} +1 -1
  505. package/dist/vega/{p-4c3a08be.entry.js → p-2f89ee14.entry.js} +1 -1
  506. package/dist/vega/{p-2bb5a232.js → p-3091b105.js} +1 -1
  507. package/dist/vega/{p-3af5e87b.entry.js → p-328ebbdb.entry.js} +1 -1
  508. package/dist/vega/p-35628af4.js +1 -0
  509. package/dist/vega/{p-9abf7d0a.entry.js → p-35d37477.entry.js} +1 -1
  510. package/dist/vega/{p-26760c66.entry.js → p-3a230d4b.entry.js} +1 -1
  511. package/dist/vega/{p-040ea356.entry.js → p-3c152bad.entry.js} +1 -1
  512. package/dist/vega/p-3c559c8b.js +1 -0
  513. package/dist/vega/{p-e3fdb65c.entry.js → p-3c8421d5.entry.js} +1 -1
  514. package/dist/vega/{p-b594a11c.entry.js → p-4022412e.entry.js} +1 -1
  515. package/dist/vega/{p-046203cb.entry.js → p-433b1395.entry.js} +1 -1
  516. package/dist/vega/{p-b98ead05.entry.js → p-4370b954.entry.js} +1 -1
  517. package/dist/vega/p-444e24c6.js +1 -0
  518. package/dist/vega/p-4473d8b1.js +3 -0
  519. package/dist/vega/{p-a484e32d.entry.js → p-44999c88.entry.js} +1 -1
  520. package/dist/vega/{p-f29ea345.entry.js → p-47a48329.entry.js} +1 -1
  521. package/dist/vega/{p-0cf3b5c1.entry.js → p-49d7cfed.entry.js} +1 -1
  522. package/dist/vega/p-4a2fe3a4.js +1 -0
  523. package/dist/vega/{p-92528dc9.entry.js → p-4afd6715.entry.js} +1 -1
  524. package/dist/vega/{p-461100b8.entry.js → p-4f6aaac5.entry.js} +1 -1
  525. package/dist/vega/p-50eda925.js +1 -0
  526. package/dist/vega/{p-ae2abdfa.js → p-5110c84f.js} +1 -1
  527. package/dist/vega/{p-deaf037a.entry.js → p-5147c30d.entry.js} +1 -1
  528. package/dist/vega/{p-874e5624.js → p-52878ea9.js} +1 -1
  529. package/dist/vega/{p-b00ec194.entry.js → p-55dc3053.entry.js} +1 -1
  530. package/dist/vega/p-5af312f8.js +1 -0
  531. package/dist/vega/{p-3e09aeed.js → p-5b3c8a56.js} +1 -1
  532. package/dist/vega/{p-2acf9b42.entry.js → p-5e9e04f3.entry.js} +1 -1
  533. package/dist/vega/p-5f377954.js +1 -1
  534. package/dist/vega/p-6493f13a.entry.js +1 -0
  535. package/dist/vega/{p-8611465f.entry.js → p-6659c910.entry.js} +1 -1
  536. package/dist/vega/{p-9c4c474e.entry.js → p-66728dd4.entry.js} +1 -1
  537. package/dist/vega/{p-21f608e8.entry.js → p-66a785d0.entry.js} +1 -1
  538. package/dist/vega/{p-0499e65a.entry.js → p-66fe8b4e.entry.js} +1 -1
  539. package/dist/vega/p-67e88cd7.entry.js +1 -0
  540. package/dist/vega/p-67eaf77e.js +1 -0
  541. package/dist/vega/{p-456f678b.js → p-691bdc20.js} +1 -1
  542. package/dist/vega/{p-1132a74e.entry.js → p-6d6b4495.entry.js} +1 -1
  543. package/dist/vega/p-6db1b9ac.js +1 -0
  544. package/dist/vega/p-7754b10f.entry.js +1 -0
  545. package/dist/vega/{p-d2cbcf5e.entry.js → p-77fb3971.entry.js} +1 -1
  546. package/dist/vega/{p-e7e963ba.entry.js → p-7977352b.entry.js} +1 -1
  547. package/dist/vega/{p-a91927eb.entry.js → p-7b133734.entry.js} +1 -1
  548. package/dist/vega/p-7b84a943.entry.js +1 -0
  549. package/dist/vega/{p-a2bd621e.js → p-83ad8988.js} +1 -1
  550. package/dist/vega/{p-2eb8e167.entry.js → p-840444c9.entry.js} +1 -1
  551. package/dist/vega/{p-703ac992.entry.js → p-860e76f5.entry.js} +1 -1
  552. package/dist/vega/p-867cc602.entry.js +1 -0
  553. package/dist/vega/p-8d2a9012.js +1 -0
  554. package/dist/vega/{p-5e37b041.js → p-8d4be607.js} +1 -1
  555. package/dist/vega/p-8d77c7cf.js +1 -0
  556. package/dist/vega/{p-c29934ef.js → p-8f3b93db.js} +1 -1
  557. package/dist/vega/{p-56417ec5.entry.js → p-944e284d.entry.js} +1 -1
  558. package/dist/vega/{p-0809c651.js → p-94b54a95.js} +1 -1
  559. package/dist/vega/p-9c2b3279.js +1 -0
  560. package/dist/vega/{p-aeb50cad.entry.js → p-9c5289dc.entry.js} +1 -1
  561. package/dist/vega/{p-d25c1a48.js → p-9c9b0629.js} +1 -1
  562. package/dist/vega/{p-abbceebf.entry.js → p-9c9ff871.entry.js} +1 -1
  563. package/dist/vega/{p-a034dfc0.entry.js → p-9d90e271.entry.js} +1 -1
  564. package/dist/vega/{p-d319ebb5.js → p-9e9f49f4.js} +1 -1
  565. package/dist/vega/{p-dcd3e3a7.js → p-a1f220ca.js} +1 -1
  566. package/dist/vega/{p-3488a3f1.entry.js → p-a4360b7e.entry.js} +1 -1
  567. package/dist/vega/{p-2540fd1c.entry.js → p-a7517760.entry.js} +1 -1
  568. package/dist/vega/{p-8ed3b2b6.entry.js → p-aa433430.entry.js} +1 -1
  569. package/dist/vega/{p-c3646101.entry.js → p-aa914dbd.entry.js} +1 -1
  570. package/dist/vega/{p-72ab5cd5.entry.js → p-b12de1bf.entry.js} +1 -1
  571. package/dist/vega/p-b21cc390.entry.js +1 -0
  572. package/dist/vega/{p-dbb4b1d1.js → p-b3c4943b.js} +1 -1
  573. package/dist/vega/{p-98fc5821.entry.js → p-ba6881da.entry.js} +1 -1
  574. package/dist/vega/p-bd7725f7.entry.js +1 -0
  575. package/dist/vega/{p-dad7d2ac.js → p-c348eaf5.js} +1 -1
  576. package/dist/vega/{p-29e3eb12.js → p-c41a2d4a.js} +1 -1
  577. package/dist/vega/{p-ccbe3f1b.entry.js → p-c8ad7207.entry.js} +1 -1
  578. package/dist/vega/{p-b3733762.entry.js → p-c8fb3aac.entry.js} +1 -1
  579. package/dist/vega/p-cc569b54.js +1 -0
  580. package/dist/vega/{p-53fd3c41.entry.js → p-ccbed811.entry.js} +1 -1
  581. package/dist/vega/{p-8ff92914.entry.js → p-ce82d6bb.entry.js} +1 -1
  582. package/dist/vega/{p-f1078e63.js → p-cede3535.js} +1 -1
  583. package/dist/vega/{p-775ecfad.entry.js → p-cf4b3d40.entry.js} +1 -1
  584. package/dist/vega/p-d35a31a5.entry.js +1 -0
  585. package/dist/vega/{p-ac94ba1b.entry.js → p-d4d90ce2.entry.js} +1 -1
  586. package/dist/vega/{p-f5095035.entry.js → p-d6023dc0.entry.js} +1 -1
  587. package/dist/vega/{p-a8174ad8.entry.js → p-d62e618a.entry.js} +1 -1
  588. package/dist/vega/{p-3f183b52.js → p-daba4af1.js} +1 -1
  589. package/dist/vega/{p-1ea9ad9a.js → p-dd0bd78b.js} +1 -1
  590. package/dist/vega/{p-36d2df7a.entry.js → p-ded1ecc1.entry.js} +1 -1
  591. package/dist/vega/p-df63c2fb.entry.js +1 -0
  592. package/dist/vega/{p-d3413a78.entry.js → p-e1a65e06.entry.js} +1 -1
  593. package/dist/vega/{p-39c5054b.js → p-e1c82ca9.js} +1 -1
  594. package/dist/vega/{p-6b76b572.entry.js → p-e469ba27.entry.js} +1 -1
  595. package/dist/vega/{p-e9d09211.js → p-e4c5cb4f.js} +1 -1
  596. package/dist/vega/{p-ec64cc59.entry.js → p-f0a0fc77.entry.js} +1 -1
  597. package/dist/vega/{p-12e8a1b2.js → p-f13b39d8.js} +1 -1
  598. package/dist/vega/{p-21626b93.entry.js → p-f2384411.entry.js} +1 -1
  599. package/dist/vega/{p-09524fec.js → p-f9a3805d.js} +1 -1
  600. package/dist/vega/{p-1e0a8fd3.js → p-fa5cd8b1.js} +1 -1
  601. package/dist/vega/{p-56d19601.entry.js → p-fb9475ab.entry.js} +1 -1
  602. package/dist/vega/{p-2c2e8615.js → p-fcb6b499.js} +1 -1
  603. package/dist/vega/vega.esm.js +1 -1
  604. package/package.json +1 -1
  605. package/dist/collection/components/vega-rich-text-editor/extensions/paste/paste-constants.js +0 -6
  606. package/dist/types/components/vega-rich-text-editor/extensions/paste/paste-constants.d.ts +0 -6
  607. package/dist/vega/p-10d43a80.entry.js +0 -1
  608. package/dist/vega/p-1250b1ab.entry.js +0 -1
  609. package/dist/vega/p-1d5e05d9.js +0 -3
  610. package/dist/vega/p-2bb60f89.js +0 -1
  611. package/dist/vega/p-2d158cbd.js +0 -1
  612. package/dist/vega/p-2d7388c9.entry.js +0 -1
  613. package/dist/vega/p-2ff4ae1c.js +0 -1
  614. package/dist/vega/p-3980dfa8.entry.js +0 -1
  615. package/dist/vega/p-3a68b729.js +0 -1
  616. package/dist/vega/p-781b7a54.js +0 -1
  617. package/dist/vega/p-81d4dfcb.entry.js +0 -1
  618. package/dist/vega/p-88a6c89a.js +0 -1
  619. package/dist/vega/p-8e7932bc.js +0 -1
  620. package/dist/vega/p-9664e914.entry.js +0 -1
  621. package/dist/vega/p-9be02fa5.js +0 -1
  622. package/dist/vega/p-ca270da3.entry.js +0 -1
  623. package/dist/vega/p-cd69aeae.js +0 -1
  624. package/dist/vega/p-de8f0d98.js +0 -1
  625. package/dist/vega/p-e9b005df.js +0 -1
  626. package/dist/vega/p-f0e828d8.js +0 -1
  627. package/dist/vega/p-f95d8d10.entry.js +0 -1
  628. package/dist/vega/p-fa65956c.entry.js +0 -1
@@ -8,27 +8,27 @@ const internalVegaZIndexManager = require('./internal-vega-z-index-manager-f03dd
8
8
  const dynamicSlimmer = require('./dynamic-slimmer-f31fdfd7.js');
9
9
  const pageResizeObserverSlimmer = require('./page-resize-observer-slimmer-273d62ef.js');
10
10
  const internalVegaEventManager = require('./internal-vega-event-manager-7003c84a.js');
11
- const internalTranslationController = require('./internal-translation-controller-1dac2df8.js');
12
- const internalCalendarPeriodFactory = require('./internal-calendar-period-factory-dfcdd3f6.js');
13
- const linkExtension = require('./link-extension-71c27ff3.js');
14
- const codeBlock = require('./code-block-28a7732e.js');
15
- const contentState = require('./content-state-e2ab0f06.js');
16
- const typeGuard = require('./type-guard-a57ef3ac.js');
11
+ const internalTranslationController = require('./internal-translation-controller-51f7f865.js');
12
+ const internalCalendarPeriodFactory = require('./internal-calendar-period-factory-6196bc80.js');
13
+ const codeBlock = require('./code-block-ea1f5eb3.js');
14
+ const contentState = require('./content-state-8d07d530.js');
15
+ const typeGuard = require('./type-guard-b8d7bb3f.js');
16
+ const linkExtension = require('./link-extension-11340e1c.js');
17
17
  const internalIconManager = require('./internal-icon-manager-40bcac72.js');
18
18
  const vegaNonceManager = require('./vega-nonce-manager-df69d3ed.js');
19
19
  const domNodeSubjectObserverFactory = require('./dom-node-subject-observer-factory-c82df135.js');
20
20
  const vegaEventId = require('./vega-event-id-58afed7a.js');
21
- const splitCellOperation = require('./split-cell-operation-51097409.js');
21
+ const splitCellOperation = require('./split-cell-operation-11bfb305.js');
22
22
  const changeManager = require('./change-manager-a297e4d2.js');
23
- const vegaInternalEventId = require('./vega-internal-event-id-ecc5b87a.js');
23
+ const vegaInternalEventId = require('./vega-internal-event-id-f4d33037.js');
24
24
  const remoteInvocationRegistry = require('./remote-invocation-registry-fc22fdd0.js');
25
- const darkModeStyleController = require('./dark-mode-style-controller-32d4054a.js');
25
+ const darkModeStyleController = require('./dark-mode-style-controller-f3da3342.js');
26
26
  const tryGetDocument = require('./try-get-document-c0ebd39a.js');
27
27
  const pixel = require('./pixel-98f2a10c.js');
28
28
  const object = require('./object-b53e9416.js');
29
29
  const timer = require('./timer-5f33058b.js');
30
30
  const string = require('./string-39438062.js');
31
- const imageExtension = require('./image-extension-1e3eeaa3.js');
31
+ const imageExtension = require('./image-extension-9659708b.js');
32
32
  const copy = require('./copy-d021c780.js');
33
33
 
34
34
  const VegaLoaderRuntimeMetricsPayloadDefinition = [
@@ -1105,7 +1105,7 @@ class VegaRTEToolbarButtonRenderer {
1105
1105
  * @returns {Nullable<RTEExtensionContext>} - The extension context associated with the host.
1106
1106
  */
1107
1107
  getExtensionContext(el) {
1108
- return linkExtension.RTEExtensionContextManager.getExtensionContext(el);
1108
+ return linkExtension.extensionContextManager.getExtensionContext(el);
1109
1109
  }
1110
1110
  /**
1111
1111
  * Renders a dropdown component with the specified properties and children.
@@ -1544,6 +1544,47 @@ class RTETokenNode extends codeBlock.RTETextNode {
1544
1544
  toHtml() {
1545
1545
  return `<span class="token" contenteditable="false" template-token-key="${this.token}" token-key="${this.token}"${super.generateAttributeString()}>${this.text}</span>`;
1546
1546
  }
1547
+ /**
1548
+ * @inheritDoc
1549
+ */
1550
+ renderHtmlFromDepth(depth, options) {
1551
+ const annotation = this.getInlineHtmlAnnotationAtDepth(depth);
1552
+ if (!annotation)
1553
+ return this.toHtml();
1554
+ return this.renderTokenWithWrapperStr(annotation);
1555
+ }
1556
+ /**
1557
+ * Render token markup wrapped by inline HTML annotations, preserving token attributes.
1558
+ *
1559
+ * @param {InlineHtmlAnnotation} inlineHtmlNode - Current wrapper annotation.
1560
+ * @returns {string} Token HTML wrapped by inline HTML annotations.
1561
+ */
1562
+ renderTokenWithWrapperStr(inlineHtmlNode) {
1563
+ const { htmlTag, child } = inlineHtmlNode;
1564
+ const attrStr = this.inlineHtmlAnnotationAttrStr(inlineHtmlNode);
1565
+ if (!child) {
1566
+ if (codeBlock.HTML_VOID_TAGS.has(htmlTag)) {
1567
+ return `<${htmlTag}${attrStr}>`;
1568
+ }
1569
+ return `<${htmlTag}${attrStr}>${this.toHtml()}</${htmlTag}>`;
1570
+ }
1571
+ return `<${htmlTag}${attrStr}>${this.renderTokenWithWrapperStr(child)}</${htmlTag}>`;
1572
+ }
1573
+ /**
1574
+ * Build inline HTML wrapper attributes string.
1575
+ *
1576
+ * @param {InlineHtmlAnnotation} annotation - Inline HTML annotation.
1577
+ * @returns {string} Serialized attribute string.
1578
+ */
1579
+ inlineHtmlAnnotationAttrStr(annotation) {
1580
+ const { customAttribute, customClass, customStyle } = annotation;
1581
+ const attrs = [
1582
+ ...((customAttribute === null || customAttribute === void 0 ? void 0 : customAttribute.toString()) || []),
1583
+ customClass === null || customClass === void 0 ? void 0 : customClass.toString(),
1584
+ customStyle === null || customStyle === void 0 ? void 0 : customStyle.toString(),
1585
+ ].filter(typeGuard.isNonNullable);
1586
+ return attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
1587
+ }
1547
1588
  }
1548
1589
  RTETokenNode.dtoName = 'RTETokenNode';
1549
1590
 
@@ -1855,6 +1896,7 @@ class VegaRTELanguageExtension extends linkExtension.VegaRTEExtension {
1855
1896
  options = {
1856
1897
  autoMatchFormat: (_a = host.sourceEditConfig.autoMatchFormat) !== null && _a !== void 0 ? _a : true,
1857
1898
  filterInlineStyles: host.sourceEditConfig.filterInlineStyles,
1899
+ blockBoundaryRegistry: this.extensionContext.blockBoundaryRegistry,
1858
1900
  };
1859
1901
  }
1860
1902
  result = contentState.VegaRTEContent.fromHtml(codeBlock.value.code, options);
@@ -1880,6 +1922,15 @@ class InsertFunctionBlockAction extends codeBlock.ModifyContentAction {
1880
1922
  }
1881
1923
  }
1882
1924
 
1925
+ const FUNCTION_BLOCK_CLASS = 'statement';
1926
+ const FUNCTION_CONTENT_CLASS = 'statementelement';
1927
+ const FUNCTION_SYNTAX_CLASS = 'statementsyntax';
1928
+ const FUNCTION_ELEMENT_CLASSES = [
1929
+ FUNCTION_BLOCK_CLASS,
1930
+ FUNCTION_CONTENT_CLASS,
1931
+ FUNCTION_SYNTAX_CLASS,
1932
+ ];
1933
+
1883
1934
  /**
1884
1935
  * RTE Function Block
1885
1936
  */
@@ -1899,7 +1950,7 @@ class RTEFunctionBlock extends codeBlock.RTEBlock {
1899
1950
  */
1900
1951
  static from(block, options) {
1901
1952
  const functionBlock = new RTEFunctionBlock(block.id);
1902
- const defaultAnnotation = { customClass: ['statement'] };
1953
+ const defaultAnnotation = { customClass: [FUNCTION_BLOCK_CLASS] };
1903
1954
  let { annotations } = block;
1904
1955
  annotations = annotations
1905
1956
  ? Object.assign(Object.assign({}, annotations), defaultAnnotation) : defaultAnnotation;
@@ -1917,14 +1968,14 @@ class RTEFunctionBlock extends codeBlock.RTEBlock {
1917
1968
  .map((child) => {
1918
1969
  if (child instanceof codeBlock.RTETextBlock) {
1919
1970
  const attrStr = this.getFunctionContentStyle(child);
1920
- return `<div class="statementelement"${attrStr}>${child.getChildrenHtml()}</div>`;
1971
+ return `<div class="${FUNCTION_CONTENT_CLASS}"${attrStr}>${child.getChildrenHtml()}</div>`;
1921
1972
  }
1922
1973
  else {
1923
1974
  return child.toHtml();
1924
1975
  }
1925
1976
  })
1926
1977
  .join('');
1927
- return `<div class="statement">${childrenHtml}</div>`;
1978
+ return `<div class="${FUNCTION_BLOCK_CLASS}" ${codeBlock.RTE_BLOCK_BOUNDARY_ATTRIBUTE}>${childrenHtml}</div>`;
1928
1979
  }
1929
1980
  /**
1930
1981
  * @inheritDoc
@@ -1945,6 +1996,15 @@ class RTEFunctionBlock extends codeBlock.RTEBlock {
1945
1996
  getLastNode() {
1946
1997
  return this.children[this.children.length - 1].getLastNode();
1947
1998
  }
1999
+ /**
2000
+ * The function block is an atomic caret boundary: descending would resolve the caret onto a
2001
+ * `contenteditable="false"` logic-control node instead of staying anchored to the block.
2002
+ *
2003
+ * @inheritDoc
2004
+ */
2005
+ allowsCaretDescend() {
2006
+ return false;
2007
+ }
1948
2008
  /**
1949
2009
  * @inheritDoc
1950
2010
  */
@@ -2018,7 +2078,7 @@ class InsertFunctionBlockStrategy extends codeBlock.ActionHandleStrategy {
2018
2078
  {
2019
2079
  id: `${id}-2`,
2020
2080
  annotations: {
2021
- customClass: ['statementelement'],
2081
+ customClass: [FUNCTION_CONTENT_CLASS],
2022
2082
  },
2023
2083
  type: 'paragraph',
2024
2084
  nodes: [
@@ -2037,7 +2097,7 @@ class InsertFunctionBlockStrategy extends codeBlock.ActionHandleStrategy {
2037
2097
  {
2038
2098
  id: `${id}-4`,
2039
2099
  annotations: {
2040
- customClass: ['statementelement'],
2100
+ customClass: [FUNCTION_CONTENT_CLASS],
2041
2101
  },
2042
2102
  type: 'paragraph',
2043
2103
  nodes: [
@@ -2188,7 +2248,7 @@ class RTELogicControlBlock extends codeBlock.RTEBlock {
2188
2248
  * @returns {string} - The HTML string of the logic control block.
2189
2249
  */
2190
2250
  toHtml() {
2191
- return `<div class='statementsyntax' contenteditable='false'>${this.statementText}</div>`;
2251
+ return `<div class='${FUNCTION_SYNTAX_CLASS}' contenteditable='false'>${this.statementText}</div>`;
2192
2252
  }
2193
2253
  /**
2194
2254
  * Clone the current block instance.
@@ -2296,7 +2356,7 @@ class RTELogicControlBlockRenderer extends linkExtension.VegaRTEExtensionRendere
2296
2356
  doRender(h, renderData) {
2297
2357
  return h('div', {
2298
2358
  key: renderData.id,
2299
- class: 'statementsyntax',
2359
+ class: FUNCTION_SYNTAX_CLASS,
2300
2360
  ref: this.useRef(renderData),
2301
2361
  }, h('span', {
2302
2362
  key: `${renderData.id}-1`,
@@ -2306,13 +2366,48 @@ class RTELogicControlBlockRenderer extends linkExtension.VegaRTEExtensionRendere
2306
2366
  }
2307
2367
  }
2308
2368
 
2369
+ /**
2370
+ * Returns true when the element belongs to function extension blocks by semantic
2371
+ * boundary attribute or known function element classes.
2372
+ *
2373
+ * Use this helper for broad detection (e.g., extension-level boundary predicate
2374
+ * registration) where both container and child function elements should be
2375
+ * treated as part of the function block structure.
2376
+ *
2377
+ * @param {HTMLElement} element - Element to inspect.
2378
+ * @returns {boolean} Whether the element belongs to a function block structure.
2379
+ * @example
2380
+ * this.registerBlockBoundaryPredicate(isFunctionBlockElement);
2381
+ */
2382
+ const isFunctionBlockElement = (element) => {
2383
+ return (element.hasAttribute(codeBlock.RTE_BLOCK_BOUNDARY_ATTRIBUTE) ||
2384
+ FUNCTION_ELEMENT_CLASSES.some((functionClass) => element.classList.contains(functionClass)));
2385
+ };
2386
+ /**
2387
+ * Returns true when the element is a function block boundary container.
2388
+ *
2389
+ * Use this helper when conversion logic must identify only the function block
2390
+ * container element (the DIV that maps to RTEFunctionBlock), not function child
2391
+ * elements such as syntax/content nodes.
2392
+ *
2393
+ * @param {HTMLElement} element - Element to inspect.
2394
+ * @returns {boolean} Whether the element is a function block boundary container.
2395
+ * @example
2396
+ * if (isFunctionBoundaryContainer(element)) {
2397
+ * // map the DIV to RTEFunctionBlock
2398
+ * }
2399
+ */
2400
+ const isFunctionBoundaryContainer = (element) => {
2401
+ return (element.hasAttribute(codeBlock.RTE_BLOCK_BOUNDARY_ATTRIBUTE) || element.classList.contains(FUNCTION_BLOCK_CLASS));
2402
+ };
2403
+
2309
2404
  /**
2310
2405
  * Strategy to convert a div with class 'statementsyntax' into an RTELogicControlBlock DTO.
2311
2406
  */
2312
2407
  class DivToLogicControlBlockStrategy extends contentState.BlockToRTEBlockStrategyAbstract {
2313
2408
  constructor() {
2314
2409
  super(...arguments);
2315
- this.blockClassName = 'statementsyntax';
2410
+ this.blockClassName = FUNCTION_SYNTAX_CLASS;
2316
2411
  }
2317
2412
  /**
2318
2413
  * @inheritDoc
@@ -2355,7 +2450,7 @@ class DivToLogicControlBlockStrategy extends contentState.BlockToRTEBlockStrateg
2355
2450
  * @returns {boolean} True if the element is a child of a function block, false otherwise
2356
2451
  */
2357
2452
  isFunctionBlockChild(element) {
2358
- return !!(element.parentElement && element.parentElement.classList.contains('statement'));
2453
+ return !!(element.parentElement && isFunctionBoundaryContainer(element.parentElement));
2359
2454
  }
2360
2455
  }
2361
2456
 
@@ -2368,11 +2463,13 @@ class DivToFunctionBlockStrategy extends contentState.ElementToDTOStrategy {
2368
2463
  */
2369
2464
  canHandle(elementsArray, currentIndex) {
2370
2465
  const element = elementsArray[currentIndex];
2371
- return element &&
2372
- element.tagName === 'DIV' &&
2373
- element.classList.contains('statement') &&
2466
+ if (element.nodeType !== Node.ELEMENT_NODE)
2467
+ return 0;
2468
+ const isFunctionBoundary = isFunctionBoundaryContainer(element);
2469
+ return element.tagName === 'DIV' &&
2470
+ isFunctionBoundary &&
2374
2471
  element.childNodes.length &&
2375
- element.querySelector('.statementsyntax')
2472
+ element.querySelector(`.${FUNCTION_SYNTAX_CLASS}`)
2376
2473
  ? 1
2377
2474
  : 0;
2378
2475
  }
@@ -2384,7 +2481,7 @@ class DivToFunctionBlockStrategy extends contentState.ElementToDTOStrategy {
2384
2481
  id: misc.generateUUID(),
2385
2482
  type: 'function-block',
2386
2483
  annotations: {
2387
- customClass: ['statement'],
2484
+ customClass: [FUNCTION_BLOCK_CLASS],
2388
2485
  },
2389
2486
  children: [],
2390
2487
  });
@@ -2412,7 +2509,7 @@ class DivToFunctionBlockStrategy extends contentState.ElementToDTOStrategy {
2412
2509
  class DivToFunctionContentStrategy extends DivToLogicControlBlockStrategy {
2413
2510
  constructor() {
2414
2511
  super(...arguments);
2415
- this.blockClassName = 'statementelement';
2512
+ this.blockClassName = FUNCTION_CONTENT_CLASS;
2416
2513
  }
2417
2514
  /**
2418
2515
  * @inheritDoc
@@ -2420,16 +2517,31 @@ class DivToFunctionContentStrategy extends DivToLogicControlBlockStrategy {
2420
2517
  handle(elementsArray, options) {
2421
2518
  const element = elementsArray[0];
2422
2519
  const annotations = contentState.htmlElementToAnnotationGenerator.generate(codeBlock.RTETextBlock, element);
2423
- const nodes = Array.from(elementsArray[0].childNodes);
2424
2520
  return codeBlock.RTETextBlock.from({
2425
2521
  id: misc.generateUUID(),
2426
2522
  type: 'paragraph',
2427
2523
  annotations: Object.assign({ customClass: [this.blockClassName] }, annotations),
2428
- nodes: this.generateChildNodes(nodes, undefined, options),
2524
+ nodes: this.generateChildNodes(Array.from(elementsArray[0].childNodes), undefined, options),
2429
2525
  });
2430
2526
  }
2431
2527
  }
2432
2528
 
2529
+ /** Preserve a div that contains a function block during auto-formatting. */
2530
+ class FunctionBlockContainerStrategy extends contentState.HtmlBlockStrategy {
2531
+ /**
2532
+ * @inheritDoc
2533
+ */
2534
+ canHandle(elementsArray, currentIndex) {
2535
+ const element = elementsArray[currentIndex];
2536
+ if (element.nodeType !== Node.ELEMENT_NODE)
2537
+ return 0;
2538
+ const containsFunctionBlock = !!element.querySelector(`[${codeBlock.RTE_BLOCK_BOUNDARY_ATTRIBUTE}], .${FUNCTION_BLOCK_CLASS}`);
2539
+ return element.nodeName === 'DIV' && !isFunctionBlockElement(element) && containsFunctionBlock
2540
+ ? 1
2541
+ : 0;
2542
+ }
2543
+ }
2544
+
2433
2545
  const functionBlockStylesCss = "div.statement{padding:2px 4px;background:rgba(var(--v-bg-accent3-secondary, 255, 239, 200));border:1px solid rgba(var(--v-bg-accent3-primary, 255, 220, 131));border-radius:4px;display:flex;flex-direction:row;justify-content:flex-start;align-items:stretch;gap:4px}div.statement .statementsyntax{padding:3px 0;line-height:22px}.rich-text-content div.statement .statementsyntax,.rich-text-content div.statement .statementelement{display:inline-block}div.statement+div.statement{margin-top:2px}div.statement .statementsyntax span{color:rgb(38, 42, 255);font-weight:bold}div.statement .statementelement{padding:2px 4px;background:rgba(var(--v-bg-secondary, 245, 247, 247));border:1px solid rgba(var(--v-border-color-input-field, 171, 198, 216));border-radius:4px;margin:0;min-width:8px}";
2434
2546
 
2435
2547
  /**
@@ -2443,6 +2555,7 @@ class RTEFunctionBlockRenderer extends linkExtension.VegaRTEExtensionRenderer {
2443
2555
  return createElement('div', {
2444
2556
  key: renderData.id,
2445
2557
  class: this.getElementClassName(renderData),
2558
+ [codeBlock.RTE_BLOCK_BOUNDARY_ATTRIBUTE]: '',
2446
2559
  ref: this.useRef(renderData),
2447
2560
  });
2448
2561
  }
@@ -2462,8 +2575,8 @@ class RTEFunctionBlockRenderer extends linkExtension.VegaRTEExtensionRenderer {
2462
2575
  */
2463
2576
  getElementClassName(block) {
2464
2577
  let classes = block.getClasses();
2465
- if (classes.indexOf('statement') < 0) {
2466
- classes = 'statement ' + classes;
2578
+ if (classes.indexOf(FUNCTION_BLOCK_CLASS) < 0) {
2579
+ classes = FUNCTION_BLOCK_CLASS + ' ' + classes;
2467
2580
  }
2468
2581
  return classes.trim();
2469
2582
  }
@@ -2637,12 +2750,14 @@ class PastePlainTextStrategy extends codeBlock.ActionHandleStrategy {
2637
2750
  const convertLogicControlBlockStrategy = new DivToLogicControlBlockStrategy();
2638
2751
  const convertFunctionContentStrategy = new DivToFunctionContentStrategy();
2639
2752
  const convertFunctionBlockStrategy = new DivToFunctionBlockStrategy();
2753
+ const convertFunctionBlockContainerStrategy = new FunctionBlockContainerStrategy();
2640
2754
  /**
2641
2755
  * Function extension class for Vega Rich Text Editor.
2642
2756
  */
2643
2757
  class VegaRTEFunctionExtension extends linkExtension.VegaRTEExtension {
2644
2758
  constructor() {
2645
2759
  super();
2760
+ this.unregisterBlockBoundaryPredicate = null;
2646
2761
  this.registerBlockBasicStrategies(RTELogicControlBlock.name);
2647
2762
  this.registerBlockBasicStrategies(RTEFunctionBlock.name);
2648
2763
  this.preventFunctionBlockDefaultInsertNewParagraph();
@@ -2653,11 +2768,25 @@ class VegaRTEFunctionExtension extends linkExtension.VegaRTEExtension {
2653
2768
  this.registerElementToBlockDTOClassStrategy(convertLogicControlBlockStrategy);
2654
2769
  this.registerElementToBlockDTOClassStrategy(convertFunctionContentStrategy);
2655
2770
  this.registerElementToBlockDTOClassStrategy(convertFunctionBlockStrategy);
2771
+ this.registerElementToBlockDTOClassStrategy(convertFunctionBlockContainerStrategy);
2656
2772
  }
2657
2773
  /** @inheritDoc */
2658
2774
  prepareBeforeLoad(host) {
2659
2775
  this.registerToolbarButtonRenderer(VegaRTEFunctionExtension.TOOLBAR_BUTTON_KEY, new FunctionToolbarButtonRenderer(), host);
2660
2776
  }
2777
+ /** @inheritDoc */
2778
+ initialExtensionAfterLoad(context) {
2779
+ var _a;
2780
+ super.initialExtensionAfterLoad(context);
2781
+ (_a = this.unregisterBlockBoundaryPredicate) === null || _a === void 0 ? void 0 : _a.call(this);
2782
+ this.unregisterBlockBoundaryPredicate = this.registerBlockBoundaryPredicate(isFunctionBlockElement);
2783
+ }
2784
+ /** @inheritDoc */
2785
+ destroyExtension() {
2786
+ var _a;
2787
+ (_a = this.unregisterBlockBoundaryPredicate) === null || _a === void 0 ? void 0 : _a.call(this);
2788
+ this.unregisterBlockBoundaryPredicate = null;
2789
+ }
2661
2790
  /**
2662
2791
  * Remove the appen children strategies to prevent default insert new paragraph logic in function block
2663
2792
  */
@@ -3312,7 +3441,7 @@ class TablePopoverBaseRenderer {
3312
3441
  */
3313
3442
  getTableExtensionContext() {
3314
3443
  return this.renderContext
3315
- ? linkExtension.RTEExtensionContextManager.getExtensionContext(this.renderContext.host)
3444
+ ? linkExtension.extensionContextManager.getExtensionContext(this.renderContext.host)
3316
3445
  : null;
3317
3446
  }
3318
3447
  }
@@ -11042,6 +11171,62 @@ class VegaRTEFullscreenExtension extends linkExtension.VegaRTEExtension {
11042
11171
  VegaRTEFullscreenExtension.TOOLBAR_BUTTON_KEY = 'fullscreen';
11043
11172
  VegaRTEFullscreenExtension.controllerStore = VegaRTEFullscreenExtension.createStateStore();
11044
11173
 
11174
+ var RTEPasteMode;
11175
+ (function (RTEPasteMode) {
11176
+ RTEPasteMode["RICH"] = "rich";
11177
+ RTEPasteMode["WORD"] = "word";
11178
+ })(RTEPasteMode || (RTEPasteMode = {}));
11179
+ /**
11180
+ * Priority scale shared by all paste processing strategies.
11181
+ */
11182
+ var PasteProcessingStrategyPriority;
11183
+ (function (PasteProcessingStrategyPriority) {
11184
+ /** Fallback strategy used only when no specialized strategy matches. */
11185
+ PasteProcessingStrategyPriority[PasteProcessingStrategyPriority["DEFAULT_FALLBACK"] = -1] = "DEFAULT_FALLBACK";
11186
+ /** Baseline priority for generic strategies. */
11187
+ PasteProcessingStrategyPriority[PasteProcessingStrategyPriority["BASE"] = 0] = "BASE";
11188
+ /** Word strategy priority so it outranks generic strategies. */
11189
+ PasteProcessingStrategyPriority[PasteProcessingStrategyPriority["WORD"] = 10] = "WORD";
11190
+ })(PasteProcessingStrategyPriority || (PasteProcessingStrategyPriority = {}));
11191
+ /**
11192
+ * Abstract base class for paste processing strategies.
11193
+ *
11194
+ * Each strategy implements detection logic (`canHandle`) and transformation
11195
+ * logic (`process`) for a specific clipboard source (Word, Google Docs, etc.).
11196
+ * The strategy selector chooses the highest-priority strategy that can handle
11197
+ * the given HTML.
11198
+ */
11199
+ class AbstractPasteProcessingStrategy {
11200
+ /**
11201
+ * Downstream paste mode marker associated with this strategy.
11202
+ *
11203
+ * Defaults to regular rich-text paste unless a specialized strategy overrides it.
11204
+ *
11205
+ * @returns {RTEPasteMode} Paste mode marker for downstream handlers.
11206
+ */
11207
+ get type() {
11208
+ return RTEPasteMode.RICH;
11209
+ }
11210
+ /**
11211
+ * Optional: Strategy priority when multiple strategies match.
11212
+ * Higher numbers = higher priority. Default: 0.
11213
+ *
11214
+ * @returns {number} Strategy priority.
11215
+ */
11216
+ get priority() {
11217
+ return PasteProcessingStrategyPriority.BASE;
11218
+ }
11219
+ /**
11220
+ * Whether pasted images from this source should be extracted into local
11221
+ * previews by the paste extension after {@link process} runs.
11222
+ *
11223
+ * @returns {boolean} `false` unless a strategy opts into image processing.
11224
+ */
11225
+ get supportsImageProcessing() {
11226
+ return false;
11227
+ }
11228
+ }
11229
+
11045
11230
  const paste = {
11046
11231
  icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path fill="currentColor" d="M80 96v16c0 17.7 14.3 32 32 32h60.8c16.6-28.7 47.6-48 83.2-48h62c-7.1-27.6-32.2-48-62-48H215.4C211.6 20.9 188.2 0 160 0s-51.6 20.9-55.4 48H64C28.7 48 0 76.7 0 112V384c0 35.3 28.7 64 64 64h96V400H64c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H80zm64-40a16 16 0 1 1 32 0 16 16 0 1 1 -32 0zM256 464c-8.8 0-16-7.2-16-16V192c0-8.8 7.2-16 16-16H384v48c0 17.7 14.3 32 32 32h48V448c0 8.8-7.2 16-16 16H256zm192 48c35.3 0 64-28.7 64-64V227.9c0-12.7-5.1-24.9-14.1-33.9l-51.9-51.9c-9-9-21.2-14.1-33.9-14.1H256c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H448z"/></svg>`,
11047
11232
  };
@@ -11050,9 +11235,10 @@ const paste = {
11050
11235
  * Renders and manages the paste format chooser UI.
11051
11236
  */
11052
11237
  class RTEPasteChooserUI {
11053
- constructor(hostRoot, callbacks) {
11238
+ constructor(hostRoot, callbacks, options = {}) {
11054
11239
  this.hostRoot = hostRoot;
11055
11240
  this.callbacks = callbacks;
11241
+ this.options = options;
11056
11242
  this.chooserEl = null;
11057
11243
  this.dropdownEl = null;
11058
11244
  this.triggerButtonEl = null;
@@ -11153,11 +11339,17 @@ class RTEPasteChooserUI {
11153
11339
  dropdownEl.append(tooltipEl);
11154
11340
  const itemsContainer = doc.createElement('div');
11155
11341
  itemsContainer.setAttribute('slot', 'items');
11156
- itemsContainer.append(this.createDropdownItem(doc, 'rich', internalTranslationController.VegaInternalTranslation.t('Use rich text'), RTEPasteChooserUI.CHOOSER_RICH_ATTR));
11342
+ const dropdownItems = [
11343
+ this.createDropdownItem(doc, RTEPasteMode.RICH, internalTranslationController.VegaInternalTranslation.t('Use rich text'), RTEPasteChooserUI.CHOOSER_RICH_ATTR),
11344
+ ];
11345
+ if (this.options.showWordOption) {
11346
+ dropdownItems.push(this.createDropdownItem(doc, RTEPasteMode.WORD, internalTranslationController.VegaInternalTranslation.t('Use Word'), RTEPasteChooserUI.CHOOSER_WORD_ATTR));
11347
+ }
11348
+ itemsContainer.append(...dropdownItems);
11157
11349
  dropdownEl.append(itemsContainer);
11158
11350
  dropdownEl.addEventListener('vegaDropdownClick', (event) => {
11159
11351
  const mode = event.detail;
11160
- if (mode === 'rich') {
11352
+ if (mode === RTEPasteMode.RICH || mode === RTEPasteMode.WORD) {
11161
11353
  this.callbacks.onModeSelect(mode);
11162
11354
  }
11163
11355
  });
@@ -11167,7 +11359,7 @@ class RTEPasteChooserUI {
11167
11359
  * Creates a single dropdown item element for a paste mode.
11168
11360
  *
11169
11361
  * @param {Document} doc Document used to create the element.
11170
- * @param {PasteMode} key Paste mode key for the item.
11362
+ * @param {RTEPasteMode} key Paste mode key for the item.
11171
11363
  * @param {string} label Display label for the item.
11172
11364
  * @param {string} dataAttrName Data attribute marker for the item.
11173
11365
  * @returns {HTMLVegaDropdownItemElement} Configured dropdown item element.
@@ -11187,161 +11379,2689 @@ class RTEPasteChooserUI {
11187
11379
  })();
11188
11380
  RTEPasteChooserUI.CHOOSER_ATTR = 'data-rte-paste-chooser';
11189
11381
  RTEPasteChooserUI.CHOOSER_RICH_ATTR = 'data-rte-paste-type-rich';
11382
+ RTEPasteChooserUI.CHOOSER_WORD_ATTR = 'data-rte-paste-type-word';
11190
11383
  RTEPasteChooserUI.CHOOSER_TRIGGER_ATTR = 'data-rte-paste-trigger';
11191
11384
 
11192
11385
  /**
11193
- * Custom DataTransfer MIME type used to tag a synthetic paste with the chosen
11194
- * paste mode (e.g. 'rich' or 'plain'), so downstream paste handling can decide
11195
- * whether to preserve the original inline styles.
11386
+ * Default paste processing strategy that acts as a fallback when no other
11387
+ * strategy matches. This strategy always accepts the content and passes it
11388
+ * through unchanged the normal RTE paste pipeline (DOMParser +
11389
+ * ElementToDtoStrategyProcessor) handles the rest.
11196
11390
  */
11197
- const RTE_PASTE_MODE_DATA_TYPE = 'application/x-vega-rte-paste-mode';
11391
+ class DefaultPasteProcessingStrategy extends AbstractPasteProcessingStrategy {
11392
+ /**
11393
+ * Always matches as the terminal fallback strategy.
11394
+ *
11395
+ * @param {string} html Raw clipboard HTML.
11396
+ * @returns {boolean} Always `true`.
11397
+ */
11398
+ canHandle(html) {
11399
+ // Default strategy is the fallback — always returns true.
11400
+ return html !== undefined;
11401
+ }
11402
+ /**
11403
+ * Returns the original HTML unchanged.
11404
+ *
11405
+ * @param {string} html Raw clipboard HTML.
11406
+ * @returns {string} Unchanged HTML.
11407
+ */
11408
+ process(html) {
11409
+ // Current default behavior: pass through unchanged
11410
+ // (DOMParser + ElementToDtoStrategyProcessor handles the rest)
11411
+ return html;
11412
+ }
11413
+ /**
11414
+ * Fallback priority so other strategies can win when matched.
11415
+ *
11416
+ * @returns {number} Strategy priority.
11417
+ */
11418
+ get priority() {
11419
+ return PasteProcessingStrategyPriority.DEFAULT_FALLBACK;
11420
+ }
11421
+ }
11198
11422
 
11199
11423
  /**
11200
- * Vega Rich Text Editor paste extension.
11201
- * Intercepts keyboard paste and allows users to choose paste mode.
11424
+ * Contract for Word DOM structure transformers. Each transformer is responsible
11425
+ * for one structural concern (list markers, paragraph roles, tables, images…).
11426
+ * Register new transformers in {@link WordPasteProcessingStrategy.STRUCTURE_TRANSFORMERS}
11427
+ * to extend Word paste support without touching the core pipeline.
11202
11428
  */
11203
- class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11204
- constructor() {
11205
- super(...arguments);
11206
- this.editableEl = null;
11207
- this.contentHostEl = null;
11208
- this.chooserUI = null;
11209
- this.pendingPastePayload = null;
11210
- this.listenerDocument = null;
11211
- this.listenerWindow = null;
11212
- /**
11213
- * Handles a native paste event by capturing the clipboard payload and
11214
- * rendering the paste-mode chooser.
11215
- *
11216
- * @param {ClipboardEvent} event Native clipboard paste event.
11217
- */
11218
- this.handlePasteEvent = (event) => {
11219
- var _a, _b;
11220
- const clipboardData = event.clipboardData;
11221
- if (!clipboardData) {
11222
- return;
11223
- }
11224
- // HTML is sanitized via DOMParser and ElementToDtoStrategyProcessor pipeline
11225
- const html = clipboardData.getData('text/html') || '';
11226
- const text = clipboardData.getData('text/plain') || '';
11227
- if (!html && !text) {
11228
- // Empty clipboard paste should not mutate editor content.
11229
- event.preventDefault();
11230
- event.stopPropagation();
11231
- return;
11232
- }
11233
- event.preventDefault();
11234
- event.stopPropagation();
11235
- const range = this.cloneCurrentSelectionRange();
11236
- const selectedNodes = ((_b = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelectedNodes) === null || _b === void 0 ? void 0 : _b.call(_a)) || [];
11237
- this.pendingPastePayload = { html, text, range, selectedNodes: [...selectedNodes] };
11238
- this.renderPasteChooser(event);
11239
- };
11240
- /**
11241
- * Dismisses the chooser when a pointer interaction occurs outside of it.
11242
- *
11243
- * @param {Event} event Pointer event captured on the document.
11244
- */
11245
- this.handleOutsidePointerDown = (event) => {
11246
- if (!this.chooserUI) {
11247
- return;
11248
- }
11249
- const eventPath = typeof event.composedPath === 'function' ? event.composedPath() : [];
11250
- if (this.chooserUI.includesEventPath(eventPath)) {
11251
- return;
11252
- }
11253
- this.clearPasteChooser();
11254
- };
11255
- /**
11256
- * Dismisses the chooser when the Escape key is pressed.
11257
- *
11258
- * @param {KeyboardEvent} event Keyboard event captured on the document.
11259
- */
11260
- this.handleOutsideKeyDown = (event) => {
11261
- if (event.key === 'Escape') {
11262
- this.clearPasteChooser();
11429
+ const WORD_SANITIZE_ALL_TAGS_KEY = '*';
11430
+
11431
+ /** CSS class prefix used by the RTE to encode text-style roles on elements. */
11432
+ const RTE_TEXT_STYLE_CLASS_PREFIX = 'v-rte--text-style-';
11433
+ /**
11434
+ * Maps Word title / subtitle / heading paragraph style class names onto RTE
11435
+ * CSS text-style classes. Any pre-existing `data-type` attributes are stripped
11436
+ * first so only trusted values remain. Using a class instead of a custom
11437
+ * attribute prevents the value from leaking into the DTO's custom-attribute
11438
+ * annotation and subsequently into the serialized HTML output.
11439
+ */
11440
+ class WordParagraphRoleTransformer {
11441
+ /** @inheritDoc */
11442
+ apply(root) {
11443
+ // Strip any pre-existing data-type attributes so untrusted source values
11444
+ // can never influence the role resolution.
11445
+ Array.from(root.querySelectorAll('[data-type]')).forEach((element) => {
11446
+ element.removeAttribute('data-type');
11447
+ });
11448
+ Array.from(root.querySelectorAll('p')).forEach((paragraph) => {
11449
+ const rawClass = paragraph.getAttribute('class');
11450
+ const role = this.resolveRole(rawClass);
11451
+ // Drop the Word paragraph class once its role has been extracted so
11452
+ // that Word artifacts (e.g. `MsoTitle`, `MsoNormal`) never leak into
11453
+ // the sanitized output.
11454
+ if (rawClass !== null) {
11455
+ paragraph.removeAttribute('class');
11456
+ }
11457
+ if (role) {
11458
+ // Encode the role as an RTE text-style CSS class so the downstream
11459
+ // DTO processor can resolve it via getElementTypeByClassName().
11460
+ paragraph.classList.add(`${RTE_TEXT_STYLE_CLASS_PREFIX}${role}`);
11263
11461
  }
11264
- };
11265
- /**
11266
- * Dismisses the chooser when any relevant scroll container scrolls.
11267
- */
11268
- this.handleOutsideScroll = () => {
11269
- this.clearPasteChooser();
11270
- };
11462
+ });
11271
11463
  }
11272
11464
  /** @inheritDoc */
11273
- initialExtensionAfterLoad(context) {
11274
- super.initialExtensionAfterLoad(context);
11275
- this.hostEl = context.host;
11276
- this.attachPasteListener();
11465
+ getAllowedTags() {
11466
+ return [
11467
+ 'P',
11468
+ 'BR',
11469
+ 'SPAN',
11470
+ 'STRONG',
11471
+ 'B',
11472
+ 'EM',
11473
+ 'I',
11474
+ 'U',
11475
+ 'S',
11476
+ 'STRIKE',
11477
+ 'DEL',
11478
+ 'H1',
11479
+ 'H2',
11480
+ 'H3',
11481
+ 'H4',
11482
+ 'H5',
11483
+ 'H6',
11484
+ 'A',
11485
+ // Inline `<code>` is treated as an inline text annotation (like
11486
+ // STRONG/EM/U) and therefore lives with the paragraph/inline
11487
+ // allowlist. Block-level `<pre>` code blocks are owned by
11488
+ // {@link WordCodeBlockTransformer}.
11489
+ 'CODE',
11490
+ ];
11277
11491
  }
11278
11492
  /** @inheritDoc */
11279
- destroyExtension() {
11280
- this.detachPasteListener();
11281
- this.clearPasteChooser();
11493
+ getAllowedAttributes() {
11494
+ // `class` must be preserved so this transformer can parse Word paragraph
11495
+ // roles (e.g., MsoTitle, MsoSubtitle, MsoHeadingN) in Phase 1. These classes
11496
+ // are ultimately stripped when pasting in autoMatchFormat mode.
11497
+ return new Map([
11498
+ [WORD_SANITIZE_ALL_TAGS_KEY, ['class', 'style']],
11499
+ ['A', ['href', 'target', 'title']],
11500
+ ]);
11501
+ }
11502
+ /** @inheritDoc */
11503
+ getAllowedStyles() {
11504
+ return [
11505
+ 'color',
11506
+ 'background',
11507
+ 'text-align',
11508
+ 'text-indent',
11509
+ 'text-decoration',
11510
+ 'margin',
11511
+ 'padding',
11512
+ 'font',
11513
+ 'line-height',
11514
+ 'letter-spacing',
11515
+ 'vertical-align',
11516
+ ];
11282
11517
  }
11283
11518
  /**
11284
- * Attaches the paste listener to the editable element.
11519
+ * Resolves the RTE text style for a Word paragraph class name.
11520
+ *
11521
+ * @param {string | null} className Raw `class` attribute value.
11522
+ * @returns {WordParagraphRole | null} Matched role or `null`.
11285
11523
  */
11286
- attachPasteListener() {
11287
- const { editableEl, contentHostEl, } = this.getEditorElements();
11288
- this.editableEl = editableEl;
11289
- this.contentHostEl = contentHostEl;
11290
- if (this.editableEl) {
11291
- this.editableEl.addEventListener('paste', this.handlePasteEvent);
11524
+ resolveRole(className) {
11525
+ if (!className) {
11526
+ return null;
11527
+ }
11528
+ const normalized = className.toLowerCase();
11529
+ const headingMatch = /msoheading([1-6])/.exec(normalized);
11530
+ if (headingMatch) {
11531
+ return `heading-${headingMatch[1]}`;
11532
+ }
11533
+ if (/msosubtitle/.test(normalized)) {
11534
+ return 'subtitle';
11535
+ }
11536
+ if (/msotitle/.test(normalized)) {
11537
+ return 'title';
11538
+ }
11539
+ return null;
11540
+ }
11541
+ }
11542
+
11543
+ /** Font families Word uses to render bullet symbols rather than real characters. */
11544
+ const BULLET_FONT_PATTERN = /symbol|wingdings|webdings/i;
11545
+ /**
11546
+ * Matches the paragraph-level Word list marker (`mso-list:l0 level1 lfo1`). The
11547
+ * presence of `l<number>` distinguishes a genuine list paragraph from the inner
11548
+ * `mso-list:Ignore` marker span.
11549
+ */
11550
+ const PARAGRAPH_LIST_PATTERN = /mso-list\s*:\s*l\d+/i;
11551
+ /** Extracts the Word nesting depth (`level<number>`) from a list paragraph. */
11552
+ const LIST_LEVEL_PATTERN = /mso-list\s*:\s*l\d+\s+level(\d+)/i;
11553
+ /** Extracts the abstract Word list template id (`l<number>`). */
11554
+ const LIST_TEMPLATE_ID_PATTERN = /mso-list\s*:\s*l(\d+)/i;
11555
+ /** Extracts the Word list-format override id (`lfo<number>`), unique per list instance. */
11556
+ const LIST_OVERRIDE_ID_PATTERN = /\blfo(\d+)/i;
11557
+ /** Matches the inner span that holds the Word-rendered bullet / number glyph. */
11558
+ const LIST_IGNORE_PATTERN = /mso-list\s*:\s*ignore/i;
11559
+ /** Extracts a `font-family` declaration value from a preserved style string. */
11560
+ const FONT_FAMILY_PATTERN = /font-family\s*:\s*([^;]+)/i;
11561
+ /** Fallback Word list paragraph class when no `mso-list` style is present. */
11562
+ const LIST_PARAGRAPH_CLASS_PATTERN = /MsoListParagraph/i;
11563
+ /**
11564
+ * CSS `list-style-type` values keyed by the shape of the Word marker token.
11565
+ * Decimal markers are intentionally absent: a plain `<ol>` already renders as
11566
+ * `1. 2. 3.`, so numeric lists keep the current behavior with no inline style.
11567
+ *
11568
+ * Roman patterns explicitly accept a lone `i` / `I` (representing 1), which is
11569
+ * the typical first item of a Roman-numeral list. Multi-character sequences
11570
+ * must contain only Roman letters.
11571
+ */
11572
+ const LOWER_ROMAN_PATTERN = /^(i|[ivxlcdm]{2,})$/;
11573
+ const UPPER_ROMAN_PATTERN = /^(I|[IVXLCDM]{2,})$/;
11574
+ const LOWER_ALPHA_PATTERN = /^[a-z]+$/;
11575
+ const UPPER_ALPHA_PATTERN = /^[A-Z]+$/;
11576
+ const DECIMAL_PATTERN = /^[0-9]+$/;
11577
+ /** Extracts the `list-style-type` value from a preserved style string. */
11578
+ const LIST_STYLE_TYPE_PATTERN = /list-style-type\s*:\s*([^;]+)/i;
11579
+ /**
11580
+ * Maps the HTML `<ol type>` attribute values Word emits on real ordered lists
11581
+ * to the equivalent CSS `list-style-type`. Decimal (`type=1`) is intentionally
11582
+ * absent: a plain `<ol>` already renders as `1. 2. 3.`, so it needs no inline
11583
+ * style (matching the marker-derived handling above).
11584
+ */
11585
+ const OL_TYPE_TO_LIST_STYLE_TYPE = {
11586
+ a: 'lower-alpha',
11587
+ A: 'upper-alpha',
11588
+ i: 'lower-roman',
11589
+ I: 'upper-roman',
11590
+ };
11591
+ /**
11592
+ * Rebuilds Word list paragraphs into real `<ul>` / `<ol>` / `<li>` structures.
11593
+ *
11594
+ * Word does not emit list elements: it emits a flat sequence of `<p>` blocks
11595
+ * that each carry an `mso-list:l<id> level<depth> lfo<override>` marker plus an
11596
+ * inner `mso-list:Ignore` span that renders the visible bullet or number. This
11597
+ * transformer walks every container that hosts list paragraphs (so nested
11598
+ * `<div>` / table cells are covered), strips the marker span, decides bullet
11599
+ * vs. numbered from the marker text / font, and folds the flat items back into
11600
+ * a nested list tree the RTE slimmer can parse.
11601
+ *
11602
+ * Runs before {@link WordParagraphRoleTransformer} so the converted `<li>`
11603
+ * elements are no longer seen as paragraphs during role resolution.
11604
+ */
11605
+ class WordListRoleTransformer {
11606
+ /** @inheritDoc */
11607
+ apply(root) {
11608
+ // Word can also emit real `<ol>` / `<ul>` structures where a nested list
11609
+ // is a **sibling** of the preceding `<li>` rather than a child of it (an
11610
+ // invalid nesting the HTML parser preserves as-is). Fix that first so the
11611
+ // rest of the pipeline sees the standard `<li><ol>…</ol></li>` shape.
11612
+ this.nestSiblingLists(root);
11613
+ // Word may nest list paragraphs inside containers (e.g. `<div>`, table
11614
+ // cells). Group by their direct parent so contiguous siblings in each
11615
+ // container are folded together, regardless of where they sit in the tree.
11616
+ const containers = new Set();
11617
+ Array.from(root.querySelectorAll('p')).forEach((paragraph) => {
11618
+ if (this.isWordListParagraph(paragraph) && paragraph.parentElement) {
11619
+ containers.add(paragraph.parentElement);
11620
+ }
11621
+ });
11622
+ // Include the root itself when it hosts list paragraphs directly.
11623
+ if (Array.from(root.children).some((child) => this.isWordListParagraph(child))) {
11624
+ containers.add(root);
11292
11625
  }
11626
+ containers.forEach((container) => this.processContainer(container));
11627
+ // Word also emits real `<ol type=...>` elements whose numbering style lives
11628
+ // on the (non-allowlisted) `type` attribute. Fold that into a preserved
11629
+ // `list-style-type` so the marker style survives the allowlist pass.
11630
+ this.applyOrderedListTypeAttributes(root);
11631
+ }
11632
+ /** @inheritDoc */
11633
+ getAllowedTags() {
11634
+ return ['UL', 'OL', 'LI'];
11635
+ }
11636
+ /** @inheritDoc */
11637
+ getAllowedAttributes() {
11638
+ return new Map();
11639
+ }
11640
+ /** @inheritDoc */
11641
+ getAllowedStyles() {
11642
+ return ['list-style'];
11293
11643
  }
11294
11644
  /**
11295
- * Detaches the paste listener from the editable element.
11645
+ * Moves any `<ol>` / `<ul>` that appears as a direct child of another
11646
+ * `<ol>` / `<ul>` into the preceding `<li>` sibling, so downstream strategies
11647
+ * see a well-formed nested list. Word emits this invalid shape when it uses
11648
+ * real list elements (rather than flat `mso-list` paragraphs) — the browser
11649
+ * parser keeps the nested list as a sibling of the `<li>` it should belong
11650
+ * to, and the RTE's OL/UL block strategy would drop it because its
11651
+ * `appendChildBlocks` only keeps `<li>` children.
11652
+ *
11653
+ * Runs bottom-up so a nested list that also has misplaced children is fixed
11654
+ * before its own parent list picks it up.
11655
+ *
11656
+ * @param {HTMLElement} root Parsed body element.
11296
11657
  */
11297
- detachPasteListener() {
11298
- if (this.editableEl) {
11299
- this.editableEl.removeEventListener('paste', this.handlePasteEvent);
11658
+ nestSiblingLists(root) {
11659
+ const lists = Array.from(root.querySelectorAll('ol, ul'));
11660
+ for (let index = lists.length - 1; index >= 0; index--) {
11661
+ Array.from(lists[index].children).forEach((child) => {
11662
+ if (child.tagName !== 'OL' && child.tagName !== 'UL') {
11663
+ return;
11664
+ }
11665
+ const previousItem = child.previousElementSibling;
11666
+ if (previousItem && previousItem.tagName === 'LI') {
11667
+ previousItem.appendChild(child);
11668
+ }
11669
+ });
11300
11670
  }
11301
- this.editableEl = null;
11302
- this.contentHostEl = null;
11303
11671
  }
11304
11672
  /**
11305
- * Resolves the content host and editable element from the host shadow tree.
11673
+ * Converts the HTML `<ol type>` attribute into a preserved
11674
+ * `list-style-type` declaration for every ordered list that carries one.
11306
11675
  *
11307
- * @returns {EditorElements} Editor elements.
11676
+ * Word can paste genuine `<ol>` elements (rather than flat list paragraphs)
11677
+ * that encode alpha / Roman numbering via the legacy `type` attribute
11678
+ * (`type=a`, `type=i`, `type=I`, …). The `type` attribute is not on the
11679
+ * allowlist and would be dropped, losing the numbering style, so it is mapped
11680
+ * onto `list-style-type` inside `data-style`. Decimal (`type=1`) lists are
11681
+ * left untouched because a bare `<ol>` already renders as `1. 2. 3.`. An
11682
+ * existing `list-style-type` declaration wins and is never overwritten.
11683
+ *
11684
+ * @param {HTMLElement} root Parsed body element.
11308
11685
  */
11309
- getEditorElements() {
11310
- var _a, _b, _c, _d, _e;
11311
- const contentHostEl = (_c = (_b = (_a = this.hostEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('vega-rich-text-content')) !== null && _c !== void 0 ? _c : null;
11312
- const editableEl = (_e = (_d = contentHostEl === null || contentHostEl === void 0 ? void 0 : contentHostEl.shadowRoot) === null || _d === void 0 ? void 0 : _d.querySelector('.rich-text-content')) !== null && _e !== void 0 ? _e : null;
11313
- return { editableEl, contentHostEl };
11686
+ applyOrderedListTypeAttributes(root) {
11687
+ Array.from(root.querySelectorAll('ol[type]')).forEach((orderedList) => {
11688
+ const type = orderedList.getAttribute('type') || '';
11689
+ const listStyleType = OL_TYPE_TO_LIST_STYLE_TYPE[type];
11690
+ if (!listStyleType) {
11691
+ return;
11692
+ }
11693
+ const style = orderedList.getAttribute('data-style') || '';
11694
+ if (LIST_STYLE_TYPE_PATTERN.test(style)) {
11695
+ return;
11696
+ }
11697
+ const prefix = style.trim() === '' ? '' : `${style.replace(/;\s*$/, '')}; `;
11698
+ orderedList.setAttribute('data-style', `${prefix}list-style-type: ${listStyleType}`);
11699
+ });
11314
11700
  }
11315
11701
  /**
11316
- * Resolves the editable content element from the host shadow tree.
11702
+ * Walks a container, folding each contiguous run of Word list paragraphs
11703
+ * into a nested list tree in place of the original paragraphs.
11317
11704
  *
11318
- * @returns {HTMLDivElement | null} Editable element or `null` when unavailable.
11705
+ * @param {HTMLElement} container Element whose direct children may include list paragraphs.
11319
11706
  */
11320
- getEditableElement() {
11321
- const { editableEl } = this.getEditorElements();
11322
- return editableEl;
11707
+ processContainer(container) {
11708
+ const children = Array.from(container.children);
11709
+ let index = 0;
11710
+ while (index < children.length) {
11711
+ if (!this.isWordListParagraph(children[index])) {
11712
+ index++;
11713
+ continue;
11714
+ }
11715
+ const run = [];
11716
+ while (index < children.length && this.isWordListParagraph(children[index])) {
11717
+ run.push(children[index]);
11718
+ index++;
11719
+ }
11720
+ this.transformRun(run, container);
11721
+ }
11323
11722
  }
11324
11723
  /**
11325
- * Clones the current selection range so it can be restored after the user
11326
- * chooses a paste mode.
11724
+ * Determines whether an element is a Word list paragraph.
11327
11725
  *
11328
- * @returns {Range | null} Cloned range or `null` when there is no selection.
11726
+ * A paragraph is only treated as a list when it carries a genuine Word list
11727
+ * marker: either the `mso-list:l<n>` paragraph style, or the `MsoListParagraph`
11728
+ * class **together with** an inner `mso-list:Ignore` marker span. The
11729
+ * `MsoListParagraph` class alone is not sufficient — Word also uses that
11730
+ * paragraph style for plain indented content (e.g. headings), which must not
11731
+ * be folded into a `<ul>` / `<ol>`.
11732
+ *
11733
+ * @param {HTMLElement} element Candidate element.
11734
+ * @returns {boolean} `true` when the element is a `<p>` carrying a Word list marker.
11329
11735
  */
11330
- cloneCurrentSelectionRange() {
11331
- var _a;
11332
- const selection = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelection();
11333
- if (selection && selection.rangeCount > 0) {
11334
- return selection.getRangeAt(0).cloneRange();
11736
+ isWordListParagraph(element) {
11737
+ if (element.tagName !== 'P') {
11738
+ return false;
11335
11739
  }
11336
- return null;
11740
+ const style = element.getAttribute('data-style') || '';
11741
+ if (PARAGRAPH_LIST_PATTERN.test(style)) {
11742
+ return true;
11743
+ }
11744
+ const className = element.getAttribute('class') || '';
11745
+ return LIST_PARAGRAPH_CLASS_PATTERN.test(className) && this.findMarker(element) !== undefined;
11746
+ }
11747
+ /**
11748
+ * Folds a flat run of Word list paragraphs into a nested list tree inserted
11749
+ * before the run, then removes the original paragraphs.
11750
+ *
11751
+ * @param {HTMLElement[]} run Consecutive Word list paragraphs.
11752
+ * @param {HTMLElement} container Element hosting the run.
11753
+ */
11754
+ transformRun(run, container) {
11755
+ const anchor = run[0];
11756
+ const stack = [];
11757
+ run.forEach((paragraph) => {
11758
+ // Compute the marker once per paragraph and thread it through the
11759
+ // helpers that need it, so the DOM is not re-queried multiple times.
11760
+ const marker = this.findMarker(paragraph);
11761
+ const level = this.resolveLevel(paragraph);
11762
+ const ordered = marker ? this.isOrderedMarker(marker, paragraph) : false;
11763
+ const listId = this.resolveListId(paragraph);
11764
+ // Close any lists deeper than the current item.
11765
+ while (stack.length > 0 && stack[stack.length - 1].level > level) {
11766
+ stack.pop();
11767
+ }
11768
+ // Resolve the marker style with the same-level list already open in
11769
+ // context, so an ambiguous lone "i" / "I" continues an alpha list
11770
+ // instead of being misread as Roman 1. The marker text is still on the
11771
+ // paragraph here (buildListItem strips it below).
11772
+ let top = stack[stack.length - 1];
11773
+ const listStyleType = ordered && marker ? this.resolveListStyleType(marker, top, level, listId) : null;
11774
+ const listItem = this.buildListItem(paragraph, marker);
11775
+ if (!top || top.level < level) {
11776
+ top = this.openNestedList(stack, container, anchor, ordered, level, listStyleType, listId);
11777
+ }
11778
+ else if (this.needsSiblingList(top, ordered, listStyleType, listId)) {
11779
+ top = this.openSiblingList(stack, ordered, level, listStyleType, listId);
11780
+ }
11781
+ top.listEl.appendChild(listItem);
11782
+ });
11783
+ run.forEach((paragraph) => paragraph.remove());
11337
11784
  }
11338
11785
  /**
11339
- * Renders the paste-mode chooser and wires up dismissal listeners.
11786
+ * Reports whether a same-level item must break out into a new sibling list.
11787
+ * A different Word list instance always splits (so two separate lists pasted
11788
+ * back-to-back keep independent numbering). Bullet-vs-numbered switches also
11789
+ * split, and numbered lists additionally split when the CSS `list-style-type`
11790
+ * changes (e.g. `1.` → `a)`).
11791
+ *
11792
+ * @param {OpenList} top Currently open list at the same nesting depth.
11793
+ * @param {boolean} ordered Whether the incoming item is numbered.
11794
+ * @param {string | null} listStyleType Incoming numbered list style, if any.
11795
+ * @param {string | null} listId Incoming Word list instance id, if any.
11796
+ * @returns {boolean} `true` when a new sibling list should be opened.
11797
+ */
11798
+ needsSiblingList(top, ordered, listStyleType, listId) {
11799
+ // A known change of list instance means the two runs are distinct lists.
11800
+ if (top.listId !== null && listId !== null && top.listId !== listId) {
11801
+ return true;
11802
+ }
11803
+ if (top.ordered !== ordered) {
11804
+ return true;
11805
+ }
11806
+ if (!ordered) {
11807
+ return false;
11808
+ }
11809
+ return this.readListStyleType(top.listEl) !== listStyleType;
11810
+ }
11811
+ /**
11812
+ * Reads a previously-written `list-style-type` from an open list element.
11813
+ *
11814
+ * @param {HTMLElement} listEl List element.
11815
+ * @returns {string | null} The style value, or `null` when none is set.
11816
+ */
11817
+ readListStyleType(listEl) {
11818
+ const match = LIST_STYLE_TYPE_PATTERN.exec(listEl.getAttribute('data-style') || '');
11819
+ return match ? match[1].trim() : null;
11820
+ }
11821
+ /**
11822
+ * Opens a new list nested one or more levels deeper than the current top.
11823
+ * Word occasionally skips indent levels on paste (e.g. jumping from `level1`
11824
+ * to `level3`); this walks the gap one level at a time, inserting placeholder
11825
+ * lists so the deeper list is nested through empty `<li>` anchors instead of
11826
+ * appearing as a sibling of the shallower list.
11827
+ *
11828
+ * @param {OpenList[]} stack Stack of currently open lists.
11829
+ * @param {HTMLElement} container Host element for the top-level list.
11830
+ * @param {HTMLElement} anchor Element the top-level list is inserted before.
11831
+ * @param {boolean} ordered Whether the new list is numbered.
11832
+ * @param {number} level Word nesting depth for the new list.
11833
+ * @param {string | null} listStyleType CSS `list-style-type` for ordered lists.
11834
+ * @param {string | null} listId Word list instance id for the new list.
11835
+ * @returns {OpenList} The newly opened list.
11836
+ */
11837
+ openNestedList(stack, container, anchor, ordered, level, listStyleType, listId) {
11838
+ let currentTop = stack[stack.length - 1];
11839
+ let currentLevel = currentTop ? currentTop.level : 0;
11840
+ while (currentLevel < level - 1) {
11841
+ const bridge = this.createListElement(container, ordered, null);
11842
+ this.attachList(bridge, currentTop, container, anchor);
11843
+ // Empty `<li>` anchor so the next deeper list can be nested inside it.
11844
+ bridge.appendChild(bridge.ownerDocument.createElement('li'));
11845
+ stack.push({ level: currentLevel + 1, ordered, listId, listEl: bridge });
11846
+ currentTop = stack[stack.length - 1];
11847
+ currentLevel = currentTop.level;
11848
+ }
11849
+ const parent = stack[stack.length - 1];
11850
+ const listEl = this.createListElement(container, ordered, listStyleType);
11851
+ this.attachList(listEl, parent, container, anchor);
11852
+ const opened = { level, ordered, listId, listEl };
11853
+ stack.push(opened);
11854
+ return opened;
11855
+ }
11856
+ /**
11857
+ * Replaces the current top list with a sibling list at the same nesting
11858
+ * depth. Used when the list flavor (bullet vs. numbered, or the ordered
11859
+ * marker style) flips between two consecutive same-level items.
11860
+ *
11861
+ * @param {OpenList[]} stack Stack of currently open lists.
11862
+ * @param {boolean} ordered Whether the sibling list is numbered.
11863
+ * @param {number} level Word nesting depth for the sibling list.
11864
+ * @param {string | null} listStyleType CSS `list-style-type` for ordered lists.
11865
+ * @param {string | null} listId Word list instance id for the sibling list.
11866
+ * @returns {OpenList} The newly opened sibling list.
11867
+ */
11868
+ openSiblingList(stack, ordered, level, listStyleType, listId) {
11869
+ const current = stack[stack.length - 1];
11870
+ const listEl = this.createListElement(current.listEl, ordered, listStyleType);
11871
+ const parentNode = current.listEl.parentNode;
11872
+ if (parentNode) {
11873
+ parentNode.insertBefore(listEl, current.listEl.nextSibling);
11874
+ }
11875
+ stack.pop();
11876
+ const opened = { level, ordered, listId, listEl };
11877
+ stack.push(opened);
11878
+ return opened;
11879
+ }
11880
+ /**
11881
+ * Attaches a freshly created list element either inside the last item of the
11882
+ * parent list (for nested lists) or before the run anchor (top-level lists).
11883
+ *
11884
+ * @param {HTMLElement} listEl List element to attach.
11885
+ * @param {OpenList | undefined} parent Currently open parent list, if any.
11886
+ * @param {HTMLElement} container Host element used for top-level insertion.
11887
+ * @param {HTMLElement} anchor Element the top-level list is inserted before.
11888
+ */
11889
+ attachList(listEl, parent, container, anchor) {
11890
+ if (parent) {
11891
+ const parentItem = parent.listEl.lastElementChild;
11892
+ (parentItem || parent.listEl).appendChild(listEl);
11893
+ }
11894
+ else {
11895
+ container.insertBefore(listEl, anchor);
11896
+ }
11897
+ }
11898
+ /**
11899
+ * Creates an empty `<ol>` or `<ul>` element, tagging ordered lists with a
11900
+ * `list-style-type` when their markers are not plain decimals.
11901
+ *
11902
+ * @param {HTMLElement} reference Element whose document creates the list.
11903
+ * @param {boolean} ordered Whether to create a numbered list.
11904
+ * @param {string | null} listStyleType CSS `list-style-type` for ordered lists.
11905
+ * @returns {HTMLElement} The new list element.
11906
+ */
11907
+ createListElement(reference, ordered, listStyleType) {
11908
+ const listEl = reference.ownerDocument.createElement(ordered ? 'ol' : 'ul');
11909
+ if (ordered && listStyleType) {
11910
+ // Styles are preserved as `data-style` across the DOMParser boundary and
11911
+ // restored to `style` after the allowlist pass.
11912
+ listEl.setAttribute('data-style', `list-style-type: ${listStyleType}`);
11913
+ }
11914
+ return listEl;
11915
+ }
11916
+ /**
11917
+ * Converts a Word list paragraph into an `<li>` element: the marker span is
11918
+ * removed and the remaining content is moved into the list item.
11919
+ *
11920
+ * @param {HTMLElement} paragraph Word list paragraph.
11921
+ * @param {HTMLElement | undefined} marker Pre-resolved `mso-list:Ignore` span.
11922
+ * @returns {HTMLLIElement} The populated list item.
11923
+ */
11924
+ buildListItem(paragraph, marker) {
11925
+ const listItem = paragraph.ownerDocument.createElement('li');
11926
+ if (marker) {
11927
+ this.preserveMarkerPlaceholderImages(marker, paragraph);
11928
+ this.removeMarker(marker, paragraph);
11929
+ }
11930
+ while (paragraph.firstChild) {
11931
+ listItem.appendChild(paragraph.firstChild);
11932
+ }
11933
+ return listItem;
11934
+ }
11935
+ /**
11936
+ * Moves any local (`file://`) images out of the marker span so they survive
11937
+ * marker removal as list-item placeholders.
11938
+ *
11939
+ * Word renders a picture bullet as an `<img>` inside the `mso-list:Ignore`
11940
+ * marker span. That bullet still occupies one image slot in the clipboard RTF,
11941
+ * so silently dropping it here would leave its RTF blip unconsumed and shift
11942
+ * every following image onto the wrong file. Keeping the bullet as a `file://`
11943
+ * placeholder inside the `<li>` lets downstream preview pairing consume its RTF
11944
+ * slot and then drop it (list images have no editor representation), keeping the
11945
+ * remaining images aligned with their files. Non-local bullet images have no RTF
11946
+ * counterpart and are left in the marker to be removed with it.
11947
+ *
11948
+ * @param {HTMLElement} marker Marker span holding the bullet glyph / image.
11949
+ * @param {HTMLElement} paragraph Owning list paragraph (placeholder host).
11950
+ */
11951
+ preserveMarkerPlaceholderImages(marker, paragraph) {
11952
+ const localImages = Array.from(marker.querySelectorAll('img')).filter((image) => (image.getAttribute('src') || '').startsWith('file://'));
11953
+ // Capture the original first child once so images land ahead of the list
11954
+ // content in their document order (repeated inserts before a stable anchor).
11955
+ const anchor = paragraph.firstChild;
11956
+ localImages.forEach((image) => {
11957
+ paragraph.insertBefore(image, anchor);
11958
+ });
11959
+ }
11960
+ /**
11961
+ * Removes the `mso-list:Ignore` marker span and any wrapper elements it
11962
+ * leaves empty behind.
11963
+ *
11964
+ * @param {HTMLElement} marker Marker span to remove.
11965
+ * @param {HTMLElement} paragraph Owning list paragraph (stop boundary).
11966
+ */
11967
+ removeMarker(marker, paragraph) {
11968
+ let current = marker;
11969
+ while (current !== paragraph) {
11970
+ const parent = current.parentElement;
11971
+ current.remove();
11972
+ if (!parent || parent === paragraph || !this.isEmptyElement(parent)) {
11973
+ break;
11974
+ }
11975
+ current = parent;
11976
+ }
11977
+ }
11978
+ /**
11979
+ * Finds the `mso-list:Ignore` marker span within a list paragraph.
11980
+ *
11981
+ * @param {HTMLElement} paragraph Word list paragraph.
11982
+ * @returns {HTMLElement | undefined} The marker span, if present.
11983
+ */
11984
+ findMarker(paragraph) {
11985
+ return Array.from(paragraph.querySelectorAll('span')).find((span) => LIST_IGNORE_PATTERN.test(span.getAttribute('data-style') || ''));
11986
+ }
11987
+ /**
11988
+ * Reports whether an element has no meaningful (non-whitespace) content left.
11989
+ *
11990
+ * @param {HTMLElement} element Element to inspect.
11991
+ * @returns {boolean} `true` when only whitespace remains.
11992
+ */
11993
+ isEmptyElement(element) {
11994
+ return (element.textContent || '').replace(/[\s\u00A0]/g, '') === '';
11995
+ }
11996
+ /**
11997
+ * Resolves the Word nesting depth for a list paragraph (defaults to 1).
11998
+ *
11999
+ * @param {HTMLElement} paragraph Word list paragraph.
12000
+ * @returns {number} One-based nesting depth.
12001
+ */
12002
+ resolveLevel(paragraph) {
12003
+ const style = paragraph.getAttribute('data-style') || '';
12004
+ const match = LIST_LEVEL_PATTERN.exec(style);
12005
+ return match ? parseInt(match[1], 10) : 1;
12006
+ }
12007
+ /**
12008
+ * Resolves the Word list instance id for a paragraph. Prefers the list-format
12009
+ * override (`lfo<n>`), which is unique per pasted list, and falls back to the
12010
+ * abstract list template id (`l<n>`). Returns `null` when neither is present
12011
+ * (e.g. the `MsoListParagraph` class fallback).
12012
+ *
12013
+ * @param {HTMLElement} paragraph Word list paragraph.
12014
+ * @returns {string | null} The list instance id, or `null` when unknown.
12015
+ */
12016
+ resolveListId(paragraph) {
12017
+ const style = paragraph.getAttribute('data-style') || '';
12018
+ const override = LIST_OVERRIDE_ID_PATTERN.exec(style);
12019
+ if (override) {
12020
+ return `lfo${override[1]}`;
12021
+ }
12022
+ const template = LIST_TEMPLATE_ID_PATTERN.exec(style);
12023
+ return template ? `l${template[1]}` : null;
12024
+ }
12025
+ /**
12026
+ * Decides whether a resolved marker span represents a numbered (`<ol>`) or
12027
+ * bulleted (`<ul>`) item based on its text and font.
12028
+ *
12029
+ * @param {HTMLElement} marker Marker span.
12030
+ * @param {HTMLElement} paragraph Owning list paragraph.
12031
+ * @returns {boolean} `true` for numbered items, `false` for bulleted items.
12032
+ */
12033
+ isOrderedMarker(marker, paragraph) {
12034
+ const markerText = marker.textContent.replace(/[\s\u00A0]/g, '');
12035
+ if (BULLET_FONT_PATTERN.test(this.resolveMarkerFont(marker, paragraph))) {
12036
+ return false;
12037
+ }
12038
+ // Numbered markers are alphanumeric sequences ("1.", "a)", "iv)");
12039
+ // bullet markers are standalone symbols.
12040
+ return /[0-9]/.test(markerText) || /^[a-z]+[.)]/i.test(markerText);
12041
+ }
12042
+ /**
12043
+ * Resolves the CSS `list-style-type` for a numbered list marker.
12044
+ * Plain decimal markers ("1.", "2.") return `null` so a bare `<ol>` is used;
12045
+ * alphabetic and Roman markers map to the matching CSS value so their marker
12046
+ * style is preserved.
12047
+ *
12048
+ * A lone `i` / `I` is ambiguous: it is both Roman numeral 1 and the 9th
12049
+ * letter of the alphabet. When the same-level list already open for this
12050
+ * instance is alpha, the marker is kept alpha so a long `a) ... h) i) j)`
12051
+ * run does not split into a Roman sibling at item 9; otherwise the lone
12052
+ * marker is treated as Roman 1 (the first item of a Roman list).
12053
+ *
12054
+ * @param {HTMLElement} marker Marker span.
12055
+ * @param {OpenList | undefined} openList Same-depth list already open, if any.
12056
+ * @param {number} level Word nesting depth of the incoming item.
12057
+ * @param {string | null} listId Word list instance id of the incoming item.
12058
+ * @returns {string | null} The CSS `list-style-type`, or `null` for decimals.
12059
+ */
12060
+ resolveListStyleType(marker, openList, level, listId) {
12061
+ // Drop surrounding whitespace and the trailing "." / ")" separator to
12062
+ // isolate the numbering token (e.g. "a)" -> "a", "iv." -> "iv").
12063
+ const token = marker.textContent.replace(/[\s\u00A0]/g, '').replace(/[.)]+$/, '');
12064
+ if (DECIMAL_PATTERN.test(token)) {
12065
+ return null;
12066
+ }
12067
+ // Disambiguate a lone "i" / "I" from list context before the Roman check:
12068
+ // if the current same-level list is already alpha, continue it.
12069
+ if (token === 'i' || token === 'I') {
12070
+ const alphaStyle = token === 'i' ? 'lower-alpha' : 'upper-alpha';
12071
+ if (this.continuesAlphaList(openList, level, listId, alphaStyle)) {
12072
+ return alphaStyle;
12073
+ }
12074
+ }
12075
+ // Roman tokens are checked before alpha so "i" / "I" (Roman numeral 1) and
12076
+ // multi-character sequences like "ii" / "IV" are not mistaken for letters.
12077
+ if (LOWER_ROMAN_PATTERN.test(token)) {
12078
+ return 'lower-roman';
12079
+ }
12080
+ if (UPPER_ROMAN_PATTERN.test(token)) {
12081
+ return 'upper-roman';
12082
+ }
12083
+ if (LOWER_ALPHA_PATTERN.test(token)) {
12084
+ return 'lower-alpha';
12085
+ }
12086
+ if (UPPER_ALPHA_PATTERN.test(token)) {
12087
+ return 'upper-alpha';
12088
+ }
12089
+ return null;
12090
+ }
12091
+ /**
12092
+ * Reports whether a lone `i` / `I` marker continues an already-open alpha
12093
+ * list rather than starting a Roman one. True only when the same-depth list
12094
+ * is a matching alpha `<ol>` for the same Word list instance, so an ambiguous
12095
+ * marker at item 9 stays part of the surrounding `lower-alpha` / `upper-alpha`
12096
+ * run instead of opening a Roman sibling.
12097
+ *
12098
+ * @param {OpenList | undefined} openList Same-depth list already open, if any.
12099
+ * @param {number} level Word nesting depth of the incoming item.
12100
+ * @param {string | null} listId Word list instance id of the incoming item.
12101
+ * @param {string} alphaStyle The alpha style the marker would continue.
12102
+ * @returns {boolean} `true` when the marker should be treated as alpha.
12103
+ */
12104
+ continuesAlphaList(openList, level, listId, alphaStyle) {
12105
+ if (!openList || !openList.ordered || openList.level !== level) {
12106
+ return false;
12107
+ }
12108
+ // Distinct Word list instances are separate lists; do not carry style over.
12109
+ if (openList.listId !== null && listId !== null && openList.listId !== listId) {
12110
+ return false;
12111
+ }
12112
+ return this.readListStyleType(openList.listEl) === alphaStyle;
12113
+ }
12114
+ /**
12115
+ * Resolves the effective `font-family` for a marker by walking up to the
12116
+ * paragraph, so Word's `Symbol` / `Wingdings` bullet fonts can be detected.
12117
+ *
12118
+ * @param {HTMLElement} marker Marker span.
12119
+ * @param {HTMLElement} paragraph Owning list paragraph.
12120
+ * @returns {string} The resolved font family, or an empty string.
12121
+ */
12122
+ resolveMarkerFont(marker, paragraph) {
12123
+ let node = marker;
12124
+ while (node && node !== paragraph.parentElement) {
12125
+ const match = FONT_FAMILY_PATTERN.exec(node.getAttribute('data-style') || '');
12126
+ if (match) {
12127
+ return match[1];
12128
+ }
12129
+ node = node.parentElement;
12130
+ }
12131
+ return '';
12132
+ }
12133
+ }
12134
+
12135
+ /**
12136
+ * Normalizes Word image markup into plain <img> tags, drops list-marker images,
12137
+ * and optionally remaps image source paths.
12138
+ */
12139
+ class WordImageTransformer {
12140
+ /** @inheritDoc */
12141
+ getAllowedTags() {
12142
+ return ['IMG'];
12143
+ }
12144
+ /** @inheritDoc */
12145
+ getAllowedAttributes() {
12146
+ return new Map([['IMG', ['src', 'alt', 'title', 'width', 'height']]]);
12147
+ }
12148
+ /** @inheritDoc */
12149
+ getAllowedStyles() {
12150
+ return [];
12151
+ }
12152
+ /** @inheritDoc */
12153
+ apply(root) {
12154
+ this.normalizeWordImageDataElements(root);
12155
+ this.normalizeStandardImages(root);
12156
+ this.liftImagesOutOfTextStyleBlocks(root);
12157
+ this.insertSpanBetweenAdjacentImages(root);
12158
+ }
12159
+ /**
12160
+ * Lifts images out of text-style blocks (`h1`-`h6`, Word title / subtitle
12161
+ * paragraphs) so they become standalone top-level images.
12162
+ *
12163
+ * The RTE data model represents these blocks as text blocks whose children
12164
+ * are text nodes only, so an `<img>` nested inside one has no text-node
12165
+ * representation and would be silently dropped during DTO conversion. Moving
12166
+ * each image to a sibling position right after its block lets the downstream
12167
+ * image-block strategies pick it up. Images are re-inserted in document order
12168
+ * so they stay aligned with the RTF files paired later.
12169
+ *
12170
+ * Word title / subtitle paragraphs still carry their raw `MsoTitle` /
12171
+ * `MsoSubtitle` class here because {@link WordParagraphRoleTransformer} runs
12172
+ * after this transformer.
12173
+ *
12174
+ * @param {HTMLElement} root Parsed Word fragment root.
12175
+ */
12176
+ liftImagesOutOfTextStyleBlocks(root) {
12177
+ const blockElements = Array.from(root.querySelectorAll('h1, h2, h3, h4, h5, h6, p')).filter((element) => this.isImageLiftTargetBlock(element));
12178
+ blockElements.forEach((blockElement) => {
12179
+ if (!blockElement.parentNode) {
12180
+ return;
12181
+ }
12182
+ const imageElements = Array.from(blockElement.querySelectorAll('img'));
12183
+ let referenceNode = blockElement;
12184
+ imageElements.forEach((imageElement) => {
12185
+ blockElement.parentNode.insertBefore(imageElement, referenceNode.nextSibling);
12186
+ referenceNode = imageElement;
12187
+ });
12188
+ });
12189
+ }
12190
+ /**
12191
+ * Reports whether a block hosts its images as text-only children, requiring
12192
+ * nested images to be lifted out: any heading (`h1`-`h6`) or a Word title /
12193
+ * subtitle paragraph.
12194
+ *
12195
+ * @param {HTMLElement} element Candidate block element.
12196
+ * @returns {boolean} `true` when nested images must be lifted out of the block.
12197
+ */
12198
+ isImageLiftTargetBlock(element) {
12199
+ const tagName = element.tagName.toLowerCase();
12200
+ if (/^h[1-6]$/.test(tagName)) {
12201
+ return true;
12202
+ }
12203
+ if (tagName === 'p') {
12204
+ const className = (element.getAttribute('class') || '').toLowerCase();
12205
+ return /msotitle/.test(className) || /msosubtitle/.test(className);
12206
+ }
12207
+ return false;
12208
+ }
12209
+ /**
12210
+ * Converts Word VML `v:imagedata` elements to plain `<img>` elements.
12211
+ *
12212
+ * @param {HTMLElement} root Parsed Word fragment root.
12213
+ */
12214
+ normalizeWordImageDataElements(root) {
12215
+ const imageDataElements = Array.from(root.querySelectorAll('v\\:imagedata, imagedata'));
12216
+ imageDataElements.forEach((imageDataElement) => {
12217
+ const src = imageDataElement.getAttribute('src') ||
12218
+ imageDataElement.getAttribute('o:href') ||
12219
+ imageDataElement.getAttribute('href') ||
12220
+ '';
12221
+ const alt = imageDataElement.getAttribute('alt') ||
12222
+ imageDataElement.getAttribute('o:title') ||
12223
+ imageDataElement.getAttribute('title') ||
12224
+ '';
12225
+ const wrapperElement = imageDataElement.closest('v\\:shape, shape') || imageDataElement;
12226
+ if (!src) {
12227
+ wrapperElement.remove();
12228
+ return;
12229
+ }
12230
+ // Non-local list images have no RTF counterpart, so drop them now. Local
12231
+ // (file://) list images are kept as placeholders and removed later during
12232
+ // preview pairing so the image order stays aligned with the RTF files.
12233
+ if (this.isListImage(wrapperElement) && !src.startsWith('file://')) {
12234
+ wrapperElement.remove();
12235
+ return;
12236
+ }
12237
+ const imageElement = root.ownerDocument.createElement('img');
12238
+ imageElement.setAttribute('src', src);
12239
+ if (alt) {
12240
+ imageElement.setAttribute('alt', alt);
12241
+ }
12242
+ if (wrapperElement.parentNode) {
12243
+ const shapeId = wrapperElement.getAttribute('id') || '';
12244
+ if (shapeId) {
12245
+ this.removeFallbackImagesForShapeId(root, shapeId);
12246
+ }
12247
+ wrapperElement.parentNode.insertBefore(imageElement, wrapperElement);
12248
+ wrapperElement.parentNode.removeChild(wrapperElement);
12249
+ }
12250
+ });
12251
+ }
12252
+ /**
12253
+ * Removes non-VML fallback images that reference a specific Word VML shape id.
12254
+ *
12255
+ * Word clipboard HTML often includes both `v:imagedata` and an `<img v:shapes="...">`
12256
+ * fallback for the same visual image. Once VML is normalized, the fallback must
12257
+ * be removed to avoid duplicated image handling.
12258
+ *
12259
+ * @param {HTMLElement} root Parsed Word fragment root.
12260
+ * @param {string} shapeId VML shape id referenced by fallback images.
12261
+ */
12262
+ removeFallbackImagesForShapeId(root, shapeId) {
12263
+ const imageElements = Array.from(root.querySelectorAll('img'));
12264
+ imageElements.forEach((imageElement) => {
12265
+ const referencedShapeId = imageElement.getAttribute('v:shapes') || imageElement.getAttribute('shapes') || '';
12266
+ if (referencedShapeId === shapeId) {
12267
+ imageElement.remove();
12268
+ }
12269
+ });
12270
+ }
12271
+ /**
12272
+ * Removes list-marker images and sourceless `<img>` elements.
12273
+ *
12274
+ * @param {HTMLElement} root Parsed Word fragment root.
12275
+ */
12276
+ normalizeStandardImages(root) {
12277
+ const imageElements = Array.from(root.querySelectorAll('img'));
12278
+ imageElements.forEach((imageElement) => {
12279
+ const src = imageElement.getAttribute('src') || '';
12280
+ if (this.isListImage(imageElement)) {
12281
+ // Non-local list images have no RTF counterpart, so drop them now. Local
12282
+ // (file://) list images are kept as placeholders and removed later during
12283
+ // preview pairing so the image order stays aligned with the RTF files.
12284
+ if (!src.startsWith('file://')) {
12285
+ imageElement.remove();
12286
+ }
12287
+ return;
12288
+ }
12289
+ if (!src) {
12290
+ imageElement.remove();
12291
+ }
12292
+ });
12293
+ }
12294
+ /**
12295
+ * Inserts an empty `<span> </span>` between adjacent images to avoid
12296
+ * downstream image-block parsing treating a compact image run as a single image.
12297
+ *
12298
+ * @param {HTMLElement} root Parsed Word fragment root.
12299
+ */
12300
+ insertSpanBetweenAdjacentImages(root) {
12301
+ const imageElements = Array.from(root.querySelectorAll('img'));
12302
+ imageElements.forEach((imageElement) => {
12303
+ if (!imageElement.parentNode) {
12304
+ return;
12305
+ }
12306
+ const nextSibling = imageElement.nextSibling;
12307
+ if (nextSibling &&
12308
+ nextSibling.nodeType === Node.ELEMENT_NODE &&
12309
+ nextSibling.tagName.toLowerCase() === 'img') {
12310
+ const spacerElement = root.ownerDocument.createElement('span');
12311
+ spacerElement.innerHTML = ' ';
12312
+ imageElement.parentNode.insertBefore(spacerElement, nextSibling);
12313
+ }
12314
+ });
12315
+ }
12316
+ /**
12317
+ * Currently the RTE does not support list images, so they must be removed from the paste content.
12318
+ * Local (file://) list images are kept as placeholders here and removed later during preview pairing
12319
+ * so the local images stay aligned with the RTF images; non-local list images are removed immediately.
12320
+ * Returns true when the image is part of list content and should be ignored.
12321
+ *
12322
+ * Word list markup (`MsoListParagraph` / `mso-list`) is already normalized into
12323
+ * real `li/ul/ol` by {@link WordListRoleTransformer}, which runs before this
12324
+ * transformer, so a plain list-ancestor check is sufficient here.
12325
+ *
12326
+ * @param {Element} element Image element (or its wrapper).
12327
+ * @returns {boolean} Whether this image belongs to list content.
12328
+ */
12329
+ isListImage(element) {
12330
+ return Boolean(element.closest('li,ul,ol'));
12331
+ }
12332
+ }
12333
+
12334
+ /**
12335
+ * Attribute that carries inline styles after `StylePreservator` renamed `style`
12336
+ * to `data-style`. All style reads/writes in Phase 1 target this name so the
12337
+ * declarations survive the CSP-sensitive `DOMParser` boundary (mirrors the
12338
+ * handling in {@link WordListRoleTransformer}).
12339
+ */
12340
+ const PRESERVED_STYLE_ATTR$1 = 'data-style';
12341
+ /** Table structural tags Word decorates with `Mso*` class artifacts. */
12342
+ const TABLE_SECTION_SELECTOR = 'thead, tbody, tfoot, caption, tr';
12343
+ /**
12344
+ * Table containers whose direct text children are always layout whitespace.
12345
+ * Word exports newlines and indentation between structural tags; those text
12346
+ * nodes are not valid table content and must be dropped so they do not surface
12347
+ * as stray spaces in the pasted table.
12348
+ */
12349
+ const TABLE_CONTAINER_SELECTOR = 'thead, tbody, tfoot, tr';
12350
+ /** Cell tags that may carry Word presentational attributes and span markers. */
12351
+ const TABLE_CELL_SELECTOR = 'td, th';
12352
+ /**
12353
+ * Block-level tags that mark a cell as containing block content. When a cell
12354
+ * holds one of these, any whitespace-only text node sitting beside it is Word
12355
+ * indentation (layout whitespace) rather than meaningful inline text.
12356
+ */
12357
+ const CELL_BLOCK_CHILD_TAGS = new Set([
12358
+ 'P',
12359
+ 'DIV',
12360
+ 'UL',
12361
+ 'OL',
12362
+ 'LI',
12363
+ 'H1',
12364
+ 'H2',
12365
+ 'H3',
12366
+ 'H4',
12367
+ 'H5',
12368
+ 'H6',
12369
+ 'TABLE',
12370
+ 'BLOCKQUOTE',
12371
+ 'PRE',
12372
+ ]);
12373
+ /** Span attributes whose value must be a positive integer greater than one. */
12374
+ const SPAN_ATTRIBUTES = ['colspan', 'rowspan'];
12375
+ /** Strict span values Word should emit after normalization. */
12376
+ const SPAN_VALUE_PATTERN = /^\d+$/;
12377
+ /**
12378
+ * Matches the border box-model declarations (shorthand, per-side, and the
12379
+ * width/style/color long-hands) that Word writes with point/centimeter units
12380
+ * and system color keywords the RTE table model cannot parse. Word also emits
12381
+ * per-side `border-*: none` resets to avoid double borders in its own layout,
12382
+ * which would leave the pasted table with inconsistent cell borders. All of
12383
+ * these are removed so the editor applies its uniform default border.
12384
+ * `border-collapse` / `border-spacing` are intentionally excluded so table
12385
+ * collapsing is preserved.
12386
+ */
12387
+ const BORDER_BOX_STYLE_PATTERN = /^border(-(top|right|bottom|left))?(-(width|style|color))?$/;
12388
+ /**
12389
+ * Matches padding declarations (shorthand and per-side) that Word writes with
12390
+ * point/centimeter units the RTE cannot parse. Removed so the editor applies
12391
+ * its default cell padding.
12392
+ */
12393
+ const PADDING_STYLE_PATTERN = /^padding(-(top|right|bottom|left))?$/;
12394
+ /**
12395
+ * Matches the standalone `width` declaration (not `min-width` / `max-width`).
12396
+ * Word writes cell widths in points, so the point-based declaration is removed
12397
+ * and replaced with the clean pixel value carried by the `width` attribute.
12398
+ */
12399
+ const WIDTH_STYLE_PATTERN = /^width$/;
12400
+ /** Numeric zero (with optional decimal and optional unit suffix). */
12401
+ const ZERO_LENGTH_PATTERN = /^0+(\.0+)?([a-z%]+)?$/i;
12402
+ /**
12403
+ * Maps Word cell presentational attributes onto the CSS declarations the RTE
12404
+ * table cell annotation understands. These attributes are not on the paste
12405
+ * allowlist, so migrating them into `data-style` is the only way to preserve
12406
+ * the author's intent (shading, alignment) through sanitization. Only
12407
+ * properties whose prefix is retained by the sanitizer are migrated, so
12408
+ * fixed-width/height artifacts are intentionally dropped.
12409
+ */
12410
+ const CELL_ATTRIBUTE_STYLE_MAP = [
12411
+ // eslint-disable-next-line spellcheck/spell-checker
12412
+ { attribute: 'valign', property: 'vertical-align' },
12413
+ { attribute: 'align', property: 'text-align' },
12414
+ // eslint-disable-next-line spellcheck/spell-checker
12415
+ { attribute: 'bgcolor', property: 'background-color' },
12416
+ ];
12417
+ /**
12418
+ * Normalizes Microsoft Word table markup into clean, RTE-friendly tables.
12419
+ *
12420
+ * Word emits real `<table>/<tr>/<td>` elements, so the paste allowlist already
12421
+ * keeps the structure. What it cannot fix is the Word-specific decoration that
12422
+ * rides along:
12423
+ * - `Mso*` class names (e.g. `MsoTableGrid`) survive because `class` is globally
12424
+ * allowed; they are Word artifacts and must not leak into the output.
12425
+ * - Cell shading and alignment live in presentational attributes (`bgcolor`,
12426
+ * `align`, `valign`) that the allowlist strips, losing the formatting; they
12427
+ * are migrated into `data-style` so the RTE cell annotation can read them.
12428
+ * - `colspan`/`rowspan` may carry redundant `1` values or non-numeric junk that
12429
+ * is normalized so span tables map cleanly onto the RTE table model.
12430
+ * - Table and cell borders/padding are written with point/centimeter units,
12431
+ * system color keywords, and per-side `border-*: none` resets that the RTE
12432
+ * cannot parse and that render inconsistent borders; they are stripped so the
12433
+ * editor applies its uniform default border and padding.
12434
+ * - Cell widths arrive both as a point-based `width` style (which the RTE cannot
12435
+ * parse) and a pixel `width` attribute; the point style is dropped and the
12436
+ * attribute is migrated into a clean pixel `width` declaration.
12437
+ */
12438
+ class WordTableTransformer {
12439
+ /** @inheritDoc */
12440
+ apply(root) {
12441
+ Array.from(root.querySelectorAll('table')).forEach((table) => this.normalizeTable(table));
12442
+ }
12443
+ /** @inheritDoc */
12444
+ getAllowedTags() {
12445
+ return ['TABLE', 'THEAD', 'TBODY', 'TR', 'TH', 'TD', 'CAPTION'];
12446
+ }
12447
+ /** @inheritDoc */
12448
+ getAllowedAttributes() {
12449
+ return new Map([
12450
+ ['TD', ['colspan', 'rowspan']],
12451
+ ['TH', ['colspan', 'rowspan']],
12452
+ ]);
12453
+ }
12454
+ /** @inheritDoc */
12455
+ getAllowedStyles() {
12456
+ return ['border', 'width'];
12457
+ }
12458
+ /**
12459
+ * Strips Word class artifacts from a table and its structural descendants,
12460
+ * then normalizes every cell's span attributes and presentational styling.
12461
+ *
12462
+ * @param {HTMLTableElement} table Word table element to normalize in place.
12463
+ */
12464
+ normalizeTable(table) {
12465
+ this.removeStructuralWhitespace(table);
12466
+ this.stripClass(table);
12467
+ this.removeBorderAndPaddingStyles(table);
12468
+ Array.from(table.querySelectorAll(TABLE_SECTION_SELECTOR)).forEach((section) => this.stripClass(section));
12469
+ this.removeHiddenMisalignedCells(table);
12470
+ this.mergeFooterIntoBody(table);
12471
+ this.promoteHeadCells(table);
12472
+ Array.from(table.querySelectorAll(TABLE_CELL_SELECTOR)).forEach((cell) => {
12473
+ this.removeCellLayoutWhitespace(cell);
12474
+ this.stripClass(cell);
12475
+ this.normalizeSpanAttributes(cell);
12476
+ this.migrateCellAttributes(cell);
12477
+ this.migrateCellWidth(cell);
12478
+ this.removeBorderAndPaddingStyles(cell);
12479
+ });
12480
+ }
12481
+ /**
12482
+ * Removes Word's hidden `!supportMisalignedRows` placeholder cell. Word
12483
+ * appends it as a zero-width trailing cell to keep legacy layout engines
12484
+ * aligned, but it is not real content and should not survive paste as an
12485
+ * extra visible column. Only the row's last cell is considered so an
12486
+ * intentional interior zero-width cell is never dropped, and only rows that
12487
+ * belong to this table (not a nested one) are touched.
12488
+ *
12489
+ * @param {HTMLTableElement} table Word table element.
12490
+ */
12491
+ removeHiddenMisalignedCells(table) {
12492
+ Array.from(table.querySelectorAll('tr'))
12493
+ .filter((row) => this.isRowOfTable(table, row))
12494
+ .forEach((row) => {
12495
+ const lastCell = row.lastElementChild;
12496
+ if (lastCell &&
12497
+ (lastCell.tagName === 'TD' || lastCell.tagName === 'TH') &&
12498
+ this.isHiddenMisalignedCell(lastCell)) {
12499
+ row.removeChild(lastCell);
12500
+ }
12501
+ });
12502
+ }
12503
+ /**
12504
+ * Reports whether a row belongs directly to the given table rather than to a
12505
+ * nested table, so the recursive `tr` query never reaches into a child table.
12506
+ *
12507
+ * @param {HTMLTableElement} table Table being normalized.
12508
+ * @param {HTMLTableRowElement} row Row to test.
12509
+ * @returns {boolean} `true` when the row's nearest ancestor table is `table`.
12510
+ */
12511
+ isRowOfTable(table, row) {
12512
+ let ancestor = row.parentElement;
12513
+ while (ancestor && ancestor.tagName !== 'BODY') {
12514
+ if (ancestor.tagName === 'TABLE') {
12515
+ return ancestor === table;
12516
+ }
12517
+ ancestor = ancestor.parentElement;
12518
+ }
12519
+ return false;
12520
+ }
12521
+ /**
12522
+ * Detects Word's hidden misaligned-row placeholder cell.
12523
+ *
12524
+ * @param {HTMLTableCellElement} cell Cell to inspect.
12525
+ * @returns {boolean} `true` when the cell is a hidden placeholder.
12526
+ */
12527
+ isHiddenMisalignedCell(cell) {
12528
+ if (this.hasExplicitSpan(cell)) {
12529
+ return false;
12530
+ }
12531
+ if (!this.hasZeroWidthMarker(cell)) {
12532
+ return false;
12533
+ }
12534
+ return !this.hasMeaningfulCellContent(cell);
12535
+ }
12536
+ /**
12537
+ * Reports whether a cell explicitly spans rows/columns.
12538
+ *
12539
+ * @param {HTMLTableCellElement} cell Cell to inspect.
12540
+ * @returns {boolean} `true` when colspan/rowspan is present.
12541
+ */
12542
+ hasExplicitSpan(cell) {
12543
+ return SPAN_ATTRIBUTES.some((attribute) => cell.hasAttribute(attribute));
12544
+ }
12545
+ /**
12546
+ * Reports whether a cell is explicitly marked with zero width via attribute
12547
+ * or preserved inline style.
12548
+ *
12549
+ * @param {HTMLTableCellElement} cell Cell to inspect.
12550
+ * @returns {boolean} `true` when width is explicitly zero.
12551
+ */
12552
+ hasZeroWidthMarker(cell) {
12553
+ const widthAttribute = cell.getAttribute('width');
12554
+ if (widthAttribute !== null && ZERO_LENGTH_PATTERN.test(widthAttribute.trim())) {
12555
+ return true;
12556
+ }
12557
+ const style = cell.getAttribute(PRESERVED_STYLE_ATTR$1);
12558
+ if (style === null) {
12559
+ return false;
12560
+ }
12561
+ const widthDeclarationValue = this.readStyleDeclaration(style, 'width');
12562
+ return widthDeclarationValue !== null && ZERO_LENGTH_PATTERN.test(widthDeclarationValue);
12563
+ }
12564
+ /**
12565
+ * Reports whether a cell has meaningful text content. `trim()` already treats
12566
+ * the non-breaking space Word emits as blank, so a zero-width cell holding
12567
+ * only markup (including an image) is intentionally treated as empty and
12568
+ * removed.
12569
+ *
12570
+ * @param {HTMLTableCellElement} cell Cell to inspect.
12571
+ * @returns {boolean} `true` when the cell has meaningful text.
12572
+ */
12573
+ hasMeaningfulCellContent(cell) {
12574
+ return (cell.textContent || '').trim() !== '';
12575
+ }
12576
+ /**
12577
+ * Reads one declaration value from a style string.
12578
+ *
12579
+ * @param {string} style Preserved style text.
12580
+ * @param {string} property CSS property name to read.
12581
+ * @returns {string | null} Declaration value when present.
12582
+ */
12583
+ readStyleDeclaration(style, property) {
12584
+ const declarations = style
12585
+ .split(';')
12586
+ .map((declaration) => declaration.trim())
12587
+ .filter((declaration) => declaration !== '');
12588
+ for (const declaration of declarations) {
12589
+ const separator = declaration.indexOf(':');
12590
+ if (separator === -1) {
12591
+ continue;
12592
+ }
12593
+ const name = declaration.slice(0, separator).trim().toLowerCase();
12594
+ if (name !== property.toLowerCase()) {
12595
+ continue;
12596
+ }
12597
+ const value = declaration.slice(separator + 1).trim();
12598
+ return value === '' ? null : value;
12599
+ }
12600
+ return null;
12601
+ }
12602
+ /**
12603
+ * Collapses every `<tfoot>` into the table body because the RTE table model
12604
+ * has no footer section. Left in place, an unsupported `<tfoot>` is unwrapped
12605
+ * by the paste sanitizer into a bare `<tr>` under `<table>`, so its rows lose
12606
+ * their section placement. When the table already has a `<tbody>`, the
12607
+ * footer's rows are appended to the last body so they stay in document order;
12608
+ * otherwise the `<tfoot>` itself is renamed to `<tbody>` so its rows survive
12609
+ * as body content.
12610
+ *
12611
+ * @param {HTMLTableElement} table Word table element.
12612
+ */
12613
+ mergeFooterIntoBody(table) {
12614
+ const footers = Array.from(table.children).filter((child) => child.tagName === 'TFOOT');
12615
+ footers.forEach((footer) => {
12616
+ const bodies = Array.from(table.children).filter((child) => child.tagName === 'TBODY');
12617
+ const targetBody = bodies[bodies.length - 1];
12618
+ if (targetBody) {
12619
+ while (footer.firstChild) {
12620
+ targetBody.appendChild(footer.firstChild);
12621
+ }
12622
+ if (footer.parentNode) {
12623
+ footer.parentNode.removeChild(footer);
12624
+ }
12625
+ }
12626
+ else {
12627
+ this.replaceElementTag(footer, 'tbody');
12628
+ }
12629
+ });
12630
+ }
12631
+ /**
12632
+ * Promotes `<td>` cells that Word places directly inside a `<thead>` row to
12633
+ * `<th>`. The RTE identifies header cells solely by the `TH` tag, so without
12634
+ * this the head row would be parsed as ordinary body cells and lose its
12635
+ * header annotations and rendering. Only direct `thead > tr > td` cells are
12636
+ * converted so cells of a nested table are never touched.
12637
+ *
12638
+ * @param {HTMLTableElement} table Word table element.
12639
+ */
12640
+ promoteHeadCells(table) {
12641
+ Array.from(table.querySelectorAll('thead')).forEach((head) => {
12642
+ Array.from(head.children).forEach((row) => {
12643
+ if (row.tagName !== 'TR') {
12644
+ return;
12645
+ }
12646
+ Array.from(row.children).forEach((cell) => {
12647
+ if (cell.tagName === 'TD') {
12648
+ this.renameCell(cell, 'th');
12649
+ }
12650
+ });
12651
+ });
12652
+ });
12653
+ }
12654
+ /**
12655
+ * Replaces a table cell element with an equivalent cell of a different tag,
12656
+ * preserving its attributes and child nodes.
12657
+ *
12658
+ * @param {HTMLTableCellElement} cell Cell to replace.
12659
+ * @param {string} tagName Target cell tag name (`th` / `td`).
12660
+ */
12661
+ renameCell(cell, tagName) {
12662
+ this.replaceElementTag(cell, tagName);
12663
+ }
12664
+ /**
12665
+ * Replaces an element with an equivalent element of a different tag,
12666
+ * preserving its attributes and child nodes and its position in the tree.
12667
+ *
12668
+ * @param {Element} element Element to replace.
12669
+ * @param {string} tagName Target tag name.
12670
+ * @returns {HTMLElement} The replacement element.
12671
+ */
12672
+ replaceElementTag(element, tagName) {
12673
+ const replacement = element.ownerDocument.createElement(tagName);
12674
+ Array.from(element.attributes).forEach((attr) => {
12675
+ replacement.setAttribute(attr.name, attr.value);
12676
+ });
12677
+ while (element.firstChild) {
12678
+ replacement.appendChild(element.firstChild);
12679
+ }
12680
+ if (element.parentNode) {
12681
+ element.parentNode.replaceChild(replacement, element);
12682
+ }
12683
+ return replacement;
12684
+ }
12685
+ /**
12686
+ * Removes the `class` attribute so Word style class names cannot leak into
12687
+ * the sanitized output (the global allowlist keeps `class` on every tag).
12688
+ *
12689
+ * @param {HTMLElement} element Table structural element.
12690
+ */
12691
+ stripClass(element) {
12692
+ if (element.hasAttribute('class')) {
12693
+ element.removeAttribute('class');
12694
+ }
12695
+ }
12696
+ /**
12697
+ * Drops whitespace-only text nodes that sit directly inside the table and its
12698
+ * row containers. Word exports newlines/indentation between structural tags,
12699
+ * which are invalid as table content and would otherwise render as stray
12700
+ * spaces once the table is pasted.
12701
+ *
12702
+ * @param {HTMLTableElement} table Word table element.
12703
+ */
12704
+ removeStructuralWhitespace(table) {
12705
+ const containers = [
12706
+ table,
12707
+ ...Array.from(table.querySelectorAll(TABLE_CONTAINER_SELECTOR)),
12708
+ ];
12709
+ containers.forEach((container) => {
12710
+ Array.from(container.childNodes).forEach((node) => {
12711
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent || '').trim() === '') {
12712
+ container.removeChild(node);
12713
+ }
12714
+ });
12715
+ });
12716
+ }
12717
+ /**
12718
+ * Drops whitespace-only text nodes that sit directly inside a cell whose
12719
+ * content is block-level (e.g. Word wraps every cell's text in `<p>`). Word
12720
+ * indents its markup, so these text nodes are pure layout whitespace; left in
12721
+ * place each one is wrapped into its own paragraph by the RTE, producing the
12722
+ * stray empty lines seen above and below the real cell content. Cells that
12723
+ * only hold inline content are skipped so meaningful spaces between inline
12724
+ * runs are never merged.
12725
+ *
12726
+ * @param {HTMLTableCellElement} cell Table cell element.
12727
+ */
12728
+ removeCellLayoutWhitespace(cell) {
12729
+ const hasBlockChild = Array.from(cell.children).some((child) => CELL_BLOCK_CHILD_TAGS.has(child.tagName));
12730
+ if (!hasBlockChild) {
12731
+ return;
12732
+ }
12733
+ Array.from(cell.childNodes).forEach((node) => {
12734
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent || '').trim() === '') {
12735
+ cell.removeChild(node);
12736
+ }
12737
+ });
12738
+ }
12739
+ /**
12740
+ * Normalizes `colspan` / `rowspan` values: a redundant span of `1` or any
12741
+ * non-numeric value is removed, and valid spans are rewritten as a clean
12742
+ * integer so the RTE table model reads them consistently.
12743
+ *
12744
+ * @param {HTMLTableCellElement} cell Table cell element.
12745
+ */
12746
+ normalizeSpanAttributes(cell) {
12747
+ SPAN_ATTRIBUTES.forEach((attribute) => {
12748
+ const raw = cell.getAttribute(attribute);
12749
+ if (raw === null) {
12750
+ return;
12751
+ }
12752
+ const value = Number(raw);
12753
+ if (!SPAN_VALUE_PATTERN.test(raw) || value <= 1) {
12754
+ cell.removeAttribute(attribute);
12755
+ }
12756
+ else {
12757
+ cell.setAttribute(attribute, String(value));
12758
+ }
12759
+ });
12760
+ }
12761
+ /**
12762
+ * Migrates Word cell presentational attributes into `data-style` so the RTE
12763
+ * cell annotation can read the shading and alignment, then removes the
12764
+ * original attributes. An existing declaration for the same property always
12765
+ * wins and is never overwritten.
12766
+ *
12767
+ * @param {HTMLTableCellElement} cell Table cell element.
12768
+ */
12769
+ migrateCellAttributes(cell) {
12770
+ CELL_ATTRIBUTE_STYLE_MAP.forEach(({ attribute, property }) => {
12771
+ const value = cell.getAttribute(attribute);
12772
+ if (value && value.trim() !== '') {
12773
+ this.appendStyleDeclaration(cell, property, value.trim().toLowerCase());
12774
+ }
12775
+ cell.removeAttribute(attribute);
12776
+ });
12777
+ }
12778
+ /**
12779
+ * Converts a cell's `width` attribute into a `width` style declaration. Word
12780
+ * emits the width both as a point-based inline style (which the RTE cannot
12781
+ * parse) and as a pixel `width` attribute; the point style is dropped and the
12782
+ * attribute value becomes a clean pixel `width` declaration (a bare number is
12783
+ * treated as pixels, any explicit unit such as `%` is kept as-is).
12784
+ *
12785
+ * @param {HTMLTableCellElement} cell Table cell element.
12786
+ */
12787
+ migrateCellWidth(cell) {
12788
+ const attributeWidth = cell.getAttribute('width');
12789
+ cell.removeAttribute('width');
12790
+ // Drop Word's point-based width so it cannot survive as an unparseable value.
12791
+ this.removeMatchingStyleDeclarations(cell, (property) => WIDTH_STYLE_PATTERN.test(property));
12792
+ const cssWidth = this.toCssWidth(attributeWidth);
12793
+ if (cssWidth !== null) {
12794
+ this.appendStyleDeclaration(cell, 'width', cssWidth);
12795
+ }
12796
+ }
12797
+ /**
12798
+ * Normalizes a cell `width` attribute value into a CSS width value. A bare
12799
+ * number is treated as pixels; any value carrying an explicit unit (e.g. `%`)
12800
+ * is kept as-is. An empty or missing value yields `null`.
12801
+ *
12802
+ * @param {string | null} value Raw `width` attribute value.
12803
+ * @returns {string | null} CSS width value, or `null` when there is nothing to migrate.
12804
+ */
12805
+ toCssWidth(value) {
12806
+ if (value === null || value.trim() === '') {
12807
+ return null;
12808
+ }
12809
+ const trimmed = value.trim();
12810
+ return /^\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
12811
+ }
12812
+ /**
12813
+ * Appends a `property: value` declaration to the preserved style attribute
12814
+ * unless the property is already declared.
12815
+ *
12816
+ * @param {HTMLElement} element Element receiving the declaration.
12817
+ * @param {string} property CSS property name.
12818
+ * @param {string} value CSS value.
12819
+ */
12820
+ appendStyleDeclaration(element, property, value) {
12821
+ const existing = element.getAttribute(PRESERVED_STYLE_ATTR$1) || '';
12822
+ if (this.styleHasProperty(existing, property)) {
12823
+ return;
12824
+ }
12825
+ const prefix = existing.trim() === '' ? '' : `${existing.replace(/;\s*$/, '')}; `;
12826
+ element.setAttribute(PRESERVED_STYLE_ATTR$1, `${prefix}${property}: ${value}`);
12827
+ }
12828
+ /**
12829
+ * Removes every border box-model and padding declaration from the preserved
12830
+ * style so Word's point/centimeter units, system color keywords, and per-side
12831
+ * `border-*: none` resets cannot reach the RTE. Once these are gone the table
12832
+ * and cell annotations fall back to the editor's default border
12833
+ * (`solid 1px #ddd`) and padding (`12px`), giving every pasted cell a uniform
12834
+ * border. `border-collapse` / `border-spacing` are preserved so table
12835
+ * collapsing is unaffected.
12836
+ *
12837
+ * @param {HTMLElement} element Table or cell element to normalize.
12838
+ */
12839
+ removeBorderAndPaddingStyles(element) {
12840
+ this.removeMatchingStyleDeclarations(element, (property) => BORDER_BOX_STYLE_PATTERN.test(property) || PADDING_STYLE_PATTERN.test(property));
12841
+ }
12842
+ /**
12843
+ * Removes every preserved-style declaration whose property name matches the
12844
+ * given predicate, rewriting (or dropping) the `data-style` attribute.
12845
+ *
12846
+ * @param {HTMLElement} element Element whose preserved style is filtered.
12847
+ * @param {(property: string) => boolean} shouldRemove Predicate that returns
12848
+ * `true` for property names to drop (receives the lower-cased name).
12849
+ */
12850
+ removeMatchingStyleDeclarations(element, shouldRemove) {
12851
+ const raw = element.getAttribute(PRESERVED_STYLE_ATTR$1);
12852
+ if (raw === null || raw.trim() === '') {
12853
+ return;
12854
+ }
12855
+ const kept = raw
12856
+ .split(';')
12857
+ .map((declaration) => declaration.trim())
12858
+ .filter((declaration) => declaration !== '')
12859
+ .filter((declaration) => {
12860
+ const separator = declaration.indexOf(':');
12861
+ if (separator === -1) {
12862
+ return true;
12863
+ }
12864
+ const property = declaration.slice(0, separator).trim().toLowerCase();
12865
+ return !shouldRemove(property);
12866
+ });
12867
+ if (kept.length > 0) {
12868
+ element.setAttribute(PRESERVED_STYLE_ATTR$1, kept.join('; '));
12869
+ }
12870
+ else {
12871
+ element.removeAttribute(PRESERVED_STYLE_ATTR$1);
12872
+ }
12873
+ }
12874
+ /**
12875
+ * Reports whether a preserved style string already declares a property.
12876
+ *
12877
+ * @param {string} style Preserved style attribute value.
12878
+ * @param {string} property CSS property name.
12879
+ * @returns {boolean} `true` when the property is already present.
12880
+ */
12881
+ styleHasProperty(style, property) {
12882
+ return new RegExp(`(^|;)\\s*${property}\\s*:`, 'i').test(style);
12883
+ }
12884
+ }
12885
+
12886
+ /**
12887
+ * Attribute that the RTE code-block parser uses to recognize code content.
12888
+ */
12889
+ const CODE_LANGUAGE_ATTR = 'data-language';
12890
+ /**
12891
+ * Default language applied when pasted code has no language metadata.
12892
+ */
12893
+ const DEFAULT_CODE_LANGUAGE = 'plainText';
12894
+ /**
12895
+ * Matches language hints encoded as CSS classes (for example `language-js`).
12896
+ */
12897
+ const LANGUAGE_CLASS_PATTERN = /(?:^|\s)(?:language|lang)-([a-z0-9+_.-]+)(?:\s|$)/i;
12898
+ /**
12899
+ * Removes Word-only class artifacts from code elements while preserving other
12900
+ * classes. Kept as defense-in-depth: even though the shared sanitizer allowlist
12901
+ * generally strips `Mso*` classes, this transformer normalizes the `<pre>`
12902
+ * class list before language resolution so a stray `MsoPlainText` can never
12903
+ * leak into `LANGUAGE_CLASS_PATTERN` matching.
12904
+ */
12905
+ const WORD_CLASS_NAME_PATTERN = /^mso[a-z0-9_-]*$/i;
12906
+ /**
12907
+ * Normalizes pasted `<pre>` nodes so they can be parsed as RTE code blocks.
12908
+ *
12909
+ * The RTE recognizes a code block only when `data-language` is present on
12910
+ * `<pre>`. Word-exported snippets commonly use plain `<pre>` with no
12911
+ * language metadata, so this transformer adds a default language and copies any
12912
+ * detectable language hint from nested code markup when available.
12913
+ */
12914
+ class WordCodeBlockTransformer {
12915
+ /**
12916
+ * Determines whether a node is a code block whose whitespace must be preserved.
12917
+ *
12918
+ * @param {Node} node Node to inspect.
12919
+ * @returns {boolean} `true` when the node is a `<pre>` element.
12920
+ */
12921
+ static isCodeBlock(node) {
12922
+ return node.nodeType === Node.ELEMENT_NODE && node.tagName.toUpperCase() === 'PRE';
12923
+ }
12924
+ /** @inheritDoc */
12925
+ apply(root) {
12926
+ Array.from(root.querySelectorAll('pre')).forEach((preElement) => {
12927
+ this.stripWordClassArtifacts(preElement);
12928
+ if (!preElement.hasAttribute(CODE_LANGUAGE_ATTR)) {
12929
+ preElement.setAttribute(CODE_LANGUAGE_ATTR, this.resolveLanguage(preElement));
12930
+ }
12931
+ });
12932
+ }
12933
+ /** @inheritDoc */
12934
+ getAllowedTags() {
12935
+ return ['PRE'];
12936
+ }
12937
+ /** @inheritDoc */
12938
+ getAllowedAttributes() {
12939
+ return new Map([['PRE', [CODE_LANGUAGE_ATTR]]]);
12940
+ }
12941
+ /** @inheritDoc */
12942
+ getAllowedStyles() {
12943
+ return [];
12944
+ }
12945
+ /**
12946
+ * Resolves code language metadata from `<pre>` class hints, falling back to
12947
+ * {@link DEFAULT_CODE_LANGUAGE}.
12948
+ *
12949
+ * `apply()` only invokes this method when the `<pre>` has no
12950
+ * `data-language` attribute, so class-based inference is the only source
12951
+ * this method needs to consult.
12952
+ *
12953
+ * @param {HTMLElement} preElement Source `<pre>` element.
12954
+ * @returns {string} Resolved language id or `plainText` fallback.
12955
+ */
12956
+ resolveLanguage(preElement) {
12957
+ const classLanguage = this.resolveLanguageFromClassName(preElement).trim();
12958
+ return classLanguage !== '' ? classLanguage : DEFAULT_CODE_LANGUAGE;
12959
+ }
12960
+ /**
12961
+ * Extracts a language id from class names like `language-js` or `lang-ts`.
12962
+ *
12963
+ * @param {HTMLElement} preElement Source `<pre>` element.
12964
+ * @returns {string} Extracted language id or empty string.
12965
+ */
12966
+ resolveLanguageFromClassName(preElement) {
12967
+ const className = preElement.getAttribute('class') || '';
12968
+ const match = LANGUAGE_CLASS_PATTERN.exec(className);
12969
+ return match ? match[1] : '';
12970
+ }
12971
+ /**
12972
+ * Removes Word-only `Mso*` class names and keeps any non-Word classes.
12973
+ *
12974
+ * @param {HTMLElement} preElement Source `<pre>` element.
12975
+ */
12976
+ stripWordClassArtifacts(preElement) {
12977
+ const className = preElement.getAttribute('class') || '';
12978
+ if (!className) {
12979
+ return;
12980
+ }
12981
+ const filteredClasses = className
12982
+ .split(/\s+/)
12983
+ .map((name) => name.trim())
12984
+ .filter((name) => name !== '' && !WORD_CLASS_NAME_PATTERN.test(name));
12985
+ if (filteredClasses.length === 0) {
12986
+ preElement.removeAttribute('class');
12987
+ return;
12988
+ }
12989
+ preElement.setAttribute('class', filteredClasses.join(' '));
12990
+ }
12991
+ }
12992
+
12993
+ /**
12994
+ * Default Word transformers. Sanitizer allowlist is aggregated from these
12995
+ * transformer contracts so adding a new transformer automatically extends the
12996
+ * allowlist surface.
12997
+ */
12998
+ const WORD_STRUCTURE_TRANSFORMERS = [
12999
+ new WordTableTransformer(),
13000
+ new WordListRoleTransformer(),
13001
+ new WordImageTransformer(),
13002
+ new WordCodeBlockTransformer(),
13003
+ new WordParagraphRoleTransformer(),
13004
+ ];
13005
+ /**
13006
+ * Aggregates the sanitizer allowlist from each transformer's declared contract
13007
+ * (tags, attributes, style prefixes), removing duplicates and normalizing case
13008
+ * so the strategy allowlist can never drift from the transformers that depend on
13009
+ * it.
13010
+ *
13011
+ * @param {WordStructureTransformer[]} transformers Registered structure transformers.
13012
+ * @returns {WordSanitizeConfig} Combined sanitizer configuration.
13013
+ */
13014
+ function buildWordSanitizeConfig(transformers) {
13015
+ const tags = new Set();
13016
+ const stylePrefixes = new Set();
13017
+ const attributesMap = new Map();
13018
+ transformers.forEach((transformer) => {
13019
+ transformer.getAllowedTags().forEach((tag) => {
13020
+ tags.add(tag.toUpperCase());
13021
+ });
13022
+ transformer.getAllowedStyles().forEach((prefix) => {
13023
+ stylePrefixes.add(prefix.toLowerCase());
13024
+ });
13025
+ transformer.getAllowedAttributes().forEach((attrs, tagName) => {
13026
+ const normalizedTagName = tagName === WORD_SANITIZE_ALL_TAGS_KEY ? tagName : tagName.toUpperCase();
13027
+ if (!attributesMap.has(normalizedTagName)) {
13028
+ attributesMap.set(normalizedTagName, new Set());
13029
+ }
13030
+ const attrSet = attributesMap.get(normalizedTagName);
13031
+ attrs.forEach((attr) => {
13032
+ attrSet.add(attr.toLowerCase());
13033
+ });
13034
+ });
13035
+ });
13036
+ const allowedAttributes = {};
13037
+ attributesMap.forEach((attrs, tagName) => {
13038
+ allowedAttributes[tagName] = Array.from(attrs);
13039
+ });
13040
+ return {
13041
+ allowedTags: Array.from(tags),
13042
+ allowedAttributes,
13043
+ allowedStylePrefixes: Array.from(stylePrefixes),
13044
+ };
13045
+ }
13046
+ /**
13047
+ * Default sanitizer allowlist aggregated from
13048
+ * {@link WORD_STRUCTURE_TRANSFORMERS}. Exported so tests and callers can still
13049
+ * provide stable overrides.
13050
+ */
13051
+ const WORD_SANITIZE_CONFIG = buildWordSanitizeConfig(WORD_STRUCTURE_TRANSFORMERS);
13052
+ /**
13053
+ * Attribute name that carries inline styles after {@link StylePreservator} has
13054
+ * renamed `style` to `data-style`. All sanitization runs against this name so
13055
+ * the styles survive the CSP-sensitive `DOMParser` boundary.
13056
+ */
13057
+ const PRESERVED_STYLE_ATTR = 'data-style';
13058
+ /**
13059
+ * Block tag that can represent an intentional blank line pasted from Word
13060
+ * (an "empty" paragraph whose only content is a non-breaking space). Headings
13061
+ * are excluded — an empty heading is treated as noise, not an intentional
13062
+ * blank line.
13063
+ */
13064
+ const BLANK_LINE_ELIGIBLE_TAGS = 'p';
13065
+ /**
13066
+ * Zero-width space used to keep an intentional blank line alive. Unlike a
13067
+ * non-breaking space, it is not stripped by `String.prototype.trim()`, so the
13068
+ * downstream empty-block removal keeps the paragraph while it still renders as
13069
+ * blank.
13070
+ */
13071
+ const ZERO_WIDTH_SPACE = '\u200B';
13072
+ /**
13073
+ * Style values the RTE cannot resolve, so any declaration whose value contains
13074
+ * one of them is dropped during sanitization. These ride on allowed properties
13075
+ * (`color`, `border`, `background`, …), so the prefix allowlist alone cannot
13076
+ * remove them. Add legacy Word keywords here to filter them everywhere.
13077
+ */
13078
+ // eslint-disable-next-line spellcheck/spell-checker
13079
+ const INVALID_STYLE_VALUES = ['windowtext'];
13080
+ /**
13081
+ * Pre processes raw Word/Office clipboard HTML into clean, RTE-friendly HTML.
13082
+ *
13083
+ * The pipeline mirrors the two-phase design of the source Word cleaner:
13084
+ * 1. Structure fixing — maps Word paragraph styles onto RTE text styles and
13085
+ * drops Word list marker artifacts.
13086
+ * 2. Dynamic allowlist sanitizing — recursively strips disallowed tags,
13087
+ * attributes and inline style declarations while keeping inner content.
13088
+ */
13089
+ class WordPasteProcessingStrategy extends AbstractPasteProcessingStrategy {
13090
+ /**
13091
+ * Downstream paste mode marker for Word-specific processing.
13092
+ *
13093
+ * @returns {RTEPasteMode} `word` so downstream handlers preserve Word annotations.
13094
+ */
13095
+ get type() {
13096
+ return RTEPasteMode.WORD;
13097
+ }
13098
+ /**
13099
+ * Detects whether raw clipboard HTML originated from Microsoft Word / Office.
13100
+ *
13101
+ * Used to auto-route a normal "rich text" paste through the Word cleaning
13102
+ * pipeline when the source is actually Word, so users get correct formatting
13103
+ * without having to explicitly pick the Word paste option.
13104
+ *
13105
+ * @param {string} html Raw `text/html` clipboard payload.
13106
+ * @returns {boolean} `true` when the HTML carries Word/Office signatures.
13107
+ */
13108
+ canHandle(html) {
13109
+ if (!html) {
13110
+ return false;
13111
+ }
13112
+ return WordPasteProcessingStrategy.WORD_SIGNATURE_PATTERNS.some((pattern) => pattern.test(html));
13113
+ }
13114
+ /**
13115
+ * Returns the priority of this strategy relative to others; higher values run first.
13116
+ *
13117
+ * @returns {number} The priority value.
13118
+ */
13119
+ get priority() {
13120
+ return PasteProcessingStrategyPriority.WORD;
13121
+ }
13122
+ /**
13123
+ * Whether this strategy extracts pasted images and produces local previews.
13124
+ *
13125
+ * @returns {boolean} Always `true`; Word paste swaps `file://` images to previews.
13126
+ */
13127
+ get supportsImageProcessing() {
13128
+ return true;
13129
+ }
13130
+ /**
13131
+ * Cleans raw Word clipboard HTML so it can flow through the normal RTE paste
13132
+ * pipeline.
13133
+ *
13134
+ * @param {string} html Raw `text/html` clipboard payload from Word.
13135
+ * @param {WordSanitizeConfig} [config] Optional allowlist override.
13136
+ * @returns {string} Sanitized HTML string.
13137
+ */
13138
+ process(html, config = WORD_SANITIZE_CONFIG) {
13139
+ if (!html) {
13140
+ return '';
13141
+ }
13142
+ const withoutComments = this.stripWordComments(html);
13143
+ // Preserve inline styles across the DOMParser boundary to avoid
13144
+ // style-src-attr CSP violations (styles become data-style attributes).
13145
+ const preservedHtml = contentState.StylePreservator.preserve(withoutComments).getProcessedHtml();
13146
+ const parsedDoc = new DOMParser().parseFromString(preservedHtml, 'text/html');
13147
+ const container = parsedDoc.body;
13148
+ this.fixWordStructure(container);
13149
+ // Sanitize each top-level child instead of the <body> itself so the body
13150
+ // container is never unwrapped away.
13151
+ Array.from(container.childNodes).forEach((child) => {
13152
+ this.sanitizeNode(child, config);
13153
+ });
13154
+ // Collapse the source line wraps Word inserts inside text so a single
13155
+ // logical line does not paste as multiple lines.
13156
+ this.collapseInsignificantWhitespace(container);
13157
+ // Drop the whitespace Word inserts directly between block elements so it
13158
+ // does not paste as empty lines.
13159
+ this.removeInterBlockWhitespace(container);
13160
+ // Keep intentional blank lines (Word empty paragraphs) from being dropped by
13161
+ // the downstream empty-block removal.
13162
+ this.preserveWordEmptyLines(container);
13163
+ return this.restoreStyleAttributeName(container.innerHTML);
13164
+ }
13165
+ /**
13166
+ * Removes Office XML comments and down-level conditional markup that would
13167
+ * otherwise confuse `DOMParser`.
13168
+ *
13169
+ * @param {string} html Raw HTML string.
13170
+ * @returns {string} HTML without Word comment blocks.
13171
+ */
13172
+ stripWordComments(html) {
13173
+ return html.replace(/<!--[\s\S]*?-->/g, '').replace(/<!\[[\s\S]*?\]>/g, '');
13174
+ }
13175
+ /**
13176
+ * First phase: repairs Word-specific structure so it aligns with RTE text
13177
+ * styles before the allowlist pass runs.
13178
+ *
13179
+ * @param {HTMLElement} root Parsed body element.
13180
+ */
13181
+ fixWordStructure(root) {
13182
+ WordPasteProcessingStrategy.STRUCTURE_TRANSFORMERS.forEach((transformer) => {
13183
+ transformer.apply(root);
13184
+ });
13185
+ }
13186
+ /**
13187
+ * Removes the whitespace-only text nodes Word emits directly between block
13188
+ * elements (the `\n\n` at the top level of the fragment). After the allowlist
13189
+ * pass, disallowed containers are already unwrapped, so any remaining
13190
+ * top-level text node that is pure whitespace is an inter-block artifact.
13191
+ * Whitespace inside inline/text elements is left untouched.
13192
+ *
13193
+ * @param {HTMLElement} root Parsed body element.
13194
+ */
13195
+ removeInterBlockWhitespace(root) {
13196
+ Array.from(root.childNodes).forEach((node) => {
13197
+ if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '') {
13198
+ root.removeChild(node);
13199
+ }
13200
+ });
13201
+ }
13202
+ /**
13203
+ * Collapses insignificant ASCII whitespace inside text nodes the way a
13204
+ * browser would when rendering. Word wraps long runs of text across multiple
13205
+ * source lines, so a single logical line such as "Number list 1" arrives as
13206
+ * "Number\nlist 1"; without normalization the newline surfaces as a spurious
13207
+ * line break in the editor. Non-breaking spaces (`\u00A0`) are left untouched
13208
+ * so intentional Word spacing and blank lines survive.
13209
+ *
13210
+ * @param {Node} node Node whose text descendants are normalized in place.
13211
+ */
13212
+ collapseInsignificantWhitespace(node) {
13213
+ // Code blocks preserve their original whitespace, so skip the whole subtree.
13214
+ if (WordCodeBlockTransformer.isCodeBlock(node)) {
13215
+ return;
13216
+ }
13217
+ if (node.nodeType === Node.TEXT_NODE) {
13218
+ const original = node.textContent;
13219
+ const collapsed = original.replace(/[ \t\r\n\f]+/g, ' ');
13220
+ if (collapsed !== original) {
13221
+ node.textContent = collapsed;
13222
+ }
13223
+ return;
13224
+ }
13225
+ Array.from(node.childNodes).forEach((child) => {
13226
+ this.collapseInsignificantWhitespace(child);
13227
+ });
13228
+ }
13229
+ /**
13230
+ * Preserves intentional blank lines authored in Word (empty paragraphs whose
13231
+ * only content is a non-breaking space). The downstream paste pipeline drops
13232
+ * any block whose text content trims to empty, and `trim()` also removes a
13233
+ * non-breaking space; replacing the content with a zero-width space keeps the
13234
+ * blank line because it survives `trim()` while still rendering as empty.
13235
+ *
13236
+ * @param {HTMLElement} root Parsed body element.
13237
+ */
13238
+ preserveWordEmptyLines(root) {
13239
+ Array.from(root.querySelectorAll(BLANK_LINE_ELIGIBLE_TAGS)).forEach((block) => {
13240
+ // Word paragraphs that host an image also carry a trailing
13241
+ // `<o:p>&nbsp;</o:p>` whose non-breaking space would misidentify the
13242
+ // paragraph as an empty line and wipe the image out.
13243
+ if (block.querySelector('img')) {
13244
+ return;
13245
+ }
13246
+ const text = block.textContent;
13247
+ const hasRealText = text.replace(/[\s\u200B]+/g, '') !== '';
13248
+ // Only an "empty" block that carries a non-breaking space is treated as
13249
+ // an intentional Word blank line.
13250
+ if (!hasRealText && /\u00A0/.test(text)) {
13251
+ block.textContent = ZERO_WIDTH_SPACE;
13252
+ }
13253
+ });
13254
+ }
13255
+ /**
13256
+ * Second phase: recursively enforces the dynamic allowlist on a node tree.
13257
+ *
13258
+ * @param {Node} node Node to sanitize in place.
13259
+ * @param {WordSanitizeConfig} config Active allowlist configuration.
13260
+ */
13261
+ sanitizeNode(node, config) {
13262
+ if (node.nodeType === Node.TEXT_NODE) {
13263
+ return;
13264
+ }
13265
+ if (node.nodeType !== Node.ELEMENT_NODE) {
13266
+ this.removeSelf(node);
13267
+ return;
13268
+ }
13269
+ const element = node;
13270
+ const tagName = element.tagName.toUpperCase();
13271
+ // Sanitize children first (reverse order keeps indices stable while nodes
13272
+ // may be removed or unwrapped).
13273
+ const children = Array.from(element.childNodes);
13274
+ for (let i = children.length - 1; i >= 0; i--) {
13275
+ this.sanitizeNode(children[i], config);
13276
+ }
13277
+ // Tag-level filter: unwrap disallowed elements but keep their content.
13278
+ if (!config.allowedTags.includes(tagName)) {
13279
+ while (element.firstChild) {
13280
+ if (element.parentNode && typeof element.parentNode.insertBefore === 'function') {
13281
+ element.parentNode.insertBefore(element.firstChild, element);
13282
+ }
13283
+ }
13284
+ this.removeSelf(element);
13285
+ return;
13286
+ }
13287
+ this.sanitizeAttributes(element, tagName, config);
13288
+ }
13289
+ /**
13290
+ * Removes disallowed attributes and filters style declarations.
13291
+ *
13292
+ * @param {Element} element Element being sanitized.
13293
+ * @param {string} tagName Upper-case tag name.
13294
+ * @param {WordSanitizeConfig} config Active sanitize configuration.
13295
+ */
13296
+ sanitizeAttributes(element, tagName, config) {
13297
+ const commonAttrs = config.allowedAttributes[WORD_SANITIZE_ALL_TAGS_KEY] || [];
13298
+ const tagAttrs = config.allowedAttributes[tagName] || [];
13299
+ const allowedAttrs = new Set([...commonAttrs, ...tagAttrs]);
13300
+ Array.from(element.attributes).forEach((attr) => {
13301
+ const attrName = attr.name.toLowerCase();
13302
+ const isStyle = attrName === PRESERVED_STYLE_ATTR;
13303
+ const normalizedName = isStyle ? 'style' : attrName;
13304
+ if (!allowedAttrs.has(normalizedName)) {
13305
+ element.removeAttribute(attr.name);
13306
+ return;
13307
+ }
13308
+ if (isStyle) {
13309
+ this.sanitizeStyleAttribute(element, attr.name, config);
13310
+ }
13311
+ });
13312
+ }
13313
+ /**
13314
+ * Filters CSS declarations inside a preserved style attribute: keeps only
13315
+ * properties matching a prefix in {@link WordSanitizeConfig.allowedStylePrefixes};
13316
+ * all others (including `mso-*` Word artifacts) are stripped.
13317
+ *
13318
+ * @param {Element} element Element owning the style attribute.
13319
+ * @param {string} attrName Preserved style attribute name.
13320
+ * @param {WordSanitizeConfig} config Active sanitize configuration.
13321
+ */
13322
+ sanitizeStyleAttribute(element, attrName, config) {
13323
+ const rawStyle = element.getAttribute(attrName) || '';
13324
+ // Delegate CSS parsing to the browser engine via a temporary element so
13325
+ // that edge cases (values containing semicolons, multi-value shorthand,
13326
+ // etc.) are handled correctly without a hand-rolled parser.
13327
+ const tempEl = element.ownerDocument.createElement('span');
13328
+ tempEl.setAttribute('style', rawStyle);
13329
+ const parsedStyle = tempEl.style;
13330
+ const cleanDeclarations = [];
13331
+ for (let i = 0; i < parsedStyle.length; i++) {
13332
+ const key = parsedStyle[i];
13333
+ // Keep only properties whose name starts with an allowed prefix.
13334
+ const isAllowed = config.allowedStylePrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}-`));
13335
+ const value = parsedStyle.getPropertyValue(key);
13336
+ // Drop declarations carrying a value the RTE cannot resolve (e.g. Word's
13337
+ // legacy `windowText` system color), even on otherwise allowed properties.
13338
+ const lowerCaseValue = value.toLowerCase();
13339
+ const hasInvalidValue = INVALID_STYLE_VALUES.some((invalid) => lowerCaseValue.includes(invalid));
13340
+ if (isAllowed && !hasInvalidValue) {
13341
+ cleanDeclarations.push(`${key}: ${value}`);
13342
+ }
13343
+ }
13344
+ const cleanStyle = cleanDeclarations.join('; ');
13345
+ if (cleanStyle) {
13346
+ element.setAttribute(attrName, cleanStyle);
13347
+ }
13348
+ else {
13349
+ element.removeAttribute(attrName);
13350
+ }
13351
+ }
13352
+ /**
13353
+ * Converts preserved `data-style` attributes back to `style` in the final
13354
+ * HTML string so downstream paste handling receives standard markup.
13355
+ *
13356
+ * @param {string} html Serialized sanitized HTML.
13357
+ * @returns {string} HTML with `style` attributes restored.
13358
+ */
13359
+ restoreStyleAttributeName(html) {
13360
+ return html.replace(/\sdata-style=/g, ' style=');
13361
+ }
13362
+ /**
13363
+ * Removes the element from its parent node if it has one.
13364
+ *
13365
+ * @param {Node | Element} elementToBeRemoved The element to be removed from the DOM.
13366
+ */
13367
+ removeSelf(elementToBeRemoved) {
13368
+ if (elementToBeRemoved.parentNode &&
13369
+ typeof elementToBeRemoved.parentNode.removeChild === 'function') {
13370
+ elementToBeRemoved.parentNode.removeChild(elementToBeRemoved);
13371
+ }
13372
+ }
13373
+ }
13374
+ /**
13375
+ * Ordered list of structure transformers applied in Phase 1 before allowlist
13376
+ * sanitization. To add support for a new node type (table, image, list…),
13377
+ * create a class that implements {@link WordStructureTransformer} and append
13378
+ * it here — no other changes to the pipeline are needed.
13379
+ */
13380
+ WordPasteProcessingStrategy.STRUCTURE_TRANSFORMERS = [
13381
+ ...WORD_STRUCTURE_TRANSFORMERS,
13382
+ ];
13383
+ /**
13384
+ * Signatures that identify clipboard HTML produced by Microsoft Word / Office.
13385
+ * Any single match is enough to treat the payload as Word content because
13386
+ * these markers do not appear in ordinary web or plain rich-text clipboards.
13387
+ */
13388
+ WordPasteProcessingStrategy.WORD_SIGNATURE_PATTERNS = [
13389
+ // <meta name=Generator content="Microsoft Word ...">
13390
+ /content=["']?Microsoft\s+Word/i,
13391
+ // eslint-disable-next-line spellcheck/spell-checker
13392
+ // Office namespace declarations (urn:schemas-microsoft-com:office:word).
13393
+ /urn:schemas-microsoft-com:office/i,
13394
+ // Word paragraph/character style classes (MsoNormal, MsoTitle, MsoListParagraph…).
13395
+ /class=["']?Mso[A-Za-z]/i,
13396
+ // Word-only inline style properties.
13397
+ /mso-[a-z-]+\s*:/i,
13398
+ // Office XML paragraph marker elements (<o:p></o:p>).
13399
+ /<\/?o:p>/i,
13400
+ ];
13401
+
13402
+ /**
13403
+ * Centralized selector for paste processing strategies.
13404
+ *
13405
+ * Maintains a registry of available strategies and selects the highest-priority
13406
+ * strategy that can handle the given clipboard HTML. Provides an extension
13407
+ * point for adding support for new clipboard sources (e.g., Google Docs, Notion)
13408
+ * without modifying the paste extension core logic.
13409
+ */
13410
+ class PasteStrategySelector {
13411
+ constructor() {
13412
+ this.defaultStrategy = new DefaultPasteProcessingStrategy();
13413
+ this.strategies = [
13414
+ new WordPasteProcessingStrategy(),
13415
+ this.defaultStrategy, // Fallback
13416
+ ];
13417
+ }
13418
+ /**
13419
+ * Select the appropriate strategy for the given HTML content.
13420
+ * Returns the highest-priority strategy that can handle the content.
13421
+ *
13422
+ * @param {string} html Raw clipboard HTML.
13423
+ * @returns {AbstractPasteProcessingStrategy} The selected strategy (always returns a strategy; default is the fallback).
13424
+ */
13425
+ selectStrategy(html) {
13426
+ return this.selectHighestPriorityStrategy(this.strategies.filter((strategy) => strategy.canHandle(html)));
13427
+ }
13428
+ /**
13429
+ * Selects the highest-priority registered strategy for an explicitly chosen paste mode.
13430
+ *
13431
+ * Used when the user manually chooses a non-default mode from the paste chooser.
13432
+ *
13433
+ * @param {RTEPasteMode} type Explicit paste mode selected by the user.
13434
+ * @returns {AbstractPasteProcessingStrategy} The matching strategy or the default fallback.
13435
+ */
13436
+ selectStrategyByType(type) {
13437
+ return this.selectHighestPriorityStrategy(this.strategies.filter((strategy) => strategy.type === type));
13438
+ }
13439
+ /**
13440
+ * Register a new paste processing strategy.
13441
+ * Useful for future extensions (e.g., GoogleDocsPasteStrategy).
13442
+ *
13443
+ * @param {AbstractPasteProcessingStrategy} strategy The strategy to register.
13444
+ */
13445
+ registerStrategy(strategy) {
13446
+ this.strategies.push(strategy);
13447
+ }
13448
+ /**
13449
+ * Returns the highest-priority strategy from a candidate set or the default fallback.
13450
+ *
13451
+ * @param {AbstractPasteProcessingStrategy[]} strategies Candidate strategies.
13452
+ * @returns {AbstractPasteProcessingStrategy} Selected strategy.
13453
+ */
13454
+ selectHighestPriorityStrategy(strategies) {
13455
+ const selectedStrategy = strategies.sort((a, b) => b.priority - a.priority)[0];
13456
+ return selectedStrategy || this.defaultStrategy;
13457
+ }
13458
+ }
13459
+
13460
+ /**
13461
+ * Extracts pasted image files from clipboard payloads and swaps matching
13462
+ * `<img>` elements to local object-URL previews.
13463
+ *
13464
+ * The parser is stateless: callers own the returned {@link PasteImagePreview}
13465
+ * list and decide how to track pending uploads.
13466
+ */
13467
+ class PasteImageParser {
13468
+ /**
13469
+ * Parses local `file://` images from the HTML, swaps matching `<img>`
13470
+ * elements to object-URL previews, and returns the rewritten HTML alongside
13471
+ * the resolved pending previews.
13472
+ *
13473
+ * When no local images are resolved, the original HTML is returned unchanged
13474
+ * with an empty preview list.
13475
+ *
13476
+ * @param {string} html HTML content to parse.
13477
+ * @param {ClipboardFileContent | null} clipboardFileContent Clipboard payload for image extraction.
13478
+ * @returns {ParseLocalImagesResult} Rewritten HTML and resolved previews.
13479
+ */
13480
+ parseLocalImages(html, clipboardFileContent) {
13481
+ if (!html.includes('<img')) {
13482
+ return { html, previews: [] };
13483
+ }
13484
+ const safeHtml = html.replace(/\s+style=/g, ' data-style=');
13485
+ const parser = new DOMParser();
13486
+ const parsedDoc = parser.parseFromString(safeHtml, 'text/html');
13487
+ const localImageElements = Array.from(parsedDoc.querySelectorAll('img')).filter((img) => img.src.startsWith('file://'));
13488
+ if (localImageElements.length === 0) {
13489
+ return { html, previews: [] };
13490
+ }
13491
+ const imageFiles = this.extractImageFiles(clipboardFileContent, localImageElements);
13492
+ if (imageFiles.length === 0) {
13493
+ return { html, previews: [] };
13494
+ }
13495
+ const previews = this.createImagePreviews(localImageElements, imageFiles);
13496
+ const rewrittenHtml = parsedDoc.body.innerHTML.replace(/ data-style=/g, ' style=');
13497
+ return { html: rewrittenHtml, previews };
13498
+ }
13499
+ /**
13500
+ * Swaps each `<img>` src to a local object-URL preview using the provided
13501
+ * image files (paired by order), returning one preview per resolved image.
13502
+ *
13503
+ * Images that live inside a list are dropped instead of being previewed: the
13504
+ * RTE does not support list images. They are removed here (after holding their
13505
+ * position while the RTF images are paired by order) so the remaining images
13506
+ * stay aligned with their files even when a list image is not the last one.
13507
+ *
13508
+ * @param {HTMLImageElement[]} images Placeholder `<img>` elements in document order.
13509
+ * @param {File[]} files Image files to pair with the elements, in the same order.
13510
+ * @returns {PasteImagePreview[]} Previews for the images that were resolved to a file.
13511
+ */
13512
+ createImagePreviews(images, files) {
13513
+ const previews = [];
13514
+ images.forEach((imageElement, index) => {
13515
+ // List images have no editor representation, so drop them here instead of
13516
+ // creating a preview. Removing them at this stage (rather than earlier)
13517
+ // keeps the remaining images aligned with their files.
13518
+ if (this.isListImage(imageElement)) {
13519
+ imageElement.remove();
13520
+ return;
13521
+ }
13522
+ const file = files[index];
13523
+ if (!file) {
13524
+ return;
13525
+ }
13526
+ const objectUrl = URL.createObjectURL(file);
13527
+ imageElement.setAttribute('src', objectUrl);
13528
+ const imageId = misc.generateUUID();
13529
+ imageElement.setAttribute(PasteImageParser.PREVIEW_IMG_KEY, imageId);
13530
+ previews.push({ id: imageId, file, objectUrl });
13531
+ });
13532
+ return previews;
13533
+ }
13534
+ /**
13535
+ * Extracts image files from clipboard data, preferring Word RTF-decoded
13536
+ * images and falling back to the clipboard file list when RTF yields none.
13537
+ *
13538
+ * @param {ClipboardFileContent | null} clipboardFileContent Clipboard payload from a paste event.
13539
+ * @param {HTMLElement[]} images `<img>` elements the files will be paired with.
13540
+ * @returns {File[]} Image files found on the clipboard.
13541
+ */
13542
+ extractImageFiles(clipboardFileContent, images) {
13543
+ let result = [];
13544
+ if (clipboardFileContent) {
13545
+ if (clipboardFileContent.rtf) {
13546
+ result = this.safeExtractWordImages(clipboardFileContent.rtf, images);
13547
+ }
13548
+ if (result.length === 0 && clipboardFileContent.files) {
13549
+ result = clipboardFileContent.files.filter((file) => file.type.startsWith('image/'));
13550
+ }
13551
+ }
13552
+ return result;
13553
+ }
13554
+ /**
13555
+ * Safely extracts image files from Word RTF clipboard data.
13556
+ *
13557
+ * @param {string} rtf Clipboard payload from a paste event.
13558
+ * @param {HTMLElement[]} images Array of `<img>` elements that may be updated with staged previews.
13559
+ * @returns {File[]} Image files found in the clipboard data.
13560
+ */
13561
+ safeExtractWordImages(rtf, images) {
13562
+ // Word defines a list's picture bullet once in the list table
13563
+ // (`{\*\listpicture ...}`) with no rendered counterpart. Drop that
13564
+ // definition so its blip is not counted as a content image; the per-item
13565
+ // `{\listtext ...}` bullet blips are kept because each still pairs with a
13566
+ // preserved bullet placeholder `<img>`.
13567
+ const contentRtf = this.stripListPictureDefinitions(rtf);
13568
+ const fileObjects = [];
13569
+ const imageRegex = /\\pngblip|\\jpegblip/g;
13570
+ const matches = [...contentRtf.matchAll(imageRegex)];
13571
+ matches.forEach((match, index) => {
13572
+ if (index >= images.length || match.index === undefined) {
13573
+ return;
13574
+ }
13575
+ const mimeType = match[0].includes('png') ? 'image/png' : 'image/jpeg';
13576
+ const startIndex = match.index + match[0].length;
13577
+ const bytes = this.extractImageBytes(contentRtf, startIndex);
13578
+ if (bytes.length === 0) {
13579
+ return;
13580
+ }
13581
+ const extension = mimeType.split('/')[1];
13582
+ const file = new File(
13583
+ // eslint-disable-next-line spellcheck/spell-checker
13584
+ [new Uint8Array(bytes)], `word_img_${Date.now()}_${index}.${extension}`, { type: mimeType });
13585
+ fileObjects.push(file);
13586
+ });
13587
+ return fileObjects;
13588
+ }
13589
+ /**
13590
+ * Removes Word list-table picture-bullet definitions from the RTF so they are
13591
+ * not counted as content images.
13592
+ *
13593
+ * Word declares a list's picture bullet once as a `{\*\listpicture ...}`
13594
+ * destination inside the list table. It renders nothing on its own, so its
13595
+ * blip has no matching content `<img>`; leaving it in shifts the ordinal
13596
+ * pairing and makes the first real image resolve to the bullet. The per-item
13597
+ * `{\listtext ...}` bullets are intentionally left untouched because each is
13598
+ * kept as a placeholder `<img>` that consumes its blip downstream.
13599
+ *
13600
+ * @param {string} rtf Full RTF clipboard payload.
13601
+ * @returns {string} RTF with list-table picture-bullet definitions removed.
13602
+ */
13603
+ stripListPictureDefinitions(rtf) {
13604
+ // eslint-disable-next-line spellcheck/spell-checker
13605
+ return this.removeBalancedGroups(rtf, '{\\*\\listpicture');
13606
+ }
13607
+ /**
13608
+ * Removes every balanced-brace RTF group that starts with the given marker.
13609
+ *
13610
+ * @param {string} rtf RTF payload to scan.
13611
+ * @param {string} marker Opening delimiter of the group to remove (including its `{`).
13612
+ * @returns {string} RTF with the matching groups removed.
13613
+ */
13614
+ removeBalancedGroups(rtf, marker) {
13615
+ let index = rtf.indexOf(marker);
13616
+ while (index !== -1) {
13617
+ const end = this.findGroupEnd(rtf, index);
13618
+ rtf = rtf.slice(0, index) + rtf.slice(end);
13619
+ index = rtf.indexOf(marker, index);
13620
+ }
13621
+ return rtf;
13622
+ }
13623
+ /**
13624
+ * Finds the index just past the brace that closes the group opened at `start`.
13625
+ *
13626
+ * A backslash escapes the next character (such as `\{` or `\}`) so escaped
13627
+ * braces are not counted. When the group is unbalanced the RTF length is
13628
+ * returned so the trailing remainder is dropped.
13629
+ *
13630
+ * @param {string} rtf RTF payload to scan.
13631
+ * @param {number} start Index of the group's opening `{`.
13632
+ * @returns {number} Index immediately after the matching closing `}`.
13633
+ */
13634
+ findGroupEnd(rtf, start) {
13635
+ const length = rtf.length;
13636
+ let depth = 0;
13637
+ let position = start;
13638
+ while (position < length) {
13639
+ const char = rtf[position];
13640
+ if (char === '\\') {
13641
+ position += 2;
13642
+ continue;
13643
+ }
13644
+ if (char === '{') {
13645
+ depth++;
13646
+ }
13647
+ else if (char === '}') {
13648
+ depth--;
13649
+ if (depth === 0) {
13650
+ return position + 1;
13651
+ }
13652
+ }
13653
+ position++;
13654
+ }
13655
+ return length;
13656
+ }
13657
+ /**
13658
+ * Extracts the raw image bytes that follow a Word RTF blip marker.
13659
+ *
13660
+ * Starting just after the blip control word, RTF control words (such as
13661
+ * `\bliptag`) and nested groups (such as `{\*\blipuid ...}`) are skipped so
13662
+ * their hex is not mistaken for image data. Hex digits are decoded into bytes
13663
+ * until the enclosing `{\pict}` group is closed.
13664
+ *
13665
+ * @param {string} rtf Full RTF clipboard payload.
13666
+ * @param {number} startIndex Index immediately after the blip marker.
13667
+ * @returns {number[]} Decoded image byte values.
13668
+ */
13669
+ extractImageBytes(rtf, startIndex) {
13670
+ const length = rtf.length;
13671
+ const bytes = [];
13672
+ let depth = 0;
13673
+ let highNibble = -1;
13674
+ let position = startIndex;
13675
+ while (position < length) {
13676
+ const char = rtf[position];
13677
+ if (char === '{') {
13678
+ depth++;
13679
+ position++;
13680
+ continue;
13681
+ }
13682
+ if (char === '}') {
13683
+ if (depth === 0) {
13684
+ break;
13685
+ }
13686
+ depth--;
13687
+ position++;
13688
+ continue;
13689
+ }
13690
+ if (depth > 0) {
13691
+ position++;
13692
+ continue;
13693
+ }
13694
+ if (char === '\\') {
13695
+ position++;
13696
+ while (position < length && /[a-zA-Z]/.test(rtf[position])) {
13697
+ position++;
13698
+ }
13699
+ if (position < length && rtf[position] === '-') {
13700
+ position++;
13701
+ }
13702
+ while (position < length && /[0-9]/.test(rtf[position])) {
13703
+ position++;
13704
+ }
13705
+ continue;
13706
+ }
13707
+ const nibble = this.hexNibble(char);
13708
+ if (nibble >= 0) {
13709
+ if (highNibble < 0) {
13710
+ highNibble = nibble;
13711
+ }
13712
+ else {
13713
+ bytes.push((highNibble << 4) | nibble);
13714
+ highNibble = -1;
13715
+ }
13716
+ }
13717
+ position++;
13718
+ }
13719
+ return bytes;
13720
+ }
13721
+ /**
13722
+ * Converts a single hex character to its numeric value.
13723
+ *
13724
+ * @param {string} char Candidate hex character.
13725
+ * @returns {number} Value in `[0, 15]`, or `-1` when not a hex digit.
13726
+ */
13727
+ hexNibble(char) {
13728
+ if (char >= '0' && char <= '9') {
13729
+ return char.charCodeAt(0) - 48;
13730
+ }
13731
+ if (char >= 'a' && char <= 'f') {
13732
+ return char.charCodeAt(0) - 87;
13733
+ }
13734
+ if (char >= 'A' && char <= 'F') {
13735
+ return char.charCodeAt(0) - 55;
13736
+ }
13737
+ return -1;
13738
+ }
13739
+ /**
13740
+ * Returns whether an `<img>` element is nested inside list content.
13741
+ *
13742
+ * The Word paste sanitizer strips non-allowlisted attributes but keeps the
13743
+ * `ul`/`ol`/`li` structure, so list membership is detected by DOM ancestry
13744
+ * here rather than by a marker attribute set upstream.
13745
+ *
13746
+ * @param {HTMLImageElement} imageElement Image element to test.
13747
+ * @returns {boolean} Whether the image is part of list content.
13748
+ */
13749
+ isListImage(imageElement) {
13750
+ return Boolean(imageElement.closest('li,ul,ol'));
13751
+ }
13752
+ }
13753
+ PasteImageParser.PREVIEW_IMG_KEY = 'data-rte-id';
13754
+
13755
+ const REPLACE_IMAGE_SRC = 'REPLACE_IMAGE_SRC';
13756
+ /**
13757
+ * Action to replace the `src` attribute of an `<img>` element in the RTE content.
13758
+ */
13759
+ class ImageReplaceSrcAction extends codeBlock.ModifyContentAction {
13760
+ constructor(imageElement, url) {
13761
+ super();
13762
+ this.type = REPLACE_IMAGE_SRC;
13763
+ this.img = imageElement;
13764
+ this.url = url;
13765
+ }
13766
+ }
13767
+
13768
+ /**
13769
+ * Strategy to handle the `ImageReplaceSrcAction` for replacing the `src` attribute of an `<img>` element in the RTE content.
13770
+ */
13771
+ class ImageReplaceSrcStrategy extends codeBlock.ActionHandleStrategy {
13772
+ /**
13773
+ * @inheritDoc
13774
+ */
13775
+ handleAction(action, target) {
13776
+ const { img, url } = action;
13777
+ img.setAttribute('src', url);
13778
+ img.removeAttribute(PasteImageParser.PREVIEW_IMG_KEY);
13779
+ target.url = url;
13780
+ if (target.annotationMap && target.annotationMap.has('CUSTOM_ATTRIBUTE')) {
13781
+ const customAttributeAnnotation = target.annotationMap.get('CUSTOM_ATTRIBUTE');
13782
+ customAttributeAnnotation.customAttribute.src = url;
13783
+ delete customAttributeAnnotation.customAttribute[PasteImageParser.PREVIEW_IMG_KEY];
13784
+ }
13785
+ }
13786
+ }
13787
+
13788
+ /**
13789
+ * Owns the lifecycle of pasted images awaiting upload: it tracks their local
13790
+ * object-URL previews, notifies the consumer once they are inserted, applies the
13791
+ * uploaded URL back onto the live preview, and revokes object URLs on teardown.
13792
+ *
13793
+ * Keeping this state here rather than on the paste extension keeps the extension
13794
+ * focused on clipboard capture and the chooser UI.
13795
+ */
13796
+ class PendingImageRegistry {
13797
+ constructor() {
13798
+ /** Ids of pending images still holding an object URL owned by this registry. */
13799
+ this.ownedImageIds = new Set();
13800
+ /** Ids queued for the next {@link notifyInserted} call. */
13801
+ this.pendingNotifyImageIds = [];
13802
+ }
13803
+ /**
13804
+ * Registers freshly created previews so they can be notified and later
13805
+ * resolved to their uploaded URLs.
13806
+ *
13807
+ * @param {PasteImagePreview[]} previews Previews produced for the current paste.
13808
+ */
13809
+ register(previews) {
13810
+ this.pendingNotifyImageIds = [];
13811
+ previews.forEach((preview) => {
13812
+ this.pendingNotifyImageIds.push(preview.id);
13813
+ this.ownedImageIds.add(preview.id);
13814
+ PendingImageRegistry.pendingImages.set(preview.id, preview);
13815
+ });
13816
+ }
13817
+ /**
13818
+ * @returns {boolean} Whether previews are queued for notification.
13819
+ */
13820
+ hasPendingNotifications() {
13821
+ return this.pendingNotifyImageIds.length > 0;
13822
+ }
13823
+ /**
13824
+ * Notifies the consumer about newly inserted pasted images so they can upload
13825
+ * them and fill in the real URLs via the supplied `replaceUrl` callback.
13826
+ *
13827
+ * @param {ImageInsertedCallback | undefined} callback Consumer callback to invoke.
13828
+ * @param {GetEditableElement} getEditableElement Resolves the live editable element.
13829
+ * @param {ResolveImageNode} resolveImageNode Resolves the DTO node for a preview element.
13830
+ */
13831
+ notifyInserted(callback, getEditableElement, resolveImageNode) {
13832
+ const images = this.pendingNotifyImageIds
13833
+ .map((id) => PendingImageRegistry.pendingImages.get(id))
13834
+ .filter((preview) => !!preview);
13835
+ this.pendingNotifyImageIds = [];
13836
+ if (images.length && typeof callback === 'function') {
13837
+ // Mark as handed to the consumer so teardown keeps them for a later replaceUrl.
13838
+ images.forEach((image) => (image.notified = true));
13839
+ const pasteImages = images.map((image) => ({
13840
+ id: image.id,
13841
+ file: image.file,
13842
+ }));
13843
+ callback(pasteImages, (imageId, url) => this.replaceUrl(imageId, url, getEditableElement, resolveImageNode));
13844
+ }
13845
+ }
13846
+ /**
13847
+ * Revokes and forgets only previews that were never handed to a consumer for
13848
+ * upload. Previews already notified via {@link notifyInserted} are left intact
13849
+ * so an async {@link replaceUrl} can still resolve them after the extension is
13850
+ * recreated; that callback owns their teardown.
13851
+ */
13852
+ discardUnsentPreviews() {
13853
+ this.ownedImageIds.forEach((imageId) => {
13854
+ const pending = PendingImageRegistry.pendingImages.get(imageId);
13855
+ if (pending && pending.notified) {
13856
+ return;
13857
+ }
13858
+ if (pending && pending.objectUrl.startsWith('blob:')) {
13859
+ URL.revokeObjectURL(pending.objectUrl);
13860
+ }
13861
+ PendingImageRegistry.pendingImages.delete(imageId);
13862
+ this.ownedImageIds.delete(imageId);
13863
+ });
13864
+ this.pendingNotifyImageIds = [];
13865
+ }
13866
+ /**
13867
+ * Applies the uploaded URL for a pending pasted image, replacing its local
13868
+ * preview `src` and revoking the object URL.
13869
+ *
13870
+ * @param {string} imageId Pending image id supplied to the consumer callback.
13871
+ * @param {string} url Uploaded image URL to apply.
13872
+ * @param {GetEditableElement} getEditableElement Resolves the live editable element.
13873
+ * @param {ResolveImageNode} resolveImageNode Resolves the DTO node for a preview element.
13874
+ */
13875
+ replaceUrl(imageId, url, getEditableElement, resolveImageNode) {
13876
+ var _a;
13877
+ const editableEl = getEditableElement();
13878
+ if (editableEl) {
13879
+ const imageElement = (_a = editableEl.querySelector(`img[data-rte-id="${imageId}"]`)) !== null && _a !== void 0 ? _a : null;
13880
+ if (imageElement) {
13881
+ const imageNode = resolveImageNode(imageElement);
13882
+ imageNode && imageNode.apply(new ImageReplaceSrcAction(imageElement, url));
13883
+ }
13884
+ }
13885
+ const pending = PendingImageRegistry.pendingImages.get(imageId);
13886
+ if (pending) {
13887
+ if (pending.objectUrl.startsWith('blob:')) {
13888
+ URL.revokeObjectURL(pending.objectUrl);
13889
+ }
13890
+ PendingImageRegistry.pendingImages.delete(imageId);
13891
+ this.ownedImageIds.delete(imageId);
13892
+ }
13893
+ }
13894
+ }
13895
+ /**
13896
+ * Pasted images awaiting upload, keyed by their {@link VegaRTEPasteImage.id}.
13897
+ *
13898
+ * Shared statically across instances so previews survive a paste-extension
13899
+ * re-instantiation: the consumer may resolve `replaceUrl` after the editor
13900
+ * has re-mounted and created a new extension/registry instance.
13901
+ */
13902
+ PendingImageRegistry.pendingImages = new Map();
13903
+
13904
+ /**
13905
+ * Vega Rich Text Editor paste extension.
13906
+ * Intercepts keyboard paste and allows users to choose paste mode.
13907
+ */
13908
+ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
13909
+ constructor(options = {}) {
13910
+ super();
13911
+ this.editableEl = null;
13912
+ this.contentHostEl = null;
13913
+ this.chooserUI = null;
13914
+ this.pendingPastePayload = null;
13915
+ this.listenerDocument = null;
13916
+ this.listenerWindow = null;
13917
+ this.options = {};
13918
+ this.strategySelector = new PasteStrategySelector();
13919
+ this.clipboardFileContent = null;
13920
+ this.imageParser = new PasteImageParser();
13921
+ /** Owns pending pasted-image previews awaiting upload and their URL replacement. */
13922
+ this.pendingImageRegistry = new PendingImageRegistry();
13923
+ /**
13924
+ * Handles a native paste event by capturing the clipboard payload and
13925
+ * rendering the paste-mode chooser.
13926
+ *
13927
+ * @param {ClipboardEvent} event Native clipboard paste event.
13928
+ */
13929
+ this.handlePasteEvent = (event) => {
13930
+ var _a, _b;
13931
+ const clipboardData = event.clipboardData;
13932
+ if (!clipboardData) {
13933
+ return;
13934
+ }
13935
+ // HTML is sanitized via DOMParser and ElementToDtoStrategyProcessor pipeline
13936
+ const html = clipboardData.getData('text/html') || '';
13937
+ const text = clipboardData.getData('text/plain') || '';
13938
+ // RTF must be read synchronously here: the paste event's DataTransfer is only
13939
+ // readable during event dispatch, so reading it later (after the chooser) returns ''.
13940
+ const rtf = clipboardData.getData('text/rtf') || '';
13941
+ if (!html && !text) {
13942
+ // Empty clipboard paste should not mutate editor content.
13943
+ event.preventDefault();
13944
+ event.stopPropagation();
13945
+ return;
13946
+ }
13947
+ event.preventDefault();
13948
+ event.stopPropagation();
13949
+ const range = this.cloneCurrentSelectionRange();
13950
+ const selectedNodes = ((_b = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelectedNodes) === null || _b === void 0 ? void 0 : _b.call(_a)) || [];
13951
+ this.pendingPastePayload = { html, text, range, selectedNodes: [...selectedNodes] };
13952
+ this.clipboardFileContent = {
13953
+ files: clipboardData.files ? Array.from(clipboardData.files) : [],
13954
+ rtf,
13955
+ };
13956
+ this.renderPasteChooser(event);
13957
+ };
13958
+ /**
13959
+ * Dismisses the chooser when a pointer interaction occurs outside of it.
13960
+ *
13961
+ * @param {Event} event Pointer event captured on the document.
13962
+ */
13963
+ this.handleOutsidePointerDown = (event) => {
13964
+ if (!this.chooserUI) {
13965
+ return;
13966
+ }
13967
+ const eventPath = typeof event.composedPath === 'function' ? event.composedPath() : [];
13968
+ if (this.chooserUI.includesEventPath(eventPath)) {
13969
+ return;
13970
+ }
13971
+ this.clearPasteChooser();
13972
+ };
13973
+ /**
13974
+ * Dismisses the chooser when the Escape key is pressed.
13975
+ *
13976
+ * @param {KeyboardEvent} event Keyboard event captured on the document.
13977
+ */
13978
+ this.handleOutsideKeyDown = (event) => {
13979
+ if (event.key === 'Escape') {
13980
+ this.clearPasteChooser();
13981
+ }
13982
+ };
13983
+ /**
13984
+ * Dismisses the chooser when any relevant scroll container scrolls.
13985
+ */
13986
+ this.handleOutsideScroll = () => {
13987
+ this.clearPasteChooser();
13988
+ };
13989
+ this.options = options;
13990
+ }
13991
+ /** @inheritDoc */
13992
+ initialExtensionAfterLoad(context) {
13993
+ super.initialExtensionAfterLoad(context);
13994
+ this.hostEl = context.host;
13995
+ this.attachPasteListener();
13996
+ }
13997
+ /** @inheritDoc */
13998
+ destroyExtension() {
13999
+ this.detachPasteListener();
14000
+ this.clearPasteChooser();
14001
+ this.pendingImageRegistry.discardUnsentPreviews();
14002
+ }
14003
+ /**
14004
+ * Attaches the paste listener to the editable element.
14005
+ */
14006
+ attachPasteListener() {
14007
+ const { editableEl, contentHostEl, } = this.getEditorElements();
14008
+ this.editableEl = editableEl;
14009
+ this.contentHostEl = contentHostEl;
14010
+ if (this.editableEl) {
14011
+ this.editableEl.addEventListener('paste', this.handlePasteEvent);
14012
+ }
14013
+ }
14014
+ /**
14015
+ * Detaches the paste listener from the editable element.
14016
+ */
14017
+ detachPasteListener() {
14018
+ if (this.editableEl) {
14019
+ this.editableEl.removeEventListener('paste', this.handlePasteEvent);
14020
+ }
14021
+ this.editableEl = null;
14022
+ this.contentHostEl = null;
14023
+ }
14024
+ /**
14025
+ * Resolves the content host and editable element from the host shadow tree.
14026
+ *
14027
+ * @returns {EditorElements} Editor elements.
14028
+ */
14029
+ getEditorElements() {
14030
+ var _a, _b, _c, _d, _e;
14031
+ const contentHostEl = (_c = (_b = (_a = this.hostEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('vega-rich-text-content')) !== null && _c !== void 0 ? _c : null;
14032
+ const editableEl = (_e = (_d = contentHostEl === null || contentHostEl === void 0 ? void 0 : contentHostEl.shadowRoot) === null || _d === void 0 ? void 0 : _d.querySelector('.rich-text-content')) !== null && _e !== void 0 ? _e : null;
14033
+ return { editableEl, contentHostEl };
14034
+ }
14035
+ /**
14036
+ * Resolves the editable content element from the host shadow tree.
14037
+ *
14038
+ * @returns {HTMLDivElement | null} Editable element or `null` when unavailable.
14039
+ */
14040
+ getEditableElement() {
14041
+ const { editableEl } = this.getEditorElements();
14042
+ return editableEl;
14043
+ }
14044
+ /**
14045
+ * Clones the current selection range so it can be restored after the user
14046
+ * chooses a paste mode.
14047
+ *
14048
+ * @returns {Range | null} Cloned range or `null` when there is no selection.
14049
+ */
14050
+ cloneCurrentSelectionRange() {
14051
+ var _a;
14052
+ const selection = (_a = this.extensionContext) === null || _a === void 0 ? void 0 : _a.getSelection();
14053
+ if (selection && selection.rangeCount > 0) {
14054
+ return selection.getRangeAt(0).cloneRange();
14055
+ }
14056
+ return null;
14057
+ }
14058
+ /**
14059
+ * Renders the paste-mode chooser and wires up dismissal listeners.
11340
14060
  *
11341
14061
  * @param {ClipboardEvent} pasteEvent Originating paste event used for positioning.
11342
14062
  */
11343
14063
  renderPasteChooser(pasteEvent) {
11344
- var _a, _b;
14064
+ var _a, _b, _c;
11345
14065
  const hostRoot = (_b = (_a = this.hostEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) !== null && _b !== void 0 ? _b : null;
11346
14066
  if (!hostRoot) {
11347
14067
  return;
@@ -11353,6 +14073,8 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11353
14073
  onModeSelect: (mode) => {
11354
14074
  this.applyPasteMode(mode);
11355
14075
  },
14076
+ }, {
14077
+ showWordOption: ((_c = this.options) === null || _c === void 0 ? void 0 : _c.enableWordPaste) === true,
11356
14078
  });
11357
14079
  this.chooserUI.open(position);
11358
14080
  this.listenerDocument = this.hostEl.ownerDocument;
@@ -11482,7 +14204,10 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11482
14204
  /**
11483
14205
  * Applies the chosen paste mode by dispatching a synthetic paste input event.
11484
14206
  *
11485
- * @param {PasteMode} mode Selected paste mode.
14207
+ * `rich` keeps auto-detection enabled; any other explicit mode forces the
14208
+ * corresponding registered strategy.
14209
+ *
14210
+ * @param {RTEPasteMode} mode Selected paste mode.
11486
14211
  */
11487
14212
  applyPasteMode(mode) {
11488
14213
  var _a;
@@ -11498,9 +14223,42 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11498
14223
  editableEl.focus();
11499
14224
  this.restoreSelectionRange(this.pendingPastePayload.range);
11500
14225
  this.restoreSelectionControllerState(this.pendingPastePayload);
11501
- const dataTransfer = this.createDataTransfer(this.pendingPastePayload.html, this.pendingPastePayload.text, mode);
14226
+ const strategy = mode === RTEPasteMode.RICH
14227
+ ? this.strategySelector.selectStrategy(this.pendingPastePayload.html)
14228
+ : this.strategySelector.selectStrategyByType(mode);
14229
+ let processedHtml = strategy.process(this.pendingPastePayload.html);
14230
+ // If the strategy supports image processing and there is clipboard file content, parse local images.
14231
+ if (strategy.supportsImageProcessing && this.clipboardFileContent) {
14232
+ processedHtml = this.parseLocalImages(processedHtml, this.clipboardFileContent);
14233
+ }
14234
+ this.clipboardFileContent = null;
14235
+ const dataTransfer = this.createDataTransfer(processedHtml, this.pendingPastePayload.text, strategy.type);
11502
14236
  this.dispatchBeforeInputPaste(editableEl, dataTransfer);
11503
14237
  this.clearPasteChooser();
14238
+ this.emitLocalImageInsertedEvent();
14239
+ }
14240
+ /**
14241
+ * Parses local images from the HTML and emits events for further processing.
14242
+ *
14243
+ * @param {string} html HTML content to parse.
14244
+ * @param {ClipboardFileContent} clipboardFileContent Clipboard file content for image extraction.
14245
+ * @returns {string} Processed HTML with local image references replaced.
14246
+ */
14247
+ parseLocalImages(html, clipboardFileContent) {
14248
+ const { html: result, previews } = this.imageParser.parseLocalImages(html, clipboardFileContent);
14249
+ if (previews.length > 0) {
14250
+ this.pendingImageRegistry.register(previews);
14251
+ }
14252
+ return result;
14253
+ }
14254
+ /**
14255
+ * Emits the `imageInsertedCallback` for any pending previews, allowing the
14256
+ * consumer to upload them and resolve their URLs.
14257
+ */
14258
+ emitLocalImageInsertedEvent() {
14259
+ if (this.pendingImageRegistry.hasPendingNotifications()) {
14260
+ this.pendingImageRegistry.notifyInserted(this.options.imageInsertedCallback, () => { var _a; return (_a = this.editableEl) !== null && _a !== void 0 ? _a : this.getEditableElement(); }, (imageElement) => this.getDTOEntityByDom(imageElement));
14261
+ }
11504
14262
  }
11505
14263
  /**
11506
14264
  * Restores SelectionController state from the captured selection so synthetic
@@ -11574,7 +14332,7 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11574
14332
  *
11575
14333
  * @param {string} html HTML clipboard content.
11576
14334
  * @param {string} text Plain text clipboard content.
11577
- * @param {PasteMode} [mode] Selected paste mode marker.
14335
+ * @param {RTEPasteMode} [mode] Selected paste mode marker.
11578
14336
  * @returns {DataTransfer} Populated data transfer payload.
11579
14337
  */
11580
14338
  createDataTransfer(html, text, mode) {
@@ -11587,7 +14345,7 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11587
14345
  dataTransfer.setData('text/plain', text);
11588
14346
  }
11589
14347
  if (mode) {
11590
- dataTransfer.setData(RTE_PASTE_MODE_DATA_TYPE, mode);
14348
+ dataTransfer.setData(codeBlock.RTE_PASTE_MODE_DATA_TYPE, mode);
11591
14349
  }
11592
14350
  return dataTransfer;
11593
14351
  }
@@ -11600,7 +14358,7 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11600
14358
  if (type === 'text/plain') {
11601
14359
  return text;
11602
14360
  }
11603
- if (type === RTE_PASTE_MODE_DATA_TYPE) {
14361
+ if (type === codeBlock.RTE_PASTE_MODE_DATA_TYPE) {
11604
14362
  return mode !== null && mode !== void 0 ? mode : '';
11605
14363
  }
11606
14364
  return '';
@@ -11635,6 +14393,9 @@ class VegaRTEPasteExtension extends linkExtension.VegaRTEExtension {
11635
14393
  }
11636
14394
  }
11637
14395
  }
14396
+ (() => {
14397
+ linkExtension.VegaRTEExtension.registerActionHandleStrategy(contentState.RTEImageNode.name, REPLACE_IMAGE_SRC, new ImageReplaceSrcStrategy());
14398
+ })();
11638
14399
 
11639
14400
  /**
11640
14401
  * Action to extract the currently selected content (HTML and plain text) from the editor.
@@ -12089,7 +14850,7 @@ class SpanToTokenNodeStrategy extends contentState.ElementToDTOStrategy {
12089
14850
  /**
12090
14851
  * @inheritDoc
12091
14852
  */
12092
- handle(elementsArray) {
14853
+ handle(elementsArray, _options, parentTextAnnotations) {
12093
14854
  const element = elementsArray[0];
12094
14855
  const tokenKey = element.getAttribute('token-key');
12095
14856
  const text = element.textContent;
@@ -12102,7 +14863,9 @@ class SpanToTokenNodeStrategy extends contentState.ElementToDTOStrategy {
12102
14863
  type: 'token',
12103
14864
  token: tokenKey,
12104
14865
  text,
12105
- annotations: Object.assign(Object.assign({}, restAnnotations), { customAttribute: this.createCustomAttrs(customAttribute) }),
14866
+ annotations: Object.assign(Object.assign(Object.assign({}, restAnnotations), ((parentTextAnnotations === null || parentTextAnnotations === void 0 ? void 0 : parentTextAnnotations.inlineHtml)
14867
+ ? { inlineHtml: parentTextAnnotations.inlineHtml }
14868
+ : {})), { customAttribute: this.createCustomAttrs(customAttribute) }),
12106
14869
  };
12107
14870
  }
12108
14871
  /**