@glideappsfinal/glide-data-grid 6.0.9

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 (745) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc +68 -0
  3. package/API.md +1466 -0
  4. package/CHANGELOG.md +895 -0
  5. package/LICENSE +21 -0
  6. package/README.md +190 -0
  7. package/build.sh +21 -0
  8. package/dist/cjs/cells/boolean-cell.js +87 -0
  9. package/dist/cjs/cells/boolean-cell.js.map +1 -0
  10. package/dist/cjs/cells/bubble-cell.js +53 -0
  11. package/dist/cjs/cells/bubble-cell.js.map +1 -0
  12. package/dist/cjs/cells/cell-types.js +2 -0
  13. package/dist/cjs/cells/cell-types.js.map +1 -0
  14. package/dist/cjs/cells/drilldown-cell.js +171 -0
  15. package/dist/cjs/cells/drilldown-cell.js.map +1 -0
  16. package/dist/cjs/cells/image-cell.js +94 -0
  17. package/dist/cjs/cells/image-cell.js.map +1 -0
  18. package/dist/cjs/cells/index.js +29 -0
  19. package/dist/cjs/cells/index.js.map +1 -0
  20. package/dist/cjs/cells/loading-cell.js +41 -0
  21. package/dist/cjs/cells/loading-cell.js.map +1 -0
  22. package/dist/cjs/cells/markdown-cell.js +30 -0
  23. package/dist/cjs/cells/markdown-cell.js.map +1 -0
  24. package/dist/cjs/cells/marker-cell.js +80 -0
  25. package/dist/cjs/cells/marker-cell.js.map +1 -0
  26. package/dist/cjs/cells/new-row-cell.js +53 -0
  27. package/dist/cjs/cells/new-row-cell.js.map +1 -0
  28. package/dist/cjs/cells/number-cell.js +44 -0
  29. package/dist/cjs/cells/number-cell.js.map +1 -0
  30. package/dist/cjs/cells/protected-cell.js +35 -0
  31. package/dist/cjs/cells/protected-cell.js.map +1 -0
  32. package/dist/cjs/cells/row-id-cell.js +23 -0
  33. package/dist/cjs/cells/row-id-cell.js.map +1 -0
  34. package/dist/cjs/cells/text-cell.js +48 -0
  35. package/dist/cjs/cells/text-cell.js.map +1 -0
  36. package/dist/cjs/cells/uri-cell.js +104 -0
  37. package/dist/cjs/cells/uri-cell.js.map +1 -0
  38. package/dist/cjs/common/browser-detect.js +20 -0
  39. package/dist/cjs/common/browser-detect.js.map +1 -0
  40. package/dist/cjs/common/image-window-loader.js +98 -0
  41. package/dist/cjs/common/image-window-loader.js.map +1 -0
  42. package/dist/cjs/common/is-hotkey.js +76 -0
  43. package/dist/cjs/common/is-hotkey.js.map +1 -0
  44. package/dist/cjs/common/math.js +297 -0
  45. package/dist/cjs/common/math.js.map +1 -0
  46. package/dist/cjs/common/render-state-provider.js +70 -0
  47. package/dist/cjs/common/render-state-provider.js.map +1 -0
  48. package/dist/cjs/common/resize-detector.js +27 -0
  49. package/dist/cjs/common/resize-detector.js.map +1 -0
  50. package/dist/cjs/common/styles.js +135 -0
  51. package/dist/cjs/common/styles.js.map +1 -0
  52. package/dist/cjs/common/support.js +60 -0
  53. package/dist/cjs/common/support.js.map +1 -0
  54. package/dist/cjs/common/utils.js +193 -0
  55. package/dist/cjs/common/utils.js.map +1 -0
  56. package/dist/cjs/data-editor/copy-paste.js +269 -0
  57. package/dist/cjs/data-editor/copy-paste.js.map +1 -0
  58. package/dist/cjs/data-editor/data-editor-fns.js +190 -0
  59. package/dist/cjs/data-editor/data-editor-fns.js.map +1 -0
  60. package/dist/cjs/data-editor/data-editor-keybindings.js +126 -0
  61. package/dist/cjs/data-editor/data-editor-keybindings.js.map +1 -0
  62. package/dist/cjs/data-editor/data-editor.js +2892 -0
  63. package/dist/cjs/data-editor/data-editor.js.map +1 -0
  64. package/dist/cjs/data-editor/group-rename.css +2 -0
  65. package/dist/cjs/data-editor/group-rename.js +49 -0
  66. package/dist/cjs/data-editor/group-rename.js.map +1 -0
  67. package/dist/cjs/data-editor/row-grouping-api.js +34 -0
  68. package/dist/cjs/data-editor/row-grouping-api.js.map +1 -0
  69. package/dist/cjs/data-editor/row-grouping.js +189 -0
  70. package/dist/cjs/data-editor/row-grouping.js.map +1 -0
  71. package/dist/cjs/data-editor/use-autoscroll.js +36 -0
  72. package/dist/cjs/data-editor/use-autoscroll.js.map +1 -0
  73. package/dist/cjs/data-editor/use-cells-for-selection.js +53 -0
  74. package/dist/cjs/data-editor/use-cells-for-selection.js.map +1 -0
  75. package/dist/cjs/data-editor/use-column-sizer.js +189 -0
  76. package/dist/cjs/data-editor/use-column-sizer.js.map +1 -0
  77. package/dist/cjs/data-editor/use-initial-scroll-offset.js +81 -0
  78. package/dist/cjs/data-editor/use-initial-scroll-offset.js.map +1 -0
  79. package/dist/cjs/data-editor/use-rem-adjuster.js +29 -0
  80. package/dist/cjs/data-editor/use-rem-adjuster.js.map +1 -0
  81. package/dist/cjs/data-editor/visible-region.js +2 -0
  82. package/dist/cjs/data-editor/visible-region.js.map +1 -0
  83. package/dist/cjs/data-editor-all.js +19 -0
  84. package/dist/cjs/data-editor-all.js.map +1 -0
  85. package/dist/cjs/index.js +36 -0
  86. package/dist/cjs/index.js.map +1 -0
  87. package/dist/cjs/internal/click-outside-container/click-outside-container.js +34 -0
  88. package/dist/cjs/internal/click-outside-container/click-outside-container.js.map +1 -0
  89. package/dist/cjs/internal/data-editor-container/data-grid-container.js +36 -0
  90. package/dist/cjs/internal/data-editor-container/data-grid-container.js.map +1 -0
  91. package/dist/cjs/internal/data-grid/animation-manager.js +95 -0
  92. package/dist/cjs/internal/data-grid/animation-manager.js.map +1 -0
  93. package/dist/cjs/internal/data-grid/cell-set.js +56 -0
  94. package/dist/cjs/internal/data-grid/cell-set.js.map +1 -0
  95. package/dist/cjs/internal/data-grid/color-parser.js +122 -0
  96. package/dist/cjs/internal/data-grid/color-parser.js.map +1 -0
  97. package/dist/cjs/internal/data-grid/data-grid-sprites.js +64 -0
  98. package/dist/cjs/internal/data-grid/data-grid-sprites.js.map +1 -0
  99. package/dist/cjs/internal/data-grid/data-grid-types.js +299 -0
  100. package/dist/cjs/internal/data-grid/data-grid-types.js.map +1 -0
  101. package/dist/cjs/internal/data-grid/data-grid.js +1208 -0
  102. package/dist/cjs/internal/data-grid/data-grid.js.map +1 -0
  103. package/dist/cjs/internal/data-grid/event-args.js +30 -0
  104. package/dist/cjs/internal/data-grid/event-args.js.map +1 -0
  105. package/dist/cjs/internal/data-grid/image-window-loader-interface.js +2 -0
  106. package/dist/cjs/internal/data-grid/image-window-loader-interface.js.map +1 -0
  107. package/dist/cjs/internal/data-grid/render/data-grid-lib.js +652 -0
  108. package/dist/cjs/internal/data-grid/render/data-grid-lib.js.map +1 -0
  109. package/dist/cjs/internal/data-grid/render/data-grid-render.blit.js +218 -0
  110. package/dist/cjs/internal/data-grid/render/data-grid-render.blit.js.map +1 -0
  111. package/dist/cjs/internal/data-grid/render/data-grid-render.cells.js +369 -0
  112. package/dist/cjs/internal/data-grid/render/data-grid-render.cells.js.map +1 -0
  113. package/dist/cjs/internal/data-grid/render/data-grid-render.header.js +440 -0
  114. package/dist/cjs/internal/data-grid/render/data-grid-render.header.js.map +1 -0
  115. package/dist/cjs/internal/data-grid/render/data-grid-render.js +316 -0
  116. package/dist/cjs/internal/data-grid/render/data-grid-render.js.map +1 -0
  117. package/dist/cjs/internal/data-grid/render/data-grid-render.lines.js +256 -0
  118. package/dist/cjs/internal/data-grid/render/data-grid-render.lines.js.map +1 -0
  119. package/dist/cjs/internal/data-grid/render/data-grid-render.walk.js +157 -0
  120. package/dist/cjs/internal/data-grid/render/data-grid-render.walk.js.map +1 -0
  121. package/dist/cjs/internal/data-grid/render/data-grid.render.rings.js +203 -0
  122. package/dist/cjs/internal/data-grid/render/data-grid.render.rings.js.map +1 -0
  123. package/dist/cjs/internal/data-grid/render/draw-checkbox.js +65 -0
  124. package/dist/cjs/internal/data-grid/render/draw-checkbox.js.map +1 -0
  125. package/dist/cjs/internal/data-grid/render/draw-edit-hover-indicator.js +38 -0
  126. package/dist/cjs/internal/data-grid/render/draw-edit-hover-indicator.js.map +1 -0
  127. package/dist/cjs/internal/data-grid/render/draw-grid-arg.js +2 -0
  128. package/dist/cjs/internal/data-grid/render/draw-grid-arg.js.map +1 -0
  129. package/dist/cjs/internal/data-grid/sprites.js +288 -0
  130. package/dist/cjs/internal/data-grid/sprites.js.map +1 -0
  131. package/dist/cjs/internal/data-grid/use-animation-queue.js +33 -0
  132. package/dist/cjs/internal/data-grid/use-animation-queue.js.map +1 -0
  133. package/dist/cjs/internal/data-grid/use-selection-behavior.js +112 -0
  134. package/dist/cjs/internal/data-grid/use-selection-behavior.js.map +1 -0
  135. package/dist/cjs/internal/data-grid-dnd/data-grid-dnd.js +239 -0
  136. package/dist/cjs/internal/data-grid-dnd/data-grid-dnd.js.map +1 -0
  137. package/dist/cjs/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js +77 -0
  138. package/dist/cjs/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js.map +1 -0
  139. package/dist/cjs/internal/data-grid-overlay-editor/data-grid-overlay-editor.js +124 -0
  140. package/dist/cjs/internal/data-grid-overlay-editor/data-grid-overlay-editor.js.map +1 -0
  141. package/dist/cjs/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.js +34 -0
  142. package/dist/cjs/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.js.map +1 -0
  143. package/dist/cjs/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.js +10 -0
  144. package/dist/cjs/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.js.map +1 -0
  145. package/dist/cjs/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.js +50 -0
  146. package/dist/cjs/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.js.map +1 -0
  147. package/dist/cjs/internal/data-grid-overlay-editor/private/image-overlay-editor-style.js +56 -0
  148. package/dist/cjs/internal/data-grid-overlay-editor/private/image-overlay-editor-style.js.map +1 -0
  149. package/dist/cjs/internal/data-grid-overlay-editor/private/image-overlay-editor.js +21 -0
  150. package/dist/cjs/internal/data-grid-overlay-editor/private/image-overlay-editor.js.map +1 -0
  151. package/dist/cjs/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.js +76 -0
  152. package/dist/cjs/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.js.map +1 -0
  153. package/dist/cjs/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js +32 -0
  154. package/dist/cjs/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js.map +1 -0
  155. package/dist/cjs/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js +15 -0
  156. package/dist/cjs/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js.map +1 -0
  157. package/dist/cjs/internal/data-grid-overlay-editor/private/number-overlay-editor.js +30 -0
  158. package/dist/cjs/internal/data-grid-overlay-editor/private/number-overlay-editor.js.map +1 -0
  159. package/dist/cjs/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.js +53 -0
  160. package/dist/cjs/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.js.map +1 -0
  161. package/dist/cjs/internal/data-grid-overlay-editor/private/uri-overlay-editor.js +21 -0
  162. package/dist/cjs/internal/data-grid-overlay-editor/private/uri-overlay-editor.js.map +1 -0
  163. package/dist/cjs/internal/data-grid-overlay-editor/use-stay-on-screen.js +47 -0
  164. package/dist/cjs/internal/data-grid-overlay-editor/use-stay-on-screen.js.map +1 -0
  165. package/dist/cjs/internal/data-grid-search/data-grid-search-style.js +96 -0
  166. package/dist/cjs/internal/data-grid-search/data-grid-search-style.js.map +1 -0
  167. package/dist/cjs/internal/data-grid-search/data-grid-search.js +297 -0
  168. package/dist/cjs/internal/data-grid-search/data-grid-search.js.map +1 -0
  169. package/dist/cjs/internal/growing-entry/growing-entry-style.js +60 -0
  170. package/dist/cjs/internal/growing-entry/growing-entry-style.js.map +1 -0
  171. package/dist/cjs/internal/growing-entry/growing-entry.js +41 -0
  172. package/dist/cjs/internal/growing-entry/growing-entry.js.map +1 -0
  173. package/dist/cjs/internal/markdown-div/markdown-div.js +41 -0
  174. package/dist/cjs/internal/markdown-div/markdown-div.js.map +1 -0
  175. package/dist/cjs/internal/markdown-div/private/markdown-container.js +19 -0
  176. package/dist/cjs/internal/markdown-div/private/markdown-container.js.map +1 -0
  177. package/dist/cjs/internal/scrolling-data-grid/infinite-scroller.js +265 -0
  178. package/dist/cjs/internal/scrolling-data-grid/infinite-scroller.js.map +1 -0
  179. package/dist/cjs/internal/scrolling-data-grid/scrolling-data-grid.js +155 -0
  180. package/dist/cjs/internal/scrolling-data-grid/scrolling-data-grid.js.map +1 -0
  181. package/dist/cjs/internal/scrolling-data-grid/use-kinetic-scroll.js +65 -0
  182. package/dist/cjs/internal/scrolling-data-grid/use-kinetic-scroll.js.map +1 -0
  183. package/dist/dts/cells/boolean-cell.d.ts +4 -0
  184. package/dist/dts/cells/boolean-cell.d.ts.map +1 -0
  185. package/dist/dts/cells/bubble-cell.d.ts +4 -0
  186. package/dist/dts/cells/bubble-cell.d.ts.map +1 -0
  187. package/dist/dts/cells/cell-types.d.ts +89 -0
  188. package/dist/dts/cells/cell-types.d.ts.map +1 -0
  189. package/dist/dts/cells/drilldown-cell.d.ts +4 -0
  190. package/dist/dts/cells/drilldown-cell.d.ts.map +1 -0
  191. package/dist/dts/cells/image-cell.d.ts +5 -0
  192. package/dist/dts/cells/image-cell.d.ts.map +1 -0
  193. package/dist/dts/cells/index.d.ts +4 -0
  194. package/dist/dts/cells/index.d.ts.map +1 -0
  195. package/dist/dts/cells/loading-cell.d.ts +4 -0
  196. package/dist/dts/cells/loading-cell.d.ts.map +1 -0
  197. package/dist/dts/cells/markdown-cell.d.ts +4 -0
  198. package/dist/dts/cells/markdown-cell.d.ts.map +1 -0
  199. package/dist/dts/cells/marker-cell.d.ts +4 -0
  200. package/dist/dts/cells/marker-cell.d.ts.map +1 -0
  201. package/dist/dts/cells/new-row-cell.d.ts +4 -0
  202. package/dist/dts/cells/new-row-cell.d.ts.map +1 -0
  203. package/dist/dts/cells/number-cell.d.ts +4 -0
  204. package/dist/dts/cells/number-cell.d.ts.map +1 -0
  205. package/dist/dts/cells/protected-cell.d.ts +4 -0
  206. package/dist/dts/cells/protected-cell.d.ts.map +1 -0
  207. package/dist/dts/cells/row-id-cell.d.ts +4 -0
  208. package/dist/dts/cells/row-id-cell.d.ts.map +1 -0
  209. package/dist/dts/cells/text-cell.d.ts +4 -0
  210. package/dist/dts/cells/text-cell.d.ts.map +1 -0
  211. package/dist/dts/cells/uri-cell.d.ts +4 -0
  212. package/dist/dts/cells/uri-cell.d.ts.map +1 -0
  213. package/dist/dts/common/browser-detect.d.ts +11 -0
  214. package/dist/dts/common/browser-detect.d.ts.map +1 -0
  215. package/dist/dts/common/image-window-loader.d.ts +15 -0
  216. package/dist/dts/common/image-window-loader.d.ts.map +1 -0
  217. package/dist/dts/common/is-hotkey.d.ts +7 -0
  218. package/dist/dts/common/is-hotkey.d.ts.map +1 -0
  219. package/dist/dts/common/math.d.ts +20 -0
  220. package/dist/dts/common/math.d.ts.map +1 -0
  221. package/dist/dts/common/render-state-provider.d.ts +20 -0
  222. package/dist/dts/common/render-state-provider.d.ts.map +1 -0
  223. package/dist/dts/common/resize-detector.d.ts +11 -0
  224. package/dist/dts/common/resize-detector.d.ts.map +1 -0
  225. package/dist/dts/common/styles.d.ts +61 -0
  226. package/dist/dts/common/styles.d.ts.map +1 -0
  227. package/dist/dts/common/support.d.ts +13 -0
  228. package/dist/dts/common/support.d.ts.map +1 -0
  229. package/dist/dts/common/utils.d.ts +38 -0
  230. package/dist/dts/common/utils.d.ts.map +1 -0
  231. package/dist/dts/data-editor/copy-paste.d.ts +22 -0
  232. package/dist/dts/data-editor/copy-paste.d.ts.map +1 -0
  233. package/dist/dts/data-editor/data-editor-fns.d.ts +16 -0
  234. package/dist/dts/data-editor/data-editor-fns.d.ts.map +1 -0
  235. package/dist/dts/data-editor/data-editor-keybindings.d.ts +62 -0
  236. package/dist/dts/data-editor/data-editor-keybindings.d.ts.map +1 -0
  237. package/dist/dts/data-editor/data-editor.d.ts +532 -0
  238. package/dist/dts/data-editor/data-editor.d.ts.map +1 -0
  239. package/dist/dts/data-editor/group-rename.d.ts +12 -0
  240. package/dist/dts/data-editor/group-rename.d.ts.map +1 -0
  241. package/dist/dts/data-editor/row-grouping-api.d.ts +21 -0
  242. package/dist/dts/data-editor/row-grouping-api.d.ts.map +1 -0
  243. package/dist/dts/data-editor/row-grouping.d.ts +82 -0
  244. package/dist/dts/data-editor/row-grouping.d.ts.map +1 -0
  245. package/dist/dts/data-editor/use-autoscroll.d.ts +4 -0
  246. package/dist/dts/data-editor/use-autoscroll.d.ts.map +1 -0
  247. package/dist/dts/data-editor/use-cells-for-selection.d.ts +7 -0
  248. package/dist/dts/data-editor/use-cells-for-selection.d.ts.map +1 -0
  249. package/dist/dts/data-editor/use-column-sizer.d.ts +11 -0
  250. package/dist/dts/data-editor/use-column-sizer.d.ts.map +1 -0
  251. package/dist/dts/data-editor/use-initial-scroll-offset.d.ts +9 -0
  252. package/dist/dts/data-editor/use-initial-scroll-offset.d.ts.map +1 -0
  253. package/dist/dts/data-editor/use-rem-adjuster.d.ts +22 -0
  254. package/dist/dts/data-editor/use-rem-adjuster.d.ts.map +1 -0
  255. package/dist/dts/data-editor/visible-region.d.ts +19 -0
  256. package/dist/dts/data-editor/visible-region.d.ts.map +1 -0
  257. package/dist/dts/data-editor-all.d.ts +8 -0
  258. package/dist/dts/data-editor-all.d.ts.map +1 -0
  259. package/dist/dts/index.d.ts +52 -0
  260. package/dist/dts/index.d.ts.map +1 -0
  261. package/dist/dts/internal/click-outside-container/click-outside-container.d.ts +15 -0
  262. package/dist/dts/internal/click-outside-container/click-outside-container.d.ts.map +1 -0
  263. package/dist/dts/internal/data-editor-container/data-grid-container.d.ts +10 -0
  264. package/dist/dts/internal/data-editor-container/data-grid-container.d.ts.map +1 -0
  265. package/dist/dts/internal/data-grid/animation-manager.d.ts +26 -0
  266. package/dist/dts/internal/data-grid/animation-manager.d.ts.map +1 -0
  267. package/dist/dts/internal/data-grid/cell-set.d.ts +17 -0
  268. package/dist/dts/internal/data-grid/cell-set.d.ts.map +1 -0
  269. package/dist/dts/internal/data-grid/color-parser.d.ts +16 -0
  270. package/dist/dts/internal/data-grid/color-parser.d.ts.map +1 -0
  271. package/dist/dts/internal/data-grid/data-grid-sprites.d.ts +35 -0
  272. package/dist/dts/internal/data-grid/data-grid-sprites.d.ts.map +1 -0
  273. package/dist/dts/internal/data-grid/data-grid-types.d.ts +443 -0
  274. package/dist/dts/internal/data-grid/data-grid-types.d.ts.map +1 -0
  275. package/dist/dts/internal/data-grid/data-grid.d.ts +244 -0
  276. package/dist/dts/internal/data-grid/data-grid.d.ts.map +1 -0
  277. package/dist/dts/internal/data-grid/event-args.d.ts +117 -0
  278. package/dist/dts/internal/data-grid/event-args.d.ts.map +1 -0
  279. package/dist/dts/internal/data-grid/image-window-loader-interface.d.ts +9 -0
  280. package/dist/dts/internal/data-grid/image-window-loader-interface.d.ts.map +1 -0
  281. package/dist/dts/internal/data-grid/render/data-grid-lib.d.ts +60 -0
  282. package/dist/dts/internal/data-grid/render/data-grid-lib.d.ts.map +1 -0
  283. package/dist/dts/internal/data-grid/render/data-grid-render.blit.d.ts +20 -0
  284. package/dist/dts/internal/data-grid/render/data-grid-render.blit.d.ts.map +1 -0
  285. package/dist/dts/internal/data-grid/render/data-grid-render.cells.d.ts +32 -0
  286. package/dist/dts/internal/data-grid/render/data-grid-render.cells.d.ts.map +1 -0
  287. package/dist/dts/internal/data-grid/render/data-grid-render.d.ts +3 -0
  288. package/dist/dts/internal/data-grid/render/data-grid-render.d.ts.map +1 -0
  289. package/dist/dts/internal/data-grid/render/data-grid-render.header.d.ts +22 -0
  290. package/dist/dts/internal/data-grid/render/data-grid-render.header.d.ts.map +1 -0
  291. package/dist/dts/internal/data-grid/render/data-grid-render.lines.d.ts +10 -0
  292. package/dist/dts/internal/data-grid/render/data-grid-render.lines.d.ts.map +1 -0
  293. package/dist/dts/internal/data-grid/render/data-grid-render.walk.d.ts +14 -0
  294. package/dist/dts/internal/data-grid/render/data-grid-render.walk.d.ts.map +1 -0
  295. package/dist/dts/internal/data-grid/render/data-grid.render.rings.d.ts +8 -0
  296. package/dist/dts/internal/data-grid/render/data-grid.render.rings.d.ts.map +1 -0
  297. package/dist/dts/internal/data-grid/render/draw-checkbox.d.ts +4 -0
  298. package/dist/dts/internal/data-grid/render/draw-checkbox.d.ts.map +1 -0
  299. package/dist/dts/internal/data-grid/render/draw-edit-hover-indicator.d.ts +4 -0
  300. package/dist/dts/internal/data-grid/render/draw-edit-hover-indicator.d.ts.map +1 -0
  301. package/dist/dts/internal/data-grid/render/draw-grid-arg.d.ts +73 -0
  302. package/dist/dts/internal/data-grid/render/draw-grid-arg.d.ts.map +1 -0
  303. package/dist/dts/internal/data-grid/sprites.d.ts +34 -0
  304. package/dist/dts/internal/data-grid/sprites.d.ts.map +1 -0
  305. package/dist/dts/internal/data-grid/use-animation-queue.d.ts +5 -0
  306. package/dist/dts/internal/data-grid/use-animation-queue.d.ts.map +1 -0
  307. package/dist/dts/internal/data-grid/use-selection-behavior.d.ts +13 -0
  308. package/dist/dts/internal/data-grid/use-selection-behavior.d.ts.map +1 -0
  309. package/dist/dts/internal/data-grid-dnd/data-grid-dnd.d.ts +63 -0
  310. package/dist/dts/internal/data-grid-dnd/data-grid-dnd.d.ts.map +1 -0
  311. package/dist/dts/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.d.ts +9 -0
  312. package/dist/dts/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.d.ts.map +1 -0
  313. package/dist/dts/internal/data-grid-overlay-editor/data-grid-overlay-editor.d.ts +32 -0
  314. package/dist/dts/internal/data-grid-overlay-editor/data-grid-overlay-editor.d.ts.map +1 -0
  315. package/dist/dts/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.d.ts +2 -0
  316. package/dist/dts/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.d.ts.map +1 -0
  317. package/dist/dts/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.d.ts +7 -0
  318. package/dist/dts/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.d.ts.map +1 -0
  319. package/dist/dts/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.d.ts +8 -0
  320. package/dist/dts/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.d.ts.map +1 -0
  321. package/dist/dts/internal/data-grid-overlay-editor/private/image-overlay-editor-style.d.ts +2 -0
  322. package/dist/dts/internal/data-grid-overlay-editor/private/image-overlay-editor-style.d.ts.map +1 -0
  323. package/dist/dts/internal/data-grid-overlay-editor/private/image-overlay-editor.d.ts +13 -0
  324. package/dist/dts/internal/data-grid-overlay-editor/private/image-overlay-editor.d.ts.map +1 -0
  325. package/dist/dts/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.d.ts +6 -0
  326. package/dist/dts/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.d.ts.map +1 -0
  327. package/dist/dts/internal/data-grid-overlay-editor/private/markdown-overlay-editor.d.ts +14 -0
  328. package/dist/dts/internal/data-grid-overlay-editor/private/markdown-overlay-editor.d.ts.map +1 -0
  329. package/dist/dts/internal/data-grid-overlay-editor/private/number-overlay-editor-style.d.ts +2 -0
  330. package/dist/dts/internal/data-grid-overlay-editor/private/number-overlay-editor-style.d.ts.map +1 -0
  331. package/dist/dts/internal/data-grid-overlay-editor/private/number-overlay-editor.d.ts +17 -0
  332. package/dist/dts/internal/data-grid-overlay-editor/private/number-overlay-editor.d.ts.map +1 -0
  333. package/dist/dts/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.d.ts +2 -0
  334. package/dist/dts/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.d.ts.map +1 -0
  335. package/dist/dts/internal/data-grid-overlay-editor/private/uri-overlay-editor.d.ts +13 -0
  336. package/dist/dts/internal/data-grid-overlay-editor/private/uri-overlay-editor.d.ts.map +1 -0
  337. package/dist/dts/internal/data-grid-overlay-editor/use-stay-on-screen.d.ts +8 -0
  338. package/dist/dts/internal/data-grid-overlay-editor/use-stay-on-screen.d.ts.map +1 -0
  339. package/dist/dts/internal/data-grid-search/data-grid-search-style.d.ts +2 -0
  340. package/dist/dts/internal/data-grid-search/data-grid-search-style.d.ts.map +1 -0
  341. package/dist/dts/internal/data-grid-search/data-grid-search.d.ts +41 -0
  342. package/dist/dts/internal/data-grid-search/data-grid-search.d.ts.map +1 -0
  343. package/dist/dts/internal/growing-entry/growing-entry-style.d.ts +4 -0
  344. package/dist/dts/internal/growing-entry/growing-entry-style.d.ts.map +1 -0
  345. package/dist/dts/internal/growing-entry/growing-entry.d.ts +12 -0
  346. package/dist/dts/internal/growing-entry/growing-entry.d.ts.map +1 -0
  347. package/dist/dts/internal/markdown-div/markdown-div.d.ts +14 -0
  348. package/dist/dts/internal/markdown-div/markdown-div.d.ts.map +1 -0
  349. package/dist/dts/internal/markdown-div/private/markdown-container.d.ts +2 -0
  350. package/dist/dts/internal/markdown-div/private/markdown-container.d.ts.map +1 -0
  351. package/dist/dts/internal/scrolling-data-grid/infinite-scroller.d.ts +38 -0
  352. package/dist/dts/internal/scrolling-data-grid/infinite-scroller.d.ts.map +1 -0
  353. package/dist/dts/internal/scrolling-data-grid/scrolling-data-grid.d.ts +57 -0
  354. package/dist/dts/internal/scrolling-data-grid/scrolling-data-grid.d.ts.map +1 -0
  355. package/dist/dts/internal/scrolling-data-grid/use-kinetic-scroll.d.ts +3 -0
  356. package/dist/dts/internal/scrolling-data-grid/use-kinetic-scroll.d.ts.map +1 -0
  357. package/dist/esm/cells/boolean-cell.js +87 -0
  358. package/dist/esm/cells/boolean-cell.js.map +1 -0
  359. package/dist/esm/cells/bubble-cell.js +53 -0
  360. package/dist/esm/cells/bubble-cell.js.map +1 -0
  361. package/dist/esm/cells/cell-types.js +2 -0
  362. package/dist/esm/cells/cell-types.js.map +1 -0
  363. package/dist/esm/cells/drilldown-cell.js +171 -0
  364. package/dist/esm/cells/drilldown-cell.js.map +1 -0
  365. package/dist/esm/cells/image-cell.js +94 -0
  366. package/dist/esm/cells/image-cell.js.map +1 -0
  367. package/dist/esm/cells/index.js +30 -0
  368. package/dist/esm/cells/index.js.map +1 -0
  369. package/dist/esm/cells/loading-cell.js +41 -0
  370. package/dist/esm/cells/loading-cell.js.map +1 -0
  371. package/dist/esm/cells/markdown-cell.js +30 -0
  372. package/dist/esm/cells/markdown-cell.js.map +1 -0
  373. package/dist/esm/cells/marker-cell.js +80 -0
  374. package/dist/esm/cells/marker-cell.js.map +1 -0
  375. package/dist/esm/cells/new-row-cell.js +53 -0
  376. package/dist/esm/cells/new-row-cell.js.map +1 -0
  377. package/dist/esm/cells/number-cell.js +44 -0
  378. package/dist/esm/cells/number-cell.js.map +1 -0
  379. package/dist/esm/cells/protected-cell.js +35 -0
  380. package/dist/esm/cells/protected-cell.js.map +1 -0
  381. package/dist/esm/cells/row-id-cell.js +23 -0
  382. package/dist/esm/cells/row-id-cell.js.map +1 -0
  383. package/dist/esm/cells/text-cell.js +48 -0
  384. package/dist/esm/cells/text-cell.js.map +1 -0
  385. package/dist/esm/cells/uri-cell.js +104 -0
  386. package/dist/esm/cells/uri-cell.js.map +1 -0
  387. package/dist/esm/common/browser-detect.js +20 -0
  388. package/dist/esm/common/browser-detect.js.map +1 -0
  389. package/dist/esm/common/image-window-loader.js +98 -0
  390. package/dist/esm/common/image-window-loader.js.map +1 -0
  391. package/dist/esm/common/is-hotkey.js +76 -0
  392. package/dist/esm/common/is-hotkey.js.map +1 -0
  393. package/dist/esm/common/math.js +297 -0
  394. package/dist/esm/common/math.js.map +1 -0
  395. package/dist/esm/common/render-state-provider.js +70 -0
  396. package/dist/esm/common/render-state-provider.js.map +1 -0
  397. package/dist/esm/common/resize-detector.js +27 -0
  398. package/dist/esm/common/resize-detector.js.map +1 -0
  399. package/dist/esm/common/styles.js +135 -0
  400. package/dist/esm/common/styles.js.map +1 -0
  401. package/dist/esm/common/support.js +60 -0
  402. package/dist/esm/common/support.js.map +1 -0
  403. package/dist/esm/common/utils.js +193 -0
  404. package/dist/esm/common/utils.js.map +1 -0
  405. package/dist/esm/data-editor/copy-paste.js +269 -0
  406. package/dist/esm/data-editor/copy-paste.js.map +1 -0
  407. package/dist/esm/data-editor/data-editor-fns.js +197 -0
  408. package/dist/esm/data-editor/data-editor-fns.js.map +1 -0
  409. package/dist/esm/data-editor/data-editor-keybindings.js +126 -0
  410. package/dist/esm/data-editor/data-editor-keybindings.js.map +1 -0
  411. package/dist/esm/data-editor/data-editor.js +2892 -0
  412. package/dist/esm/data-editor/data-editor.js.map +1 -0
  413. package/dist/esm/data-editor/group-rename.css +2 -0
  414. package/dist/esm/data-editor/group-rename.js +49 -0
  415. package/dist/esm/data-editor/group-rename.js.map +1 -0
  416. package/dist/esm/data-editor/row-grouping-api.js +34 -0
  417. package/dist/esm/data-editor/row-grouping-api.js.map +1 -0
  418. package/dist/esm/data-editor/row-grouping.js +189 -0
  419. package/dist/esm/data-editor/row-grouping.js.map +1 -0
  420. package/dist/esm/data-editor/use-autoscroll.js +36 -0
  421. package/dist/esm/data-editor/use-autoscroll.js.map +1 -0
  422. package/dist/esm/data-editor/use-cells-for-selection.js +53 -0
  423. package/dist/esm/data-editor/use-cells-for-selection.js.map +1 -0
  424. package/dist/esm/data-editor/use-column-sizer.js +189 -0
  425. package/dist/esm/data-editor/use-column-sizer.js.map +1 -0
  426. package/dist/esm/data-editor/use-initial-scroll-offset.js +81 -0
  427. package/dist/esm/data-editor/use-initial-scroll-offset.js.map +1 -0
  428. package/dist/esm/data-editor/use-rem-adjuster.js +29 -0
  429. package/dist/esm/data-editor/use-rem-adjuster.js.map +1 -0
  430. package/dist/esm/data-editor/visible-region.js +2 -0
  431. package/dist/esm/data-editor/visible-region.js.map +1 -0
  432. package/dist/esm/data-editor-all.js +19 -0
  433. package/dist/esm/data-editor-all.js.map +1 -0
  434. package/dist/esm/index.js +36 -0
  435. package/dist/esm/index.js.map +1 -0
  436. package/dist/esm/internal/click-outside-container/click-outside-container.js +34 -0
  437. package/dist/esm/internal/click-outside-container/click-outside-container.js.map +1 -0
  438. package/dist/esm/internal/data-editor-container/data-grid-container.js +36 -0
  439. package/dist/esm/internal/data-editor-container/data-grid-container.js.map +1 -0
  440. package/dist/esm/internal/data-grid/animation-manager.js +95 -0
  441. package/dist/esm/internal/data-grid/animation-manager.js.map +1 -0
  442. package/dist/esm/internal/data-grid/cell-set.js +56 -0
  443. package/dist/esm/internal/data-grid/cell-set.js.map +1 -0
  444. package/dist/esm/internal/data-grid/color-parser.js +122 -0
  445. package/dist/esm/internal/data-grid/color-parser.js.map +1 -0
  446. package/dist/esm/internal/data-grid/data-grid-sprites.js +65 -0
  447. package/dist/esm/internal/data-grid/data-grid-sprites.js.map +1 -0
  448. package/dist/esm/internal/data-grid/data-grid-types.js +299 -0
  449. package/dist/esm/internal/data-grid/data-grid-types.js.map +1 -0
  450. package/dist/esm/internal/data-grid/data-grid.js +1209 -0
  451. package/dist/esm/internal/data-grid/data-grid.js.map +1 -0
  452. package/dist/esm/internal/data-grid/event-args.js +30 -0
  453. package/dist/esm/internal/data-grid/event-args.js.map +1 -0
  454. package/dist/esm/internal/data-grid/image-window-loader-interface.js +2 -0
  455. package/dist/esm/internal/data-grid/image-window-loader-interface.js.map +1 -0
  456. package/dist/esm/internal/data-grid/render/data-grid-lib.js +653 -0
  457. package/dist/esm/internal/data-grid/render/data-grid-lib.js.map +1 -0
  458. package/dist/esm/internal/data-grid/render/data-grid-render.blit.js +219 -0
  459. package/dist/esm/internal/data-grid/render/data-grid-render.blit.js.map +1 -0
  460. package/dist/esm/internal/data-grid/render/data-grid-render.cells.js +370 -0
  461. package/dist/esm/internal/data-grid/render/data-grid-render.cells.js.map +1 -0
  462. package/dist/esm/internal/data-grid/render/data-grid-render.header.js +440 -0
  463. package/dist/esm/internal/data-grid/render/data-grid-render.header.js.map +1 -0
  464. package/dist/esm/internal/data-grid/render/data-grid-render.js +320 -0
  465. package/dist/esm/internal/data-grid/render/data-grid-render.js.map +1 -0
  466. package/dist/esm/internal/data-grid/render/data-grid-render.lines.js +261 -0
  467. package/dist/esm/internal/data-grid/render/data-grid-render.lines.js.map +1 -0
  468. package/dist/esm/internal/data-grid/render/data-grid-render.walk.js +158 -0
  469. package/dist/esm/internal/data-grid/render/data-grid-render.walk.js.map +1 -0
  470. package/dist/esm/internal/data-grid/render/data-grid.render.rings.js +205 -0
  471. package/dist/esm/internal/data-grid/render/data-grid.render.rings.js.map +1 -0
  472. package/dist/esm/internal/data-grid/render/draw-checkbox.js +65 -0
  473. package/dist/esm/internal/data-grid/render/draw-checkbox.js.map +1 -0
  474. package/dist/esm/internal/data-grid/render/draw-edit-hover-indicator.js +38 -0
  475. package/dist/esm/internal/data-grid/render/draw-edit-hover-indicator.js.map +1 -0
  476. package/dist/esm/internal/data-grid/render/draw-grid-arg.js +2 -0
  477. package/dist/esm/internal/data-grid/render/draw-grid-arg.js.map +1 -0
  478. package/dist/esm/internal/data-grid/sprites.js +288 -0
  479. package/dist/esm/internal/data-grid/sprites.js.map +1 -0
  480. package/dist/esm/internal/data-grid/use-animation-queue.js +34 -0
  481. package/dist/esm/internal/data-grid/use-animation-queue.js.map +1 -0
  482. package/dist/esm/internal/data-grid/use-selection-behavior.js +112 -0
  483. package/dist/esm/internal/data-grid/use-selection-behavior.js.map +1 -0
  484. package/dist/esm/internal/data-grid-dnd/data-grid-dnd.js +239 -0
  485. package/dist/esm/internal/data-grid-dnd/data-grid-dnd.js.map +1 -0
  486. package/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js +77 -0
  487. package/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js.map +1 -0
  488. package/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor.js +124 -0
  489. package/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor.js.map +1 -0
  490. package/dist/esm/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.js +34 -0
  491. package/dist/esm/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.js.map +1 -0
  492. package/dist/esm/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.js +10 -0
  493. package/dist/esm/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.js.map +1 -0
  494. package/dist/esm/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.js +50 -0
  495. package/dist/esm/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.js.map +1 -0
  496. package/dist/esm/internal/data-grid-overlay-editor/private/image-overlay-editor-style.js +56 -0
  497. package/dist/esm/internal/data-grid-overlay-editor/private/image-overlay-editor-style.js.map +1 -0
  498. package/dist/esm/internal/data-grid-overlay-editor/private/image-overlay-editor.js +21 -0
  499. package/dist/esm/internal/data-grid-overlay-editor/private/image-overlay-editor.js.map +1 -0
  500. package/dist/esm/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.js +76 -0
  501. package/dist/esm/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.js.map +1 -0
  502. package/dist/esm/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js +32 -0
  503. package/dist/esm/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js.map +1 -0
  504. package/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js +15 -0
  505. package/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js.map +1 -0
  506. package/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor.js +30 -0
  507. package/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor.js.map +1 -0
  508. package/dist/esm/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.js +53 -0
  509. package/dist/esm/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.js.map +1 -0
  510. package/dist/esm/internal/data-grid-overlay-editor/private/uri-overlay-editor.js +21 -0
  511. package/dist/esm/internal/data-grid-overlay-editor/private/uri-overlay-editor.js.map +1 -0
  512. package/dist/esm/internal/data-grid-overlay-editor/use-stay-on-screen.js +47 -0
  513. package/dist/esm/internal/data-grid-overlay-editor/use-stay-on-screen.js.map +1 -0
  514. package/dist/esm/internal/data-grid-search/data-grid-search-style.js +96 -0
  515. package/dist/esm/internal/data-grid-search/data-grid-search-style.js.map +1 -0
  516. package/dist/esm/internal/data-grid-search/data-grid-search.js +297 -0
  517. package/dist/esm/internal/data-grid-search/data-grid-search.js.map +1 -0
  518. package/dist/esm/internal/growing-entry/growing-entry-style.js +60 -0
  519. package/dist/esm/internal/growing-entry/growing-entry-style.js.map +1 -0
  520. package/dist/esm/internal/growing-entry/growing-entry.js +41 -0
  521. package/dist/esm/internal/growing-entry/growing-entry.js.map +1 -0
  522. package/dist/esm/internal/markdown-div/markdown-div.js +41 -0
  523. package/dist/esm/internal/markdown-div/markdown-div.js.map +1 -0
  524. package/dist/esm/internal/markdown-div/private/markdown-container.js +19 -0
  525. package/dist/esm/internal/markdown-div/private/markdown-container.js.map +1 -0
  526. package/dist/esm/internal/scrolling-data-grid/infinite-scroller.js +265 -0
  527. package/dist/esm/internal/scrolling-data-grid/infinite-scroller.js.map +1 -0
  528. package/dist/esm/internal/scrolling-data-grid/scrolling-data-grid.js +155 -0
  529. package/dist/esm/internal/scrolling-data-grid/scrolling-data-grid.js.map +1 -0
  530. package/dist/esm/internal/scrolling-data-grid/use-kinetic-scroll.js +65 -0
  531. package/dist/esm/internal/scrolling-data-grid/use-kinetic-scroll.js.map +1 -0
  532. package/dist/index.css +2 -0
  533. package/package.json +81 -0
  534. package/src/cells/boolean-cell.tsx +135 -0
  535. package/src/cells/bubble-cell.tsx +68 -0
  536. package/src/cells/cell-types.ts +124 -0
  537. package/src/cells/drilldown-cell.tsx +244 -0
  538. package/src/cells/image-cell.tsx +117 -0
  539. package/src/cells/index.ts +31 -0
  540. package/src/cells/loading-cell.tsx +56 -0
  541. package/src/cells/markdown-cell.tsx +44 -0
  542. package/src/cells/marker-cell.tsx +110 -0
  543. package/src/cells/new-row-cell.tsx +60 -0
  544. package/src/cells/number-cell.tsx +64 -0
  545. package/src/cells/protected-cell.tsx +42 -0
  546. package/src/cells/row-id-cell.tsx +35 -0
  547. package/src/cells/text-cell.tsx +63 -0
  548. package/src/cells/uri-cell.tsx +155 -0
  549. package/src/common/browser-detect.ts +25 -0
  550. package/src/common/image-window-loader.ts +114 -0
  551. package/src/common/is-hotkey.ts +86 -0
  552. package/src/common/math.ts +357 -0
  553. package/src/common/render-state-provider.ts +87 -0
  554. package/src/common/resize-detector.ts +43 -0
  555. package/src/common/styles.ts +214 -0
  556. package/src/common/support.ts +67 -0
  557. package/src/common/utils.tsx +284 -0
  558. package/src/data-editor/copy-paste.ts +320 -0
  559. package/src/data-editor/data-editor-fns.ts +227 -0
  560. package/src/data-editor/data-editor-keybindings.ts +198 -0
  561. package/src/data-editor/data-editor.tsx +4382 -0
  562. package/src/data-editor/group-rename.tsx +67 -0
  563. package/src/data-editor/row-grouping-api.ts +72 -0
  564. package/src/data-editor/row-grouping.ts +326 -0
  565. package/src/data-editor/stories/data-editor-repros.stories.tsx +107 -0
  566. package/src/data-editor/stories/data-editor.stories.tsx +796 -0
  567. package/src/data-editor/stories/utils.tsx +827 -0
  568. package/src/data-editor/use-autoscroll.ts +41 -0
  569. package/src/data-editor/use-cells-for-selection.ts +72 -0
  570. package/src/data-editor/use-column-sizer.ts +253 -0
  571. package/src/data-editor/use-initial-scroll-offset.ts +102 -0
  572. package/src/data-editor/use-rem-adjuster.ts +59 -0
  573. package/src/data-editor/visible-region.ts +20 -0
  574. package/src/data-editor-all.tsx +36 -0
  575. package/src/docs/00-faq.stories.tsx +63 -0
  576. package/src/docs/01-getting-started.stories.tsx +299 -0
  577. package/src/docs/02-editing-data.stories.tsx +365 -0
  578. package/src/docs/03-grid-column.stories.tsx +146 -0
  579. package/src/docs/04-streaming-data.stories.tsx +434 -0
  580. package/src/docs/05-copy-paste.stories.tsx.tsx +279 -0
  581. package/src/docs/06-search.stories.tsx +219 -0
  582. package/src/docs/07-column-grouping.stories.tsx +212 -0
  583. package/src/docs/08-theming.stories.tsx +409 -0
  584. package/src/docs/09-menus.stories.tsx +344 -0
  585. package/src/docs/doc-wrapper.tsx +512 -0
  586. package/src/docs/examples/add-column.stories.tsx +62 -0
  587. package/src/docs/examples/add-data-to-middle.stories.tsx +93 -0
  588. package/src/docs/examples/add-data-to-top.stories.tsx +70 -0
  589. package/src/docs/examples/add-data.stories.tsx +74 -0
  590. package/src/docs/examples/all-cell-kinds.stories.tsx +61 -0
  591. package/src/docs/examples/append-row-handle.stories.tsx +79 -0
  592. package/src/docs/examples/automatic-row-markers.stories.tsx +57 -0
  593. package/src/docs/examples/built-in-search.stories.tsx +83 -0
  594. package/src/docs/examples/cell-activated-event.stories.tsx +92 -0
  595. package/src/docs/examples/column-group-collapse.stories.tsx +105 -0
  596. package/src/docs/examples/column-groups.stories.tsx +51 -0
  597. package/src/docs/examples/content-alignment.stories.tsx +64 -0
  598. package/src/docs/examples/controlled-search.stories.tsx +93 -0
  599. package/src/docs/examples/controlled-selection.stories.tsx +98 -0
  600. package/src/docs/examples/copy-support.stories.tsx +63 -0
  601. package/src/docs/examples/custom-editors.stories.tsx +90 -0
  602. package/src/docs/examples/custom-event-target.stories.tsx +157 -0
  603. package/src/docs/examples/custom-group-header.stories.tsx +423 -0
  604. package/src/docs/examples/custom-header-icons.stories.tsx +76 -0
  605. package/src/docs/examples/custom-header.stories.tsx +67 -0
  606. package/src/docs/examples/custom-renderers.stories.tsx +72 -0
  607. package/src/docs/examples/drag-source.stories.tsx +63 -0
  608. package/src/docs/examples/drop-events.stories.tsx +155 -0
  609. package/src/docs/examples/fill-handle.stories.tsx +118 -0
  610. package/src/docs/examples/freeze-columns.stories.tsx +59 -0
  611. package/src/docs/examples/freeze-rows.stories.tsx +71 -0
  612. package/src/docs/examples/get-mouse-args.stories.tsx +61 -0
  613. package/src/docs/examples/header-menus.stories.tsx +162 -0
  614. package/src/docs/examples/highlight-cells.stories.tsx +84 -0
  615. package/src/docs/examples/imperative-scroll.stories.tsx +96 -0
  616. package/src/docs/examples/input-blending.stories.tsx +116 -0
  617. package/src/docs/examples/keybindings.stories.tsx +90 -0
  618. package/src/docs/examples/layout-integration.stories.tsx +48 -0
  619. package/src/docs/examples/multi-level-column-groups.stories.tsx +119 -0
  620. package/src/docs/examples/multi-select-columns.stories.tsx +47 -0
  621. package/src/docs/examples/new-column-button.stories.tsx +56 -0
  622. package/src/docs/examples/obscured-grid.stories.tsx +70 -0
  623. package/src/docs/examples/observe-visible-region.stories.tsx +56 -0
  624. package/src/docs/examples/one-hundred-thousand-columns.stories.tsx +31 -0
  625. package/src/docs/examples/one-million-rows.stories.tsx +37 -0
  626. package/src/docs/examples/overscroll.stories.tsx +74 -0
  627. package/src/docs/examples/padding.stories.tsx +74 -0
  628. package/src/docs/examples/paste-support.stories.tsx +73 -0
  629. package/src/docs/examples/prevent-diagonal-scroll.stories.tsx +47 -0
  630. package/src/docs/examples/rapid-updates.stories.tsx +108 -0
  631. package/src/docs/examples/rearrange-columns.stories.tsx +76 -0
  632. package/src/docs/examples/reorder-rows.stories.tsx +83 -0
  633. package/src/docs/examples/resizable-columns.stories.tsx +67 -0
  634. package/src/docs/examples/right-element.stories.tsx +87 -0
  635. package/src/docs/examples/right-to-left.stories.tsx +73 -0
  636. package/src/docs/examples/row-and-header-sizes.stories.tsx +75 -0
  637. package/src/docs/examples/row-grouping.stories.tsx +142 -0
  638. package/src/docs/examples/row-hover.stories.tsx +67 -0
  639. package/src/docs/examples/row-markers.stories.tsx +74 -0
  640. package/src/docs/examples/row-selections.stories.tsx +84 -0
  641. package/src/docs/examples/scaled-view.stories.tsx +36 -0
  642. package/src/docs/examples/scroll-offset.stories.tsx +49 -0
  643. package/src/docs/examples/scroll-shadows.stories.tsx +83 -0
  644. package/src/docs/examples/search-as-filter.stories.tsx +76 -0
  645. package/src/docs/examples/selection-serialization.stories.tsx +208 -0
  646. package/src/docs/examples/server-side-data.stories.tsx +219 -0
  647. package/src/docs/examples/shadow-dom.stories.tsx +107 -0
  648. package/src/docs/examples/silly-numbers.stories.tsx +40 -0
  649. package/src/docs/examples/small-editable-grid.stories.tsx +39 -0
  650. package/src/docs/examples/smooth-scrolling-grid.stories.tsx +56 -0
  651. package/src/docs/examples/span-cell.stories.tsx +99 -0
  652. package/src/docs/examples/stretch-column-size.stories.tsx +55 -0
  653. package/src/docs/examples/ten-million-cells.stories.tsx +34 -0
  654. package/src/docs/examples/theme-per-column.stories.tsx +88 -0
  655. package/src/docs/examples/theme-per-row.stories.tsx +73 -0
  656. package/src/docs/examples/theme-support.stories.tsx +150 -0
  657. package/src/docs/examples/tooltips.stories.tsx +119 -0
  658. package/src/docs/examples/trailing-row-options.stories.tsx +106 -0
  659. package/src/docs/examples/uneven-rows.stories.tsx +44 -0
  660. package/src/docs/examples/validate-data.stories.tsx +63 -0
  661. package/src/docs/examples/wrapping-text.stories.tsx +97 -0
  662. package/src/docs/template.tsx +69 -0
  663. package/src/index.ts +87 -0
  664. package/src/internal/click-outside-container/click-outside-container.tsx +50 -0
  665. package/src/internal/data-editor-container/data-grid-container.tsx +48 -0
  666. package/src/internal/data-grid/animation-manager.ts +119 -0
  667. package/src/internal/data-grid/cell-set.ts +65 -0
  668. package/src/internal/data-grid/color-parser.ts +141 -0
  669. package/src/internal/data-grid/data-grid-sprites.ts +107 -0
  670. package/src/internal/data-grid/data-grid-types.ts +742 -0
  671. package/src/internal/data-grid/data-grid.stories.tsx +410 -0
  672. package/src/internal/data-grid/data-grid.tsx +1978 -0
  673. package/src/internal/data-grid/event-args.ts +157 -0
  674. package/src/internal/data-grid/image-window-loader-interface.ts +9 -0
  675. package/src/internal/data-grid/render/data-grid-lib.ts +906 -0
  676. package/src/internal/data-grid/render/data-grid-render.blit.ts +291 -0
  677. package/src/internal/data-grid/render/data-grid-render.cells.ts +564 -0
  678. package/src/internal/data-grid/render/data-grid-render.header.ts +842 -0
  679. package/src/internal/data-grid/render/data-grid-render.lines.ts +379 -0
  680. package/src/internal/data-grid/render/data-grid-render.ts +825 -0
  681. package/src/internal/data-grid/render/data-grid-render.walk.ts +243 -0
  682. package/src/internal/data-grid/render/data-grid.render.rings.ts +348 -0
  683. package/src/internal/data-grid/render/draw-checkbox.ts +122 -0
  684. package/src/internal/data-grid/render/draw-edit-hover-indicator.ts +61 -0
  685. package/src/internal/data-grid/render/draw-grid-arg.ts +85 -0
  686. package/src/internal/data-grid/sprites.ts +321 -0
  687. package/src/internal/data-grid/use-animation-queue.ts +41 -0
  688. package/src/internal/data-grid/use-selection-behavior.ts +152 -0
  689. package/src/internal/data-grid-dnd/data-grid-dnd.tsx +450 -0
  690. package/src/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.tsx +83 -0
  691. package/src/internal/data-grid-overlay-editor/data-grid-overlay-editor.tsx +262 -0
  692. package/src/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.tsx +34 -0
  693. package/src/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.tsx +21 -0
  694. package/src/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.tsx +63 -0
  695. package/src/internal/data-grid-overlay-editor/private/image-overlay-editor-style.tsx +56 -0
  696. package/src/internal/data-grid-overlay-editor/private/image-overlay-editor.tsx +51 -0
  697. package/src/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.tsx +80 -0
  698. package/src/internal/data-grid-overlay-editor/private/markdown-overlay-editor.tsx +65 -0
  699. package/src/internal/data-grid-overlay-editor/private/number-overlay-editor-style.tsx +15 -0
  700. package/src/internal/data-grid-overlay-editor/private/number-overlay-editor.tsx +77 -0
  701. package/src/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.tsx +53 -0
  702. package/src/internal/data-grid-overlay-editor/private/uri-overlay-editor.tsx +52 -0
  703. package/src/internal/data-grid-overlay-editor/use-stay-on-screen.ts +61 -0
  704. package/src/internal/data-grid-search/data-grid-search-style.tsx +96 -0
  705. package/src/internal/data-grid-search/data-grid-search.tsx +578 -0
  706. package/src/internal/growing-entry/growing-entry-style.tsx +62 -0
  707. package/src/internal/growing-entry/growing-entry.tsx +74 -0
  708. package/src/internal/markdown-div/markdown-div.tsx +55 -0
  709. package/src/internal/markdown-div/private/markdown-container.tsx +19 -0
  710. package/src/internal/scrolling-data-grid/infinite-scroller.tsx +379 -0
  711. package/src/internal/scrolling-data-grid/scrolling-data-grid.stories.tsx +164 -0
  712. package/src/internal/scrolling-data-grid/scrolling-data-grid.tsx +353 -0
  713. package/src/internal/scrolling-data-grid/use-kinetic-scroll.ts +78 -0
  714. package/src/stories/story-utils.tsx +72 -0
  715. package/test/animation-manager.test.ts +147 -0
  716. package/test/cells.test.tsx +122 -0
  717. package/test/click-outside-container.test.tsx +62 -0
  718. package/test/color-parser.test.ts +68 -0
  719. package/test/common.test.ts +74 -0
  720. package/test/compact-selection.test.ts +221 -0
  721. package/test/copy-paste.test.ts +485 -0
  722. package/test/data-editor-fns.test.ts +153 -0
  723. package/test/data-editor-input.test.tsx +683 -0
  724. package/test/data-editor-resize.test.tsx +271 -0
  725. package/test/data-editor.test.tsx +4945 -0
  726. package/test/data-grid-lib.test.ts +457 -0
  727. package/test/data-grid-overlay.test.tsx +67 -0
  728. package/test/data-grid-types.test.ts +52 -0
  729. package/test/data-grid.test.tsx +399 -0
  730. package/test/image-window-loader.test.ts +211 -0
  731. package/test/math.test.ts +201 -0
  732. package/test/render-state-provider.test.ts +86 -0
  733. package/test/row-grouping-api.test.ts +77 -0
  734. package/test/row-grouping.test.ts +586 -0
  735. package/test/test-utils.tsx +365 -0
  736. package/test/uri-cell.test.ts +124 -0
  737. package/test/use-animation-queue.test.ts +53 -0
  738. package/test/use-autoscroll.test.tsx +108 -0
  739. package/test/use-column-sizer.test.tsx +414 -0
  740. package/test/use-deep-memo.test.ts +41 -0
  741. package/test/use-kinetic-scroll.test.ts +57 -0
  742. package/test/use-rem-adjuster.test.ts +69 -0
  743. package/test/utils.test.ts +127 -0
  744. package/vitest.config.ts +40 -0
  745. package/vitest.setup.ts +13 -0
