photon-grid-core 0.0.1

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 (555) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +0 -0
  3. package/cell-selection/active-grid-registry.d.ts +37 -0
  4. package/cell-selection/active-grid-registry.d.ts.map +1 -0
  5. package/cell-selection/active-grid-registry.js +25 -0
  6. package/cell-selection/active-grid-registry.js.map +1 -0
  7. package/cell-selection/cell-selection-engine.d.ts +336 -0
  8. package/cell-selection/cell-selection-engine.d.ts.map +1 -0
  9. package/cell-selection/cell-selection-engine.js +1534 -0
  10. package/cell-selection/cell-selection-engine.js.map +1 -0
  11. package/cell-selection/selection-range.d.ts +16 -0
  12. package/cell-selection/selection-range.d.ts.map +1 -0
  13. package/cell-selection/selection-range.js +48 -0
  14. package/cell-selection/selection-range.js.map +1 -0
  15. package/cell-selection/selection-renderer.d.ts +24 -0
  16. package/cell-selection/selection-renderer.d.ts.map +1 -0
  17. package/cell-selection/selection-renderer.js +107 -0
  18. package/cell-selection/selection-renderer.js.map +1 -0
  19. package/chart/chart-analyzer/aggregation-analyzer.d.ts +18 -0
  20. package/chart/chart-analyzer/aggregation-analyzer.d.ts.map +1 -0
  21. package/chart/chart-analyzer/aggregation-analyzer.js +63 -0
  22. package/chart/chart-analyzer/aggregation-analyzer.js.map +1 -0
  23. package/chart/chart-analyzer/bar-analyzer.d.ts +19 -0
  24. package/chart/chart-analyzer/bar-analyzer.d.ts.map +1 -0
  25. package/chart/chart-analyzer/bar-analyzer.js +56 -0
  26. package/chart/chart-analyzer/bar-analyzer.js.map +1 -0
  27. package/chart/chart-analyzer/bubble-analyzer.d.ts +16 -0
  28. package/chart/chart-analyzer/bubble-analyzer.d.ts.map +1 -0
  29. package/chart/chart-analyzer/bubble-analyzer.js +61 -0
  30. package/chart/chart-analyzer/bubble-analyzer.js.map +1 -0
  31. package/chart/chart-analyzer/cardinality-analyzer.d.ts +13 -0
  32. package/chart/chart-analyzer/cardinality-analyzer.d.ts.map +1 -0
  33. package/chart/chart-analyzer/cardinality-analyzer.js +28 -0
  34. package/chart/chart-analyzer/cardinality-analyzer.js.map +1 -0
  35. package/chart/chart-analyzer/dimension-analyzer.d.ts +12 -0
  36. package/chart/chart-analyzer/dimension-analyzer.d.ts.map +1 -0
  37. package/chart/chart-analyzer/dimension-analyzer.js +33 -0
  38. package/chart/chart-analyzer/dimension-analyzer.js.map +1 -0
  39. package/chart/chart-analyzer/histogram-analyzer.d.ts +11 -0
  40. package/chart/chart-analyzer/histogram-analyzer.d.ts.map +1 -0
  41. package/chart/chart-analyzer/histogram-analyzer.js +43 -0
  42. package/chart/chart-analyzer/histogram-analyzer.js.map +1 -0
  43. package/chart/chart-analyzer/index.d.ts +58 -0
  44. package/chart/chart-analyzer/index.d.ts.map +1 -0
  45. package/chart/chart-analyzer/index.js +130 -0
  46. package/chart/chart-analyzer/index.js.map +1 -0
  47. package/chart/chart-analyzer/line-analyzer.d.ts +23 -0
  48. package/chart/chart-analyzer/line-analyzer.d.ts.map +1 -0
  49. package/chart/chart-analyzer/line-analyzer.js +61 -0
  50. package/chart/chart-analyzer/line-analyzer.js.map +1 -0
  51. package/chart/chart-analyzer/measure-analyzer.d.ts +11 -0
  52. package/chart/chart-analyzer/measure-analyzer.d.ts.map +1 -0
  53. package/chart/chart-analyzer/measure-analyzer.js +44 -0
  54. package/chart/chart-analyzer/measure-analyzer.js.map +1 -0
  55. package/chart/chart-analyzer/pie-analyzer.d.ts +19 -0
  56. package/chart/chart-analyzer/pie-analyzer.d.ts.map +1 -0
  57. package/chart/chart-analyzer/pie-analyzer.js +64 -0
  58. package/chart/chart-analyzer/pie-analyzer.js.map +1 -0
  59. package/chart/chart-analyzer/recommendation-engine.d.ts +18 -0
  60. package/chart/chart-analyzer/recommendation-engine.d.ts.map +1 -0
  61. package/chart/chart-analyzer/recommendation-engine.js +82 -0
  62. package/chart/chart-analyzer/recommendation-engine.js.map +1 -0
  63. package/chart/chart-analyzer/scatter-analyzer.d.ts +16 -0
  64. package/chart/chart-analyzer/scatter-analyzer.d.ts.map +1 -0
  65. package/chart/chart-analyzer/scatter-analyzer.js +70 -0
  66. package/chart/chart-analyzer/scatter-analyzer.js.map +1 -0
  67. package/chart/chart-analyzer/types.d.ts +52 -0
  68. package/chart/chart-analyzer/types.d.ts.map +1 -0
  69. package/chart/chart-analyzer/types.js +10 -0
  70. package/chart/chart-analyzer/types.js.map +1 -0
  71. package/chart/chart-analyzer.d.ts +12 -0
  72. package/chart/chart-analyzer.d.ts.map +1 -0
  73. package/chart/chart-analyzer.js +77 -0
  74. package/chart/chart-analyzer.js.map +1 -0
  75. package/chart/chart-data-transformer.d.ts +26 -0
  76. package/chart/chart-data-transformer.d.ts.map +1 -0
  77. package/chart/chart-data-transformer.js +93 -0
  78. package/chart/chart-data-transformer.js.map +1 -0
  79. package/chart/chart-engine.d.ts +38 -0
  80. package/chart/chart-engine.d.ts.map +1 -0
  81. package/chart/chart-engine.js +96 -0
  82. package/chart/chart-engine.js.map +1 -0
  83. package/chart/chart-panel.d.ts +54 -0
  84. package/chart/chart-panel.d.ts.map +1 -0
  85. package/chart/chart-panel.js +350 -0
  86. package/chart/chart-panel.js.map +1 -0
  87. package/chart/chart-renderer.d.ts +93 -0
  88. package/chart/chart-renderer.d.ts.map +1 -0
  89. package/chart/chart-renderer.js +1330 -0
  90. package/chart/chart-renderer.js.map +1 -0
  91. package/chart/sparkline/sparkline-renderer.d.ts +269 -0
  92. package/chart/sparkline/sparkline-renderer.d.ts.map +1 -0
  93. package/chart/sparkline/sparkline-renderer.js +875 -0
  94. package/chart/sparkline/sparkline-renderer.js.map +1 -0
  95. package/chart/sparkline/sparkline.types.d.ts +214 -0
  96. package/chart/sparkline/sparkline.types.d.ts.map +1 -0
  97. package/chart/sparkline/sparkline.types.js +8 -0
  98. package/chart/sparkline/sparkline.types.js.map +1 -0
  99. package/column-groups/column-group-drag-handler.d.ts +169 -0
  100. package/column-groups/column-group-drag-handler.d.ts.map +1 -0
  101. package/column-groups/column-group-drag-handler.js +517 -0
  102. package/column-groups/column-group-drag-handler.js.map +1 -0
  103. package/column-groups/column-group-header-builder.d.ts +106 -0
  104. package/column-groups/column-group-header-builder.d.ts.map +1 -0
  105. package/column-groups/column-group-header-builder.js +311 -0
  106. package/column-groups/column-group-header-builder.js.map +1 -0
  107. package/column-groups/column-group-model.d.ts +248 -0
  108. package/column-groups/column-group-model.d.ts.map +1 -0
  109. package/column-groups/column-group-model.js +668 -0
  110. package/column-groups/column-group-model.js.map +1 -0
  111. package/column-groups/column-group-state-manager.d.ts +110 -0
  112. package/column-groups/column-group-state-manager.d.ts.map +1 -0
  113. package/column-groups/column-group-state-manager.js +153 -0
  114. package/column-groups/column-group-state-manager.js.map +1 -0
  115. package/column-groups/column-group.types.d.ts +250 -0
  116. package/column-groups/column-group.types.d.ts.map +1 -0
  117. package/column-groups/column-group.types.js +20 -0
  118. package/column-groups/column-group.types.js.map +1 -0
  119. package/column-groups/display-group-builder.d.ts +91 -0
  120. package/column-groups/display-group-builder.d.ts.map +1 -0
  121. package/column-groups/display-group-builder.js +225 -0
  122. package/column-groups/display-group-builder.js.map +1 -0
  123. package/column-groups/display-group-drag-handler.d.ts +205 -0
  124. package/column-groups/display-group-drag-handler.d.ts.map +1 -0
  125. package/column-groups/display-group-drag-handler.js +536 -0
  126. package/column-groups/display-group-drag-handler.js.map +1 -0
  127. package/column-groups/display-group-engine.d.ts +310 -0
  128. package/column-groups/display-group-engine.d.ts.map +1 -0
  129. package/column-groups/display-group-engine.js +549 -0
  130. package/column-groups/display-group-engine.js.map +1 -0
  131. package/column-groups/display-group-header-builder.d.ts +94 -0
  132. package/column-groups/display-group-header-builder.d.ts.map +1 -0
  133. package/column-groups/display-group-header-builder.js +224 -0
  134. package/column-groups/display-group-header-builder.js.map +1 -0
  135. package/column-groups/display-group.types.d.ts +140 -0
  136. package/column-groups/display-group.types.d.ts.map +1 -0
  137. package/column-groups/display-group.types.js +2 -0
  138. package/column-groups/display-group.types.js.map +1 -0
  139. package/column-groups/index.d.ts +25 -0
  140. package/column-groups/index.d.ts.map +1 -0
  141. package/column-groups/index.js +26 -0
  142. package/column-groups/index.js.map +1 -0
  143. package/column-groups/logical-group-registry.d.ts +77 -0
  144. package/column-groups/logical-group-registry.d.ts.map +1 -0
  145. package/column-groups/logical-group-registry.js +118 -0
  146. package/column-groups/logical-group-registry.js.map +1 -0
  147. package/core/column-model.d.ts +30 -0
  148. package/core/column-model.d.ts.map +1 -0
  149. package/core/column-model.js +203 -0
  150. package/core/column-model.js.map +1 -0
  151. package/core/grid-api.d.ts +187 -0
  152. package/core/grid-api.d.ts.map +1 -0
  153. package/core/grid-api.js +569 -0
  154. package/core/grid-api.js.map +1 -0
  155. package/core/grid-context.d.ts +61 -0
  156. package/core/grid-context.d.ts.map +1 -0
  157. package/core/grid-context.js +2 -0
  158. package/core/grid-context.js.map +1 -0
  159. package/core/grid-core.d.ts +49 -0
  160. package/core/grid-core.d.ts.map +1 -0
  161. package/core/grid-core.js +675 -0
  162. package/core/grid-core.js.map +1 -0
  163. package/core/grid-store.d.ts +62 -0
  164. package/core/grid-store.d.ts.map +1 -0
  165. package/core/grid-store.js +104 -0
  166. package/core/grid-store.js.map +1 -0
  167. package/core/row-model.d.ts +32 -0
  168. package/core/row-model.d.ts.map +1 -0
  169. package/core/row-model.js +150 -0
  170. package/core/row-model.js.map +1 -0
  171. package/drag-drop/drag-autoscroll.d.ts +14 -0
  172. package/drag-drop/drag-autoscroll.d.ts.map +1 -0
  173. package/drag-drop/drag-autoscroll.js +66 -0
  174. package/drag-drop/drag-autoscroll.js.map +1 -0
  175. package/drag-drop/drag-drop-engine.d.ts +55 -0
  176. package/drag-drop/drag-drop-engine.d.ts.map +1 -0
  177. package/drag-drop/drag-drop-engine.js +179 -0
  178. package/drag-drop/drag-drop-engine.js.map +1 -0
  179. package/drag-drop/drag-preview.d.ts +32 -0
  180. package/drag-drop/drag-preview.d.ts.map +1 -0
  181. package/drag-drop/drag-preview.js +74 -0
  182. package/drag-drop/drag-preview.js.map +1 -0
  183. package/engines/aggregation/aggregation-engine.d.ts +66 -0
  184. package/engines/aggregation/aggregation-engine.d.ts.map +1 -0
  185. package/engines/aggregation/aggregation-engine.js +149 -0
  186. package/engines/aggregation/aggregation-engine.js.map +1 -0
  187. package/engines/clipboard/clipboard-engine.d.ts +89 -0
  188. package/engines/clipboard/clipboard-engine.d.ts.map +1 -0
  189. package/engines/clipboard/clipboard-engine.js +323 -0
  190. package/engines/clipboard/clipboard-engine.js.map +1 -0
  191. package/engines/editing/cell-editor-engine.d.ts +62 -0
  192. package/engines/editing/cell-editor-engine.d.ts.map +1 -0
  193. package/engines/editing/cell-editor-engine.js +359 -0
  194. package/engines/editing/cell-editor-engine.js.map +1 -0
  195. package/engines/editing/custom-dropdown-editor.d.ts +99 -0
  196. package/engines/editing/custom-dropdown-editor.d.ts.map +1 -0
  197. package/engines/editing/custom-dropdown-editor.js +355 -0
  198. package/engines/editing/custom-dropdown-editor.js.map +1 -0
  199. package/engines/editing/value-parser.d.ts +12 -0
  200. package/engines/editing/value-parser.d.ts.map +1 -0
  201. package/engines/editing/value-parser.js +125 -0
  202. package/engines/editing/value-parser.js.map +1 -0
  203. package/engines/export/export-engine.d.ts +16 -0
  204. package/engines/export/export-engine.d.ts.map +1 -0
  205. package/engines/export/export-engine.js +99 -0
  206. package/engines/export/export-engine.js.map +1 -0
  207. package/engines/filter/filter-condition.d.ts +7 -0
  208. package/engines/filter/filter-condition.d.ts.map +1 -0
  209. package/engines/filter/filter-condition.js +126 -0
  210. package/engines/filter/filter-condition.js.map +1 -0
  211. package/engines/filter/filter-engine.d.ts +35 -0
  212. package/engines/filter/filter-engine.d.ts.map +1 -0
  213. package/engines/filter/filter-engine.js +150 -0
  214. package/engines/filter/filter-engine.js.map +1 -0
  215. package/engines/filter/filter-panel.d.ts +103 -0
  216. package/engines/filter/filter-panel.d.ts.map +1 -0
  217. package/engines/filter/filter-panel.js +645 -0
  218. package/engines/filter/filter-panel.js.map +1 -0
  219. package/engines/grouping/group-node.d.ts +21 -0
  220. package/engines/grouping/group-node.d.ts.map +1 -0
  221. package/engines/grouping/group-node.js +96 -0
  222. package/engines/grouping/group-node.js.map +1 -0
  223. package/engines/grouping/grouping-engine.d.ts +33 -0
  224. package/engines/grouping/grouping-engine.d.ts.map +1 -0
  225. package/engines/grouping/grouping-engine.js +118 -0
  226. package/engines/grouping/grouping-engine.js.map +1 -0
  227. package/engines/master-detail/master-detail-engine.d.ts +77 -0
  228. package/engines/master-detail/master-detail-engine.d.ts.map +1 -0
  229. package/engines/master-detail/master-detail-engine.js +199 -0
  230. package/engines/master-detail/master-detail-engine.js.map +1 -0
  231. package/engines/pagination/pagination-engine.d.ts +33 -0
  232. package/engines/pagination/pagination-engine.d.ts.map +1 -0
  233. package/engines/pagination/pagination-engine.js +110 -0
  234. package/engines/pagination/pagination-engine.js.map +1 -0
  235. package/engines/selection/row-selection-engine.d.ts +26 -0
  236. package/engines/selection/row-selection-engine.d.ts.map +1 -0
  237. package/engines/selection/row-selection-engine.js +145 -0
  238. package/engines/selection/row-selection-engine.js.map +1 -0
  239. package/engines/sort/sort-comparator.d.ts +43 -0
  240. package/engines/sort/sort-comparator.d.ts.map +1 -0
  241. package/engines/sort/sort-comparator.js +113 -0
  242. package/engines/sort/sort-comparator.js.map +1 -0
  243. package/engines/sort/sort-engine.d.ts +67 -0
  244. package/engines/sort/sort-engine.d.ts.map +1 -0
  245. package/engines/sort/sort-engine.js +178 -0
  246. package/engines/sort/sort-engine.js.map +1 -0
  247. package/engines/summary/summary-engine.d.ts +17 -0
  248. package/engines/summary/summary-engine.d.ts.map +1 -0
  249. package/engines/summary/summary-engine.js +72 -0
  250. package/engines/summary/summary-engine.js.map +1 -0
  251. package/engines/tree/tree-data-service.d.ts +117 -0
  252. package/engines/tree/tree-data-service.d.ts.map +1 -0
  253. package/engines/tree/tree-data-service.js +296 -0
  254. package/engines/tree/tree-data-service.js.map +1 -0
  255. package/engines/tree/tree-expansion-service.d.ts +35 -0
  256. package/engines/tree/tree-expansion-service.d.ts.map +1 -0
  257. package/engines/tree/tree-expansion-service.js +84 -0
  258. package/engines/tree/tree-expansion-service.js.map +1 -0
  259. package/engines/tree/tree-node.d.ts +40 -0
  260. package/engines/tree/tree-node.d.ts.map +1 -0
  261. package/engines/tree/tree-node.js +199 -0
  262. package/engines/tree/tree-node.js.map +1 -0
  263. package/engines/tree/tree-selection-service.d.ts +35 -0
  264. package/engines/tree/tree-selection-service.d.ts.map +1 -0
  265. package/engines/tree/tree-selection-service.js +72 -0
  266. package/engines/tree/tree-selection-service.js.map +1 -0
  267. package/engines/undo-redo/undo-redo-engine.d.ts +114 -0
  268. package/engines/undo-redo/undo-redo-engine.d.ts.map +1 -0
  269. package/engines/undo-redo/undo-redo-engine.js +165 -0
  270. package/engines/undo-redo/undo-redo-engine.js.map +1 -0
  271. package/event-bus/event-bus.d.ts +21 -0
  272. package/event-bus/event-bus.d.ts.map +1 -0
  273. package/event-bus/event-bus.js +106 -0
  274. package/event-bus/event-bus.js.map +1 -0
  275. package/icons/icon-registry.d.ts +19 -0
  276. package/icons/icon-registry.d.ts.map +1 -0
  277. package/icons/icon-registry.js +37 -0
  278. package/icons/icon-registry.js.map +1 -0
  279. package/icons/icon-renderer.d.ts +18 -0
  280. package/icons/icon-renderer.d.ts.map +1 -0
  281. package/icons/icon-renderer.js +75 -0
  282. package/icons/icon-renderer.js.map +1 -0
  283. package/icons/icon-sets/core-icons.d.ts +2 -0
  284. package/icons/icon-sets/core-icons.d.ts.map +1 -0
  285. package/icons/icon-sets/core-icons.js +48 -0
  286. package/icons/icon-sets/core-icons.js.map +1 -0
  287. package/index.d.ts +72 -0
  288. package/index.d.ts.map +1 -0
  289. package/index.js +54 -0
  290. package/index.js.map +1 -0
  291. package/package.json +64 -0
  292. package/photon-ai/builtins/filter.commands.d.ts +4 -0
  293. package/photon-ai/builtins/filter.commands.d.ts.map +1 -0
  294. package/photon-ai/builtins/filter.commands.js +189 -0
  295. package/photon-ai/builtins/filter.commands.js.map +1 -0
  296. package/photon-ai/builtins/grouping.commands.d.ts +4 -0
  297. package/photon-ai/builtins/grouping.commands.d.ts.map +1 -0
  298. package/photon-ai/builtins/grouping.commands.js +115 -0
  299. package/photon-ai/builtins/grouping.commands.js.map +1 -0
  300. package/photon-ai/builtins/index.d.ts +10 -0
  301. package/photon-ai/builtins/index.d.ts.map +1 -0
  302. package/photon-ai/builtins/index.js +26 -0
  303. package/photon-ai/builtins/index.js.map +1 -0
  304. package/photon-ai/builtins/info.commands.d.ts +4 -0
  305. package/photon-ai/builtins/info.commands.d.ts.map +1 -0
  306. package/photon-ai/builtins/info.commands.js +201 -0
  307. package/photon-ai/builtins/info.commands.js.map +1 -0
  308. package/photon-ai/builtins/move.commands.d.ts +4 -0
  309. package/photon-ai/builtins/move.commands.d.ts.map +1 -0
  310. package/photon-ai/builtins/move.commands.js +53 -0
  311. package/photon-ai/builtins/move.commands.js.map +1 -0
  312. package/photon-ai/builtins/pin.commands.d.ts +4 -0
  313. package/photon-ai/builtins/pin.commands.d.ts.map +1 -0
  314. package/photon-ai/builtins/pin.commands.js +106 -0
  315. package/photon-ai/builtins/pin.commands.js.map +1 -0
  316. package/photon-ai/builtins/selection.commands.d.ts +4 -0
  317. package/photon-ai/builtins/selection.commands.d.ts.map +1 -0
  318. package/photon-ai/builtins/selection.commands.js +102 -0
  319. package/photon-ai/builtins/selection.commands.js.map +1 -0
  320. package/photon-ai/builtins/sort.commands.d.ts +4 -0
  321. package/photon-ai/builtins/sort.commands.d.ts.map +1 -0
  322. package/photon-ai/builtins/sort.commands.js +67 -0
  323. package/photon-ai/builtins/sort.commands.js.map +1 -0
  324. package/photon-ai/builtins/visibility.commands.d.ts +4 -0
  325. package/photon-ai/builtins/visibility.commands.d.ts.map +1 -0
  326. package/photon-ai/builtins/visibility.commands.js +75 -0
  327. package/photon-ai/builtins/visibility.commands.js.map +1 -0
  328. package/photon-ai/command-builder.d.ts +21 -0
  329. package/photon-ai/command-builder.d.ts.map +1 -0
  330. package/photon-ai/command-builder.js +18 -0
  331. package/photon-ai/command-builder.js.map +1 -0
  332. package/photon-ai/command-executor.d.ts +15 -0
  333. package/photon-ai/command-executor.d.ts.map +1 -0
  334. package/photon-ai/command-executor.js +26 -0
  335. package/photon-ai/command-executor.js.map +1 -0
  336. package/photon-ai/entity-resolver.d.ts +121 -0
  337. package/photon-ai/entity-resolver.d.ts.map +1 -0
  338. package/photon-ai/entity-resolver.js +393 -0
  339. package/photon-ai/entity-resolver.js.map +1 -0
  340. package/photon-ai/fuzzy-match.d.ts +12 -0
  341. package/photon-ai/fuzzy-match.d.ts.map +1 -0
  342. package/photon-ai/fuzzy-match.js +37 -0
  343. package/photon-ai/fuzzy-match.js.map +1 -0
  344. package/photon-ai/index.d.ts +15 -0
  345. package/photon-ai/index.d.ts.map +1 -0
  346. package/photon-ai/index.js +13 -0
  347. package/photon-ai/index.js.map +1 -0
  348. package/photon-ai/intent-parser.d.ts +20 -0
  349. package/photon-ai/intent-parser.d.ts.map +1 -0
  350. package/photon-ai/intent-parser.js +52 -0
  351. package/photon-ai/intent-parser.js.map +1 -0
  352. package/photon-ai/photon-ai-memory.d.ts +42 -0
  353. package/photon-ai/photon-ai-memory.d.ts.map +1 -0
  354. package/photon-ai/photon-ai-memory.js +86 -0
  355. package/photon-ai/photon-ai-memory.js.map +1 -0
  356. package/photon-ai/photon-ai-panel.d.ts +54 -0
  357. package/photon-ai/photon-ai-panel.d.ts.map +1 -0
  358. package/photon-ai/photon-ai-panel.js +191 -0
  359. package/photon-ai/photon-ai-panel.js.map +1 -0
  360. package/photon-ai/photon-ai-registry.d.ts +25 -0
  361. package/photon-ai/photon-ai-registry.d.ts.map +1 -0
  362. package/photon-ai/photon-ai-registry.js +41 -0
  363. package/photon-ai/photon-ai-registry.js.map +1 -0
  364. package/photon-ai/photon-ai-service.d.ts +71 -0
  365. package/photon-ai/photon-ai-service.d.ts.map +1 -0
  366. package/photon-ai/photon-ai-service.js +165 -0
  367. package/photon-ai/photon-ai-service.js.map +1 -0
  368. package/photon-ai/photon-ai.types.d.ts +100 -0
  369. package/photon-ai/photon-ai.types.d.ts.map +1 -0
  370. package/photon-ai/photon-ai.types.js +2 -0
  371. package/photon-ai/photon-ai.types.js.map +1 -0
  372. package/photon-ai/query-splitter.d.ts +22 -0
  373. package/photon-ai/query-splitter.d.ts.map +1 -0
  374. package/photon-ai/query-splitter.js +81 -0
  375. package/photon-ai/query-splitter.js.map +1 -0
  376. package/photon-ai/text-normalizer.d.ts +33 -0
  377. package/photon-ai/text-normalizer.d.ts.map +1 -0
  378. package/photon-ai/text-normalizer.js +132 -0
  379. package/photon-ai/text-normalizer.js.map +1 -0
  380. package/renderer/auto-scroller.d.ts +78 -0
  381. package/renderer/auto-scroller.d.ts.map +1 -0
  382. package/renderer/auto-scroller.js +141 -0
  383. package/renderer/auto-scroller.js.map +1 -0
  384. package/renderer/body-renderer.d.ts +191 -0
  385. package/renderer/body-renderer.d.ts.map +1 -0
  386. package/renderer/body-renderer.js +806 -0
  387. package/renderer/body-renderer.js.map +1 -0
  388. package/renderer/cell-renderer.d.ts +26 -0
  389. package/renderer/cell-renderer.d.ts.map +1 -0
  390. package/renderer/cell-renderer.js +201 -0
  391. package/renderer/cell-renderer.js.map +1 -0
  392. package/renderer/column-menu.d.ts +285 -0
  393. package/renderer/column-menu.d.ts.map +1 -0
  394. package/renderer/column-menu.js +873 -0
  395. package/renderer/column-menu.js.map +1 -0
  396. package/renderer/column-style-manager.d.ts +38 -0
  397. package/renderer/column-style-manager.d.ts.map +1 -0
  398. package/renderer/column-style-manager.js +110 -0
  399. package/renderer/column-style-manager.js.map +1 -0
  400. package/renderer/detail-row-renderer.d.ts +153 -0
  401. package/renderer/detail-row-renderer.d.ts.map +1 -0
  402. package/renderer/detail-row-renderer.js +388 -0
  403. package/renderer/detail-row-renderer.js.map +1 -0
  404. package/renderer/dom-utils.d.ts +22 -0
  405. package/renderer/dom-utils.d.ts.map +1 -0
  406. package/renderer/dom-utils.js +132 -0
  407. package/renderer/dom-utils.js.map +1 -0
  408. package/renderer/footer-renderer.d.ts +25 -0
  409. package/renderer/footer-renderer.d.ts.map +1 -0
  410. package/renderer/footer-renderer.js +168 -0
  411. package/renderer/footer-renderer.js.map +1 -0
  412. package/renderer/grid-renderer.d.ts +295 -0
  413. package/renderer/grid-renderer.d.ts.map +1 -0
  414. package/renderer/grid-renderer.js +1456 -0
  415. package/renderer/grid-renderer.js.map +1 -0
  416. package/renderer/group-context-menu.d.ts +79 -0
  417. package/renderer/group-context-menu.d.ts.map +1 -0
  418. package/renderer/group-context-menu.js +238 -0
  419. package/renderer/group-context-menu.js.map +1 -0
  420. package/renderer/group-drop-zone.d.ts +56 -0
  421. package/renderer/group-drop-zone.d.ts.map +1 -0
  422. package/renderer/group-drop-zone.js +376 -0
  423. package/renderer/group-drop-zone.js.map +1 -0
  424. package/renderer/header-renderer.d.ts +264 -0
  425. package/renderer/header-renderer.d.ts.map +1 -0
  426. package/renderer/header-renderer.js +1321 -0
  427. package/renderer/header-renderer.js.map +1 -0
  428. package/renderer/overlay-renderer.d.ts +16 -0
  429. package/renderer/overlay-renderer.d.ts.map +1 -0
  430. package/renderer/overlay-renderer.js +64 -0
  431. package/renderer/overlay-renderer.js.map +1 -0
  432. package/renderer/renderer-resolver.d.ts +18 -0
  433. package/renderer/renderer-resolver.d.ts.map +1 -0
  434. package/renderer/renderer-resolver.js +18 -0
  435. package/renderer/renderer-resolver.js.map +1 -0
  436. package/renderer/row-animator.d.ts +72 -0
  437. package/renderer/row-animator.d.ts.map +1 -0
  438. package/renderer/row-animator.js +175 -0
  439. package/renderer/row-animator.js.map +1 -0
  440. package/renderer/row-drag-renderer.d.ts +58 -0
  441. package/renderer/row-drag-renderer.d.ts.map +1 -0
  442. package/renderer/row-drag-renderer.js +395 -0
  443. package/renderer/row-drag-renderer.js.map +1 -0
  444. package/renderer/row-offset-search.d.ts +17 -0
  445. package/renderer/row-offset-search.d.ts.map +1 -0
  446. package/renderer/row-offset-search.js +31 -0
  447. package/renderer/row-offset-search.js.map +1 -0
  448. package/renderer/row-position-sheet.d.ts +11 -0
  449. package/renderer/row-position-sheet.d.ts.map +1 -0
  450. package/renderer/row-position-sheet.js +29 -0
  451. package/renderer/row-position-sheet.js.map +1 -0
  452. package/renderer/scroll-controller.d.ts +58 -0
  453. package/renderer/scroll-controller.d.ts.map +1 -0
  454. package/renderer/scroll-controller.js +200 -0
  455. package/renderer/scroll-controller.js.map +1 -0
  456. package/renderer/sticky-row-tracker.d.ts +40 -0
  457. package/renderer/sticky-row-tracker.d.ts.map +1 -0
  458. package/renderer/sticky-row-tracker.js +63 -0
  459. package/renderer/sticky-row-tracker.js.map +1 -0
  460. package/renderer/tooltip-renderer.d.ts +36 -0
  461. package/renderer/tooltip-renderer.d.ts.map +1 -0
  462. package/renderer/tooltip-renderer.js +133 -0
  463. package/renderer/tooltip-renderer.js.map +1 -0
  464. package/renderer/tree-cell-renderer.d.ts +37 -0
  465. package/renderer/tree-cell-renderer.d.ts.map +1 -0
  466. package/renderer/tree-cell-renderer.js +75 -0
  467. package/renderer/tree-cell-renderer.js.map +1 -0
  468. package/renderer/tree-sticky-row-tracker.d.ts +62 -0
  469. package/renderer/tree-sticky-row-tracker.d.ts.map +1 -0
  470. package/renderer/tree-sticky-row-tracker.js +120 -0
  471. package/renderer/tree-sticky-row-tracker.js.map +1 -0
  472. package/renderer/virtual-scroll-renderer.d.ts +37 -0
  473. package/renderer/virtual-scroll-renderer.d.ts.map +1 -0
  474. package/renderer/virtual-scroll-renderer.js +133 -0
  475. package/renderer/virtual-scroll-renderer.js.map +1 -0
  476. package/styles/base-styles.d.ts +3 -0
  477. package/styles/base-styles.d.ts.map +1 -0
  478. package/styles/base-styles.js +3045 -0
  479. package/styles/base-styles.js.map +1 -0
  480. package/styles/themes/theme-alpine.d.ts +2 -0
  481. package/styles/themes/theme-alpine.d.ts.map +1 -0
  482. package/styles/themes/theme-alpine.js +120 -0
  483. package/styles/themes/theme-alpine.js.map +1 -0
  484. package/styles/themes/theme-balham.d.ts +2 -0
  485. package/styles/themes/theme-balham.d.ts.map +1 -0
  486. package/styles/themes/theme-balham.js +120 -0
  487. package/styles/themes/theme-balham.js.map +1 -0
  488. package/styles/themes/theme-dark.d.ts +2 -0
  489. package/styles/themes/theme-dark.d.ts.map +1 -0
  490. package/styles/themes/theme-dark.js +216 -0
  491. package/styles/themes/theme-dark.js.map +1 -0
  492. package/styles/themes/theme-material.d.ts +2 -0
  493. package/styles/themes/theme-material.d.ts.map +1 -0
  494. package/styles/themes/theme-material.js +134 -0
  495. package/styles/themes/theme-material.js.map +1 -0
  496. package/styles/themes/theme-quartz.d.ts +2 -0
  497. package/styles/themes/theme-quartz.d.ts.map +1 -0
  498. package/styles/themes/theme-quartz.js +120 -0
  499. package/styles/themes/theme-quartz.js.map +1 -0
  500. package/theme/css-var-injector.d.ts +10 -0
  501. package/theme/css-var-injector.d.ts.map +1 -0
  502. package/theme/css-var-injector.js +63 -0
  503. package/theme/css-var-injector.js.map +1 -0
  504. package/theme/theme-manager.d.ts +20 -0
  505. package/theme/theme-manager.d.ts.map +1 -0
  506. package/theme/theme-manager.js +79 -0
  507. package/theme/theme-manager.js.map +1 -0
  508. package/theme/themes/dark-theme.d.ts +3 -0
  509. package/theme/themes/dark-theme.d.ts.map +1 -0
  510. package/theme/themes/dark-theme.js +149 -0
  511. package/theme/themes/dark-theme.js.map +1 -0
  512. package/theme/themes/light-theme.d.ts +3 -0
  513. package/theme/themes/light-theme.d.ts.map +1 -0
  514. package/theme/themes/light-theme.js +149 -0
  515. package/theme/themes/light-theme.js.map +1 -0
  516. package/types/column.types.d.ts +195 -0
  517. package/types/column.types.d.ts.map +1 -0
  518. package/types/column.types.js +2 -0
  519. package/types/column.types.js.map +1 -0
  520. package/types/event.types.d.ts +213 -0
  521. package/types/event.types.d.ts.map +1 -0
  522. package/types/event.types.js +67 -0
  523. package/types/event.types.js.map +1 -0
  524. package/types/filter.types.d.ts +42 -0
  525. package/types/filter.types.d.ts.map +1 -0
  526. package/types/filter.types.js +2 -0
  527. package/types/filter.types.js.map +1 -0
  528. package/types/grid.types.d.ts +214 -0
  529. package/types/grid.types.d.ts.map +1 -0
  530. package/types/grid.types.js +2 -0
  531. package/types/grid.types.js.map +1 -0
  532. package/types/master-detail.types.d.ts +112 -0
  533. package/types/master-detail.types.d.ts.map +1 -0
  534. package/types/master-detail.types.js +2 -0
  535. package/types/master-detail.types.js.map +1 -0
  536. package/types/photon-ai.types.d.ts +19 -0
  537. package/types/photon-ai.types.d.ts.map +1 -0
  538. package/types/photon-ai.types.js +2 -0
  539. package/types/photon-ai.types.js.map +1 -0
  540. package/types/renderer.types.d.ts +156 -0
  541. package/types/renderer.types.d.ts.map +1 -0
  542. package/types/renderer.types.js +19 -0
  543. package/types/renderer.types.js.map +1 -0
  544. package/types/row.types.d.ts +127 -0
  545. package/types/row.types.d.ts.map +1 -0
  546. package/types/row.types.js +9 -0
  547. package/types/row.types.js.map +1 -0
  548. package/types/theme.types.d.ts +159 -0
  549. package/types/theme.types.d.ts.map +1 -0
  550. package/types/theme.types.js +2 -0
  551. package/types/theme.types.js.map +1 -0
  552. package/types/tree-data.types.d.ts +94 -0
  553. package/types/tree-data.types.d.ts.map +1 -0
  554. package/types/tree-data.types.js +2 -0
  555. package/types/tree-data.types.js.map +1 -0
