@meta-1/editor 0.0.28 → 1.0.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 (502) hide show
  1. package/package.json +52 -67
  2. package/src/components/notion-like/notion-like-editor-toolbar-floating.tsx +181 -0
  3. package/src/components/tiptap-extension/list-normalization-extension.ts +112 -0
  4. package/src/components/tiptap-extension/node-alignment-extension.ts +285 -0
  5. package/src/components/tiptap-extension/node-background-extension.ts +150 -0
  6. package/src/components/tiptap-extension/ui-state-extension.ts +97 -0
  7. package/src/components/tiptap-icons/add-col-left-icon.tsx +30 -0
  8. package/src/components/tiptap-icons/add-col-right-icon.tsx +30 -0
  9. package/src/components/tiptap-icons/add-row-bottom-icon.tsx +30 -0
  10. package/src/components/tiptap-icons/add-row-top-icon.tsx +30 -0
  11. package/src/components/tiptap-icons/ai-sparkles-icon.tsx +32 -0
  12. package/src/components/tiptap-icons/align-bottom-icon.tsx +28 -0
  13. package/src/components/tiptap-icons/align-center-icon.tsx +38 -0
  14. package/src/components/tiptap-icons/align-center-vertical-icon.tsx +34 -0
  15. package/src/components/tiptap-icons/align-end-vertical-icon.tsx +34 -0
  16. package/src/components/tiptap-icons/align-justify-icon.tsx +38 -0
  17. package/src/components/tiptap-icons/align-left-icon.tsx +38 -0
  18. package/src/components/tiptap-icons/align-middle-icon.tsx +55 -0
  19. package/src/components/tiptap-icons/align-right-icon.tsx +38 -0
  20. package/src/components/tiptap-icons/align-start-vertical-icon.tsx +32 -0
  21. package/src/components/tiptap-icons/align-top-icon.tsx +28 -0
  22. package/src/components/tiptap-icons/alignment-icon.tsx +72 -0
  23. package/src/components/tiptap-icons/arrow-down-a-z-icon.tsx +34 -0
  24. package/src/components/tiptap-icons/arrow-down-icon.tsx +24 -0
  25. package/src/components/tiptap-icons/arrow-down-to-line-icon.tsx +28 -0
  26. package/src/components/tiptap-icons/arrow-down-z-a-icon.tsx +34 -0
  27. package/src/components/tiptap-icons/arrow-left-icon.tsx +24 -0
  28. package/src/components/tiptap-icons/arrow-right-icon.tsx +26 -0
  29. package/src/components/tiptap-icons/arrow-up-icon.tsx +26 -0
  30. package/src/components/tiptap-icons/at-sign-icon.tsx +26 -0
  31. package/src/components/tiptap-icons/ban-icon.tsx +26 -0
  32. package/src/components/tiptap-icons/blockquote-icon.tsx +44 -0
  33. package/src/components/tiptap-icons/bold-icon.tsx +26 -0
  34. package/src/components/tiptap-icons/check-ai-icon.tsx +32 -0
  35. package/src/components/tiptap-icons/check-icon.tsx +26 -0
  36. package/src/components/tiptap-icons/chevron-down-icon.tsx +26 -0
  37. package/src/components/tiptap-icons/chevron-right-icon.tsx +26 -0
  38. package/src/components/tiptap-icons/clipboard-icon.tsx +24 -0
  39. package/src/components/tiptap-icons/close-icon.tsx +24 -0
  40. package/src/components/tiptap-icons/code-block-icon.tsx +38 -0
  41. package/src/components/tiptap-icons/code2-icon.tsx +32 -0
  42. package/src/components/tiptap-icons/complete-sentence-icon.tsx +44 -0
  43. package/src/components/tiptap-icons/copy-icon.tsx +32 -0
  44. package/src/components/tiptap-icons/corner-down-left-icon.tsx +26 -0
  45. package/src/components/tiptap-icons/external-link-icon.tsx +28 -0
  46. package/src/components/tiptap-icons/grip-4-icon.tsx +24 -0
  47. package/src/components/tiptap-icons/grip-vertical-icon.tsx +44 -0
  48. package/src/components/tiptap-icons/heading-five-icon.tsx +28 -0
  49. package/src/components/tiptap-icons/heading-four-icon.tsx +28 -0
  50. package/src/components/tiptap-icons/heading-one-icon.tsx +28 -0
  51. package/src/components/tiptap-icons/heading-six-icon.tsx +30 -0
  52. package/src/components/tiptap-icons/heading-three-icon.tsx +36 -0
  53. package/src/components/tiptap-icons/heading-two-icon.tsx +28 -0
  54. package/src/components/tiptap-icons/highlighter-icon.tsx +26 -0
  55. package/src/components/tiptap-icons/image-caption-icon.tsx +38 -0
  56. package/src/components/tiptap-icons/image-icon.tsx +26 -0
  57. package/src/components/tiptap-icons/image-plus-icon.tsx +26 -0
  58. package/src/components/tiptap-icons/italic-icon.tsx +24 -0
  59. package/src/components/tiptap-icons/languages-icon.tsx +34 -0
  60. package/src/components/tiptap-icons/link-icon.tsx +28 -0
  61. package/src/components/tiptap-icons/list-icon.tsx +56 -0
  62. package/src/components/tiptap-icons/list-ordered-icon.tsx +56 -0
  63. package/src/components/tiptap-icons/list-todo-icon.tsx +50 -0
  64. package/src/components/tiptap-icons/message-square-icon.tsx +26 -0
  65. package/src/components/tiptap-icons/message-square-plus-icon.tsx +32 -0
  66. package/src/components/tiptap-icons/mic-ai-icon.tsx +34 -0
  67. package/src/components/tiptap-icons/minus-icon.tsx +26 -0
  68. package/src/components/tiptap-icons/moon-star-icon.tsx +30 -0
  69. package/src/components/tiptap-icons/more-vertical-icon.tsx +38 -0
  70. package/src/components/tiptap-icons/move-horizontal-icon.tsx +24 -0
  71. package/src/components/tiptap-icons/paint-bucket-icon.tsx +32 -0
  72. package/src/components/tiptap-icons/plus-icon.tsx +24 -0
  73. package/src/components/tiptap-icons/plus-small-icon.tsx +24 -0
  74. package/src/components/tiptap-icons/redo2-icon.tsx +26 -0
  75. package/src/components/tiptap-icons/refresh-ai-icon.tsx +34 -0
  76. package/src/components/tiptap-icons/refresh-ccw-icon.tsx +28 -0
  77. package/src/components/tiptap-icons/repeat-2-icon.tsx +26 -0
  78. package/src/components/tiptap-icons/rotate-ccw-icon.tsx +24 -0
  79. package/src/components/tiptap-icons/simplify-2-icon.tsx +24 -0
  80. package/src/components/tiptap-icons/smile-ai-icon.tsx +38 -0
  81. package/src/components/tiptap-icons/smile-plus-icon.tsx +26 -0
  82. package/src/components/tiptap-icons/square-x-icon.tsx +26 -0
  83. package/src/components/tiptap-icons/stop-circle-2-icon.tsx +26 -0
  84. package/src/components/tiptap-icons/strike-icon.tsx +28 -0
  85. package/src/components/tiptap-icons/subscript-icon.tsx +38 -0
  86. package/src/components/tiptap-icons/summarize-text-icon.tsx +36 -0
  87. package/src/components/tiptap-icons/sun-icon.tsx +58 -0
  88. package/src/components/tiptap-icons/superscript-icon.tsx +38 -0
  89. package/src/components/tiptap-icons/table-cell-merge-icon.tsx +44 -0
  90. package/src/components/tiptap-icons/table-cell-split-icon.tsx +44 -0
  91. package/src/components/tiptap-icons/table-column-icon.tsx +26 -0
  92. package/src/components/tiptap-icons/table-header-column-icon.tsx +28 -0
  93. package/src/components/tiptap-icons/table-header-row-icon.tsx +26 -0
  94. package/src/components/tiptap-icons/table-icon.tsx +26 -0
  95. package/src/components/tiptap-icons/table-row-icon.tsx +26 -0
  96. package/src/components/tiptap-icons/text-color-small-icon.tsx +26 -0
  97. package/src/components/tiptap-icons/text-extend-icon.tsx +36 -0
  98. package/src/components/tiptap-icons/text-reduce-icon.tsx +32 -0
  99. package/src/components/tiptap-icons/trash-icon.tsx +26 -0
  100. package/src/components/tiptap-icons/type-icon.tsx +24 -0
  101. package/src/components/tiptap-icons/underline-icon.tsx +26 -0
  102. package/src/components/tiptap-icons/undo2-icon.tsx +26 -0
  103. package/src/components/tiptap-icons/x-icon.tsx +24 -0
  104. package/src/components/tiptap-node/blockquote-node/blockquote-node.css +18 -0
  105. package/src/components/tiptap-node/code-block-node/code-block-node.css +24 -0
  106. package/src/components/tiptap-node/heading-node/heading-node.css +33 -0
  107. package/src/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +11 -0
  108. package/src/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.css +12 -0
  109. package/src/components/tiptap-node/image-node/image-node-extension.ts +169 -0
  110. package/src/components/tiptap-node/image-node/image-node-floating.tsx +41 -0
  111. package/src/components/tiptap-node/image-node/image-node-view.css +60 -0
  112. package/src/components/tiptap-node/image-node/image-node-view.tsx +316 -0
  113. package/src/components/tiptap-node/image-node/image-node.css +28 -0
  114. package/src/components/tiptap-node/image-upload-node/image-upload-node-extension.ts +155 -0
  115. package/src/components/tiptap-node/image-upload-node/image-upload-node.css +141 -0
  116. package/src/components/tiptap-node/image-upload-node/image-upload-node.tsx +513 -0
  117. package/src/components/tiptap-node/image-upload-node/index.tsx +1 -0
  118. package/src/components/tiptap-node/list-node/list-node.css +127 -0
  119. package/src/components/tiptap-node/paragraph-node/paragraph-node.css +198 -0
  120. package/src/components/tiptap-node/table-node/extensions/table-handle/helpers/create-image.ts +273 -0
  121. package/src/components/tiptap-node/table-node/extensions/table-handle/index.ts +2 -0
  122. package/src/components/tiptap-node/table-node/extensions/table-handle/table-handle-plugin.ts +718 -0
  123. package/src/components/tiptap-node/table-node/extensions/table-handle/table-handle.ts +48 -0
  124. package/src/components/tiptap-node/table-node/extensions/table-node-extension.ts +226 -0
  125. package/src/components/tiptap-node/table-node/hooks/use-table-handle-state.ts +66 -0
  126. package/src/components/tiptap-node/table-node/lib/tiptap-table-utils.ts +1289 -0
  127. package/src/components/tiptap-node/table-node/styles/prosemirror-table.css +35 -0
  128. package/src/components/tiptap-node/table-node/styles/table-node.css +158 -0
  129. package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/index.tsx +2 -0
  130. package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/table-add-row-column-button.tsx +94 -0
  131. package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/use-table-add-row-column.ts +325 -0
  132. package/src/components/tiptap-node/table-node/ui/table-align-cell-button/index.tsx +2 -0
  133. package/src/components/tiptap-node/table-node/ui/table-align-cell-button/table-align-cell-button.tsx +129 -0
  134. package/src/components/tiptap-node/table-node/ui/table-align-cell-button/use-table-align-cell.ts +528 -0
  135. package/src/components/tiptap-node/table-node/ui/table-alignment-menu/index.tsx +1 -0
  136. package/src/components/tiptap-node/table-node/ui/table-alignment-menu/table-alignment-menu.tsx +154 -0
  137. package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/index.tsx +1 -0
  138. package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/table-cell-handle-menu.css +62 -0
  139. package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/table-cell-handle-menu.tsx +212 -0
  140. package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/index.tsx +2 -0
  141. package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/table-clear-row-column-content-button.tsx +101 -0
  142. package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/use-table-clear-row-column-content.ts +423 -0
  143. package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/index.tsx +2 -0
  144. package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/table-delete-row-column-button.tsx +100 -0
  145. package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/use-table-delete-row-column.ts +243 -0
  146. package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/index.tsx +2 -0
  147. package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/table-duplicate-row-column-button.tsx +92 -0
  148. package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/use-table-duplicate-row-column.ts +357 -0
  149. package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/index.tsx +2 -0
  150. package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/table-extend-row-column-button.css +17 -0
  151. package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/table-extend-row-column-button.tsx +240 -0
  152. package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/use-table-extend-row-column.ts +118 -0
  153. package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/index.tsx +2 -0
  154. package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/table-fit-to-width-button.tsx +98 -0
  155. package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/use-table-fit-to-width.ts +223 -0
  156. package/src/components/tiptap-node/table-node/ui/table-handle/index.tsx +2 -0
  157. package/src/components/tiptap-node/table-node/ui/table-handle/table-handle.tsx +163 -0
  158. package/src/components/tiptap-node/table-node/ui/table-handle/use-table-handle-positioning.ts +255 -0
  159. package/src/components/tiptap-node/table-node/ui/table-handle-menu/index.tsx +1 -0
  160. package/src/components/tiptap-node/table-node/ui/table-handle-menu/table-handle-menu.css +39 -0
  161. package/src/components/tiptap-node/table-node/ui/table-handle-menu/table-handle-menu.tsx +681 -0
  162. package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/index.tsx +2 -0
  163. package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/table-header-row-column-button.tsx +99 -0
  164. package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/use-table-header-row-column.ts +227 -0
  165. package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/index.tsx +2 -0
  166. package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/table-merge-split-cell-button.tsx +125 -0
  167. package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/use-table-merge-split-cell.ts +267 -0
  168. package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/index.tsx +2 -0
  169. package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/table-move-row-column-button.tsx +123 -0
  170. package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +431 -0
  171. package/src/components/tiptap-node/table-node/ui/table-selection-overlay/index.tsx +1 -0
  172. package/src/components/tiptap-node/table-node/ui/table-selection-overlay/table-selection-overlay.tsx +483 -0
  173. package/src/components/tiptap-node/table-node/ui/table-selection-overlay/use-resize-overlay.ts +78 -0
  174. package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/index.tsx +2 -0
  175. package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/table-sort-row-column-button.tsx +100 -0
  176. package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/use-table-sort-row-column.ts +444 -0
  177. package/src/components/tiptap-node/table-node/ui/table-trigger-button/index.tsx +3 -0
  178. package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-grid-selector.css +39 -0
  179. package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-grid-selector.tsx +219 -0
  180. package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-trigger-button.tsx +132 -0
  181. package/src/components/tiptap-node/table-node/ui/table-trigger-button/use-table-trigger.ts +166 -0
  182. package/src/components/tiptap-ui/blockquote-button/blockquote-button.tsx +125 -0
  183. package/src/components/tiptap-ui/blockquote-button/index.tsx +2 -0
  184. package/src/components/tiptap-ui/blockquote-button/use-blockquote.ts +246 -0
  185. package/src/components/tiptap-ui/code-block-button/code-block-button.tsx +100 -0
  186. package/src/components/tiptap-ui/code-block-button/index.tsx +2 -0
  187. package/src/components/tiptap-ui/code-block-button/use-code-block.ts +256 -0
  188. package/src/components/tiptap-ui/color-highlight-button/color-highlight-button.css +32 -0
  189. package/src/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx +171 -0
  190. package/src/components/tiptap-ui/color-highlight-button/index.tsx +2 -0
  191. package/src/components/tiptap-ui/color-highlight-button/use-color-highlight.ts +296 -0
  192. package/src/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +211 -0
  193. package/src/components/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  194. package/src/components/tiptap-ui/color-menu/color-menu.tsx +178 -0
  195. package/src/components/tiptap-ui/color-menu/index.tsx +1 -0
  196. package/src/components/tiptap-ui/color-text-button/color-text-button.css +31 -0
  197. package/src/components/tiptap-ui/color-text-button/color-text-button.tsx +150 -0
  198. package/src/components/tiptap-ui/color-text-button/index.tsx +2 -0
  199. package/src/components/tiptap-ui/color-text-button/use-color-text.ts +251 -0
  200. package/src/components/tiptap-ui/color-text-popover/color-text-popover.css +25 -0
  201. package/src/components/tiptap-ui/color-text-popover/color-text-popover.tsx +360 -0
  202. package/src/components/tiptap-ui/color-text-popover/index.tsx +2 -0
  203. package/src/components/tiptap-ui/color-text-popover/use-color-text-popover.ts +229 -0
  204. package/src/components/tiptap-ui/copy-anchor-link-button/copy-anchor-link-button.tsx +118 -0
  205. package/src/components/tiptap-ui/copy-anchor-link-button/index.tsx +3 -0
  206. package/src/components/tiptap-ui/copy-anchor-link-button/use-copy-anchor-link.ts +252 -0
  207. package/src/components/tiptap-ui/copy-anchor-link-button/use-scroll-to-hash.ts +128 -0
  208. package/src/components/tiptap-ui/copy-to-clipboard-button/copy-to-clipboard-button.tsx +116 -0
  209. package/src/components/tiptap-ui/copy-to-clipboard-button/index.tsx +2 -0
  210. package/src/components/tiptap-ui/copy-to-clipboard-button/use-copy-to-clipboard.ts +234 -0
  211. package/src/components/tiptap-ui/delete-node-button/delete-node-button.tsx +98 -0
  212. package/src/components/tiptap-ui/delete-node-button/index.tsx +2 -0
  213. package/src/components/tiptap-ui/delete-node-button/use-delete-node.ts +236 -0
  214. package/src/components/tiptap-ui/drag-context-menu/drag-context-menu-types.ts +28 -0
  215. package/src/components/tiptap-ui/drag-context-menu/drag-context-menu.css +17 -0
  216. package/src/components/tiptap-ui/drag-context-menu/drag-context-menu.tsx +413 -0
  217. package/src/components/tiptap-ui/drag-context-menu/index.tsx +2 -0
  218. package/src/components/tiptap-ui/duplicate-button/duplicate-button.tsx +114 -0
  219. package/src/components/tiptap-ui/duplicate-button/index.tsx +2 -0
  220. package/src/components/tiptap-ui/duplicate-button/use-duplicate.ts +208 -0
  221. package/src/components/tiptap-ui/emoji-dropdown-menu/emoji-dropdown-menu.tsx +103 -0
  222. package/src/components/tiptap-ui/emoji-dropdown-menu/index.tsx +1 -0
  223. package/src/components/tiptap-ui/emoji-menu/emoji-menu-utils.ts +36 -0
  224. package/src/components/tiptap-ui/emoji-menu/emoji-menu.css +30 -0
  225. package/src/components/tiptap-ui/emoji-menu/emoji-menu.tsx +142 -0
  226. package/src/components/tiptap-ui/emoji-menu/index.tsx +2 -0
  227. package/src/components/tiptap-ui/emoji-trigger-button/emoji-trigger-button.tsx +128 -0
  228. package/src/components/tiptap-ui/emoji-trigger-button/index.tsx +2 -0
  229. package/src/components/tiptap-ui/emoji-trigger-button/use-emoji-trigger.ts +315 -0
  230. package/src/components/tiptap-ui/heading-button/heading-button.tsx +127 -0
  231. package/src/components/tiptap-ui/heading-button/index.tsx +2 -0
  232. package/src/components/tiptap-ui/heading-button/use-heading.ts +321 -0
  233. package/src/components/tiptap-ui/image-align-button/image-align-button.tsx +114 -0
  234. package/src/components/tiptap-ui/image-align-button/index.tsx +2 -0
  235. package/src/components/tiptap-ui/image-align-button/use-image-align.ts +295 -0
  236. package/src/components/tiptap-ui/image-caption-button/image-caption-button.tsx +77 -0
  237. package/src/components/tiptap-ui/image-caption-button/index.tsx +2 -0
  238. package/src/components/tiptap-ui/image-caption-button/use-image-caption.ts +212 -0
  239. package/src/components/tiptap-ui/image-download-button/image-download-button.tsx +104 -0
  240. package/src/components/tiptap-ui/image-download-button/index.tsx +2 -0
  241. package/src/components/tiptap-ui/image-download-button/use-image-download.ts +364 -0
  242. package/src/components/tiptap-ui/image-upload-button/image-upload-button.tsx +133 -0
  243. package/src/components/tiptap-ui/image-upload-button/index.tsx +2 -0
  244. package/src/components/tiptap-ui/image-upload-button/use-image-upload.ts +192 -0
  245. package/src/components/tiptap-ui/link-popover/index.tsx +2 -0
  246. package/src/components/tiptap-ui/link-popover/link-popover.tsx +271 -0
  247. package/src/components/tiptap-ui/link-popover/use-link-popover.ts +286 -0
  248. package/src/components/tiptap-ui/list-button/index.tsx +2 -0
  249. package/src/components/tiptap-ui/list-button/list-button.tsx +123 -0
  250. package/src/components/tiptap-ui/list-button/use-list.ts +326 -0
  251. package/src/components/tiptap-ui/mark-button/index.tsx +2 -0
  252. package/src/components/tiptap-ui/mark-button/mark-button.tsx +110 -0
  253. package/src/components/tiptap-ui/mark-button/use-mark.ts +195 -0
  254. package/src/components/tiptap-ui/mention-dropdown-menu/index.tsx +1 -0
  255. package/src/components/tiptap-ui/mention-dropdown-menu/mention-dropdown-menu.tsx +212 -0
  256. package/src/components/tiptap-ui/mention-trigger-button/index.tsx +2 -0
  257. package/src/components/tiptap-ui/mention-trigger-button/mention-trigger-button.tsx +122 -0
  258. package/src/components/tiptap-ui/mention-trigger-button/use-mention-trigger.ts +339 -0
  259. package/src/components/tiptap-ui/move-node-button/index.tsx +2 -0
  260. package/src/components/tiptap-ui/move-node-button/move-node-button.tsx +120 -0
  261. package/src/components/tiptap-ui/move-node-button/use-move-node.ts +207 -0
  262. package/src/components/tiptap-ui/reset-all-formatting-button/index.tsx +2 -0
  263. package/src/components/tiptap-ui/reset-all-formatting-button/reset-all-formatting-button.tsx +126 -0
  264. package/src/components/tiptap-ui/reset-all-formatting-button/use-reset-all-formatting.ts +250 -0
  265. package/src/components/tiptap-ui/slash-command-trigger-button/index.tsx +2 -0
  266. package/src/components/tiptap-ui/slash-command-trigger-button/slash-command-trigger-button.tsx +128 -0
  267. package/src/components/tiptap-ui/slash-command-trigger-button/use-slash-command-trigger.ts +255 -0
  268. package/src/components/tiptap-ui/slash-dropdown-menu/index.tsx +2 -0
  269. package/src/components/tiptap-ui/slash-dropdown-menu/slash-dropdown-menu.css +33 -0
  270. package/src/components/tiptap-ui/slash-dropdown-menu/slash-dropdown-menu.tsx +159 -0
  271. package/src/components/tiptap-ui/slash-dropdown-menu/use-slash-dropdown-menu.ts +317 -0
  272. package/src/components/tiptap-ui/text-align-button/index.tsx +2 -0
  273. package/src/components/tiptap-ui/text-align-button/text-align-button.tsx +120 -0
  274. package/src/components/tiptap-ui/text-align-button/use-text-align.ts +224 -0
  275. package/src/components/tiptap-ui/text-button/index.tsx +2 -0
  276. package/src/components/tiptap-ui/text-button/text-button.tsx +117 -0
  277. package/src/components/tiptap-ui/text-button/use-text.ts +264 -0
  278. package/src/components/tiptap-ui/turn-into-dropdown/index.tsx +2 -0
  279. package/src/components/tiptap-ui/turn-into-dropdown/turn-into-dropdown.tsx +192 -0
  280. package/src/components/tiptap-ui/turn-into-dropdown/use-turn-into-dropdown.ts +260 -0
  281. package/src/components/tiptap-ui/undo-redo-button/index.tsx +2 -0
  282. package/src/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx +126 -0
  283. package/src/components/tiptap-ui/undo-redo-button/use-undo-redo.ts +184 -0
  284. package/src/components/tiptap-ui-primitive/avatar/avatar.css +83 -0
  285. package/src/components/tiptap-ui-primitive/avatar/avatar.tsx +239 -0
  286. package/src/components/tiptap-ui-primitive/avatar/index.tsx +1 -0
  287. package/src/components/tiptap-ui-primitive/badge/badge-colors.css +358 -0
  288. package/src/components/tiptap-ui-primitive/badge/badge-group.css +18 -0
  289. package/src/components/tiptap-ui-primitive/badge/badge.css +93 -0
  290. package/src/components/tiptap-ui-primitive/badge/badge.tsx +46 -0
  291. package/src/components/tiptap-ui-primitive/badge/index.tsx +1 -0
  292. package/src/components/tiptap-ui-primitive/button/button-colors.css +6 -0
  293. package/src/components/tiptap-ui-primitive/button/button-group.css +17 -0
  294. package/src/components/tiptap-ui-primitive/button/button.css +428 -0
  295. package/src/components/tiptap-ui-primitive/button/button.tsx +116 -0
  296. package/src/components/tiptap-ui-primitive/button/index.tsx +1 -0
  297. package/src/components/tiptap-ui-primitive/card/card.css +42 -0
  298. package/src/components/tiptap-ui-primitive/card/card.tsx +79 -0
  299. package/src/components/tiptap-ui-primitive/card/index.tsx +1 -0
  300. package/src/components/tiptap-ui-primitive/combobox/combobox.css +15 -0
  301. package/src/components/tiptap-ui-primitive/combobox/combobox.tsx +73 -0
  302. package/src/components/tiptap-ui-primitive/combobox/index.tsx +1 -0
  303. package/src/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.css +49 -0
  304. package/src/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +98 -0
  305. package/src/components/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
  306. package/src/components/tiptap-ui-primitive/input/index.tsx +1 -0
  307. package/src/components/tiptap-ui-primitive/input/input.css +26 -0
  308. package/src/components/tiptap-ui-primitive/input/input.tsx +24 -0
  309. package/src/components/tiptap-ui-primitive/label/index.tsx +1 -0
  310. package/src/components/tiptap-ui-primitive/label/label.css +9 -0
  311. package/src/components/tiptap-ui-primitive/label/label.tsx +42 -0
  312. package/src/components/tiptap-ui-primitive/menu/index.tsx +5 -0
  313. package/src/components/tiptap-ui-primitive/menu/menu-context.ts +19 -0
  314. package/src/components/tiptap-ui-primitive/menu/menu-hooks.ts +102 -0
  315. package/src/components/tiptap-ui-primitive/menu/menu-types.ts +56 -0
  316. package/src/components/tiptap-ui-primitive/menu/menu-utils.ts +64 -0
  317. package/src/components/tiptap-ui-primitive/menu/menu.css +49 -0
  318. package/src/components/tiptap-ui-primitive/menu/menu.tsx +235 -0
  319. package/src/components/tiptap-ui-primitive/popover/index.tsx +1 -0
  320. package/src/components/tiptap-ui-primitive/popover/popover.css +49 -0
  321. package/src/components/tiptap-ui-primitive/popover/popover.tsx +37 -0
  322. package/src/components/tiptap-ui-primitive/separator/index.tsx +1 -0
  323. package/src/components/tiptap-ui-primitive/separator/separator.css +19 -0
  324. package/src/components/tiptap-ui-primitive/separator/separator.tsx +33 -0
  325. package/src/components/tiptap-ui-primitive/sidebar/index.tsx +1 -0
  326. package/src/components/tiptap-ui-primitive/sidebar/sidebar.css +140 -0
  327. package/src/components/tiptap-ui-primitive/sidebar/sidebar.tsx +299 -0
  328. package/src/components/tiptap-ui-primitive/spacer/index.tsx +1 -0
  329. package/src/components/tiptap-ui-primitive/spacer/spacer.tsx +26 -0
  330. package/src/components/tiptap-ui-primitive/textarea-autosize/index.tsx +1 -0
  331. package/src/components/tiptap-ui-primitive/textarea-autosize/textarea-autosize.tsx +18 -0
  332. package/src/components/tiptap-ui-primitive/toolbar/index.tsx +1 -0
  333. package/src/components/tiptap-ui-primitive/toolbar/toolbar.css +65 -0
  334. package/src/components/tiptap-ui-primitive/toolbar/toolbar.tsx +123 -0
  335. package/src/components/tiptap-ui-primitive/tooltip/index.tsx +1 -0
  336. package/src/components/tiptap-ui-primitive/tooltip/tooltip.css +21 -0
  337. package/src/components/tiptap-ui-primitive/tooltip/tooltip.tsx +237 -0
  338. package/src/components/tiptap-ui-utils/floating-element/floating-element-utils.ts +23 -0
  339. package/src/components/tiptap-ui-utils/floating-element/floating-element.tsx +343 -0
  340. package/src/components/tiptap-ui-utils/floating-element/index.tsx +2 -0
  341. package/src/components/tiptap-ui-utils/suggestion-menu/index.tsx +3 -0
  342. package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu-types.ts +91 -0
  343. package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu-utils.ts +87 -0
  344. package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu.tsx +240 -0
  345. package/src/content/index.tsx +27 -0
  346. package/src/content/style.css +12 -0
  347. package/src/contexts/ai-context.tsx +65 -0
  348. package/src/contexts/app-context.tsx +159 -0
  349. package/src/contexts/user-context.tsx +138 -0
  350. package/src/editor/collaboration/index.tsx +64 -0
  351. package/src/editor/index.tsx +144 -42
  352. package/src/hooks/use-composed-ref.ts +47 -0
  353. package/src/hooks/use-cursor-visibility.ts +69 -0
  354. package/src/hooks/use-editor.ts +237 -0
  355. package/src/hooks/use-element-rect.ts +165 -0
  356. package/src/hooks/use-floating-element.ts +101 -0
  357. package/src/hooks/use-floating-toolbar-visibility.ts +123 -0
  358. package/src/hooks/use-is-breakpoint.ts +37 -0
  359. package/src/hooks/use-isomorphic-layout-effect.ts +11 -0
  360. package/src/hooks/use-menu-navigation.ts +182 -0
  361. package/src/hooks/use-on-click-outside.ts +135 -0
  362. package/src/hooks/use-scrolling.ts +75 -0
  363. package/src/hooks/use-throttled-callback.ts +48 -0
  364. package/src/hooks/use-tiptap-editor.ts +49 -0
  365. package/src/hooks/use-ui-editor-state.ts +29 -0
  366. package/src/hooks/use-unmount.ts +21 -0
  367. package/src/hooks/use-window-size.ts +88 -0
  368. package/src/index.ts +4 -7
  369. package/src/lib/tiptap-advanced-utils.ts +362 -0
  370. package/src/lib/tiptap-collab-utils.ts +289 -0
  371. package/src/lib/tiptap-utils.ts +612 -0
  372. package/src/locales/en.json +123 -0
  373. package/src/locales/zh-CN.json +123 -0
  374. package/src/locales/zh-TW.json +123 -0
  375. package/src/styles/variables.css +92 -0
  376. package/README.md +0 -458
  377. package/src/editor/constants.tsx +0 -66
  378. package/src/editor/container.css +0 -46
  379. package/src/editor/control/character-count/index.tsx +0 -39
  380. package/src/editor/control/drag-handle/index.tsx +0 -85
  381. package/src/editor/control/drag-handle/use.content.actions.ts +0 -71
  382. package/src/editor/control/drag-handle/use.data.ts +0 -29
  383. package/src/editor/control/drag-handle/use.handle.id.ts +0 -6
  384. package/src/editor/control/index.tsx +0 -35
  385. package/src/editor/editor.css +0 -626
  386. package/src/editor/extension/block-quote-figure/BlockquoteFigure.ts +0 -73
  387. package/src/editor/extension/block-quote-figure/Quote/Quote.ts +0 -31
  388. package/src/editor/extension/block-quote-figure/Quote/index.ts +0 -1
  389. package/src/editor/extension/block-quote-figure/QuoteCaption/QuoteCaption.ts +0 -54
  390. package/src/editor/extension/block-quote-figure/QuoteCaption/index.ts +0 -1
  391. package/src/editor/extension/block-quote-figure/index.ts +0 -1
  392. package/src/editor/extension/document/index.ts +0 -5
  393. package/src/editor/extension/figcaption/Figcaption.ts +0 -90
  394. package/src/editor/extension/figcaption/index.ts +0 -1
  395. package/src/editor/extension/figure/Figure.ts +0 -62
  396. package/src/editor/extension/figure/index.ts +0 -1
  397. package/src/editor/extension/font-size/FontSize.ts +0 -64
  398. package/src/editor/extension/font-size/index.ts +0 -1
  399. package/src/editor/extension/global-drag-handle/clipboard-serializer.ts +0 -28
  400. package/src/editor/extension/global-drag-handle/index.ts +0 -377
  401. package/src/editor/extension/heading/index.ts +0 -13
  402. package/src/editor/extension/horizontal-rule/HorizontalRule.ts +0 -10
  403. package/src/editor/extension/horizontal-rule/index.ts +0 -1
  404. package/src/editor/extension/image/index.ts +0 -5
  405. package/src/editor/extension/image-block/ImageBlock.ts +0 -103
  406. package/src/editor/extension/image-block/components/ImageBlockMenu.tsx +0 -100
  407. package/src/editor/extension/image-block/components/ImageBlockView.tsx +0 -47
  408. package/src/editor/extension/image-block/components/ImageBlockWidth.tsx +0 -40
  409. package/src/editor/extension/image-block/index.ts +0 -1
  410. package/src/editor/extension/image-upload/ImageUpload.ts +0 -58
  411. package/src/editor/extension/image-upload/index.ts +0 -1
  412. package/src/editor/extension/image-upload/view/ImageUpload.tsx +0 -27
  413. package/src/editor/extension/image-upload/view/ImageUploader.tsx +0 -64
  414. package/src/editor/extension/image-upload/view/hooks.ts +0 -109
  415. package/src/editor/extension/image-upload/view/index.tsx +0 -1
  416. package/src/editor/extension/index.ts +0 -30
  417. package/src/editor/extension/link/Link.ts +0 -39
  418. package/src/editor/extension/link/index.ts +0 -1
  419. package/src/editor/extension/multi-column/Column.ts +0 -33
  420. package/src/editor/extension/multi-column/Columns.ts +0 -65
  421. package/src/editor/extension/multi-column/index.ts +0 -2
  422. package/src/editor/extension/multi-column/menus/ColumnsMenu.tsx +0 -82
  423. package/src/editor/extension/multi-column/menus/index.ts +0 -1
  424. package/src/editor/extension/selection/Selection.ts +0 -36
  425. package/src/editor/extension/selection/index.ts +0 -1
  426. package/src/editor/extension/slash-command/MenuList.tsx +0 -145
  427. package/src/editor/extension/slash-command/groups.ts +0 -153
  428. package/src/editor/extension/slash-command/index.ts +0 -277
  429. package/src/editor/extension/slash-command/types.ts +0 -25
  430. package/src/editor/extension/table/Cell.ts +0 -126
  431. package/src/editor/extension/table/Header.ts +0 -89
  432. package/src/editor/extension/table/Row.ts +0 -8
  433. package/src/editor/extension/table/Table.ts +0 -9
  434. package/src/editor/extension/table/index.ts +0 -4
  435. package/src/editor/extension/table/menus/TableColumn/index.tsx +0 -73
  436. package/src/editor/extension/table/menus/TableColumn/utils.ts +0 -38
  437. package/src/editor/extension/table/menus/TableRow/index.tsx +0 -74
  438. package/src/editor/extension/table/menus/TableRow/utils.ts +0 -38
  439. package/src/editor/extension/table/menus/index.tsx +0 -2
  440. package/src/editor/extension/table/utils.ts +0 -258
  441. package/src/editor/extension/task-item/index.ts +0 -1
  442. package/src/editor/extension/task-item/task-item.ts +0 -225
  443. package/src/editor/extension/task-list/index.ts +0 -1
  444. package/src/editor/extension/task-list/task-list.ts +0 -81
  445. package/src/editor/extension/trailing-node/index.ts +0 -1
  446. package/src/editor/extension/trailing-node/trailing-node.ts +0 -70
  447. package/src/editor/extension/unique-id/index.ts +0 -1
  448. package/src/editor/extension/unique-id/uniqueId.ts +0 -123
  449. package/src/editor/hooks.ts +0 -264
  450. package/src/editor/menus/LinkMenu/LinkMenu.tsx +0 -75
  451. package/src/editor/menus/LinkMenu/index.tsx +0 -1
  452. package/src/editor/menus/TextMenu/TextMenu.tsx +0 -193
  453. package/src/editor/menus/TextMenu/components/AIDropdown.tsx +0 -140
  454. package/src/editor/menus/TextMenu/components/ContentTypePicker.tsx +0 -76
  455. package/src/editor/menus/TextMenu/components/EditLinkPopover.tsx +0 -25
  456. package/src/editor/menus/TextMenu/components/FontFamilyPicker.tsx +0 -84
  457. package/src/editor/menus/TextMenu/components/FontSizePicker.tsx +0 -56
  458. package/src/editor/menus/TextMenu/hooks/useTextmenuCommands.ts +0 -96
  459. package/src/editor/menus/TextMenu/hooks/useTextmenuContentTypes.ts +0 -86
  460. package/src/editor/menus/TextMenu/hooks/useTextmenuStates.ts +0 -50
  461. package/src/editor/menus/TextMenu/index.tsx +0 -2
  462. package/src/editor/menus/types.ts +0 -21
  463. package/src/editor/panels/Colorpicker/ColorButton.tsx +0 -35
  464. package/src/editor/panels/Colorpicker/Colorpicker.tsx +0 -67
  465. package/src/editor/panels/Colorpicker/index.tsx +0 -2
  466. package/src/editor/panels/LinkEditorPanel/LinkEditorPanel.tsx +0 -76
  467. package/src/editor/panels/LinkEditorPanel/index.tsx +0 -1
  468. package/src/editor/panels/LinkPreviewPanel/LinkPreviewPanel.tsx +0 -32
  469. package/src/editor/panels/LinkPreviewPanel/index.tsx +0 -1
  470. package/src/editor/panels/index.tsx +0 -3
  471. package/src/editor/types.tsx +0 -38
  472. package/src/editor/ui/Button/Button.tsx +0 -70
  473. package/src/editor/ui/Button/index.tsx +0 -2
  474. package/src/editor/ui/Dropdown/Dropdown.tsx +0 -39
  475. package/src/editor/ui/Dropdown/index.tsx +0 -1
  476. package/src/editor/ui/Icon.tsx +0 -21
  477. package/src/editor/ui/Loader/Loader.tsx +0 -39
  478. package/src/editor/ui/Loader/index.ts +0 -1
  479. package/src/editor/ui/Loader/types.ts +0 -7
  480. package/src/editor/ui/Panel/index.tsx +0 -109
  481. package/src/editor/ui/PopoverMenu.tsx +0 -127
  482. package/src/editor/ui/Spinner/Spinner.tsx +0 -10
  483. package/src/editor/ui/Spinner/index.tsx +0 -1
  484. package/src/editor/ui/Surface.tsx +0 -27
  485. package/src/editor/ui/Textarea/Textarea.tsx +0 -20
  486. package/src/editor/ui/Textarea/index.tsx +0 -1
  487. package/src/editor/ui/Toggle/Toggle.tsx +0 -39
  488. package/src/editor/ui/Toggle/index.tsx +0 -1
  489. package/src/editor/ui/Toolbar.tsx +0 -107
  490. package/src/editor/ui/Tooltip/index.tsx +0 -77
  491. package/src/editor/ui/Tooltip/types.ts +0 -17
  492. package/src/editor/utils/cssVar.ts +0 -14
  493. package/src/editor/utils/getRenderContainer.ts +0 -39
  494. package/src/editor/utils/index.ts +0 -16
  495. package/src/editor/utils/isCustomNodeSelected.ts +0 -47
  496. package/src/editor/utils/isTextSelected.ts +0 -25
  497. package/src/editor/utils/locale.ts +0 -5
  498. package/src/editor/viewer/index.tsx +0 -26
  499. package/src/globals.css +0 -1
  500. package/src/locales/en-us.ts +0 -133
  501. package/src/locales/zh-cn.ts +0 -133
  502. package/src/locales/zh-tw.ts +0 -133