@@ -0,0 +1,4382 @@
1
+ /* eslint-disable sonarjs/no-duplicate-string */
2
+ import * as React from "react";
3
+ import { assert, assertNever, maybe } from "../common/support.js";
4
+ import clamp from "lodash/clamp.js";
5
+ import uniq from "lodash/uniq.js";
6
+ import flatten from "lodash/flatten.js";
7
+ import range from "lodash/range.js";
8
+ import debounce from "lodash/debounce.js";
9
+ import {
10
+ type EditableGridCell,
11
+ type GridCell,
12
+ GridCellKind,
13
+ type GridSelection,
14
+ isEditableGridCell,
15
+ type Rectangle,
16
+ isReadWriteCell,
17
+ type InnerGridCell,
18
+ InnerGridCellKind,
19
+ CompactSelection,
20
+ type Slice,
21
+ isInnerOnlyCell,
22
+ type ProvideEditorCallback,
23
+ type GridColumn,
24
+ isObjectEditorCallbackResult,
25
+ type Item,
26
+ type MarkerCell,
27
+ type ValidatedGridCell,
28
+ type ImageEditorType,
29
+ type CustomCell,
30
+ BooleanEmpty,
31
+ BooleanIndeterminate,
32
+ type FillHandleDirection,
33
+ type EditListItem,
34
+ type CellActivationBehavior,
35
+ } from "../internal/data-grid/data-grid-types.js";
36
+ import DataGridSearch, { type DataGridSearchProps } from "../internal/data-grid-search/data-grid-search.js";
37
+ import { browserIsOSX } from "../common/browser-detect.js";
38
+ import {
39
+ getDataEditorTheme,
40
+ makeCSSStyle,
41
+ type FullTheme,
42
+ type Theme,
43
+ ThemeContext,
44
+ mergeAndRealizeTheme,
45
+ } from "../common/styles.js";
46
+ import type { DataGridRef } from "../internal/data-grid/data-grid.js";
47
+ import { getScrollBarWidth, useEventListener, whenDefined } from "../common/utils.js";
48
+ import {
49
+ isGroupEqual,
50
+ itemsAreEqual,
51
+ itemIsInRect,
52
+ gridSelectionHasItem,
53
+ getFreezeTrailingHeight,
54
+ } from "../internal/data-grid/render/data-grid-lib.js";
55
+ import { GroupRename } from "./group-rename.js";
56
+ import { measureColumn, useColumnSizer } from "./use-column-sizer.js";
57
+ import { isHotkey } from "../common/is-hotkey.js";
58
+ import { type SelectionBlending, useSelectionBehavior } from "../internal/data-grid/use-selection-behavior.js";
59
+ import { useCellsForSelection } from "./use-cells-for-selection.js";
60
+ import { unquote, expandSelection, copyToClipboard, toggleBoolean } from "./data-editor-fns.js";
61
+ import { DataEditorContainer } from "../internal/data-editor-container/data-grid-container.js";
62
+ import { useAutoscroll } from "./use-autoscroll.js";
63
+ import type { CustomRenderer, CellRenderer, InternalCellRenderer } from "../cells/cell-types.js";
64
+ import { decodeHTML, type CopyBuffer } from "./copy-paste.js";
65
+ import { useRemAdjuster } from "./use-rem-adjuster.js";
66
+ import { withAlpha } from "../internal/data-grid/color-parser.js";
67
+ import { combineRects, getClosestRect, pointInRect } from "../common/math.js";
68
+ import {
69
+ type HeaderClickedEventArgs,
70
+ type GroupHeaderClickedEventArgs,
71
+ type CellClickedEventArgs,
72
+ type FillPatternEventArgs,
73
+ type GridMouseEventArgs,
74
+ groupHeaderKind,
75
+ outOfBoundsKind,
76
+ type GridMouseCellEventArgs,
77
+ headerKind,
78
+ type GridDragEventArgs,
79
+ mouseEventArgsAreEqual,
80
+ type GridKeyEventArgs,
81
+ type CellActivatedEventArgs,
82
+ } from "../internal/data-grid/event-args.js";
83
+ import { type Keybinds, useKeybindingsWithDefaults } from "./data-editor-keybindings.js";
84
+ import type { Highlight } from "../internal/data-grid/render/data-grid-render.cells.js";
85
+ import { useRowGroupingInner, type RowGroupingOptions } from "./row-grouping.js";
86
+ import { useRowGrouping } from "./row-grouping-api.js";
87
+ import { useInitialScrollOffset } from "./use-initial-scroll-offset.js";
88
+ import type { VisibleRegion } from "./visible-region.js";
89
+
90
+ const DataGridOverlayEditor = React.lazy(
91
+ async () => await import("../internal/data-grid-overlay-editor/data-grid-overlay-editor.js")
92
+ );
93
+
94
+ // There must be a better way
95
+ let idCounter = 0;
96
+
97
+ export interface RowMarkerOptions {
98
+ kind: "checkbox" | "number" | "clickable-number" | "checkbox-visible" | "both" | "none";
99
+ checkboxStyle?: "circle" | "square";
100
+ startIndex?: number;
101
+ width?: number;
102
+ theme?: Partial<Theme>;
103
+ headerTheme?: Partial<Theme>;
104
+ headerAlwaysVisible?: boolean;
105
+ headerDisabled?: boolean;
106
+ }
107
+
108
+ interface MouseState {
109
+ readonly previousSelection?: GridSelection;
110
+ readonly fillHandle?: boolean;
111
+ }
112
+
113
+ type Props = Partial<
114
+ Omit<
115
+ DataGridSearchProps,
116
+ | "accessibilityHeight"
117
+ | "canvasRef"
118
+ | "cellXOffset"
119
+ | "cellYOffset"
120
+ | "className"
121
+ | "clientSize"
122
+ | "columns"
123
+ | "disabledRows"
124
+ | "drawFocusRing"
125
+ | "enableGroups"
126
+ | "firstColAccessible"
127
+ | "firstColSticky"
128
+ | "freezeColumns"
129
+ | "hasAppendRow"
130
+ | "getCellContent"
131
+ | "getCellRenderer"
132
+ | "getCellsForSelection"
133
+ | "getRowThemeOverride"
134
+ | "gridRef"
135
+ | "groupHeaderHeight"
136
+ | "headerHeight"
137
+ | "isFilling"
138
+ | "isFocused"
139
+ | "imageWindowLoader"
140
+ | "lockColumns"
141
+ | "maxColumnWidth"
142
+ | "minColumnWidth"
143
+ | "nonGrowWidth"
144
+ | "onCanvasBlur"
145
+ | "onCanvasFocused"
146
+ | "onCellFocused"
147
+ | "onContextMenu"
148
+ | "onDragEnd"
149
+ | "onMouseDown"
150
+ | "onMouseMove"
151
+ | "onMouseUp"
152
+ | "onVisibleRegionChanged"
153
+ | "rowHeight"
154
+ | "rows"
155
+ | "scrollRef"
156
+ | "searchInputRef"
157
+ | "selectedColumns"
158
+ | "selection"
159
+ | "theme"
160
+ | "translateX"
161
+ | "translateY"
162
+ | "verticalBorder"
163
+ >
164
+ >;
165
+
166
+ type EmitEvents = "copy" | "paste" | "delete" | "fill-right" | "fill-down";
167
+
168
+ function getSpanStops(cells: readonly (readonly GridCell[])[]): number[] {
169
+ return uniq(
170
+ flatten(
171
+ flatten(cells)
172
+ .filter(c => c.span !== undefined)
173
+ .map(c => range((c.span?.[0] ?? 0) + 1, (c.span?.[1] ?? 0) + 1))
174
+ )
175
+ );
176
+ }
177
+
178
+ function shiftSelection(input: GridSelection, offset: number): GridSelection {
179
+ if (input === undefined || offset === 0 || (input.columns.length === 0 && input.current === undefined))
180
+ return input;
181
+
182
+ return {
183
+ current:
184
+ input.current === undefined
185
+ ? undefined
186
+ : {
187
+ cell: [input.current.cell[0] + offset, input.current.cell[1]],
188
+ range: {
189
+ ...input.current.range,
190
+ x: input.current.range.x + offset,
191
+ },
192
+ rangeStack: input.current.rangeStack.map(r => ({
193
+ ...r,
194
+ x: r.x + offset,
195
+ })),
196
+ },
197
+ rows: input.rows,
198
+ columns: input.columns.offset(offset),
199
+ };
200
+ }
201
+
202
+ /**
203
+ * @category DataEditor
204
+ */
205
+ export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "imageWindowLoader"> {
206
+ /** Emitted whenever the user has requested the deletion of the selection.
207
+ * @group Editing
208
+ */
209
+ readonly onDelete?: (selection: GridSelection) => boolean | GridSelection;
210
+ /** Emitted whenever a cell edit is completed.
211
+ * @group Editing
212
+ */
213
+ readonly onCellEdited?: (cell: Item, newValue: EditableGridCell) => void;
214
+ /** Emitted whenever a cell mutation is completed and provides all edits inbound as a single batch.
215
+ * @group Editing
216
+ */
217
+ readonly onCellsEdited?: (newValues: readonly EditListItem[]) => boolean | void;
218
+ /** Emitted whenever a row append operation is requested. Append location can be set in callback.
219
+ * @group Editing
220
+ */
221
+ readonly onRowAppended?: () => Promise<"top" | "bottom" | number | undefined> | void;
222
+ /** Emitted whenever a column append operation is requested. Append location can be set in callback.
223
+ * @group Editing
224
+ */
225
+ readonly onColumnAppended?: () => Promise<"left" | "right" | number | undefined> | void;
226
+ /** Emitted when a column header should show a context menu. Usually right click.
227
+ * @group Events
228
+ */
229
+ readonly onHeaderClicked?: (colIndex: number, event: HeaderClickedEventArgs) => void;
230
+ /** Emitted when a group header is clicked.
231
+ * @group Events
232
+ */
233
+ readonly onGroupHeaderClicked?: (colIndex: number, event: GroupHeaderClickedEventArgs) => void;
234
+ /** Emitted whe the user wishes to rename a group.
235
+ * @group Events
236
+ */
237
+ readonly onGroupHeaderRenamed?: (groupName: string, newVal: string) => void;
238
+ /** Emitted when a cell is clicked.
239
+ * @group Events
240
+ */
241
+ readonly onCellClicked?: (cell: Item, event: CellClickedEventArgs) => void;
242
+ /** Emitted when a cell is activated, by pressing Enter, Space or double clicking it.
243
+ * @group Events
244
+ */
245
+ readonly onCellActivated?: (cell: Item, event: CellActivatedEventArgs) => void;
246
+
247
+ /**
248
+ * Emitted whenever the user initiats a pattern fill using the fill handle. This event provides both
249
+ * a patternSource region and a fillDestination region, and can be prevented.
250
+ * @group Editing
251
+ */
252
+ readonly onFillPattern?: (event: FillPatternEventArgs) => void;
253
+ /** Emitted when editing has finished, regardless of data changing or not.
254
+ * @group Editing
255
+ */
256
+ readonly onFinishedEditing?: (newValue: GridCell | undefined, movement: Item) => void;
257
+ /** Emitted when a column header should show a context menu. Usually right click.
258
+ * @group Events
259
+ */
260
+ readonly onHeaderContextMenu?: (colIndex: number, event: HeaderClickedEventArgs) => void;
261
+ /** Emitted when a group header should show a context menu. Usually right click.
262
+ * @group Events
263
+ */
264
+ readonly onGroupHeaderContextMenu?: (colIndex: number, event: GroupHeaderClickedEventArgs) => void;
265
+ /** Emitted when a cell should show a context menu. Usually right click.
266
+ * @group Events
267
+ */
268
+ readonly onCellContextMenu?: (cell: Item, event: CellClickedEventArgs) => void;
269
+ /** Used for validating cell values during editing.
270
+ * @group Editing
271
+ * @param cell The cell which is being validated.
272
+ * @param newValue The new value being proposed.
273
+ * @param prevValue The previous value before the edit.
274
+ * @returns A return of false indicates the value will not be accepted. A value of
275
+ * true indicates the value will be accepted. Returning a new GridCell will immediately coerce the value to match.
276
+ */
277
+ readonly validateCell?: (
278
+ cell: Item,
279
+ newValue: EditableGridCell,
280
+ prevValue: GridCell
281
+ ) => boolean | ValidatedGridCell;
282
+
283
+ /** The columns to display in the data grid.
284
+ * @group Data
285
+ */
286
+ readonly columns: readonly GridColumn[];
287
+
288
+ /** Controls the trailing row used to insert new data into the grid.
289
+ * @group Editing
290
+ */
291
+ readonly trailingRowOptions?: {
292
+ /** If the trailing row should be tinted */
293
+ readonly tint?: boolean;
294
+ /** A hint string displayed on hover. Usually something like "New row" */
295
+ readonly hint?: string;
296
+ /** When set to true, the trailing row is always visible. */
297
+ readonly sticky?: boolean;
298
+ /** The icon to use for the cell. Either a GridColumnIcon or a member of the passed headerIcons */
299
+ readonly addIcon?: string;
300
+ /** Overrides the column to focus when a new row is created. */
301
+ readonly targetColumn?: number | GridColumn;
302
+ };
303
+ /** Controls the height of the header row
304
+ * @defaultValue 36
305
+ * @group Style
306
+ */
307
+ readonly headerHeight?: number;
308
+ /** Controls the header of the group header row
309
+ * @defaultValue `headerHeight`
310
+ * @group Style
311
+ */
312
+ readonly groupHeaderHeight?: number | number[];
313
+
314
+ /**
315
+ * The number of rows in the grid.
316
+ * @group Data
317
+ */
318
+ readonly rows: number;
319
+
320
+ /** Determines if row markers should be automatically added to the grid.
321
+ * Interactive row markers allow the user to select a row.
322
+ *
323
+ * - "clickable-number" renders a number that can be clicked to
324
+ * select the row
325
+ * - "both" causes the row marker to show up as a number but
326
+ * reveal a checkbox when the marker is hovered.
327
+ *
328
+ * @defaultValue `none`
329
+ * @group Style
330
+ */
331
+ readonly rowMarkers?: RowMarkerOptions["kind"] | RowMarkerOptions;
332
+ /**
333
+ * Sets the width of row markers in pixels, if unset row markers will automatically size.
334
+ * @group Style
335
+ * @deprecated Use `rowMarkers` instead.
336
+ */
337
+ readonly rowMarkerWidth?: number;
338
+ /** Changes the starting index for row markers.
339
+ * @defaultValue 1
340
+ * @group Style
341
+ * @deprecated Use `rowMarkers` instead.
342
+ */
343
+ readonly rowMarkerStartIndex?: number;
344
+
345
+ /** Changes the theme of the row marker column
346
+ * @group Style
347
+ * @deprecated Use `rowMarkers` instead.
348
+ */
349
+ readonly rowMarkerTheme?: Partial<Theme>;
350
+
351
+ /** Sets the width of the data grid.
352
+ * @group Style
353
+ */
354
+ readonly width?: number | string;
355
+ /** Sets the height of the data grid.
356
+ * @group Style
357
+ */
358
+ readonly height?: number | string;
359
+ /** Custom classname for data grid wrapper.
360
+ * @group Style
361
+ */
362
+ readonly className?: string;
363
+
364
+ /** If set to `default`, `gridSelection` will be coerced to always include full spans.
365
+ * @group Selection
366
+ * @defaultValue `default`
367
+ */
368
+ readonly spanRangeBehavior?: "default" | "allowPartial";
369
+
370
+ /** Controls which types of selections can exist at the same time in the grid. If selection blending is set to
371
+ * exclusive, the grid will clear other types of selections when the exclusive selection is made. By default row,
372
+ * column, and range selections are exclusive.
373
+ * @group Selection
374
+ * @defaultValue `exclusive`
375
+ * */
376
+ readonly rangeSelectionBlending?: SelectionBlending;
377
+ /** {@inheritDoc rangeSelectionBlending}
378
+ * @group Selection
379
+ */
380
+ readonly columnSelectionBlending?: SelectionBlending;
381
+ /** {@inheritDoc rangeSelectionBlending}
382
+ * @group Selection
383
+ */
384
+ readonly rowSelectionBlending?: SelectionBlending;
385
+ /** Controls if multi-selection is allowed. If disabled, shift/ctrl/command clicking will work as if no modifiers
386
+ * are pressed.
387
+ *
388
+ * When range select is set to cell, only one cell may be selected at a time. When set to rect one one rect at a
389
+ * time. The multi variants allow for multiples of the rect or cell to be selected.
390
+ * @group Selection
391
+ * @defaultValue `rect`
392
+ */
393
+ readonly rangeSelect?: "none" | "cell" | "rect" | "multi-cell" | "multi-rect";
394
+ /** {@inheritDoc rangeSelect}
395
+ * @group Selection
396
+ * @defaultValue `multi`
397
+ */
398
+ readonly columnSelect?: "none" | "single" | "multi";
399
+ /** {@inheritDoc rangeSelect}
400
+ * @group Selection
401
+ * @defaultValue `multi`
402
+ */
403
+ readonly rowSelect?: "none" | "single" | "multi";
404
+
405
+ /** Controls if range selection is allowed to span columns.
406
+ * @group Selection
407
+ * @defaultValue `true`
408
+ */
409
+ readonly rangeSelectionColumnSpanning?: boolean;
410
+
411
+ /** Sets the initial scroll Y offset.
412
+ * @see {@link scrollOffsetX}
413
+ * @group Advanced
414
+ */
415
+ readonly scrollOffsetY?: number;
416
+ /** Sets the initial scroll X offset
417
+ * @see {@link scrollOffsetY}
418
+ * @group Advanced
419
+ */
420
+ readonly scrollOffsetX?: number;
421
+
422
+ /** Determins the height of each row.
423
+ * @group Style
424
+ * @defaultValue 34
425
+ */
426
+ readonly rowHeight?: DataGridSearchProps["rowHeight"];
427
+ /** Fires whenever the mouse moves
428
+ * @group Events
429
+ * @param args
430
+ */
431
+ readonly onMouseMove?: DataGridSearchProps["onMouseMove"];
432
+
433
+ /**
434
+ * The minimum width a column can be resized to.
435
+ * @defaultValue 50
436
+ * @group Style
437
+ */
438
+ readonly minColumnWidth?: DataGridSearchProps["minColumnWidth"];
439
+ /**
440
+ * The maximum width a column can be resized to.
441
+ * @defaultValue 500
442
+ * @group Style
443
+ */
444
+ readonly maxColumnWidth?: DataGridSearchProps["maxColumnWidth"];
445
+ /**
446
+ * The maximum width a column can be automatically sized to.
447
+ * @defaultValue `maxColumnWidth`
448
+ * @group Style
449
+ */
450
+ readonly maxColumnAutoWidth?: number;
451
+
452
+ /**
453
+ * Used to provide an override to the default image editor for the data grid. `provideEditor` may be a better
454
+ * choice for most people.
455
+ * @group Advanced
456
+ * */
457
+ readonly imageEditorOverride?: ImageEditorType;
458
+ /**
459
+ * If specified, it will be used to render Markdown, instead of the default Markdown renderer used by the Grid.
460
+ * You'll want to use this if you need to process your Markdown for security purposes, or if you want to use a
461
+ * renderer with different Markdown features.
462
+ * @group Advanced
463
+ */
464
+ readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
465
+
466
+ /**
467
+ * Allows overriding the theme of any row
468
+ * @param row represents the row index of the row, increasing by 1 for every represented row. Collapsed rows are not included.
469
+ * @param groupRow represents the row index of the group row. Only distinct when row grouping enabled.
470
+ * @param contentRow represents the index of the row excluding group headers. Only distinct when row grouping enabled.
471
+ * @returns
472
+ */
473
+ readonly getRowThemeOverride?: (row: number, groupRow: number, contentRow: number) => Partial<Theme> | undefined;
474
+
475
+ /** Callback for providing a custom editor for a cell.
476
+ * @group Editing
477
+ */
478
+ readonly provideEditor?: ProvideEditorCallback<GridCell>;
479
+ /**
480
+ * Allows coercion of pasted values.
481
+ * @group Editing
482
+ * @param val The pasted value
483
+ * @param cell The cell being pasted into
484
+ * @returns `undefined` to accept default behavior or a `GridCell` which should be used to represent the pasted value.
485
+ */
486
+ readonly coercePasteValue?: (val: string, cell: GridCell) => GridCell | undefined;
487
+
488
+ /**
489
+ * Emitted when the grid selection is cleared.
490
+ * @group Selection
491
+ */
492
+ readonly onSelectionCleared?: () => void;
493
+
494
+ /**
495
+ * The current selection of the data grid. Contains all selected cells, ranges, rows, and columns.
496
+ * Used in conjunction with {@link onGridSelectionChange}
497
+ * method to implement a controlled selection.
498
+ * @group Selection
499
+ */
500
+ readonly gridSelection?: GridSelection;
501
+ /**
502
+ * Emitted whenever the grid selection changes. Specifying
503
+ * this function will make the grid’s selection controlled, so
504
+ * so you will need to specify {@link gridSelection} as well. See
505
+ * the "Controlled Selection" example for details.
506
+ *
507
+ * @param newSelection The new gridSelection as created by user input.
508
+ * @group Selection
509
+ */
510
+ readonly onGridSelectionChange?: (newSelection: GridSelection) => void;
511
+ /**
512
+ * Emitted whenever the visible cells change, usually due to scrolling.
513
+ * @group Events
514
+ * @param range An inclusive range of all visible cells. May include cells obscured by UI elements such
515
+ * as headers.
516
+ * @param tx The x transform of the cell region.
517
+ * @param ty The y transform of the cell region.
518
+ * @param extras Contains information about the selected cell and
519
+ * any visible freeze columns.
520
+ */
521
+ readonly onVisibleRegionChanged?: (
522
+ range: Rectangle,
523
+ tx: number,
524
+ ty: number,
525
+ extras: {
526
+ /** The selected item if visible */
527
+ selected?: Item;
528
+ /** A selection of visible freeze columns
529
+ * @deprecated
530
+ */
531
+ freezeRegion?: Rectangle;
532
+
533
+ /**
534
+ * All visible freeze regions
535
+ */
536
+ freezeRegions?: readonly Rectangle[];
537
+ }
538
+ ) => void;
539
+
540
+ /**
541
+ * The primary callback for getting cell data into the data grid.
542
+ * @group Data
543
+ * @param cell The location of the cell being requested.
544
+ * @returns A valid GridCell to be rendered by the Grid.
545
+ */
546
+ readonly getCellContent: (cell: Item) => GridCell;
547
+
548
+ /**
549
+ * Determines if row selection requires a modifier key to enable multi-selection or not. In auto mode it adapts to
550
+ * touch or mouse environments automatically, in multi-mode it always acts as if the multi key (Ctrl) is pressed.
551
+ * @group Editing
552
+ * @defaultValue `auto`
553
+ */
554
+ readonly rowSelectionMode?: "auto" | "multi";
555
+
556
+ /**
557
+ * Determines if column selection requires a modifier key to enable multi-selection or not. In auto mode it adapts to
558
+ * touch or mouse environments automatically, in multi-mode it always acts as if the multi key (Ctrl) is pressed.
559
+ * @group Editing
560
+ * @defaultValue `auto`
561
+ */
562
+ readonly columnSelectionMode?: "auto" | "multi";
563
+
564
+ /**
565
+ * Add table headers to copied data.
566
+ * @group Editing
567
+ * @defaultValue `false`
568
+ */
569
+ readonly copyHeaders?: boolean;
570
+
571
+ /**
572
+ * Determins which keybindings are enabled.
573
+ * @group Editing
574
+ */
575
+ readonly keybindings?: Partial<Keybinds>;
576
+
577
+ /**
578
+ * Determines if the data editor should immediately begin editing when the user types on a selected cell
579
+ * @group Editing
580
+ */
581
+ readonly editOnType?: boolean;
582
+
583
+ /**
584
+ * Used to fetch large amounts of cells at once. Used for copy/paste, if unset copy will not work.
585
+ *
586
+ * `getCellsForSelection` is called when the user copies a selection to the clipboard or the data editor needs to
587
+ * inspect data which may be outside the curently visible range. It must return a two-dimensional array (an array of
588
+ * rows, where each row is an array of cells) of the cells in the selection's rectangle. Note that the rectangle can
589
+ * include cells that are not currently visible.
590
+ *
591
+ * If `true` is passed instead of a callback, the data grid will internally use the `getCellContent` callback to
592
+ * provide a basic implementation of `getCellsForSelection`. This can make it easier to light up more data grid
593
+ * functionality, but may have negative side effects if your data source is not able to handle being queried for
594
+ * data outside the normal window.
595
+ *
596
+ * If `getCellsForSelection` returns a thunk, the data may be loaded asynchronously, however the data grid may be
597
+ * unable to properly react to column spans when performing range selections. Copying large amounts of data out of
598
+ * the grid will depend on the performance of the thunk as well.
599
+ * @group Data
600
+ * @param {Rectangle} selection The range of requested cells
601
+ * @param {AbortSignal} abortSignal A signal indicating the requested cells are no longer needed
602
+ * @returns A row-major collection of cells or an async thunk which returns a row-major collection.
603
+ */
604
+ readonly getCellsForSelection?: DataGridSearchProps["getCellsForSelection"] | true;
605
+
606
+ /** The number of columns which should remain in place when scrolling horizontally. The row marker column, if
607
+ * enabled is always frozen and is not included in this count.
608
+ * @defaultValue 0
609
+ * @group Style
610
+ */
611
+ readonly freezeColumns?: DataGridSearchProps["freezeColumns"];
612
+
613
+ /**
614
+ * Controls the drawing of the left hand vertical border of a column. If set to a boolean value it controls all
615
+ * borders.
616
+ * @defaultValue `true`
617
+ * @group Style
618
+ */
619
+ readonly verticalBorder?: DataGridSearchProps["verticalBorder"] | boolean;
620
+
621
+ /**
622
+ * Controls the grouping of rows to be drawn in the grid.
623
+ */
624
+ readonly rowGrouping?: RowGroupingOptions;
625
+
626
+ /**
627
+ * Called when data is pasted into the grid. If left undefined, the `DataEditor` will operate in a
628
+ * fallback mode and attempt to paste the text buffer into the current cell assuming the current cell is not
629
+ * readonly and can accept the data type. If `onPaste` is set to false or the function returns false, the grid will
630
+ * simply ignore paste. If `onPaste` evaluates to true the grid will attempt to split the data by tabs and newlines
631
+ * and paste into available cells.
632
+ *
633
+ * The grid will not attempt to add additional rows if more data is pasted then can fit. In that case it is
634
+ * advisable to simply return false from onPaste and handle the paste manually.
635
+ * @group Editing
636
+ */
637
+ readonly onPaste?: ((target: Item, values: readonly (readonly string[])[]) => boolean) | boolean;
638
+
639
+ /**
640
+ * The theme used by the data grid to get all color and font information
641
+ * @group Style
642
+ */
643
+ readonly theme?: Partial<Theme>;
644
+
645
+ readonly renderers?: readonly InternalCellRenderer<InnerGridCell>[];
646
+
647
+ /**
648
+ * An array of custom renderers which can be used to extend the data grid.
649
+ * @group Advanced
650
+ */
651
+ readonly customRenderers?: readonly CustomRenderer<any>[];
652
+
653
+ /**
654
+ * Scales most elements in the theme to match rem scaling automatically
655
+ * @defaultValue false
656
+ */
657
+ readonly scaleToRem?: boolean;
658
+
659
+ /**
660
+ * Custom predicate function to decide whether the click event occurred outside the grid
661
+ * Especially used when custom editor is opened with the portal and is outside the grid, but there is no possibility
662
+ * to add a class "click-outside-ignore"
663
+ * If this function is supplied and returns false, the click event is ignored
664
+ */
665
+ readonly isOutsideClick?: (e: MouseEvent | TouchEvent) => boolean;
666
+
667
+ /**
668
+ * Controls which directions fill is allowed in.
669
+ */
670
+ readonly allowedFillDirections?: FillHandleDirection;
671
+
672
+ /**
673
+ * Determines when a cell is considered activated and will emit the `onCellActivated` event. Generally an activated
674
+ * cell will open to edit mode.
675
+ */
676
+ readonly cellActivationBehavior?: CellActivationBehavior;
677
+
678
+ /**
679
+ * Controls if focus will trap inside the data grid when doing tab and caret navigation.
680
+ */
681
+ readonly trapFocus?: boolean;
682
+
683
+ /**
684
+ * Allows overriding the default amount of bloom (the size growth of the overlay editor)
685
+ */
686
+ readonly editorBloom?: readonly [number, number];
687
+
688
+ /**
689
+ * If set to true, the data grid will attempt to scroll to keep the selction in view
690
+ */
691
+ readonly scrollToActiveCell?: boolean;
692
+
693
+ readonly drawFocusRing?: boolean | "no-editor";
694
+
695
+ /**
696
+ * Allows overriding the default portal element.
697
+ */
698
+ readonly portalElementRef?: React.RefObject<HTMLElement>;
699
+ }
700
+
701
+ type ScrollToFn = (
702
+ col: number | { amount: number; unit: "cell" | "px" },
703
+ row: number | { amount: number; unit: "cell" | "px" },
704
+ dir?: "horizontal" | "vertical" | "both",
705
+ paddingX?: number,
706
+ paddingY?: number,
707
+ options?: {
708
+ hAlign?: "start" | "center" | "end";
709
+ vAlign?: "start" | "center" | "end";
710
+ behavior?: ScrollBehavior;
711
+ }
712
+ ) => void;
713
+
714
+ /** @category DataEditor */
715
+ export interface DataEditorRef {
716
+ /**
717
+ * Programatically appends a row.
718
+ * @param col The column index to focus in the new row.
719
+ * @returns A promise which waits for the append to complete.
720
+ */
721
+ appendRow: (col: number, openOverlay?: boolean, behavior?: ScrollBehavior) => Promise<void>;
722
+ /**
723
+ * Programatically appends a column.
724
+ * @param row The row index to focus in the new column.
725
+ * @returns A promise which waits for the append to complete.
726
+ */
727
+ appendColumn: (row: number, openOverlay?: boolean) => Promise<void>;
728
+ /**
729
+ * Triggers cells to redraw.
730
+ */
731
+ updateCells: DataGridRef["damage"];
732
+ /**
733
+ * Gets the screen space bounds of the requested item.
734
+ */
735
+ getBounds: DataGridRef["getBounds"];
736
+ /**
737
+ * Triggers the data grid to focus itself or the correct accessibility element.
738
+ */
739
+ focus: DataGridRef["focus"];
740
+ /**
741
+ * Generic API for emitting events as if they had been triggered via user interaction.
742
+ */
743
+ emit: (eventName: EmitEvents) => Promise<void>;
744
+ /**
745
+ * Scrolls to the desired cell or location in the grid.
746
+ */
747
+ scrollTo: ScrollToFn;
748
+ /**
749
+ * Causes the columns in the selection to have their natural size recomputed and re-emitted as a resize event.
750
+ */
751
+ remeasureColumns: (cols: CompactSelection) => void;
752
+ /**
753
+ * Gets the mouse args from pointer event position.
754
+ */
755
+ getMouseArgsForPosition: (
756
+ posX: number,
757
+ posY: number,
758
+ ev?: MouseEvent | TouchEvent
759
+ ) => GridMouseEventArgs | undefined;
760
+ }
761
+
762
+ const loadingCell: GridCell = {
763
+ kind: GridCellKind.Loading,
764
+ allowOverlay: false,
765
+ };
766
+
767
+ export const emptyGridSelection: GridSelection = {
768
+ columns: CompactSelection.empty(),
769
+ rows: CompactSelection.empty(),
770
+ current: undefined,
771
+ };
772
+
773
+ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorProps> = (p, forwardedRef) => {
774
+ const [gridSelectionInner, setGridSelectionInner] = React.useState<GridSelection>(emptyGridSelection);
775
+
776
+ const [overlay, setOverlay] = React.useState<{
777
+ target: Rectangle;
778
+ content: GridCell;
779
+ theme: FullTheme;
780
+ initialValue: string | undefined;
781
+ cell: Item;
782
+ highlight: boolean;
783
+ forceEditMode: boolean;
784
+ activation: CellActivatedEventArgs;
785
+ }>();
786
+ const searchInputRef = React.useRef<HTMLInputElement | null>(null);
787
+ const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
788
+ const [mouseState, setMouseState] = React.useState<MouseState>();
789
+ const lastSent = React.useRef<[number, number] | undefined>(undefined);
790
+
791
+ const safeWindow = typeof window === "undefined" ? null : window;
792
+
793
+ const {
794
+ imageEditorOverride,
795
+ getRowThemeOverride: getRowThemeOverrideIn,
796
+ markdownDivCreateNode,
797
+ width,
798
+ height,
799
+ columns: columnsIn,
800
+ rows: rowsIn,
801
+ getCellContent,
802
+ onCellClicked,
803
+ onCellActivated,
804
+ onFillPattern,
805
+ onFinishedEditing,
806
+ coercePasteValue,
807
+ drawHeader: drawHeaderIn,
808
+ drawGroupHeader: drawGroupHeaderIn,
809
+ drawCell: drawCellIn,
810
+ editorBloom,
811
+ onHeaderClicked,
812
+ onColumnProposeMove,
813
+ rangeSelectionColumnSpanning = true,
814
+ spanRangeBehavior = "default",
815
+ onGroupHeaderClicked,
816
+ onCellContextMenu,
817
+ className,
818
+ onHeaderContextMenu,
819
+ getCellsForSelection: getCellsForSelectionIn,
820
+ onGroupHeaderContextMenu,
821
+ onGroupHeaderRenamed,
822
+ onCellEdited,
823
+ onCellsEdited,
824
+ onSearchResultsChanged: onSearchResultsChangedIn,
825
+ searchResults,
826
+ onSearchValueChange,
827
+ searchValue,
828
+ onKeyDown: onKeyDownIn,
829
+ onKeyUp: onKeyUpIn,
830
+ keybindings: keybindingsIn,
831
+ editOnType = true,
832
+ onRowAppended,
833
+ onColumnAppended,
834
+ onColumnMoved,
835
+ validateCell: validateCellIn,
836
+ highlightRegions: highlightRegionsIn,
837
+ rangeSelect = "rect",
838
+ columnSelect = "multi",
839
+ rowSelect = "multi",
840
+ rangeSelectionBlending = "exclusive",
841
+ columnSelectionBlending = "exclusive",
842
+ rowSelectionBlending = "exclusive",
843
+ onDelete: onDeleteIn,
844
+ onDragStart,
845
+ onMouseMove,
846
+ onPaste,
847
+ copyHeaders = false,
848
+ freezeColumns = 0,
849
+ cellActivationBehavior = "second-click",
850
+ rowSelectionMode = "auto",
851
+ columnSelectionMode = "auto",
852
+ onHeaderMenuClick,
853
+ onHeaderIndicatorClick,
854
+ getGroupDetails,
855
+ rowGrouping,
856
+ onSearchClose: onSearchCloseIn,
857
+ onItemHovered,
858
+ onSelectionCleared,
859
+ showSearch: showSearchIn,
860
+ onVisibleRegionChanged,
861
+ gridSelection: gridSelectionOuter,
862
+ onGridSelectionChange,
863
+ minColumnWidth: minColumnWidthIn = 50,
864
+ maxColumnWidth: maxColumnWidthIn = 500,
865
+ maxColumnAutoWidth: maxColumnAutoWidthIn,
866
+ provideEditor,
867
+ trailingRowOptions,
868
+ freezeTrailingRows = 0,
869
+ allowedFillDirections = "orthogonal",
870
+ scrollOffsetX,
871
+ scrollOffsetY,
872
+ verticalBorder,
873
+ onDragOverCell,
874
+ onDrop,
875
+ onColumnResize: onColumnResizeIn,
876
+ onColumnResizeEnd: onColumnResizeEndIn,
877
+ onColumnResizeStart: onColumnResizeStartIn,
878
+ customRenderers: additionalRenderers,
879
+ fillHandle,
880
+ experimental,
881
+ fixedShadowX,
882
+ fixedShadowY,
883
+ headerIcons,
884
+ imageWindowLoader,
885
+ initialSize,
886
+ isDraggable,
887
+ onDragLeave,
888
+ onRowMoved,
889
+ overscrollX: overscrollXIn,
890
+ overscrollY: overscrollYIn,
891
+ preventDiagonalScrolling,
892
+ rightElement,
893
+ rightElementProps,
894
+ trapFocus = false,
895
+ smoothScrollX,
896
+ smoothScrollY,
897
+ scaleToRem = false,
898
+ rowHeight: rowHeightIn = 34,
899
+ headerHeight: headerHeightIn = 36,
900
+ groupHeaderHeight: groupHeaderHeightIn = headerHeightIn,
901
+ theme: themeIn,
902
+ isOutsideClick,
903
+ renderers,
904
+ resizeIndicator,
905
+ scrollToActiveCell = true,
906
+ drawFocusRing: drawFocusRingIn = true,
907
+ portalElementRef,
908
+ } = p;
909
+
910
+ const drawFocusRing = drawFocusRingIn === "no-editor" ? overlay === undefined : drawFocusRingIn;
911
+
912
+ const rowMarkersObj = typeof p.rowMarkers === "string" ? undefined : p.rowMarkers;
913
+
914
+ const rowMarkers = rowMarkersObj?.kind ?? (p.rowMarkers as RowMarkerOptions["kind"]) ?? "none";
915
+ const rowMarkerWidthRaw = rowMarkersObj?.width ?? p.rowMarkerWidth;
916
+ const rowMarkerStartIndex = rowMarkersObj?.startIndex ?? p.rowMarkerStartIndex ?? 1;
917
+ const rowMarkerTheme = rowMarkersObj?.theme ?? p.rowMarkerTheme;
918
+ const headerRowMarkerTheme = rowMarkersObj?.headerTheme;
919
+ const headerRowMarkerAlwaysVisible = rowMarkersObj?.headerAlwaysVisible;
920
+ const headerRowMarkerDisabled = rowSelect !== "multi" || rowMarkersObj?.headerDisabled === true;
921
+ const rowMarkerCheckboxStyle = rowMarkersObj?.checkboxStyle ?? "square";
922
+
923
+ const minColumnWidth = Math.max(minColumnWidthIn, 20);
924
+ const maxColumnWidth = Math.max(maxColumnWidthIn, minColumnWidth);
925
+ const maxColumnAutoWidth = Math.max(maxColumnAutoWidthIn ?? maxColumnWidth, minColumnWidth);
926
+
927
+ const docStyle = React.useMemo(() => {
928
+ if (typeof window === "undefined") return { fontSize: "16px" };
929
+ return window.getComputedStyle(document.documentElement);
930
+ }, []);
931
+
932
+ const {
933
+ rows,
934
+ rowNumberMapper,
935
+ rowHeight: rowHeightPostGrouping,
936
+ getRowThemeOverride,
937
+ } = useRowGroupingInner(rowGrouping, rowsIn, rowHeightIn, getRowThemeOverrideIn);
938
+
939
+ const remSize = React.useMemo(() => Number.parseFloat(docStyle.fontSize), [docStyle]);
940
+ const { rowHeight, headerHeight, groupHeaderHeight, theme, overscrollX, overscrollY } = useRemAdjuster({
941
+ groupHeaderHeight: groupHeaderHeightIn,
942
+ headerHeight: headerHeightIn,
943
+ overscrollX: overscrollXIn,
944
+ overscrollY: overscrollYIn,
945
+ remSize,
946
+ rowHeight: rowHeightPostGrouping,
947
+ scaleToRem,
948
+ theme: themeIn,
949
+ });
950
+
951
+ const keybindings = useKeybindingsWithDefaults(keybindingsIn);
952
+
953
+ const rowMarkerWidth = rowMarkerWidthRaw ?? (rowsIn > 10_000 ? 48 : rowsIn > 1000 ? 44 : rowsIn > 100 ? 36 : 32);
954
+ const hasRowMarkers = rowMarkers !== "none";
955
+ const rowMarkerOffset = hasRowMarkers ? 1 : 0;
956
+ const showTrailingBlankRow = trailingRowOptions !== undefined;
957
+ const lastRowSticky = trailingRowOptions?.sticky === true;
958
+
959
+ const [showSearchInner, setShowSearchInner] = React.useState(false);
960
+ const showSearch = showSearchIn ?? showSearchInner;
961
+
962
+ const onSearchClose = React.useCallback(() => {
963
+ if (onSearchCloseIn !== undefined) {
964
+ onSearchCloseIn();
965
+ } else {
966
+ setShowSearchInner(false);
967
+ }
968
+ }, [onSearchCloseIn]);
969
+
970
+ const gridSelectionOuterMangled: GridSelection | undefined = React.useMemo((): GridSelection | undefined => {
971
+ return gridSelectionOuter === undefined ? undefined : shiftSelection(gridSelectionOuter, rowMarkerOffset);
972
+ }, [gridSelectionOuter, rowMarkerOffset]);
973
+ const gridSelection = gridSelectionOuterMangled ?? gridSelectionInner;
974
+
975
+ const abortControllerRef = React.useRef<AbortController | undefined>(undefined) as React.MutableRefObject<AbortController>;
976
+ if (abortControllerRef.current === undefined) abortControllerRef.current = new AbortController();
977
+
978
+ React.useEffect(() => () => abortControllerRef?.current.abort(), []);
979
+
980
+ const [getCellsForSelection, getCellsForSeletionDirect] = useCellsForSelection(
981
+ getCellsForSelectionIn,
982
+ getCellContent,
983
+ rowMarkerOffset,
984
+ abortControllerRef.current,
985
+ rows
986
+ );
987
+
988
+ const validateCell = React.useCallback<NonNullable<typeof validateCellIn>>(
989
+ (cell, newValue, prevValue) => {
990
+ if (validateCellIn === undefined) return true;
991
+ const item: Item = [cell[0] - rowMarkerOffset, cell[1]];
992
+ return validateCellIn?.(item, newValue, prevValue);
993
+ },
994
+ [rowMarkerOffset, validateCellIn]
995
+ );
996
+
997
+ const expectedExternalGridSelection = React.useRef<GridSelection | undefined>(gridSelectionOuter);
998
+ const setGridSelection = React.useCallback(
999
+ (newVal: GridSelection, expand: boolean): void => {
1000
+ if (expand) {
1001
+ newVal = expandSelection(
1002
+ newVal,
1003
+ getCellsForSelection,
1004
+ rowMarkerOffset,
1005
+ spanRangeBehavior,
1006
+ abortControllerRef.current
1007
+ );
1008
+ }
1009
+ if (onGridSelectionChange !== undefined) {
1010
+ expectedExternalGridSelection.current = shiftSelection(newVal, -rowMarkerOffset);
1011
+ onGridSelectionChange(expectedExternalGridSelection.current);
1012
+ } else {
1013
+ setGridSelectionInner(newVal);
1014
+ }
1015
+ },
1016
+ [onGridSelectionChange, getCellsForSelection, rowMarkerOffset, spanRangeBehavior]
1017
+ );
1018
+
1019
+ const onColumnResize = whenDefined(
1020
+ onColumnResizeIn,
1021
+ React.useCallback<NonNullable<typeof onColumnResizeIn>>(
1022
+ (_, w, ind, wg) => {
1023
+ onColumnResizeIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
1024
+ },
1025
+ [onColumnResizeIn, rowMarkerOffset, columnsIn]
1026
+ )
1027
+ );
1028
+
1029
+ const onColumnResizeEnd = whenDefined(
1030
+ onColumnResizeEndIn,
1031
+ React.useCallback<NonNullable<typeof onColumnResizeEndIn>>(
1032
+ (_, w, ind, wg) => {
1033
+ onColumnResizeEndIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
1034
+ },
1035
+ [onColumnResizeEndIn, rowMarkerOffset, columnsIn]
1036
+ )
1037
+ );
1038
+
1039
+ const onColumnResizeStart = whenDefined(
1040
+ onColumnResizeStartIn,
1041
+ React.useCallback<NonNullable<typeof onColumnResizeStartIn>>(
1042
+ (_, w, ind, wg) => {
1043
+ onColumnResizeStartIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
1044
+ },
1045
+ [onColumnResizeStartIn, rowMarkerOffset, columnsIn]
1046
+ )
1047
+ );
1048
+
1049
+ const drawHeader = whenDefined(
1050
+ drawHeaderIn,
1051
+ React.useCallback<NonNullable<typeof drawHeaderIn>>(
1052
+ (args, draw) => {
1053
+ return drawHeaderIn?.({ ...args, columnIndex: args.columnIndex - rowMarkerOffset }, draw) ?? false;
1054
+ },
1055
+ [drawHeaderIn, rowMarkerOffset]
1056
+ )
1057
+ );
1058
+
1059
+ const drawCell = whenDefined(
1060
+ drawCellIn,
1061
+ React.useCallback<NonNullable<typeof drawCellIn>>(
1062
+ (args, draw) => {
1063
+ return drawCellIn?.({ ...args, col: args.col - rowMarkerOffset }, draw) ?? false;
1064
+ },
1065
+ [drawCellIn, rowMarkerOffset]
1066
+ )
1067
+ );
1068
+
1069
+ const onDelete = React.useCallback<NonNullable<DataEditorProps["onDelete"]>>(
1070
+ sel => {
1071
+ if (onDeleteIn !== undefined) {
1072
+ const result = onDeleteIn(shiftSelection(sel, -rowMarkerOffset));
1073
+ if (typeof result === "boolean") {
1074
+ return result;
1075
+ }
1076
+ return shiftSelection(result, rowMarkerOffset);
1077
+ }
1078
+ return true;
1079
+ },
1080
+ [onDeleteIn, rowMarkerOffset]
1081
+ );
1082
+
1083
+ const [setCurrent, setSelectedRows, setSelectedColumns] = useSelectionBehavior(
1084
+ gridSelection,
1085
+ setGridSelection,
1086
+ rangeSelectionBlending,
1087
+ columnSelectionBlending,
1088
+ rowSelectionBlending,
1089
+ rangeSelect,
1090
+ rangeSelectionColumnSpanning
1091
+ );
1092
+
1093
+ const mergedTheme = React.useMemo(() => {
1094
+ return mergeAndRealizeTheme(getDataEditorTheme(), theme);
1095
+ }, [theme]);
1096
+
1097
+ const [clientSize, setClientSize] = React.useState<readonly [number, number, number]>([0, 0, 0]);
1098
+
1099
+ const rendererMap = React.useMemo(() => {
1100
+ if (renderers === undefined) return {};
1101
+ const result: Partial<Record<InnerGridCellKind | GridCellKind, InternalCellRenderer<InnerGridCell>>> = {};
1102
+ for (const r of renderers) {
1103
+ result[r.kind] = r;
1104
+ }
1105
+ return result;
1106
+ }, [renderers]);
1107
+
1108
+ const getCellRenderer: <T extends InnerGridCell>(cell: T) => CellRenderer<T> | undefined = React.useCallback(
1109
+ <T extends InnerGridCell>(cell: T) => {
1110
+ if (cell.kind !== GridCellKind.Custom) {
1111
+ return rendererMap[cell.kind] as unknown as CellRenderer<T>;
1112
+ }
1113
+ return additionalRenderers?.find(x => x.isMatch(cell)) as CellRenderer<T>;
1114
+ },
1115
+ [additionalRenderers, rendererMap]
1116
+ );
1117
+
1118
+ // eslint-disable-next-line prefer-const
1119
+ let { sizedColumns: columns, nonGrowWidth } = useColumnSizer(
1120
+ columnsIn,
1121
+ rows,
1122
+ getCellsForSeletionDirect,
1123
+ clientSize[0] - (rowMarkerOffset === 0 ? 0 : rowMarkerWidth) - clientSize[2],
1124
+ minColumnWidth,
1125
+ maxColumnAutoWidth,
1126
+ mergedTheme,
1127
+ getCellRenderer,
1128
+ abortControllerRef.current
1129
+ );
1130
+ if (rowMarkers !== "none") nonGrowWidth += rowMarkerWidth;
1131
+
1132
+ const enableGroups = React.useMemo(() => {
1133
+ return columns.some(c => c.group !== undefined);
1134
+ }, [columns]);
1135
+
1136
+ const groupHeights = enableGroups
1137
+ ? Array.isArray(groupHeaderHeight)
1138
+ ? groupHeaderHeight.reduce((sum, h) => sum + h, 0)
1139
+ : groupHeaderHeight
1140
+ : 0;
1141
+ const totalHeaderHeight = headerHeight + groupHeights;
1142
+
1143
+ const numSelectedRows = gridSelection.rows.length;
1144
+ const rowMarkerChecked =
1145
+ rowMarkers === "none" ? undefined : numSelectedRows === 0 ? false : numSelectedRows === rows ? true : undefined;
1146
+
1147
+ const mangledCols = React.useMemo(() => {
1148
+ if (rowMarkers === "none") return columns;
1149
+ return [
1150
+ {
1151
+ title: "",
1152
+ width: rowMarkerWidth,
1153
+ icon: undefined,
1154
+ hasMenu: false,
1155
+ style: "normal" as const,
1156
+ themeOverride: rowMarkerTheme,
1157
+ rowMarker: rowMarkerCheckboxStyle,
1158
+ rowMarkerChecked,
1159
+ headerRowMarkerTheme,
1160
+ headerRowMarkerAlwaysVisible,
1161
+ headerRowMarkerDisabled,
1162
+ },
1163
+ ...columns,
1164
+ ];
1165
+ }, [
1166
+ rowMarkers,
1167
+ columns,
1168
+ rowMarkerWidth,
1169
+ rowMarkerTheme,
1170
+ rowMarkerCheckboxStyle,
1171
+ rowMarkerChecked,
1172
+ headerRowMarkerTheme,
1173
+ headerRowMarkerAlwaysVisible,
1174
+ headerRowMarkerDisabled,
1175
+ ]);
1176
+
1177
+ const visibleRegionRef = React.useRef<VisibleRegion>({
1178
+ height: 1,
1179
+ width: 1,
1180
+ x: 0,
1181
+ y: 0,
1182
+ });
1183
+
1184
+ const hasJustScrolled = React.useRef(false);
1185
+
1186
+ const { setVisibleRegion, visibleRegion, scrollRef } = useInitialScrollOffset(
1187
+ scrollOffsetX,
1188
+ scrollOffsetY,
1189
+ rowHeight,
1190
+ visibleRegionRef,
1191
+ () => (hasJustScrolled.current = true)
1192
+ );
1193
+
1194
+ visibleRegionRef.current = visibleRegion;
1195
+
1196
+ const cellXOffset = visibleRegion.x + rowMarkerOffset;
1197
+ const cellYOffset = visibleRegion.y;
1198
+
1199
+ const gridRef = React.useRef<DataGridRef | null>(null);
1200
+
1201
+ const focus = React.useCallback((immediate?: boolean) => {
1202
+ if (immediate === true) {
1203
+ gridRef.current?.focus();
1204
+ } else {
1205
+ window.requestAnimationFrame(() => {
1206
+ gridRef.current?.focus();
1207
+ });
1208
+ }
1209
+ }, []);
1210
+
1211
+ const mangledRows = showTrailingBlankRow ? rows + 1 : rows;
1212
+
1213
+ const mangledOnCellsEdited = React.useCallback<NonNullable<typeof onCellsEdited>>(
1214
+ (items: readonly EditListItem[]) => {
1215
+ const mangledItems =
1216
+ rowMarkerOffset === 0
1217
+ ? items
1218
+ : items.map(x => ({
1219
+ ...x,
1220
+ location: [x.location[0] - rowMarkerOffset, x.location[1]] as const,
1221
+ }));
1222
+ const r = onCellsEdited?.(mangledItems);
1223
+
1224
+ if (r !== true) {
1225
+ for (const i of mangledItems) onCellEdited?.(i.location, i.value);
1226
+ }
1227
+
1228
+ return r;
1229
+ },
1230
+ [onCellEdited, onCellsEdited, rowMarkerOffset]
1231
+ );
1232
+
1233
+ const [fillHighlightRegion, setFillHighlightRegion] = React.useState<Rectangle | undefined>();
1234
+
1235
+ // this will generally be undefined triggering the memo less often
1236
+ const highlightRange =
1237
+ gridSelection.current !== undefined &&
1238
+ gridSelection.current.range.width * gridSelection.current.range.height > 1
1239
+ ? gridSelection.current.range
1240
+ : undefined;
1241
+
1242
+ const highlightFocus = drawFocusRing ? gridSelection.current?.cell : undefined;
1243
+ const highlightFocusCol = highlightFocus?.[0];
1244
+ const highlightFocusRow = highlightFocus?.[1];
1245
+
1246
+ const highlightRegions = React.useMemo(() => {
1247
+ if (
1248
+ (highlightRegionsIn === undefined || highlightRegionsIn.length === 0) &&
1249
+ (highlightRange ?? highlightFocusCol ?? highlightFocusRow ?? fillHighlightRegion) === undefined
1250
+ )
1251
+ return undefined;
1252
+
1253
+ const regions: Highlight[] = [];
1254
+
1255
+ if (highlightRegionsIn !== undefined) {
1256
+ for (const r of highlightRegionsIn) {
1257
+ const maxWidth = mangledCols.length - r.range.x - rowMarkerOffset;
1258
+ if (maxWidth > 0) {
1259
+ regions.push({
1260
+ color: r.color,
1261
+ range: {
1262
+ ...r.range,
1263
+ x: r.range.x + rowMarkerOffset,
1264
+ width: Math.min(maxWidth, r.range.width),
1265
+ },
1266
+ style: r.style,
1267
+ });
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ if (fillHighlightRegion !== undefined) {
1273
+ regions.push({
1274
+ color: withAlpha(mergedTheme.accentColor, 0),
1275
+ range: fillHighlightRegion,
1276
+ style: "dashed",
1277
+ });
1278
+ }
1279
+
1280
+ if (highlightRange !== undefined) {
1281
+ regions.push({
1282
+ color: withAlpha(mergedTheme.accentColor, 0.5),
1283
+ range: highlightRange,
1284
+ style: "solid-outline",
1285
+ });
1286
+ }
1287
+
1288
+ if (highlightFocusCol !== undefined && highlightFocusRow !== undefined) {
1289
+ regions.push({
1290
+ color: mergedTheme.accentColor,
1291
+ range: {
1292
+ x: highlightFocusCol,
1293
+ y: highlightFocusRow,
1294
+ width: 1,
1295
+ height: 1,
1296
+ },
1297
+ style: "solid-outline",
1298
+ });
1299
+ }
1300
+
1301
+ return regions.length > 0 ? regions : undefined;
1302
+ }, [
1303
+ fillHighlightRegion,
1304
+ highlightRange,
1305
+ highlightFocusCol,
1306
+ highlightFocusRow,
1307
+ highlightRegionsIn,
1308
+ mangledCols.length,
1309
+ mergedTheme.accentColor,
1310
+ rowMarkerOffset,
1311
+ ]);
1312
+
1313
+ const mangledColsRef = React.useRef(mangledCols);
1314
+ mangledColsRef.current = mangledCols;
1315
+ const getMangledCellContent = React.useCallback(
1316
+ ([col, row]: Item, forceStrict: boolean = false): InnerGridCell => {
1317
+ const isTrailing = showTrailingBlankRow && row === mangledRows - 1;
1318
+ const isRowMarkerCol = col === 0 && hasRowMarkers;
1319
+ if (isRowMarkerCol) {
1320
+ if (isTrailing) {
1321
+ return loadingCell;
1322
+ }
1323
+ const mappedRow = rowNumberMapper(row);
1324
+ if (mappedRow === undefined) return loadingCell;
1325
+ return {
1326
+ kind: InnerGridCellKind.Marker,
1327
+ allowOverlay: false,
1328
+ checkboxStyle: rowMarkerCheckboxStyle,
1329
+ checked: gridSelection?.rows.hasIndex(row) === true,
1330
+ markerKind: rowMarkers === "clickable-number" ? "number" : rowMarkers,
1331
+ row: rowMarkerStartIndex + mappedRow,
1332
+ drawHandle: onRowMoved !== undefined,
1333
+ cursor: rowMarkers === "clickable-number" ? "pointer" : undefined,
1334
+ };
1335
+ } else if (isTrailing) {
1336
+ //If the grid is empty, we will return text
1337
+ const isFirst = col === rowMarkerOffset;
1338
+
1339
+ const maybeFirstColumnHint = isFirst ? (trailingRowOptions?.hint ?? "") : "";
1340
+ const c = mangledColsRef.current[col];
1341
+
1342
+ if (c?.trailingRowOptions?.disabled === true) {
1343
+ return loadingCell;
1344
+ } else {
1345
+ const hint = c?.trailingRowOptions?.hint ?? maybeFirstColumnHint;
1346
+ const icon = c?.trailingRowOptions?.addIcon ?? trailingRowOptions?.addIcon;
1347
+ return {
1348
+ kind: InnerGridCellKind.NewRow,
1349
+ hint,
1350
+ allowOverlay: false,
1351
+ icon,
1352
+ };
1353
+ }
1354
+ } else {
1355
+ const outerCol = col - rowMarkerOffset;
1356
+ if (forceStrict || experimental?.strict === true) {
1357
+ const vr = visibleRegionRef.current;
1358
+ const isOutsideMainArea =
1359
+ vr.x > outerCol ||
1360
+ outerCol > vr.x + vr.width ||
1361
+ vr.y > row ||
1362
+ row > vr.y + vr.height ||
1363
+ row >= rowsRef.current;
1364
+ const isSelected = outerCol === vr.extras?.selected?.[0] && row === vr.extras?.selected[1];
1365
+ let isInFreezeArea = false;
1366
+ if (vr.extras?.freezeRegions !== undefined) {
1367
+ for (const fr of vr.extras.freezeRegions) {
1368
+ if (pointInRect(fr, outerCol, row)) {
1369
+ isInFreezeArea = true;
1370
+ break;
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+ if (isOutsideMainArea && !isSelected && !isInFreezeArea) {
1376
+ return loadingCell;
1377
+ }
1378
+ }
1379
+ let result = getCellContent([outerCol, row]);
1380
+ if (rowMarkerOffset !== 0 && result.span !== undefined) {
1381
+ result = {
1382
+ ...result,
1383
+ span: [result.span[0] + rowMarkerOffset, result.span[1] + rowMarkerOffset],
1384
+ };
1385
+ }
1386
+ return result;
1387
+ }
1388
+ },
1389
+ [
1390
+ showTrailingBlankRow,
1391
+ mangledRows,
1392
+ hasRowMarkers,
1393
+ rowNumberMapper,
1394
+ rowMarkerCheckboxStyle,
1395
+ gridSelection?.rows,
1396
+ rowMarkers,
1397
+ rowMarkerStartIndex,
1398
+ onRowMoved,
1399
+ rowMarkerOffset,
1400
+ trailingRowOptions?.hint,
1401
+ trailingRowOptions?.addIcon,
1402
+ experimental?.strict,
1403
+ getCellContent,
1404
+ ]
1405
+ );
1406
+
1407
+ const mangledGetGroupDetails = React.useCallback<NonNullable<DataEditorProps["getGroupDetails"]>>(
1408
+ group => {
1409
+ let result = getGroupDetails?.(group) ?? { name: group };
1410
+ if (onGroupHeaderRenamed !== undefined && group !== "") {
1411
+ result = {
1412
+ icon: result.icon,
1413
+ name: result.name,
1414
+ overrideTheme: result.overrideTheme,
1415
+ actions: [
1416
+ ...(result.actions ?? []),
1417
+ {
1418
+ title: "Rename",
1419
+ icon: "renameIcon",
1420
+ onClick: e =>
1421
+ setRenameGroup({
1422
+ group: result.name,
1423
+ bounds: e.bounds,
1424
+ }),
1425
+ },
1426
+ ],
1427
+ };
1428
+ }
1429
+ return result;
1430
+ },
1431
+ [getGroupDetails, onGroupHeaderRenamed]
1432
+ );
1433
+
1434
+ const setOverlaySimple = React.useCallback(
1435
+ (val: Omit<NonNullable<typeof overlay>, "theme">) => {
1436
+ const [col, row] = val.cell;
1437
+ const column = mangledCols[col];
1438
+ const groupName =
1439
+ column?.group !== undefined
1440
+ ? Array.isArray(column.group)
1441
+ ? (column.group[0] ?? "")
1442
+ : column.group
1443
+ : "";
1444
+ const groupTheme = groupName !== "" ? mangledGetGroupDetails(groupName)?.overrideTheme : undefined;
1445
+ const colTheme = column?.themeOverride;
1446
+ const rowTheme = getRowThemeOverride?.(row);
1447
+
1448
+ setOverlay({
1449
+ ...val,
1450
+ theme: mergeAndRealizeTheme(mergedTheme, groupTheme, colTheme, rowTheme, val.content.themeOverride),
1451
+ });
1452
+ },
1453
+ [getRowThemeOverride, mangledCols, mangledGetGroupDetails, mergedTheme]
1454
+ );
1455
+
1456
+ const reselect = React.useCallback(
1457
+ (bounds: Rectangle, activation: CellActivatedEventArgs, initialValue?: string) => {
1458
+ if (gridSelection.current === undefined) return;
1459
+
1460
+ const [col, row] = gridSelection.current.cell;
1461
+ const c = getMangledCellContent([col, row]);
1462
+ if (c.kind !== GridCellKind.Boolean && c.allowOverlay) {
1463
+ let content = c;
1464
+ if (initialValue !== undefined) {
1465
+ switch (content.kind) {
1466
+ case GridCellKind.Number: {
1467
+ const d = maybe(() => (initialValue === "-" ? -0 : Number.parseFloat(initialValue)), 0);
1468
+ content = {
1469
+ ...content,
1470
+ data: Number.isNaN(d) ? 0 : d,
1471
+ };
1472
+ break;
1473
+ }
1474
+ case GridCellKind.Text:
1475
+ case GridCellKind.Markdown:
1476
+ case GridCellKind.Uri:
1477
+ content = {
1478
+ ...content,
1479
+ data: initialValue,
1480
+ };
1481
+ break;
1482
+ }
1483
+ }
1484
+
1485
+ setOverlaySimple({
1486
+ target: bounds,
1487
+ content,
1488
+ initialValue,
1489
+ cell: [col, row],
1490
+ highlight: initialValue === undefined,
1491
+ forceEditMode: initialValue !== undefined,
1492
+ activation,
1493
+ });
1494
+ } else if (c.kind === GridCellKind.Boolean && activation.inputType === "keyboard" && c.readonly !== true) {
1495
+ mangledOnCellsEdited([
1496
+ {
1497
+ location: gridSelection.current.cell,
1498
+ value: {
1499
+ ...c,
1500
+ data: toggleBoolean(c.data),
1501
+ },
1502
+ },
1503
+ ]);
1504
+ gridRef.current?.damage([{ cell: gridSelection.current.cell }]);
1505
+ }
1506
+ },
1507
+ [getMangledCellContent, gridSelection, mangledOnCellsEdited, setOverlaySimple]
1508
+ );
1509
+
1510
+ const focusOnRowFromTrailingBlankRow = React.useCallback(
1511
+ (col: number, row: number) => {
1512
+ const bounds = gridRef.current?.getBounds(col, row);
1513
+ if (bounds === undefined || scrollRef.current === null) {
1514
+ return;
1515
+ }
1516
+
1517
+ const content = getMangledCellContent([col, row]);
1518
+ if (!content.allowOverlay) {
1519
+ return;
1520
+ }
1521
+
1522
+ setOverlaySimple({
1523
+ target: bounds,
1524
+ content,
1525
+ initialValue: undefined,
1526
+ highlight: true,
1527
+ cell: [col, row],
1528
+ forceEditMode: true,
1529
+ activation: { inputType: "keyboard", key: "Enter" },
1530
+ });
1531
+ },
1532
+ [getMangledCellContent, scrollRef, setOverlaySimple]
1533
+ );
1534
+
1535
+ const scrollTo = React.useCallback<ScrollToFn>(
1536
+ (col, row, dir = "both", paddingX = 0, paddingY = 0, options = undefined): void => {
1537
+ if (scrollRef.current !== null) {
1538
+ const grid = gridRef.current;
1539
+ const canvas = canvasRef.current;
1540
+
1541
+ const trueCol = typeof col !== "number" ? (col.unit === "cell" ? col.amount : undefined) : col;
1542
+ const trueRow = typeof row !== "number" ? (row.unit === "cell" ? row.amount : undefined) : row;
1543
+ const desiredX = typeof col !== "number" && col.unit === "px" ? col.amount : undefined;
1544
+ const desiredY = typeof row !== "number" && row.unit === "px" ? row.amount : undefined;
1545
+ if (grid !== null && canvas !== null) {
1546
+ let targetRect: Rectangle = {
1547
+ x: 0,
1548
+ y: 0,
1549
+ width: 0,
1550
+ height: 0,
1551
+ };
1552
+
1553
+ let scrollX = 0;
1554
+ let scrollY = 0;
1555
+
1556
+ if (trueCol !== undefined || trueRow !== undefined) {
1557
+ targetRect = grid.getBounds((trueCol ?? 0) + rowMarkerOffset, trueRow ?? 0) ?? targetRect;
1558
+ if (targetRect.width === 0 || targetRect.height === 0) return;
1559
+ }
1560
+
1561
+ const scrollBounds = canvas.getBoundingClientRect();
1562
+ const scale = scrollBounds.width / canvas.offsetWidth;
1563
+
1564
+ if (desiredX !== undefined) {
1565
+ targetRect = {
1566
+ ...targetRect,
1567
+ x: desiredX - scrollBounds.left - scrollRef.current.scrollLeft,
1568
+ width: 1,
1569
+ };
1570
+ }
1571
+ if (desiredY !== undefined) {
1572
+ targetRect = {
1573
+ ...targetRect,
1574
+ y: desiredY + scrollBounds.top - scrollRef.current.scrollTop,
1575
+ height: 1,
1576
+ };
1577
+ }
1578
+
1579
+ if (targetRect !== undefined) {
1580
+ const bounds = {
1581
+ x: targetRect.x - paddingX,
1582
+ y: targetRect.y - paddingY,
1583
+ width: targetRect.width + 2 * paddingX,
1584
+ height: targetRect.height + 2 * paddingY,
1585
+ };
1586
+
1587
+ let frozenWidth = 0;
1588
+ for (let i = 0; i < freezeColumns; i++) {
1589
+ frozenWidth += columns[i].width;
1590
+ }
1591
+ let trailingRowHeight = 0;
1592
+ const freezeTrailingRowsEffective = freezeTrailingRows + (lastRowSticky ? 1 : 0);
1593
+ if (freezeTrailingRowsEffective > 0) {
1594
+ trailingRowHeight = getFreezeTrailingHeight(
1595
+ mangledRows,
1596
+ freezeTrailingRowsEffective,
1597
+ rowHeight
1598
+ );
1599
+ }
1600
+
1601
+ // scrollBounds is already scaled
1602
+ let sLeft = frozenWidth * scale + scrollBounds.left + rowMarkerOffset * rowMarkerWidth * scale;
1603
+ let sRight = scrollBounds.right;
1604
+ let sTop = scrollBounds.top + totalHeaderHeight * scale;
1605
+ let sBottom = scrollBounds.bottom - trailingRowHeight * scale;
1606
+
1607
+ const minx = targetRect.width + paddingX * 2;
1608
+ switch (options?.hAlign) {
1609
+ case "start":
1610
+ sRight = sLeft + minx;
1611
+ break;
1612
+ case "end":
1613
+ sLeft = sRight - minx;
1614
+ break;
1615
+ case "center":
1616
+ sLeft = Math.floor((sLeft + sRight) / 2) - minx / 2;
1617
+ sRight = sLeft + minx;
1618
+ break;
1619
+ }
1620
+
1621
+ const miny = targetRect.height + paddingY * 2;
1622
+ switch (options?.vAlign) {
1623
+ case "start":
1624
+ sBottom = sTop + miny;
1625
+ break;
1626
+ case "end":
1627
+ sTop = sBottom - miny;
1628
+ break;
1629
+ case "center":
1630
+ sTop = Math.floor((sTop + sBottom) / 2) - miny / 2;
1631
+ sBottom = sTop + miny;
1632
+ break;
1633
+ }
1634
+
1635
+ if (sLeft > bounds.x) {
1636
+ scrollX = bounds.x - sLeft;
1637
+ } else if (sRight < bounds.x + bounds.width) {
1638
+ scrollX = bounds.x + bounds.width - sRight;
1639
+ }
1640
+
1641
+ if (sTop > bounds.y) {
1642
+ scrollY = bounds.y - sTop;
1643
+ } else if (sBottom < bounds.y + bounds.height) {
1644
+ scrollY = bounds.y + bounds.height - sBottom;
1645
+ }
1646
+
1647
+ if (dir === "vertical" || (typeof col === "number" && col < freezeColumns)) {
1648
+ scrollX = 0;
1649
+ } else if (
1650
+ dir === "horizontal" ||
1651
+ (typeof row === "number" && row >= mangledRows - freezeTrailingRowsEffective)
1652
+ ) {
1653
+ scrollY = 0;
1654
+ }
1655
+
1656
+ if (scrollX !== 0 || scrollY !== 0) {
1657
+ // Remove scaling as scrollTo method is unaffected by transform scale.
1658
+ if (scale !== 1) {
1659
+ scrollX /= scale;
1660
+ scrollY /= scale;
1661
+ }
1662
+ scrollRef.current.scrollTo({
1663
+ left: scrollX + scrollRef.current.scrollLeft,
1664
+ top: scrollY + scrollRef.current.scrollTop,
1665
+ behavior: options?.behavior ?? "auto",
1666
+ });
1667
+ }
1668
+ }
1669
+ }
1670
+ }
1671
+ },
1672
+ [
1673
+ rowMarkerOffset,
1674
+ freezeTrailingRows,
1675
+ rowMarkerWidth,
1676
+ scrollRef,
1677
+ totalHeaderHeight,
1678
+ freezeColumns,
1679
+ columns,
1680
+ mangledRows,
1681
+ lastRowSticky,
1682
+ rowHeight,
1683
+ ]
1684
+ );
1685
+
1686
+ const focusCallback = React.useRef(focusOnRowFromTrailingBlankRow);
1687
+ const getCellContentRef = React.useRef(getCellContent);
1688
+
1689
+ focusCallback.current = focusOnRowFromTrailingBlankRow;
1690
+ getCellContentRef.current = getCellContent;
1691
+
1692
+ const rowsRef = React.useRef(rows);
1693
+ rowsRef.current = rows;
1694
+
1695
+ const colsRef = React.useRef(mangledCols.length);
1696
+ colsRef.current = mangledCols.length;
1697
+
1698
+ const appendRow = React.useCallback(
1699
+ async (col: number, openOverlay: boolean = true, behavior?: ScrollBehavior): Promise<void> => {
1700
+ const c = mangledCols[col];
1701
+ if (c?.trailingRowOptions?.disabled === true) {
1702
+ return;
1703
+ }
1704
+ const appendResult = onRowAppended?.();
1705
+
1706
+ let r: "top" | "bottom" | number | undefined = undefined;
1707
+ let bottom = true;
1708
+ if (appendResult !== undefined) {
1709
+ r = await appendResult;
1710
+ if (r === "top") bottom = false;
1711
+ if (typeof r === "number") bottom = false;
1712
+ }
1713
+
1714
+ let backoff = 0;
1715
+ const doFocus = () => {
1716
+ if (rowsRef.current <= rows) {
1717
+ if (backoff < 500) {
1718
+ window.setTimeout(doFocus, backoff);
1719
+ }
1720
+ backoff = 50 + backoff * 2;
1721
+ return;
1722
+ }
1723
+
1724
+ const row = typeof r === "number" ? r : bottom ? rows : 0;
1725
+ scrollToRef.current(col - rowMarkerOffset, row, "both", 0, 0, behavior ? { behavior } : undefined);
1726
+ setCurrent(
1727
+ {
1728
+ cell: [col, row],
1729
+ range: {
1730
+ x: col,
1731
+ y: row,
1732
+ width: 1,
1733
+ height: 1,
1734
+ },
1735
+ },
1736
+ false,
1737
+ false,
1738
+ "edit"
1739
+ );
1740
+
1741
+ const cell = getCellContentRef.current([col - rowMarkerOffset, row]);
1742
+ if (cell.allowOverlay && isReadWriteCell(cell) && cell.readonly !== true && openOverlay) {
1743
+ // wait for scroll to have a chance to process
1744
+ window.setTimeout(() => {
1745
+ focusCallback.current(col, row);
1746
+ }, 0);
1747
+ }
1748
+ };
1749
+ // Queue up to allow the consumer to react to the event and let us check if they did
1750
+ doFocus();
1751
+ },
1752
+ [mangledCols, onRowAppended, rowMarkerOffset, rows, setCurrent]
1753
+ );
1754
+
1755
+ const appendColumn = React.useCallback(
1756
+ async (row: number, openOverlay: boolean = true): Promise<void> => {
1757
+ const appendResult = onColumnAppended?.();
1758
+
1759
+ let r: "left" | "right" | number | undefined = undefined;
1760
+ let right = true;
1761
+ if (appendResult !== undefined) {
1762
+ r = await appendResult;
1763
+ if (r === "left") right = false;
1764
+ if (typeof r === "number") right = false;
1765
+ }
1766
+
1767
+ let backoff = 0;
1768
+ const doFocus = () => {
1769
+ if (colsRef.current <= mangledCols.length) {
1770
+ if (backoff < 500) {
1771
+ window.setTimeout(doFocus, backoff);
1772
+ }
1773
+ backoff = 50 + backoff * 2;
1774
+ return;
1775
+ }
1776
+
1777
+ const col = typeof r === "number" ? r : right ? mangledCols.length : 0;
1778
+ scrollTo(col - rowMarkerOffset, row);
1779
+ setCurrent(
1780
+ {
1781
+ cell: [col, row],
1782
+ range: {
1783
+ x: col,
1784
+ y: row,
1785
+ width: 1,
1786
+ height: 1,
1787
+ },
1788
+ },
1789
+ false,
1790
+ false,
1791
+ "edit"
1792
+ );
1793
+
1794
+ const cell = getCellContentRef.current([col - rowMarkerOffset, row]);
1795
+ if (cell.allowOverlay && isReadWriteCell(cell) && cell.readonly !== true && openOverlay) {
1796
+ window.setTimeout(() => {
1797
+ focusCallback.current(col, row);
1798
+ }, 0);
1799
+ }
1800
+ };
1801
+ doFocus();
1802
+ },
1803
+ [mangledCols, onColumnAppended, rowMarkerOffset, scrollTo, setCurrent]
1804
+ );
1805
+
1806
+ const getCustomNewRowTargetColumn = React.useCallback(
1807
+ (col: number): number | undefined => {
1808
+ const customTargetColumn =
1809
+ columns[col]?.trailingRowOptions?.targetColumn ?? trailingRowOptions?.targetColumn;
1810
+
1811
+ if (typeof customTargetColumn === "number") {
1812
+ const customTargetOffset = hasRowMarkers ? 1 : 0;
1813
+ return customTargetColumn + customTargetOffset;
1814
+ }
1815
+
1816
+ if (typeof customTargetColumn === "object") {
1817
+ const maybeIndex = columnsIn.indexOf(customTargetColumn);
1818
+ if (maybeIndex >= 0) {
1819
+ const customTargetOffset = hasRowMarkers ? 1 : 0;
1820
+ return maybeIndex + customTargetOffset;
1821
+ }
1822
+ }
1823
+
1824
+ return undefined;
1825
+ },
1826
+ [columns, columnsIn, hasRowMarkers, trailingRowOptions?.targetColumn]
1827
+ );
1828
+
1829
+ const lastSelectedRowRef = React.useRef<number | undefined>(undefined);
1830
+ const lastSelectedColRef = React.useRef<number | undefined>(undefined);
1831
+
1832
+ const themeForCell = React.useCallback(
1833
+ (cell: InnerGridCell, pos: Item): FullTheme => {
1834
+ const [col, row] = pos;
1835
+ return mergeAndRealizeTheme(
1836
+ mergedTheme,
1837
+ mangledCols[col]?.themeOverride,
1838
+ getRowThemeOverride?.(row),
1839
+ cell.themeOverride
1840
+ );
1841
+ },
1842
+ [getRowThemeOverride, mangledCols, mergedTheme]
1843
+ );
1844
+
1845
+ const { mapper } = useRowGrouping(rowGrouping, rowsIn);
1846
+
1847
+ const rowGroupingNavBehavior = rowGrouping?.navigationBehavior;
1848
+
1849
+ const handleSelect = React.useCallback(
1850
+ (args: GridMouseEventArgs) => {
1851
+ const isMultiKey = browserIsOSX.value ? args.metaKey : args.ctrlKey;
1852
+ const isMultiRow = isMultiKey && rowSelect === "multi";
1853
+
1854
+ const [col, row] = args.location;
1855
+ const selectedColumns = gridSelection.columns;
1856
+ const selectedRows = gridSelection.rows;
1857
+ const [cellCol, cellRow] = gridSelection.current?.cell ?? [];
1858
+ // eslint-disable-next-line unicorn/prefer-switch
1859
+ if (args.kind === "cell") {
1860
+ lastSelectedColRef.current = undefined;
1861
+
1862
+ lastMouseSelectLocation.current = [col, row];
1863
+
1864
+ if (col === 0 && hasRowMarkers) {
1865
+ if (
1866
+ (showTrailingBlankRow === true && row === rows) ||
1867
+ rowMarkers === "number" ||
1868
+ rowSelect === "none"
1869
+ )
1870
+ return;
1871
+
1872
+ const markerCell = getMangledCellContent(args.location);
1873
+ if (markerCell.kind !== InnerGridCellKind.Marker) {
1874
+ return;
1875
+ }
1876
+
1877
+ if (onRowMoved !== undefined) {
1878
+ const renderer = getCellRenderer(markerCell);
1879
+ assert(renderer?.kind === InnerGridCellKind.Marker);
1880
+ const postClick = renderer?.onClick?.({
1881
+ ...args,
1882
+ cell: markerCell,
1883
+ posX: args.localEventX,
1884
+ posY: args.localEventY,
1885
+ bounds: args.bounds,
1886
+ theme: themeForCell(markerCell, args.location),
1887
+ preventDefault: () => undefined,
1888
+ }) as MarkerCell | undefined;
1889
+ if (postClick === undefined || postClick.checked === markerCell.checked) return;
1890
+ }
1891
+
1892
+ setOverlay(undefined);
1893
+ focus();
1894
+ const isSelected = selectedRows.hasIndex(row);
1895
+
1896
+ const lastHighlighted = lastSelectedRowRef.current;
1897
+ if (
1898
+ rowSelect === "multi" &&
1899
+ (args.shiftKey || args.isLongTouch === true) &&
1900
+ lastHighlighted !== undefined &&
1901
+ selectedRows.hasIndex(lastHighlighted)
1902
+ ) {
1903
+ const newSlice: Slice = [Math.min(lastHighlighted, row), Math.max(lastHighlighted, row) + 1];
1904
+
1905
+ if (isMultiRow || rowSelectionMode === "multi") {
1906
+ setSelectedRows(undefined, newSlice, true);
1907
+ } else {
1908
+ setSelectedRows(CompactSelection.fromSingleSelection(newSlice), undefined, isMultiRow);
1909
+ }
1910
+ } else if (rowSelect === "multi" && (isMultiRow || args.isTouch || rowSelectionMode === "multi")) {
1911
+ if (isSelected) {
1912
+ setSelectedRows(selectedRows.remove(row), undefined, true);
1913
+ } else {
1914
+ setSelectedRows(undefined, row, true);
1915
+ lastSelectedRowRef.current = row;
1916
+ }
1917
+ } else if (isSelected && selectedRows.length === 1) {
1918
+ setSelectedRows(CompactSelection.empty(), undefined, isMultiKey);
1919
+ } else {
1920
+ setSelectedRows(CompactSelection.fromSingleSelection(row), undefined, isMultiKey);
1921
+ lastSelectedRowRef.current = row;
1922
+ }
1923
+ } else if (col >= rowMarkerOffset && showTrailingBlankRow && row === rows) {
1924
+ const customTargetColumn = getCustomNewRowTargetColumn(col);
1925
+ void appendRow(customTargetColumn ?? col);
1926
+ } else {
1927
+ if (cellCol !== col || cellRow !== row) {
1928
+ const cell = getMangledCellContent(args.location);
1929
+ const renderer = getCellRenderer(cell);
1930
+
1931
+ if (renderer?.onSelect !== undefined) {
1932
+ let prevented = false;
1933
+ renderer.onSelect({
1934
+ ...args,
1935
+ cell,
1936
+ posX: args.localEventX,
1937
+ posY: args.localEventY,
1938
+ bounds: args.bounds,
1939
+ preventDefault: () => (prevented = true),
1940
+ theme: themeForCell(cell, args.location),
1941
+ });
1942
+ if (prevented) {
1943
+ return;
1944
+ }
1945
+ }
1946
+
1947
+ if (rowGroupingNavBehavior === "block" && mapper(row).isGroupHeader) {
1948
+ return;
1949
+ }
1950
+
1951
+ const isLastStickyRow = lastRowSticky && row === rows;
1952
+
1953
+ const startedFromLastSticky =
1954
+ lastRowSticky && gridSelection !== undefined && gridSelection.current?.cell[1] === rows;
1955
+
1956
+ if (
1957
+ (args.shiftKey || args.isLongTouch === true) &&
1958
+ cellCol !== undefined &&
1959
+ cellRow !== undefined &&
1960
+ gridSelection.current !== undefined &&
1961
+ !startedFromLastSticky
1962
+ ) {
1963
+ if (isLastStickyRow) {
1964
+ // If we're making a selection and shift click in to the last sticky row,
1965
+ // just drop the event. Don't kill the selection.
1966
+ return;
1967
+ }
1968
+
1969
+ const left = Math.min(col, cellCol);
1970
+ const right = Math.max(col, cellCol);
1971
+ const top = Math.min(row, cellRow);
1972
+ const bottom = Math.max(row, cellRow);
1973
+ setCurrent(
1974
+ {
1975
+ ...gridSelection.current,
1976
+ range: {
1977
+ x: left,
1978
+ y: top,
1979
+ width: right - left + 1,
1980
+ height: bottom - top + 1,
1981
+ },
1982
+ },
1983
+ true,
1984
+ isMultiKey,
1985
+ "click"
1986
+ );
1987
+ lastSelectedRowRef.current = undefined;
1988
+ focus();
1989
+ } else {
1990
+ setCurrent(
1991
+ {
1992
+ cell: [col, row],
1993
+ range: { x: col, y: row, width: 1, height: 1 },
1994
+ },
1995
+ true,
1996
+ isMultiKey,
1997
+ "click"
1998
+ );
1999
+ lastSelectedRowRef.current = undefined;
2000
+ setOverlay(undefined);
2001
+ focus();
2002
+ }
2003
+ }
2004
+ }
2005
+ } else if (args.kind === "header") {
2006
+ lastMouseSelectLocation.current = [col, row];
2007
+ setOverlay(undefined);
2008
+ if (hasRowMarkers && col === 0) {
2009
+ lastSelectedRowRef.current = undefined;
2010
+ lastSelectedColRef.current = undefined;
2011
+ if (!headerRowMarkerDisabled && rowSelect === "multi") {
2012
+ if (selectedRows.length !== rows) {
2013
+ setSelectedRows(CompactSelection.fromSingleSelection([0, rows]), undefined, isMultiKey);
2014
+ } else {
2015
+ setSelectedRows(CompactSelection.empty(), undefined, isMultiKey);
2016
+ }
2017
+ focus();
2018
+ }
2019
+ } else {
2020
+ const lastCol = lastSelectedColRef.current;
2021
+ if (
2022
+ columnSelect === "multi" &&
2023
+ (args.shiftKey || args.isLongTouch === true) &&
2024
+ lastCol !== undefined &&
2025
+ selectedColumns.hasIndex(lastCol)
2026
+ ) {
2027
+ // Support for selecting a slice of columns:
2028
+ const newSlice: Slice = [Math.min(lastCol, col), Math.max(lastCol, col) + 1];
2029
+
2030
+ if (isMultiKey || args.isTouch || columnSelectionMode === "multi") {
2031
+ setSelectedColumns(undefined, newSlice, isMultiKey);
2032
+ } else {
2033
+ setSelectedColumns(CompactSelection.fromSingleSelection(newSlice), undefined, isMultiKey);
2034
+ }
2035
+ } else if (
2036
+ columnSelect === "multi" &&
2037
+ (isMultiKey || args.isTouch || columnSelectionMode === "multi")
2038
+ ) {
2039
+ // Support for selecting a single columns additively:
2040
+ if (selectedColumns.hasIndex(col)) {
2041
+ // If the column is already selected, deselect that column:
2042
+ setSelectedColumns(selectedColumns.remove(col), undefined, isMultiKey);
2043
+ } else {
2044
+ setSelectedColumns(undefined, col, isMultiKey);
2045
+ }
2046
+ lastSelectedColRef.current = col;
2047
+ } else if (columnSelect !== "none") {
2048
+ if (selectedColumns.hasIndex(col)) {
2049
+ // If the column is already selected, deselect that column:
2050
+ setSelectedColumns(selectedColumns.remove(col), undefined, isMultiKey);
2051
+ } else {
2052
+ setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, isMultiKey);
2053
+ }
2054
+ lastSelectedColRef.current = col;
2055
+ }
2056
+ lastSelectedRowRef.current = undefined;
2057
+ focus();
2058
+ }
2059
+ } else if (args.kind === groupHeaderKind) {
2060
+ lastMouseSelectLocation.current = [col, row];
2061
+ } else if (args.kind === outOfBoundsKind && !args.isMaybeScrollbar) {
2062
+ setGridSelection(emptyGridSelection, false);
2063
+ setOverlay(undefined);
2064
+ focus();
2065
+ onSelectionCleared?.();
2066
+ lastSelectedRowRef.current = undefined;
2067
+ lastSelectedColRef.current = undefined;
2068
+ }
2069
+ },
2070
+ [
2071
+ rowSelect,
2072
+ columnSelect,
2073
+ gridSelection,
2074
+ hasRowMarkers,
2075
+ rowMarkerOffset,
2076
+ showTrailingBlankRow,
2077
+ rows,
2078
+ rowMarkers,
2079
+ getMangledCellContent,
2080
+ onRowMoved,
2081
+ focus,
2082
+ rowSelectionMode,
2083
+ columnSelectionMode,
2084
+ getCellRenderer,
2085
+ themeForCell,
2086
+ setSelectedRows,
2087
+ getCustomNewRowTargetColumn,
2088
+ appendRow,
2089
+ rowGroupingNavBehavior,
2090
+ mapper,
2091
+ lastRowSticky,
2092
+ setCurrent,
2093
+ headerRowMarkerDisabled,
2094
+ setSelectedColumns,
2095
+ setGridSelection,
2096
+ onSelectionCleared,
2097
+ ]
2098
+ );
2099
+ const isActivelyDraggingHeader = React.useRef(false);
2100
+ const lastMouseSelectLocation = React.useRef<readonly [number, number] | undefined>(undefined);
2101
+ const touchDownArgs = React.useRef(visibleRegion);
2102
+ const mouseDownData = React.useRef<{
2103
+ time: number;
2104
+ button: number;
2105
+ location: Item;
2106
+ } | undefined>(undefined);
2107
+ const onMouseDown = React.useCallback(
2108
+ (args: GridMouseEventArgs) => {
2109
+ isPrevented.current = false;
2110
+ touchDownArgs.current = visibleRegionRef.current;
2111
+ if (args.button !== 0 && args.button !== 1) {
2112
+ mouseDownData.current = undefined;
2113
+ return;
2114
+ }
2115
+
2116
+ const time = performance.now();
2117
+ mouseDownData.current = {
2118
+ button: args.button,
2119
+ time,
2120
+ location: args.location,
2121
+ };
2122
+
2123
+ if (args?.kind === "header") {
2124
+ isActivelyDraggingHeader.current = true;
2125
+ }
2126
+
2127
+ const fh = args.kind === "cell" && args.isFillHandle;
2128
+
2129
+ if (!fh && args.kind !== "cell" && args.isEdge) return;
2130
+
2131
+ setMouseState({
2132
+ previousSelection: gridSelection,
2133
+ fillHandle: fh,
2134
+ });
2135
+ lastMouseSelectLocation.current = undefined;
2136
+
2137
+ if (!args.isTouch && args.button === 0 && !fh) {
2138
+ handleSelect(args);
2139
+ } else if (!args.isTouch && args.button === 1) {
2140
+ lastMouseSelectLocation.current = args.location;
2141
+ }
2142
+ },
2143
+ [gridSelection, handleSelect]
2144
+ );
2145
+
2146
+ const [renameGroup, setRenameGroup] = React.useState<{
2147
+ group: string;
2148
+ bounds: Rectangle;
2149
+ }>();
2150
+
2151
+ const handleGroupHeaderSelection = React.useCallback(
2152
+ (args: GridMouseEventArgs) => {
2153
+ if (args.kind !== groupHeaderKind || columnSelect !== "multi") {
2154
+ return;
2155
+ }
2156
+ const isMultiKey = browserIsOSX.value ? args.metaKey : args.ctrlKey;
2157
+ const [col, row] = args.location;
2158
+ const selectedColumns = gridSelection.columns;
2159
+
2160
+ if (col < rowMarkerOffset) return;
2161
+
2162
+ // Determine the level of the clicked group header
2163
+ // row is -2 for level 0, -3 for level 1, etc.
2164
+ const level = row <= -2 ? -2 - row : 0;
2165
+
2166
+ const needle = mangledCols[col];
2167
+
2168
+ // Find all columns that belong to the same group at this level and all levels below
2169
+ // We need to find columns that match at the clicked level AND all parent levels
2170
+ let start = col;
2171
+ let end = col;
2172
+
2173
+ // Find start: go backwards and check if groups match at all levels from 0 to the clicked level
2174
+ for (let i = col - 1; i >= rowMarkerOffset; i--) {
2175
+ let matches = true;
2176
+ for (let l = 0; l <= level; l++) {
2177
+ if (!isGroupEqual(needle.group, mangledCols[i].group, l)) {
2178
+ matches = false;
2179
+ break;
2180
+ }
2181
+ }
2182
+ if (!matches) break;
2183
+ start--;
2184
+ }
2185
+
2186
+ // Find end: go forwards and check if groups match at all levels from 0 to the clicked level
2187
+ for (let i = col + 1; i < mangledCols.length; i++) {
2188
+ let matches = true;
2189
+ for (let l = 0; l <= level; l++) {
2190
+ if (!isGroupEqual(needle.group, mangledCols[i].group, l)) {
2191
+ matches = false;
2192
+ break;
2193
+ }
2194
+ }
2195
+ if (!matches) break;
2196
+ end++;
2197
+ }
2198
+
2199
+ focus();
2200
+
2201
+ if (isMultiKey || args.isTouch || columnSelectionMode === "multi") {
2202
+ if (selectedColumns.hasAll([start, end + 1])) {
2203
+ let newVal = selectedColumns;
2204
+ for (let index = start; index <= end; index++) {
2205
+ newVal = newVal.remove(index);
2206
+ }
2207
+ setSelectedColumns(newVal, undefined, isMultiKey);
2208
+ } else {
2209
+ setSelectedColumns(undefined, [start, end + 1], isMultiKey);
2210
+ }
2211
+ } else {
2212
+ setSelectedColumns(CompactSelection.fromSingleSelection([start, end + 1]), undefined, isMultiKey);
2213
+ }
2214
+ },
2215
+ [
2216
+ columnSelect,
2217
+ focus,
2218
+ gridSelection.columns,
2219
+ mangledCols,
2220
+ rowMarkerOffset,
2221
+ setSelectedColumns,
2222
+ columnSelectionMode,
2223
+ ]
2224
+ );
2225
+
2226
+ const isPrevented = React.useRef(false);
2227
+
2228
+ const normalSizeColumn = React.useCallback(
2229
+ async (col: number): Promise<void> => {
2230
+ if (getCellsForSelection !== undefined && onColumnResize !== undefined) {
2231
+ const start = visibleRegionRef.current.y;
2232
+ const end = visibleRegionRef.current.height;
2233
+ let cells = getCellsForSelection(
2234
+ {
2235
+ x: col,
2236
+ y: start,
2237
+ width: 1,
2238
+ height: Math.min(end, rows - start),
2239
+ },
2240
+ abortControllerRef.current.signal
2241
+ );
2242
+ if (typeof cells !== "object") {
2243
+ cells = await cells();
2244
+ }
2245
+ const inputCol = columns[col - rowMarkerOffset];
2246
+ const offscreen = document.createElement("canvas");
2247
+ const ctx = offscreen.getContext("2d", { alpha: false });
2248
+ if (ctx !== null) {
2249
+ ctx.font = mergedTheme.baseFontFull;
2250
+ const newCol = measureColumn(
2251
+ ctx,
2252
+ mergedTheme,
2253
+ inputCol,
2254
+ 0,
2255
+ cells,
2256
+ minColumnWidth,
2257
+ maxColumnWidth,
2258
+ false,
2259
+ getCellRenderer
2260
+ );
2261
+ onColumnResize?.(inputCol, newCol.width, col, newCol.width);
2262
+ }
2263
+ }
2264
+ },
2265
+ [
2266
+ columns,
2267
+ getCellsForSelection,
2268
+ maxColumnWidth,
2269
+ mergedTheme,
2270
+ minColumnWidth,
2271
+ onColumnResize,
2272
+ rowMarkerOffset,
2273
+ rows,
2274
+ getCellRenderer,
2275
+ ]
2276
+ );
2277
+
2278
+ const [scrollDir, setScrollDir] = React.useState<GridMouseEventArgs["scrollEdge"]>();
2279
+
2280
+ const fillPattern = React.useCallback(
2281
+ async (previousSelection: GridSelection, currentSelection: GridSelection) => {
2282
+ const patternRange = previousSelection.current?.range;
2283
+
2284
+ if (
2285
+ patternRange === undefined ||
2286
+ getCellsForSelection === undefined ||
2287
+ currentSelection.current === undefined
2288
+ ) {
2289
+ return;
2290
+ }
2291
+ const currentRange = currentSelection.current.range;
2292
+
2293
+ if (onFillPattern !== undefined) {
2294
+ let canceled = false;
2295
+ onFillPattern({
2296
+ fillDestination: { ...currentRange, x: currentRange.x - rowMarkerOffset },
2297
+ patternSource: { ...patternRange, x: patternRange.x - rowMarkerOffset },
2298
+ preventDefault: () => (canceled = true),
2299
+ });
2300
+ if (canceled) return;
2301
+ }
2302
+
2303
+ let cells = getCellsForSelection(patternRange, abortControllerRef.current.signal);
2304
+ if (typeof cells !== "object") cells = await cells();
2305
+
2306
+ const pattern = cells;
2307
+
2308
+ // loop through all cells in currentSelection.current.range
2309
+ const editItemList: EditListItem[] = [];
2310
+ for (let x = 0; x < currentRange.width; x++) {
2311
+ for (let y = 0; y < currentRange.height; y++) {
2312
+ const cell: Item = [currentRange.x + x, currentRange.y + y];
2313
+ if (itemIsInRect(cell, patternRange)) continue;
2314
+ const patternCell = pattern[y % patternRange.height][x % patternRange.width];
2315
+ if (isInnerOnlyCell(patternCell) || !isReadWriteCell(patternCell)) continue;
2316
+ editItemList.push({
2317
+ location: cell,
2318
+ value: { ...patternCell },
2319
+ });
2320
+ }
2321
+ }
2322
+ mangledOnCellsEdited(editItemList);
2323
+
2324
+ gridRef.current?.damage(
2325
+ editItemList.map(c => ({
2326
+ cell: c.location,
2327
+ }))
2328
+ );
2329
+ },
2330
+ [getCellsForSelection, mangledOnCellsEdited, onFillPattern, rowMarkerOffset]
2331
+ );
2332
+
2333
+ const fillRight = React.useCallback(() => {
2334
+ if (gridSelection.current === undefined || gridSelection.current.range.width <= 1) return;
2335
+
2336
+ const firstColSelection = {
2337
+ ...gridSelection,
2338
+ current: {
2339
+ ...gridSelection.current,
2340
+ range: {
2341
+ ...gridSelection.current.range,
2342
+ width: 1,
2343
+ },
2344
+ },
2345
+ };
2346
+
2347
+ void fillPattern(firstColSelection, gridSelection);
2348
+ }, [fillPattern, gridSelection]);
2349
+
2350
+ const fillDown = React.useCallback(() => {
2351
+ if (gridSelection.current === undefined || gridSelection.current.range.height <= 1) return;
2352
+
2353
+ const firstRowSelection = {
2354
+ ...gridSelection,
2355
+ current: {
2356
+ ...gridSelection.current,
2357
+ range: {
2358
+ ...gridSelection.current.range,
2359
+ height: 1,
2360
+ },
2361
+ },
2362
+ };
2363
+
2364
+ void fillPattern(firstRowSelection, gridSelection);
2365
+ }, [fillPattern, gridSelection]);
2366
+
2367
+ const onMouseUp = React.useCallback(
2368
+ (args: GridMouseEventArgs, isOutside: boolean) => {
2369
+ const mouse = mouseState;
2370
+ setMouseState(undefined);
2371
+ setFillHighlightRegion(undefined);
2372
+ setScrollDir(undefined);
2373
+ isActivelyDraggingHeader.current = false;
2374
+
2375
+ if (isOutside) return;
2376
+
2377
+ if (
2378
+ mouse?.fillHandle === true &&
2379
+ gridSelection.current !== undefined &&
2380
+ mouse.previousSelection?.current !== undefined
2381
+ ) {
2382
+ if (fillHighlightRegion === undefined) return;
2383
+ const newRange = {
2384
+ ...gridSelection,
2385
+ current: {
2386
+ ...gridSelection.current,
2387
+ range: combineRects(mouse.previousSelection.current.range, fillHighlightRegion),
2388
+ },
2389
+ };
2390
+ void fillPattern(mouse.previousSelection, newRange);
2391
+ setGridSelection(newRange, true);
2392
+ return;
2393
+ }
2394
+
2395
+ const [col, row] = args.location;
2396
+ const [lastMouseDownCol, lastMouseDownRow] = lastMouseSelectLocation.current ?? [];
2397
+
2398
+ const preventDefault = () => {
2399
+ isPrevented.current = true;
2400
+ };
2401
+
2402
+ const handleMaybeClick = (a: GridMouseCellEventArgs): boolean => {
2403
+ const isValidClick = a.isTouch || (lastMouseDownCol === col && lastMouseDownRow === row);
2404
+ if (isValidClick) {
2405
+ onCellClicked?.([col - rowMarkerOffset, row], {
2406
+ ...a,
2407
+ preventDefault,
2408
+ });
2409
+ }
2410
+ if (a.button === 1) return !isPrevented.current;
2411
+ if (!isPrevented.current) {
2412
+ const c = getMangledCellContent(args.location);
2413
+ const r = getCellRenderer(c);
2414
+ if (r !== undefined && r.onClick !== undefined && isValidClick) {
2415
+ const newVal = r.onClick({
2416
+ ...a,
2417
+ cell: c,
2418
+ posX: a.localEventX,
2419
+ posY: a.localEventY,
2420
+ bounds: a.bounds,
2421
+ theme: themeForCell(c, args.location),
2422
+ preventDefault,
2423
+ });
2424
+ if (newVal !== undefined && !isInnerOnlyCell(newVal) && isEditableGridCell(newVal)) {
2425
+ mangledOnCellsEdited([{ location: a.location, value: newVal }]);
2426
+ gridRef.current?.damage([
2427
+ {
2428
+ cell: a.location,
2429
+ },
2430
+ ]);
2431
+ }
2432
+ }
2433
+ if (isPrevented.current || gridSelection.current === undefined) return false;
2434
+
2435
+ let shouldActivate = false;
2436
+ switch (c.activationBehaviorOverride ?? cellActivationBehavior) {
2437
+ case "double-click":
2438
+ case "second-click": {
2439
+ if (mouse?.previousSelection?.current?.cell === undefined) break;
2440
+ const [selectedCol, selectedRow] = gridSelection.current.cell;
2441
+ const [prevCol, prevRow] = mouse.previousSelection.current.cell;
2442
+ const isClickOnSelected =
2443
+ col === selectedCol && col === prevCol && row === selectedRow && row === prevRow;
2444
+ shouldActivate =
2445
+ isClickOnSelected &&
2446
+ (a.isDoubleClick === true || cellActivationBehavior === "second-click");
2447
+ break;
2448
+ }
2449
+ case "single-click": {
2450
+ shouldActivate = true;
2451
+ break;
2452
+ }
2453
+ }
2454
+ if (shouldActivate) {
2455
+ const act =
2456
+ a.isDoubleClick === true
2457
+ ? "double-click"
2458
+ : (c.activationBehaviorOverride ?? cellActivationBehavior);
2459
+ const activationEvent: CellActivatedEventArgs = {
2460
+ inputType: "pointer",
2461
+ pointerActivation: act,
2462
+ pointerType: a.isTouch ? "touch" : "mouse",
2463
+ };
2464
+ onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
2465
+ reselect(a.bounds, activationEvent);
2466
+ return true;
2467
+ }
2468
+ }
2469
+ return false;
2470
+ };
2471
+
2472
+ const clickLocation = args.location[0] - rowMarkerOffset;
2473
+ if (args.isTouch) {
2474
+ const vr = visibleRegionRef.current;
2475
+ const touchVr = touchDownArgs.current;
2476
+ if (vr.x !== touchVr.x || vr.y !== touchVr.y) {
2477
+ // we scrolled, abort
2478
+ return;
2479
+ }
2480
+ // take care of context menus first if long pressed item is already selected
2481
+ if (args.isLongTouch === true) {
2482
+ if (args.kind === "cell" && itemsAreEqual(gridSelection.current?.cell, args.location)) {
2483
+ onCellContextMenu?.([clickLocation, args.location[1]], {
2484
+ ...args,
2485
+ preventDefault,
2486
+ });
2487
+ return;
2488
+ } else if (args.kind === "header" && gridSelection.columns.hasIndex(col)) {
2489
+ onHeaderContextMenu?.(clickLocation, { ...args, preventDefault });
2490
+ return;
2491
+ } else if (args.kind === groupHeaderKind) {
2492
+ if (clickLocation < 0) {
2493
+ return;
2494
+ }
2495
+
2496
+ onGroupHeaderContextMenu?.(clickLocation, { ...args, preventDefault });
2497
+ return;
2498
+ }
2499
+ }
2500
+ if (args.kind === "cell") {
2501
+ // click that cell
2502
+ if (!handleMaybeClick(args)) {
2503
+ handleSelect(args);
2504
+ }
2505
+ } else if (args.kind === groupHeaderKind) {
2506
+ onGroupHeaderClicked?.(clickLocation, { ...args, preventDefault });
2507
+ } else {
2508
+ if (args.kind === headerKind) {
2509
+ onHeaderClicked?.(clickLocation, {
2510
+ ...args,
2511
+ preventDefault,
2512
+ });
2513
+ }
2514
+ handleSelect(args);
2515
+ }
2516
+ return;
2517
+ }
2518
+
2519
+ if (args.kind === "header") {
2520
+ if (clickLocation < 0) {
2521
+ return;
2522
+ }
2523
+
2524
+ if (args.isEdge) {
2525
+ if (args.isDoubleClick === true) {
2526
+ void normalSizeColumn(col);
2527
+ }
2528
+ } else if (args.button === 0 && col === lastMouseDownCol && row === lastMouseDownRow) {
2529
+ onHeaderClicked?.(clickLocation, { ...args, preventDefault });
2530
+ }
2531
+ }
2532
+
2533
+ if (args.kind === groupHeaderKind) {
2534
+ if (clickLocation < 0) {
2535
+ return;
2536
+ }
2537
+
2538
+ if (args.button === 0 && col === lastMouseDownCol && row === lastMouseDownRow) {
2539
+ onGroupHeaderClicked?.(clickLocation, { ...args, preventDefault });
2540
+ if (!isPrevented.current) {
2541
+ handleGroupHeaderSelection(args);
2542
+ }
2543
+ }
2544
+ }
2545
+
2546
+ if (args.kind === "cell" && (args.button === 0 || args.button === 1)) {
2547
+ handleMaybeClick(args);
2548
+ }
2549
+
2550
+ lastMouseSelectLocation.current = undefined;
2551
+ },
2552
+ [
2553
+ mouseState,
2554
+ gridSelection,
2555
+ rowMarkerOffset,
2556
+ fillHighlightRegion,
2557
+ fillPattern,
2558
+ setGridSelection,
2559
+ onCellClicked,
2560
+ getMangledCellContent,
2561
+ getCellRenderer,
2562
+ cellActivationBehavior,
2563
+ themeForCell,
2564
+ mangledOnCellsEdited,
2565
+ onCellActivated,
2566
+ reselect,
2567
+ onCellContextMenu,
2568
+ onHeaderContextMenu,
2569
+ onGroupHeaderContextMenu,
2570
+ handleSelect,
2571
+ onGroupHeaderClicked,
2572
+ onHeaderClicked,
2573
+ normalSizeColumn,
2574
+ handleGroupHeaderSelection,
2575
+ ]
2576
+ );
2577
+
2578
+ const onMouseMoveImpl = React.useCallback(
2579
+ (args: GridMouseEventArgs) => {
2580
+ const a: GridMouseEventArgs = {
2581
+ ...args,
2582
+ location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
2583
+ };
2584
+ onMouseMove?.(a);
2585
+
2586
+ if (mouseState !== undefined && args.buttons === 0) {
2587
+ setMouseState(undefined);
2588
+ setFillHighlightRegion(undefined);
2589
+ setScrollDir(undefined);
2590
+ isActivelyDraggingHeader.current = false;
2591
+ }
2592
+
2593
+ setScrollDir(cv => {
2594
+ if (isActivelyDraggingHeader.current) return [args.scrollEdge[0], 0];
2595
+ if (args.scrollEdge[0] === cv?.[0] && args.scrollEdge[1] === cv[1]) return cv;
2596
+ return mouseState === undefined || (mouseDownData.current?.location[0] ?? 0) < rowMarkerOffset
2597
+ ? undefined
2598
+ : args.scrollEdge;
2599
+ });
2600
+ },
2601
+ [mouseState, onMouseMove, rowMarkerOffset]
2602
+ );
2603
+
2604
+ const onHeaderMenuClickInner = React.useCallback(
2605
+ (col: number, screenPosition: Rectangle) => {
2606
+ onHeaderMenuClick?.(col - rowMarkerOffset, screenPosition);
2607
+ },
2608
+ [onHeaderMenuClick, rowMarkerOffset]
2609
+ );
2610
+
2611
+ const onHeaderIndicatorClickInner = React.useCallback(
2612
+ (col: number, screenPosition: Rectangle) => {
2613
+ onHeaderIndicatorClick?.(col - rowMarkerOffset, screenPosition);
2614
+ },
2615
+ [onHeaderIndicatorClick, rowMarkerOffset]
2616
+ );
2617
+
2618
+ const currentCell = gridSelection?.current?.cell;
2619
+ const onVisibleRegionChangedImpl = React.useCallback(
2620
+ (
2621
+ region: Rectangle,
2622
+ clientWidth: number,
2623
+ clientHeight: number,
2624
+ rightElWidth: number,
2625
+ tx: number,
2626
+ ty: number
2627
+ ) => {
2628
+ hasJustScrolled.current = false;
2629
+ let selected = currentCell;
2630
+ if (selected !== undefined) {
2631
+ selected = [selected[0] - rowMarkerOffset, selected[1]];
2632
+ }
2633
+
2634
+ const freezeRegion =
2635
+ freezeColumns === 0
2636
+ ? undefined
2637
+ : {
2638
+ x: 0,
2639
+ y: region.y,
2640
+ width: freezeColumns,
2641
+ height: region.height,
2642
+ };
2643
+
2644
+ const freezeRegions: Rectangle[] = [];
2645
+ if (freezeRegion !== undefined) freezeRegions.push(freezeRegion);
2646
+ if (freezeTrailingRows > 0) {
2647
+ freezeRegions.push({
2648
+ x: region.x - rowMarkerOffset,
2649
+ y: rows - freezeTrailingRows,
2650
+ width: region.width,
2651
+ height: freezeTrailingRows,
2652
+ });
2653
+
2654
+ if (freezeColumns > 0) {
2655
+ freezeRegions.push({
2656
+ x: 0,
2657
+ y: rows - freezeTrailingRows,
2658
+ width: freezeColumns,
2659
+ height: freezeTrailingRows,
2660
+ });
2661
+ }
2662
+ }
2663
+
2664
+ const newRegion = {
2665
+ x: region.x - rowMarkerOffset,
2666
+ y: region.y,
2667
+ width: region.width,
2668
+ height: showTrailingBlankRow && region.y + region.height >= rows ? region.height - 1 : region.height,
2669
+ tx,
2670
+ ty,
2671
+ extras: {
2672
+ selected,
2673
+ freezeRegion,
2674
+ freezeRegions,
2675
+ },
2676
+ };
2677
+ visibleRegionRef.current = newRegion;
2678
+ setVisibleRegion(newRegion);
2679
+ setClientSize([clientWidth, clientHeight, rightElWidth]);
2680
+ onVisibleRegionChanged?.(newRegion, newRegion.tx, newRegion.ty, newRegion.extras);
2681
+ },
2682
+ [
2683
+ currentCell,
2684
+ rowMarkerOffset,
2685
+ showTrailingBlankRow,
2686
+ rows,
2687
+ freezeColumns,
2688
+ freezeTrailingRows,
2689
+ setVisibleRegion,
2690
+ onVisibleRegionChanged,
2691
+ ]
2692
+ );
2693
+
2694
+ const onColumnProposeMoveImpl = whenDefined(
2695
+ onColumnProposeMove,
2696
+ React.useCallback(
2697
+ (startIndex: number, endIndex: number) => {
2698
+ return onColumnProposeMove?.(startIndex - rowMarkerOffset, endIndex - rowMarkerOffset) !== false;
2699
+ },
2700
+ [onColumnProposeMove, rowMarkerOffset]
2701
+ )
2702
+ );
2703
+
2704
+ const onColumnMovedImpl = whenDefined(
2705
+ onColumnMoved,
2706
+ React.useCallback(
2707
+ (startIndex: number, endIndex: number) => {
2708
+ onColumnMoved?.(startIndex - rowMarkerOffset, endIndex - rowMarkerOffset);
2709
+ if (columnSelect !== "none") {
2710
+ setSelectedColumns(CompactSelection.fromSingleSelection(endIndex), undefined, true);
2711
+ }
2712
+ },
2713
+ [columnSelect, onColumnMoved, rowMarkerOffset, setSelectedColumns]
2714
+ )
2715
+ );
2716
+
2717
+ const isActivelyDragging = React.useRef(false);
2718
+ const onDragStartImpl = React.useCallback(
2719
+ (args: GridDragEventArgs) => {
2720
+ if (args.location[0] === 0 && rowMarkerOffset > 0) {
2721
+ args.preventDefault();
2722
+ return;
2723
+ }
2724
+ onDragStart?.({
2725
+ ...args,
2726
+ location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
2727
+ });
2728
+
2729
+ if (!args.defaultPrevented()) {
2730
+ isActivelyDragging.current = true;
2731
+ }
2732
+ setMouseState(undefined);
2733
+ },
2734
+ [onDragStart, rowMarkerOffset]
2735
+ );
2736
+
2737
+ const onDragEnd = React.useCallback(() => {
2738
+ isActivelyDragging.current = false;
2739
+ }, []);
2740
+
2741
+ const rowGroupingSelectionBehavior = rowGrouping?.selectionBehavior;
2742
+
2743
+ const getSelectionRowLimits = React.useCallback(
2744
+ (selectedRow: number): readonly [number, number] | undefined => {
2745
+ if (rowGroupingSelectionBehavior !== "block-spanning") return undefined;
2746
+
2747
+ const { isGroupHeader, path, groupRows } = mapper(selectedRow);
2748
+
2749
+ if (isGroupHeader) {
2750
+ return [selectedRow, selectedRow];
2751
+ }
2752
+
2753
+ const groupRowIndex = path[path.length - 1];
2754
+ const lowerBounds = selectedRow - groupRowIndex;
2755
+ const upperBounds = selectedRow + groupRows - groupRowIndex - 1;
2756
+
2757
+ return [lowerBounds, upperBounds];
2758
+ },
2759
+ [mapper, rowGroupingSelectionBehavior]
2760
+ );
2761
+
2762
+ const hoveredRef = React.useRef<GridMouseEventArgs | undefined>(undefined);
2763
+ const onItemHoveredImpl = React.useCallback(
2764
+ (args: GridMouseEventArgs) => {
2765
+ // make sure we still have a button down
2766
+ if (mouseEventArgsAreEqual(args, hoveredRef.current)) return;
2767
+ hoveredRef.current = args;
2768
+ if (mouseDownData?.current?.button !== undefined && mouseDownData.current.button >= 1) return;
2769
+ if (
2770
+ args.buttons !== 0 &&
2771
+ mouseState !== undefined &&
2772
+ mouseDownData.current?.location[0] === 0 &&
2773
+ rowMarkerOffset === 1 &&
2774
+ rowSelect === "multi" &&
2775
+ mouseState.previousSelection &&
2776
+ !mouseState.previousSelection.rows.hasIndex(mouseDownData.current.location[1]) &&
2777
+ gridSelection.rows.hasIndex(mouseDownData.current.location[1])
2778
+ ) {
2779
+ const start = Math.min(mouseDownData.current.location[1], args.location[1]);
2780
+ const end = Math.max(mouseDownData.current.location[1], args.location[1]) + 1;
2781
+ setSelectedRows(CompactSelection.fromSingleSelection([start, end]), undefined, false);
2782
+ }
2783
+ // Only handle rect selection if not already processed by row selection:
2784
+ else if (
2785
+ args.buttons !== 0 &&
2786
+ mouseState !== undefined &&
2787
+ gridSelection.current !== undefined &&
2788
+ !isActivelyDragging.current &&
2789
+ !isActivelyDraggingHeader.current &&
2790
+ (rangeSelect === "rect" || rangeSelect === "multi-rect")
2791
+ ) {
2792
+ const [selectedCol, selectedRow] = gridSelection.current.cell;
2793
+ // eslint-disable-next-line prefer-const
2794
+ let [col, row] = args.location;
2795
+
2796
+ if (row < 0) {
2797
+ row = visibleRegionRef.current.y;
2798
+ }
2799
+
2800
+ if (mouseState.fillHandle === true && mouseState.previousSelection?.current !== undefined) {
2801
+ const prevRange = mouseState.previousSelection.current.range;
2802
+ row = Math.min(row, showTrailingBlankRow ? rows - 1 : rows);
2803
+ const rect = getClosestRect(prevRange, col, row, allowedFillDirections);
2804
+ setFillHighlightRegion(rect);
2805
+ } else {
2806
+ const startedFromLastStickyRow = showTrailingBlankRow && selectedRow === rows;
2807
+ if (startedFromLastStickyRow) return;
2808
+
2809
+ const landedOnLastStickyRow = showTrailingBlankRow && row === rows;
2810
+ if (landedOnLastStickyRow) {
2811
+ if (args.kind === outOfBoundsKind) row--;
2812
+ else return;
2813
+ }
2814
+
2815
+ col = Math.max(col, rowMarkerOffset);
2816
+ const clampLimits = getSelectionRowLimits(selectedRow);
2817
+ row = clampLimits === undefined ? row : clamp(row, clampLimits[0], clampLimits[1]);
2818
+
2819
+ // FIXME: Restrict row based on rowGrouping.selectionBehavior here
2820
+
2821
+ const deltaX = col - selectedCol;
2822
+ const deltaY = row - selectedRow;
2823
+
2824
+ const newRange: Rectangle = {
2825
+ x: deltaX >= 0 ? selectedCol : col,
2826
+ y: deltaY >= 0 ? selectedRow : row,
2827
+ width: Math.abs(deltaX) + 1,
2828
+ height: Math.abs(deltaY) + 1,
2829
+ };
2830
+
2831
+ setCurrent(
2832
+ {
2833
+ ...gridSelection.current,
2834
+ range: newRange,
2835
+ },
2836
+ true,
2837
+ false,
2838
+ "drag"
2839
+ );
2840
+ }
2841
+ }
2842
+
2843
+ onItemHovered?.({ ...args, location: [args.location[0] - rowMarkerOffset, args.location[1]] as any });
2844
+ },
2845
+ [
2846
+ mouseState,
2847
+ rowMarkerOffset,
2848
+ rowSelect,
2849
+ gridSelection,
2850
+ rangeSelect,
2851
+ onItemHovered,
2852
+ setSelectedRows,
2853
+ showTrailingBlankRow,
2854
+ rows,
2855
+ allowedFillDirections,
2856
+ getSelectionRowLimits,
2857
+ setCurrent,
2858
+ ]
2859
+ );
2860
+
2861
+ const adjustSelectionOnScroll = React.useCallback(() => {
2862
+ const args = hoveredRef.current;
2863
+ if (args === undefined) return;
2864
+ const [xDir, yDir] = args.scrollEdge;
2865
+ let [col, row] = args.location;
2866
+ const visible = visibleRegionRef.current;
2867
+ if (xDir === -1) {
2868
+ col = visible.extras?.freezeRegion?.x ?? visible.x;
2869
+ } else if (xDir === 1) {
2870
+ col = visible.x + visible.width;
2871
+ }
2872
+ if (yDir === -1) {
2873
+ row = Math.max(0, visible.y);
2874
+ } else if (yDir === 1) {
2875
+ row = Math.min(rows - 1, visible.y + visible.height);
2876
+ }
2877
+ col = clamp(col, 0, mangledCols.length - 1);
2878
+ row = clamp(row, 0, rows - 1);
2879
+ onItemHoveredImpl({
2880
+ ...args,
2881
+ location: [col, row] as any,
2882
+ });
2883
+ }, [mangledCols.length, onItemHoveredImpl, rows]);
2884
+
2885
+ useAutoscroll(scrollDir, scrollRef, adjustSelectionOnScroll);
2886
+
2887
+ // 1 === move one
2888
+ // 2 === move to end
2889
+ const adjustSelection = React.useCallback(
2890
+ (direction: [0 | 1 | -1 | 2 | -2, 0 | 1 | -1 | 2 | -2]) => {
2891
+ if (gridSelection.current === undefined) return;
2892
+
2893
+ const [x, y] = direction;
2894
+ const [col, row] = gridSelection.current.cell;
2895
+ const old = gridSelection.current.range;
2896
+ let left = old.x;
2897
+ let right = old.x + old.width;
2898
+ let top = old.y;
2899
+ let bottom = old.y + old.height;
2900
+
2901
+ const [minRow, maxRowRaw] = getSelectionRowLimits(row) ?? [0, rows - 1];
2902
+ const maxRow = maxRowRaw + 1; // we need an inclusive value
2903
+
2904
+ // take care of vertical first in case new spans come in
2905
+ if (y !== 0) {
2906
+ switch (y) {
2907
+ case 2: {
2908
+ // go to end
2909
+ bottom = maxRow;
2910
+ top = row;
2911
+ scrollTo(0, bottom, "vertical");
2912
+
2913
+ break;
2914
+ }
2915
+ case -2: {
2916
+ // go to start
2917
+ top = minRow;
2918
+ bottom = row + 1;
2919
+ scrollTo(0, top, "vertical");
2920
+
2921
+ break;
2922
+ }
2923
+ case 1: {
2924
+ // motion down
2925
+ if (top < row) {
2926
+ top++;
2927
+ scrollTo(0, top, "vertical");
2928
+ } else {
2929
+ bottom = Math.min(maxRow, bottom + 1);
2930
+ scrollTo(0, bottom, "vertical");
2931
+ }
2932
+
2933
+ break;
2934
+ }
2935
+ case -1: {
2936
+ // motion up
2937
+ if (bottom > row + 1) {
2938
+ bottom--;
2939
+ scrollTo(0, bottom, "vertical");
2940
+ } else {
2941
+ top = Math.max(minRow, top - 1);
2942
+ scrollTo(0, top, "vertical");
2943
+ }
2944
+
2945
+ break;
2946
+ }
2947
+ default: {
2948
+ assertNever(y);
2949
+ }
2950
+ }
2951
+ }
2952
+
2953
+ if (x !== 0) {
2954
+ if (x === 2) {
2955
+ right = mangledCols.length;
2956
+ left = col;
2957
+ scrollTo(right - 1 - rowMarkerOffset, 0, "horizontal");
2958
+ } else if (x === -2) {
2959
+ left = rowMarkerOffset;
2960
+ right = col + 1;
2961
+ scrollTo(left - rowMarkerOffset, 0, "horizontal");
2962
+ } else {
2963
+ let disallowed: number[] = [];
2964
+ if (getCellsForSelection !== undefined) {
2965
+ const cells = getCellsForSelection(
2966
+ {
2967
+ x: left,
2968
+ y: top,
2969
+ width: right - left - rowMarkerOffset,
2970
+ height: bottom - top,
2971
+ },
2972
+ abortControllerRef.current.signal
2973
+ );
2974
+
2975
+ if (typeof cells === "object") {
2976
+ disallowed = getSpanStops(cells);
2977
+ }
2978
+ }
2979
+ if (x === 1) {
2980
+ // motion right
2981
+ let done = false;
2982
+ if (left < col) {
2983
+ if (disallowed.length > 0) {
2984
+ const target = range(left + 1, col + 1).find(
2985
+ n => !disallowed.includes(n - rowMarkerOffset)
2986
+ );
2987
+ if (target !== undefined) {
2988
+ left = target;
2989
+ done = true;
2990
+ }
2991
+ } else {
2992
+ left++;
2993
+ done = true;
2994
+ }
2995
+ if (done) scrollTo(left, 0, "horizontal");
2996
+ }
2997
+ if (!done) {
2998
+ right = Math.min(mangledCols.length, right + 1);
2999
+ scrollTo(right - 1 - rowMarkerOffset, 0, "horizontal");
3000
+ }
3001
+ } else if (x === -1) {
3002
+ // motion left
3003
+ let done = false;
3004
+ if (right > col + 1) {
3005
+ if (disallowed.length > 0) {
3006
+ const target = range(right - 1, col, -1).find(
3007
+ n => !disallowed.includes(n - rowMarkerOffset)
3008
+ );
3009
+ if (target !== undefined) {
3010
+ right = target;
3011
+ done = true;
3012
+ }
3013
+ } else {
3014
+ right--;
3015
+ done = true;
3016
+ }
3017
+ if (done) scrollTo(right - rowMarkerOffset, 0, "horizontal");
3018
+ }
3019
+ if (!done) {
3020
+ left = Math.max(rowMarkerOffset, left - 1);
3021
+ scrollTo(left - rowMarkerOffset, 0, "horizontal");
3022
+ }
3023
+ } else {
3024
+ assertNever(x);
3025
+ }
3026
+ }
3027
+ }
3028
+
3029
+ setCurrent(
3030
+ {
3031
+ cell: gridSelection.current.cell,
3032
+ range: {
3033
+ x: left,
3034
+ y: top,
3035
+ width: right - left,
3036
+ height: bottom - top,
3037
+ },
3038
+ },
3039
+ true,
3040
+ false,
3041
+ "keyboard-select"
3042
+ );
3043
+ },
3044
+ [
3045
+ getCellsForSelection,
3046
+ getSelectionRowLimits,
3047
+ gridSelection,
3048
+ mangledCols.length,
3049
+ rowMarkerOffset,
3050
+ rows,
3051
+ scrollTo,
3052
+ setCurrent,
3053
+ ]
3054
+ );
3055
+
3056
+ const scrollToActiveCellRef = React.useRef(scrollToActiveCell);
3057
+ scrollToActiveCellRef.current = scrollToActiveCell;
3058
+
3059
+ const updateSelectedCell = React.useCallback(
3060
+ (col: number, row: number, fromEditingTrailingRow: boolean, freeMove: boolean): boolean => {
3061
+ const rowMax = mangledRows - (fromEditingTrailingRow ? 0 : 1);
3062
+ col = clamp(col, rowMarkerOffset, columns.length - 1 + rowMarkerOffset);
3063
+ row = clamp(row, 0, rowMax);
3064
+
3065
+ const curCol = currentCell?.[0];
3066
+ const curRow = currentCell?.[1];
3067
+
3068
+ if (col === curCol && row === curRow) return false;
3069
+ if (freeMove && gridSelection.current !== undefined) {
3070
+ const newStack = [...gridSelection.current.rangeStack];
3071
+ if (gridSelection.current.range.width > 1 || gridSelection.current.range.height > 1) {
3072
+ newStack.push(gridSelection.current.range);
3073
+ }
3074
+ setGridSelection(
3075
+ {
3076
+ ...gridSelection,
3077
+ current: {
3078
+ cell: [col, row],
3079
+ range: { x: col, y: row, width: 1, height: 1 },
3080
+ rangeStack: newStack,
3081
+ },
3082
+ },
3083
+ true
3084
+ );
3085
+ } else {
3086
+ setCurrent(
3087
+ {
3088
+ cell: [col, row],
3089
+ range: { x: col, y: row, width: 1, height: 1 },
3090
+ },
3091
+ true,
3092
+ false,
3093
+ "keyboard-nav"
3094
+ );
3095
+ }
3096
+
3097
+ if (lastSent.current !== undefined && lastSent.current[0] === col && lastSent.current[1] === row) {
3098
+ lastSent.current = undefined;
3099
+ }
3100
+
3101
+ if (scrollToActiveCellRef.current) {
3102
+ scrollTo(col - rowMarkerOffset, row);
3103
+ }
3104
+
3105
+ return true;
3106
+ },
3107
+ [
3108
+ mangledRows,
3109
+ rowMarkerOffset,
3110
+ columns.length,
3111
+ currentCell,
3112
+ gridSelection,
3113
+ scrollTo,
3114
+ setGridSelection,
3115
+ setCurrent,
3116
+ ]
3117
+ );
3118
+
3119
+ const onFinishEditing = React.useCallback(
3120
+ (newValue: GridCell | undefined, movement: readonly [-1 | 0 | 1, -1 | 0 | 1]) => {
3121
+ if (overlay?.cell !== undefined && newValue !== undefined && isEditableGridCell(newValue)) {
3122
+ mangledOnCellsEdited([{ location: overlay.cell, value: newValue }]);
3123
+ window.requestAnimationFrame(() => {
3124
+ gridRef.current?.damage([
3125
+ {
3126
+ cell: overlay.cell,
3127
+ },
3128
+ ]);
3129
+ });
3130
+ }
3131
+ focus(true);
3132
+ setOverlay(undefined);
3133
+
3134
+ const [movX, movY] = movement;
3135
+ if (gridSelection.current !== undefined && (movX !== 0 || movY !== 0)) {
3136
+ const isEditingLastRow = gridSelection.current.cell[1] === mangledRows - 1 && newValue !== undefined;
3137
+ const isEditingLastCol =
3138
+ gridSelection.current.cell[0] === mangledCols.length - 1 && newValue !== undefined;
3139
+ let updateSelected = true;
3140
+ if (isEditingLastRow && movY === 1 && onRowAppended !== undefined) {
3141
+ updateSelected = false;
3142
+ const col = gridSelection.current.cell[0] + movX;
3143
+ const customTargetColumn = getCustomNewRowTargetColumn(col);
3144
+ void appendRow(customTargetColumn ?? col, false);
3145
+ }
3146
+ if (isEditingLastCol && movX === 1 && onColumnAppended !== undefined) {
3147
+ updateSelected = false;
3148
+ const row = gridSelection.current.cell[1] + movY;
3149
+ void appendColumn(row, false);
3150
+ }
3151
+ if (updateSelected) {
3152
+ updateSelectedCell(
3153
+ clamp(gridSelection.current.cell[0] + movX, 0, mangledCols.length - 1),
3154
+ clamp(gridSelection.current.cell[1] + movY, 0, mangledRows - 1),
3155
+ isEditingLastRow,
3156
+ false
3157
+ );
3158
+ }
3159
+ }
3160
+ onFinishedEditing?.(newValue, movement);
3161
+ },
3162
+ [
3163
+ overlay?.cell,
3164
+ focus,
3165
+ gridSelection,
3166
+ onFinishedEditing,
3167
+ mangledOnCellsEdited,
3168
+ mangledRows,
3169
+ updateSelectedCell,
3170
+ mangledCols.length,
3171
+ appendRow,
3172
+ appendColumn,
3173
+ onRowAppended,
3174
+ onColumnAppended,
3175
+ getCustomNewRowTargetColumn,
3176
+ ]
3177
+ );
3178
+
3179
+ const overlayID = React.useMemo(() => {
3180
+ return `gdg-overlay-${idCounter++}`;
3181
+ }, []);
3182
+
3183
+ const deleteRange = React.useCallback(
3184
+ (r: Rectangle) => {
3185
+ focus();
3186
+ const editList: EditListItem[] = [];
3187
+ for (let x = r.x; x < r.x + r.width; x++) {
3188
+ for (let y = r.y; y < r.y + r.height; y++) {
3189
+ const cellValue = getCellContent([x - rowMarkerOffset, y]);
3190
+ if (!cellValue.allowOverlay && cellValue.kind !== GridCellKind.Boolean) continue;
3191
+ let newVal: InnerGridCell | undefined = undefined;
3192
+ if (cellValue.kind === GridCellKind.Custom) {
3193
+ const toDelete = getCellRenderer(cellValue);
3194
+ const editor = toDelete?.provideEditor?.({
3195
+ ...cellValue,
3196
+ location: [x - rowMarkerOffset, y],
3197
+ });
3198
+ if (toDelete?.onDelete !== undefined) {
3199
+ newVal = toDelete.onDelete(cellValue);
3200
+ } else if (isObjectEditorCallbackResult(editor)) {
3201
+ newVal = editor?.deletedValue?.(cellValue);
3202
+ }
3203
+ } else if (
3204
+ (isEditableGridCell(cellValue) && cellValue.allowOverlay) ||
3205
+ cellValue.kind === GridCellKind.Boolean
3206
+ ) {
3207
+ const toDelete = getCellRenderer(cellValue);
3208
+ newVal = toDelete?.onDelete?.(cellValue);
3209
+ }
3210
+ if (newVal !== undefined && !isInnerOnlyCell(newVal) && isEditableGridCell(newVal)) {
3211
+ editList.push({ location: [x, y], value: newVal });
3212
+ }
3213
+ }
3214
+ }
3215
+ mangledOnCellsEdited(editList);
3216
+ gridRef.current?.damage(editList.map(x => ({ cell: x.location })));
3217
+ },
3218
+ [focus, getCellContent, getCellRenderer, mangledOnCellsEdited, rowMarkerOffset]
3219
+ );
3220
+
3221
+ const overlayOpen = overlay !== undefined;
3222
+
3223
+ const handleFixedKeybindings = React.useCallback(
3224
+ (event: GridKeyEventArgs): boolean => {
3225
+ const cancel = () => {
3226
+ event.stopPropagation();
3227
+ event.preventDefault();
3228
+ };
3229
+
3230
+ const details = {
3231
+ didMatch: false,
3232
+ };
3233
+
3234
+ const { bounds } = event;
3235
+ const selectedColumns = gridSelection.columns;
3236
+ const selectedRows = gridSelection.rows;
3237
+
3238
+ const keys = keybindings;
3239
+
3240
+ if (!overlayOpen && isHotkey(keys.clear, event, details)) {
3241
+ setGridSelection(emptyGridSelection, false);
3242
+ onSelectionCleared?.();
3243
+ } else if (!overlayOpen && isHotkey(keys.selectAll, event, details)) {
3244
+ setGridSelection(
3245
+ {
3246
+ columns: CompactSelection.empty(),
3247
+ rows: CompactSelection.empty(),
3248
+ current: {
3249
+ cell: gridSelection.current?.cell ?? [rowMarkerOffset, 0],
3250
+ range: {
3251
+ x: rowMarkerOffset,
3252
+ y: 0,
3253
+ width: columnsIn.length,
3254
+ height: rows,
3255
+ },
3256
+ rangeStack: [],
3257
+ },
3258
+ },
3259
+ false
3260
+ );
3261
+ } else if (isHotkey(keys.search, event, details)) {
3262
+ searchInputRef?.current?.focus({ preventScroll: true });
3263
+ setShowSearchInner(true);
3264
+ } else if (isHotkey(keys.delete, event, details)) {
3265
+ const callbackResult = onDelete?.(gridSelection) ?? true;
3266
+ if (callbackResult !== false) {
3267
+ const toDelete = callbackResult === true ? gridSelection : callbackResult;
3268
+
3269
+ // delete order:
3270
+ // 1) primary range
3271
+ // 2) secondary ranges
3272
+ // 3) columns
3273
+ // 4) rows
3274
+
3275
+ if (toDelete.current !== undefined) {
3276
+ deleteRange(toDelete.current.range);
3277
+ for (const r of toDelete.current.rangeStack) {
3278
+ deleteRange(r);
3279
+ }
3280
+ }
3281
+
3282
+ for (const r of toDelete.rows) {
3283
+ deleteRange({
3284
+ x: rowMarkerOffset,
3285
+ y: r,
3286
+ width: columnsIn.length,
3287
+ height: 1,
3288
+ });
3289
+ }
3290
+
3291
+ for (const col of toDelete.columns) {
3292
+ deleteRange({
3293
+ x: col,
3294
+ y: 0,
3295
+ width: 1,
3296
+ height: rows,
3297
+ });
3298
+ }
3299
+ }
3300
+ }
3301
+
3302
+ if (details.didMatch) {
3303
+ cancel();
3304
+ return true;
3305
+ }
3306
+
3307
+ if (gridSelection.current === undefined) return false;
3308
+ let [col, row] = gridSelection.current.cell;
3309
+ const [, startRow] = gridSelection.current.cell;
3310
+ let freeMove = false;
3311
+ let cancelOnlyOnMove = false;
3312
+
3313
+ if (isHotkey(keys.scrollToSelectedCell, event, details)) {
3314
+ scrollToRef.current(col - rowMarkerOffset, row);
3315
+ } else if (columnSelect !== "none" && isHotkey(keys.selectColumn, event, details)) {
3316
+ if (selectedColumns.hasIndex(col)) {
3317
+ setSelectedColumns(selectedColumns.remove(col), undefined, true);
3318
+ } else {
3319
+ if (columnSelect === "single") {
3320
+ setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, true);
3321
+ } else {
3322
+ setSelectedColumns(undefined, col, true);
3323
+ }
3324
+ }
3325
+ } else if (rowSelect !== "none" && isHotkey(keys.selectRow, event, details)) {
3326
+ if (selectedRows.hasIndex(row)) {
3327
+ setSelectedRows(selectedRows.remove(row), undefined, true);
3328
+ } else {
3329
+ if (rowSelect === "single") {
3330
+ setSelectedRows(CompactSelection.fromSingleSelection(row), undefined, true);
3331
+ } else {
3332
+ setSelectedRows(undefined, row, true);
3333
+ }
3334
+ }
3335
+ } else if (!overlayOpen && bounds !== undefined && isHotkey(keys.activateCell, event, details)) {
3336
+ if (row === rows && showTrailingBlankRow) {
3337
+ window.setTimeout(() => {
3338
+ const customTargetColumn = getCustomNewRowTargetColumn(col);
3339
+ void appendRow(customTargetColumn ?? col);
3340
+ }, 0);
3341
+ } else {
3342
+ const activationEvent: CellActivatedEventArgs = {
3343
+ inputType: "keyboard",
3344
+ key: event.key,
3345
+ };
3346
+ onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
3347
+ reselect(bounds, activationEvent);
3348
+ }
3349
+ } else if (gridSelection.current.range.height > 1 && isHotkey(keys.downFill, event, details)) {
3350
+ fillDown();
3351
+ } else if (gridSelection.current.range.width > 1 && isHotkey(keys.rightFill, event, details)) {
3352
+ fillRight();
3353
+ } else if (isHotkey(keys.goToNextPage, event, details)) {
3354
+ row += Math.max(1, visibleRegionRef.current.height - 4); // partial cell accounting
3355
+ } else if (isHotkey(keys.goToPreviousPage, event, details)) {
3356
+ row -= Math.max(1, visibleRegionRef.current.height - 4); // partial cell accounting
3357
+ } else if (isHotkey(keys.goToFirstCell, event, details)) {
3358
+ setOverlay(undefined);
3359
+ row = 0;
3360
+ col = 0;
3361
+ } else if (isHotkey(keys.goToLastCell, event, details)) {
3362
+ setOverlay(undefined);
3363
+ row = Number.MAX_SAFE_INTEGER;
3364
+ col = Number.MAX_SAFE_INTEGER;
3365
+ } else if (isHotkey(keys.selectToFirstCell, event, details)) {
3366
+ setOverlay(undefined);
3367
+ adjustSelection([-2, -2]);
3368
+ } else if (isHotkey(keys.selectToLastCell, event, details)) {
3369
+ setOverlay(undefined);
3370
+ adjustSelection([2, 2]);
3371
+ } else if (!overlayOpen) {
3372
+ if (isHotkey(keys.goDownCell, event, details)) {
3373
+ row += 1;
3374
+ } else if (isHotkey(keys.goUpCell, event, details)) {
3375
+ row -= 1;
3376
+ } else if (isHotkey(keys.goRightCell, event, details)) {
3377
+ col += 1;
3378
+ } else if (isHotkey(keys.goLeftCell, event, details)) {
3379
+ col -= 1;
3380
+ } else if (isHotkey(keys.goDownCellRetainSelection, event, details)) {
3381
+ row += 1;
3382
+ freeMove = true;
3383
+ } else if (isHotkey(keys.goUpCellRetainSelection, event, details)) {
3384
+ row -= 1;
3385
+ freeMove = true;
3386
+ } else if (isHotkey(keys.goRightCellRetainSelection, event, details)) {
3387
+ col += 1;
3388
+ freeMove = true;
3389
+ } else if (isHotkey(keys.goLeftCellRetainSelection, event, details)) {
3390
+ col -= 1;
3391
+ freeMove = true;
3392
+ } else if (isHotkey(keys.goToLastRow, event, details)) {
3393
+ row = rows - 1;
3394
+ } else if (isHotkey(keys.goToFirstRow, event, details)) {
3395
+ row = Number.MIN_SAFE_INTEGER;
3396
+ } else if (isHotkey(keys.goToLastColumn, event, details)) {
3397
+ col = Number.MAX_SAFE_INTEGER;
3398
+ } else if (isHotkey(keys.goToFirstColumn, event, details)) {
3399
+ col = Number.MIN_SAFE_INTEGER;
3400
+ } else if (rangeSelect === "rect" || rangeSelect === "multi-rect") {
3401
+ if (isHotkey(keys.selectGrowDown, event, details)) {
3402
+ adjustSelection([0, 1]);
3403
+ } else if (isHotkey(keys.selectGrowUp, event, details)) {
3404
+ adjustSelection([0, -1]);
3405
+ } else if (isHotkey(keys.selectGrowRight, event, details)) {
3406
+ adjustSelection([1, 0]);
3407
+ } else if (isHotkey(keys.selectGrowLeft, event, details)) {
3408
+ adjustSelection([-1, 0]);
3409
+ } else if (isHotkey(keys.selectToLastRow, event, details)) {
3410
+ adjustSelection([0, 2]);
3411
+ } else if (isHotkey(keys.selectToFirstRow, event, details)) {
3412
+ adjustSelection([0, -2]);
3413
+ } else if (isHotkey(keys.selectToLastColumn, event, details)) {
3414
+ adjustSelection([2, 0]);
3415
+ } else if (isHotkey(keys.selectToFirstColumn, event, details)) {
3416
+ adjustSelection([-2, 0]);
3417
+ }
3418
+ }
3419
+ cancelOnlyOnMove = details.didMatch;
3420
+ } else {
3421
+ if (isHotkey(keys.closeOverlay, event, details)) {
3422
+ setOverlay(undefined);
3423
+ }
3424
+
3425
+ if (isHotkey(keys.acceptOverlayDown, event, details)) {
3426
+ setOverlay(undefined);
3427
+ row++;
3428
+ }
3429
+
3430
+ if (isHotkey(keys.acceptOverlayUp, event, details)) {
3431
+ setOverlay(undefined);
3432
+ row--;
3433
+ }
3434
+
3435
+ if (isHotkey(keys.acceptOverlayLeft, event, details)) {
3436
+ setOverlay(undefined);
3437
+ col--;
3438
+ }
3439
+
3440
+ if (isHotkey(keys.acceptOverlayRight, event, details)) {
3441
+ setOverlay(undefined);
3442
+ col++;
3443
+ }
3444
+ }
3445
+ // #endregion
3446
+
3447
+ const mustRestrictRow = rowGroupingNavBehavior !== undefined && rowGroupingNavBehavior !== "normal";
3448
+
3449
+ if (mustRestrictRow && row !== startRow) {
3450
+ const skipUp =
3451
+ rowGroupingNavBehavior === "skip-up" ||
3452
+ rowGroupingNavBehavior === "skip" ||
3453
+ rowGroupingNavBehavior === "block";
3454
+ const skipDown =
3455
+ rowGroupingNavBehavior === "skip-down" ||
3456
+ rowGroupingNavBehavior === "skip" ||
3457
+ rowGroupingNavBehavior === "block";
3458
+ const didMoveUp = row < startRow;
3459
+ if (didMoveUp && skipUp) {
3460
+ while (row >= 0 && mapper(row).isGroupHeader) {
3461
+ row--;
3462
+ }
3463
+
3464
+ if (row < 0) {
3465
+ row = startRow;
3466
+ }
3467
+ } else if (!didMoveUp && skipDown) {
3468
+ while (row < rows && mapper(row).isGroupHeader) {
3469
+ row++;
3470
+ }
3471
+
3472
+ if (row >= rows) {
3473
+ row = startRow;
3474
+ }
3475
+ }
3476
+ }
3477
+
3478
+ const moved = updateSelectedCell(col, row, false, freeMove);
3479
+
3480
+ const didMatch = details.didMatch;
3481
+
3482
+ if (didMatch && (moved || !cancelOnlyOnMove || trapFocus)) {
3483
+ cancel();
3484
+ }
3485
+
3486
+ return didMatch;
3487
+ },
3488
+ [
3489
+ rowGroupingNavBehavior,
3490
+ overlayOpen,
3491
+ gridSelection,
3492
+ keybindings,
3493
+ columnSelect,
3494
+ rowSelect,
3495
+ rangeSelect,
3496
+ rowMarkerOffset,
3497
+ mapper,
3498
+ rows,
3499
+ updateSelectedCell,
3500
+ setGridSelection,
3501
+ onSelectionCleared,
3502
+ columnsIn.length,
3503
+ onDelete,
3504
+ trapFocus,
3505
+ deleteRange,
3506
+ setSelectedColumns,
3507
+ setSelectedRows,
3508
+ showTrailingBlankRow,
3509
+ getCustomNewRowTargetColumn,
3510
+ appendRow,
3511
+ onCellActivated,
3512
+ reselect,
3513
+ fillDown,
3514
+ fillRight,
3515
+ adjustSelection,
3516
+ ]
3517
+ );
3518
+
3519
+ const onKeyDown = React.useCallback(
3520
+ (event: GridKeyEventArgs) => {
3521
+ let cancelled = false;
3522
+ if (onKeyDownIn !== undefined) {
3523
+ onKeyDownIn({
3524
+ ...event,
3525
+ ...(event.location && {
3526
+ location: [event.location[0] - rowMarkerOffset, event.location[1]] as any,
3527
+ }),
3528
+ cancel: () => {
3529
+ cancelled = true;
3530
+ },
3531
+ });
3532
+ }
3533
+
3534
+ if (cancelled) return;
3535
+
3536
+ if (handleFixedKeybindings(event)) return;
3537
+
3538
+ if (gridSelection.current === undefined) return;
3539
+ const [col, row] = gridSelection.current.cell;
3540
+ const vr = visibleRegionRef.current;
3541
+
3542
+ if (
3543
+ editOnType &&
3544
+ !event.metaKey &&
3545
+ !event.ctrlKey &&
3546
+ gridSelection.current !== undefined &&
3547
+ event.key.length === 1 &&
3548
+ /[\p{L}\p{M}\p{N}\p{S}\p{P}]/u.test(event.key) &&
3549
+ event.bounds !== undefined &&
3550
+ isReadWriteCell(getCellContent([col - rowMarkerOffset, Math.max(0, Math.min(row, rows - 1))]))
3551
+ ) {
3552
+ if (
3553
+ (!showTrailingBlankRow || row !== rows) &&
3554
+ (vr.y > row || row > vr.y + vr.height || vr.x > col || col > vr.x + vr.width)
3555
+ ) {
3556
+ return;
3557
+ }
3558
+ const activationEvent: CellActivatedEventArgs = {
3559
+ inputType: "keyboard",
3560
+ key: event.key,
3561
+ };
3562
+ onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
3563
+ reselect(event.bounds, activationEvent, event.key);
3564
+ event.stopPropagation();
3565
+ event.preventDefault();
3566
+ }
3567
+ },
3568
+ [
3569
+ editOnType,
3570
+ onKeyDownIn,
3571
+ handleFixedKeybindings,
3572
+ gridSelection,
3573
+ getCellContent,
3574
+ rowMarkerOffset,
3575
+ rows,
3576
+ showTrailingBlankRow,
3577
+ onCellActivated,
3578
+ reselect,
3579
+ ]
3580
+ );
3581
+
3582
+ const onContextMenu = React.useCallback(
3583
+ (args: GridMouseEventArgs, preventDefault: () => void) => {
3584
+ const adjustedCol = args.location[0] - rowMarkerOffset;
3585
+ if (args.kind === "header") {
3586
+ onHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3587
+ }
3588
+
3589
+ if (args.kind === groupHeaderKind) {
3590
+ if (adjustedCol < 0) {
3591
+ return;
3592
+ }
3593
+ onGroupHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3594
+ }
3595
+
3596
+ if (args.kind === "cell") {
3597
+ const [col, row] = args.location;
3598
+ onCellContextMenu?.([adjustedCol, row], {
3599
+ ...args,
3600
+ preventDefault,
3601
+ });
3602
+
3603
+ if (!gridSelectionHasItem(gridSelection, args.location)) {
3604
+ updateSelectedCell(col, row, false, false);
3605
+ }
3606
+ }
3607
+ },
3608
+ [
3609
+ gridSelection,
3610
+ onCellContextMenu,
3611
+ onGroupHeaderContextMenu,
3612
+ onHeaderContextMenu,
3613
+ rowMarkerOffset,
3614
+ updateSelectedCell,
3615
+ ]
3616
+ );
3617
+
3618
+ const onPasteInternal = React.useCallback(
3619
+ async (e?: ClipboardEvent) => {
3620
+ if (!keybindings.paste) return;
3621
+ function pasteToCell(
3622
+ inner: InnerGridCell,
3623
+ target: Item,
3624
+ rawValue: string | boolean | string[] | number | boolean | BooleanEmpty | BooleanIndeterminate,
3625
+ formatted?: string | string[]
3626
+ ): EditListItem | undefined {
3627
+ const stringifiedRawValue =
3628
+ typeof rawValue === "object" ? (rawValue?.join("\n") ?? "") : (rawValue?.toString() ?? "");
3629
+
3630
+ if (!isInnerOnlyCell(inner) && isReadWriteCell(inner) && inner.readonly !== true) {
3631
+ const coerced = coercePasteValue?.(stringifiedRawValue, inner);
3632
+ if (coerced !== undefined && isEditableGridCell(coerced)) {
3633
+ if (process.env.NODE_ENV !== "production" && coerced.kind !== inner.kind) {
3634
+ // eslint-disable-next-line no-console
3635
+ console.warn("Coercion should not change cell kind.");
3636
+ }
3637
+ return {
3638
+ location: target,
3639
+ value: coerced,
3640
+ };
3641
+ }
3642
+ const r = getCellRenderer(inner);
3643
+ if (r === undefined) return undefined;
3644
+ if (r.kind === GridCellKind.Custom) {
3645
+ assert(inner.kind === GridCellKind.Custom);
3646
+ const newVal = (r as unknown as CustomRenderer<CustomCell<any>>).onPaste?.(
3647
+ stringifiedRawValue,
3648
+ inner.data
3649
+ );
3650
+ if (newVal === undefined) return undefined;
3651
+ return {
3652
+ location: target,
3653
+ value: {
3654
+ ...inner,
3655
+ data: newVal,
3656
+ },
3657
+ };
3658
+ } else {
3659
+ const newVal = r.onPaste?.(stringifiedRawValue, inner, {
3660
+ formatted,
3661
+ formattedString: typeof formatted === "string" ? formatted : formatted?.join("\n"),
3662
+ rawValue,
3663
+ });
3664
+ if (newVal === undefined) return undefined;
3665
+ assert(newVal.kind === inner.kind);
3666
+ return {
3667
+ location: target,
3668
+ value: newVal,
3669
+ };
3670
+ }
3671
+ }
3672
+ return undefined;
3673
+ }
3674
+
3675
+ const selectedColumns = gridSelection.columns;
3676
+ const selectedRows = gridSelection.rows;
3677
+ const focused =
3678
+ scrollRef.current?.contains(document.activeElement) === true ||
3679
+ canvasRef.current?.contains(document.activeElement) === true;
3680
+
3681
+ let target: Item | undefined;
3682
+
3683
+ if (gridSelection.current !== undefined) {
3684
+ target = [gridSelection.current.range.x, gridSelection.current.range.y];
3685
+ } else if (selectedColumns.length === 1) {
3686
+ target = [selectedColumns.first() ?? 0, 0];
3687
+ } else if (selectedRows.length === 1) {
3688
+ target = [rowMarkerOffset, selectedRows.first() ?? 0];
3689
+ }
3690
+
3691
+ if (focused && target !== undefined) {
3692
+ let data: CopyBuffer | undefined;
3693
+ let text: string | undefined;
3694
+
3695
+ const textPlain = "text/plain";
3696
+ const textHtml = "text/html";
3697
+
3698
+ if (navigator.clipboard.read !== undefined) {
3699
+ const clipboardContent = await navigator.clipboard.read();
3700
+
3701
+ for (const item of clipboardContent) {
3702
+ if (item.types.includes(textHtml)) {
3703
+ const htmlBlob = await item.getType(textHtml);
3704
+ const html = await htmlBlob.text();
3705
+ const decoded = decodeHTML(html);
3706
+ if (decoded !== undefined) {
3707
+ data = decoded;
3708
+ break;
3709
+ }
3710
+ }
3711
+ if (item.types.includes(textPlain)) {
3712
+ // eslint-disable-next-line unicorn/no-await-expression-member
3713
+ text = await (await item.getType(textPlain)).text();
3714
+ }
3715
+ }
3716
+ } else if (navigator.clipboard.readText !== undefined) {
3717
+ text = await navigator.clipboard.readText();
3718
+ } else if (e !== undefined && e?.clipboardData !== null) {
3719
+ if (e.clipboardData.types.includes(textHtml)) {
3720
+ const html = e.clipboardData.getData(textHtml);
3721
+ data = decodeHTML(html);
3722
+ }
3723
+ if (data === undefined && e.clipboardData.types.includes(textPlain)) {
3724
+ text = e.clipboardData.getData(textPlain);
3725
+ }
3726
+ } else {
3727
+ return; // I didn't want to read that paste value anyway
3728
+ }
3729
+
3730
+ const [targetCol, targetRow] = target;
3731
+
3732
+ const editList: EditListItem[] = [];
3733
+ do {
3734
+ if (onPaste === undefined) {
3735
+ const cellData = getMangledCellContent(target);
3736
+ const rawValue = text ?? data?.map(r => r.map(cb => cb.rawValue).join("\t")).join("\t") ?? "";
3737
+ const newVal = pasteToCell(cellData, target, rawValue, undefined);
3738
+ if (newVal !== undefined) {
3739
+ editList.push(newVal);
3740
+ }
3741
+ break;
3742
+ }
3743
+
3744
+ if (data === undefined) {
3745
+ if (text === undefined) return;
3746
+ data = unquote(text);
3747
+ }
3748
+
3749
+ if (
3750
+ onPaste === false ||
3751
+ (typeof onPaste === "function" &&
3752
+ onPaste?.(
3753
+ [target[0] - rowMarkerOffset, target[1]],
3754
+ data.map(r => r.map(cb => cb.rawValue?.toString() ?? ""))
3755
+ ) !== true)
3756
+ ) {
3757
+ return;
3758
+ }
3759
+
3760
+ for (const [row, dataRow] of data.entries()) {
3761
+ if (row + targetRow >= rows) break;
3762
+ for (const [col, dataItem] of dataRow.entries()) {
3763
+ const index = [col + targetCol, row + targetRow] as const;
3764
+ const [writeCol, writeRow] = index;
3765
+ if (writeCol >= mangledCols.length) continue;
3766
+ if (writeRow >= mangledRows) continue;
3767
+ const cellData = getMangledCellContent(index);
3768
+ const newVal = pasteToCell(cellData, index, dataItem.rawValue, dataItem.formatted);
3769
+ if (newVal !== undefined) {
3770
+ editList.push(newVal);
3771
+ }
3772
+ }
3773
+ }
3774
+ // eslint-disable-next-line no-constant-condition
3775
+ } while (false);
3776
+
3777
+ mangledOnCellsEdited(editList);
3778
+
3779
+ gridRef.current?.damage(
3780
+ editList.map(c => ({
3781
+ cell: c.location,
3782
+ }))
3783
+ );
3784
+ }
3785
+ },
3786
+ [
3787
+ coercePasteValue,
3788
+ getCellRenderer,
3789
+ getMangledCellContent,
3790
+ gridSelection,
3791
+ keybindings.paste,
3792
+ scrollRef,
3793
+ mangledCols.length,
3794
+ mangledOnCellsEdited,
3795
+ mangledRows,
3796
+ onPaste,
3797
+ rowMarkerOffset,
3798
+ rows,
3799
+ ]
3800
+ );
3801
+
3802
+ useEventListener("paste", onPasteInternal, safeWindow, false, true);
3803
+
3804
+ // While this function is async, we deeply prefer not to await if we don't have to. This will lead to unpacking
3805
+ // promises in rather awkward ways when possible to avoid awaiting. We have to use fallback copy mechanisms when
3806
+ // an await has happened.
3807
+ const onCopy = React.useCallback(
3808
+ async (e?: ClipboardEvent, ignoreFocus?: boolean) => {
3809
+ if (!keybindings.copy) return;
3810
+ const focused =
3811
+ ignoreFocus === true ||
3812
+ scrollRef.current?.contains(document.activeElement) === true ||
3813
+ canvasRef.current?.contains(document.activeElement) === true;
3814
+
3815
+ const selectedColumns = gridSelection.columns;
3816
+ const selectedRows = gridSelection.rows;
3817
+
3818
+ const copyToClipboardWithHeaders = (
3819
+ cells: readonly (readonly GridCell[])[],
3820
+ columnIndexes: readonly number[]
3821
+ ) => {
3822
+ if (!copyHeaders) {
3823
+ copyToClipboard(cells, columnIndexes, e);
3824
+ } else {
3825
+ const headers = columnIndexes.map(index => ({
3826
+ kind: GridCellKind.Text,
3827
+ data: columnsIn[index].title,
3828
+ displayData: columnsIn[index].title,
3829
+ allowOverlay: false,
3830
+ })) as GridCell[];
3831
+ copyToClipboard([headers, ...cells], columnIndexes, e);
3832
+ }
3833
+ };
3834
+
3835
+ if (focused && getCellsForSelection !== undefined) {
3836
+ if (gridSelection.current !== undefined) {
3837
+ let thunk = getCellsForSelection(gridSelection.current.range, abortControllerRef.current.signal);
3838
+ if (typeof thunk !== "object") {
3839
+ thunk = await thunk();
3840
+ }
3841
+ copyToClipboardWithHeaders(
3842
+ thunk,
3843
+ range(
3844
+ gridSelection.current.range.x - rowMarkerOffset,
3845
+ gridSelection.current.range.x + gridSelection.current.range.width - rowMarkerOffset
3846
+ )
3847
+ );
3848
+ } else if (selectedRows !== undefined && selectedRows.length > 0) {
3849
+ const toCopy = [...selectedRows];
3850
+ const cells = toCopy.map(rowIndex => {
3851
+ const thunk = getCellsForSelection(
3852
+ {
3853
+ x: rowMarkerOffset,
3854
+ y: rowIndex,
3855
+ width: columnsIn.length,
3856
+ height: 1,
3857
+ },
3858
+ abortControllerRef.current.signal
3859
+ );
3860
+ if (typeof thunk === "object") {
3861
+ return thunk[0];
3862
+ }
3863
+ return thunk().then(v => v[0]);
3864
+ });
3865
+ if (cells.some(x => x instanceof Promise)) {
3866
+ const settled = await Promise.all(cells);
3867
+ copyToClipboardWithHeaders(settled, range(columnsIn.length));
3868
+ } else {
3869
+ copyToClipboardWithHeaders(cells as (readonly GridCell[])[], range(columnsIn.length));
3870
+ }
3871
+ } else if (selectedColumns.length > 0) {
3872
+ const results: (readonly (readonly GridCell[])[])[] = [];
3873
+ const cols: number[] = [];
3874
+ for (const col of selectedColumns) {
3875
+ let thunk = getCellsForSelection(
3876
+ {
3877
+ x: col,
3878
+ y: 0,
3879
+ width: 1,
3880
+ height: rows,
3881
+ },
3882
+ abortControllerRef.current.signal
3883
+ );
3884
+ if (typeof thunk !== "object") {
3885
+ thunk = await thunk();
3886
+ }
3887
+ results.push(thunk);
3888
+ cols.push(col - rowMarkerOffset);
3889
+ }
3890
+ if (results.length === 1) {
3891
+ copyToClipboardWithHeaders(results[0], cols);
3892
+ } else {
3893
+ // FIXME: this is dumb
3894
+ const toCopy = results.reduce((pv, cv) => pv.map((row, index) => [...row, ...cv[index]]));
3895
+ copyToClipboardWithHeaders(toCopy, cols);
3896
+ }
3897
+ }
3898
+ }
3899
+ },
3900
+ [
3901
+ columnsIn,
3902
+ getCellsForSelection,
3903
+ gridSelection,
3904
+ keybindings.copy,
3905
+ rowMarkerOffset,
3906
+ scrollRef,
3907
+ rows,
3908
+ copyHeaders,
3909
+ ]
3910
+ );
3911
+
3912
+ useEventListener("copy", onCopy, safeWindow, false, false);
3913
+
3914
+ const onCut = React.useCallback(
3915
+ async (e?: ClipboardEvent) => {
3916
+ if (!keybindings.cut) return;
3917
+ const focused =
3918
+ scrollRef.current?.contains(document.activeElement) === true ||
3919
+ canvasRef.current?.contains(document.activeElement) === true;
3920
+
3921
+ if (!focused) return;
3922
+ await onCopy(e);
3923
+ if (gridSelection.current !== undefined) {
3924
+ let effectiveSelection: GridSelection = {
3925
+ current: {
3926
+ cell: gridSelection.current.cell,
3927
+ range: gridSelection.current.range,
3928
+ rangeStack: [],
3929
+ },
3930
+ rows: CompactSelection.empty(),
3931
+ columns: CompactSelection.empty(),
3932
+ };
3933
+ const onDeleteResult = onDelete?.(effectiveSelection);
3934
+ if (onDeleteResult === false) return;
3935
+ effectiveSelection = onDeleteResult === true ? effectiveSelection : onDeleteResult;
3936
+ if (effectiveSelection.current === undefined) return;
3937
+ deleteRange(effectiveSelection.current.range);
3938
+ }
3939
+ },
3940
+ [deleteRange, gridSelection, keybindings.cut, onCopy, scrollRef, onDelete]
3941
+ );
3942
+
3943
+ useEventListener("cut", onCut, safeWindow, false, false);
3944
+
3945
+ const onSearchResultsChanged = React.useCallback(
3946
+ (results: readonly Item[], navIndex: number) => {
3947
+ if (onSearchResultsChangedIn !== undefined) {
3948
+ if (rowMarkerOffset !== 0) {
3949
+ results = results.map(item => [item[0] - rowMarkerOffset, item[1]]);
3950
+ }
3951
+ onSearchResultsChangedIn(results, navIndex);
3952
+ return;
3953
+ }
3954
+ if (results.length === 0 || navIndex === -1) return;
3955
+
3956
+ const [col, row] = results[navIndex];
3957
+ if (lastSent.current !== undefined && lastSent.current[0] === col && lastSent.current[1] === row) {
3958
+ return;
3959
+ }
3960
+ lastSent.current = [col, row];
3961
+ updateSelectedCell(col, row, false, false);
3962
+ },
3963
+ [onSearchResultsChangedIn, rowMarkerOffset, updateSelectedCell]
3964
+ );
3965
+
3966
+ // this effects purpose in life is to scroll the newly selected cell into view when and ONLY when that cell
3967
+ // is from an external gridSelection change. Also note we want the unmangled out selection because scrollTo
3968
+ // expects unmangled indexes
3969
+ const [outCol, outRow] = gridSelectionOuter?.current?.cell ?? [];
3970
+ const scrollToRef = React.useRef(scrollTo);
3971
+ scrollToRef.current = scrollTo;
3972
+ React.useLayoutEffect(() => {
3973
+ if (
3974
+ scrollToActiveCellRef.current &&
3975
+ !hasJustScrolled.current &&
3976
+ outCol !== undefined &&
3977
+ outRow !== undefined &&
3978
+ (outCol !== expectedExternalGridSelection.current?.current?.cell[0] ||
3979
+ outRow !== expectedExternalGridSelection.current?.current?.cell[1])
3980
+ ) {
3981
+ scrollToRef.current(outCol, outRow);
3982
+ }
3983
+ hasJustScrolled.current = false; //only allow skipping a single scroll
3984
+ }, [outCol, outRow]);
3985
+
3986
+ const selectionOutOfBounds =
3987
+ gridSelection.current !== undefined &&
3988
+ (gridSelection.current.cell[0] >= mangledCols.length || gridSelection.current.cell[1] >= mangledRows);
3989
+ React.useLayoutEffect(() => {
3990
+ if (selectionOutOfBounds) {
3991
+ setGridSelection(emptyGridSelection, false);
3992
+ }
3993
+ }, [selectionOutOfBounds, setGridSelection]);
3994
+
3995
+ const disabledRows = React.useMemo(() => {
3996
+ if (showTrailingBlankRow === true && trailingRowOptions?.tint === true) {
3997
+ return CompactSelection.fromSingleSelection(mangledRows - 1);
3998
+ }
3999
+ return CompactSelection.empty();
4000
+ }, [mangledRows, showTrailingBlankRow, trailingRowOptions?.tint]);
4001
+
4002
+ const mangledVerticalBorder = React.useCallback(
4003
+ (col: number) => {
4004
+ return typeof verticalBorder === "boolean"
4005
+ ? verticalBorder
4006
+ : (verticalBorder?.(col - rowMarkerOffset) ?? true);
4007
+ },
4008
+ [rowMarkerOffset, verticalBorder]
4009
+ );
4010
+
4011
+ const renameGroupNode = React.useMemo(() => {
4012
+ if (renameGroup === undefined || canvasRef.current === null) return null;
4013
+ const { bounds, group } = renameGroup;
4014
+ const canvasBounds = canvasRef.current.getBoundingClientRect();
4015
+ return (
4016
+ <GroupRename
4017
+ bounds={bounds}
4018
+ group={group}
4019
+ canvasBounds={canvasBounds}
4020
+ onClose={() => setRenameGroup(undefined)}
4021
+ onFinish={(newVal: string) => {
4022
+ setRenameGroup(undefined);
4023
+ onGroupHeaderRenamed?.(group, newVal);
4024
+ }}
4025
+ />
4026
+ );
4027
+ }, [onGroupHeaderRenamed, renameGroup]);
4028
+
4029
+ const mangledFreezeColumns = Math.min(mangledCols.length, freezeColumns + (hasRowMarkers ? 1 : 0));
4030
+
4031
+ React.useImperativeHandle(
4032
+ forwardedRef,
4033
+ () => ({
4034
+ appendRow: (col: number, openOverlay?: boolean) => appendRow(col + rowMarkerOffset, openOverlay),
4035
+ appendColumn: (row: number, openOverlay?: boolean) => appendColumn(row, openOverlay),
4036
+ updateCells: damageList => {
4037
+ if (rowMarkerOffset !== 0) {
4038
+ damageList = damageList.map(x => ({ cell: [x.cell[0] + rowMarkerOffset, x.cell[1]] }));
4039
+ }
4040
+ return gridRef.current?.damage(damageList);
4041
+ },
4042
+ getBounds: (col, row) => {
4043
+ if (canvasRef?.current === null || scrollRef?.current === null) {
4044
+ return undefined;
4045
+ }
4046
+
4047
+ if (col === undefined && row === undefined) {
4048
+ // Return the bounds of the entire scroll area:
4049
+ const rect = canvasRef.current.getBoundingClientRect();
4050
+ const scale = rect.width / scrollRef.current.clientWidth;
4051
+ return {
4052
+ x: rect.x - scrollRef.current.scrollLeft * scale,
4053
+ y: rect.y - scrollRef.current.scrollTop * scale,
4054
+ width: scrollRef.current.scrollWidth * scale,
4055
+ height: scrollRef.current.scrollHeight * scale,
4056
+ };
4057
+ }
4058
+ return gridRef.current?.getBounds((col ?? 0) + rowMarkerOffset, row);
4059
+ },
4060
+ focus: () => gridRef.current?.focus(),
4061
+ emit: async e => {
4062
+ switch (e) {
4063
+ case "delete":
4064
+ onKeyDown({
4065
+ bounds: undefined,
4066
+ cancel: () => undefined,
4067
+ stopPropagation: () => undefined,
4068
+ preventDefault: () => undefined,
4069
+ ctrlKey: false,
4070
+ key: "Delete",
4071
+ keyCode: 46,
4072
+ metaKey: false,
4073
+ shiftKey: false,
4074
+ altKey: false,
4075
+ rawEvent: undefined,
4076
+ location: undefined,
4077
+ });
4078
+ break;
4079
+ case "fill-right":
4080
+ onKeyDown({
4081
+ bounds: undefined,
4082
+ cancel: () => undefined,
4083
+ stopPropagation: () => undefined,
4084
+ preventDefault: () => undefined,
4085
+ ctrlKey: true,
4086
+ key: "r",
4087
+ keyCode: 82,
4088
+ metaKey: false,
4089
+ shiftKey: false,
4090
+ altKey: false,
4091
+ rawEvent: undefined,
4092
+ location: undefined,
4093
+ });
4094
+ break;
4095
+ case "fill-down":
4096
+ onKeyDown({
4097
+ bounds: undefined,
4098
+ cancel: () => undefined,
4099
+ stopPropagation: () => undefined,
4100
+ preventDefault: () => undefined,
4101
+ ctrlKey: true,
4102
+ key: "d",
4103
+ keyCode: 68,
4104
+ metaKey: false,
4105
+ shiftKey: false,
4106
+ altKey: false,
4107
+ rawEvent: undefined,
4108
+ location: undefined,
4109
+ });
4110
+ break;
4111
+ case "copy":
4112
+ await onCopy(undefined, true);
4113
+ break;
4114
+ case "paste":
4115
+ await onPasteInternal();
4116
+ break;
4117
+ }
4118
+ },
4119
+ scrollTo,
4120
+ remeasureColumns: cols => {
4121
+ for (const col of cols) {
4122
+ void normalSizeColumn(col + rowMarkerOffset);
4123
+ }
4124
+ },
4125
+ getMouseArgsForPosition: (
4126
+ posX: number,
4127
+ posY: number,
4128
+ ev?: MouseEvent | TouchEvent
4129
+ ): GridMouseEventArgs | undefined => {
4130
+ if (gridRef?.current === null) {
4131
+ return undefined;
4132
+ }
4133
+
4134
+ const args = gridRef.current.getMouseArgsForPosition(posX, posY, ev);
4135
+ if (args === undefined) {
4136
+ return undefined;
4137
+ }
4138
+
4139
+ return {
4140
+ ...args,
4141
+ location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
4142
+ };
4143
+ },
4144
+ }),
4145
+ [
4146
+ appendRow,
4147
+ appendColumn,
4148
+ normalSizeColumn,
4149
+ scrollRef,
4150
+ onCopy,
4151
+ onKeyDown,
4152
+ onPasteInternal,
4153
+ rowMarkerOffset,
4154
+ scrollTo,
4155
+ ]
4156
+ );
4157
+
4158
+ const [selCol, selRow] = currentCell ?? [];
4159
+ const onCellFocused = React.useCallback(
4160
+ (cell: Item) => {
4161
+ const [col, row] = cell;
4162
+
4163
+ if (row === -1) {
4164
+ if (columnSelect !== "none") {
4165
+ setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, false);
4166
+ focus();
4167
+ }
4168
+ return;
4169
+ }
4170
+
4171
+ if (selCol === col && selRow === row) return;
4172
+ setCurrent(
4173
+ {
4174
+ cell,
4175
+ range: { x: col, y: row, width: 1, height: 1 },
4176
+ },
4177
+ true,
4178
+ false,
4179
+ "keyboard-nav"
4180
+ );
4181
+ scrollTo(col, row);
4182
+ },
4183
+ [columnSelect, focus, scrollTo, selCol, selRow, setCurrent, setSelectedColumns]
4184
+ );
4185
+
4186
+ const [isFocused, setIsFocused] = React.useState(false);
4187
+ const setIsFocusedDebounced = React.useRef(
4188
+ debounce((val: boolean) => {
4189
+ setIsFocused(val);
4190
+ }, 5)
4191
+ );
4192
+
4193
+ const onCanvasFocused = React.useCallback(() => {
4194
+ setIsFocusedDebounced.current(true);
4195
+
4196
+ // check for mouse state, don't do anything if the user is clicked to focus.
4197
+ if (
4198
+ gridSelection.current === undefined &&
4199
+ gridSelection.columns.length === 0 &&
4200
+ gridSelection.rows.length === 0 &&
4201
+ mouseState === undefined
4202
+ ) {
4203
+ setCurrent(
4204
+ {
4205
+ cell: [rowMarkerOffset, cellYOffset],
4206
+ range: {
4207
+ x: rowMarkerOffset,
4208
+ y: cellYOffset,
4209
+ width: 1,
4210
+ height: 1,
4211
+ },
4212
+ },
4213
+ true,
4214
+ false,
4215
+ "keyboard-select"
4216
+ );
4217
+ }
4218
+ }, [cellYOffset, gridSelection, mouseState, rowMarkerOffset, setCurrent]);
4219
+
4220
+ const onFocusOut = React.useCallback(() => {
4221
+ setIsFocusedDebounced.current(false);
4222
+ }, []);
4223
+
4224
+ const [idealWidth, idealHeight] = React.useMemo(() => {
4225
+ let h: number;
4226
+ const scrollbarWidth = experimental?.scrollbarWidthOverride ?? getScrollBarWidth();
4227
+ const rowsCountWithTrailingRow = rows + (showTrailingBlankRow ? 1 : 0);
4228
+ if (typeof rowHeight === "number") {
4229
+ h = totalHeaderHeight + rowsCountWithTrailingRow * rowHeight;
4230
+ } else {
4231
+ let avg = 0;
4232
+ const toAverage = Math.min(rowsCountWithTrailingRow, 10);
4233
+ for (let i = 0; i < toAverage; i++) {
4234
+ avg += rowHeight(i);
4235
+ }
4236
+ avg = Math.floor(avg / toAverage);
4237
+
4238
+ h = totalHeaderHeight + rowsCountWithTrailingRow * avg;
4239
+ }
4240
+ h += scrollbarWidth;
4241
+
4242
+ const w = mangledCols.reduce((acc, x) => x.width + acc, 0) + scrollbarWidth;
4243
+
4244
+ // We need to set a reasonable cap here as some browsers will just ignore huge values
4245
+ // rather than treat them as huge values.
4246
+ return [`${Math.min(100_000, w)}px`, `${Math.min(100_000, h)}px`];
4247
+ }, [mangledCols, experimental?.scrollbarWidthOverride, rowHeight, rows, showTrailingBlankRow, totalHeaderHeight]);
4248
+
4249
+ const cssStyle = React.useMemo(() => {
4250
+ return makeCSSStyle(mergedTheme);
4251
+ }, [mergedTheme]);
4252
+
4253
+ return (
4254
+ <ThemeContext.Provider value={mergedTheme}>
4255
+ <DataEditorContainer
4256
+ style={cssStyle}
4257
+ className={className}
4258
+ inWidth={width ?? idealWidth}
4259
+ inHeight={height ?? idealHeight}>
4260
+ <DataGridSearch
4261
+ fillHandle={fillHandle}
4262
+ drawFocusRing={drawFocusRing}
4263
+ experimental={experimental}
4264
+ fixedShadowX={fixedShadowX}
4265
+ fixedShadowY={fixedShadowY}
4266
+ getRowThemeOverride={getRowThemeOverride}
4267
+ headerIcons={headerIcons}
4268
+ imageWindowLoader={imageWindowLoader}
4269
+ initialSize={initialSize}
4270
+ isDraggable={isDraggable}
4271
+ onDragLeave={onDragLeave}
4272
+ onRowMoved={onRowMoved}
4273
+ overscrollX={overscrollX}
4274
+ overscrollY={overscrollY}
4275
+ preventDiagonalScrolling={preventDiagonalScrolling}
4276
+ rightElement={rightElement}
4277
+ rightElementProps={rightElementProps}
4278
+ smoothScrollX={smoothScrollX}
4279
+ smoothScrollY={smoothScrollY}
4280
+ className={className}
4281
+ enableGroups={enableGroups}
4282
+ onCanvasFocused={onCanvasFocused}
4283
+ onCanvasBlur={onFocusOut}
4284
+ canvasRef={canvasRef}
4285
+ onContextMenu={onContextMenu}
4286
+ theme={mergedTheme}
4287
+ cellXOffset={cellXOffset}
4288
+ cellYOffset={cellYOffset}
4289
+ accessibilityHeight={visibleRegion.height}
4290
+ onDragEnd={onDragEnd}
4291
+ columns={mangledCols}
4292
+ nonGrowWidth={nonGrowWidth}
4293
+ drawHeader={drawHeader}
4294
+ drawGroupHeader={drawGroupHeaderIn}
4295
+ onColumnProposeMove={onColumnProposeMoveImpl}
4296
+ drawCell={drawCell}
4297
+ disabledRows={disabledRows}
4298
+ freezeColumns={mangledFreezeColumns}
4299
+ lockColumns={rowMarkerOffset}
4300
+ firstColAccessible={rowMarkerOffset === 0}
4301
+ getCellContent={getMangledCellContent}
4302
+ minColumnWidth={minColumnWidth}
4303
+ maxColumnWidth={maxColumnWidth}
4304
+ searchInputRef={searchInputRef}
4305
+ showSearch={showSearch}
4306
+ onSearchClose={onSearchClose}
4307
+ highlightRegions={highlightRegions}
4308
+ getCellsForSelection={getCellsForSelection}
4309
+ getGroupDetails={mangledGetGroupDetails}
4310
+ headerHeight={headerHeight}
4311
+ isFocused={isFocused}
4312
+ groupHeaderHeight={enableGroups ? groupHeaderHeight : 0}
4313
+ freezeTrailingRows={
4314
+ freezeTrailingRows + (showTrailingBlankRow && trailingRowOptions?.sticky === true ? 1 : 0)
4315
+ }
4316
+ hasAppendRow={showTrailingBlankRow}
4317
+ onColumnResize={onColumnResize}
4318
+ onColumnResizeEnd={onColumnResizeEnd}
4319
+ onColumnResizeStart={onColumnResizeStart}
4320
+ onCellFocused={onCellFocused}
4321
+ onColumnMoved={onColumnMovedImpl}
4322
+ onDragStart={onDragStartImpl}
4323
+ onHeaderMenuClick={onHeaderMenuClickInner}
4324
+ onHeaderIndicatorClick={onHeaderIndicatorClickInner}
4325
+ onItemHovered={onItemHoveredImpl}
4326
+ isFilling={mouseState?.fillHandle === true}
4327
+ onMouseMove={onMouseMoveImpl}
4328
+ onKeyDown={onKeyDown}
4329
+ onKeyUp={onKeyUpIn}
4330
+ onMouseDown={onMouseDown}
4331
+ onMouseUp={onMouseUp}
4332
+ onDragOverCell={onDragOverCell}
4333
+ onDrop={onDrop}
4334
+ onSearchResultsChanged={onSearchResultsChanged}
4335
+ onVisibleRegionChanged={onVisibleRegionChangedImpl}
4336
+ clientSize={clientSize}
4337
+ rowHeight={rowHeight}
4338
+ searchResults={searchResults}
4339
+ searchValue={searchValue}
4340
+ onSearchValueChange={onSearchValueChange}
4341
+ rows={mangledRows}
4342
+ scrollRef={scrollRef}
4343
+ selection={gridSelection}
4344
+ translateX={visibleRegion.tx}
4345
+ translateY={visibleRegion.ty}
4346
+ verticalBorder={mangledVerticalBorder}
4347
+ gridRef={gridRef}
4348
+ getCellRenderer={getCellRenderer}
4349
+ resizeIndicator={resizeIndicator}
4350
+ setScrollDir={setScrollDir}
4351
+ />
4352
+ {renameGroupNode}
4353
+ {overlay !== undefined && (
4354
+ <React.Suspense fallback={null}>
4355
+ <DataGridOverlayEditor
4356
+ {...overlay}
4357
+ validateCell={validateCell}
4358
+ bloom={editorBloom}
4359
+ id={overlayID}
4360
+ getCellRenderer={getCellRenderer}
4361
+ className={experimental?.isSubGrid === true ? "click-outside-ignore" : undefined}
4362
+ provideEditor={provideEditor}
4363
+ imageEditorOverride={imageEditorOverride}
4364
+ portalElementRef={portalElementRef}
4365
+ onFinishEditing={onFinishEditing}
4366
+ markdownDivCreateNode={markdownDivCreateNode}
4367
+ isOutsideClick={isOutsideClick}
4368
+ customEventTarget={experimental?.eventTarget}
4369
+ />
4370
+ </React.Suspense>
4371
+ )}
4372
+ </DataEditorContainer>
4373
+ </ThemeContext.Provider>
4374
+ );
4375
+ };
4376
+
4377
+ /**
4378
+ * The primary component of Glide Data Grid.
4379
+ * @category DataEditor
4380
+ * @param {DataEditorProps} props
4381
+ */
4382
+ export const DataEditor = React.forwardRef(DataEditorImpl);