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,1456 @@
1
+ import { ColumnGroupDragHandler } from '../column-groups/column-group-drag-handler';
2
+ import { GridEventType } from '../types/event.types';
3
+ import { GroupDropZone } from './group-drop-zone';
4
+ import { HeaderRenderer } from './header-renderer';
5
+ import { BodyRenderer } from './body-renderer';
6
+ import { RowDragRenderer } from './row-drag-renderer';
7
+ import { FooterRenderer } from './footer-renderer';
8
+ import { OverlayRenderer } from './overlay-renderer';
9
+ import { ColumnStyleManager } from './column-style-manager';
10
+ import { RowPositionSheet } from './row-position-sheet';
11
+ import { ScrollController } from './scroll-controller';
12
+ import { AutoScroller } from './auto-scroller';
13
+ import { RowAnimator } from './row-animator';
14
+ import { FilterPanel } from '../engines/filter/filter-panel';
15
+ import { createDiv } from './dom-utils';
16
+ import { DetailRowRenderer } from './detail-row-renderer';
17
+ import { StickyRowTracker } from './sticky-row-tracker';
18
+ import { TreeStickyRowTracker } from './tree-sticky-row-tracker';
19
+ import { PhotonAIPanel } from '../photon-ai/photon-ai-panel';
20
+ import { TooltipController } from './tooltip-renderer';
21
+ const CHECKBOX_COL_WIDTH = 44;
22
+ const SERIAL_COL_WIDTH = 52;
23
+ const ROW_BUFFER = 5;
24
+ const COL_BUFFER = 2;
25
+ const AUTO_GROUP_COL_WIDTH = 200;
26
+ export class GridRenderer {
27
+ constructor(containerEl, store, eventBus, columnModel, paginationEngine, iconRenderer, cellSelectionEngine, sortEngine, rowSelectionEngine, groupingEngine, options) {
28
+ this.containerEl = containerEl;
29
+ this.store = store;
30
+ this.eventBus = eventBus;
31
+ this.columnModel = columnModel;
32
+ this.paginationEngine = paginationEngine;
33
+ this.iconRenderer = iconRenderer;
34
+ this.cellSelectionEngine = cellSelectionEngine;
35
+ this.sortEngine = sortEngine;
36
+ this.rowSelectionEngine = rowSelectionEngine;
37
+ this.groupingEngine = groupingEngine;
38
+ this.options = options;
39
+ this.wrapperEl = null;
40
+ // Header panel elements
41
+ this.leftHeaderPanelEl = null;
42
+ this.centerHeaderInnerEl = null;
43
+ this.rightHeaderPanelEl = null;
44
+ // Body panel elements
45
+ this.leftBodyPanelEl = null;
46
+ this.centerBodyEl = null; // the .pg-panel__body for viewport size
47
+ this.centerBodyContentEl = null;
48
+ this.rightBodyPanelEl = null;
49
+ this.leftBodyContentEl = null;
50
+ this.rightBodyContentEl = null;
51
+ this.footerContainerEl = null;
52
+ this.bodyWrapEl = null;
53
+ // Sticky-row overlay layers — one per panel, siblings of `*ContentEl` (not
54
+ // children), so a stuck Master/Detail row ignores the scroll transform.
55
+ // Only created when `masterDetail.enabled`, per `masterDetailEnabledAtConstruction`.
56
+ this.leftStickyRowEl = null;
57
+ this.centerStickyRowEl = null;
58
+ this.rightStickyRowEl = null;
59
+ /** `nodeId` of the currently-stuck master row, or `null` when none is sticky. */
60
+ this.stickyNodeId = null;
61
+ this.stickyRowTracker = new StickyRowTracker();
62
+ this.treeStickyRowTracker = new TreeStickyRowTracker();
63
+ this.groupDropZone = null;
64
+ this.rowDragRenderer = null;
65
+ this.treeDragConfig = null;
66
+ this.detailRowRenderer = null;
67
+ this.masterDetailEngine = null;
68
+ this.treeExpansionService = null;
69
+ this.treeToggleColumnId = null;
70
+ /** Floating Photon AI command bar — only created when `photonAI.enabled`. */
71
+ this.photonAIPanel = null;
72
+ this.rafId = null;
73
+ this.autoScroller = null;
74
+ this.unsubscribers = [];
75
+ this.headerRendered = false;
76
+ this.lastCenterColStart = -1;
77
+ this.lastCenterColEnd = -1;
78
+ this.rowAnimator = new RowAnimator();
79
+ // ── Column-group support ──────────────────────────────────────────────────
80
+ this.columnGroupModel = null;
81
+ this.groupHeaderBuilder = null;
82
+ this.groupDragHandler = null;
83
+ /** New Display Group Engine — takes priority over the legacy ColumnGroupModel when set. */
84
+ this.displayGroupEngine = null;
85
+ // ── Filter panel management ────────────────────────────────────────────────
86
+ this.filterEngine = null;
87
+ this.filterRefreshFn = null;
88
+ this.activeFilterPanel = null;
89
+ // ── Render caches ─────────────────────────────────────────────────────────
90
+ // Column/row computation is skipped on scroll-only frames by comparing the
91
+ // store array reference — a new reference means data/columns actually changed.
92
+ /** Last `columns` array reference seen — guards column-width recomputation. */
93
+ this._lastColumnsRef = null;
94
+ /** Last `groupedColumnIds` array reference seen — guards grouping recomputation. */
95
+ this._lastGroupedIdsRef = null;
96
+ /** Last `visibleRows` array reference seen — guards total-height recomputation. */
97
+ this._lastRowsRef = null;
98
+ /** Cached total content height in pixels (sum of all visible row heights). */
99
+ this._cachedTotalHeight = 0;
100
+ /** Cached center-panel content width in pixels. */
101
+ this._cachedCenterW = 0;
102
+ /**
103
+ * Center-panel `clientWidth` last used to resolve `flex` columns. Flex
104
+ * widths are normally only re-resolved when the columns array itself
105
+ * changes (cheap, guards the 60fps scroll path) — but the container can
106
+ * also resize with the columns reference untouched, e.g. a vertical
107
+ * scrollbar transiently appearing/disappearing as a Master/Detail row is
108
+ * inserted. Comparing against this on every render (a single cheap
109
+ * `clientWidth` read, only paid by grids that actually use `flex` columns)
110
+ * catches that case so flex columns don't get stuck sized for a stale width.
111
+ */
112
+ this._lastFlexResolvedWidth = -1;
113
+ this.colStyles = new ColumnStyleManager();
114
+ this.rowPositionSheet = new RowPositionSheet();
115
+ this.scrollController = new ScrollController();
116
+ this.masterDetailEnabledAtConstruction = options.masterDetail?.enabled ?? false;
117
+ this.treeDataEnabledAtConstruction = options.treeData?.enabled ?? false;
118
+ if (this.masterDetailEnabledAtConstruction) {
119
+ this.scrollController.setReserveVerticalGutter(true);
120
+ }
121
+ this.headerRenderer = new HeaderRenderer(store, eventBus, iconRenderer, columnModel, sortEngine, this.colStyles);
122
+ if (options.showGroupingBar) {
123
+ this.groupDropZone = new GroupDropZone(store, groupingEngine, iconRenderer);
124
+ this.headerRenderer.setGroupDropZone(this.groupDropZone);
125
+ }
126
+ this.bodyRenderer = new BodyRenderer(store, eventBus, iconRenderer, rowSelectionEngine);
127
+ this.footerRenderer = new FooterRenderer(eventBus, iconRenderer, paginationEngine);
128
+ this.overlayRenderer = new OverlayRenderer(iconRenderer);
129
+ if (options.masterDetail?.enabled) {
130
+ this.detailRowRenderer = new DetailRowRenderer();
131
+ // Nested grids never scroll themselves via mouse wheel — every wheel
132
+ // gesture over one drives the parent's own scroll instead, so the
133
+ // parent + detail sections read as one continuous scrollable surface.
134
+ this.detailRowRenderer.setParentScrollForwarder((delta) => {
135
+ this.scrollController.scrollToY(this.scrollController.getScrollTop() + delta);
136
+ });
137
+ }
138
+ if (options.photonAI?.enabled) {
139
+ this.photonAIPanel = new PhotonAIPanel(iconRenderer);
140
+ }
141
+ this.tooltipController = new TooltipController(store, columnModel, null);
142
+ }
143
+ /**
144
+ * Enables Tree Data drag-to-reparent on the row-drag system. Must be
145
+ * called before `mount()` (mirrors `setMasterDetailConfig`) — `mount()`
146
+ * is when `RowDragRenderer` is actually constructed.
147
+ */
148
+ setTreeDragConfig(active, reparentHandler) {
149
+ this.treeDragConfig = { active, reparentHandler };
150
+ }
151
+ /** Wires Tree Data's expansion state + toggle column into the body renderer, so `data-level` indentation and the expand/collapse toggle render on the configured column. A no-op (undefined `treeData` on every `renderRows` call) until this is called. */
152
+ setTreeRenderConfig(toggleColumnId, expansionService) {
153
+ this.treeToggleColumnId = toggleColumnId ?? null;
154
+ this.treeExpansionService = expansionService;
155
+ }
156
+ mount() {
157
+ this.colStyles.mount();
158
+ this.rowPositionSheet.mount();
159
+ this.buildLayout();
160
+ if (this.wrapperEl && this.bodyWrapEl) {
161
+ this.rowDragRenderer = new RowDragRenderer(this.store, this.eventBus, this.iconRenderer);
162
+ this.rowDragRenderer.mount(this.wrapperEl, this.bodyWrapEl, (dy) => this.scrollController.scrollToY(this.scrollController.getScrollTop() + dy));
163
+ if (this.treeDragConfig) {
164
+ this.rowDragRenderer.setTreeMode(this.treeDragConfig.active, this.treeDragConfig.reparentHandler);
165
+ }
166
+ }
167
+ this.subscribeToStore();
168
+ this.scheduleRender();
169
+ }
170
+ scheduleRender() {
171
+ if (this.rafId !== null)
172
+ return;
173
+ this.rafId = requestAnimationFrame(() => {
174
+ this.rafId = null;
175
+ this.performRender();
176
+ });
177
+ }
178
+ forceRender() {
179
+ if (this.rafId !== null) {
180
+ cancelAnimationFrame(this.rafId);
181
+ this.rafId = null;
182
+ }
183
+ this.performRender();
184
+ }
185
+ /**
186
+ * Clears the body-renderer's row cache so the next render fully rebuilds every
187
+ * visible row from the data model. Use this after in-place data mutations
188
+ * (paste, cut) where the `visibleRows` reference is unchanged but cell values
189
+ * have been updated — `updatePanelRow` only refreshes row-level classes, not
190
+ * cell content, so a cache invalidation + re-render is required.
191
+ */
192
+ invalidateBodyRows() {
193
+ this.bodyRenderer.clear();
194
+ this.scheduleRender();
195
+ }
196
+ /**
197
+ * Evicts only the rows with the given node IDs from the render cache and
198
+ * schedules a repaint. All other rows keep their cached DOM elements so
199
+ * custom cell renderers (images, flags, etc.) are not needlessly re-executed.
200
+ *
201
+ * Prefer this over `invalidateBodyRows` whenever the set of mutated rows is
202
+ * known (fill, cut, paste, undo/redo).
203
+ *
204
+ * @param nodeIds - Node IDs of the rows whose cache entries should be evicted.
205
+ */
206
+ invalidateBodyRowsByIds(nodeIds) {
207
+ this.bodyRenderer.invalidateRowsByNodeId(nodeIds);
208
+ this.scheduleRender();
209
+ }
210
+ // ── Filter panel public API ────────────────────────────────────────────────
211
+ /**
212
+ * Provides the renderer with a `FilterEngine` reference so it can read the
213
+ * current filter model and write column filters when the user interacts with
214
+ * the filter panel. Called from `GridApi` after construction.
215
+ */
216
+ setFilterEngine(engine) {
217
+ this.filterEngine = engine;
218
+ }
219
+ /**
220
+ * Registers a callback that runs the full sort/filter pipeline and triggers
221
+ * a render whenever the filter state changes from within the panel.
222
+ * Called from `GridApi` after construction.
223
+ */
224
+ setFilterRefreshCallback(fn) {
225
+ this.filterRefreshFn = fn;
226
+ }
227
+ /**
228
+ * Wire the column-group model and header builder into the renderer.
229
+ *
230
+ * Must be called **before** the first `mount()` so that `renderInPanels`
231
+ * can insert group header rows above the leaf row. Called by `GridCore`
232
+ * when any top-level `ColumnDef` has a `children` array.
233
+ *
234
+ * @param model - The live tree model.
235
+ * @param builder - The DOM builder instance.
236
+ */
237
+ setColumnGroupModel(model, builder) {
238
+ this.columnGroupModel = model;
239
+ this.groupHeaderBuilder = builder;
240
+ // Create the drag handler — gridElGetter returns wrapperEl which is set
241
+ // during mount(); drag can only start after mount so it is always non-null.
242
+ this.groupDragHandler = new ColumnGroupDragHandler(this.columnModel, model, builder, this.colStyles, this.eventBus, () => this.wrapperEl);
243
+ // Wire drag handler into builder so every group cell gets drag listeners
244
+ builder.setDragConfig(this.groupDragHandler, () => this.wrapperEl);
245
+ // Forward references into HeaderRenderer
246
+ this.headerRenderer.setColumnGroupModel(model, builder);
247
+ this.headerRenderer.setGroupDragHandler(this.groupDragHandler);
248
+ // Wire collapse/expand: toggle model state, sync leaf visibility, rebuild header
249
+ this.headerRenderer.setGroupToggleCallback((groupId) => {
250
+ this.handleGroupToggle(groupId);
251
+ });
252
+ // Wire group resize: distribute delta proportionally across all leaf columns
253
+ this.headerRenderer.setGroupResizeCallback((groupId, newWidth) => {
254
+ this.handleGroupResize(groupId, newWidth);
255
+ });
256
+ }
257
+ /**
258
+ * Wire the new Display Group Engine into the renderer.
259
+ *
260
+ * Creates the drag handler, forwards the engine into `HeaderRenderer`, and
261
+ * subscribes to the events that trigger header rebuilds. Must be called
262
+ * before `mount()` when the grid's column definitions contain groups.
263
+ *
264
+ * Takes priority over the legacy `setColumnGroupModel` path.
265
+ *
266
+ * @param engine - Fully-initialised `DisplayGroupEngine` instance.
267
+ */
268
+ setDisplayGroupEngine(engine) {
269
+ this.displayGroupEngine = engine;
270
+ // Create the drag handler bound to this engine
271
+ engine.createDragHandler(() => this.wrapperEl);
272
+ // Forward engine into HeaderRenderer
273
+ this.headerRenderer.setDisplayGroupEngine(engine);
274
+ // Wire toggle/resize callbacks (HeaderRenderer fires these on user interaction)
275
+ this.headerRenderer.setGroupToggleCallback((groupId) => {
276
+ this.handleGroupToggle(groupId);
277
+ });
278
+ this.headerRenderer.setGroupResizeCallback((groupId, newWidth) => {
279
+ this.handleGroupResize(groupId, newWidth);
280
+ });
281
+ // Subscribe to group collapse/expand events from the event bus
282
+ this.unsubscribers.push(this.eventBus.on(GridEventType.COLUMN_GROUP_HEADER_COLLAPSED, () => {
283
+ this.rebuildHeader();
284
+ }), this.eventBus.on(GridEventType.COLUMN_GROUP_HEADER_EXPANDED, () => {
285
+ this.rebuildHeader();
286
+ }),
287
+ // Group drag-drop (and leaf clone) completion fires COLUMN_MOVED with
288
+ // fromIndex === -1 as a sentinel. Sync the flat store.columns order from
289
+ // the group tree so body rows reflect the new column sequence, then rebuild.
290
+ this.eventBus.on(GridEventType.COLUMN_MOVED, (payload) => {
291
+ const p = payload;
292
+ if (p?.fromIndex === -1) {
293
+ if (this.columnGroupModel) {
294
+ const newLeaves = this.columnGroupModel.getAllLeaves();
295
+ const storeColumns = this.store.get('columns');
296
+ const colMap = new Map(storeColumns.map((c) => [c.colId, c]));
297
+ const reordered = newLeaves
298
+ .map((l) => colMap.get(l.colId))
299
+ .filter((c) => c !== undefined);
300
+ // Only update the store when the order actually changed so we don't
301
+ // trigger an unnecessary store.watch('columns') rebuild cycle.
302
+ const orderChanged = reordered.some((c, i) => c.colId !== storeColumns[i]?.colId);
303
+ if (orderChanged) {
304
+ this.store.set('columns', reordered);
305
+ }
306
+ }
307
+ this.bodyRenderer.clear();
308
+ this.rebuildHeader();
309
+ }
310
+ }));
311
+ }
312
+ /**
313
+ * Opens (or replaces) the floating filter panel for the given column.
314
+ * Called by `HeaderRenderer` when the user clicks a column's filter icon.
315
+ *
316
+ * @param colDef - Column definition the filter applies to.
317
+ * @param anchorEl - Filter-icon button element — panel positions below this.
318
+ */
319
+ openFilterPanel(colDef, anchorEl) {
320
+ if (!this.filterEngine || !this.wrapperEl)
321
+ return;
322
+ // If the same column's panel is already open, close it (toggle behaviour)
323
+ if (this.activeFilterPanel) {
324
+ this.activeFilterPanel.destroy();
325
+ this.activeFilterPanel = null;
326
+ return;
327
+ }
328
+ const currentFilter = this.filterEngine.getFilterModel()[colDef.colId] ?? null;
329
+ const uniqueOptions = this.extractUniqueOptions(colDef);
330
+ this.activeFilterPanel = new FilterPanel({
331
+ colDef,
332
+ anchorEl,
333
+ containerEl: this.wrapperEl,
334
+ currentFilter,
335
+ uniqueOptions,
336
+ onFilterChange: (filter) => {
337
+ this.filterEngine.setColumnFilter(colDef.colId, filter);
338
+ this.filterRefreshFn?.();
339
+ },
340
+ onClose: () => {
341
+ this.activeFilterPanel = null;
342
+ },
343
+ });
344
+ this.activeFilterPanel.open();
345
+ }
346
+ /**
347
+ * Extracts unique display value/label pairs for set-type (dropdown / array)
348
+ * filter panels. For `dropdown` columns the predefined `dropdownOptions`
349
+ * are used directly; for other types unique values are scanned from `allRows`.
350
+ */
351
+ extractUniqueOptions(colDef) {
352
+ // Dropdown: use predefined options list
353
+ if (colDef.type === 'dropdown' && colDef.dropdownOptions?.length) {
354
+ return colDef.dropdownOptions.map((o) => ({
355
+ value: String(o.value),
356
+ label: o.label ?? String(o.value),
357
+ }));
358
+ }
359
+ const allRows = this.store.get('allRows');
360
+ const field = colDef.field;
361
+ const parts = field.split('.');
362
+ const nested = field.includes('.');
363
+ const seen = new Set();
364
+ for (const row of allRows) {
365
+ if (row.type !== 'data')
366
+ continue;
367
+ let val;
368
+ if (nested) {
369
+ val = row.data;
370
+ for (const part of parts) {
371
+ if (val == null)
372
+ break;
373
+ val = val[part];
374
+ }
375
+ }
376
+ else {
377
+ val = row.data[field];
378
+ }
379
+ if (Array.isArray(val)) {
380
+ for (const v of val) {
381
+ if (v != null && v !== '')
382
+ seen.add(String(v));
383
+ }
384
+ }
385
+ else if (val != null && val !== '') {
386
+ seen.add(String(val));
387
+ }
388
+ }
389
+ return Array.from(seen)
390
+ .sort((a, b) => a.localeCompare(b))
391
+ .map((v) => ({ value: v, label: v }));
392
+ }
393
+ /**
394
+ * Snapshot current row positions so the next render animates the transition.
395
+ * Call this **before** any pipeline that reorders or hides rows.
396
+ *
397
+ * @param rows - Current visible rows before the pipeline runs.
398
+ * @param type - `'sort'` (default) or `'filter'` — controls duration and entrance style.
399
+ */
400
+ captureRowAnimation(rows, type = 'sort') {
401
+ this.rowAnimator.capture(rows, type);
402
+ }
403
+ /** Wire up the group-bar search input to an external handler (e.g. api.setQuickFilter). */
404
+ setSearchCallback(fn) {
405
+ this.groupDropZone?.setSearchCallback(fn);
406
+ }
407
+ /**
408
+ * Wires the Master/Detail engine and nested-grid factory into the renderer.
409
+ * A no-op when `masterDetail.enabled` was falsy at construction (the
410
+ * `DetailRowRenderer` instance was never created). Called once from
411
+ * `GridCore.buildContext`, before `mount()`.
412
+ */
413
+ setMasterDetailConfig(engine, nestedGridFactory, iconRenderer, themeManager) {
414
+ this.masterDetailEngine = engine;
415
+ this.detailRowRenderer?.setDependencies(engine, nestedGridFactory, iconRenderer, themeManager);
416
+ }
417
+ /**
418
+ * Late-bound once the owning `GridCore`'s `GridApi` exists — passed through
419
+ * to `masterDetail.detailRendererFn` as `DetailRendererParams.parentApi`.
420
+ */
421
+ setParentApiForDetail(api) {
422
+ this.detailRowRenderer?.setParentApi(api);
423
+ }
424
+ /** The nested grid's `GridApi` for an expanded master row, or `undefined`. Backs `GridApi.getDetailGridApi`. */
425
+ getDetailGridApi(parentNodeId) {
426
+ return this.detailRowRenderer?.getNestedInstance(parentNodeId)?.api;
427
+ }
428
+ /**
429
+ * Wires the callback the Photon AI panel's send button/Enter key invokes —
430
+ * late-bound once the owning `GridCore`'s `GridApi` (and therefore its
431
+ * `PhotonAIService`) exists. A no-op when `photonAI.enabled` was falsy at
432
+ * construction (the panel was never created).
433
+ */
434
+ setPhotonAISubmitHandler(fn) {
435
+ this.photonAIPanel?.setSubmitHandler(fn);
436
+ }
437
+ /** Programmatic entry point mirroring the panel's own UI — backs `GridApi.submitAICommand`. */
438
+ submitAICommand(text) {
439
+ return this.photonAIPanel?.invoke(text)
440
+ ?? { success: false, message: 'Photon AI is not enabled on this grid.' };
441
+ }
442
+ /**
443
+ * Starts the shrink/fade-out animation for `parentNodeId`'s detail row.
444
+ * Must be called synchronously **before** the pipeline re-runs and removes
445
+ * the row — see `DetailRowRenderer.beginCollapse` for why the timing matters.
446
+ */
447
+ beginDetailCollapse(parentNodeId) {
448
+ this.detailRowRenderer?.beginCollapse(parentNodeId);
449
+ }
450
+ scrollToRow(rowIndex) {
451
+ const rows = this.store.get('visibleRows');
452
+ this.scrollController.scrollToRow(rowIndex, rows);
453
+ }
454
+ scrollToTop() {
455
+ this.scrollController.scrollToTop();
456
+ }
457
+ /** Whether the body can still scroll further up. Used by a Master/Detail parent to chain wheel scroll into this grid before forwarding it further up itself. */
458
+ canScrollUp() {
459
+ return this.scrollController.canScrollUp();
460
+ }
461
+ /** Whether the body can still scroll further down. */
462
+ canScrollDown() {
463
+ return this.scrollController.canScrollDown();
464
+ }
465
+ /**
466
+ * Scrolls the grid body (vertically and horizontally) so that the cell at
467
+ * `rowIndex` / `colIndex` is fully visible — mirrors AG Grid's auto-scroll
468
+ * behaviour on keyboard navigation.
469
+ *
470
+ * - For pinned-left/right columns only vertical scrolling is applied.
471
+ * - For center columns both axes are adjusted when the cell is out of view.
472
+ *
473
+ * @param rowIndex - Index into `visibleRows`
474
+ * @param colIndex - Index in the flat visible-columns array (left + center + right)
475
+ */
476
+ scrollToCell(rowIndex, colIndex) {
477
+ const rows = this.store.get('visibleRows');
478
+ const allCols = this.store.get('columns').filter((c) => c.visible !== false);
479
+ const row = rows[rowIndex];
480
+ if (!row)
481
+ return;
482
+ // colIndex −1 is the virtual auto-group label column — vertical scroll is
483
+ // still needed, but there is no horizontal column descriptor for it.
484
+ const col = colIndex >= 0 ? allCols[colIndex] : null;
485
+ // ── Vertical ──────────────────────────────────────────────────────────────
486
+ // A Master/Detail sticky row is already fully visible — pinned at the
487
+ // viewport's top — even though its logical `row.top` sits above the current
488
+ // scroll position. Auto-scrolling it into view would scroll UP to reveal
489
+ // its real (non-sticky) position, un-sticking it and jarringly jumping the
490
+ // grid (and its nested detail) on a mere cell click. Skip vertical scroll
491
+ // for it; horizontal scroll below still applies normally.
492
+ if (row.nodeId !== this.stickyNodeId) {
493
+ const rowH = row.height ?? (this.options.rowHeight ?? 48);
494
+ const scrollTop = this.scrollController.getScrollTop();
495
+ const vpH = this.scrollController.getViewportHeight();
496
+ if (row.top < scrollTop) {
497
+ this.scrollController.scrollToY(row.top);
498
+ }
499
+ else if (row.top + rowH > scrollTop + vpH) {
500
+ this.scrollController.scrollToY(row.top + rowH - vpH);
501
+ }
502
+ }
503
+ // ── Horizontal (center columns only) ──────────────────────────────────────
504
+ // col is null for the virtual auto-group label column — it is always visible
505
+ // within the fixed-width group column area, so no horizontal scroll is needed.
506
+ if (!col || col.pinned === 'left' || col.pinned === 'right')
507
+ return;
508
+ const leftCols = allCols.filter((c) => c.pinned === 'left');
509
+ const centerCols = allCols.filter((c) => c.pinned !== 'left' && c.pinned !== 'right');
510
+ const centerIdx = colIndex - leftCols.length;
511
+ if (centerIdx < 0 || centerIdx >= centerCols.length)
512
+ return;
513
+ const groupedIds = this.store.get('groupedColumnIds');
514
+ const groupOffset = groupedIds.length > 0 ? AUTO_GROUP_COL_WIDTH : 0;
515
+ const colX = groupOffset + this.colStyles.getTotalWidth(centerCols.slice(0, centerIdx).map((c) => c.colId));
516
+ const colW = this.colStyles.getWidth(centerCols[centerIdx].colId);
517
+ const scrollLeft = this.scrollController.getScrollLeft();
518
+ const vpW = this.scrollController.getCenterViewportWidth();
519
+ if (colX < scrollLeft) {
520
+ this.scrollController.scrollToX(colX);
521
+ }
522
+ else if (colX + colW > scrollLeft + vpW) {
523
+ this.scrollController.scrollToX(colX + colW - vpW);
524
+ }
525
+ }
526
+ getCellRect(rowIndex, colIndex) {
527
+ for (const content of [this.leftBodyContentEl, this.centerBodyContentEl, this.rightBodyContentEl]) {
528
+ if (!content)
529
+ continue;
530
+ const cellEl = content.querySelector(`[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`);
531
+ if (cellEl)
532
+ return cellEl.getBoundingClientRect();
533
+ }
534
+ return null;
535
+ }
536
+ enterFullScreen() {
537
+ if (!this.wrapperEl)
538
+ return;
539
+ this.wrapperEl.requestFullscreen?.();
540
+ this.wrapperEl.classList.add('pg-grid--fullscreen');
541
+ this.store.set('fullScreen', true);
542
+ }
543
+ exitFullScreen() {
544
+ if (document.fullscreenElement === this.wrapperEl) {
545
+ document.exitFullscreen?.();
546
+ }
547
+ this.wrapperEl?.classList.remove('pg-grid--fullscreen');
548
+ this.store.set('fullScreen', false);
549
+ }
550
+ destroy() {
551
+ if (this.rafId !== null) {
552
+ cancelAnimationFrame(this.rafId);
553
+ this.rafId = null;
554
+ }
555
+ for (const unsub of this.unsubscribers)
556
+ unsub();
557
+ this.unsubscribers = [];
558
+ this.headerRenderer.destroy();
559
+ this.bodyRenderer.destroy();
560
+ this.footerRenderer.destroy();
561
+ this.overlayRenderer.destroy();
562
+ this.detailRowRenderer?.destroy();
563
+ this.photonAIPanel?.destroy();
564
+ this.tooltipController.destroy();
565
+ this.scrollController.destroy();
566
+ this.groupDropZone?.destroy();
567
+ this.rowDragRenderer?.destroy();
568
+ this.groupDragHandler?.destroy();
569
+ this.rowPositionSheet.destroy();
570
+ this.rowAnimator.destroy();
571
+ this.colStyles.destroy();
572
+ this.autoScroller?.stop();
573
+ this.autoScroller = null;
574
+ this.wrapperEl?.remove();
575
+ this.wrapperEl = null;
576
+ }
577
+ // ─── Layout ──────────────────────────────────────────────────────────────
578
+ buildLayout() {
579
+ this.wrapperEl = createDiv('pg-grid');
580
+ this.wrapperEl.setAttribute('role', 'grid');
581
+ const gridId = this.generateId();
582
+ this.wrapperEl.setAttribute('data-photon-grid-id', gridId);
583
+ // Scopes ColumnStyleManager's generated width rules to this instance —
584
+ // without it, two GridCore instances on the same page (e.g. a Master/Detail
585
+ // parent and its nested grid) sharing a user-provided colId like "year"
586
+ // would resize each other via the same unscoped [data-col-id] selector.
587
+ this.colStyles.setScopeId(gridId);
588
+ if (this.masterDetailEnabledAtConstruction) {
589
+ // Pinned left/right panels are full-height, `pointer-events: auto`
590
+ // (default) blocks regardless of whether they have a row at a given Y
591
+ // — including the Y range where a full-width detail/nested grid is
592
+ // showing underneath. Scoped to master-detail grids only: see the
593
+ // paired `.pg-grid--has-master-detail` rule in base-styles.ts, which
594
+ // makes empty panel space pass clicks through while individual rows
595
+ // (which do have real content) explicitly opt back into receiving them.
596
+ this.wrapperEl.classList.add('pg-grid--has-master-detail');
597
+ }
598
+ if (!this.options.showVerticalBorders) {
599
+ this.wrapperEl.classList.add('pg-grid--no-v-borders');
600
+ }
601
+ if (this.options.rowHeightMode === 'auto') {
602
+ this.wrapperEl.classList.add('pg-grid--auto-row-height');
603
+ }
604
+ this.containerEl.appendChild(this.wrapperEl);
605
+ // Apply custom row heights as CSS vars
606
+ if (this.options.headerRowHeight) {
607
+ this.wrapperEl.style.setProperty('--pg-header-row-height', `${this.options.headerRowHeight}px`);
608
+ }
609
+ if (this.options.filterRowHeight) {
610
+ this.wrapperEl.style.setProperty('--pg-filter-row-height', `${this.options.filterRowHeight}px`);
611
+ }
612
+ // ── Outer flex-row (contains group zone when left/right docked + main col) ─
613
+ const outerRowEl = createDiv('pg-grid-outer');
614
+ this.wrapperEl.appendChild(outerRowEl);
615
+ // ── Main flex-col (header + body + scrollbar) ────────────────────────────
616
+ const mainColEl = createDiv('pg-grid-main');
617
+ outerRowEl.appendChild(mainColEl);
618
+ // ── Group drop zone (defaults to top of mainColEl) ───────────────────────
619
+ if (this.groupDropZone) {
620
+ this.groupDropZone.mount(outerRowEl, mainColEl);
621
+ }
622
+ // ── Header ──────────────────────────────────────────────────────────────
623
+ const headerWrapEl = createDiv('pg-grid__header');
624
+ mainColEl.appendChild(headerWrapEl);
625
+ // Left header panel
626
+ const leftHeaderPanelEl = createDiv('pg-panel pg-panel--left');
627
+ const leftHeaderInner = createDiv('pg-panel__header');
628
+ leftHeaderPanelEl.appendChild(leftHeaderInner);
629
+ headerWrapEl.appendChild(leftHeaderPanelEl);
630
+ this.leftHeaderPanelEl = leftHeaderInner;
631
+ // Center header panel
632
+ const centerHeaderPanelEl = createDiv('pg-panel pg-panel--center');
633
+ const centerHeaderOuter = createDiv('pg-panel__header');
634
+ const centerHeaderInnerEl = createDiv('pg-panel__header-inner');
635
+ centerHeaderOuter.appendChild(centerHeaderInnerEl);
636
+ centerHeaderPanelEl.appendChild(centerHeaderOuter);
637
+ headerWrapEl.appendChild(centerHeaderPanelEl);
638
+ this.centerHeaderInnerEl = centerHeaderInnerEl;
639
+ // Right header panel
640
+ const rightHeaderPanelEl = createDiv('pg-panel pg-panel--right');
641
+ const rightHeaderInner = createDiv('pg-panel__header');
642
+ rightHeaderPanelEl.appendChild(rightHeaderInner);
643
+ headerWrapEl.appendChild(rightHeaderPanelEl);
644
+ this.rightHeaderPanelEl = rightHeaderInner;
645
+ // Spacer that mirrors the vertical scrollbar flex item in the body row so
646
+ // the center header panel is exactly as wide as the center body panel.
647
+ // Without this the header center is scrollbar_width wider and the right
648
+ // pinned-column header is shifted right relative to the body cells.
649
+ const headerVScrollSpacerEl = createDiv('pg-header-vscroll-spacer');
650
+ headerWrapEl.appendChild(headerVScrollSpacerEl);
651
+ // ── Body ─────────────────────────────────────────────────────────────────
652
+ const bodyWrapEl = createDiv('pg-grid__body');
653
+ mainColEl.appendChild(bodyWrapEl);
654
+ this.bodyWrapEl = bodyWrapEl;
655
+ // Left body panel
656
+ const leftBodyPanelEl = createDiv('pg-panel pg-panel--left');
657
+ const leftBodyEl = createDiv('pg-panel__body');
658
+ const leftBodyContentEl = createDiv('pg-panel__content');
659
+ leftBodyEl.appendChild(leftBodyContentEl);
660
+ leftBodyPanelEl.appendChild(leftBodyEl);
661
+ bodyWrapEl.appendChild(leftBodyPanelEl);
662
+ this.leftBodyPanelEl = leftBodyPanelEl;
663
+ this.leftBodyContentEl = leftBodyContentEl;
664
+ // Center body panel
665
+ const centerBodyPanelEl = createDiv('pg-panel pg-panel--center');
666
+ const centerBodyEl = createDiv('pg-panel__body');
667
+ const centerBodyContentEl = createDiv('pg-panel__content');
668
+ centerBodyEl.appendChild(centerBodyContentEl);
669
+ centerBodyPanelEl.appendChild(centerBodyEl);
670
+ bodyWrapEl.appendChild(centerBodyPanelEl);
671
+ this.centerBodyEl = centerBodyEl;
672
+ this.centerBodyContentEl = centerBodyContentEl;
673
+ // Right body panel
674
+ const rightBodyPanelEl = createDiv('pg-panel pg-panel--right');
675
+ const rightBodyEl = createDiv('pg-panel__body');
676
+ const rightBodyContentEl = createDiv('pg-panel__content');
677
+ rightBodyEl.appendChild(rightBodyContentEl);
678
+ rightBodyPanelEl.appendChild(rightBodyEl);
679
+ bodyWrapEl.appendChild(rightBodyPanelEl);
680
+ this.rightBodyPanelEl = rightBodyPanelEl;
681
+ this.rightBodyContentEl = rightBodyContentEl;
682
+ // Native vertical scrollbar: a flex item beside the panels (not absolutely
683
+ // positioned) so it never overlaps cell content. The height is governed by
684
+ // the flex container; the scroll range by the inner height spacer.
685
+ const sbVNative = createDiv('pg-scrollbar-v-native');
686
+ const sbVSpacer = createDiv('pg-scrollbar-v-spacer');
687
+ sbVNative.appendChild(sbVSpacer);
688
+ bodyWrapEl.appendChild(sbVNative);
689
+ // Read the actual rendered width of the scrollbar element itself — this is
690
+ // always exact because it is the same element whose width we need to mirror.
691
+ // Accessing offsetWidth forces a synchronous reflow; safe here because
692
+ // sbVNative is already attached to the live DOM tree.
693
+ const sbVWidth = sbVNative.offsetWidth;
694
+ this.wrapperEl.style.setProperty('--pg-scrollbar-v-width', `${sbVWidth}px`);
695
+ // Horizontal scrollbar row: left spacer | native scroll container | right spacer | v-scroll spacer
696
+ const sbHRowEl = createDiv('pg-scrollbar-h-row');
697
+ const sbHLeftEl = createDiv('pg-scrollbar-h-spacer pg-scrollbar-h-spacer--left');
698
+ const sbHNative = createDiv('pg-scrollbar-h-native');
699
+ const sbHSpacer = createDiv('pg-scrollbar-h-content');
700
+ sbHNative.appendChild(sbHSpacer);
701
+ const sbHRightEl = createDiv('pg-scrollbar-h-spacer pg-scrollbar-h-spacer--right');
702
+ // Mirrors the vertical scrollbar column so the h-scroll track aligns with the center panel
703
+ const sbHVScrollEl = createDiv('pg-scrollbar-h-spacer pg-scrollbar-h-spacer--vscroll');
704
+ sbHRowEl.appendChild(sbHLeftEl);
705
+ sbHRowEl.appendChild(sbHNative);
706
+ sbHRowEl.appendChild(sbHRightEl);
707
+ sbHRowEl.appendChild(sbHVScrollEl);
708
+ mainColEl.appendChild(sbHRowEl);
709
+ // Footer
710
+ if (this.options.showFooter !== false && this.options.pagination?.enabled) {
711
+ this.footerContainerEl = createDiv('pg-grid__footer');
712
+ this.wrapperEl.appendChild(this.footerContainerEl);
713
+ }
714
+ // Mount scroll controller — both V and H use native browser scrollbars
715
+ this.scrollController.mount(this.wrapperEl, bodyWrapEl, centerBodyEl, sbVNative, sbVSpacer, sbHNative, sbHSpacer, sbHRowEl);
716
+ this.scrollController.onScrollY(() => this.scheduleRender());
717
+ this.scrollController.onScrollX(() => this.scheduleRender());
718
+ // Expose horizontal scroll to header renderer for column drag auto-scroll
719
+ this.headerRenderer.setScrollCallback((dx) => this.scrollController.scrollToX(this.scrollController.getScrollLeft() + dx), (dir) => dir < 0 ? this.scrollController.canScrollLeft() : this.scrollController.canScrollRight());
720
+ // Re-render during column resize so the horizontal scrollbar updates in real time
721
+ this.headerRenderer.setResizeCallback(() => this.scheduleRender());
722
+ // Mount overlay on body (spans all panels)
723
+ this.overlayRenderer.mount(bodyWrapEl);
724
+ // Mount the Master/Detail full-width layer as a sibling of the
725
+ // left/center/right panels — see `DetailRowRenderer` for why detail rows
726
+ // must live outside the pinned-column panel structure entirely.
727
+ if (this.detailRowRenderer)
728
+ this.detailRowRenderer.mount(bodyWrapEl);
729
+ // Sticky-row layer: a TOP-LEVEL sibling of the panels and the detail
730
+ // layer — deliberately NOT nested inside `.pg-panel--left/right`.
731
+ // Those panels set their own explicit z-index (for pinned-column
732
+ // elevation), which makes each one its own stacking context; anything
733
+ // nested inside — including an earlier version of this layer — is
734
+ // trapped there and can never out-rank `.pg-detail-layer` merely by
735
+ // having a higher z-index of its own. Living at the same level as both
736
+ // lets a single z-index correctly out-rank everything at once, in every
737
+ // pinned/non-pinned column, with no stacking-context surprises.
738
+ if (this.masterDetailEnabledAtConstruction || this.treeDataEnabledAtConstruction) {
739
+ this.buildStickyLayer(bodyWrapEl);
740
+ }
741
+ // Mount the Photon AI command bar — a floating overlay anchored to the
742
+ // body's bottom-right corner. `bodyWrapEl`'s own `overflow: hidden` keeps
743
+ // it inside the grid container; position: absolute keeps it out of the
744
+ // flex layout entirely, so it never affects row/column virtualization.
745
+ if (this.photonAIPanel) {
746
+ this.photonAIPanel.mount(bodyWrapEl, this.options.photonAI);
747
+ }
748
+ this.tooltipController.mount(bodyWrapEl);
749
+ // Attach cell selection to center content
750
+ this.cellSelectionEngine.attach(centerBodyContentEl);
751
+ // Pass panels to body renderer
752
+ this.bodyRenderer.setPanels(leftBodyContentEl, centerBodyContentEl, rightBodyContentEl);
753
+ // Pass body content panels to cell selection engine for CSS class-based
754
+ // highlighting. The Master/Detail sticky-row containers are included too:
755
+ // a stuck row's cells are physically re-parented out of the normal content
756
+ // panels into the sticky layer, so without scanning those containers
757
+ // `applySelectionClasses` could never highlight the active/selected cell
758
+ // while its row is pinned at the top. A cell only ever lives in one place
759
+ // at a time, so there is no double-processing.
760
+ const selectionPanels = [leftBodyContentEl, centerBodyContentEl, rightBodyContentEl];
761
+ if (this.leftStickyRowEl)
762
+ selectionPanels.push(this.leftStickyRowEl);
763
+ if (this.centerStickyRowEl)
764
+ selectionPanels.push(this.centerStickyRowEl);
765
+ if (this.rightStickyRowEl)
766
+ selectionPanels.push(this.rightStickyRowEl);
767
+ this.cellSelectionEngine.setBodyPanels(selectionPanels);
768
+ // Wire auto-scroll: whenever the active cell changes, scroll it into view
769
+ this.cellSelectionEngine.setScrollToCellCallback((r, c) => this.scrollToCell(r, c));
770
+ // Wire PageUp/PageDown: return the number of fully visible rows so the
771
+ // engine can jump by exactly one viewport height worth of rows.
772
+ this.cellSelectionEngine.setGetViewportRowCountCallback(() => {
773
+ const vpH = this.scrollController.getViewportHeight();
774
+ const rowH = this.options.rowHeight ?? 48;
775
+ return Math.max(1, Math.floor(vpH / rowH));
776
+ });
777
+ // Wire paste/fill/cut invalidation: evict only the mutated rows from the
778
+ // cache so custom cell renderers in unchanged rows are not re-executed.
779
+ this.cellSelectionEngine.setDataChangedCallback((nodeIds) => {
780
+ if (nodeIds && nodeIds.size > 0) {
781
+ this.invalidateBodyRowsByIds(nodeIds);
782
+ }
783
+ else {
784
+ this.invalidateBodyRows();
785
+ }
786
+ });
787
+ // Wire filter-panel opening: header filter icon click → open panel overlay
788
+ this.headerRenderer.setOpenFilterPanelCallback((col, anchor) => this.openFilterPanel(col, anchor));
789
+ // ── Edge auto-scroller ───────────────────────────────────────────────────
790
+ // A single RAF-based AutoScroller handles both cell-range selection drag
791
+ // and fill-handle drag. When the cursor is within 60 px of the body
792
+ // viewport edges it applies a proportional scroll delta each frame.
793
+ // After each scrolled frame `onScrolled` re-evaluates which cell lies
794
+ // under the cursor so the selection or fill preview stays accurate.
795
+ this.autoScroller = new AutoScroller(() => this.bodyWrapEl?.getBoundingClientRect() ?? null, (dy) => this.scrollController.scrollToY(this.scrollController.getScrollTop() + dy), (dx) => this.scrollController.scrollToX(this.scrollController.getScrollLeft() + dx), (cx, cy) => {
796
+ if (this.cellSelectionEngine.isSelecting) {
797
+ // Re-hit-test after the grid scrolled so the selection follows.
798
+ const el = document.elementFromPoint(cx, cy);
799
+ const cellEl = el?.closest('[data-row-index][data-col-index]');
800
+ if (!cellEl || !cellEl.hasAttribute('data-col-id'))
801
+ return;
802
+ const ri = Number(cellEl.getAttribute('data-row-index'));
803
+ const ci = Number(cellEl.getAttribute('data-col-index'));
804
+ if (!isNaN(ri) && !isNaN(ci))
805
+ this.cellSelectionEngine.extendSelection(ri, ci);
806
+ }
807
+ else {
808
+ // Fill-handle drag: re-process fill position after scroll.
809
+ this.cellSelectionEngine.updateFillPosition(cx, cy);
810
+ }
811
+ });
812
+ // Wire fill-handle drag into the auto-scroller via CellSelectionEngine callbacks.
813
+ this.cellSelectionEngine.setFillDragScrollCallback((cx, cy) => this.autoScroller.updateMouse(cx, cy));
814
+ this.cellSelectionEngine.setFillDragEndCallback(() => this.autoScroller?.stop());
815
+ // Provide the body viewport rect so fill drag can clamp its hit-test
816
+ // coordinates when the cursor exits the grid boundary.
817
+ this.cellSelectionEngine.setDragViewportRectCallback(() => this.bodyWrapEl?.getBoundingClientRect() ?? null);
818
+ // Mouse drag to extend selection.
819
+ // Guard on e.buttons: if no mouse button is held (e.g. touchpad hover after
820
+ // keyboard navigation left _isSelecting true), cancel the drag immediately
821
+ // instead of extending the selection on every pointer movement.
822
+ bodyWrapEl.addEventListener('mousemove', (e) => {
823
+ if (!this.cellSelectionEngine.isSelecting)
824
+ return;
825
+ if (e.buttons === 0) {
826
+ this.cellSelectionEngine.endSelection();
827
+ this.autoScroller?.stop();
828
+ return;
829
+ }
830
+ // Feed cursor position to the auto-scroller — triggers edge scrolling
831
+ // when the cursor is within the threshold of the viewport boundary.
832
+ this.autoScroller?.updateMouse(e.clientX, e.clientY);
833
+ const cellEl = e.target.closest('[data-row-index][data-col-index]');
834
+ if (!cellEl || !cellEl.hasAttribute('data-col-id'))
835
+ return;
836
+ const ri = Number(cellEl.getAttribute('data-row-index'));
837
+ const ci = Number(cellEl.getAttribute('data-col-index'));
838
+ if (!isNaN(ri) && !isNaN(ci))
839
+ this.cellSelectionEngine.extendSelection(ri, ci);
840
+ });
841
+ bodyWrapEl.addEventListener('mouseup', () => {
842
+ this.cellSelectionEngine.endSelection();
843
+ this.autoScroller?.stop();
844
+ });
845
+ // The bodyWrapEl mouseup only fires when the pointer is released inside the
846
+ // grid. When the auto-scroller is running the user's cursor is outside the
847
+ // viewport edge — releasing there fires no bodyWrapEl event, so scrolling
848
+ // never stops. A document-level listener catches the release everywhere and
849
+ // is removed in destroy() to prevent leaks.
850
+ const docMouseUp = () => {
851
+ if (this.cellSelectionEngine.isSelecting) {
852
+ this.cellSelectionEngine.endSelection();
853
+ }
854
+ this.autoScroller?.stop();
855
+ };
856
+ document.addEventListener('mouseup', docMouseUp);
857
+ this.unsubscribers.push(() => document.removeEventListener('mouseup', docMouseUp));
858
+ }
859
+ /**
860
+ * Builds the top-level sticky-row layer and its three left/center/right
861
+ * regions, mirroring the pinned-column layout via the same
862
+ * `--pg-left-panel-width` / `--pg-right-panel-width` CSS vars the real
863
+ * panels use — so a stuck row lines up pixel-for-pixel with the columns
864
+ * it belongs to. The center region gets its own horizontal-scroll
865
+ * transform so a stuck row's center cells track the user's horizontal
866
+ * scroll exactly like the real (non-sticky) center panel does.
867
+ */
868
+ buildStickyLayer(bodyWrapEl) {
869
+ const layer = createDiv('pg-sticky-layer');
870
+ this.leftStickyRowEl = createDiv('pg-sticky-layer__left');
871
+ layer.appendChild(this.leftStickyRowEl);
872
+ const centerRegion = createDiv('pg-sticky-layer__center');
873
+ this.centerStickyRowEl = createDiv('pg-sticky-layer__center-inner');
874
+ centerRegion.appendChild(this.centerStickyRowEl);
875
+ layer.appendChild(centerRegion);
876
+ this.rightStickyRowEl = createDiv('pg-sticky-layer__right');
877
+ layer.appendChild(this.rightStickyRowEl);
878
+ bodyWrapEl.appendChild(layer);
879
+ this.bodyRenderer.setStickyContainers(this.leftStickyRowEl, this.centerStickyRowEl, this.rightStickyRowEl);
880
+ }
881
+ // ─── Render ───────────────────────────────────────────────────────────────
882
+ performRender() {
883
+ const rows = this.store.get('visibleRows');
884
+ const rawCols = this.store.get('columns');
885
+ const groupedIds = this.store.get('groupedColumnIds');
886
+ const loading = this.store.get('loading');
887
+ const allColumns = rawCols.filter((c) => c.visible !== false);
888
+ const leftCols = allColumns.filter((c) => c.pinned === 'left');
889
+ const centerCols = allColumns.filter((c) => c.pinned !== 'left' && c.pinned !== 'right');
890
+ const rightCols = allColumns.filter((c) => c.pinned === 'right');
891
+ const rowHeight = this.options.rowHeight ?? 48;
892
+ const w = this.wrapperEl;
893
+ // ── Column / grouping-dependent work ──────────────────────────────────────
894
+ // Skipped on scroll-only frames: columns and grouping never change during
895
+ // a scroll event, so the store returns the same array reference each time.
896
+ // initFromColumns calls flush() which rewrites the entire column <style> tag —
897
+ // running it at 60 fps wastes ~1 ms/frame for nothing.
898
+ const colsChanged = rawCols !== this._lastColumnsRef;
899
+ const groupingChanged = groupedIds !== this._lastGroupedIdsRef;
900
+ // Derived from groupedIds — always available for the horizontal scroll logic below.
901
+ const hasGroupedColumns = groupedIds.length > 0;
902
+ // ColumnDef for the deepest grouping field — used by leaf data rows in the
903
+ // auto-group column. Looked up in rawCols (all columns, including hidden
904
+ // ones) so that grouping by a hidden column still shows leaf values.
905
+ const leafGroupColDef = hasGroupedColumns
906
+ ? (rawCols.find((c) => c.colId === groupedIds[groupedIds.length - 1]) ?? null)
907
+ : null;
908
+ if (colsChanged || groupingChanged) {
909
+ this._lastColumnsRef = rawCols;
910
+ this._lastGroupedIdsRef = groupedIds;
911
+ this.colStyles.initFromColumns(allColumns);
912
+ const showCb = this.options.showCheckboxes ? CHECKBOX_COL_WIDTH : 0;
913
+ const showSn = this.options.showSerialNumber ? SERIAL_COL_WIDTH : 0;
914
+ const leftPinnedWidth = this.colStyles.getTotalWidth(leftCols.map((c) => c.colId));
915
+ const rightContentWidth = this.colStyles.getTotalWidth(rightCols.map((c) => c.colId));
916
+ const hasLeft = showCb > 0 || showSn > 0 || leftCols.length > 0;
917
+ const hasRight = rightCols.length > 0;
918
+ // Set left/right panel CSS vars BEFORE resolving flex so that
919
+ // centerBodyEl.clientWidth reflects the true center panel width.
920
+ w.style.setProperty('--pg-left-panel-width', hasLeft ? `${showCb + showSn + leftPinnedWidth}px` : '0px');
921
+ w.style.setProperty('--pg-right-panel-width', hasRight ? `${rightContentWidth}px` : '0px');
922
+ // Resolve flex columns — clientWidth read forces a layout reflow;
923
+ // done here so it only happens when column definitions change.
924
+ const centerColIds = centerCols.map((c) => c.colId);
925
+ if (this.colStyles.hasFlex(centerColIds)) {
926
+ const centerPanelW = this.centerBodyEl?.clientWidth ?? 0;
927
+ if (centerPanelW > 0) {
928
+ this.colStyles.resolveFlex(centerColIds, centerPanelW);
929
+ this._lastFlexResolvedWidth = centerPanelW;
930
+ }
931
+ }
932
+ const centerContentWidth = this.colStyles.getTotalWidth(centerColIds)
933
+ + (hasGroupedColumns ? AUTO_GROUP_COL_WIDTH : 0);
934
+ this._cachedCenterW = centerContentWidth;
935
+ // Show/hide left/right panels
936
+ if (this.leftBodyPanelEl) {
937
+ this.leftBodyPanelEl.style.setProperty('display', hasLeft ? '' : 'none');
938
+ if (this.leftHeaderPanelEl?.parentElement) {
939
+ this.leftHeaderPanelEl.parentElement.style.setProperty('display', hasLeft ? '' : 'none');
940
+ }
941
+ }
942
+ if (this.rightBodyPanelEl) {
943
+ this.rightBodyPanelEl.style.setProperty('display', hasRight ? '' : 'none');
944
+ if (this.rightHeaderPanelEl?.parentElement) {
945
+ this.rightHeaderPanelEl.parentElement.style.setProperty('display', hasRight ? '' : 'none');
946
+ }
947
+ }
948
+ w.style.setProperty('--pg-center-content-width', `${centerContentWidth}px`);
949
+ }
950
+ else if (this.colStyles.hasFlex(centerCols.map((c) => c.colId))) {
951
+ // Columns didn't change, but the container may have resized with no
952
+ // columns-array change to signal it — e.g. a vertical scrollbar
953
+ // transiently appearing/disappearing as a Master/Detail row is
954
+ // inserted, or a plain window resize. Flex columns must track that,
955
+ // or they stay sized for a stale width and show a spurious scrollbar
956
+ // (or an unfilled gap) until something else happens to touch the
957
+ // columns array (resize/sort/pin) and incidentally re-resolves them.
958
+ const centerColIds = centerCols.map((c) => c.colId);
959
+ const centerPanelW = this.centerBodyEl?.clientWidth ?? 0;
960
+ if (centerPanelW > 0 && Math.abs(centerPanelW - this._lastFlexResolvedWidth) > 1) {
961
+ this.colStyles.resolveFlex(centerColIds, centerPanelW);
962
+ this._lastFlexResolvedWidth = centerPanelW;
963
+ const centerContentWidth = this.colStyles.getTotalWidth(centerColIds)
964
+ + (hasGroupedColumns ? AUTO_GROUP_COL_WIDTH : 0);
965
+ this._cachedCenterW = centerContentWidth;
966
+ w.style.setProperty('--pg-center-content-width', `${centerContentWidth}px`);
967
+ }
968
+ }
969
+ // ── Row-dependent work ────────────────────────────────────────────────────
970
+ // The total-height O(n) loop and scrollController.updateSizes are skipped
971
+ // when the rows reference hasn't changed (i.e. during scroll-only frames).
972
+ const rowsChanged = rows !== this._lastRowsRef;
973
+ if (rowsChanged) {
974
+ this._lastRowsRef = rows;
975
+ let totalHeight = 0;
976
+ for (const row of rows)
977
+ totalHeight += row.height ?? rowHeight;
978
+ this._cachedTotalHeight = totalHeight;
979
+ w.style.setProperty('--pg-content-height', `${totalHeight}px`);
980
+ this.scrollController.updateSizes(totalHeight, this._cachedCenterW);
981
+ }
982
+ else if (colsChanged || groupingChanged) {
983
+ // Center width changed but row count did not — update the horizontal size only.
984
+ this.scrollController.updateSizes(this._cachedTotalHeight, this._cachedCenterW);
985
+ }
986
+ else if (this.headerRenderer.isResizingColumn) {
987
+ // A live column-width drag never touches the `columns` store reference
988
+ // (ColumnModel.setColumnWidth only fires on mouseup) — `colsChanged`
989
+ // stays false for the whole gesture, so the block above never runs.
990
+ // Recompute the center width straight from already-known column widths
991
+ // (no DOM measurement needed) so the horizontal scrollbar's spacer —
992
+ // and thus its visible track/thumb size — tracks the resize in real
993
+ // time instead of jumping only once the mouse is released.
994
+ const centerColIds = centerCols.map((c) => c.colId);
995
+ const liveCenterW = this.colStyles.getTotalWidth(centerColIds) + (hasGroupedColumns ? AUTO_GROUP_COL_WIDTH : 0);
996
+ this._cachedCenterW = liveCenterW;
997
+ w.style.setProperty('--pg-center-content-width', `${liveCenterW}px`);
998
+ this.scrollController.updateSizes(this._cachedTotalHeight, liveCenterW);
999
+ }
1000
+ if (loading) {
1001
+ this.overlayRenderer.showLoading(this.options.loadingOverlayText);
1002
+ return;
1003
+ }
1004
+ this.overlayRenderer.hideLoading();
1005
+ if (rows.length === 0) {
1006
+ this.overlayRenderer.showNoRows(this.options.noRowsOverlayHtml, this.options.noRowsOverlayText);
1007
+ }
1008
+ else {
1009
+ this.overlayRenderer.hideNoRows();
1010
+ }
1011
+ // ── Horizontal virtual scroll: compute visible center col range ───────────
1012
+ const scrollLeft = this.scrollController.getScrollLeft();
1013
+ const centerViewportW = this.centerBodyEl?.clientWidth ?? 800;
1014
+ // Accumulate column positions to find the first/last visible col
1015
+ let accumX = hasGroupedColumns ? AUTO_GROUP_COL_WIDTH : 0;
1016
+ let visColStart = centerCols.length; // pessimistic
1017
+ let visColEnd = 0;
1018
+ for (let i = 0; i < centerCols.length; i++) {
1019
+ const cw = this.colStyles.getWidth(centerCols[i].colId);
1020
+ const colLeft = accumX;
1021
+ const colRight = accumX + cw;
1022
+ if (colRight > scrollLeft && visColStart > i)
1023
+ visColStart = i;
1024
+ if (colLeft < scrollLeft + centerViewportW)
1025
+ visColEnd = i + 1;
1026
+ accumX += cw;
1027
+ }
1028
+ if (visColStart === centerCols.length) {
1029
+ visColStart = 0;
1030
+ visColEnd = 0;
1031
+ }
1032
+ // During a column drag, expand the virtual column range to all center columns
1033
+ // so the dragged column and every potential drop target remain in the DOM for
1034
+ // both header and body. The pg-grid--col-autoscrolling class added at drag
1035
+ // start suppresses the 180 ms transition during this initial range expansion,
1036
+ // preventing newly-added body cells from animating in from transform:0.
1037
+ const isDraggingCol = this.headerRenderer.isDraggingCol;
1038
+ const isDraggingGroup = this.displayGroupEngine?.isDraggingGroup ?? false;
1039
+ const colBuf = (isDraggingCol || isDraggingGroup) ? centerCols.length : COL_BUFFER;
1040
+ const colStart = Math.max(0, visColStart - colBuf);
1041
+ const colEnd = Math.min(centerCols.length, visColEnd + colBuf);
1042
+ // Spacer widths represent off-screen columns
1043
+ const leftSpacerW = this.colStyles.getTotalWidth(centerCols.slice(0, colStart).map((c) => c.colId));
1044
+ const rightSpacerW = this.colStyles.getTotalWidth(centerCols.slice(colEnd).map((c) => c.colId));
1045
+ const visibleCenterCols = centerCols.slice(colStart, colEnd);
1046
+ // Build header once (rebuilt when columns change)
1047
+ const headerOptions = {
1048
+ showCheckboxes: this.options.showCheckboxes,
1049
+ showSerialNumber: this.options.showSerialNumber,
1050
+ showColumnMenu: this.options.showColumnMenu !== false,
1051
+ showFilterRow: this.options.showFilterRow,
1052
+ headerRowHeight: this.options.headerRowHeight,
1053
+ filterRowHeight: this.options.filterRowHeight,
1054
+ hasGroupedColumns,
1055
+ autoGroupColWidth: AUTO_GROUP_COL_WIDTH,
1056
+ };
1057
+ if (!this.headerRendered && this.leftHeaderPanelEl && this.centerHeaderInnerEl && this.rightHeaderPanelEl) {
1058
+ this.headerRenderer.renderInPanels(this.leftHeaderPanelEl, this.centerHeaderInnerEl, this.rightHeaderPanelEl, allColumns, headerOptions);
1059
+ this.headerRendered = true;
1060
+ }
1061
+ // Update center header when the column range changes.
1062
+ if (colStart !== this.lastCenterColStart || colEnd !== this.lastCenterColEnd) {
1063
+ this.headerRenderer.updateCenterVisibleCols(visibleCenterCols, leftSpacerW, rightSpacerW, headerOptions);
1064
+ this.lastCenterColStart = colStart;
1065
+ this.lastCenterColEnd = colEnd;
1066
+ }
1067
+ // Vertical virtual scroll range
1068
+ const scrollTop = this.scrollController.getScrollTop();
1069
+ const viewportHeight = this.centerBodyEl?.clientHeight ?? 400;
1070
+ const buffer = this.options.virtualScroll?.rowBuffer ?? ROW_BUFFER;
1071
+ // During animation, expand the render window by one extra viewport so rows
1072
+ // just outside the buffer are already in the DOM and can participate in FLIP.
1073
+ const animExtra = this.rowAnimator.hasPending() ? Math.ceil(viewportHeight / rowHeight) : 0;
1074
+ const isAutoHeight = this.options.rowHeightMode === 'auto';
1075
+ let start;
1076
+ let end;
1077
+ if (isAutoHeight) {
1078
+ const bufferPx = (buffer + animExtra) * rowHeight;
1079
+ const viewStart = scrollTop - bufferPx;
1080
+ const viewEnd = scrollTop + viewportHeight + bufferPx;
1081
+ start = 0;
1082
+ end = rows.length;
1083
+ for (let i = 0; i < rows.length; i++) {
1084
+ if ((rows[i].top + (rows[i].height ?? rowHeight)) >= viewStart) {
1085
+ start = i;
1086
+ break;
1087
+ }
1088
+ }
1089
+ for (let i = start; i < rows.length; i++) {
1090
+ if (rows[i].top > viewEnd) {
1091
+ end = i;
1092
+ break;
1093
+ }
1094
+ }
1095
+ }
1096
+ else {
1097
+ start = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer - animExtra);
1098
+ end = Math.min(rows.length, Math.ceil((scrollTop + viewportHeight) / rowHeight) + buffer + animExtra);
1099
+ }
1100
+ // ── Master/Detail sticky row ────────────────────────────────────────────
1101
+ // See `StickyRowTracker` for the full rule. `minStart` widens the render
1102
+ // window when needed so the sticky row's cells stay rendered even though
1103
+ // virtualization would otherwise have scrolled past its natural position.
1104
+ let stickyNodeId = null;
1105
+ let stickyOffsetPx = 0;
1106
+ let treeStickyEntries = [];
1107
+ if (this.masterDetailEnabledAtConstruction) {
1108
+ const sticky = this.stickyRowTracker.compute(rows, scrollTop, rowHeight, start);
1109
+ stickyNodeId = sticky.nodeId;
1110
+ stickyOffsetPx = sticky.offsetPx;
1111
+ start = sticky.minStart;
1112
+ }
1113
+ else if (this.treeExpansionService) {
1114
+ // Tree Data's generalization of the same rule — see `TreeStickyRowTracker`:
1115
+ // every ancestor of the row currently at the viewport's top stacks as
1116
+ // its own sticky row, instead of there only ever being one.
1117
+ const sticky = this.treeStickyRowTracker.compute(rows, scrollTop, rowHeight, start);
1118
+ treeStickyEntries = sticky.entries;
1119
+ start = sticky.minStart;
1120
+ }
1121
+ this.stickyNodeId = stickyNodeId;
1122
+ // Update row position stylesheet for visible rows.
1123
+ // In auto-height mode always use rowHeight as min-height so that widening a
1124
+ // column allows rows to shrink back — the previously measured row.height must
1125
+ // not pin min-height or rows can never get shorter.
1126
+ this.rowPositionSheet.update(rows.slice(start, end).map((row) => ({
1127
+ nodeId: row.nodeId,
1128
+ top: row.top,
1129
+ height: isAutoHeight ? rowHeight : (row.height ?? rowHeight),
1130
+ })), isAutoHeight);
1131
+ // Render body rows with visible center col slice + spacer info.
1132
+ // During an active column resize the CSS style tag (ColumnStyleManager) has
1133
+ // already updated every [data-col-id] element's width, so the body DOM is
1134
+ // visually correct without a full renderRows pass. Calling renderRows on
1135
+ // every mousemove would wipe and rebuild all center panels whenever the
1136
+ // virtual column range shifts by even 1 column — destroying and re-creating
1137
+ // custom cell renderers (images, flags, etc.) and causing visible blinking.
1138
+ // We skip renderRows while resizing and instead only advance the tracked
1139
+ // range so the next normal paint (after mouseup) does not see a stale range.
1140
+ const renderedRows = rows.slice(start, end);
1141
+ const masterDetailOptions = this.masterDetailEngine?.isEnabled()
1142
+ ? {
1143
+ toggleColumnId: this.masterDetailEngine.getConfig()?.toggleColumnId ?? allColumns[0]?.colId ?? '',
1144
+ isExpandedFn: (nodeId) => this.masterDetailEngine.isExpanded(nodeId),
1145
+ hasDetailFn: (rowData) => this.masterDetailEngine.hasDetail(rowData),
1146
+ }
1147
+ : undefined;
1148
+ const treeDataOptions = this.treeExpansionService
1149
+ ? {
1150
+ toggleColumnId: this.treeToggleColumnId ?? allColumns[0]?.colId ?? '',
1151
+ isExpandedFn: (nodeId) => this.treeExpansionService.isExpanded(nodeId),
1152
+ }
1153
+ : undefined;
1154
+ if (this.headerRenderer.isResizingColumn) {
1155
+ this.bodyRenderer.syncCenterRange(colStart, colStart + visibleCenterCols.length);
1156
+ }
1157
+ else {
1158
+ this.bodyRenderer.renderRows(renderedRows, leftCols, visibleCenterCols, rightCols, {
1159
+ showCheckboxes: this.options.showCheckboxes,
1160
+ showSerialNumber: this.options.showSerialNumber,
1161
+ showVerticalBorders: this.options.showVerticalBorders,
1162
+ rowShading: this.options.rowShading,
1163
+ rowHeight: this.options.rowHeight,
1164
+ dateFormat: this.options.dateFormat,
1165
+ timeZone: this.options.timeZone,
1166
+ currencySymbol: this.options.currencySymbol,
1167
+ locale: this.options.locale,
1168
+ api: null,
1169
+ showGroupsColumn: hasGroupedColumns,
1170
+ autoGroupColWidth: AUTO_GROUP_COL_WIDTH,
1171
+ leafGroupColDef,
1172
+ // Unfiltered (includes columns hidden because they're currently
1173
+ // grouped by) so a group row's `groupField` always resolves to its
1174
+ // ColumnDef, even though that column itself isn't rendered as a cell.
1175
+ allLeafColumns: rawCols,
1176
+ centerColStart: colStart,
1177
+ centerLeftSpacerW: leftSpacerW,
1178
+ centerRightSpacerW: rightSpacerW,
1179
+ totalCenterCols: centerCols.length,
1180
+ masterDetail: masterDetailOptions,
1181
+ treeData: treeDataOptions,
1182
+ });
1183
+ }
1184
+ if (this.detailRowRenderer) {
1185
+ const allDetailNodeIds = new Set(rows.filter((r) => r.type === 'detail').map((r) => r.nodeId));
1186
+ const windowedDetailRows = renderedRows.filter((r) => r.type === 'detail');
1187
+ this.detailRowRenderer.sync(windowedDetailRows, allDetailNodeIds);
1188
+ }
1189
+ if (this.masterDetailEnabledAtConstruction) {
1190
+ this.bodyRenderer.setStickyRows(stickyNodeId ? [{ nodeId: stickyNodeId, top: stickyOffsetPx }] : []);
1191
+ }
1192
+ else if (this.treeExpansionService) {
1193
+ this.bodyRenderer.setStickyRows(treeStickyEntries);
1194
+ }
1195
+ // Animate rows after sort (FLIP slide) or filter (FLIP slide + fade-in for new rows)
1196
+ if (this.rowAnimator.hasPending()) {
1197
+ const animContainers = [
1198
+ this.leftBodyContentEl,
1199
+ this.centerBodyContentEl,
1200
+ this.rightBodyContentEl,
1201
+ ].filter(Boolean);
1202
+ this.rowAnimator.animate(animContainers, renderedRows, viewportHeight);
1203
+ }
1204
+ // Auto-height measurement pass: read actual row heights and sync all panels
1205
+ if (isAutoHeight) {
1206
+ const nodeIdToRow = new Map(renderedRows.map((r) => [r.nodeId, r]));
1207
+ const measured = new Map();
1208
+ for (const panel of [this.leftBodyContentEl, this.centerBodyContentEl, this.rightBodyContentEl]) {
1209
+ if (!panel)
1210
+ continue;
1211
+ for (const el of panel.querySelectorAll('[data-node-id]')) {
1212
+ const nodeId = el.getAttribute('data-node-id');
1213
+ if (!nodeId)
1214
+ continue;
1215
+ const h = el.offsetHeight;
1216
+ if (h > 0)
1217
+ measured.set(nodeId, Math.max(measured.get(nodeId) ?? 0, h));
1218
+ }
1219
+ }
1220
+ let anyChanged = false;
1221
+ for (const [nodeId, h] of measured) {
1222
+ const row = nodeIdToRow.get(nodeId);
1223
+ if (row && h !== row.height) {
1224
+ row.height = h;
1225
+ anyChanged = true;
1226
+ }
1227
+ }
1228
+ if (anyChanged) {
1229
+ let top = 0;
1230
+ for (const row of rows) {
1231
+ row.top = top;
1232
+ top += row.height ?? rowHeight;
1233
+ }
1234
+ this.rowPositionSheet.update(renderedRows.map((r) => ({ nodeId: r.nodeId, top: r.top, height: rowHeight })), true);
1235
+ this._cachedTotalHeight = top;
1236
+ w.style.setProperty('--pg-content-height', `${top}px`);
1237
+ this.scrollController.updateSizes(top, this._cachedCenterW);
1238
+ }
1239
+ }
1240
+ this.store.set('firstRenderedRowIndex', start);
1241
+ this.store.set('lastRenderedRowIndex', end);
1242
+ // Footer
1243
+ if (this.footerContainerEl) {
1244
+ if (!this.footerContainerEl.hasChildNodes()) {
1245
+ this.footerRenderer.render(this.footerContainerEl, {
1246
+ showPagination: this.options.pagination?.enabled,
1247
+ showRowCount: true,
1248
+ footerHeight: this.options.footerRowHeight,
1249
+ });
1250
+ }
1251
+ else {
1252
+ this.footerRenderer.updatePaginationState();
1253
+ }
1254
+ }
1255
+ // Only query-select and re-classify cells when there is an active selection.
1256
+ // During unselected scroll this querySelectorAll over ~600 cells is pure waste.
1257
+ const hasSelection = this.store.get('cellRanges').length > 0
1258
+ || this.store.get('activeCell') !== null;
1259
+ if (hasSelection) {
1260
+ this.cellSelectionEngine.applySelectionClasses();
1261
+ }
1262
+ this.eventBus.emit(GridEventType.ROWS_RENDERED, { renderedCount: renderedRows.length });
1263
+ }
1264
+ // ─── Store subscriptions ──────────────────────────────────────────────────
1265
+ subscribeToStore() {
1266
+ this.unsubscribers.push(this.store.watch('visibleRows', () => this.scheduleRender()), this.store.watch('loading', () => this.scheduleRender()), this.store.watch('columns', () => {
1267
+ if (this.headerRenderer.isDraggingCol || this.displayGroupEngine?.isDraggingGroup) {
1268
+ // Live drag (leaf column or group): skip header destroy so drag state
1269
+ // and live-preview group rows are preserved. Only reset body + virtual
1270
+ // column range so body cells and panel widths stay in sync.
1271
+ this.wrapperEl?.classList.add('pg-grid--drag-preview-sync');
1272
+ this.lastCenterColStart = -1;
1273
+ this.lastCenterColEnd = -1;
1274
+ this.bodyRenderer.clear();
1275
+ this.scheduleRender();
1276
+ requestAnimationFrame(() => {
1277
+ this.wrapperEl?.classList.remove('pg-grid--drag-preview-sync');
1278
+ });
1279
+ return;
1280
+ }
1281
+ this.headerRendered = false;
1282
+ this.lastCenterColStart = -1;
1283
+ this.lastCenterColEnd = -1;
1284
+ if (this.leftHeaderPanelEl)
1285
+ this.leftHeaderPanelEl.innerHTML = '';
1286
+ if (this.centerHeaderInnerEl)
1287
+ this.centerHeaderInnerEl.innerHTML = '';
1288
+ if (this.rightHeaderPanelEl)
1289
+ this.rightHeaderPanelEl.innerHTML = '';
1290
+ this.headerRenderer.destroy();
1291
+ // Re-wire group model references cleared by destroy() — this is required
1292
+ // whenever columns change due to collapse/expand (setColumnVisible fires
1293
+ // COLUMNS_STATE_CHANGED which updates the store, triggering this watcher).
1294
+ // Without re-wiring here, the next renderInPanels call would build no group rows.
1295
+ this.rewireGroupModelIntoHeaderRenderer();
1296
+ this.bodyRenderer.clear();
1297
+ this.scheduleRender();
1298
+ }), this.store.watch('groupedColumnIds', () => {
1299
+ this.groupDropZone?.update();
1300
+ }), this.store.watch('filterModel', (model) => {
1301
+ const activeColIds = new Set(Object.keys(model));
1302
+ this.headerRenderer.updateFilterIndicators(activeColIds);
1303
+ }), this.store.watch('scrollTop', () => this.scheduleRender()), this.store.watch('selectedRowIds', (ids) => {
1304
+ const rows = this.store.get('visibleRows');
1305
+ for (const row of rows) {
1306
+ this.bodyRenderer.updateRowSelection(row.nodeId, ids.has(row.nodeId));
1307
+ }
1308
+ }), this.store.watch('isAllSelected', (isAll) => {
1309
+ const isInd = this.store.get('isIndeterminate');
1310
+ this.headerRenderer.updateAllChecked(isAll, isInd);
1311
+ }), this.store.watch('cellRanges', () => {
1312
+ this.cellSelectionEngine.applySelectionClasses();
1313
+ }), this.store.watch('activeCell', () => {
1314
+ this.cellSelectionEngine.applySelectionClasses();
1315
+ }));
1316
+ this.eventBus.on(GridEventType.ALL_ROWS_SELECTED, (payload) => {
1317
+ const p = payload;
1318
+ if (p?.action === 'selectAll') {
1319
+ this.rowSelectionEngine.selectAll(this.store.get('allRows'));
1320
+ }
1321
+ else if (p?.action === 'deselectAll') {
1322
+ this.rowSelectionEngine.deselectAll(this.store.get('allRows'));
1323
+ }
1324
+ });
1325
+ // Cell click → start, extend, or multi-range selection
1326
+ this.eventBus.on(GridEventType.CELL_CLICKED, (payload) => {
1327
+ const p = payload;
1328
+ if (p.event.shiftKey) {
1329
+ // Shift+Click: extend range from existing anchor to clicked cell
1330
+ this.cellSelectionEngine.extendSelection(p.rowIndex, p.colIndex);
1331
+ }
1332
+ else if (p.event.ctrlKey || p.event.metaKey) {
1333
+ // Ctrl/Cmd+Click: add or remove a single cell from multi-range selection
1334
+ this.cellSelectionEngine.addRangeCell(p.rowIndex, p.colIndex);
1335
+ }
1336
+ else {
1337
+ this.cellSelectionEngine.startSelection(p.rowIndex, p.colIndex);
1338
+ }
1339
+ });
1340
+ // Right-click → show context menu (select cell if not already in range)
1341
+ this.eventBus.on(GridEventType.CELL_CONTEXT_MENU, (payload) => {
1342
+ const p = payload;
1343
+ if (!this.cellSelectionEngine.isCellSelected(p.rowIndex, p.colIndex)) {
1344
+ this.cellSelectionEngine.startSelection(p.rowIndex, p.colIndex);
1345
+ }
1346
+ this.cellSelectionEngine.showContextMenu(p.x, p.y);
1347
+ });
1348
+ }
1349
+ // ── Column-group handlers ─────────────────────────────────────────────────
1350
+ /**
1351
+ * Called when the user clicks a group collapse/expand toggle.
1352
+ *
1353
+ * When **collapsing**: hides all leaf columns except the first one (the "peek"
1354
+ * column) so the group header continues to show meaningful data.
1355
+ * When **expanding**: restores all leaf columns to visible.
1356
+ *
1357
+ * `setColumnVisible` fires `COLUMNS_STATE_CHANGED` → full rebuild.
1358
+ */
1359
+ handleGroupToggle(groupId) {
1360
+ // New Display Group Engine path
1361
+ if (this.displayGroupEngine) {
1362
+ this.displayGroupEngine.toggleGroup(groupId);
1363
+ return;
1364
+ }
1365
+ // Legacy ColumnGroupModel path
1366
+ if (!this.columnGroupModel)
1367
+ return;
1368
+ const group = this.columnGroupModel.getGroup(groupId);
1369
+ if (!group)
1370
+ return;
1371
+ const wasCollapsed = group.collapsed;
1372
+ this.columnGroupModel.toggleGroup(groupId);
1373
+ const isNowCollapsed = !wasCollapsed;
1374
+ const leaves = this.columnGroupModel.getLeavesInGroup(groupId);
1375
+ for (let i = 0; i < leaves.length; i++) {
1376
+ const visible = !isNowCollapsed || i === 0;
1377
+ this.columnModel.setColumnVisible(leaves[i].colId, visible);
1378
+ }
1379
+ }
1380
+ /**
1381
+ * Called when the user drags a group resize handle.
1382
+ * Distributes the new width proportionally among all visible leaf columns.
1383
+ */
1384
+ handleGroupResize(groupId, newWidth) {
1385
+ // New Display Group Engine path — instanceId resolves leaves internally
1386
+ if (this.displayGroupEngine) {
1387
+ this.displayGroupEngine.resizeGroup(groupId, newWidth);
1388
+ this.scheduleRender();
1389
+ return;
1390
+ }
1391
+ // Legacy ColumnGroupModel path
1392
+ if (!this.columnGroupModel)
1393
+ return;
1394
+ const group = this.columnGroupModel.getGroup(groupId);
1395
+ if (!group)
1396
+ return;
1397
+ const currentWidth = this.columnGroupModel.computeGroupWidth(groupId, this.colStyles);
1398
+ if (currentWidth <= 0 || Math.abs(newWidth - currentWidth) < 1)
1399
+ return;
1400
+ const ratio = newWidth / currentWidth;
1401
+ const leaves = this.columnGroupModel.getLeavesInGroup(groupId);
1402
+ for (const leaf of leaves) {
1403
+ const oldW = this.colStyles.getWidth(leaf.colId);
1404
+ const newW = Math.max(leaf.minWidth ?? 40, Math.round(oldW * ratio));
1405
+ this.colStyles.setWidth(leaf.colId, newW);
1406
+ this.columnModel.setColumnWidth(leaf.colId, newW, false);
1407
+ }
1408
+ this.scheduleRender();
1409
+ }
1410
+ /**
1411
+ * Re-wires column-group references back into `HeaderRenderer` after
1412
+ * `headerRenderer.destroy()` has cleared them.
1413
+ */
1414
+ rewireGroupModelIntoHeaderRenderer() {
1415
+ // New Display Group Engine path takes priority
1416
+ if (this.displayGroupEngine) {
1417
+ this.headerRenderer.setDisplayGroupEngine(this.displayGroupEngine);
1418
+ this.headerRenderer.setGroupToggleCallback((gid) => this.handleGroupToggle(gid));
1419
+ this.headerRenderer.setGroupResizeCallback((gid, w) => this.handleGroupResize(gid, w));
1420
+ return;
1421
+ }
1422
+ // Legacy ColumnGroupModel path
1423
+ if (!this.columnGroupModel || !this.groupHeaderBuilder)
1424
+ return;
1425
+ this.headerRenderer.setColumnGroupModel(this.columnGroupModel, this.groupHeaderBuilder);
1426
+ this.headerRenderer.setGroupToggleCallback((gid) => this.handleGroupToggle(gid));
1427
+ this.headerRenderer.setGroupResizeCallback((gid, w) => this.handleGroupResize(gid, w));
1428
+ if (this.groupDragHandler) {
1429
+ this.groupHeaderBuilder.setDragConfig(this.groupDragHandler, () => this.wrapperEl);
1430
+ this.headerRenderer.setGroupDragHandler(this.groupDragHandler);
1431
+ }
1432
+ }
1433
+ /**
1434
+ * Full header rebuild — clears inner HTML and resets the rendered flag so
1435
+ * the next `performRender` call re-runs `renderInPanels` with the current
1436
+ * group model state.
1437
+ */
1438
+ rebuildHeader() {
1439
+ this.headerRendered = false;
1440
+ this.lastCenterColStart = -1;
1441
+ this.lastCenterColEnd = -1;
1442
+ if (this.leftHeaderPanelEl)
1443
+ this.leftHeaderPanelEl.innerHTML = '';
1444
+ if (this.centerHeaderInnerEl)
1445
+ this.centerHeaderInnerEl.innerHTML = '';
1446
+ if (this.rightHeaderPanelEl)
1447
+ this.rightHeaderPanelEl.innerHTML = '';
1448
+ this.headerRenderer.destroy();
1449
+ this.rewireGroupModelIntoHeaderRenderer();
1450
+ this.scheduleRender();
1451
+ }
1452
+ generateId() {
1453
+ return `pg_${Math.random().toString(36).slice(2, 9)}`;
1454
+ }
1455
+ }
1456
+ //# sourceMappingURL=grid-renderer.js.map