@@ -0,0 +1,1289 @@
1
+ import type { Editor } from "@tiptap/react"
2
+ import type { Node } from "@tiptap/pm/model"
3
+ import type { Command } from "@tiptap/pm/state"
4
+ import { Selection, type EditorState, type Transaction } from "@tiptap/pm/state"
5
+ import type { FindNodeResult, Rect } from "@tiptap/pm/tables"
6
+ import {
7
+ TableMap,
8
+ CellSelection,
9
+ findTable,
10
+ selectedRect,
11
+ cellAround,
12
+ selectionCell,
13
+ isInTable,
14
+ } from "@tiptap/pm/tables"
15
+ import { Mapping } from "@tiptap/pm/transform"
16
+
17
+ export const RESIZE_MIN_WIDTH = 35
18
+ export const EMPTY_CELL_WIDTH = 120
19
+ export const EMPTY_CELL_HEIGHT = 40
20
+
21
+ export type Orientation = "row" | "column"
22
+ export interface CellInfo extends FindNodeResult {
23
+ row: number
24
+ column: number
25
+ }
26
+
27
+ export type CellCoordinates = {
28
+ row: number
29
+ col: number
30
+ }
31
+
32
+ export type SelectionReturnMode = "state" | "transaction" | "dispatch"
33
+
34
+ export type BaseSelectionOptions = { mode?: SelectionReturnMode }
35
+ export type DispatchSelectionOptions = {
36
+ mode: "dispatch"
37
+ dispatch: (tr: Transaction) => void
38
+ }
39
+ export type TransactionSelectionOptions = { mode: "transaction" }
40
+ export type StateSelectionOptions = { mode?: "state" }
41
+
42
+ export type TableInfo = {
43
+ map: TableMap
44
+ } & FindNodeResult
45
+
46
+ // ============================================================================
47
+ // HELPER CONSTANTS & UTILITIES
48
+ // ============================================================================
49
+
50
+ const EMPTY_CELLS_RESULT = { cells: [], mergedCells: [] }
51
+
52
+ export function isHTMLElement(n: unknown): n is HTMLElement {
53
+ return n instanceof HTMLElement
54
+ }
55
+
56
+ export type DomCellAroundResult =
57
+ | {
58
+ type: "cell"
59
+ domNode: HTMLElement
60
+ tbodyNode: HTMLTableSectionElement | null
61
+ }
62
+ | {
63
+ type: "wrapper"
64
+ domNode: HTMLElement
65
+ tbodyNode: HTMLTableSectionElement | null
66
+ }
67
+
68
+ export function safeClosest<T extends Element>(
69
+ start: Element | null,
70
+ selector: string
71
+ ): T | null {
72
+ return (start?.closest?.(selector) as T | null) ?? null
73
+ }
74
+
75
+ /**
76
+ * Walk up from an element until we find a TD/TH or the table wrapper.
77
+ * Returns the found element plus its tbody (if present).
78
+ */
79
+ export function domCellAround(
80
+ target: Element
81
+ ): DomCellAroundResult | undefined {
82
+ let current: Element | null = target
83
+
84
+ while (
85
+ current &&
86
+ current.tagName !== "TD" &&
87
+ current.tagName !== "TH" &&
88
+ !current.classList.contains("tableWrapper")
89
+ ) {
90
+ if (current.classList.contains("ProseMirror")) return undefined
91
+ current = isHTMLElement(current.parentNode)
92
+ ? (current.parentNode as Element)
93
+ : null
94
+ }
95
+
96
+ if (!current) return undefined
97
+
98
+ if (current.tagName === "TD" || current.tagName === "TH") {
99
+ return {
100
+ type: "cell",
101
+ domNode: current as HTMLElement,
102
+ tbodyNode: safeClosest<HTMLTableSectionElement>(current, "tbody"),
103
+ }
104
+ }
105
+
106
+ return {
107
+ type: "wrapper",
108
+ domNode: current as HTMLElement,
109
+ tbodyNode: (current as HTMLElement).querySelector("tbody"),
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Clamps a value between min and max bounds
115
+ */
116
+ export function clamp(value: number, min: number, max: number): number {
117
+ return Math.max(min, Math.min(value, max))
118
+ }
119
+
120
+ /**
121
+ * Validates if row/col indices are within table bounds
122
+ */
123
+ function isWithinBounds(row: number, col: number, map: TableMap): boolean {
124
+ return row >= 0 && row < map.height && col >= 0 && col < map.width
125
+ }
126
+
127
+ /**
128
+ * Resolves the index for a row or column based on current selection or provided value
129
+ */
130
+ function resolveOrientationIndex(
131
+ state: EditorState,
132
+ table: TableInfo,
133
+ orientation: Orientation,
134
+ providedIndex?: number
135
+ ): number | null {
136
+ if (typeof providedIndex === "number") {
137
+ return providedIndex
138
+ }
139
+
140
+ if (state.selection instanceof CellSelection) {
141
+ const rect = selectedRect(state)
142
+ return orientation === "row" ? rect.top : rect.left
143
+ }
144
+
145
+ const $cell = cellAround(state.selection.$anchor) ?? selectionCell(state)
146
+ if (!$cell) return null
147
+
148
+ const rel = $cell.pos - table.start
149
+ const rect = table.map.findCell(rel)
150
+ return orientation === "row" ? rect.top : rect.left
151
+ }
152
+
153
+ /**
154
+ * Creates a CellInfo object from position data
155
+ */
156
+ function createCellInfo(
157
+ row: number,
158
+ column: number,
159
+ cellPos: number,
160
+ cellNode: Node
161
+ ): CellInfo {
162
+ return {
163
+ row,
164
+ column,
165
+ pos: cellPos,
166
+ node: cellNode,
167
+ start: cellPos + 1,
168
+ depth: cellNode ? cellNode.content.size : 0,
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Checks if a cell is merged (has colspan or rowspan > 1)
174
+ */
175
+ export function isCellMerged(node: Node | null): boolean {
176
+ if (!node) return false
177
+ const colspan = node.attrs.colspan ?? 1
178
+ const rowspan = node.attrs.rowspan ?? 1
179
+ return colspan > 1 || rowspan > 1
180
+ }
181
+
182
+ /**
183
+ * Generic function to collect cells along a row or column
184
+ */
185
+ function collectCells(
186
+ editor: Editor | null,
187
+ orientation: Orientation,
188
+ index?: number,
189
+ tablePos?: number
190
+ ): { cells: CellInfo[]; mergedCells: CellInfo[] } {
191
+ if (!editor) return EMPTY_CELLS_RESULT
192
+
193
+ const { state } = editor
194
+ const table = getTable(editor, tablePos)
195
+ if (!table) return EMPTY_CELLS_RESULT
196
+
197
+ const tableStart = table.start
198
+ const tableNode = table.node
199
+ const map = table.map
200
+
201
+ const resolvedIndex = resolveOrientationIndex(
202
+ state,
203
+ table,
204
+ orientation,
205
+ index
206
+ )
207
+ if (resolvedIndex === null) return EMPTY_CELLS_RESULT
208
+
209
+ // Bounds check
210
+ const maxIndex = orientation === "row" ? map.height : map.width
211
+ if (resolvedIndex < 0 || resolvedIndex >= maxIndex) {
212
+ return EMPTY_CELLS_RESULT
213
+ }
214
+
215
+ const cells: CellInfo[] = []
216
+ const mergedCells: CellInfo[] = []
217
+ const seenMerged = new Set<number>()
218
+
219
+ const iterationCount = orientation === "row" ? map.width : map.height
220
+
221
+ for (let i = 0; i < iterationCount; i++) {
222
+ const row = orientation === "row" ? resolvedIndex : i
223
+ const col = orientation === "row" ? i : resolvedIndex
224
+ const cellIndex = row * map.width + col
225
+ const mapCell = map.map[cellIndex]
226
+
227
+ if (mapCell === undefined) continue
228
+
229
+ const cellPos = tableStart + mapCell
230
+ const cellNode = tableNode.nodeAt(mapCell)
231
+ if (!cellNode) continue
232
+
233
+ const cell = createCellInfo(row, col, cellPos, cellNode)
234
+
235
+ if (isCellMerged(cellNode)) {
236
+ if (!seenMerged.has(cellPos)) {
237
+ mergedCells.push(cell)
238
+ seenMerged.add(cellPos)
239
+ }
240
+ }
241
+
242
+ cells.push(cell)
243
+ }
244
+
245
+ return { cells, mergedCells }
246
+ }
247
+
248
+ /**
249
+ * Generic function to count empty cells from the end of a row or column
250
+ */
251
+ function countEmptyCellsFromEnd(
252
+ editor: Editor,
253
+ tablePos: number,
254
+ orientation: Orientation
255
+ ): number {
256
+ const table = getTable(editor, tablePos)
257
+ if (!table) return 0
258
+
259
+ const { doc } = editor.state
260
+ const maxIndex = orientation === "row" ? table.map.height : table.map.width
261
+
262
+ let emptyCount = 0
263
+ for (let idx = maxIndex - 1; idx >= 0; idx--) {
264
+ const seen = new Set<number>()
265
+ let isLineEmpty = true
266
+
267
+ const iterationCount =
268
+ orientation === "row" ? table.map.width : table.map.height
269
+
270
+ for (let i = 0; i < iterationCount; i++) {
271
+ const row = orientation === "row" ? idx : i
272
+ const col = orientation === "row" ? i : idx
273
+ const rel = table.map.positionAt(row, col, table.node)
274
+
275
+ if (seen.has(rel)) continue
276
+ seen.add(rel)
277
+
278
+ const abs = tablePos + 1 + rel
279
+ const cell = doc.nodeAt(abs)
280
+ if (!cell) continue
281
+
282
+ if (!isCellEmpty(cell)) {
283
+ isLineEmpty = false
284
+ break
285
+ }
286
+ }
287
+
288
+ if (isLineEmpty) emptyCount++
289
+ else break
290
+ }
291
+
292
+ return emptyCount
293
+ }
294
+
295
+ /**
296
+ * Get information about the table at the current selection or a specific position.
297
+ *
298
+ * If `tablePos` is provided, it looks for a table at that exact position.
299
+ * Otherwise, it finds the nearest table containing the current selection.
300
+ *
301
+ * Returns an object with:
302
+ * - `node`: the table node
303
+ * - `pos`: the position of the table in the document
304
+ * - `start`: the position just after the table node (where its content starts)
305
+ * - `map`: the `TableMap` for layout info (rows, columns, spans)
306
+ *
307
+ * If no table is found, returns null.
308
+ */
309
+ export function getTable(editor: Editor | null, tablePos?: number) {
310
+ if (!editor) return null
311
+
312
+ let table = null
313
+
314
+ if (typeof tablePos === "number") {
315
+ const tableNode = editor.state.doc.nodeAt(tablePos)
316
+ if (tableNode?.type.name === "table") {
317
+ table = {
318
+ node: tableNode,
319
+ pos: tablePos,
320
+ start: tablePos + 1,
321
+ depth: editor.state.doc.resolve(tablePos).depth,
322
+ }
323
+ }
324
+ }
325
+
326
+ if (!table) {
327
+ const { state } = editor
328
+ const $from = state.doc.resolve(state.selection.from)
329
+ table = findTable($from)
330
+ }
331
+
332
+ if (!table) return null
333
+
334
+ const tableMap = TableMap.get(table.node)
335
+ if (!tableMap) return null
336
+
337
+ return { ...table, map: tableMap }
338
+ }
339
+
340
+ /**
341
+ * Checks if the current text selection is inside a table cell.
342
+ * @param state - The editor state to check
343
+ * @returns true if the selection is inside a table cell; false otherwise
344
+ */
345
+ export function isSelectionInCell(state: EditorState): boolean {
346
+ const { selection } = state
347
+ const $from = selection.$from
348
+
349
+ for (let depth = $from.depth; depth > 0; depth--) {
350
+ const node = $from.node(depth)
351
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
352
+ return true
353
+ }
354
+ }
355
+
356
+ return false
357
+ }
358
+
359
+ /**
360
+ * Cells overlap a rectangle if any of the cells in the rectangle are merged
361
+ * with cells outside the rectangle.
362
+ */
363
+ export function cellsOverlapRectangle(
364
+ { width, height, map }: TableMap,
365
+ rect: Rect
366
+ ) {
367
+ let indexTop = rect.top * width + rect.left,
368
+ indexLeft = indexTop
369
+ let indexBottom = (rect.bottom - 1) * width + rect.left,
370
+ indexRight = indexTop + (rect.right - rect.left - 1)
371
+ for (let i = rect.top; i < rect.bottom; i++) {
372
+ if (
373
+ (rect.left > 0 && map[indexLeft] == map[indexLeft - 1]) ||
374
+ (rect.right < width && map[indexRight] == map[indexRight + 1])
375
+ )
376
+ return true
377
+ indexLeft += width
378
+ indexRight += width
379
+ }
380
+ for (let i = rect.left; i < rect.right; i++) {
381
+ if (
382
+ (rect.top > 0 && map[indexTop] == map[indexTop - width]) ||
383
+ (rect.bottom < height && map[indexBottom] == map[indexBottom + width])
384
+ )
385
+ return true
386
+ indexTop++
387
+ indexBottom++
388
+ }
389
+ return false
390
+ }
391
+
392
+ /**
393
+ * Runs a function while preserving the editor's selection.
394
+ * @param editor The Tiptap editor instance
395
+ * @param fn The function to run
396
+ * @returns True if the selection was successfully restored, false otherwise
397
+ */
398
+ export function runPreservingCursor(editor: Editor, fn: () => void): boolean {
399
+ const view = editor.view
400
+ const startSel = view.state.selection
401
+ const bookmark = startSel.getBookmark()
402
+
403
+ const mapping = new Mapping()
404
+ const originalDispatch = view.dispatch
405
+
406
+ view.dispatch = (tr) => {
407
+ mapping.appendMapping(tr.mapping)
408
+ originalDispatch(tr)
409
+ }
410
+
411
+ try {
412
+ fn()
413
+ } finally {
414
+ view.dispatch = originalDispatch
415
+ }
416
+
417
+ try {
418
+ const sel = bookmark.map(mapping).resolve(view.state.doc)
419
+ view.dispatch(view.state.tr.setSelection(sel))
420
+ return true
421
+ } catch {
422
+ // Fallback: if the exact spot vanished (e.g., cell deleted),
423
+ // go to the nearest valid position.
424
+ const mappedPos = mapping.map(startSel.from, -1)
425
+ const clamped = clamp(mappedPos, 0, view.state.doc.content.size)
426
+ const near = Selection.near(view.state.doc.resolve(clamped), -1)
427
+ view.dispatch(view.state.tr.setSelection(near))
428
+ return false
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Determines whether a table cell is effectively empty.
434
+ *
435
+ * A cell is considered empty when:
436
+ * - it has no children, or
437
+ * - it contains only whitespace text, or
438
+ * - it contains no text and no non-text leaf nodes (images, embeds, etc.)
439
+ *
440
+ * Early-outs as soon as any meaningful content is found.
441
+ *
442
+ * @param cellNode - The table cell node to check
443
+ * @returns true if the cell is empty; false otherwise
444
+ */
445
+ export function isCellEmpty(cellNode: Node): boolean {
446
+ if (cellNode.childCount === 0) return true
447
+
448
+ let isEmpty = true
449
+ cellNode.descendants((n) => {
450
+ if (n.isText && n.text?.trim()) {
451
+ isEmpty = false
452
+ return false
453
+ }
454
+ if (n.isLeaf && !n.isText) {
455
+ isEmpty = false
456
+ return false
457
+ }
458
+ return true
459
+ })
460
+
461
+ return isEmpty
462
+ }
463
+
464
+ /**
465
+ * Determine if the current selection is a full row or column selection.
466
+ *
467
+ * If the selection is a `CellSelection` that spans an entire row or column,
468
+ * returns an object indicating the type and index:
469
+ * - `{ type: "row", index: number }` for full row selections
470
+ * - `{ type: "column", index: number }` for full column selections
471
+ *
472
+ * If the selection is not a full row/column, or if no table is found, returns null.
473
+ */
474
+ export function getTableSelectionType(
475
+ editor: Editor | null,
476
+ index?: number,
477
+ orientation?: Orientation,
478
+ tablePos?: number
479
+ ): { orientation: Orientation; index: number } | null {
480
+ if (typeof index === "number" && orientation) {
481
+ return { orientation, index }
482
+ }
483
+
484
+ if (!editor) return null
485
+
486
+ const { state } = editor
487
+
488
+ const table = getTable(editor, tablePos)
489
+ if (!table) return null
490
+
491
+ if (state.selection instanceof CellSelection) {
492
+ const rect = selectedRect(state)
493
+ const width = rect.right - rect.left
494
+ const height = rect.bottom - rect.top
495
+
496
+ if (height === 1 && width >= 1) {
497
+ return { orientation: "row", index: rect.top }
498
+ }
499
+
500
+ if (width === 1 && height >= 1) {
501
+ return { orientation: "column", index: rect.left }
502
+ }
503
+
504
+ return null
505
+ }
506
+
507
+ return null
508
+ }
509
+
510
+ /**
511
+ * Get all cells (and unique merged cells) in the selected row or column.
512
+ *
513
+ * - If `index` is provided, uses that row/column index.
514
+ * - If omitted, uses the first selected row/column based on current selection.
515
+ *
516
+ * Returns an object with:
517
+ * - `cells`: all cells in the row/column
518
+ * - `mergedCells`: only the unique cells that have rowspan/colspan > 1
519
+ *
520
+ * If no valid selection or index is found, returns empty arrays.
521
+ */
522
+ export function getRowOrColumnCells(
523
+ editor: Editor | null,
524
+ index?: number,
525
+ orientation?: Orientation,
526
+ tablePos?: number
527
+ ): {
528
+ cells: CellInfo[]
529
+ mergedCells: CellInfo[]
530
+ index?: number
531
+ orientation?: Orientation
532
+ tablePos?: number
533
+ } {
534
+ const emptyResult = {
535
+ cells: [],
536
+ mergedCells: [],
537
+ index: undefined,
538
+ orientation: undefined,
539
+ tablePos: undefined,
540
+ }
541
+
542
+ if (!editor) {
543
+ return emptyResult
544
+ }
545
+
546
+ if (
547
+ typeof index !== "number" &&
548
+ !(editor.state.selection instanceof CellSelection)
549
+ ) {
550
+ return emptyResult
551
+ }
552
+
553
+ let finalIndex = index
554
+ let finalOrientation = orientation
555
+
556
+ if (
557
+ typeof finalIndex !== "number" ||
558
+ !finalOrientation ||
559
+ !["row", "column"].includes(finalOrientation)
560
+ ) {
561
+ const selectionType = getTableSelectionType(editor)
562
+ if (!selectionType) return emptyResult
563
+
564
+ finalIndex = selectionType.index
565
+ finalOrientation = selectionType.orientation
566
+ }
567
+
568
+ const result = collectCells(editor, finalOrientation, finalIndex, tablePos)
569
+ return { ...result, index: finalIndex, orientation: finalOrientation }
570
+ }
571
+
572
+ /**
573
+ * Collect cells (and unique merged cells) from a specific row.
574
+ * - If `rowIndex` is provided, scans that row.
575
+ * - If omitted, uses the first (topmost) selected row based on the current selection.
576
+ */
577
+ export function getRowCells(
578
+ editor: Editor | null,
579
+ rowIndex?: number,
580
+ tablePos?: number
581
+ ): { cells: CellInfo[]; mergedCells: CellInfo[] } {
582
+ return collectCells(editor, "row", rowIndex, tablePos)
583
+ }
584
+
585
+ /**
586
+ * Collect cells (and unique merged cells) from the current table.
587
+ * - If `columnIndex` is provided, scans that column.
588
+ * - If omitted, uses the first (leftmost) selected column based on the current selection.
589
+ */
590
+ export function getColumnCells(
591
+ editor: Editor | null,
592
+ columnIndex?: number,
593
+ tablePos?: number
594
+ ): { cells: CellInfo[]; mergedCells: CellInfo[] } {
595
+ return collectCells(editor, "column", columnIndex, tablePos)
596
+ }
597
+
598
+ /**
599
+ * After moving a row or column, update the selection to the moved item.
600
+ *
601
+ * This ensures that after a move operation, the selection remains on the
602
+ * moved row or column, providing better user feedback.
603
+ *
604
+ * @param editor - The editor instance
605
+ * @param orientation - "row" or "column" indicating what was moved
606
+ * @param newIndex - The new index of the moved row/column
607
+ * @param tablePos - Optional position of the table in the document
608
+ */
609
+ export function updateSelectionAfterAction(
610
+ editor: Editor,
611
+ orientation: Orientation,
612
+ newIndex: number,
613
+ tablePos?: number
614
+ ): void {
615
+ try {
616
+ const table = getTable(editor, tablePos)
617
+ if (!table) return
618
+
619
+ const { state } = editor
620
+ const { map } = table
621
+
622
+ if (orientation === "row") {
623
+ if (newIndex >= 0 && newIndex < map.height) {
624
+ const startCol = 0
625
+ const endCol = map.width - 1
626
+
627
+ const startCellPos =
628
+ table.start + map.positionAt(newIndex, startCol, table.node)
629
+ const endCellPos =
630
+ table.start + map.positionAt(newIndex, endCol, table.node)
631
+
632
+ const $start = state.doc.resolve(startCellPos)
633
+ const $end = state.doc.resolve(endCellPos)
634
+
635
+ const newSelection = CellSelection.create(
636
+ state.doc,
637
+ $start.pos,
638
+ $end.pos
639
+ )
640
+ const tr = state.tr.setSelection(newSelection)
641
+ editor.view.dispatch(tr)
642
+ }
643
+ } else if (orientation === "column") {
644
+ if (newIndex >= 0 && newIndex < map.width) {
645
+ const startRow = 0
646
+ const endRow = map.height - 1
647
+
648
+ const startCellPos =
649
+ table.start + map.positionAt(startRow, newIndex, table.node)
650
+ const endCellPos =
651
+ table.start + map.positionAt(endRow, newIndex, table.node)
652
+
653
+ const $start = state.doc.resolve(startCellPos)
654
+ const $end = state.doc.resolve(endCellPos)
655
+
656
+ const newSelection = CellSelection.create(
657
+ state.doc,
658
+ $start.pos,
659
+ $end.pos
660
+ )
661
+ const tr = state.tr.setSelection(newSelection)
662
+ editor.view.dispatch(tr)
663
+ }
664
+ }
665
+ } catch (error) {
666
+ console.warn("Failed to update selection after move:", error)
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Returns a command that sets the given attributes to the given values,
672
+ * and is only available when the currently selected cell doesn't
673
+ * already have those attributes set to those values.
674
+ *
675
+ * @public
676
+ */
677
+ export function setCellAttr(attrs: Record<string, unknown>): Command
678
+ export function setCellAttr(name: string, value: unknown): Command
679
+ export function setCellAttr(
680
+ nameOrAttrs: string | Record<string, unknown>,
681
+ value?: unknown
682
+ ): Command {
683
+ return function (state, dispatch) {
684
+ if (!isInTable(state)) return false
685
+ const $cell = selectionCell(state)
686
+
687
+ const attrs =
688
+ typeof nameOrAttrs === "string" ? { [nameOrAttrs]: value } : nameOrAttrs
689
+
690
+ if (dispatch) {
691
+ const tr = state.tr
692
+ if (state.selection instanceof CellSelection) {
693
+ state.selection.forEachCell((node, pos) => {
694
+ const needsUpdate = Object.entries(attrs).some(
695
+ ([name, val]) => node.attrs[name] !== val
696
+ )
697
+
698
+ if (needsUpdate) {
699
+ tr.setNodeMarkup(pos, null, {
700
+ ...node.attrs,
701
+ ...attrs,
702
+ })
703
+ }
704
+ })
705
+ } else {
706
+ const needsUpdate = Object.entries(attrs).some(
707
+ ([name, val]) => $cell.nodeAfter!.attrs[name] !== val
708
+ )
709
+
710
+ if (needsUpdate) {
711
+ tr.setNodeMarkup($cell.pos, null, {
712
+ ...$cell.nodeAfter!.attrs,
713
+ ...attrs,
714
+ })
715
+ }
716
+ }
717
+ dispatch(tr)
718
+ }
719
+ return true
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Counts how many consecutive empty rows exist at the bottom of a given table.
725
+ *
726
+ * This function:
727
+ * - Locates the exact table in the document via reference matching
728
+ * - Iterates from the last visual row upward
729
+ * - Deduplicates cells per row using `TableMap` (merged cells can repeat positions)
730
+ * - Treats a row as empty only if all its unique cells are empty by `isCellEmpty`
731
+ *
732
+ * @param editor - The editor whose document contains the table
733
+ * @param target - The table node instance to analyze (must be the same reference as in the doc)
734
+ * @returns The number of trailing empty rows (0 if table not found)
735
+ */
736
+ export function countEmptyRowsFromEnd(
737
+ editor: Editor,
738
+ tablePos: number
739
+ ): number {
740
+ return countEmptyCellsFromEnd(editor, tablePos, "row")
741
+ }
742
+
743
+ /**
744
+ * Counts how many consecutive empty columns exist at the right edge of a given table.
745
+ *
746
+ * Similar to `countEmptyRowsFromEnd`, but scans by columns:
747
+ * - Iterates from the last visual column leftward
748
+ * - Deduplicates per-column cells using `TableMap`
749
+ * - A column is empty only if all unique cells in that column are empty
750
+ *
751
+ * @param editor - The editor whose document contains the table
752
+ * @param target - The table node instance to analyze (must be the same reference as in the doc)
753
+ * @returns The number of trailing empty columns (0 if table not found)
754
+ */
755
+ export function countEmptyColumnsFromEnd(
756
+ editor: Editor,
757
+ tablePos: number
758
+ ): number {
759
+ return countEmptyCellsFromEnd(editor, tablePos, "column")
760
+ }
761
+
762
+ /**
763
+ * Rounds a number with a symmetric "dead-zone" around integer boundaries,
764
+ * which makes drag/resize UX feel less jittery near thresholds.
765
+ *
766
+ * For example, with `margin = 0.3`:
767
+ * - values < n + 0.3 snap down to `n`
768
+ * - values > n + 0.7 snap up to `n + 1`
769
+ * - values in [n + 0.3, n + 0.7] fall back to `Math.round`
770
+ *
771
+ * @param num - The floating value to round
772
+ * @param margin - Half-width of the dead-zone around integer boundaries (default 0.3)
773
+ * @returns The rounded value using the dead-zone heuristic
774
+ */
775
+ export function marginRound(num: number, margin = 0.3): number {
776
+ const floor = Math.floor(num)
777
+ const ceil = Math.ceil(num)
778
+ const lowerBound = floor + margin
779
+ const upperBound = ceil - margin
780
+
781
+ if (num < lowerBound) return floor
782
+ if (num > upperBound) return ceil
783
+ return Math.round(num)
784
+ }
785
+
786
+ /**
787
+ * Compares two DOMRect objects for equality.
788
+ *
789
+ * Treats `undefined` as a valid state, where two `undefined` rects are equal,
790
+ * and `undefined` is not equal to any defined rect.
791
+ *
792
+ * @param a - The first DOMRect or undefined
793
+ * @param b - The second DOMRect or undefined
794
+ * @returns true if both rects are equal or both are undefined; false otherwise
795
+ */
796
+ export function rectEq(a?: DOMRect | null, b?: DOMRect | null): boolean {
797
+ if (!a && !b) return true
798
+ if (!a || !b) return false
799
+ return (
800
+ a.left === b.left &&
801
+ a.top === b.top &&
802
+ a.width === b.width &&
803
+ a.height === b.height
804
+ )
805
+ }
806
+
807
+ /**
808
+ * Applies the transaction based on the specified mode
809
+ */
810
+ function applySelectionWithMode(
811
+ state: EditorState,
812
+ transaction: Transaction,
813
+ options: BaseSelectionOptions | DispatchSelectionOptions
814
+ ): EditorState | Transaction | void {
815
+ const mode: SelectionReturnMode = options.mode ?? "state"
816
+
817
+ switch (mode) {
818
+ case "dispatch": {
819
+ const dispatchOptions = options as DispatchSelectionOptions
820
+ if (typeof dispatchOptions.dispatch === "function") {
821
+ dispatchOptions.dispatch(transaction)
822
+ }
823
+ return
824
+ }
825
+
826
+ case "transaction":
827
+ return transaction
828
+
829
+ default: // "state"
830
+ return state.apply(transaction)
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Create or apply a `CellSelection` inside a table.
836
+ *
837
+ * Depending on the `mode` option, this helper behaves differently:
838
+ *
839
+ * - `"state"` (default) → Returns a new `EditorState` with the selection applied.
840
+ * - `"transaction"` → Returns a `Transaction` with the selection set, without applying it.
841
+ * - `"dispatch"` → Immediately calls `dispatch(tr)` with the new selection.
842
+ *
843
+ * This allows you to reuse the same helper in commands, tests, or utilities
844
+ * without duplicating logic.
845
+ *
846
+ * Example:
847
+ * ```ts
848
+ * // Get new state
849
+ * const nextState = createTableCellSelection(state, tablePosition, { row: 1, col: 1 }, { row: 2, col: 3 })
850
+ *
851
+ * // Get transaction only
852
+ * const tr = createTableCellSelection(state, tablePosition, { row: 0, col: 0 }, { row: 0, col: 2 }, { mode: "transaction" })
853
+ *
854
+ * // Dispatch directly
855
+ * createTableCellSelection(state, tablePosition, { row: 1, col: 1 }, { row: 3, col: 2 }, { mode: "dispatch", dispatch })
856
+ * ```
857
+ */
858
+ export function createTableCellSelection(
859
+ state: EditorState,
860
+ tablePosition: number,
861
+ startCell: CellCoordinates,
862
+ endCell?: CellCoordinates,
863
+ options?: StateSelectionOptions
864
+ ): EditorState
865
+ export function createTableCellSelection(
866
+ state: EditorState,
867
+ tablePosition: number,
868
+ startCell: CellCoordinates,
869
+ endCell: CellCoordinates | undefined,
870
+ options: TransactionSelectionOptions
871
+ ): Transaction
872
+ export function createTableCellSelection(
873
+ state: EditorState,
874
+ tablePosition: number,
875
+ startCell: CellCoordinates,
876
+ endCell: CellCoordinates | undefined,
877
+ options: DispatchSelectionOptions
878
+ ): void
879
+
880
+ export function createTableCellSelection(
881
+ state: EditorState,
882
+ tablePosition: number,
883
+ startCell: CellCoordinates,
884
+ endCell: CellCoordinates = startCell,
885
+ options: BaseSelectionOptions | DispatchSelectionOptions = { mode: "state" }
886
+ ): EditorState | Transaction | void {
887
+ const startCellPosition = getCellPosition(state, tablePosition, startCell)
888
+ const endCellPosition = getCellPosition(state, tablePosition, endCell)
889
+
890
+ if (!startCellPosition || !endCellPosition) {
891
+ return
892
+ }
893
+
894
+ const transaction = state.tr.setSelection(
895
+ new CellSelection(startCellPosition, endCellPosition)
896
+ )
897
+
898
+ return applySelectionWithMode(state, transaction, options)
899
+ }
900
+
901
+ /**
902
+ * Get the position of a cell inside a table by relative row/col indices.
903
+ * Returns the position *before* the cell, which is what `CellSelection` expects.
904
+ */
905
+ export function getCellPosition(
906
+ state: EditorState,
907
+ tablePosition: number,
908
+ cellCoordinates: CellCoordinates
909
+ ) {
910
+ const resolvedTablePosition = state.doc.resolve(tablePosition)
911
+ const resolvedRowPosition = state.doc.resolve(
912
+ resolvedTablePosition.posAtIndex(cellCoordinates.row) + 1
913
+ )
914
+ const resolvedColPosition = state.doc.resolve(
915
+ resolvedRowPosition.posAtIndex(cellCoordinates.col)
916
+ )
917
+
918
+ const $cell = cellAround(resolvedColPosition)
919
+ if (!$cell) return null
920
+
921
+ return resolvedColPosition
922
+ }
923
+
924
+ /**
925
+ * Selects table cells by their (row, col) coordinates.
926
+ *
927
+ * This function can be used in three modes:
928
+ * - `"state"` (default) → Returns a new `EditorState` with the selection applied, or null if failed.
929
+ * - `"transaction"` → Returns a `Transaction` with the selection set, or null if failed.
930
+ * - `"dispatch"` → Immediately dispatches the selection and returns boolean success status.
931
+ *
932
+ * @param editor - The editor instance
933
+ * @param tablePos - Position of the table in the document
934
+ * @param coords - Array of {row, col} coordinates to select
935
+ * @param options - Mode and dispatch options
936
+ */
937
+ export function selectCellsByCoords(
938
+ editor: Editor | null,
939
+ tablePos: number,
940
+ coords: { row: number; col: number }[],
941
+ options?: StateSelectionOptions
942
+ ): EditorState
943
+ export function selectCellsByCoords(
944
+ editor: Editor | null,
945
+ tablePos: number,
946
+ coords: { row: number; col: number }[],
947
+ options: TransactionSelectionOptions
948
+ ): Transaction
949
+ export function selectCellsByCoords(
950
+ editor: Editor | null,
951
+ tablePos: number,
952
+ coords: { row: number; col: number }[],
953
+ options: DispatchSelectionOptions
954
+ ): void
955
+ export function selectCellsByCoords(
956
+ editor: Editor | null,
957
+ tablePos: number,
958
+ coords: { row: number; col: number }[],
959
+ options: BaseSelectionOptions | DispatchSelectionOptions = { mode: "state" }
960
+ ): EditorState | Transaction | void {
961
+ if (!editor) return
962
+
963
+ const table = getTable(editor, tablePos)
964
+ if (!table) return
965
+
966
+ const { state } = editor
967
+ const tableMap = table.map
968
+
969
+ const cleanedCoords = coords
970
+ .map((coord) => ({
971
+ row: clamp(coord.row, 0, tableMap.height - 1),
972
+ col: clamp(coord.col, 0, tableMap.width - 1),
973
+ }))
974
+ .filter((coord) => isWithinBounds(coord.row, coord.col, tableMap))
975
+
976
+ if (cleanedCoords.length === 0) {
977
+ return
978
+ }
979
+
980
+ // --- Find the smallest rectangle that contains all our coordinates ---
981
+ const allRows = cleanedCoords.map((coord) => coord.row)
982
+ const topRow = Math.min(...allRows)
983
+ const bottomRow = Math.max(...allRows)
984
+
985
+ const allCols = cleanedCoords.map((coord) => coord.col)
986
+ const leftCol = Math.min(...allCols)
987
+ const rightCol = Math.max(...allCols)
988
+
989
+ // --- Convert visual coordinates to document positions ---
990
+ // Use TableMap.map array directly to handle merged cells correctly
991
+ const getCellPositionFromMap = (row: number, col: number): number | null => {
992
+ // TableMap.map is a flat array where each entry represents a cell
993
+ // For merged cells, the same offset appears multiple times
994
+ const cellOffset = tableMap.map[row * tableMap.width + col]
995
+ if (cellOffset === undefined) return null
996
+
997
+ // Convert the relative offset to an absolute position in the document
998
+ // tablePos + 1 skips the table opening tag
999
+ return tablePos + 1 + cellOffset
1000
+ }
1001
+
1002
+ // Anchor = where the selection starts (top-left of bounding box)
1003
+ const anchorPosition = getCellPositionFromMap(topRow, leftCol)
1004
+ if (anchorPosition === null) return
1005
+
1006
+ // Head = where the selection ends (usually bottom-right of bounding box)
1007
+ let headPosition = getCellPositionFromMap(bottomRow, rightCol)
1008
+ if (headPosition === null) return
1009
+
1010
+ // --- Handle edge case with merged cells ---
1011
+ // If anchor and head point to the same cell, we need to find a different head
1012
+ // This happens when selecting a single merged cell or when all coords point to one cell
1013
+ if (headPosition === anchorPosition) {
1014
+ let foundDifferentCell = false
1015
+
1016
+ // Search backwards from bottom-right to find a cell with a different position
1017
+ for (let row = bottomRow; row >= topRow && !foundDifferentCell; row--) {
1018
+ for (let col = rightCol; col >= leftCol && !foundDifferentCell; col--) {
1019
+ const candidatePosition = getCellPositionFromMap(row, col)
1020
+
1021
+ if (
1022
+ candidatePosition !== null &&
1023
+ candidatePosition !== anchorPosition
1024
+ ) {
1025
+ headPosition = candidatePosition
1026
+ foundDifferentCell = true
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ try {
1033
+ const anchorRef = state.doc.resolve(anchorPosition)
1034
+ const headRef = state.doc.resolve(headPosition)
1035
+
1036
+ const cellSelection = new CellSelection(anchorRef, headRef)
1037
+ const transaction = state.tr.setSelection(cellSelection)
1038
+
1039
+ return applySelectionWithMode(state, transaction, options)
1040
+ } catch (error) {
1041
+ console.error("Failed to create cell selection:", error)
1042
+ return
1043
+ }
1044
+ }
1045
+
1046
+ /**
1047
+ * Select the cell at (row, col) using `cellAround` to respect merged cells.
1048
+ *
1049
+ * @param editor Tiptap editor
1050
+ * @param row Row index (0-based)
1051
+ * @param col Column index (0-based)
1052
+ * @param tablePos Optional absolute position of the table node
1053
+ * @param dispatch Optional dispatch; defaults to editor.view.dispatch
1054
+ */
1055
+ export function selectCellAt({
1056
+ editor,
1057
+ row,
1058
+ col,
1059
+ tablePos,
1060
+ dispatch,
1061
+ }: {
1062
+ editor: Editor | null
1063
+ row: number
1064
+ col: number
1065
+ tablePos?: number
1066
+ dispatch?: (tr: Transaction) => void
1067
+ }): boolean {
1068
+ if (!editor) return false
1069
+
1070
+ const { state, view } = editor
1071
+ const found = getTable(editor, tablePos)
1072
+ if (!found) return false
1073
+
1074
+ // Bounds check
1075
+ if (!isWithinBounds(row, col, found.map)) {
1076
+ return false
1077
+ }
1078
+
1079
+ const relCellPos = found.map.positionAt(row, col, found.node)
1080
+ const absCellPos = found.start + relCellPos
1081
+
1082
+ const $abs = state.doc.resolve(absCellPos)
1083
+ const $cell = cellAround($abs)
1084
+ const cellPos = $cell ? $cell.pos : absCellPos
1085
+
1086
+ const sel = CellSelection.create(state.doc, cellPos)
1087
+
1088
+ const doDispatch = dispatch ?? view?.dispatch
1089
+ if (!doDispatch) return false
1090
+
1091
+ doDispatch(state.tr.setSelection(sel))
1092
+ return true
1093
+ }
1094
+
1095
+ /**
1096
+ * Selects a boundary cell of the table based on orientation.
1097
+ *
1098
+ * For row orientation, selects the bottom-left cell of the table.
1099
+ * For column orientation, selects the top-right cell of the table.
1100
+ *
1101
+ * This function accounts for merged cells to ensure the correct cell is selected.
1102
+ *
1103
+ * @param editor The Tiptap editor instance
1104
+ * @param tableNode The table node
1105
+ * @param tablePos The position of the table node in the document
1106
+ * @param orientation "row" to select bottom-left, "column" to select top-right
1107
+ * @returns true if the selection was successful; false otherwise
1108
+ */
1109
+ export function selectLastCell(
1110
+ editor: Editor,
1111
+ tableNode: Node,
1112
+ tablePos: number,
1113
+ orientation: Orientation
1114
+ ) {
1115
+ const map = TableMap.get(tableNode)
1116
+ const isRow = orientation === "row"
1117
+
1118
+ // For rows, select bottom-left cell; for columns, select top-right cell
1119
+ const row = isRow ? map.height - 1 : 0
1120
+ const col = isRow ? 0 : map.width - 1
1121
+
1122
+ // Calculate the index in the table map
1123
+ const index = row * map.width + col
1124
+
1125
+ // Get the actual cell position from the map (handles merged cells)
1126
+ const cellPos = map.map[index]
1127
+ if (!cellPos && cellPos !== 0) {
1128
+ console.warn("selectLastCell: cell position not found in map", {
1129
+ index,
1130
+ row,
1131
+ col,
1132
+ map,
1133
+ })
1134
+ return false
1135
+ }
1136
+
1137
+ // Find the row and column of the actual cell
1138
+ const cellIndex = map.map.indexOf(cellPos)
1139
+ const actualRow = cellIndex >= 0 ? Math.floor(cellIndex / map.width) : 0
1140
+ const actualCol = cellIndex >= 0 ? cellIndex % map.width : 0
1141
+
1142
+ return selectCellAt({
1143
+ editor,
1144
+ row: actualRow,
1145
+ col: actualCol,
1146
+ tablePos,
1147
+ dispatch: editor.view.dispatch.bind(editor.view),
1148
+ })
1149
+ }
1150
+
1151
+ /**
1152
+ * Get all (row, col) coordinates for a given row or column index.
1153
+ *
1154
+ * - If `orientation` is "row", returns all columns in that row.
1155
+ * - If `orientation` is "column", returns all rows in that column.
1156
+ *
1157
+ * Returns null if:
1158
+ * - the editor or table is not found
1159
+ * - the index is out of bounds
1160
+ *
1161
+ * @param editor The Tiptap editor instance
1162
+ * @param index The row or column index (0-based)
1163
+ * @param orientation "row" to get row coordinates, "column" for column coordinates
1164
+ * @param tablePos Optional position of the table node in the document
1165
+ * @returns Array of {row, col} objects or null if invalid
1166
+ */
1167
+ export function getIndexCoordinates({
1168
+ editor,
1169
+ index,
1170
+ orientation,
1171
+ tablePos,
1172
+ }: {
1173
+ editor: Editor | null
1174
+ index: number
1175
+ orientation?: Orientation
1176
+ tablePos?: number
1177
+ }): { row: number; col: number }[] | null {
1178
+ if (!editor) return null
1179
+
1180
+ const table = getTable(editor, tablePos)
1181
+ if (!table) return null
1182
+
1183
+ const { map } = table
1184
+ const { width, height } = map
1185
+
1186
+ if (index < 0) return null
1187
+ if (orientation === "row" && index >= height) return null
1188
+ if (orientation === "column" && index >= width) return null
1189
+
1190
+ return orientation === "row"
1191
+ ? Array.from({ length: map.width }, (_, col) => ({ row: index, col }))
1192
+ : Array.from({ length: map.height }, (_, row) => ({ row, col: index }))
1193
+ }
1194
+
1195
+ /**
1196
+ * Given a DOM cell element, find its (row, col) indices within the table.
1197
+ *
1198
+ * This function:
1199
+ * - Locates the nearest ancestor table element
1200
+ * - Uses the editor's document model to resolve the cell's position
1201
+ * - Traverses up the node hierarchy to find the corresponding table cell node
1202
+ * - Uses `TableMap` to translate the cell's position into (row, col) indices
1203
+ *
1204
+ * Returns null if:
1205
+ * - the table or cell cannot be found in the editor's document
1206
+ * - any error occurs during position resolution
1207
+ *
1208
+ * @param cell The HTMLTableCellElement (td or th)
1209
+ * @param tableNode The table node in the ProseMirror document
1210
+ * @param editor The Tiptap editor instance
1211
+ * @returns An object with { rowIndex, colIndex } or null if not found
1212
+ */
1213
+ export function getCellIndicesFromDOM(
1214
+ cell: HTMLTableCellElement,
1215
+ tableNode: Node | null,
1216
+ editor: Editor
1217
+ ): { rowIndex: number; colIndex: number } | null {
1218
+ if (!tableNode) return null
1219
+
1220
+ try {
1221
+ const cellPos = editor.view.posAtDOM(cell, 0)
1222
+ const $cellPos = editor.view.state.doc.resolve(cellPos)
1223
+
1224
+ for (let d = $cellPos.depth; d > 0; d--) {
1225
+ const node = $cellPos.node(d)
1226
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
1227
+ const tableMap = TableMap.get(tableNode)
1228
+ const cellNodePos = $cellPos.before(d)
1229
+ const tableStart = $cellPos.start(d - 2)
1230
+ const cellOffset = cellNodePos - tableStart
1231
+ const cellIndex = tableMap.map.indexOf(cellOffset)
1232
+
1233
+ return {
1234
+ rowIndex: Math.floor(cellIndex / tableMap.width),
1235
+ colIndex: cellIndex % tableMap.width,
1236
+ }
1237
+ }
1238
+ }
1239
+ } catch (error) {
1240
+ console.warn("Could not get cell position:", error)
1241
+ }
1242
+ return null
1243
+ }
1244
+
1245
+ /**
1246
+ * Given a DOM element inside a table, find the corresponding table node and its position.
1247
+ *
1248
+ * This function:
1249
+ * - Locates the nearest ancestor table element
1250
+ * - Uses the editor's document model to resolve the table's position
1251
+ * - Traverses up the node hierarchy to find the corresponding table node
1252
+ *
1253
+ * Returns null if:
1254
+ * - the table cannot be found in the editor's document
1255
+ * - any error occurs during position resolution
1256
+ *
1257
+ * @param tableElement The HTMLTableElement or an element inside it
1258
+ * @param editor The Tiptap editor instance
1259
+ * @returns An object with { node: tableNode, pos: tablePos } or null if not found
1260
+ */
1261
+ export function getTableFromDOM(
1262
+ tableElement: HTMLElement,
1263
+ editor: Editor
1264
+ ): { node: Node; pos: number } | null {
1265
+ try {
1266
+ const pos = editor.view.posAtDOM(tableElement, 0)
1267
+ const $pos = editor.view.state.doc.resolve(pos)
1268
+
1269
+ for (let d = $pos.depth; d >= 0; d--) {
1270
+ const node = $pos.node(d)
1271
+ if (isTableNode(node)) {
1272
+ return { node, pos: d === 0 ? 0 : $pos.before(d) }
1273
+ }
1274
+ }
1275
+ } catch (error) {
1276
+ console.warn("Could not get table from DOM:", error)
1277
+ }
1278
+ return null
1279
+ }
1280
+
1281
+ /**
1282
+ * Checks if a node is a table node
1283
+ */
1284
+ export function isTableNode(node: Node | null | undefined): node is Node {
1285
+ return (
1286
+ !!node &&
1287
+ (node.type.name === "table" || node.type.spec.tableRole === "table")
1288
+ )
1289
+ }