@@ -0,0 +1,1534 @@
1
+ import { GridEventType } from '../types/event.types';
2
+ import { isCellInRanges, normalizeRange } from './selection-range';
3
+ import { activeGridRegistry } from './active-grid-registry';
4
+ export class CellSelectionEngine {
5
+ constructor(store, eventBus, clipboardEngine,
6
+ /** Optional undo/redo engine. When provided, cut, paste, and edit operations are recorded. */
7
+ undoRedoEngine) {
8
+ this.store = store;
9
+ this.eventBus = eventBus;
10
+ this.clipboardEngine = clipboardEngine;
11
+ this.undoRedoEngine = undoRedoEngine;
12
+ this._isSelecting = false;
13
+ this.anchorCell = null;
14
+ this.bodyPanels = [];
15
+ this.contextMenuEl = null;
16
+ this.chartOpenCallback = null;
17
+ /**
18
+ * Optional callback invoked when the user presses Enter on a focused (non-editing) cell.
19
+ * Return `true` to absorb the event (editing started); `false` to fall through to
20
+ * the default down-navigation behavior.
21
+ */
22
+ this.enterEditHandler = null;
23
+ /** Optional callback invoked on ArrowLeft/ArrowRight for Tree Data collapse/expand — see `setTreeToggleHandler`. */
24
+ this.treeToggleHandler = null;
25
+ /**
26
+ * Optional callback invoked after every active-cell change so the grid body
27
+ * can scroll the newly active cell into view (AG Grid-style auto-scroll).
28
+ */
29
+ this.scrollToCellCallback = null;
30
+ /**
31
+ * Returns the number of rows that fit in the visible body viewport.
32
+ * Used by PageUp / PageDown to determine how far to jump.
33
+ */
34
+ this.getViewportRowCountCallback = null;
35
+ /**
36
+ * Optional callback invoked after a data mutation (fill, cut, paste, undo/redo).
37
+ * When `nodeIds` is provided the renderer evicts only those rows from its cache
38
+ * so custom cell renderers in untouched rows are NOT re-executed.
39
+ * Omitting `nodeIds` falls back to a full cache clear (safe but unoptimised).
40
+ */
41
+ this.dataChangedCallback = null;
42
+ // ─── Fill handle state ────────────────────────────────────────────────────
43
+ /** DOM element for the interactive fill handle corner square. */
44
+ this.fillHandleEl = null;
45
+ /** The `.pg-cell` element that currently hosts the fill handle as a child. */
46
+ this.fillHandleParentCell = null;
47
+ /** `true` while the user is dragging the fill handle. */
48
+ this.isFillDragging = false;
49
+ /** Normalised source range captured at the start of a fill drag. */
50
+ this.fillSourceRange = null;
51
+ /** DOM cells currently showing the dashed fill-preview overlay. */
52
+ this.fillPreviewCells = [];
53
+ /** Locked fill direction; null until the user moves far enough in one axis. */
54
+ this.fillDirection = null;
55
+ /** Target row index for up/down fills; null when direction is horizontal. */
56
+ this.fillTargetRow = null;
57
+ /** Target column index for left/right fills; null when direction is vertical. */
58
+ this.fillTargetCol = null;
59
+ /** Callback invoked on every fill-drag mousemove to drive the edge auto-scroller. */
60
+ this.fillDragScrollCallback = null;
61
+ /** Callback invoked when the fill drag ends to stop the edge auto-scroller. */
62
+ this.fillDragEndCallback = null;
63
+ /**
64
+ * Returns the bounding rect of the grid's scroll viewport. Used by
65
+ * `processFillPosition` to clamp the hit-test coordinates when the cursor
66
+ * has moved outside the body area so edge cells are always reachable.
67
+ */
68
+ this.dragViewportRectFn = null;
69
+ this.boundKeydown = this.onKeydown.bind(this);
70
+ this.boundHideCtx = () => this.hideContextMenu();
71
+ this.boundFillMouseMove = this.onFillMouseMove.bind(this);
72
+ this.boundFillMouseUp = this.onFillMouseUp.bind(this);
73
+ }
74
+ get isSelecting() { return this._isSelecting; }
75
+ attach(containerEl) {
76
+ // containerEl kept for signature compat — no canvas attached
77
+ void containerEl;
78
+ document.addEventListener('keydown', this.boundKeydown);
79
+ this.buildContextMenu();
80
+ }
81
+ setBodyPanels(panels) {
82
+ this.bodyPanels = panels.filter(Boolean);
83
+ }
84
+ /**
85
+ * Register a callback that is invoked when the user presses Enter on a focused
86
+ * cell that is not in edit mode. Return `true` to absorb the event (editing
87
+ * started); return `false` to fall through to the default down-navigation.
88
+ */
89
+ setEnterEditHandler(fn) {
90
+ this.enterEditHandler = fn;
91
+ }
92
+ /**
93
+ * Register a callback invoked when Left/Right is pressed (without Ctrl/Cmd
94
+ * or Shift) on a row with children — Tree Data's collapse/expand-via-
95
+ * keyboard convention. Return `true` to absorb the key press (the tree
96
+ * toggled, or focus jumped to a parent/first child); return `false` to
97
+ * fall through to normal column navigation. `CellSelectionEngine` never
98
+ * imports tree types itself — this indirection is how it stays unaware of
99
+ * Tree Data entirely, same as `setEnterEditHandler` above for editing.
100
+ */
101
+ setTreeToggleHandler(fn) {
102
+ this.treeToggleHandler = fn;
103
+ }
104
+ /**
105
+ * Register a callback invoked after every active-cell change so the grid body
106
+ * can scroll the newly active cell into view (AG Grid-style auto-scroll).
107
+ * The renderer wires this in `buildLayout`.
108
+ *
109
+ * @param fn - Called with the new `rowIndex` and `colIndex` after each navigation.
110
+ */
111
+ setScrollToCellCallback(fn) {
112
+ this.scrollToCellCallback = fn;
113
+ }
114
+ /**
115
+ * Registers a callback that returns the number of data rows currently
116
+ * visible in the body viewport. Used by PageUp / PageDown to compute the
117
+ * jump distance. Wired by `GridRenderer` via the scroll controller.
118
+ *
119
+ * @param fn - Returns the visible row count (≥ 1).
120
+ */
121
+ setGetViewportRowCountCallback(fn) {
122
+ this.getViewportRowCountCallback = fn;
123
+ }
124
+ /**
125
+ * Register a callback invoked after a data mutation (fill, cut, paste, undo/redo).
126
+ * Pass `nodeIds` to evict only the mutated rows from the renderer cache;
127
+ * omit to fall back to a full cache clear.
128
+ *
129
+ * @param fn - Called with an optional set of mutated row node IDs.
130
+ */
131
+ setDataChangedCallback(fn) {
132
+ this.dataChangedCallback = fn;
133
+ }
134
+ /**
135
+ * Registers a callback invoked on every fill-drag `mousemove` with the
136
+ * current cursor coordinates. Wire this to `AutoScroller.updateMouse` in
137
+ * the grid renderer to enable edge auto-scrolling during fill-handle drags.
138
+ *
139
+ * @param fn - Called with `clientX` and `clientY` of each mousemove event.
140
+ */
141
+ setFillDragScrollCallback(fn) {
142
+ this.fillDragScrollCallback = fn;
143
+ }
144
+ /**
145
+ * Registers a callback invoked when the fill drag ends (mouseup).
146
+ * Wire this to `AutoScroller.stop` in the grid renderer so the RAF loop
147
+ * terminates as soon as the user releases the fill handle.
148
+ *
149
+ * @param fn - Called with no arguments when the fill drag terminates.
150
+ */
151
+ setFillDragEndCallback(fn) {
152
+ this.fillDragEndCallback = fn;
153
+ }
154
+ /**
155
+ * Registers a callback that returns the bounding rect of the grid's scroll
156
+ * viewport (`bodyWrapEl`). When set, `processFillPosition` clamps its
157
+ * hit-test coordinates to just inside this rect so edge cells are always
158
+ * found even when the cursor has moved outside the grid boundary.
159
+ *
160
+ * @param fn - Returns the current viewport `DOMRect`, or `null` when unmounted.
161
+ */
162
+ setDragViewportRectCallback(fn) {
163
+ this.dragViewportRectFn = fn;
164
+ }
165
+ /**
166
+ * Re-evaluates which cell lies at `(clientX, clientY)` and updates the fill
167
+ * direction and dashed preview accordingly. Called by the auto-scroller's
168
+ * `onScrolled` callback so the preview stays current after the grid has
169
+ * scrolled under a stationary cursor.
170
+ *
171
+ * No-op when no fill drag is currently active.
172
+ *
173
+ * @param clientX - Viewport X coordinate of the drag cursor.
174
+ * @param clientY - Viewport Y coordinate of the drag cursor.
175
+ */
176
+ updateFillPosition(clientX, clientY) {
177
+ this.processFillPosition(clientX, clientY);
178
+ }
179
+ detach() {
180
+ document.removeEventListener('keydown', this.boundKeydown);
181
+ document.removeEventListener('mousemove', this.boundFillMouseMove);
182
+ document.removeEventListener('mouseup', this.boundFillMouseUp);
183
+ activeGridRegistry.release(this);
184
+ this.fillHandleParentCell?.classList.remove('pg-cell--has-fill-handle');
185
+ this.fillHandleParentCell = null;
186
+ this.fillHandleEl?.remove();
187
+ this.fillHandleEl = null;
188
+ this.fillDragScrollCallback = null;
189
+ this.fillDragEndCallback = null;
190
+ this.dragViewportRectFn = null;
191
+ this.contextMenuEl?.remove();
192
+ this.contextMenuEl = null;
193
+ }
194
+ // ─── Selection API ────────────────────────────────────────────────────────
195
+ startSelection(rowIndex, colIndex, extend = false) {
196
+ // Claims this grid as the page's active selection surface, deactivating
197
+ // (clearing) whichever grid held that role before — see active-grid-registry.ts.
198
+ activeGridRegistry.setActive(this);
199
+ if (!extend) {
200
+ this.anchorCell = { rowIndex, colIndex };
201
+ this.store.set('cellRanges', [{
202
+ startRowIndex: rowIndex, endRowIndex: rowIndex,
203
+ startColIndex: colIndex, endColIndex: colIndex,
204
+ }]);
205
+ }
206
+ this._isSelecting = true;
207
+ this.store.set('activeCell', { rowIndex, colIndex });
208
+ this.emitSelectionChanged();
209
+ this.scrollToCellCallback?.(rowIndex, colIndex);
210
+ }
211
+ extendSelection(rowIndex, colIndex) {
212
+ if (!this.anchorCell) {
213
+ this.startSelection(rowIndex, colIndex);
214
+ return;
215
+ }
216
+ activeGridRegistry.setActive(this);
217
+ this.store.set('cellRanges', [{
218
+ startRowIndex: this.anchorCell.rowIndex,
219
+ endRowIndex: rowIndex,
220
+ startColIndex: this.anchorCell.colIndex,
221
+ endColIndex: colIndex,
222
+ }]);
223
+ this.store.set('activeCell', { rowIndex, colIndex });
224
+ this.emitSelectionChanged();
225
+ this.scrollToCellCallback?.(rowIndex, colIndex);
226
+ }
227
+ endSelection() {
228
+ this._isSelecting = false;
229
+ }
230
+ clearSelection() {
231
+ this.anchorCell = null;
232
+ this._isSelecting = false;
233
+ this.store.set('cellRanges', []);
234
+ this.store.set('activeCell', null);
235
+ this.applySelectionClasses();
236
+ this.emitSelectionChanged();
237
+ }
238
+ isCellSelected(rowIndex, colIndex) {
239
+ return isCellInRanges(rowIndex, colIndex, this.store.get('cellRanges'));
240
+ }
241
+ /**
242
+ * Adds the given cell as a new independent 1×1 range to the selection
243
+ * (Ctrl+Click multi-range). If the cell is already the sole member of a
244
+ * 1×1 range it is deselected instead (toggle behaviour matching AG Grid).
245
+ *
246
+ * @param rowIndex - Row index of the clicked cell.
247
+ * @param colIndex - Column index of the clicked cell.
248
+ */
249
+ addRangeCell(rowIndex, colIndex) {
250
+ activeGridRegistry.setActive(this);
251
+ const existing = this.store.get('cellRanges');
252
+ const dupeIdx = existing.findIndex((r) => r.startRowIndex === rowIndex && r.endRowIndex === rowIndex &&
253
+ r.startColIndex === colIndex && r.endColIndex === colIndex);
254
+ const ranges = dupeIdx >= 0
255
+ ? existing.filter((_, i) => i !== dupeIdx)
256
+ : [...existing, {
257
+ startRowIndex: rowIndex, endRowIndex: rowIndex,
258
+ startColIndex: colIndex, endColIndex: colIndex,
259
+ }];
260
+ this.anchorCell = { rowIndex, colIndex };
261
+ this.store.set('cellRanges', ranges);
262
+ this.store.set('activeCell', ranges.length > 0 ? { rowIndex, colIndex } : null);
263
+ this.applySelectionClasses();
264
+ this.emitSelectionChanged();
265
+ }
266
+ // ─── CSS-based rendering (replaces canvas) ────────────────────────────────
267
+ /**
268
+ * Applies selection CSS classes to every visible cell DOM element.
269
+ *
270
+ * For multi-range selections (Ctrl+Click) edge classes are unioned across
271
+ * all ranges a cell belongs to, so each range always renders a complete
272
+ * closed border regardless of how many ranges share a cell.
273
+ *
274
+ * After updating classes, repositions the fill handle at the primary
275
+ * range's bottom-right corner.
276
+ */
277
+ applySelectionClasses() {
278
+ const ranges = this.store.get('cellRanges');
279
+ const activeCell = this.store.get('activeCell');
280
+ const hasRanges = ranges.length > 0;
281
+ // Pre-normalise all ranges once so the inner cell loop is cheap.
282
+ const norms = hasRanges ? ranges.map(normalizeRange) : [];
283
+ for (const panel of this.bodyPanels) {
284
+ for (const el of panel.querySelectorAll('.pg-cell[data-row-index][data-col-index]')) {
285
+ const ri = Number(el.getAttribute('data-row-index'));
286
+ const ci = Number(el.getAttribute('data-col-index'));
287
+ const inRange = hasRanges && isCellInRanges(ri, ci, ranges);
288
+ const isActive = !!activeCell && activeCell.rowIndex === ri && activeCell.colIndex === ci;
289
+ el.classList.toggle('pg-cell--in-selection', inRange);
290
+ el.classList.toggle('pg-cell--active-cell', isActive);
291
+ if (inRange) {
292
+ // Union edges from every range that contains this cell so each
293
+ // range always closes cleanly (supports Ctrl+Click multi-ranges).
294
+ let isTop = false, isBottom = false, isLeft = false, isRight = false;
295
+ for (const n of norms) {
296
+ if (ri >= n.startRowIndex && ri <= n.endRowIndex &&
297
+ ci >= n.startColIndex && ci <= n.endColIndex) {
298
+ if (ri === n.startRowIndex)
299
+ isTop = true;
300
+ if (ri === n.endRowIndex)
301
+ isBottom = true;
302
+ if (ci === n.startColIndex)
303
+ isLeft = true;
304
+ if (ci === n.endColIndex)
305
+ isRight = true;
306
+ }
307
+ }
308
+ el.classList.toggle('pg-cell--sel-top', isTop);
309
+ el.classList.toggle('pg-cell--sel-bottom', isBottom);
310
+ el.classList.toggle('pg-cell--sel-left', isLeft);
311
+ el.classList.toggle('pg-cell--sel-right', isRight);
312
+ }
313
+ else {
314
+ el.classList.remove('pg-cell--sel-top', 'pg-cell--sel-bottom', 'pg-cell--sel-left', 'pg-cell--sel-right');
315
+ }
316
+ }
317
+ }
318
+ this.updateFillHandle();
319
+ }
320
+ /** @deprecated Use `applySelectionClasses` directly. Kept for caller compatibility. */
321
+ renderSelection(_getCellRect) {
322
+ this.applySelectionClasses();
323
+ }
324
+ // ─── Fill handle ──────────────────────────────────────────────────────────
325
+ /**
326
+ * Creates (once) or repositions the fill handle at the bottom-right corner
327
+ * of the primary selection range. Hidden during an active fill drag or when
328
+ * there are multiple ranges (Ctrl+Click mode).
329
+ *
330
+ * The element uses `position: fixed` so it always sits above the grid
331
+ * regardless of overflow or scroll state.
332
+ */
333
+ updateFillHandle() {
334
+ if (this.isFillDragging)
335
+ return;
336
+ const ranges = this.store.get('cellRanges');
337
+ if (ranges.length !== 1) {
338
+ this.hideFillHandle();
339
+ return;
340
+ }
341
+ const n = normalizeRange(ranges[0]);
342
+ // Locate the bottom-right corner cell across all body panels.
343
+ let cornerCell = null;
344
+ for (const panel of this.bodyPanels) {
345
+ const el = panel.querySelector(`[data-row-index="${n.endRowIndex}"][data-col-index="${n.endColIndex}"]`);
346
+ if (el) {
347
+ cornerCell = el;
348
+ break;
349
+ }
350
+ }
351
+ if (!cornerCell) {
352
+ this.hideFillHandle();
353
+ return;
354
+ }
355
+ // Create the handle element once.
356
+ if (!this.fillHandleEl) {
357
+ const handle = document.createElement('div');
358
+ handle.className = 'pg-fill-handle';
359
+ handle.addEventListener('mousedown', (e) => {
360
+ e.preventDefault();
361
+ e.stopPropagation();
362
+ this.startFillDrag();
363
+ });
364
+ this.fillHandleEl = handle;
365
+ }
366
+ // Re-parent when the corner cell changed (e.g. virtual scroll rebuilt the row).
367
+ if (this.fillHandleParentCell !== cornerCell) {
368
+ this.fillHandleParentCell?.classList.remove('pg-cell--has-fill-handle');
369
+ this.fillHandleParentCell = cornerCell;
370
+ cornerCell.classList.add('pg-cell--has-fill-handle');
371
+ cornerCell.appendChild(this.fillHandleEl);
372
+ }
373
+ this.fillHandleEl.style.display = '';
374
+ }
375
+ /** Hides the fill handle and clears the overflow-visible class from its host cell. */
376
+ hideFillHandle() {
377
+ if (this.fillHandleEl)
378
+ this.fillHandleEl.style.display = 'none';
379
+ this.fillHandleParentCell?.classList.remove('pg-cell--has-fill-handle');
380
+ this.fillHandleParentCell = null;
381
+ }
382
+ /**
383
+ * Begins a fill-handle drag operation. Captures the current selection as
384
+ * the fill source and attaches document-level mouse event listeners.
385
+ */
386
+ startFillDrag() {
387
+ const ranges = this.store.get('cellRanges');
388
+ if (!ranges.length)
389
+ return;
390
+ this.fillSourceRange = normalizeRange(ranges[0]);
391
+ this.isFillDragging = true;
392
+ this.fillDirection = null;
393
+ this.fillTargetRow = null;
394
+ this.fillTargetCol = null;
395
+ document.addEventListener('mousemove', this.boundFillMouseMove);
396
+ document.addEventListener('mouseup', this.boundFillMouseUp);
397
+ }
398
+ /**
399
+ * Handles fill-drag `mousemove` by delegating position processing and
400
+ * notifying the auto-scroller callback so edge scrolling can be triggered.
401
+ */
402
+ onFillMouseMove(e) {
403
+ if (!this.isFillDragging || !this.fillSourceRange)
404
+ return;
405
+ this.processFillPosition(e.clientX, e.clientY);
406
+ this.fillDragScrollCallback?.(e.clientX, e.clientY);
407
+ }
408
+ /**
409
+ * Core fill-drag position logic: resolves the fill direction and target
410
+ * cell from a viewport coordinate pair, then refreshes the dashed preview.
411
+ *
412
+ * Separated from `onFillMouseMove` so it can also be called by the
413
+ * auto-scroller's `onScrolled` callback — keeping the preview accurate
414
+ * after the grid has scrolled under a stationary cursor.
415
+ *
416
+ * @param clientX - Viewport X coordinate of the drag cursor.
417
+ * @param clientY - Viewport Y coordinate of the drag cursor.
418
+ */
419
+ processFillPosition(clientX, clientY) {
420
+ if (!this.isFillDragging || !this.fillSourceRange)
421
+ return;
422
+ let target = document.elementFromPoint(clientX, clientY);
423
+ let cellEl = target?.closest('[data-row-index][data-col-index]');
424
+ // When the cursor is outside the grid viewport (negative dist from any edge),
425
+ // `elementFromPoint` hits the page background rather than a cell. Clamp the
426
+ // coordinates to just inside the viewport boundary so we always resolve to
427
+ // the nearest edge cell — mirroring AG Grid fill-handle behaviour.
428
+ if (!cellEl) {
429
+ const vr = this.dragViewportRectFn?.();
430
+ if (vr) {
431
+ const cx2 = Math.max(vr.left + 1, Math.min(vr.right - 1, clientX));
432
+ const cy2 = Math.max(vr.top + 1, Math.min(vr.bottom - 1, clientY));
433
+ const t2 = document.elementFromPoint(cx2, cy2);
434
+ cellEl = t2?.closest('[data-row-index][data-col-index]');
435
+ }
436
+ }
437
+ if (!cellEl)
438
+ return;
439
+ const ri = Number(cellEl.getAttribute('data-row-index'));
440
+ const ci = Number(cellEl.getAttribute('data-col-index'));
441
+ if (isNaN(ri) || isNaN(ci))
442
+ return;
443
+ const src = this.fillSourceRange;
444
+ // Distance from the edge of the source range along each axis.
445
+ const dRow = ri < src.startRowIndex ? ri - src.startRowIndex
446
+ : ri > src.endRowIndex ? ri - src.endRowIndex
447
+ : 0;
448
+ const dCol = ci < src.startColIndex ? ci - src.startColIndex
449
+ : ci > src.endColIndex ? ci - src.endColIndex
450
+ : 0;
451
+ // Lock to the axis with the larger displacement.
452
+ if (Math.abs(dRow) >= Math.abs(dCol)) {
453
+ this.fillDirection = dRow >= 0 ? 'down' : 'up';
454
+ this.fillTargetRow = ri;
455
+ this.fillTargetCol = null;
456
+ }
457
+ else {
458
+ this.fillDirection = dCol >= 0 ? 'right' : 'left';
459
+ this.fillTargetRow = null;
460
+ this.fillTargetCol = ci;
461
+ }
462
+ this.updateFillPreview();
463
+ }
464
+ /**
465
+ * Applies `pg-cell--fill-preview` and its directional edge classes to
466
+ * cells in the fill target area, replacing any previous preview.
467
+ */
468
+ updateFillPreview() {
469
+ // Clear the previous cycle's preview classes.
470
+ for (const el of this.fillPreviewCells) {
471
+ el.classList.remove('pg-cell--fill-preview', 'pg-cell--fp-top', 'pg-cell--fp-bottom', 'pg-cell--fp-left', 'pg-cell--fp-right');
472
+ }
473
+ this.fillPreviewCells = [];
474
+ if (!this.fillSourceRange || !this.fillDirection)
475
+ return;
476
+ const src = this.fillSourceRange;
477
+ let startRow, endRow, startCol, endCol;
478
+ switch (this.fillDirection) {
479
+ case 'down':
480
+ if (this.fillTargetRow === null || this.fillTargetRow <= src.endRowIndex)
481
+ return;
482
+ startRow = src.endRowIndex + 1;
483
+ endRow = this.fillTargetRow;
484
+ startCol = src.startColIndex;
485
+ endCol = src.endColIndex;
486
+ break;
487
+ case 'up':
488
+ if (this.fillTargetRow === null || this.fillTargetRow >= src.startRowIndex)
489
+ return;
490
+ startRow = this.fillTargetRow;
491
+ endRow = src.startRowIndex - 1;
492
+ startCol = src.startColIndex;
493
+ endCol = src.endColIndex;
494
+ break;
495
+ case 'right':
496
+ if (this.fillTargetCol === null || this.fillTargetCol <= src.endColIndex)
497
+ return;
498
+ startRow = src.startRowIndex;
499
+ endRow = src.endRowIndex;
500
+ startCol = src.endColIndex + 1;
501
+ endCol = this.fillTargetCol;
502
+ break;
503
+ case 'left':
504
+ if (this.fillTargetCol === null || this.fillTargetCol >= src.startColIndex)
505
+ return;
506
+ startRow = src.startRowIndex;
507
+ endRow = src.endRowIndex;
508
+ startCol = this.fillTargetCol;
509
+ endCol = src.startColIndex - 1;
510
+ break;
511
+ default: return;
512
+ }
513
+ for (const panel of this.bodyPanels) {
514
+ for (const el of panel.querySelectorAll('.pg-cell[data-row-index][data-col-index]')) {
515
+ const ri = Number(el.getAttribute('data-row-index'));
516
+ const ci = Number(el.getAttribute('data-col-index'));
517
+ if (ri >= startRow && ri <= endRow && ci >= startCol && ci <= endCol) {
518
+ el.classList.add('pg-cell--fill-preview');
519
+ if (ri === startRow)
520
+ el.classList.add('pg-cell--fp-top');
521
+ if (ri === endRow)
522
+ el.classList.add('pg-cell--fp-bottom');
523
+ if (ci === startCol)
524
+ el.classList.add('pg-cell--fp-left');
525
+ if (ci === endCol)
526
+ el.classList.add('pg-cell--fp-right');
527
+ this.fillPreviewCells.push(el);
528
+ }
529
+ }
530
+ }
531
+ }
532
+ /**
533
+ * Finalises the fill drag: applies data, clears preview classes, and
534
+ * restores the fill handle to its resting position.
535
+ */
536
+ onFillMouseUp() {
537
+ document.removeEventListener('mousemove', this.boundFillMouseMove);
538
+ document.removeEventListener('mouseup', this.boundFillMouseUp);
539
+ this.fillDragEndCallback?.();
540
+ if (this.fillSourceRange && this.fillDirection) {
541
+ this.applyFill();
542
+ }
543
+ // Remove dashed fill-preview styling.
544
+ for (const el of this.fillPreviewCells) {
545
+ el.classList.remove('pg-cell--fill-preview', 'pg-cell--fp-top', 'pg-cell--fp-bottom', 'pg-cell--fp-left', 'pg-cell--fp-right');
546
+ }
547
+ this.fillPreviewCells = [];
548
+ this.isFillDragging = false;
549
+ this.fillSourceRange = null;
550
+ this.fillDirection = null;
551
+ this.fillTargetRow = null;
552
+ this.fillTargetCol = null;
553
+ // Re-apply classes so the fill handle is repositioned.
554
+ this.applySelectionClasses();
555
+ }
556
+ /**
557
+ * Copies source cell values into the fill target area.
558
+ *
559
+ * Cycling semantics:
560
+ * - Vertical fill: each column in the fill area copies from the same
561
+ * column in the source range, cycling rows (`srcH` modulo).
562
+ * - Horizontal fill: each row copies from the same row in the source
563
+ * range, cycling columns (`srcW` modulo).
564
+ *
565
+ * The operation is recorded in the undo/redo engine and triggers an
566
+ * immediate renderer refresh via `dataChangedCallback`.
567
+ */
568
+ applyFill() {
569
+ if (!this.fillSourceRange || !this.fillDirection)
570
+ return;
571
+ const src = this.fillSourceRange;
572
+ const srcH = src.endRowIndex - src.startRowIndex + 1;
573
+ const srcW = src.endColIndex - src.startColIndex + 1;
574
+ let fillStartRow, fillEndRow, fillStartCol, fillEndCol;
575
+ switch (this.fillDirection) {
576
+ case 'down':
577
+ if (this.fillTargetRow === null || this.fillTargetRow <= src.endRowIndex)
578
+ return;
579
+ fillStartRow = src.endRowIndex + 1;
580
+ fillEndRow = this.fillTargetRow;
581
+ fillStartCol = src.startColIndex;
582
+ fillEndCol = src.endColIndex;
583
+ break;
584
+ case 'up':
585
+ if (this.fillTargetRow === null || this.fillTargetRow >= src.startRowIndex)
586
+ return;
587
+ fillStartRow = this.fillTargetRow;
588
+ fillEndRow = src.startRowIndex - 1;
589
+ fillStartCol = src.startColIndex;
590
+ fillEndCol = src.endColIndex;
591
+ break;
592
+ case 'right':
593
+ if (this.fillTargetCol === null || this.fillTargetCol <= src.endColIndex)
594
+ return;
595
+ fillStartRow = src.startRowIndex;
596
+ fillEndRow = src.endRowIndex;
597
+ fillStartCol = src.endColIndex + 1;
598
+ fillEndCol = this.fillTargetCol;
599
+ break;
600
+ case 'left':
601
+ if (this.fillTargetCol === null || this.fillTargetCol >= src.startColIndex)
602
+ return;
603
+ fillStartRow = src.startRowIndex;
604
+ fillEndRow = src.endRowIndex;
605
+ fillStartCol = this.fillTargetCol;
606
+ fillEndCol = src.startColIndex - 1;
607
+ break;
608
+ default: return;
609
+ }
610
+ if (fillStartRow > fillEndRow || fillStartCol > fillEndCol)
611
+ return;
612
+ const visRows = this.store.get('visibleRows');
613
+ const columns = this.getVisibleColumns();
614
+ const changes = [];
615
+ for (let r = fillStartRow; r <= fillEndRow; r++) {
616
+ const row = visRows[r];
617
+ if (!row || row.type !== 'data')
618
+ continue;
619
+ for (let c = fillStartCol; c <= fillEndCol; c++) {
620
+ const col = columns[c];
621
+ if (!col)
622
+ continue;
623
+ // Map the fill cell back to its corresponding source cell.
624
+ let srcR, srcC;
625
+ const dir = this.fillDirection;
626
+ if (dir === 'down') {
627
+ srcR = src.startRowIndex + ((r - fillStartRow) % srcH);
628
+ srcC = c;
629
+ }
630
+ else if (dir === 'up') {
631
+ srcR = src.endRowIndex - ((fillEndRow - r) % srcH);
632
+ srcC = c;
633
+ }
634
+ else if (dir === 'right') {
635
+ srcR = r;
636
+ srcC = src.startColIndex + ((c - fillStartCol) % srcW);
637
+ }
638
+ else {
639
+ srcR = r;
640
+ srcC = src.endColIndex - ((fillEndCol - c) % srcW);
641
+ }
642
+ const srcRow = visRows[srcR];
643
+ if (!srcRow || srcRow.type !== 'data')
644
+ continue;
645
+ const srcCol = columns[srcC];
646
+ if (!srcCol)
647
+ continue;
648
+ const rawValue = srcRow.data[srcCol.field];
649
+ const srcType = srcCol.type ?? 'string';
650
+ const dstType = col.type ?? 'string';
651
+ // Coerce to the destination column's data type when source and
652
+ // destination types differ. Same-type copies skip coercion so that
653
+ // exact values are preserved (avoids spurious Date re-creation).
654
+ const newValue = srcType !== dstType
655
+ ? this.coerceToColumnType(rawValue, dstType)
656
+ : rawValue;
657
+ const oldValue = row.data[col.field];
658
+ if (!Object.is(oldValue, newValue)) {
659
+ changes.push({ nodeId: row.nodeId, field: col.field, oldValue, newValue });
660
+ row.data[col.field] = newValue;
661
+ }
662
+ }
663
+ }
664
+ if (changes.length > 0) {
665
+ this.undoRedoEngine?.record({ type: 'paste', changes });
666
+ this.store.set('allRows', [...this.store.get('allRows')]);
667
+ this.dataChangedCallback?.(new Set(changes.map((c) => c.nodeId)));
668
+ // Expand the selection to cover both the source range and the fill area
669
+ // so the user can immediately see the full result of the operation.
670
+ const newRange = {
671
+ startRowIndex: Math.min(src.startRowIndex, fillStartRow),
672
+ endRowIndex: Math.max(src.endRowIndex, fillEndRow),
673
+ startColIndex: Math.min(src.startColIndex, fillStartCol),
674
+ endColIndex: Math.max(src.endColIndex, fillEndCol),
675
+ };
676
+ this.anchorCell = { rowIndex: newRange.startRowIndex, colIndex: newRange.startColIndex };
677
+ this.store.set('cellRanges', [newRange]);
678
+ this.store.set('activeCell', { rowIndex: newRange.startRowIndex, colIndex: newRange.startColIndex });
679
+ this.emitSelectionChanged();
680
+ // Defer the success flash so it runs after applySelectionClasses has
681
+ // painted the expanded selection classes onto the DOM.
682
+ const fsr = fillStartRow, fer = fillEndRow, fsc = fillStartCol, fec = fillEndCol;
683
+ setTimeout(() => this.flashFillArea(fsr, fer, fsc, fec), 0);
684
+ }
685
+ }
686
+ /**
687
+ * Flashes the cells in the filled area with a success colour overlay,
688
+ * giving clear visual feedback that the fill operation succeeded.
689
+ *
690
+ * Uses `::before` so it never conflicts with the selection `::after` border.
691
+ *
692
+ * @param startRow - First row of the fill target area.
693
+ * @param endRow - Last row of the fill target area.
694
+ * @param startCol - First column of the fill target area.
695
+ * @param endCol - Last column of the fill target area.
696
+ */
697
+ /**
698
+ * Applies a flash animation to all visible cells within the given row/column
699
+ * bounding box.
700
+ *
701
+ * @param startRow - First row index (inclusive).
702
+ * @param endRow - Last row index (inclusive).
703
+ * @param startCol - First column index (inclusive, ≥ 0).
704
+ * @param endCol - Last column index (inclusive).
705
+ * @param flashClass - CSS class that carries the animation keyframe.
706
+ * Defaults to `'pg-cell--fill-flash'` (green success).
707
+ * Pass `'pg-cell--cut-flash'` for the red danger flash.
708
+ */
709
+ flashFillArea(startRow, endRow, startCol, endCol, flashClass = 'pg-cell--fill-flash') {
710
+ const cells = [];
711
+ for (const panel of this.bodyPanels) {
712
+ for (const el of panel.querySelectorAll('.pg-cell[data-row-index][data-col-index]')) {
713
+ const ri = Number(el.getAttribute('data-row-index'));
714
+ const ci = Number(el.getAttribute('data-col-index'));
715
+ if (ri >= startRow && ri <= endRow && ci >= startCol && ci <= endCol) {
716
+ cells.push(el);
717
+ }
718
+ }
719
+ }
720
+ for (const el of cells) {
721
+ el.classList.remove('pg-cell--fill-flash', 'pg-cell--cut-flash');
722
+ void el.offsetWidth; // restart animation if re-triggered
723
+ el.classList.add(flashClass);
724
+ }
725
+ setTimeout(() => {
726
+ for (const el of cells)
727
+ el.classList.remove(flashClass);
728
+ }, 700);
729
+ }
730
+ // ─── Navigation ──────────────────────────────────────────────────────────
731
+ moveActiveCell(dRow, dCol, rowCount, colCount, extend = false) {
732
+ const active = this.store.get('activeCell');
733
+ if (!active)
734
+ return;
735
+ const newRow = Math.max(0, Math.min(rowCount - 1, active.rowIndex + dRow));
736
+ const newCol = Math.max(0, Math.min(colCount - 1, active.colIndex + dCol));
737
+ if (extend)
738
+ this.extendSelection(newRow, newCol);
739
+ else
740
+ this.startSelection(newRow, newCol);
741
+ }
742
+ jumpToEdge(direction, rowCount, colCount, extend = false) {
743
+ const active = this.store.get('activeCell');
744
+ if (!active)
745
+ return;
746
+ let newRow = active.rowIndex;
747
+ let newCol = active.colIndex;
748
+ switch (direction) {
749
+ case 'up':
750
+ newRow = 0;
751
+ break;
752
+ case 'down':
753
+ newRow = rowCount - 1;
754
+ break;
755
+ case 'left':
756
+ newCol = 0;
757
+ break;
758
+ case 'right':
759
+ newCol = colCount - 1;
760
+ break;
761
+ }
762
+ if (extend)
763
+ this.extendSelection(newRow, newCol);
764
+ else
765
+ this.startSelection(newRow, newCol);
766
+ }
767
+ setChartOpenCallback(fn) {
768
+ this.chartOpenCallback = fn;
769
+ }
770
+ // ─── Clipboard ───────────────────────────────────────────────────────────
771
+ async copySelection(rows, columns) {
772
+ this.flashSelection('copy');
773
+ return this.clipboardEngine.copyRangesToClipboard(this.store.get('cellRanges'), rows, columns, false, this.getLeafGroupField());
774
+ }
775
+ async copySelectionWithHeaders(rows, columns) {
776
+ this.flashSelection('copy');
777
+ return this.clipboardEngine.copyRangesToClipboard(this.store.get('cellRanges'), rows, columns, true, this.getLeafGroupField());
778
+ }
779
+ async cutSelection(rows, columns) {
780
+ const ranges = this.store.get('cellRanges');
781
+ const leafGroupField = this.getLeafGroupField();
782
+ await this.clipboardEngine.copyRangesToClipboard(ranges, rows, columns, false, leafGroupField);
783
+ // Capture old values and apply mutations in a single pass per range so we
784
+ // never need a second traversal for undo recording.
785
+ const changes = [];
786
+ for (const range of ranges) {
787
+ const n = normalizeRange(range);
788
+ const hasGroupLabel = n.startColIndex < 0;
789
+ for (let r = n.startRowIndex; r <= n.endRowIndex; r++) {
790
+ const row = rows[r];
791
+ if (!row || row.type !== 'data')
792
+ continue;
793
+ // Auto-group label column (colIndex −1) → leaf group field.
794
+ if (hasGroupLabel && leafGroupField) {
795
+ const oldVal = row.data[leafGroupField];
796
+ changes.push({ nodeId: row.nodeId, field: leafGroupField, oldValue: oldVal, newValue: null });
797
+ row.data[leafGroupField] = null;
798
+ }
799
+ // Regular columns (columns[−1] is undefined; the `if (col)` guard skips it).
800
+ const dataStart = hasGroupLabel ? 0 : n.startColIndex;
801
+ for (let c = dataStart; c <= n.endColIndex; c++) {
802
+ const col = columns[c];
803
+ if (!col)
804
+ continue;
805
+ const oldVal = row.data[col.field];
806
+ changes.push({ nodeId: row.nodeId, field: col.field, oldValue: oldVal, newValue: null });
807
+ row.data[col.field] = null;
808
+ }
809
+ }
810
+ }
811
+ this.undoRedoEngine?.record({ type: 'cut', changes });
812
+ // Clear cell text immediately so the user sees blank cells without waiting
813
+ // for the render RAF.
814
+ this.clearCutCellsInDom(ranges);
815
+ // Sync allRows so pipeline/watchers stay consistent
816
+ this.store.set('allRows', [...this.store.get('allRows')]);
817
+ const cutNodeIds = new Set(changes.map((c) => c.nodeId));
818
+ // dataChangedCallback evicts the row DOM immediately (invalidateRowsByNodeId
819
+ // calls .remove() synchronously). The old flashSelection('cut') ran before
820
+ // this eviction, so the flash class was added to elements that were then
821
+ // detached before the browser could paint — the animation never appeared.
822
+ // Fix: schedule the flash with a double-RAF so it runs after the render RAF
823
+ // has rebuilt the cleared row DOM.
824
+ this.dataChangedCallback?.(cutNodeIds);
825
+ // Compute the bounding box across all cut ranges for the flash.
826
+ let minRow = Infinity, maxRow = -Infinity, minCol = Infinity, maxCol = -Infinity;
827
+ for (const range of ranges) {
828
+ const n = normalizeRange(range);
829
+ if (n.startRowIndex < minRow)
830
+ minRow = n.startRowIndex;
831
+ if (n.endRowIndex > maxRow)
832
+ maxRow = n.endRowIndex;
833
+ if (n.startColIndex < minCol)
834
+ minCol = n.startColIndex;
835
+ if (n.endColIndex > maxCol)
836
+ maxCol = n.endColIndex;
837
+ }
838
+ if (minRow !== Infinity) {
839
+ const fsr = minRow, fer = maxRow, fsc = Math.max(0, minCol), fec = maxCol;
840
+ requestAnimationFrame(() => requestAnimationFrame(() => this.flashFillArea(fsr, fer, fsc, fec, 'pg-cell--cut-flash')));
841
+ }
842
+ }
843
+ async pasteSelection(rows, columns) {
844
+ const activeCell = this.store.get('activeCell');
845
+ if (!activeCell)
846
+ return;
847
+ const clipData = await this.clipboardEngine.pasteFromClipboard();
848
+ if (!clipData.length)
849
+ return;
850
+ const startRow = activeCell.rowIndex;
851
+ const startCol = activeCell.colIndex;
852
+ const leafGroupField = this.getLeafGroupField();
853
+ // When pasting from the auto-group column (colIndex −1), the first clipboard
854
+ // column maps to the leaf group field; subsequent columns map to regular
855
+ // columns at indices 0, 1, 2 … (startCol+ci arithmetic handles this naturally).
856
+ const hasGroupLabel = startCol < 0 && !!leafGroupField;
857
+ // Capture old values and apply mutations in the same loop pass so a second
858
+ // traversal is never required for undo recording.
859
+ const changes = [];
860
+ // Track the actual row/column bounds for the success flash.
861
+ let actualEndRow = startRow;
862
+ let maxClipCols = 0;
863
+ let pastedRows = 0;
864
+ for (let ri = startRow; pastedRows < clipData.length && ri < rows.length; ri++) {
865
+ const row = rows[ri];
866
+ if (!row || row.type !== 'data')
867
+ continue;
868
+ const clipRow = clipData[pastedRows++];
869
+ actualEndRow = ri;
870
+ if (clipRow.length > maxClipCols)
871
+ maxClipCols = clipRow.length;
872
+ for (let ci = 0; ci < clipRow.length; ci++) {
873
+ const colIdx = startCol + ci; // −1+0=−1 for group label; −1+1=0, −1+2=1 …
874
+ if (hasGroupLabel && colIdx < 0) {
875
+ const field = leafGroupField;
876
+ changes.push({ nodeId: row.nodeId, field, oldValue: row.data[field], newValue: clipRow[ci] });
877
+ row.data[field] = clipRow[ci];
878
+ }
879
+ else {
880
+ const col = columns[colIdx];
881
+ if (col) {
882
+ // Coerce the clipboard string to the column's native type — mirrors
883
+ // the same conversion used by the fill-handle so pasting "42" into a
884
+ // number column stores the number 42, not the string "42".
885
+ const coerced = this.coerceToColumnType(clipRow[ci], col.type ?? 'string');
886
+ changes.push({ nodeId: row.nodeId, field: col.field, oldValue: row.data[col.field], newValue: coerced });
887
+ row.data[col.field] = coerced;
888
+ }
889
+ }
890
+ }
891
+ }
892
+ this.undoRedoEngine?.record({ type: 'paste', changes });
893
+ this.store.set('allRows', [...this.store.get('allRows')]);
894
+ // Evict only the pasted rows from the renderer cache so custom cell renderers
895
+ // in untouched rows (images, flags, etc.) are not needlessly re-executed.
896
+ this.dataChangedCallback?.(new Set(changes.map((c) => c.nodeId)));
897
+ if (changes.length > 0 && maxClipCols > 0) {
898
+ // The group-label column (colIndex −1) is not a regular body cell, so clamp
899
+ // the flash range to column 0. When hasGroupLabel the first clip column maps
900
+ // to the group field (colIdx −1) and regular columns start at colIdx 0.
901
+ const flashStartCol = hasGroupLabel ? 0 : Math.max(0, startCol);
902
+ const flashEndCol = hasGroupLabel
903
+ ? maxClipCols - 2 // −1 + maxClipCols − 1
904
+ : startCol + maxClipCols - 1;
905
+ if (flashEndCol >= flashStartCol) {
906
+ const fsr = startRow, fer = actualEndRow, fsc = flashStartCol, fec = flashEndCol;
907
+ // Double-RAF: the first RAF fires the pending render (which rebuilds the
908
+ // invalidated row DOM); the second RAF fires after those cells are in the
909
+ // DOM so flashFillArea can query and animate them.
910
+ requestAnimationFrame(() => requestAnimationFrame(() => this.flashFillArea(fsr, fer, fsc, fec)));
911
+ }
912
+ }
913
+ }
914
+ // ─── Undo / Redo ─────────────────────────────────────────────────────────
915
+ /**
916
+ * Reverts the most recent recorded action (edit / cut / paste) by applying
917
+ * the inverse cell changes and triggering a re-render.
918
+ * No-op when the undo stack is empty or no engine is wired up.
919
+ */
920
+ performUndo() {
921
+ const changes = this.undoRedoEngine?.undo();
922
+ if (changes)
923
+ this.applyUndoRedoChanges(changes);
924
+ }
925
+ /**
926
+ * Re-applies the most recently undone action and triggers a re-render.
927
+ * No-op when the redo stack is empty or no engine is wired up.
928
+ */
929
+ performRedo() {
930
+ const changes = this.undoRedoEngine?.redo();
931
+ if (changes)
932
+ this.applyUndoRedoChanges(changes);
933
+ }
934
+ /**
935
+ * Writes the given cell changes into `allRows`, triggers a renderer refresh,
936
+ * scrolls the first affected cell into view, and flashes the affected region.
937
+ *
938
+ * Used by both `performUndo` and `performRedo`.
939
+ *
940
+ * A `Map<nodeId, RowNode>` is built once before the change loop so each row
941
+ * lookup is O(1) instead of O(n) — important for large paste undo operations.
942
+ *
943
+ * @param changes - Ordered list of `{ nodeId, field, newValue }` to apply.
944
+ */
945
+ applyUndoRedoChanges(changes) {
946
+ const allRows = this.store.get('allRows');
947
+ const nodeMap = new Map();
948
+ for (const row of allRows)
949
+ nodeMap.set(row.nodeId, row);
950
+ for (const change of changes) {
951
+ const row = nodeMap.get(change.nodeId);
952
+ if (row)
953
+ row.data[change.field] = change.newValue;
954
+ }
955
+ this.store.set('allRows', [...allRows]);
956
+ this.dataChangedCallback?.(new Set(changes.map((c) => c.nodeId)));
957
+ if (changes.length === 0)
958
+ return;
959
+ // Translate nodeId/field into visible row/column indices so we can scroll
960
+ // and flash the affected region. Rows or columns that are filtered/hidden
961
+ // won't appear in these maps and are silently skipped.
962
+ const visibleRows = this.store.get('visibleRows');
963
+ const columns = this.getVisibleColumns();
964
+ const nodeIdToRowIndex = new Map();
965
+ for (let i = 0; i < visibleRows.length; i++)
966
+ nodeIdToRowIndex.set(visibleRows[i].nodeId, i);
967
+ const fieldToColIndex = new Map();
968
+ for (let i = 0; i < columns.length; i++)
969
+ fieldToColIndex.set(columns[i].field, i);
970
+ let minRow = Infinity, maxRow = -Infinity;
971
+ let minCol = Infinity, maxCol = -Infinity;
972
+ for (const change of changes) {
973
+ const ri = nodeIdToRowIndex.get(change.nodeId);
974
+ const ci = fieldToColIndex.get(change.field);
975
+ if (ri !== undefined) {
976
+ if (ri < minRow)
977
+ minRow = ri;
978
+ if (ri > maxRow)
979
+ maxRow = ri;
980
+ }
981
+ if (ci !== undefined) {
982
+ if (ci < minCol)
983
+ minCol = ci;
984
+ if (ci > maxCol)
985
+ maxCol = ci;
986
+ }
987
+ }
988
+ if (minRow === Infinity)
989
+ return; // all affected rows are filtered out
990
+ const effectiveMinCol = minCol !== Infinity ? minCol : 0;
991
+ const effectiveMaxCol = maxCol !== Infinity ? maxCol : 0;
992
+ // Select the bounding box of the affected cells so the user can see exactly
993
+ // which cells changed. This mirrors the selection expansion that fill-handle
994
+ // does after applyFill.
995
+ const newRange = {
996
+ startRowIndex: minRow,
997
+ endRowIndex: maxRow,
998
+ startColIndex: effectiveMinCol,
999
+ endColIndex: effectiveMaxCol,
1000
+ };
1001
+ this.anchorCell = { rowIndex: minRow, colIndex: effectiveMinCol };
1002
+ this.store.set('cellRanges', [newRange]);
1003
+ this.store.set('activeCell', { rowIndex: minRow, colIndex: effectiveMinCol });
1004
+ this.emitSelectionChanged();
1005
+ // Scroll the top-left affected cell into the viewport.
1006
+ this.scrollToCellCallback?.(minRow, effectiveMinCol);
1007
+ // Flash the affected bounding box after the render RAF has rebuilt the cell
1008
+ // DOM. Double-RAF: the first RAF fires the pending render (triggered by
1009
+ // dataChangedCallback above), the second fires once those new cells are live.
1010
+ const fsr = minRow, fer = maxRow;
1011
+ const fsc = Math.max(0, effectiveMinCol), fec = Math.max(0, effectiveMaxCol);
1012
+ requestAnimationFrame(() => requestAnimationFrame(() => this.flashFillArea(fsr, fer, fsc, fec)));
1013
+ }
1014
+ // ─── Grouping helpers ─────────────────────────────────────────────────────
1015
+ /**
1016
+ * Returns the `field` name of the innermost (deepest) grouping column, or
1017
+ * `undefined` when no grouping is active.
1018
+ *
1019
+ * Reads from the store's full column list (including hidden columns) rather
1020
+ * than the visible-columns slice passed to copy/cut/paste methods — the
1021
+ * grouped column is often hidden from the regular column display (visible: false)
1022
+ * but the leaf group cell must still be able to copy its value.
1023
+ */
1024
+ getLeafGroupField() {
1025
+ const groupedIds = this.store.get('groupedColumnIds');
1026
+ if (groupedIds.length === 0)
1027
+ return undefined;
1028
+ const leafColId = groupedIds[groupedIds.length - 1];
1029
+ const allCols = this.store.get('columns');
1030
+ return allCols.find((c) => c.colId === leafColId)?.field;
1031
+ }
1032
+ // ─── Context menu ─────────────────────────────────────────────────────────
1033
+ showContextMenu(x, y) {
1034
+ if (!this.contextMenuEl)
1035
+ return;
1036
+ // Clamp to viewport
1037
+ const vw = window.innerWidth, vh = window.innerHeight;
1038
+ const mw = 200, mh = 300;
1039
+ const left = x + mw > vw ? vw - mw - 4 : x;
1040
+ const top = y + mh > vh ? vh - mh - 4 : y;
1041
+ this.contextMenuEl.style.left = `${left}px`;
1042
+ this.contextMenuEl.style.top = `${top}px`;
1043
+ this.contextMenuEl.classList.add('pg-context-menu--visible');
1044
+ requestAnimationFrame(() => {
1045
+ document.addEventListener('mousedown', this.boundHideCtx, { once: true });
1046
+ });
1047
+ }
1048
+ hideContextMenu() {
1049
+ this.contextMenuEl?.classList.remove('pg-context-menu--visible');
1050
+ }
1051
+ // ─── Private ─────────────────────────────────────────────────────────────
1052
+ flashSelection(type) {
1053
+ const cls = type === 'copy' ? 'pg-cell--copy-flash' : 'pg-cell--cut-flash';
1054
+ const cells = [];
1055
+ for (const panel of this.bodyPanels) {
1056
+ for (const el of panel.querySelectorAll('.pg-cell--in-selection, .pg-cell--active-cell')) {
1057
+ cells.push(el);
1058
+ }
1059
+ }
1060
+ for (const el of cells) {
1061
+ el.classList.remove('pg-cell--copy-flash', 'pg-cell--cut-flash');
1062
+ // Force reflow so removing+re-adding the class restarts the animation
1063
+ void el.offsetWidth;
1064
+ el.classList.add(cls);
1065
+ }
1066
+ setTimeout(() => {
1067
+ for (const el of cells)
1068
+ el.classList.remove(cls);
1069
+ }, 700);
1070
+ }
1071
+ clearCutCellsInDom(ranges) {
1072
+ const norms = ranges.map(normalizeRange);
1073
+ for (const panel of this.bodyPanels) {
1074
+ for (const el of panel.querySelectorAll('.pg-cell[data-row-index][data-col-index]')) {
1075
+ const ri = Number(el.getAttribute('data-row-index'));
1076
+ const ci = Number(el.getAttribute('data-col-index'));
1077
+ const hit = norms.some((n) => ri >= n.startRowIndex && ri <= n.endRowIndex && ci >= n.startColIndex && ci <= n.endColIndex);
1078
+ if (hit) {
1079
+ const inner = el.querySelector('.pg-cell__inner');
1080
+ if (inner)
1081
+ inner.textContent = '';
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+ getVisibleColumns() {
1087
+ return this.store.get('columns').filter((c) => c.visible !== false);
1088
+ }
1089
+ /**
1090
+ * Coerces `value` to the primitive type expected by `targetType`.
1091
+ *
1092
+ * Called by `applyFill` whenever the source column type differs from the
1093
+ * destination column type so that data remains correctly typed after a
1094
+ * cross-type fill — even if the result is `NaN` or an `Invalid Date`.
1095
+ *
1096
+ * | `targetType` | Conversion |
1097
+ * |---------------------------------|-------------------------------------|
1098
+ * | `'number'` / `'currency'` / | `Number(value)` — may produce `NaN` |
1099
+ * | `'percentage'` | |
1100
+ * | `'date'` | `new Date(String(value))` — may |
1101
+ * | | produce `Invalid Date` |
1102
+ * | `'boolean'` | `Boolean(value)` |
1103
+ * | `'string'` | `String(value)` if not already |
1104
+ * | anything else | unchanged |
1105
+ *
1106
+ * @param value - Raw value from the source cell.
1107
+ * @param targetType - `ColumnDef.type` of the destination column.
1108
+ */
1109
+ coerceToColumnType(value, targetType) {
1110
+ if (value == null)
1111
+ return value;
1112
+ switch (targetType) {
1113
+ case 'number':
1114
+ case 'currency':
1115
+ case 'percentage':
1116
+ return Number(value);
1117
+ case 'date':
1118
+ // Preserve existing Date objects with a safe copy; parse everything
1119
+ // else via the Date constructor — result may be an Invalid Date.
1120
+ return value instanceof Date
1121
+ ? new Date(value.getTime())
1122
+ : new Date(String(value));
1123
+ case 'boolean':
1124
+ return Boolean(value);
1125
+ case 'string':
1126
+ return typeof value === 'string' ? value : String(value);
1127
+ default:
1128
+ return value;
1129
+ }
1130
+ }
1131
+ onKeydown(e) {
1132
+ // Don't steal keyboard from input / editable elements
1133
+ const target = e.target;
1134
+ if (target.tagName === 'INPUT' ||
1135
+ target.tagName === 'TEXTAREA' ||
1136
+ target.contentEditable === 'true')
1137
+ return;
1138
+ const active = this.store.get('activeCell');
1139
+ if (!active)
1140
+ return;
1141
+ const jump = e.ctrlKey || e.metaKey;
1142
+ const key = e.key.length === 1 ? e.key.toLowerCase() : e.key;
1143
+ // Undo / redo operate on allRows and must not be blocked by an empty
1144
+ // visibleRows slice (e.g. all rows filtered out).
1145
+ if (jump && key === 'z') {
1146
+ e.preventDefault();
1147
+ this.performUndo();
1148
+ return;
1149
+ }
1150
+ if (jump && key === 'y') {
1151
+ e.preventDefault();
1152
+ this.performRedo();
1153
+ return;
1154
+ }
1155
+ const rows = this.store.get('visibleRows');
1156
+ const columns = this.getVisibleColumns();
1157
+ if (rows.length === 0 || columns.length === 0)
1158
+ return;
1159
+ const extend = e.shiftKey;
1160
+ switch (key) {
1161
+ case 'ArrowUp':
1162
+ e.preventDefault();
1163
+ jump
1164
+ ? this.jumpToEdge('up', rows.length, columns.length, extend)
1165
+ : this.moveActiveCell(-1, 0, rows.length, columns.length, extend);
1166
+ break;
1167
+ case 'ArrowDown':
1168
+ e.preventDefault();
1169
+ jump
1170
+ ? this.jumpToEdge('down', rows.length, columns.length, extend)
1171
+ : this.moveActiveCell(1, 0, rows.length, columns.length, extend);
1172
+ break;
1173
+ case 'ArrowLeft':
1174
+ e.preventDefault();
1175
+ if (!jump && !extend && this.treeToggleHandler) {
1176
+ const ac = active;
1177
+ const row = rows[ac.rowIndex];
1178
+ if (row && this.treeToggleHandler(row, 'left'))
1179
+ break;
1180
+ }
1181
+ if (jump) {
1182
+ this.jumpToEdge('left', rows.length, columns.length, extend);
1183
+ }
1184
+ else if (!extend) {
1185
+ const ac = active;
1186
+ if (ac.colIndex === 0 && ac.rowIndex > 0) {
1187
+ // Wrap: first column → last column of the previous row
1188
+ this.startSelection(ac.rowIndex - 1, columns.length - 1);
1189
+ }
1190
+ else {
1191
+ this.moveActiveCell(0, -1, rows.length, columns.length, false);
1192
+ }
1193
+ }
1194
+ else {
1195
+ this.moveActiveCell(0, -1, rows.length, columns.length, true);
1196
+ }
1197
+ break;
1198
+ case 'ArrowRight':
1199
+ e.preventDefault();
1200
+ if (!jump && !extend && this.treeToggleHandler) {
1201
+ const ac = active;
1202
+ const row = rows[ac.rowIndex];
1203
+ if (row && this.treeToggleHandler(row, 'right'))
1204
+ break;
1205
+ }
1206
+ if (jump) {
1207
+ this.jumpToEdge('right', rows.length, columns.length, extend);
1208
+ }
1209
+ else if (!extend) {
1210
+ const ac = active;
1211
+ if (ac.colIndex >= columns.length - 1 && ac.rowIndex < rows.length - 1) {
1212
+ // Wrap: last column → first column of the next row
1213
+ this.startSelection(ac.rowIndex + 1, 0);
1214
+ }
1215
+ else {
1216
+ this.moveActiveCell(0, 1, rows.length, columns.length, false);
1217
+ }
1218
+ }
1219
+ else {
1220
+ this.moveActiveCell(0, 1, rows.length, columns.length, true);
1221
+ }
1222
+ break;
1223
+ case 'PageDown': {
1224
+ e.preventDefault();
1225
+ const pageDownSize = this.getViewportRowCountCallback?.() ?? 10;
1226
+ this.moveActiveCell(pageDownSize, 0, rows.length, columns.length, extend);
1227
+ break;
1228
+ }
1229
+ case 'PageUp': {
1230
+ e.preventDefault();
1231
+ const pageUpSize = this.getViewportRowCountCallback?.() ?? 10;
1232
+ this.moveActiveCell(-pageUpSize, 0, rows.length, columns.length, extend);
1233
+ break;
1234
+ }
1235
+ case 'Home':
1236
+ e.preventDefault();
1237
+ if (jump) {
1238
+ // Ctrl+Home → absolute first cell (0, 0)
1239
+ this.startSelection(0, 0, extend);
1240
+ }
1241
+ else {
1242
+ // Home → first column in the current row
1243
+ this.moveActiveCell(0, -active.colIndex, rows.length, columns.length, extend);
1244
+ }
1245
+ break;
1246
+ case 'End':
1247
+ e.preventDefault();
1248
+ if (jump) {
1249
+ // Ctrl+End → absolute last cell
1250
+ this.startSelection(rows.length - 1, columns.length - 1, extend);
1251
+ }
1252
+ else {
1253
+ // End → last column in the current row
1254
+ this.moveActiveCell(0, columns.length - 1 - active.colIndex, rows.length, columns.length, extend);
1255
+ }
1256
+ break;
1257
+ case 'Tab': {
1258
+ e.preventDefault();
1259
+ const ac = active;
1260
+ if (e.shiftKey) {
1261
+ if (ac.colIndex === 0 && ac.rowIndex > 0) {
1262
+ this.startSelection(ac.rowIndex - 1, columns.length - 1);
1263
+ }
1264
+ else {
1265
+ this.moveActiveCell(0, -1, rows.length, columns.length, false);
1266
+ }
1267
+ }
1268
+ else {
1269
+ if (ac.colIndex >= columns.length - 1 && ac.rowIndex < rows.length - 1) {
1270
+ this.startSelection(ac.rowIndex + 1, 0);
1271
+ }
1272
+ else {
1273
+ this.moveActiveCell(0, 1, rows.length, columns.length, false);
1274
+ }
1275
+ }
1276
+ break;
1277
+ }
1278
+ case 'Enter':
1279
+ e.preventDefault();
1280
+ if (!e.shiftKey && this.enterEditHandler) {
1281
+ const ac = active;
1282
+ if (this.enterEditHandler(ac.rowIndex, ac.colIndex))
1283
+ break;
1284
+ }
1285
+ // Shift+Enter goes up; plain Enter goes down
1286
+ this.moveActiveCell(e.shiftKey ? -1 : 1, 0, rows.length, columns.length, false);
1287
+ break;
1288
+ case 'c':
1289
+ if (jump) {
1290
+ e.preventDefault();
1291
+ this.copySelection(rows, columns);
1292
+ }
1293
+ break;
1294
+ case 'x':
1295
+ if (jump) {
1296
+ e.preventDefault();
1297
+ this.cutSelection(rows, columns);
1298
+ }
1299
+ break;
1300
+ case 'v':
1301
+ if (jump) {
1302
+ e.preventDefault();
1303
+ this.pasteSelection(rows, columns);
1304
+ }
1305
+ break;
1306
+ case 'a':
1307
+ if (jump) {
1308
+ e.preventDefault();
1309
+ this.selectAll(rows.length, columns.length);
1310
+ }
1311
+ break;
1312
+ case 'Delete':
1313
+ case 'Backspace':
1314
+ // Delete / Backspace clears the selected range — same as Ctrl+X but
1315
+ // without writing to the clipboard.
1316
+ if (this.store.get('cellRanges').length > 0) {
1317
+ e.preventDefault();
1318
+ this.cutSelection(rows, columns);
1319
+ }
1320
+ break;
1321
+ case 'Escape':
1322
+ this.clearSelection();
1323
+ break;
1324
+ }
1325
+ }
1326
+ selectAll(rowCount, colCount) {
1327
+ // When grouping is active the auto-group column lives at virtual colIndex −1.
1328
+ // Ctrl+A must include it so all visible cells (group label + data columns)
1329
+ // are highlighted and can be copied in a single operation.
1330
+ const groupedIds = this.store.get('groupedColumnIds');
1331
+ const startCol = groupedIds.length > 0 ? -1 : 0;
1332
+ this.anchorCell = { rowIndex: 0, colIndex: startCol };
1333
+ this.store.set('cellRanges', [{
1334
+ startRowIndex: 0, endRowIndex: rowCount - 1,
1335
+ startColIndex: startCol, endColIndex: colCount - 1,
1336
+ }]);
1337
+ this.store.set('activeCell', { rowIndex: 0, colIndex: startCol });
1338
+ this.emitSelectionChanged();
1339
+ }
1340
+ buildContextMenu() {
1341
+ const ICON_CUT = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" y1="4" x2="8.12" y2="15.88"/><line x1="14.47" y1="14.48" x2="20" y2="20"/><line x1="8.12" y1="8.12" x2="12" y2="12"/></svg>`;
1342
+ const ICON_COPY = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
1343
+ const ICON_PASTE = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/></svg>`;
1344
+ const ICON_CHART = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>`;
1345
+ const ICON_EXPORT = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
1346
+ const el = document.createElement('div');
1347
+ el.className = 'pg-context-menu';
1348
+ el.setAttribute('role', 'menu');
1349
+ const makeItem = (action, icon, label, kbd) => {
1350
+ const btn = document.createElement('button');
1351
+ btn.className = 'pg-context-menu__item';
1352
+ btn.setAttribute('role', 'menuitem');
1353
+ btn.setAttribute('data-action', action);
1354
+ const iconSpan = document.createElement('span');
1355
+ iconSpan.className = 'pg-context-menu__icon';
1356
+ iconSpan.innerHTML = icon;
1357
+ const labelSpan = document.createElement('span');
1358
+ labelSpan.className = 'pg-context-menu__label';
1359
+ labelSpan.textContent = label;
1360
+ btn.appendChild(iconSpan);
1361
+ btn.appendChild(labelSpan);
1362
+ if (kbd) {
1363
+ const kbdSpan = document.createElement('span');
1364
+ kbdSpan.className = 'pg-context-menu__kbd';
1365
+ kbdSpan.textContent = kbd;
1366
+ btn.appendChild(kbdSpan);
1367
+ }
1368
+ return btn;
1369
+ };
1370
+ const makeSep = () => {
1371
+ const sep = document.createElement('div');
1372
+ sep.className = 'pg-context-menu__sep';
1373
+ sep.setAttribute('role', 'separator');
1374
+ return sep;
1375
+ };
1376
+ const makeChartSubItem = (type, label) => {
1377
+ const btn = document.createElement('button');
1378
+ btn.className = 'pg-context-menu__item';
1379
+ btn.setAttribute('role', 'menuitem');
1380
+ btn.setAttribute('data-chart-type', type);
1381
+ const labelSpan = document.createElement('span');
1382
+ labelSpan.className = 'pg-context-menu__label';
1383
+ labelSpan.textContent = label;
1384
+ btn.appendChild(labelSpan);
1385
+ return btn;
1386
+ };
1387
+ const makeSubGroup = (label, items) => {
1388
+ const wrapper = document.createElement('div');
1389
+ wrapper.className = 'pg-context-menu__item pg-context-menu__item--has-sub';
1390
+ wrapper.setAttribute('role', 'menuitem');
1391
+ const labelSpan = document.createElement('span');
1392
+ labelSpan.className = 'pg-context-menu__label';
1393
+ labelSpan.textContent = label;
1394
+ wrapper.appendChild(labelSpan);
1395
+ const sub = document.createElement('div');
1396
+ sub.className = 'pg-context-menu__sub';
1397
+ for (const item of items)
1398
+ sub.appendChild(item);
1399
+ wrapper.appendChild(sub);
1400
+ return wrapper;
1401
+ };
1402
+ // Cut / Copy / Copy with Headers / Paste
1403
+ el.appendChild(makeItem('cut', ICON_CUT, 'Cut', 'Ctrl+X'));
1404
+ el.appendChild(makeItem('copy', ICON_COPY, 'Copy', 'Ctrl+C'));
1405
+ el.appendChild(makeItem('copy-headers', ICON_COPY, 'Copy with Headers'));
1406
+ el.appendChild(makeItem('paste', ICON_PASTE, 'Paste', 'Ctrl+V'));
1407
+ el.appendChild(makeSep());
1408
+ // Chart Range with nested submenu
1409
+ const chartItem = document.createElement('div');
1410
+ chartItem.className = 'pg-context-menu__item pg-context-menu__item--has-sub';
1411
+ chartItem.setAttribute('role', 'menuitem');
1412
+ const chartIcon = document.createElement('span');
1413
+ chartIcon.className = 'pg-context-menu__icon';
1414
+ chartIcon.innerHTML = ICON_CHART;
1415
+ const chartLabel = document.createElement('span');
1416
+ chartLabel.className = 'pg-context-menu__label';
1417
+ chartLabel.textContent = 'Chart Range';
1418
+ chartItem.appendChild(chartIcon);
1419
+ chartItem.appendChild(chartLabel);
1420
+ const chartSub = document.createElement('div');
1421
+ chartSub.className = 'pg-context-menu__sub';
1422
+ // Column sub-group
1423
+ chartSub.appendChild(makeSubGroup('Column', [
1424
+ makeChartSubItem('column-grouped', 'Grouped'),
1425
+ makeChartSubItem('column-stacked', 'Stacked'),
1426
+ makeChartSubItem('column-100stacked', '100% Stacked'),
1427
+ ]));
1428
+ // Bar sub-group
1429
+ chartSub.appendChild(makeSubGroup('Bar', [
1430
+ makeChartSubItem('bar-grouped', 'Grouped'),
1431
+ makeChartSubItem('bar-stacked', 'Stacked'),
1432
+ makeChartSubItem('bar-100stacked', '100% Stacked'),
1433
+ ]));
1434
+ chartSub.appendChild(makeChartSubItem('pie', 'Pie'));
1435
+ chartSub.appendChild(makeChartSubItem('line', 'Line'));
1436
+ chartSub.appendChild(makeChartSubItem('area', 'Area'));
1437
+ chartSub.appendChild(makeChartSubItem('scatter', 'X Y (Scatter)'));
1438
+ chartSub.appendChild(makeChartSubItem('polar', 'Polar'));
1439
+ chartSub.appendChild(makeChartSubItem('funnel', 'Funnel'));
1440
+ chartItem.appendChild(chartSub);
1441
+ el.appendChild(chartItem);
1442
+ el.appendChild(makeSep());
1443
+ // Export sub-group
1444
+ const exportItem = document.createElement('div');
1445
+ exportItem.className = 'pg-context-menu__item pg-context-menu__item--has-sub';
1446
+ exportItem.setAttribute('role', 'menuitem');
1447
+ const exportIcon = document.createElement('span');
1448
+ exportIcon.className = 'pg-context-menu__icon';
1449
+ exportIcon.innerHTML = ICON_EXPORT;
1450
+ const exportLabel = document.createElement('span');
1451
+ exportLabel.className = 'pg-context-menu__label';
1452
+ exportLabel.textContent = 'Export';
1453
+ exportItem.appendChild(exportIcon);
1454
+ exportItem.appendChild(exportLabel);
1455
+ const exportSub = document.createElement('div');
1456
+ exportSub.className = 'pg-context-menu__sub';
1457
+ const csvBtn = document.createElement('button');
1458
+ csvBtn.className = 'pg-context-menu__item';
1459
+ csvBtn.setAttribute('role', 'menuitem');
1460
+ csvBtn.setAttribute('data-action', 'export-csv');
1461
+ const csvLabel = document.createElement('span');
1462
+ csvLabel.className = 'pg-context-menu__label';
1463
+ csvLabel.textContent = 'Export as CSV';
1464
+ csvBtn.appendChild(csvLabel);
1465
+ exportSub.appendChild(csvBtn);
1466
+ exportItem.appendChild(exportSub);
1467
+ el.appendChild(exportItem);
1468
+ el.addEventListener('mousedown', (e) => e.stopPropagation());
1469
+ el.addEventListener('click', (e) => {
1470
+ const btn = e.target.closest('[data-action],[data-chart-type]');
1471
+ if (!btn)
1472
+ return;
1473
+ // Don't close if it's a submenu parent clicked but not an actual action
1474
+ const action = btn.getAttribute('data-action');
1475
+ const chartType = btn.getAttribute('data-chart-type');
1476
+ if (!action && !chartType)
1477
+ return;
1478
+ this.hideContextMenu();
1479
+ const rows = this.store.get('visibleRows');
1480
+ const columns = this.getVisibleColumns();
1481
+ if (chartType) {
1482
+ this.chartOpenCallback?.(chartType);
1483
+ return;
1484
+ }
1485
+ switch (action) {
1486
+ case 'cut':
1487
+ this.cutSelection(rows, columns);
1488
+ break;
1489
+ case 'copy':
1490
+ this.copySelection(rows, columns);
1491
+ break;
1492
+ case 'copy-headers':
1493
+ this.copySelectionWithHeaders(rows, columns);
1494
+ break;
1495
+ case 'paste':
1496
+ this.pasteSelection(rows, columns);
1497
+ break;
1498
+ case 'selectAll':
1499
+ this.selectAll(rows.length, columns.length);
1500
+ break;
1501
+ case 'export-csv':
1502
+ this.exportAsCsv(rows, columns);
1503
+ break;
1504
+ }
1505
+ });
1506
+ document.body.appendChild(el);
1507
+ this.contextMenuEl = el;
1508
+ }
1509
+ exportAsCsv(rows, columns) {
1510
+ const header = columns.map((c) => `"${c.header}"`).join(',');
1511
+ const body = rows
1512
+ .filter((r) => r.type === 'data')
1513
+ .map((row) => columns.map((col) => {
1514
+ const v = row.data[col.field];
1515
+ const s = v == null ? '' : String(v);
1516
+ return `"${s.replace(/"/g, '""')}"`;
1517
+ }).join(','))
1518
+ .join('\n');
1519
+ const csv = `${header}\n${body}`;
1520
+ const blob = new Blob([csv], { type: 'text/csv' });
1521
+ const url = URL.createObjectURL(blob);
1522
+ const a = document.createElement('a');
1523
+ a.href = url;
1524
+ a.download = 'export.csv';
1525
+ a.click();
1526
+ URL.revokeObjectURL(url);
1527
+ }
1528
+ emitSelectionChanged() {
1529
+ this.eventBus.emit(GridEventType.CELL_SELECTION_CHANGED, {
1530
+ ranges: this.store.get('cellRanges'),
1531
+ });
1532
+ }
1533
+ }
1534
+ //# sourceMappingURL=cell-selection-engine.js.map