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.
- package/LICENSE +1 -0
- package/README.md +0 -0
- package/cell-selection/active-grid-registry.d.ts +37 -0
- package/cell-selection/active-grid-registry.d.ts.map +1 -0
- package/cell-selection/active-grid-registry.js +25 -0
- package/cell-selection/active-grid-registry.js.map +1 -0
- package/cell-selection/cell-selection-engine.d.ts +336 -0
- package/cell-selection/cell-selection-engine.d.ts.map +1 -0
- package/cell-selection/cell-selection-engine.js +1534 -0
- package/cell-selection/cell-selection-engine.js.map +1 -0
- package/cell-selection/selection-range.d.ts +16 -0
- package/cell-selection/selection-range.d.ts.map +1 -0
- package/cell-selection/selection-range.js +48 -0
- package/cell-selection/selection-range.js.map +1 -0
- package/cell-selection/selection-renderer.d.ts +24 -0
- package/cell-selection/selection-renderer.d.ts.map +1 -0
- package/cell-selection/selection-renderer.js +107 -0
- package/cell-selection/selection-renderer.js.map +1 -0
- package/chart/chart-analyzer/aggregation-analyzer.d.ts +18 -0
- package/chart/chart-analyzer/aggregation-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/aggregation-analyzer.js +63 -0
- package/chart/chart-analyzer/aggregation-analyzer.js.map +1 -0
- package/chart/chart-analyzer/bar-analyzer.d.ts +19 -0
- package/chart/chart-analyzer/bar-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/bar-analyzer.js +56 -0
- package/chart/chart-analyzer/bar-analyzer.js.map +1 -0
- package/chart/chart-analyzer/bubble-analyzer.d.ts +16 -0
- package/chart/chart-analyzer/bubble-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/bubble-analyzer.js +61 -0
- package/chart/chart-analyzer/bubble-analyzer.js.map +1 -0
- package/chart/chart-analyzer/cardinality-analyzer.d.ts +13 -0
- package/chart/chart-analyzer/cardinality-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/cardinality-analyzer.js +28 -0
- package/chart/chart-analyzer/cardinality-analyzer.js.map +1 -0
- package/chart/chart-analyzer/dimension-analyzer.d.ts +12 -0
- package/chart/chart-analyzer/dimension-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/dimension-analyzer.js +33 -0
- package/chart/chart-analyzer/dimension-analyzer.js.map +1 -0
- package/chart/chart-analyzer/histogram-analyzer.d.ts +11 -0
- package/chart/chart-analyzer/histogram-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/histogram-analyzer.js +43 -0
- package/chart/chart-analyzer/histogram-analyzer.js.map +1 -0
- package/chart/chart-analyzer/index.d.ts +58 -0
- package/chart/chart-analyzer/index.d.ts.map +1 -0
- package/chart/chart-analyzer/index.js +130 -0
- package/chart/chart-analyzer/index.js.map +1 -0
- package/chart/chart-analyzer/line-analyzer.d.ts +23 -0
- package/chart/chart-analyzer/line-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/line-analyzer.js +61 -0
- package/chart/chart-analyzer/line-analyzer.js.map +1 -0
- package/chart/chart-analyzer/measure-analyzer.d.ts +11 -0
- package/chart/chart-analyzer/measure-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/measure-analyzer.js +44 -0
- package/chart/chart-analyzer/measure-analyzer.js.map +1 -0
- package/chart/chart-analyzer/pie-analyzer.d.ts +19 -0
- package/chart/chart-analyzer/pie-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/pie-analyzer.js +64 -0
- package/chart/chart-analyzer/pie-analyzer.js.map +1 -0
- package/chart/chart-analyzer/recommendation-engine.d.ts +18 -0
- package/chart/chart-analyzer/recommendation-engine.d.ts.map +1 -0
- package/chart/chart-analyzer/recommendation-engine.js +82 -0
- package/chart/chart-analyzer/recommendation-engine.js.map +1 -0
- package/chart/chart-analyzer/scatter-analyzer.d.ts +16 -0
- package/chart/chart-analyzer/scatter-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer/scatter-analyzer.js +70 -0
- package/chart/chart-analyzer/scatter-analyzer.js.map +1 -0
- package/chart/chart-analyzer/types.d.ts +52 -0
- package/chart/chart-analyzer/types.d.ts.map +1 -0
- package/chart/chart-analyzer/types.js +10 -0
- package/chart/chart-analyzer/types.js.map +1 -0
- package/chart/chart-analyzer.d.ts +12 -0
- package/chart/chart-analyzer.d.ts.map +1 -0
- package/chart/chart-analyzer.js +77 -0
- package/chart/chart-analyzer.js.map +1 -0
- package/chart/chart-data-transformer.d.ts +26 -0
- package/chart/chart-data-transformer.d.ts.map +1 -0
- package/chart/chart-data-transformer.js +93 -0
- package/chart/chart-data-transformer.js.map +1 -0
- package/chart/chart-engine.d.ts +38 -0
- package/chart/chart-engine.d.ts.map +1 -0
- package/chart/chart-engine.js +96 -0
- package/chart/chart-engine.js.map +1 -0
- package/chart/chart-panel.d.ts +54 -0
- package/chart/chart-panel.d.ts.map +1 -0
- package/chart/chart-panel.js +350 -0
- package/chart/chart-panel.js.map +1 -0
- package/chart/chart-renderer.d.ts +93 -0
- package/chart/chart-renderer.d.ts.map +1 -0
- package/chart/chart-renderer.js +1330 -0
- package/chart/chart-renderer.js.map +1 -0
- package/chart/sparkline/sparkline-renderer.d.ts +269 -0
- package/chart/sparkline/sparkline-renderer.d.ts.map +1 -0
- package/chart/sparkline/sparkline-renderer.js +875 -0
- package/chart/sparkline/sparkline-renderer.js.map +1 -0
- package/chart/sparkline/sparkline.types.d.ts +214 -0
- package/chart/sparkline/sparkline.types.d.ts.map +1 -0
- package/chart/sparkline/sparkline.types.js +8 -0
- package/chart/sparkline/sparkline.types.js.map +1 -0
- package/column-groups/column-group-drag-handler.d.ts +169 -0
- package/column-groups/column-group-drag-handler.d.ts.map +1 -0
- package/column-groups/column-group-drag-handler.js +517 -0
- package/column-groups/column-group-drag-handler.js.map +1 -0
- package/column-groups/column-group-header-builder.d.ts +106 -0
- package/column-groups/column-group-header-builder.d.ts.map +1 -0
- package/column-groups/column-group-header-builder.js +311 -0
- package/column-groups/column-group-header-builder.js.map +1 -0
- package/column-groups/column-group-model.d.ts +248 -0
- package/column-groups/column-group-model.d.ts.map +1 -0
- package/column-groups/column-group-model.js +668 -0
- package/column-groups/column-group-model.js.map +1 -0
- package/column-groups/column-group-state-manager.d.ts +110 -0
- package/column-groups/column-group-state-manager.d.ts.map +1 -0
- package/column-groups/column-group-state-manager.js +153 -0
- package/column-groups/column-group-state-manager.js.map +1 -0
- package/column-groups/column-group.types.d.ts +250 -0
- package/column-groups/column-group.types.d.ts.map +1 -0
- package/column-groups/column-group.types.js +20 -0
- package/column-groups/column-group.types.js.map +1 -0
- package/column-groups/display-group-builder.d.ts +91 -0
- package/column-groups/display-group-builder.d.ts.map +1 -0
- package/column-groups/display-group-builder.js +225 -0
- package/column-groups/display-group-builder.js.map +1 -0
- package/column-groups/display-group-drag-handler.d.ts +205 -0
- package/column-groups/display-group-drag-handler.d.ts.map +1 -0
- package/column-groups/display-group-drag-handler.js +536 -0
- package/column-groups/display-group-drag-handler.js.map +1 -0
- package/column-groups/display-group-engine.d.ts +310 -0
- package/column-groups/display-group-engine.d.ts.map +1 -0
- package/column-groups/display-group-engine.js +549 -0
- package/column-groups/display-group-engine.js.map +1 -0
- package/column-groups/display-group-header-builder.d.ts +94 -0
- package/column-groups/display-group-header-builder.d.ts.map +1 -0
- package/column-groups/display-group-header-builder.js +224 -0
- package/column-groups/display-group-header-builder.js.map +1 -0
- package/column-groups/display-group.types.d.ts +140 -0
- package/column-groups/display-group.types.d.ts.map +1 -0
- package/column-groups/display-group.types.js +2 -0
- package/column-groups/display-group.types.js.map +1 -0
- package/column-groups/index.d.ts +25 -0
- package/column-groups/index.d.ts.map +1 -0
- package/column-groups/index.js +26 -0
- package/column-groups/index.js.map +1 -0
- package/column-groups/logical-group-registry.d.ts +77 -0
- package/column-groups/logical-group-registry.d.ts.map +1 -0
- package/column-groups/logical-group-registry.js +118 -0
- package/column-groups/logical-group-registry.js.map +1 -0
- package/core/column-model.d.ts +30 -0
- package/core/column-model.d.ts.map +1 -0
- package/core/column-model.js +203 -0
- package/core/column-model.js.map +1 -0
- package/core/grid-api.d.ts +187 -0
- package/core/grid-api.d.ts.map +1 -0
- package/core/grid-api.js +569 -0
- package/core/grid-api.js.map +1 -0
- package/core/grid-context.d.ts +61 -0
- package/core/grid-context.d.ts.map +1 -0
- package/core/grid-context.js +2 -0
- package/core/grid-context.js.map +1 -0
- package/core/grid-core.d.ts +49 -0
- package/core/grid-core.d.ts.map +1 -0
- package/core/grid-core.js +675 -0
- package/core/grid-core.js.map +1 -0
- package/core/grid-store.d.ts +62 -0
- package/core/grid-store.d.ts.map +1 -0
- package/core/grid-store.js +104 -0
- package/core/grid-store.js.map +1 -0
- package/core/row-model.d.ts +32 -0
- package/core/row-model.d.ts.map +1 -0
- package/core/row-model.js +150 -0
- package/core/row-model.js.map +1 -0
- package/drag-drop/drag-autoscroll.d.ts +14 -0
- package/drag-drop/drag-autoscroll.d.ts.map +1 -0
- package/drag-drop/drag-autoscroll.js +66 -0
- package/drag-drop/drag-autoscroll.js.map +1 -0
- package/drag-drop/drag-drop-engine.d.ts +55 -0
- package/drag-drop/drag-drop-engine.d.ts.map +1 -0
- package/drag-drop/drag-drop-engine.js +179 -0
- package/drag-drop/drag-drop-engine.js.map +1 -0
- package/drag-drop/drag-preview.d.ts +32 -0
- package/drag-drop/drag-preview.d.ts.map +1 -0
- package/drag-drop/drag-preview.js +74 -0
- package/drag-drop/drag-preview.js.map +1 -0
- package/engines/aggregation/aggregation-engine.d.ts +66 -0
- package/engines/aggregation/aggregation-engine.d.ts.map +1 -0
- package/engines/aggregation/aggregation-engine.js +149 -0
- package/engines/aggregation/aggregation-engine.js.map +1 -0
- package/engines/clipboard/clipboard-engine.d.ts +89 -0
- package/engines/clipboard/clipboard-engine.d.ts.map +1 -0
- package/engines/clipboard/clipboard-engine.js +323 -0
- package/engines/clipboard/clipboard-engine.js.map +1 -0
- package/engines/editing/cell-editor-engine.d.ts +62 -0
- package/engines/editing/cell-editor-engine.d.ts.map +1 -0
- package/engines/editing/cell-editor-engine.js +359 -0
- package/engines/editing/cell-editor-engine.js.map +1 -0
- package/engines/editing/custom-dropdown-editor.d.ts +99 -0
- package/engines/editing/custom-dropdown-editor.d.ts.map +1 -0
- package/engines/editing/custom-dropdown-editor.js +355 -0
- package/engines/editing/custom-dropdown-editor.js.map +1 -0
- package/engines/editing/value-parser.d.ts +12 -0
- package/engines/editing/value-parser.d.ts.map +1 -0
- package/engines/editing/value-parser.js +125 -0
- package/engines/editing/value-parser.js.map +1 -0
- package/engines/export/export-engine.d.ts +16 -0
- package/engines/export/export-engine.d.ts.map +1 -0
- package/engines/export/export-engine.js +99 -0
- package/engines/export/export-engine.js.map +1 -0
- package/engines/filter/filter-condition.d.ts +7 -0
- package/engines/filter/filter-condition.d.ts.map +1 -0
- package/engines/filter/filter-condition.js +126 -0
- package/engines/filter/filter-condition.js.map +1 -0
- package/engines/filter/filter-engine.d.ts +35 -0
- package/engines/filter/filter-engine.d.ts.map +1 -0
- package/engines/filter/filter-engine.js +150 -0
- package/engines/filter/filter-engine.js.map +1 -0
- package/engines/filter/filter-panel.d.ts +103 -0
- package/engines/filter/filter-panel.d.ts.map +1 -0
- package/engines/filter/filter-panel.js +645 -0
- package/engines/filter/filter-panel.js.map +1 -0
- package/engines/grouping/group-node.d.ts +21 -0
- package/engines/grouping/group-node.d.ts.map +1 -0
- package/engines/grouping/group-node.js +96 -0
- package/engines/grouping/group-node.js.map +1 -0
- package/engines/grouping/grouping-engine.d.ts +33 -0
- package/engines/grouping/grouping-engine.d.ts.map +1 -0
- package/engines/grouping/grouping-engine.js +118 -0
- package/engines/grouping/grouping-engine.js.map +1 -0
- package/engines/master-detail/master-detail-engine.d.ts +77 -0
- package/engines/master-detail/master-detail-engine.d.ts.map +1 -0
- package/engines/master-detail/master-detail-engine.js +199 -0
- package/engines/master-detail/master-detail-engine.js.map +1 -0
- package/engines/pagination/pagination-engine.d.ts +33 -0
- package/engines/pagination/pagination-engine.d.ts.map +1 -0
- package/engines/pagination/pagination-engine.js +110 -0
- package/engines/pagination/pagination-engine.js.map +1 -0
- package/engines/selection/row-selection-engine.d.ts +26 -0
- package/engines/selection/row-selection-engine.d.ts.map +1 -0
- package/engines/selection/row-selection-engine.js +145 -0
- package/engines/selection/row-selection-engine.js.map +1 -0
- package/engines/sort/sort-comparator.d.ts +43 -0
- package/engines/sort/sort-comparator.d.ts.map +1 -0
- package/engines/sort/sort-comparator.js +113 -0
- package/engines/sort/sort-comparator.js.map +1 -0
- package/engines/sort/sort-engine.d.ts +67 -0
- package/engines/sort/sort-engine.d.ts.map +1 -0
- package/engines/sort/sort-engine.js +178 -0
- package/engines/sort/sort-engine.js.map +1 -0
- package/engines/summary/summary-engine.d.ts +17 -0
- package/engines/summary/summary-engine.d.ts.map +1 -0
- package/engines/summary/summary-engine.js +72 -0
- package/engines/summary/summary-engine.js.map +1 -0
- package/engines/tree/tree-data-service.d.ts +117 -0
- package/engines/tree/tree-data-service.d.ts.map +1 -0
- package/engines/tree/tree-data-service.js +296 -0
- package/engines/tree/tree-data-service.js.map +1 -0
- package/engines/tree/tree-expansion-service.d.ts +35 -0
- package/engines/tree/tree-expansion-service.d.ts.map +1 -0
- package/engines/tree/tree-expansion-service.js +84 -0
- package/engines/tree/tree-expansion-service.js.map +1 -0
- package/engines/tree/tree-node.d.ts +40 -0
- package/engines/tree/tree-node.d.ts.map +1 -0
- package/engines/tree/tree-node.js +199 -0
- package/engines/tree/tree-node.js.map +1 -0
- package/engines/tree/tree-selection-service.d.ts +35 -0
- package/engines/tree/tree-selection-service.d.ts.map +1 -0
- package/engines/tree/tree-selection-service.js +72 -0
- package/engines/tree/tree-selection-service.js.map +1 -0
- package/engines/undo-redo/undo-redo-engine.d.ts +114 -0
- package/engines/undo-redo/undo-redo-engine.d.ts.map +1 -0
- package/engines/undo-redo/undo-redo-engine.js +165 -0
- package/engines/undo-redo/undo-redo-engine.js.map +1 -0
- package/event-bus/event-bus.d.ts +21 -0
- package/event-bus/event-bus.d.ts.map +1 -0
- package/event-bus/event-bus.js +106 -0
- package/event-bus/event-bus.js.map +1 -0
- package/icons/icon-registry.d.ts +19 -0
- package/icons/icon-registry.d.ts.map +1 -0
- package/icons/icon-registry.js +37 -0
- package/icons/icon-registry.js.map +1 -0
- package/icons/icon-renderer.d.ts +18 -0
- package/icons/icon-renderer.d.ts.map +1 -0
- package/icons/icon-renderer.js +75 -0
- package/icons/icon-renderer.js.map +1 -0
- package/icons/icon-sets/core-icons.d.ts +2 -0
- package/icons/icon-sets/core-icons.d.ts.map +1 -0
- package/icons/icon-sets/core-icons.js +48 -0
- package/icons/icon-sets/core-icons.js.map +1 -0
- package/index.d.ts +72 -0
- package/index.d.ts.map +1 -0
- package/index.js +54 -0
- package/index.js.map +1 -0
- package/package.json +64 -0
- package/photon-ai/builtins/filter.commands.d.ts +4 -0
- package/photon-ai/builtins/filter.commands.d.ts.map +1 -0
- package/photon-ai/builtins/filter.commands.js +189 -0
- package/photon-ai/builtins/filter.commands.js.map +1 -0
- package/photon-ai/builtins/grouping.commands.d.ts +4 -0
- package/photon-ai/builtins/grouping.commands.d.ts.map +1 -0
- package/photon-ai/builtins/grouping.commands.js +115 -0
- package/photon-ai/builtins/grouping.commands.js.map +1 -0
- package/photon-ai/builtins/index.d.ts +10 -0
- package/photon-ai/builtins/index.d.ts.map +1 -0
- package/photon-ai/builtins/index.js +26 -0
- package/photon-ai/builtins/index.js.map +1 -0
- package/photon-ai/builtins/info.commands.d.ts +4 -0
- package/photon-ai/builtins/info.commands.d.ts.map +1 -0
- package/photon-ai/builtins/info.commands.js +201 -0
- package/photon-ai/builtins/info.commands.js.map +1 -0
- package/photon-ai/builtins/move.commands.d.ts +4 -0
- package/photon-ai/builtins/move.commands.d.ts.map +1 -0
- package/photon-ai/builtins/move.commands.js +53 -0
- package/photon-ai/builtins/move.commands.js.map +1 -0
- package/photon-ai/builtins/pin.commands.d.ts +4 -0
- package/photon-ai/builtins/pin.commands.d.ts.map +1 -0
- package/photon-ai/builtins/pin.commands.js +106 -0
- package/photon-ai/builtins/pin.commands.js.map +1 -0
- package/photon-ai/builtins/selection.commands.d.ts +4 -0
- package/photon-ai/builtins/selection.commands.d.ts.map +1 -0
- package/photon-ai/builtins/selection.commands.js +102 -0
- package/photon-ai/builtins/selection.commands.js.map +1 -0
- package/photon-ai/builtins/sort.commands.d.ts +4 -0
- package/photon-ai/builtins/sort.commands.d.ts.map +1 -0
- package/photon-ai/builtins/sort.commands.js +67 -0
- package/photon-ai/builtins/sort.commands.js.map +1 -0
- package/photon-ai/builtins/visibility.commands.d.ts +4 -0
- package/photon-ai/builtins/visibility.commands.d.ts.map +1 -0
- package/photon-ai/builtins/visibility.commands.js +75 -0
- package/photon-ai/builtins/visibility.commands.js.map +1 -0
- package/photon-ai/command-builder.d.ts +21 -0
- package/photon-ai/command-builder.d.ts.map +1 -0
- package/photon-ai/command-builder.js +18 -0
- package/photon-ai/command-builder.js.map +1 -0
- package/photon-ai/command-executor.d.ts +15 -0
- package/photon-ai/command-executor.d.ts.map +1 -0
- package/photon-ai/command-executor.js +26 -0
- package/photon-ai/command-executor.js.map +1 -0
- package/photon-ai/entity-resolver.d.ts +121 -0
- package/photon-ai/entity-resolver.d.ts.map +1 -0
- package/photon-ai/entity-resolver.js +393 -0
- package/photon-ai/entity-resolver.js.map +1 -0
- package/photon-ai/fuzzy-match.d.ts +12 -0
- package/photon-ai/fuzzy-match.d.ts.map +1 -0
- package/photon-ai/fuzzy-match.js +37 -0
- package/photon-ai/fuzzy-match.js.map +1 -0
- package/photon-ai/index.d.ts +15 -0
- package/photon-ai/index.d.ts.map +1 -0
- package/photon-ai/index.js +13 -0
- package/photon-ai/index.js.map +1 -0
- package/photon-ai/intent-parser.d.ts +20 -0
- package/photon-ai/intent-parser.d.ts.map +1 -0
- package/photon-ai/intent-parser.js +52 -0
- package/photon-ai/intent-parser.js.map +1 -0
- package/photon-ai/photon-ai-memory.d.ts +42 -0
- package/photon-ai/photon-ai-memory.d.ts.map +1 -0
- package/photon-ai/photon-ai-memory.js +86 -0
- package/photon-ai/photon-ai-memory.js.map +1 -0
- package/photon-ai/photon-ai-panel.d.ts +54 -0
- package/photon-ai/photon-ai-panel.d.ts.map +1 -0
- package/photon-ai/photon-ai-panel.js +191 -0
- package/photon-ai/photon-ai-panel.js.map +1 -0
- package/photon-ai/photon-ai-registry.d.ts +25 -0
- package/photon-ai/photon-ai-registry.d.ts.map +1 -0
- package/photon-ai/photon-ai-registry.js +41 -0
- package/photon-ai/photon-ai-registry.js.map +1 -0
- package/photon-ai/photon-ai-service.d.ts +71 -0
- package/photon-ai/photon-ai-service.d.ts.map +1 -0
- package/photon-ai/photon-ai-service.js +165 -0
- package/photon-ai/photon-ai-service.js.map +1 -0
- package/photon-ai/photon-ai.types.d.ts +100 -0
- package/photon-ai/photon-ai.types.d.ts.map +1 -0
- package/photon-ai/photon-ai.types.js +2 -0
- package/photon-ai/photon-ai.types.js.map +1 -0
- package/photon-ai/query-splitter.d.ts +22 -0
- package/photon-ai/query-splitter.d.ts.map +1 -0
- package/photon-ai/query-splitter.js +81 -0
- package/photon-ai/query-splitter.js.map +1 -0
- package/photon-ai/text-normalizer.d.ts +33 -0
- package/photon-ai/text-normalizer.d.ts.map +1 -0
- package/photon-ai/text-normalizer.js +132 -0
- package/photon-ai/text-normalizer.js.map +1 -0
- package/renderer/auto-scroller.d.ts +78 -0
- package/renderer/auto-scroller.d.ts.map +1 -0
- package/renderer/auto-scroller.js +141 -0
- package/renderer/auto-scroller.js.map +1 -0
- package/renderer/body-renderer.d.ts +191 -0
- package/renderer/body-renderer.d.ts.map +1 -0
- package/renderer/body-renderer.js +806 -0
- package/renderer/body-renderer.js.map +1 -0
- package/renderer/cell-renderer.d.ts +26 -0
- package/renderer/cell-renderer.d.ts.map +1 -0
- package/renderer/cell-renderer.js +201 -0
- package/renderer/cell-renderer.js.map +1 -0
- package/renderer/column-menu.d.ts +285 -0
- package/renderer/column-menu.d.ts.map +1 -0
- package/renderer/column-menu.js +873 -0
- package/renderer/column-menu.js.map +1 -0
- package/renderer/column-style-manager.d.ts +38 -0
- package/renderer/column-style-manager.d.ts.map +1 -0
- package/renderer/column-style-manager.js +110 -0
- package/renderer/column-style-manager.js.map +1 -0
- package/renderer/detail-row-renderer.d.ts +153 -0
- package/renderer/detail-row-renderer.d.ts.map +1 -0
- package/renderer/detail-row-renderer.js +388 -0
- package/renderer/detail-row-renderer.js.map +1 -0
- package/renderer/dom-utils.d.ts +22 -0
- package/renderer/dom-utils.d.ts.map +1 -0
- package/renderer/dom-utils.js +132 -0
- package/renderer/dom-utils.js.map +1 -0
- package/renderer/footer-renderer.d.ts +25 -0
- package/renderer/footer-renderer.d.ts.map +1 -0
- package/renderer/footer-renderer.js +168 -0
- package/renderer/footer-renderer.js.map +1 -0
- package/renderer/grid-renderer.d.ts +295 -0
- package/renderer/grid-renderer.d.ts.map +1 -0
- package/renderer/grid-renderer.js +1456 -0
- package/renderer/grid-renderer.js.map +1 -0
- package/renderer/group-context-menu.d.ts +79 -0
- package/renderer/group-context-menu.d.ts.map +1 -0
- package/renderer/group-context-menu.js +238 -0
- package/renderer/group-context-menu.js.map +1 -0
- package/renderer/group-drop-zone.d.ts +56 -0
- package/renderer/group-drop-zone.d.ts.map +1 -0
- package/renderer/group-drop-zone.js +376 -0
- package/renderer/group-drop-zone.js.map +1 -0
- package/renderer/header-renderer.d.ts +264 -0
- package/renderer/header-renderer.d.ts.map +1 -0
- package/renderer/header-renderer.js +1321 -0
- package/renderer/header-renderer.js.map +1 -0
- package/renderer/overlay-renderer.d.ts +16 -0
- package/renderer/overlay-renderer.d.ts.map +1 -0
- package/renderer/overlay-renderer.js +64 -0
- package/renderer/overlay-renderer.js.map +1 -0
- package/renderer/renderer-resolver.d.ts +18 -0
- package/renderer/renderer-resolver.d.ts.map +1 -0
- package/renderer/renderer-resolver.js +18 -0
- package/renderer/renderer-resolver.js.map +1 -0
- package/renderer/row-animator.d.ts +72 -0
- package/renderer/row-animator.d.ts.map +1 -0
- package/renderer/row-animator.js +175 -0
- package/renderer/row-animator.js.map +1 -0
- package/renderer/row-drag-renderer.d.ts +58 -0
- package/renderer/row-drag-renderer.d.ts.map +1 -0
- package/renderer/row-drag-renderer.js +395 -0
- package/renderer/row-drag-renderer.js.map +1 -0
- package/renderer/row-offset-search.d.ts +17 -0
- package/renderer/row-offset-search.d.ts.map +1 -0
- package/renderer/row-offset-search.js +31 -0
- package/renderer/row-offset-search.js.map +1 -0
- package/renderer/row-position-sheet.d.ts +11 -0
- package/renderer/row-position-sheet.d.ts.map +1 -0
- package/renderer/row-position-sheet.js +29 -0
- package/renderer/row-position-sheet.js.map +1 -0
- package/renderer/scroll-controller.d.ts +58 -0
- package/renderer/scroll-controller.d.ts.map +1 -0
- package/renderer/scroll-controller.js +200 -0
- package/renderer/scroll-controller.js.map +1 -0
- package/renderer/sticky-row-tracker.d.ts +40 -0
- package/renderer/sticky-row-tracker.d.ts.map +1 -0
- package/renderer/sticky-row-tracker.js +63 -0
- package/renderer/sticky-row-tracker.js.map +1 -0
- package/renderer/tooltip-renderer.d.ts +36 -0
- package/renderer/tooltip-renderer.d.ts.map +1 -0
- package/renderer/tooltip-renderer.js +133 -0
- package/renderer/tooltip-renderer.js.map +1 -0
- package/renderer/tree-cell-renderer.d.ts +37 -0
- package/renderer/tree-cell-renderer.d.ts.map +1 -0
- package/renderer/tree-cell-renderer.js +75 -0
- package/renderer/tree-cell-renderer.js.map +1 -0
- package/renderer/tree-sticky-row-tracker.d.ts +62 -0
- package/renderer/tree-sticky-row-tracker.d.ts.map +1 -0
- package/renderer/tree-sticky-row-tracker.js +120 -0
- package/renderer/tree-sticky-row-tracker.js.map +1 -0
- package/renderer/virtual-scroll-renderer.d.ts +37 -0
- package/renderer/virtual-scroll-renderer.d.ts.map +1 -0
- package/renderer/virtual-scroll-renderer.js +133 -0
- package/renderer/virtual-scroll-renderer.js.map +1 -0
- package/styles/base-styles.d.ts +3 -0
- package/styles/base-styles.d.ts.map +1 -0
- package/styles/base-styles.js +3045 -0
- package/styles/base-styles.js.map +1 -0
- package/styles/themes/theme-alpine.d.ts +2 -0
- package/styles/themes/theme-alpine.d.ts.map +1 -0
- package/styles/themes/theme-alpine.js +120 -0
- package/styles/themes/theme-alpine.js.map +1 -0
- package/styles/themes/theme-balham.d.ts +2 -0
- package/styles/themes/theme-balham.d.ts.map +1 -0
- package/styles/themes/theme-balham.js +120 -0
- package/styles/themes/theme-balham.js.map +1 -0
- package/styles/themes/theme-dark.d.ts +2 -0
- package/styles/themes/theme-dark.d.ts.map +1 -0
- package/styles/themes/theme-dark.js +216 -0
- package/styles/themes/theme-dark.js.map +1 -0
- package/styles/themes/theme-material.d.ts +2 -0
- package/styles/themes/theme-material.d.ts.map +1 -0
- package/styles/themes/theme-material.js +134 -0
- package/styles/themes/theme-material.js.map +1 -0
- package/styles/themes/theme-quartz.d.ts +2 -0
- package/styles/themes/theme-quartz.d.ts.map +1 -0
- package/styles/themes/theme-quartz.js +120 -0
- package/styles/themes/theme-quartz.js.map +1 -0
- package/theme/css-var-injector.d.ts +10 -0
- package/theme/css-var-injector.d.ts.map +1 -0
- package/theme/css-var-injector.js +63 -0
- package/theme/css-var-injector.js.map +1 -0
- package/theme/theme-manager.d.ts +20 -0
- package/theme/theme-manager.d.ts.map +1 -0
- package/theme/theme-manager.js +79 -0
- package/theme/theme-manager.js.map +1 -0
- package/theme/themes/dark-theme.d.ts +3 -0
- package/theme/themes/dark-theme.d.ts.map +1 -0
- package/theme/themes/dark-theme.js +149 -0
- package/theme/themes/dark-theme.js.map +1 -0
- package/theme/themes/light-theme.d.ts +3 -0
- package/theme/themes/light-theme.d.ts.map +1 -0
- package/theme/themes/light-theme.js +149 -0
- package/theme/themes/light-theme.js.map +1 -0
- package/types/column.types.d.ts +195 -0
- package/types/column.types.d.ts.map +1 -0
- package/types/column.types.js +2 -0
- package/types/column.types.js.map +1 -0
- package/types/event.types.d.ts +213 -0
- package/types/event.types.d.ts.map +1 -0
- package/types/event.types.js +67 -0
- package/types/event.types.js.map +1 -0
- package/types/filter.types.d.ts +42 -0
- package/types/filter.types.d.ts.map +1 -0
- package/types/filter.types.js +2 -0
- package/types/filter.types.js.map +1 -0
- package/types/grid.types.d.ts +214 -0
- package/types/grid.types.d.ts.map +1 -0
- package/types/grid.types.js +2 -0
- package/types/grid.types.js.map +1 -0
- package/types/master-detail.types.d.ts +112 -0
- package/types/master-detail.types.d.ts.map +1 -0
- package/types/master-detail.types.js +2 -0
- package/types/master-detail.types.js.map +1 -0
- package/types/photon-ai.types.d.ts +19 -0
- package/types/photon-ai.types.d.ts.map +1 -0
- package/types/photon-ai.types.js +2 -0
- package/types/photon-ai.types.js.map +1 -0
- package/types/renderer.types.d.ts +156 -0
- package/types/renderer.types.d.ts.map +1 -0
- package/types/renderer.types.js +19 -0
- package/types/renderer.types.js.map +1 -0
- package/types/row.types.d.ts +127 -0
- package/types/row.types.d.ts.map +1 -0
- package/types/row.types.js +9 -0
- package/types/row.types.js.map +1 -0
- package/types/theme.types.d.ts +159 -0
- package/types/theme.types.d.ts.map +1 -0
- package/types/theme.types.js +2 -0
- package/types/theme.types.js.map +1 -0
- package/types/tree-data.types.d.ts +94 -0
- package/types/tree-data.types.d.ts.map +1 -0
- package/types/tree-data.types.js +2 -0
- 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
|