gp-grid-react 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # gp-grid-react 🏁 🏎️
2
2
 
3
3
  <div align="center">
4
- <a href="https://gp-grid-docs.vercel.app">
4
+ <a href="https://www.gp-grid.io">
5
5
  <picture>
6
6
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/GioPat/gp-grid-docs/refs/heads/master/public/logo-light.svg"/>
7
7
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/GioPat/gp-grid-docs/refs/heads/master/public/logo-dark.svg"/>
@@ -10,10 +10,12 @@
10
10
  </a>
11
11
  <div align="center">
12
12
  Logo by <a href="https://github.com/camillo18tre">camillo18tre ❤️</a>
13
- <h4><a href="https://gp-grid-docs.vercel.app/">🎮 Demo</a> • <a href="https://gp-grid-docs.vercel.app/docs/react">📖 Documentation</a>
13
+ <h4><a href="https://www.gp-grid.io/">🎮 Demo</a> • <a href="https://www.gp-grid.io/docs/react">📖 Documentation</a>
14
14
  </div>
15
15
  </div>
16
16
 
17
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/GioPat/gp-grid)
18
+
17
19
  A high-performance, feature lean React data grid component built to manage grids with huge amount (millions) of rows. It's based on its core dependency: `gp-grid-core`, featuring virtual scrolling, cell selection, sorting, filtering, editing, and Excel-like fill handle.
18
20
 
19
21
  ## Table of Contents
@@ -29,7 +31,7 @@ A high-performance, feature lean React data grid component built to manage grids
29
31
 
30
32
  ## Features
31
33
 
32
- - **Virtual Scrolling**: Efficiently handles 150,000+ rows through slot-based recycling
34
+ - **Virtual Scrolling**: Efficiently handles millions of rows through slot-based recycling
33
35
  - **Cell Selection**: Single cell, range selection, Shift+click extend, Ctrl+click toggle
34
36
  - **Multi-Column Sorting**: Click to sort, Shift+click for multi-column sort
35
37
  - **Column Filtering**: Built-in filter row with debounced input
package/dist/index.d.ts CHANGED
@@ -60,6 +60,81 @@ interface FillHandleState {
60
60
  targetCol: number;
61
61
  }
62
62
  //#endregion
63
+ //#region ../core/src/types/highlighting.d.ts
64
+ /**
65
+ * Minimal column info for highlighting context.
66
+ * Uses structural typing to avoid circular dependency with columns.ts.
67
+ */
68
+ interface HighlightColumnInfo {
69
+ field: string;
70
+ colId?: string;
71
+ }
72
+ /**
73
+ * Unified context for row, column, and cell highlighting.
74
+ *
75
+ * - Row context: `rowIndex` is set, `colIndex` is null
76
+ * - Column context: `colIndex` is set, `rowIndex` is null
77
+ * - Cell context: both `rowIndex` and `colIndex` are set
78
+ */
79
+ interface HighlightContext<TData = Record<string, unknown>> {
80
+ /** Row index. Null for column-only context. */
81
+ rowIndex: number | null;
82
+ /** Column index. Null for row-only context. */
83
+ colIndex: number | null;
84
+ /** Column definition. Present for column and cell contexts. */
85
+ column?: HighlightColumnInfo;
86
+ /** Row data. Present for row and cell contexts. */
87
+ rowData?: TData;
88
+ /** Currently hovered cell position, null if not hovering */
89
+ hoverPosition: CellPosition | null;
90
+ /** Currently active (focused) cell position */
91
+ activeCell: CellPosition | null;
92
+ /** Current selection range */
93
+ selectionRange: CellRange | null;
94
+ /** Whether this row/column/cell is hovered (respects hoverScope) */
95
+ isHovered: boolean;
96
+ /** Whether this row/column contains or is the active cell */
97
+ isActive: boolean;
98
+ /** Whether this row/column/cell overlaps or is in the selection range */
99
+ isSelected: boolean;
100
+ }
101
+ /**
102
+ * Grid-level highlighting options.
103
+ * Hover tracking is automatically enabled when any highlighting callback is defined.
104
+ * Each callback type has its own natural interpretation of `isHovered`:
105
+ * - computeRowClasses: isHovered = mouse is on any cell in this row
106
+ * - computeColumnClasses: isHovered = mouse is on any cell in this column
107
+ * - computeCellClasses: isHovered = mouse is on this exact cell
108
+ *
109
+ * For a crosshair effect, implement both computeRowClasses and computeColumnClasses.
110
+ */
111
+ interface HighlightingOptions<TData = Record<string, unknown>> {
112
+ /**
113
+ * Row-level class callback.
114
+ * Classes returned are applied to the row container element.
115
+ * Context has `rowIndex` set, `colIndex` is null.
116
+ * `isHovered` is true when the mouse is on any cell in this row.
117
+ * @returns Array of CSS class names
118
+ */
119
+ computeRowClasses?: (context: HighlightContext<TData>) => string[];
120
+ /**
121
+ * Column-level class callback.
122
+ * Classes returned are applied to all cells in that column (not header).
123
+ * Context has `colIndex` set, `rowIndex` is null.
124
+ * `isHovered` is true when the mouse is on any cell in this column.
125
+ * @returns Array of CSS class names
126
+ */
127
+ computeColumnClasses?: (context: HighlightContext<TData>) => string[];
128
+ /**
129
+ * Cell-level class callback.
130
+ * Classes returned are applied to individual cells for fine-grained control.
131
+ * Context has both `rowIndex` and `colIndex` set.
132
+ * `isHovered` is true only when the mouse is on this exact cell.
133
+ * @returns Array of CSS class names
134
+ */
135
+ computeCellClasses?: (context: HighlightContext<TData>) => string[];
136
+ }
137
+ //#endregion
63
138
  //#region ../core/src/types/columns.d.ts
64
139
  /** Column definition */
65
140
  interface ColumnDefinition {
@@ -73,10 +148,26 @@ interface ColumnDefinition {
73
148
  sortable?: boolean;
74
149
  /** Whether column is filterable. Default: true */
75
150
  filterable?: boolean;
151
+ /** Whether column is hidden. Hidden columns are not rendered but still exist in the definition. Default: false */
152
+ hidden?: boolean;
76
153
  /** Renderer key for adapter lookup, or inline renderer function */
77
154
  cellRenderer?: string;
78
155
  editRenderer?: string;
79
156
  headerRenderer?: string;
157
+ /**
158
+ * Per-column override for column-level highlighting.
159
+ * If defined, overrides grid-level computeColumnClasses for this column.
160
+ * Context has `colIndex` set, `rowIndex` is null.
161
+ * @returns Array of CSS class names to apply to all cells in this column
162
+ */
163
+ computeColumnClasses?: (context: HighlightContext) => string[];
164
+ /**
165
+ * Per-column override for cell-level highlighting.
166
+ * If defined, overrides grid-level computeCellClasses for cells in this column.
167
+ * Context has both `rowIndex` and `colIndex` set.
168
+ * @returns Array of CSS class names to apply to individual cells
169
+ */
170
+ computeCellClasses?: (context: HighlightContext) => string[];
80
171
  }
81
172
  //#endregion
82
173
  //#region ../core/src/types/filters.d.ts
@@ -188,6 +279,11 @@ interface SetActiveCellInstruction {
188
279
  type: "SET_ACTIVE_CELL";
189
280
  position: CellPosition | null;
190
281
  }
282
+ /** Set hover position instruction (for highlighting) */
283
+ interface SetHoverPositionInstruction {
284
+ type: "SET_HOVER_POSITION";
285
+ position: CellPosition | null;
286
+ }
191
287
  /** Set selection range instruction */
192
288
  interface SetSelectionRangeInstruction {
193
289
  type: "SET_SELECTION_RANGE";
@@ -297,19 +393,20 @@ interface DataErrorInstruction {
297
393
  /** Rows added instruction */
298
394
  interface RowsAddedInstruction {
299
395
  type: "ROWS_ADDED";
396
+ indices: number[];
300
397
  count: number;
301
398
  totalRows: number;
302
399
  }
303
400
  /** Rows removed instruction */
304
401
  interface RowsRemovedInstruction {
305
402
  type: "ROWS_REMOVED";
306
- count: number;
403
+ indices: number[];
307
404
  totalRows: number;
308
405
  }
309
406
  /** Rows updated instruction */
310
407
  interface RowsUpdatedInstruction {
311
408
  type: "ROWS_UPDATED";
312
- count: number;
409
+ indices: number[];
313
410
  }
314
411
  /** Transaction processed instruction */
315
412
  interface TransactionProcessedInstruction {
@@ -322,6 +419,7 @@ interface TransactionProcessedInstruction {
322
419
  type GridInstruction = /** Slot lifecycle */
323
420
  CreateSlotInstruction | DestroySlotInstruction | AssignSlotInstruction | MoveSlotInstruction
324
421
  /** Selection */ | SetActiveCellInstruction | SetSelectionRangeInstruction | UpdateVisibleRangeInstruction
422
+ /** Highlighting */ | SetHoverPositionInstruction
325
423
  /** Editing */ | StartEditInstruction | StopEditInstruction | CommitEditInstruction
326
424
  /** Layout */ | SetContentSizeInstruction | UpdateHeaderInstruction
327
425
  /** Filter popup */ | OpenFilterPopupInstruction | CloseFilterPopupInstruction
@@ -330,8 +428,6 @@ CreateSlotInstruction | DestroySlotInstruction | AssignSlotInstruction | MoveSlo
330
428
  /** Transactions */ | RowsAddedInstruction | RowsRemovedInstruction | RowsUpdatedInstruction | TransactionProcessedInstruction;
331
429
  /** Instruction listener: Single instruction Listener that receives a single instruction, used by frameworks to update their state */
332
430
  type InstructionListener = (instruction: GridInstruction) => void;
333
- /** Batch instruction listener: Batch instruction Listener that receives an array of instructions, used by frameworks to update their state */
334
- type BatchInstructionListener = (instructions: GridInstruction[]) => void;
335
431
  //#endregion
336
432
  //#region ../core/src/types/renderers.d.ts
337
433
  /** Cell renderer params */
@@ -405,6 +501,8 @@ interface GridCoreOptions<TData = Row> {
405
501
  transactionDebounceMs?: number;
406
502
  /** Function to extract unique ID from row. Required for mutations. */
407
503
  getRowId?: (row: TData) => RowId;
504
+ /** Row/column/cell highlighting configuration */
505
+ highlighting?: HighlightingOptions<TData>;
408
506
  }
409
507
  //#endregion
410
508
  //#region ../core/src/types/input.d.ts
@@ -485,10 +583,16 @@ interface InputHandlerDeps {
485
583
  getHeaderHeight: () => number;
486
584
  /** Get row height */
487
585
  getRowHeight: () => number;
488
- /** Get column positions array */
586
+ /** Get column positions array (indexed by visible column) */
489
587
  getColumnPositions: () => number[];
490
- /** Get column count */
588
+ /** Get visible column count */
491
589
  getColumnCount: () => number;
590
+ /**
591
+ * Convert visible column index to original column index.
592
+ * Used when columns can be hidden. Returns the original index for selection tracking.
593
+ * If not provided, visible index is used directly (no hidden columns).
594
+ */
595
+ getOriginalColumnIndex?: (visibleIndex: number) => number;
492
596
  }
493
597
  /** Current drag state for UI rendering */
494
598
  interface DragState {
@@ -505,6 +609,12 @@ interface DragState {
505
609
  } | null;
506
610
  }
507
611
  //#endregion
612
+ //#region ../core/src/utils/event-emitter.d.ts
613
+ /**
614
+ * Batch instruction listener for efficient state updates
615
+ */
616
+ type BatchInstructionListener = (instructions: GridInstruction[]) => void;
617
+ //#endregion
508
618
  //#region ../core/src/selection.d.ts
509
619
  type Direction = "up" | "down" | "left" | "right";
510
620
  interface SelectionManagerOptions {
@@ -520,10 +630,10 @@ interface SelectionManagerOptions {
520
630
  declare class SelectionManager {
521
631
  private state;
522
632
  private options;
523
- private listeners;
524
- constructor(options: SelectionManagerOptions);
525
- onInstruction(listener: InstructionListener): () => void;
633
+ private emitter;
634
+ onInstruction: (listener: InstructionListener) => () => void;
526
635
  private emit;
636
+ constructor(options: SelectionManagerOptions);
527
637
  getState(): SelectionState;
528
638
  getActiveCell(): CellPosition | null;
529
639
  getSelectionRange(): CellRange | null;
@@ -588,10 +698,10 @@ interface FillManagerOptions {
588
698
  declare class FillManager {
589
699
  private state;
590
700
  private options;
591
- private listeners;
592
- constructor(options: FillManagerOptions);
593
- onInstruction(listener: InstructionListener): () => void;
701
+ private emitter;
702
+ onInstruction: (listener: InstructionListener) => () => void;
594
703
  private emit;
704
+ constructor(options: FillManagerOptions);
595
705
  getState(): FillHandleState | null;
596
706
  isActive(): boolean;
597
707
  /**
@@ -648,6 +758,14 @@ declare class InputHandler<TData extends Row = Row> {
648
758
  * Handle cell double click event (start editing)
649
759
  */
650
760
  handleCellDoubleClick(rowIndex: number, colIndex: number): void;
761
+ /**
762
+ * Handle cell mouse enter event (for hover highlighting)
763
+ */
764
+ handleCellMouseEnter(rowIndex: number, colIndex: number): void;
765
+ /**
766
+ * Handle cell mouse leave event (for hover highlighting)
767
+ */
768
+ handleCellMouseLeave(): void;
651
769
  /**
652
770
  * Handle fill handle mouse down event
653
771
  */
@@ -683,16 +801,222 @@ declare class InputHandler<TData extends Row = Row> {
683
801
  row: number;
684
802
  col: number;
685
803
  } | null, filterPopupOpen: boolean): KeyboardResult;
686
- /**
687
- * Find column index at a given X coordinate
688
- */
689
- private findColumnAtX;
690
804
  /**
691
805
  * Calculate auto-scroll deltas based on mouse position
692
806
  */
693
807
  private calculateAutoScroll;
694
808
  }
695
809
  //#endregion
810
+ //#region ../core/src/highlight-manager.d.ts
811
+ interface HighlightManagerOptions {
812
+ getActiveCell: () => CellPosition | null;
813
+ getSelectionRange: () => CellRange | null;
814
+ getColumn: (colIndex: number) => ColumnDefinition | undefined;
815
+ }
816
+ /**
817
+ * Manages row/column/cell highlighting state and class computation.
818
+ * Emits SET_HOVER_POSITION instructions when hover position changes.
819
+ */
820
+ declare class HighlightManager<TData = Record<string, unknown>> {
821
+ private options;
822
+ private highlightingOptions;
823
+ private hoverPosition;
824
+ private emitter;
825
+ onInstruction: (listener: InstructionListener) => () => void;
826
+ private emit;
827
+ private rowClassCache;
828
+ private columnClassCache;
829
+ private cellClassCache;
830
+ constructor(options: HighlightManagerOptions, highlightingOptions?: HighlightingOptions<TData>);
831
+ /**
832
+ * Check if highlighting is enabled (any callback defined).
833
+ * Hover tracking is automatically enabled when highlighting is enabled.
834
+ */
835
+ isEnabled(): boolean;
836
+ /**
837
+ * Set the current hover position. Clears caches and emits instruction.
838
+ * Hover tracking is automatically enabled when any highlighting callback is defined.
839
+ */
840
+ setHoverPosition(position: CellPosition | null): void;
841
+ /**
842
+ * Get the current hover position
843
+ */
844
+ getHoverPosition(): CellPosition | null;
845
+ /**
846
+ * Called when selection changes. Clears all caches.
847
+ */
848
+ onSelectionChange(): void;
849
+ /**
850
+ * Build context for row highlighting callback.
851
+ * Returns context with `rowIndex` set, `colIndex` is null.
852
+ * `isHovered` is true when the mouse is on any cell in this row.
853
+ */
854
+ buildRowContext(rowIndex: number, rowData?: TData): HighlightContext<TData>;
855
+ /**
856
+ * Build context for column highlighting callback.
857
+ * Returns context with `colIndex` set, `rowIndex` is null.
858
+ * `isHovered` is true when the mouse is on any cell in this column.
859
+ */
860
+ buildColumnContext(colIndex: number, column: ColumnDefinition): HighlightContext<TData>;
861
+ /**
862
+ * Build context for cell highlighting callback.
863
+ * Returns context with both `rowIndex` and `colIndex` set.
864
+ * `isHovered` is true only when the mouse is on this exact cell.
865
+ */
866
+ buildCellContext(rowIndex: number, colIndex: number, column: ColumnDefinition, rowData?: TData): HighlightContext<TData>;
867
+ /**
868
+ * Compute row classes using cache and user callback
869
+ */
870
+ computeRowClasses(rowIndex: number, rowData?: TData): string[];
871
+ /**
872
+ * Compute column classes using cache and user callback (or per-column override)
873
+ */
874
+ computeColumnClasses(colIndex: number, column: ColumnDefinition): string[];
875
+ /**
876
+ * Compute cell classes using cache and user callback (or per-column override)
877
+ */
878
+ computeCellClasses(rowIndex: number, colIndex: number, column: ColumnDefinition, rowData?: TData): string[];
879
+ /**
880
+ * Compute combined cell classes (column + cell classes flattened)
881
+ */
882
+ computeCombinedCellClasses(rowIndex: number, colIndex: number, column: ColumnDefinition, rowData?: TData): string[];
883
+ /**
884
+ * Clear all caches
885
+ */
886
+ clearAllCaches(): void;
887
+ /**
888
+ * Destroy the manager and release resources
889
+ */
890
+ destroy(): void;
891
+ }
892
+ //#endregion
893
+ //#region ../core/src/sort-filter-manager.d.ts
894
+ interface SortFilterManagerOptions<TData> {
895
+ /** Get all columns */
896
+ getColumns: () => ColumnDefinition[];
897
+ /** Check if sorting is enabled globally */
898
+ isSortingEnabled: () => boolean;
899
+ /** Get cached rows for distinct value computation */
900
+ getCachedRows: () => Map<number, TData>;
901
+ /** Called when sort/filter changes to trigger data refresh */
902
+ onSortFilterChange: () => Promise<void>;
903
+ /** Called after data refresh to update UI */
904
+ onDataRefreshed: () => void;
905
+ }
906
+ /**
907
+ * Manages sorting and filtering state and operations.
908
+ */
909
+ declare class SortFilterManager<TData = Record<string, unknown>> {
910
+ private options;
911
+ private emitter;
912
+ private sortModel;
913
+ private filterModel;
914
+ private openFilterColIndex;
915
+ onInstruction: (listener: InstructionListener) => () => void;
916
+ private emit;
917
+ constructor(options: SortFilterManagerOptions<TData>);
918
+ setSort(colId: string, direction: SortDirection | null, addToExisting?: boolean): Promise<void>;
919
+ getSortModel(): SortModel[];
920
+ setFilter(colId: string, filter: ColumnFilterModel | string | null): Promise<void>;
921
+ getFilterModel(): FilterModel;
922
+ /**
923
+ * Check if a column has an active filter
924
+ */
925
+ hasActiveFilter(colId: string): boolean;
926
+ /**
927
+ * Check if a column is sortable
928
+ */
929
+ isColumnSortable(colIndex: number): boolean;
930
+ /**
931
+ * Check if a column is filterable
932
+ */
933
+ isColumnFilterable(colIndex: number): boolean;
934
+ /**
935
+ * Get distinct values for a column (for filter dropdowns)
936
+ * For array-type columns (like tags), each unique array combination is returned.
937
+ * Arrays are sorted internally for consistent comparison.
938
+ * Limited to maxValues to avoid performance issues with large datasets.
939
+ */
940
+ getDistinctValuesForColumn(colId: string, maxValues?: number): CellValue[];
941
+ /**
942
+ * Open filter popup for a column (toggles if already open for same column)
943
+ */
944
+ openFilterPopup(colIndex: number, anchorRect: {
945
+ top: number;
946
+ left: number;
947
+ width: number;
948
+ height: number;
949
+ }): void;
950
+ /**
951
+ * Close filter popup
952
+ */
953
+ closeFilterPopup(): void;
954
+ /**
955
+ * Get sort info map for header rendering
956
+ */
957
+ getSortInfoMap(): Map<string, {
958
+ direction: SortDirection;
959
+ index: number;
960
+ }>;
961
+ destroy(): void;
962
+ }
963
+ //#endregion
964
+ //#region ../core/src/row-mutation-manager.d.ts
965
+ interface RowMutationManagerOptions<TData> {
966
+ /** Get the cached rows map */
967
+ getCachedRows: () => Map<number, TData>;
968
+ /** Set the cached rows map (for bulk operations) */
969
+ setCachedRows: (rows: Map<number, TData>) => void;
970
+ /** Get total row count */
971
+ getTotalRows: () => number;
972
+ /** Set total row count */
973
+ setTotalRows: (count: number) => void;
974
+ /** Update a single slot after row change */
975
+ updateSlot: (rowIndex: number) => void;
976
+ /** Refresh all slots after bulk changes */
977
+ refreshAllSlots: () => void;
978
+ /** Emit content size change */
979
+ emitContentSize: () => void;
980
+ /** Clear selection if it references invalid rows */
981
+ clearSelectionIfInvalid: (maxValidRow: number) => void;
982
+ }
983
+ /**
984
+ * Manages row CRUD operations and cache management.
985
+ */
986
+ declare class RowMutationManager<TData extends Row = Row> {
987
+ private options;
988
+ private emitter;
989
+ onInstruction: (listener: InstructionListener) => () => void;
990
+ private emit;
991
+ constructor(options: RowMutationManagerOptions<TData>);
992
+ /**
993
+ * Get a row by index.
994
+ */
995
+ getRow(index: number): TData | undefined;
996
+ /**
997
+ * Add rows to the grid at the specified index.
998
+ * If no index is provided, rows are added at the end.
999
+ */
1000
+ addRows(rows: TData[], index?: number): void;
1001
+ /**
1002
+ * Update existing rows with partial data.
1003
+ */
1004
+ updateRows(updates: Array<{
1005
+ index: number;
1006
+ data: Partial<TData>;
1007
+ }>): void;
1008
+ /**
1009
+ * Delete rows at the specified indices.
1010
+ */
1011
+ deleteRows(indices: number[]): void;
1012
+ /**
1013
+ * Set a complete row at the specified index.
1014
+ * Use this for complete row replacement. For partial updates, use updateRows.
1015
+ */
1016
+ setRow(index: number, data: TData): void;
1017
+ destroy(): void;
1018
+ }
1019
+ //#endregion
696
1020
  //#region ../core/src/grid-core.d.ts
697
1021
  declare class GridCore<TData extends Row = Row> {
698
1022
  private columns;
@@ -709,29 +1033,25 @@ declare class GridCore<TData extends Row = Row> {
709
1033
  private totalRows;
710
1034
  private currentPageIndex;
711
1035
  private pageSize;
712
- private sortModel;
713
- private filterModel;
714
- private openFilterColIndex;
715
1036
  readonly selection: SelectionManager;
716
1037
  readonly fill: FillManager;
717
1038
  readonly input: InputHandler<TData>;
1039
+ readonly highlight: HighlightManager<TData> | null;
1040
+ readonly sortFilter: SortFilterManager<TData>;
1041
+ readonly rowMutation: RowMutationManager<TData>;
718
1042
  private readonly slotPool;
719
1043
  private readonly editManager;
720
1044
  private columnPositions;
721
- private listeners;
722
- private batchListeners;
1045
+ private emitter;
1046
+ onInstruction: (listener: InstructionListener) => () => void;
1047
+ onBatchInstruction: (listener: BatchInstructionListener) => () => void;
1048
+ private emit;
1049
+ private emitBatch;
723
1050
  private naturalContentHeight;
724
1051
  private virtualContentHeight;
725
1052
  private scrollRatio;
1053
+ private isDestroyed;
726
1054
  constructor(options: GridCoreOptions<TData>);
727
- onInstruction(listener: InstructionListener): () => void;
728
- /**
729
- * Subscribe to batched instructions for efficient React state updates.
730
- * Batch listeners receive arrays of instructions instead of individual ones.
731
- */
732
- onBatchInstruction(listener: BatchInstructionListener): () => void;
733
- private emit;
734
- private emitBatch;
735
1055
  /**
736
1056
  * Initialize the grid and load initial data.
737
1057
  */
@@ -745,37 +1065,16 @@ declare class GridCore<TData extends Row = Row> {
745
1065
  private fetchAllData;
746
1066
  setSort(colId: string, direction: SortDirection | null, addToExisting?: boolean): Promise<void>;
747
1067
  setFilter(colId: string, filter: ColumnFilterModel | string | null): Promise<void>;
748
- /**
749
- * Check if a column has an active filter
750
- */
751
1068
  hasActiveFilter(colId: string): boolean;
752
- /**
753
- * Check if a column is sortable
754
- */
755
1069
  isColumnSortable(colIndex: number): boolean;
756
- /**
757
- * Check if a column is filterable
758
- */
759
1070
  isColumnFilterable(colIndex: number): boolean;
760
- /**
761
- * Get distinct values for a column (for filter dropdowns)
762
- * For array-type columns (like tags), each unique array combination is returned.
763
- * Arrays are sorted internally for consistent comparison.
764
- * Limited to MAX_DISTINCT_VALUES to avoid performance issues with large datasets.
765
- */
766
1071
  getDistinctValuesForColumn(colId: string, maxValues?: number): CellValue[];
767
- /**
768
- * Open filter popup for a column (toggles if already open for same column)
769
- */
770
1072
  openFilterPopup(colIndex: number, anchorRect: {
771
1073
  top: number;
772
1074
  left: number;
773
1075
  width: number;
774
1076
  height: number;
775
1077
  }): void;
776
- /**
777
- * Close filter popup
778
- */
779
1078
  closeFilterPopup(): void;
780
1079
  getSortModel(): SortModel[];
781
1080
  getFilterModel(): FilterModel;
@@ -786,8 +1085,6 @@ declare class GridCore<TData extends Row = Row> {
786
1085
  getEditState(): EditState | null;
787
1086
  getCellValue(row: number, col: number): CellValue;
788
1087
  setCellValue(row: number, col: number, value: CellValue): void;
789
- private getFieldValue;
790
- private setFieldValue;
791
1088
  private computeColumnPositions;
792
1089
  private emitContentSize;
793
1090
  private emitHeaders;
@@ -844,6 +1141,31 @@ declare class GridCore<TData extends Row = Row> {
844
1141
  * Useful after in-place data modifications like fill operations.
845
1142
  */
846
1143
  refreshSlotData(): void;
1144
+ /**
1145
+ * Add rows to the grid at the specified index.
1146
+ * If no index is provided, rows are added at the end.
1147
+ */
1148
+ addRows(rows: TData[], index?: number): void;
1149
+ /**
1150
+ * Update existing rows with partial data.
1151
+ */
1152
+ updateRows(updates: Array<{
1153
+ index: number;
1154
+ data: Partial<TData>;
1155
+ }>): void;
1156
+ /**
1157
+ * Delete rows at the specified indices.
1158
+ */
1159
+ deleteRows(indices: number[]): void;
1160
+ /**
1161
+ * Get a row by index.
1162
+ */
1163
+ getRow(index: number): TData | undefined;
1164
+ /**
1165
+ * Set a complete row at the specified index.
1166
+ * Use this for complete row replacement. For partial updates, use updateRows.
1167
+ */
1168
+ setRow(index: number, data: TData): void;
847
1169
  /**
848
1170
  * Update the data source and refresh.
849
1171
  */
@@ -852,6 +1174,22 @@ declare class GridCore<TData extends Row = Row> {
852
1174
  * Update columns and recompute layout.
853
1175
  */
854
1176
  setColumns(columns: ColumnDefinition[]): void;
1177
+ /**
1178
+ * Destroy the grid core and release all references.
1179
+ * Call this before discarding the GridCore to ensure proper cleanup.
1180
+ * This method is idempotent - safe to call multiple times.
1181
+ */
1182
+ destroy(): void;
1183
+ }
1184
+ //#endregion
1185
+ //#region ../core/src/sorting/parallel-sort-manager.d.ts
1186
+ interface ParallelSortOptions {
1187
+ /** Maximum number of workers (default: navigator.hardwareConcurrency || 4) */
1188
+ maxWorkers?: number;
1189
+ /** Threshold for parallel sorting (default: 400000) */
1190
+ parallelThreshold?: number;
1191
+ /** Minimum chunk size (default: 50000) */
1192
+ minChunkSize?: number;
855
1193
  }
856
1194
  //#endregion
857
1195
  //#region ../core/src/data-source/client-data-source.d.ts
@@ -860,6 +1198,8 @@ interface ClientDataSourceOptions<TData> {
860
1198
  getFieldValue?: (row: TData, field: string) => CellValue;
861
1199
  /** Use Web Worker for sorting large datasets (default: true) */
862
1200
  useWorker?: boolean;
1201
+ /** Options for parallel sorting (only used when useWorker is true) */
1202
+ parallelSort?: ParallelSortOptions | false;
863
1203
  }
864
1204
  /**
865
1205
  * Creates a client-side data source that holds all data in memory.
@@ -985,6 +1325,8 @@ interface GridProps<TData extends Row = Row> {
985
1325
  initialHeight?: number;
986
1326
  /** Optional ref to access GridCore API */
987
1327
  gridRef?: React.MutableRefObject<GridRef<TData> | null>;
1328
+ /** Row/column/cell highlighting configuration */
1329
+ highlighting?: HighlightingOptions<TData>;
988
1330
  }
989
1331
  //#endregion
990
1332
  //#region src/Grid.d.ts
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import e,{useCallback as t,useEffect as n,useMemo as r,useReducer as i,useRef as a,useState as o}from"react";import{GridCore as s,GridCore as c,buildCellClasses as l,calculateScaledColumnPositions as u,createClientDataSource as d,createClientDataSource as f,createDataSourceFromArray as p,createDataSourceFromArray as m,createMutableClientDataSource as h,createServerDataSource as g,getTotalWidth as _,injectStyles as v,isCellActive as y,isCellEditing as b,isCellInFillPreview as x,isCellSelected as S}from"gp-grid-core";import{Fragment as C,jsx as w,jsxs as T}from"react/jsx-runtime";const E=[{value:`contains`,label:`Contains`},{value:`notContains`,label:`Does not contain`},{value:`equals`,label:`Equals`},{value:`notEquals`,label:`Does not equal`},{value:`startsWith`,label:`Starts with`},{value:`endsWith`,label:`Ends with`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Is not blank`}];function D({distinctValues:e,currentFilter:n,onApply:i,onClose:a}){let s=t(e=>Array.isArray(e)?e.join(`, `):String(e??``),[]),c=r(()=>{let t=e.filter(e=>e!=null&&e!==``&&!(Array.isArray(e)&&e.length===0)).map(e=>s(e));return Array.from(new Set(t)).sort((e,t)=>{let n=parseFloat(e),r=parseFloat(t);return!isNaN(n)&&!isNaN(r)?n-r:e.localeCompare(t,void 0,{numeric:!0,sensitivity:`base`})})},[e,s]),l=c.length>100,[u,d]=o(r(()=>{if(!n?.conditions[0])return l?`condition`:`values`;let e=n.conditions[0];return e.selectedValues&&e.selectedValues.size>0?`values`:`condition`},[n,l])),f=r(()=>n?.conditions[0]?n.conditions[0].selectedValues??new Set:new Set,[n]),p=r(()=>n?.conditions[0]?n.conditions[0].includeBlank??!0:!0,[n]),[m,h]=o(``),[g,_]=o(f),[v,y]=o(p),[b,x]=o(r(()=>{if(!n?.conditions.length)return[{operator:`contains`,value:``,nextOperator:`and`}];let e=n.conditions[0];if(e.selectedValues&&e.selectedValues.size>0)return[{operator:`contains`,value:``,nextOperator:`and`}];let t=n.combination??`and`;return n.conditions.map(e=>{let n=e;return{operator:n.operator,value:n.value??``,nextOperator:n.nextOperator??t}})},[n])),S=r(()=>{if(!m)return c;let e=m.toLowerCase();return c.filter(t=>t.toLowerCase().includes(e))},[c,m]),D=r(()=>e.some(e=>e==null||e===``),[e]),O=r(()=>S.every(e=>g.has(e))&&(!D||v),[S,g,D,v]),k=t(()=>{_(new Set(S)),D&&y(!0)},[S,D]),A=t(()=>{_(new Set),y(!1)},[]),j=t(e=>{_(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),M=t((e,t)=>{x(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),N=t(()=>{x(e=>[...e,{operator:`contains`,value:``,nextOperator:`and`}])},[]),P=t(e=>{x(t=>t.filter((t,n)=>n!==e))},[]),F=t(()=>{if(u===`values`){if(c.every(e=>g.has(e))&&(!D||v)){i(null);return}i({conditions:[{type:`text`,operator:`equals`,selectedValues:g,includeBlank:v}],combination:`and`})}else{let e=b.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.value.trim()!==``);if(e.length===0){i(null);return}i({conditions:e.map(e=>({type:`text`,operator:e.operator,value:e.value,nextOperator:e.nextOperator})),combination:`and`})}},[u,c,g,v,D,b,i]),I=t(()=>{i(null)},[i]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-text`,children:[!l&&T(`div`,{className:`gp-grid-filter-mode-toggle`,children:[w(`button`,{type:`button`,className:u===`values`?`active`:``,onClick:()=>d(`values`),children:`Values`}),w(`button`,{type:`button`,className:u===`condition`?`active`:``,onClick:()=>d(`condition`),children:`Condition`})]}),l&&u===`condition`&&T(`div`,{className:`gp-grid-filter-info`,children:[`Too many unique values (`,c.length,`). Use conditions to filter.`]}),u===`values`&&T(C,{children:[w(`input`,{className:`gp-grid-filter-search`,type:`text`,placeholder:`Search...`,value:m,onChange:e=>h(e.target.value),autoFocus:!0}),T(`div`,{className:`gp-grid-filter-actions`,children:[w(`button`,{type:`button`,onClick:k,disabled:O,children:`Select All`}),w(`button`,{type:`button`,onClick:A,children:`Deselect All`})]}),T(`div`,{className:`gp-grid-filter-list`,children:[D&&T(`label`,{className:`gp-grid-filter-option`,children:[w(`input`,{type:`checkbox`,checked:v,onChange:()=>y(!v)}),w(`span`,{className:`gp-grid-filter-blank`,children:`(Blanks)`})]}),S.map(e=>T(`label`,{className:`gp-grid-filter-option`,children:[w(`input`,{type:`checkbox`,checked:g.has(e),onChange:()=>j(e)}),w(`span`,{children:e})]},e))]})]}),u===`condition`&&T(C,{children:[b.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:b[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>M(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:b[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>M(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>M(t,{operator:e.target.value}),autoFocus:t===0,children:E.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`text`,value:e.value,onChange:e=>M(t,{value:e.target.value}),placeholder:`Value`,className:`gp-grid-filter-text-input`}),b.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>P(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:N,children:`+ Add condition`})]}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:I,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:F,children:`Apply`})]})]})}const O=[{value:`=`,label:`=`},{value:`!=`,label:`≠`},{value:`>`,label:`>`},{value:`<`,label:`<`},{value:`>=`,label:`≥`},{value:`<=`,label:`≤`},{value:`between`,label:`↔`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Not blank`}];function k({currentFilter:e,onApply:n,onClose:i}){let[a,s]=o(r(()=>{if(!e?.conditions.length)return[{operator:`=`,value:``,valueTo:``,nextOperator:`and`}];let t=e.combination??`and`;return e.conditions.map(e=>{let n=e;return{operator:n.operator,value:n.value==null?``:String(n.value),valueTo:n.valueTo==null?``:String(n.valueTo),nextOperator:n.nextOperator??t}})},[e])),c=t((e,t)=>{s(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),l=t(()=>{s(e=>[...e,{operator:`=`,value:``,valueTo:``,nextOperator:`and`}])},[]),u=t(e=>{s(t=>t.filter((t,n)=>n!==e))},[]),d=t(()=>{let e=a.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.operator===`between`?e.value!==``&&e.valueTo!==``:e.value!==``);if(e.length===0){n(null);return}n({conditions:e.map(e=>({type:`number`,operator:e.operator,value:e.value?parseFloat(e.value):void 0,valueTo:e.valueTo?parseFloat(e.valueTo):void 0,nextOperator:e.nextOperator})),combination:`and`})},[a,n]),f=t(()=>{n(null)},[n]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-number`,children:[a.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>c(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>c(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>c(t,{operator:e.target.value}),children:O.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`number`,value:e.value,onChange:e=>c(t,{value:e.target.value}),placeholder:`Value`}),e.operator===`between`&&T(C,{children:[w(`span`,{className:`gp-grid-filter-to`,children:`to`}),w(`input`,{type:`number`,value:e.valueTo,onChange:e=>c(t,{valueTo:e.target.value}),placeholder:`Value`})]}),a.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>u(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:l,children:`+ Add condition`}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:f,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:d,children:`Apply`})]})]})}const A=[{value:`=`,label:`=`},{value:`!=`,label:`≠`},{value:`>`,label:`>`},{value:`<`,label:`<`},{value:`between`,label:`↔`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Not blank`}];function j(e){if(!e)return``;let t=typeof e==`string`?new Date(e):e;return isNaN(t.getTime())?``:t.toISOString().split(`T`)[0]}function M({currentFilter:e,onApply:n,onClose:i}){let[a,s]=o(r(()=>{if(!e?.conditions.length)return[{operator:`=`,value:``,valueTo:``,nextOperator:`and`}];let t=e.combination??`and`;return e.conditions.map(e=>{let n=e;return{operator:n.operator,value:j(n.value),valueTo:j(n.valueTo),nextOperator:n.nextOperator??t}})},[e])),c=t((e,t)=>{s(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),l=t(()=>{s(e=>[...e,{operator:`=`,value:``,valueTo:``,nextOperator:`and`}])},[]),u=t(e=>{s(t=>t.filter((t,n)=>n!==e))},[]),d=t(()=>{let e=a.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.operator===`between`?e.value!==``&&e.valueTo!==``:e.value!==``);if(e.length===0){n(null);return}n({conditions:e.map(e=>({type:`date`,operator:e.operator,value:e.value||void 0,valueTo:e.valueTo||void 0,nextOperator:e.nextOperator})),combination:`and`})},[a,n]),f=t(()=>{n(null)},[n]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-date`,children:[a.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>c(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>c(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>c(t,{operator:e.target.value}),children:A.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`date`,value:e.value,onChange:e=>c(t,{value:e.target.value})}),e.operator===`between`&&T(C,{children:[w(`span`,{className:`gp-grid-filter-to`,children:`to`}),w(`input`,{type:`date`,value:e.valueTo,onChange:e=>c(t,{valueTo:e.target.value})})]}),a.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>u(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:l,children:`+ Add condition`}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:f,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:d,children:`Apply`})]})]})}function N({column:e,colIndex:r,anchorRect:i,distinctValues:o,currentFilter:s,onApply:c,onClose:l}){let u=a(null);n(()=>{let e=e=>{let t=e.target;t.closest(`.gp-grid-filter-icon`)||u.current&&!u.current.contains(t)&&l()},t=e=>{e.key===`Escape`&&l()};return requestAnimationFrame(()=>{document.addEventListener(`mousedown`,e),document.addEventListener(`keydown`,t)}),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`keydown`,t)}},[l]);let d=t(t=>{c(e.colId??e.field,t),l()},[e,c,l]),f={position:`fixed`,top:i.top+i.height+4,left:i.left,minWidth:Math.max(200,i.width),zIndex:1e4},p=e.cellDataType,m=p===`text`||p===`object`,h=p===`number`,g=p===`date`||p===`dateString`||p===`dateTime`||p===`dateTimeString`;return T(`div`,{ref:u,className:`gp-grid-filter-popup`,style:f,children:[T(`div`,{className:`gp-grid-filter-header`,children:[`Filter: `,e.headerName??e.field]}),m&&w(D,{distinctValues:o,currentFilter:s,onApply:d,onClose:l}),h&&w(k,{currentFilter:s,onApply:d,onClose:l}),g&&w(M,{currentFilter:s,onApply:d,onClose:l}),!m&&!h&&!g&&w(D,{distinctValues:o,currentFilter:s,onApply:d,onClose:l})]})}function P(e){return{slots:new Map,activeCell:null,selectionRange:null,editingCell:null,contentWidth:0,contentHeight:e?.initialHeight??0,viewportWidth:e?.initialWidth??0,headers:new Map,filterPopup:null,isLoading:!1,error:null,totalRows:0,visibleRowRange:null}}function F(e,t,n){switch(e.type){case`CREATE_SLOT`:return t.set(e.slotId,{slotId:e.slotId,rowIndex:-1,rowData:{},translateY:0}),null;case`DESTROY_SLOT`:return t.delete(e.slotId),null;case`ASSIGN_SLOT`:{let n=t.get(e.slotId);return n&&t.set(e.slotId,{...n,rowIndex:e.rowIndex,rowData:e.rowData}),null}case`MOVE_SLOT`:{let n=t.get(e.slotId);return n&&t.set(e.slotId,{...n,translateY:e.translateY}),null}case`SET_ACTIVE_CELL`:return{activeCell:e.position};case`SET_SELECTION_RANGE`:return{selectionRange:e.range};case`UPDATE_VISIBLE_RANGE`:return{visibleRowRange:{start:e.start,end:e.end}};case`START_EDIT`:return{editingCell:{row:e.row,col:e.col,initialValue:e.initialValue}};case`STOP_EDIT`:return{editingCell:null};case`SET_CONTENT_SIZE`:return{contentWidth:e.width,contentHeight:e.height,viewportWidth:e.viewportWidth};case`UPDATE_HEADER`:return n.set(e.colIndex,{column:e.column,sortDirection:e.sortDirection,sortIndex:e.sortIndex,sortable:e.sortable,filterable:e.filterable,hasFilter:e.hasFilter}),null;case`OPEN_FILTER_POPUP`:return{filterPopup:{isOpen:!0,colIndex:e.colIndex,column:e.column,anchorRect:e.anchorRect,distinctValues:e.distinctValues,currentFilter:e.currentFilter}};case`CLOSE_FILTER_POPUP`:return{filterPopup:null};case`DATA_LOADING`:return{isLoading:!0,error:null};case`DATA_LOADED`:return{isLoading:!1,totalRows:e.totalRows};case`DATA_ERROR`:return{isLoading:!1,error:e.error};case`ROWS_ADDED`:case`ROWS_REMOVED`:return{totalRows:e.totalRows};case`ROWS_UPDATED`:case`TRANSACTION_PROCESSED`:return null;default:return null}}function I(e,t){if(t.type===`RESET`)return P();let{instructions:n}=t;if(n.length===0)return e;let r=new Map(e.slots),i=new Map(e.headers),a={};for(let e of n){let t=F(e,r,i);t&&(a={...a,...t})}return{...e,...a,slots:r,headers:i}}function L(e,t){for(let n of e.values())if(n.rowIndex===t)return n;return null}function R(e,t,n,r,i,a){let o=L(a,n),s=(o?o.translateY:i+n*r)-t.scrollTop,c=s+r,l=i,u=t.clientHeight;if(s<l)t.scrollTop=e.getScrollTopForRow(n);else if(c>u){let a=t.clientHeight-i,o=Math.floor(a/r),s=Math.max(0,n-o+1);t.scrollTop=e.getScrollTopForRow(s)}}function z(e,r,i,s){let{activeCell:c,selectionRange:l,editingCell:u,filterPopupOpen:d,rowHeight:f,headerHeight:p,columnPositions:m,slots:h}=s,g=a(null),[_,v]=o({isDragging:!1,dragType:null,fillSourceRange:null,fillTarget:null});n(()=>{let t=e.current;t?.input&&t.input.updateDeps({getHeaderHeight:()=>p,getRowHeight:()=>f,getColumnPositions:()=>m,getColumnCount:()=>i.length})},[e,p,f,m,i.length]);let y=t((e,t)=>{g.current&&clearInterval(g.current),g.current=setInterval(()=>{let n=r.current;n&&(n.scrollTop+=t,n.scrollLeft+=e)},16)},[r]),b=t(()=>{g.current&&=(clearInterval(g.current),null)},[]),x=t(()=>{let e=r.current;if(!e)return null;let t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height,scrollTop:e.scrollTop,scrollLeft:e.scrollLeft}},[r]),S=e=>({clientX:e.clientX,clientY:e.clientY,button:e.button,shiftKey:e.shiftKey,ctrlKey:e.ctrlKey,metaKey:e.metaKey}),C=t(()=>{let t=t=>{let n=e.current,r=x();if(!n?.input||!r)return;let i=n.input.handleDragMove(S(t),r);i&&(i.autoScroll?y(i.autoScroll.dx,i.autoScroll.dy):b(),v(n.input.getDragState()))},n=()=>{let r=e.current;r?.input&&(r.input.handleDragEnd(),v(r.input.getDragState())),b(),document.removeEventListener(`mousemove`,t),document.removeEventListener(`mouseup`,n)};document.addEventListener(`mousemove`,t),document.addEventListener(`mouseup`,n)},[e,x,y,b]),w=t((t,n,i)=>{let a=e.current;if(!a?.input)return;let o=a.input.handleCellMouseDown(t,n,S(i));o.focusContainer&&r.current?.focus(),o.startDrag===`selection`&&(a.input.startSelectionDrag(),v(a.input.getDragState()),C())},[e,r,C]),T=t((t,n)=>{let r=e.current;r?.input&&r.input.handleCellDoubleClick(t,n)},[e]),E=t(t=>{let n=e.current;if(!n?.input)return;let r=n.input.handleFillHandleMouseDown(c,l,S(t));r.preventDefault&&t.preventDefault(),r.stopPropagation&&t.stopPropagation(),r.startDrag===`fill`&&(v(n.input.getDragState()),C())},[e,c,l,C]),D=t((t,n)=>{let r=e.current;if(!r?.input)return;let a=i[t];if(!a)return;let o=a.colId??a.field;r.input.handleHeaderClick(o,n.shiftKey)},[e,i]),O=t(t=>{let n=e.current,i=r.current;if(!n?.input)return;let a=n.input.handleKeyDown({key:t.key,shiftKey:t.shiftKey,ctrlKey:t.ctrlKey,metaKey:t.metaKey},c,u,d);a.preventDefault&&t.preventDefault(),a.scrollToCell&&i&&R(n,i,a.scrollToCell.row,f,p,h)},[e,r,c,u,d,f,p,h]),k=t((t,n)=>{let i=e.current,a=r.current;if(!i?.input||!a)return;let o=i.input.handleWheel(t.deltaY,t.deltaX,n);o&&(t.preventDefault(),a.scrollTop+=o.dy,a.scrollLeft+=o.dx)},[e,r]);return n(()=>()=>{b()},[b]),{handleCellMouseDown:w,handleCellDoubleClick:T,handleFillHandleMouseDown:E,handleHeaderClick:D,handleKeyDown:O,handleWheel:k,dragState:_}}function B(e,t){let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null}function V(e){let{column:t,rowData:n,rowIndex:r,colIndex:i,isActive:a,isSelected:o,isEditing:s,cellRenderers:c,globalCellRenderer:l}=e,u=B(n,t.field),d={value:u,rowData:n,column:t,rowIndex:r,colIndex:i,isActive:a,isSelected:o,isEditing:s};if(t.cellRenderer&&typeof t.cellRenderer==`string`){let e=c[t.cellRenderer];if(e)return e(d)}return l?l(d):u==null?``:String(u)}function H(e){let{column:t,rowData:n,rowIndex:r,colIndex:i,initialValue:a,coreRef:o,editRenderers:s,globalEditRenderer:c}=e,l=o.current;if(!l)return null;let u={value:B(n,t.field),rowData:n,column:t,rowIndex:r,colIndex:i,isActive:!0,isSelected:!0,isEditing:!0,initialValue:a,onValueChange:e=>l.updateEditValue(e),onCommit:()=>l.commitEdit(),onCancel:()=>l.cancelEdit()};if(t.editRenderer&&typeof t.editRenderer==`string`){let e=s[t.editRenderer];if(e)return e(u)}return c?c(u):w(`input`,{className:`gp-grid-edit-input`,type:`text`,defaultValue:a==null?``:String(a),autoFocus:!0,onFocus:e=>e.target.select(),onChange:e=>l.updateEditValue(e.target.value),onKeyDown:e=>{e.stopPropagation(),e.key===`Enter`?l.commitEdit():e.key===`Escape`?l.cancelEdit():e.key===`Tab`&&(e.preventDefault(),l.commitEdit(),l.selection.moveFocus(e.shiftKey?`left`:`right`,!1))},onBlur:()=>l.commitEdit()})}function U(e){let{column:t,colIndex:n,sortDirection:r,sortIndex:i,sortable:a,filterable:o,hasFilter:s,coreRef:c,containerRef:l,headerRenderers:u,globalHeaderRenderer:d}=e,f=c.current,p={column:t,colIndex:n,sortDirection:r,sortIndex:i,sortable:a,filterable:o,hasFilter:s,onSort:(e,n)=>{f&&a&&f.setSort(t.colId??t.field,e,n)},onFilterClick:()=>{if(f&&o){let e=l.current?.querySelector(`[data-col-index="${n}"]`);if(e){let t=e.getBoundingClientRect();f.openFilterPopup(n,{top:t.top,left:t.left,width:t.width,height:t.height})}}}};if(t.headerRenderer&&typeof t.headerRenderer==`string`){let e=u[t.headerRenderer];if(e)return e(p)}return d?d(p):T(C,{children:[w(`span`,{className:`gp-grid-header-text`,children:t.headerName??t.field}),T(`span`,{className:`gp-grid-header-icons`,children:[a&&T(`span`,{className:`gp-grid-sort-arrows`,children:[T(`span`,{className:`gp-grid-sort-arrows-stack`,children:[w(`svg`,{className:`gp-grid-sort-arrow-up${r===`asc`?` active`:``}`,width:`8`,height:`6`,viewBox:`0 0 8 6`,children:w(`path`,{d:`M4 0L8 6H0L4 0Z`,fill:`currentColor`})}),w(`svg`,{className:`gp-grid-sort-arrow-down${r===`desc`?` active`:``}`,width:`8`,height:`6`,viewBox:`0 0 8 6`,children:w(`path`,{d:`M4 6L0 0H8L4 6Z`,fill:`currentColor`})})]}),i!==void 0&&i>0&&w(`span`,{className:`gp-grid-sort-index`,children:i})]}),o&&w(`span`,{className:`gp-grid-filter-icon${s?` active`:``}`,onMouseDown:e=>{e.stopPropagation(),e.preventDefault(),p.onFilterClick()},onClick:e=>{e.stopPropagation()},children:w(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`currentColor`,children:w(`path`,{d:`M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z`})})})]})]})}function W(e){v();let{columns:o,dataSource:s,rowData:d,rowHeight:p,headerHeight:h=p,overscan:g=3,sortingEnabled:C=!0,darkMode:E=!1,wheelDampening:D=.1,cellRenderers:O={},editRenderers:k={},headerRenderers:A={},cellRenderer:j,editRenderer:M,headerRenderer:F,initialWidth:L,initialHeight:R}=e,B=a(null),W=a(null),[G,ee]=i(I,{initialWidth:L,initialHeight:R},P),K=h,q=r(()=>s||(d?m(d):f([])),[s,d]),{positions:J,widths:Y}=r(()=>u(o,G.viewportWidth),[o,G.viewportWidth]),X=_(J),{handleCellMouseDown:te,handleCellDoubleClick:ne,handleFillHandleMouseDown:re,handleHeaderClick:ie,handleKeyDown:ae,handleWheel:oe,dragState:Z}=z(W,B,o,{activeCell:G.activeCell,selectionRange:G.selectionRange,editingCell:G.editingCell,filterPopupOpen:G.filterPopup?.isOpen??!1,rowHeight:p,headerHeight:K,columnPositions:J,slots:G.slots});n(()=>{let e=new c({columns:o,dataSource:q,rowHeight:p,headerHeight:K,overscan:g,sortingEnabled:C});W.current=e;let t=e.onBatchInstruction(e=>{ee({type:`BATCH_INSTRUCTIONS`,instructions:e})});return e.initialize(),()=>{t(),W.current=null}},[o,q,p,K,g,C]),n(()=>{let e=q;if(e.subscribe)return e.subscribe(()=>{W.current?.refresh()})},[q]);let Q=t(()=>{let e=B.current,t=W.current;!e||!t||t.setViewport(e.scrollTop,e.scrollLeft,e.clientWidth,e.clientHeight)},[]);n(()=>{let e=B.current,t=W.current;if(!e||!t)return;if(typeof ResizeObserver>`u`){Q();return}let n=new ResizeObserver(()=>{t.setViewport(e.scrollTop,e.scrollLeft,e.clientWidth,e.clientHeight)});return n.observe(e),Q(),()=>n.disconnect()},[Q]);let se=t((e,t)=>{let n=W.current;n&&n.setFilter(e,t)},[]),ce=t(()=>{let e=W.current;e&&e.closeFilterPopup()},[]),le=r(()=>Array.from(G.slots.values()),[G.slots]),$=r(()=>{let{activeCell:e,selectionRange:t,slots:n}=G;if(!e&&!t)return null;let r,i,a,s;if(t)r=Math.max(t.startRow,t.endRow),i=Math.max(t.startCol,t.endCol),a=Math.min(t.startCol,t.endCol),s=Math.max(t.startCol,t.endCol);else if(e)r=e.row,i=e.col,a=i,s=i;else return null;for(let e=a;e<=s;e++){let t=o[e];if(!t||t.editable!==!0)return null}let c=null;for(let e of n.values())if(e.rowIndex===r){c=e.translateY;break}if(c===null)return null;let l=J[i]??0,u=Y[i]??0;return{top:c+p-5,left:l+u-20}},[G.activeCell,G.selectionRange,G.slots,p,J,Y,o]);return T(`div`,{ref:B,className:`gp-grid-container${E?` gp-grid-container--dark`:``}`,style:{width:`100%`,height:`100%`,overflow:`auto`,position:`relative`},onScroll:Q,onWheel:e=>oe(e,D),onKeyDown:ae,tabIndex:0,children:[T(`div`,{style:{width:Math.max(G.contentWidth,X),height:Math.max(G.contentHeight,K),position:`relative`,minWidth:`100%`},children:[w(`div`,{className:`gp-grid-header`,style:{position:`sticky`,top:0,left:0,height:h,width:Math.max(G.contentWidth,X),minWidth:`100%`},children:o.map((e,t)=>{let n=G.headers.get(t);return w(`div`,{className:`gp-grid-header-cell`,"data-col-index":t,style:{position:`absolute`,left:`${J[t]}px`,top:0,width:`${Y[t]}px`,height:`${h}px`,background:`transparent`},onClick:e=>ie(t,e),children:U({column:e,colIndex:t,sortDirection:n?.sortDirection,sortIndex:n?.sortIndex,sortable:n?.sortable??!0,filterable:n?.filterable??!0,hasFilter:n?.hasFilter??!1,coreRef:W,containerRef:B,headerRenderers:A,globalHeaderRenderer:F})},e.colId??e.field)})}),le.map(e=>e.rowIndex<0?null:w(`div`,{className:`gp-grid-row ${e.rowIndex%2==0?`gp-grid-row--even`:``}`,style:{position:`absolute`,top:0,left:0,transform:`translateY(${e.translateY}px)`,width:`${Math.max(G.contentWidth,X)}px`,height:`${p}px`},children:o.map((t,n)=>{let r=b(e.rowIndex,n,G.editingCell),i=y(e.rowIndex,n,G.activeCell),a=S(e.rowIndex,n,G.selectionRange);return w(`div`,{className:l(i,a,r,x(e.rowIndex,n,Z.dragType===`fill`,Z.fillSourceRange,Z.fillTarget)),style:{position:`absolute`,left:`${J[n]}px`,top:0,width:`${Y[n]}px`,height:`${p}px`},onMouseDown:t=>te(e.rowIndex,n,t),onDoubleClick:()=>ne(e.rowIndex,n),children:r&&G.editingCell?H({column:t,rowData:e.rowData,rowIndex:e.rowIndex,colIndex:n,initialValue:G.editingCell.initialValue,coreRef:W,editRenderers:k,globalEditRenderer:M}):V({column:t,rowData:e.rowData,rowIndex:e.rowIndex,colIndex:n,isActive:i,isSelected:a,isEditing:r,cellRenderers:O,globalCellRenderer:j})},`${e.slotId}-${n}`)})},e.slotId)),$&&!G.editingCell&&w(`div`,{className:`gp-grid-fill-handle`,style:{position:`absolute`,top:$.top,left:$.left,zIndex:200},onMouseDown:re}),G.isLoading&&T(`div`,{className:`gp-grid-loading`,children:[w(`div`,{className:`gp-grid-loading-spinner`}),`Loading...`]}),G.error&&T(`div`,{className:`gp-grid-error`,children:[`Error: `,G.error]}),!G.isLoading&&!G.error&&G.totalRows===0&&w(`div`,{className:`gp-grid-empty`,children:`No data to display`})]}),G.filterPopup?.isOpen&&G.filterPopup.column&&G.filterPopup.anchorRect&&w(N,{column:G.filterPopup.column,colIndex:G.filterPopup.colIndex,anchorRect:G.filterPopup.anchorRect,distinctValues:G.filterPopup.distinctValues,currentFilter:G.filterPopup.currentFilter,onApply:se,onClose:ce})]})}export{W as Grid,s as GridCore,d as createClientDataSource,p as createDataSourceFromArray,h as createMutableClientDataSource,g as createServerDataSource};
1
+ import e,{useCallback as t,useEffect as n,useMemo as r,useReducer as i,useRef as a,useState as o}from"react";import{GridCore as s,GridCore as c,buildCellClasses as l,calculateScaledColumnPositions as u,createClientDataSource as d,createClientDataSource as f,createDataSourceFromArray as p,createDataSourceFromArray as m,createMutableClientDataSource as h,createServerDataSource as g,getTotalWidth as _,injectStyles as v,isCellActive as y,isCellEditing as b,isCellInFillPreview as x,isCellSelected as S}from"gp-grid-core";import{Fragment as C,jsx as w,jsxs as T}from"react/jsx-runtime";const E=[{value:`contains`,label:`Contains`},{value:`notContains`,label:`Does not contain`},{value:`equals`,label:`Equals`},{value:`notEquals`,label:`Does not equal`},{value:`startsWith`,label:`Starts with`},{value:`endsWith`,label:`Ends with`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Is not blank`}];function D({distinctValues:e,currentFilter:n,onApply:i,onClose:a}){let s=t(e=>Array.isArray(e)?e.join(`, `):String(e??``),[]),c=r(()=>{let t=e.filter(e=>e!=null&&e!==``&&!(Array.isArray(e)&&e.length===0)).map(e=>s(e));return Array.from(new Set(t)).sort((e,t)=>{let n=parseFloat(e),r=parseFloat(t);return!isNaN(n)&&!isNaN(r)?n-r:e.localeCompare(t,void 0,{numeric:!0,sensitivity:`base`})})},[e,s]),l=c.length>100,[u,d]=o(r(()=>{if(!n?.conditions[0])return l?`condition`:`values`;let e=n.conditions[0];return e.selectedValues&&e.selectedValues.size>0?`values`:`condition`},[n,l])),f=r(()=>n?.conditions[0]?n.conditions[0].selectedValues??new Set:new Set,[n]),p=r(()=>n?.conditions[0]?n.conditions[0].includeBlank??!0:!0,[n]),[m,h]=o(``),[g,_]=o(f),[v,y]=o(p),[b,x]=o(r(()=>{if(!n?.conditions.length)return[{operator:`contains`,value:``,nextOperator:`and`}];let e=n.conditions[0];if(e.selectedValues&&e.selectedValues.size>0)return[{operator:`contains`,value:``,nextOperator:`and`}];let t=n.combination??`and`;return n.conditions.map(e=>{let n=e;return{operator:n.operator,value:n.value??``,nextOperator:n.nextOperator??t}})},[n])),S=r(()=>{if(!m)return c;let e=m.toLowerCase();return c.filter(t=>t.toLowerCase().includes(e))},[c,m]),D=r(()=>e.some(e=>e==null||e===``),[e]),O=r(()=>S.every(e=>g.has(e))&&(!D||v),[S,g,D,v]),k=t(()=>{_(new Set(S)),D&&y(!0)},[S,D]),A=t(()=>{_(new Set),y(!1)},[]),j=t(e=>{_(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),M=t((e,t)=>{x(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),N=t(()=>{x(e=>[...e,{operator:`contains`,value:``,nextOperator:`and`}])},[]),P=t(e=>{x(t=>t.filter((t,n)=>n!==e))},[]),F=t(()=>{if(u===`values`){if(c.every(e=>g.has(e))&&(!D||v)){i(null);return}i({conditions:[{type:`text`,operator:`equals`,selectedValues:g,includeBlank:v}],combination:`and`})}else{let e=b.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.value.trim()!==``);if(e.length===0){i(null);return}i({conditions:e.map(e=>({type:`text`,operator:e.operator,value:e.value,nextOperator:e.nextOperator})),combination:`and`})}},[u,c,g,v,D,b,i]),I=t(()=>{i(null)},[i]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-text`,children:[!l&&T(`div`,{className:`gp-grid-filter-mode-toggle`,children:[w(`button`,{type:`button`,className:u===`values`?`active`:``,onClick:()=>d(`values`),children:`Values`}),w(`button`,{type:`button`,className:u===`condition`?`active`:``,onClick:()=>d(`condition`),children:`Condition`})]}),l&&u===`condition`&&T(`div`,{className:`gp-grid-filter-info`,children:[`Too many unique values (`,c.length,`). Use conditions to filter.`]}),u===`values`&&T(C,{children:[w(`input`,{className:`gp-grid-filter-search`,type:`text`,placeholder:`Search...`,value:m,onChange:e=>h(e.target.value),autoFocus:!0}),T(`div`,{className:`gp-grid-filter-actions`,children:[w(`button`,{type:`button`,onClick:k,disabled:O,children:`Select All`}),w(`button`,{type:`button`,onClick:A,children:`Deselect All`})]}),T(`div`,{className:`gp-grid-filter-list`,children:[D&&T(`label`,{className:`gp-grid-filter-option`,children:[w(`input`,{type:`checkbox`,checked:v,onChange:()=>y(!v)}),w(`span`,{className:`gp-grid-filter-blank`,children:`(Blanks)`})]}),S.map(e=>T(`label`,{className:`gp-grid-filter-option`,children:[w(`input`,{type:`checkbox`,checked:g.has(e),onChange:()=>j(e)}),w(`span`,{children:e})]},e))]})]}),u===`condition`&&T(C,{children:[b.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:b[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>M(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:b[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>M(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>M(t,{operator:e.target.value}),autoFocus:t===0,children:E.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`text`,value:e.value,onChange:e=>M(t,{value:e.target.value}),placeholder:`Value`,className:`gp-grid-filter-text-input`}),b.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>P(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:N,children:`+ Add condition`})]}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:I,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:F,children:`Apply`})]})]})}const O=[{value:`=`,label:`=`},{value:`!=`,label:`≠`},{value:`>`,label:`>`},{value:`<`,label:`<`},{value:`>=`,label:`≥`},{value:`<=`,label:`≤`},{value:`between`,label:`↔`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Not blank`}];function k({currentFilter:e,onApply:n,onClose:i}){let[a,s]=o(r(()=>{if(!e?.conditions.length)return[{operator:`=`,value:``,valueTo:``,nextOperator:`and`}];let t=e.combination??`and`;return e.conditions.map(e=>{let n=e;return{operator:n.operator,value:n.value==null?``:String(n.value),valueTo:n.valueTo==null?``:String(n.valueTo),nextOperator:n.nextOperator??t}})},[e])),c=t((e,t)=>{s(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),l=t(()=>{s(e=>[...e,{operator:`=`,value:``,valueTo:``,nextOperator:`and`}])},[]),u=t(e=>{s(t=>t.filter((t,n)=>n!==e))},[]),d=t(()=>{let e=a.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.operator===`between`?e.value!==``&&e.valueTo!==``:e.value!==``);if(e.length===0){n(null);return}n({conditions:e.map(e=>({type:`number`,operator:e.operator,value:e.value?parseFloat(e.value):void 0,valueTo:e.valueTo?parseFloat(e.valueTo):void 0,nextOperator:e.nextOperator})),combination:`and`})},[a,n]),f=t(()=>{n(null)},[n]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-number`,children:[a.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>c(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>c(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>c(t,{operator:e.target.value}),children:O.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`number`,value:e.value,onChange:e=>c(t,{value:e.target.value}),placeholder:`Value`}),e.operator===`between`&&T(C,{children:[w(`span`,{className:`gp-grid-filter-to`,children:`to`}),w(`input`,{type:`number`,value:e.valueTo,onChange:e=>c(t,{valueTo:e.target.value}),placeholder:`Value`})]}),a.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>u(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:l,children:`+ Add condition`}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:f,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:d,children:`Apply`})]})]})}const A=[{value:`=`,label:`=`},{value:`!=`,label:`≠`},{value:`>`,label:`>`},{value:`<`,label:`<`},{value:`between`,label:`↔`},{value:`blank`,label:`Is blank`},{value:`notBlank`,label:`Not blank`}];function j(e){if(!e)return``;let t=typeof e==`string`?new Date(e):e;return isNaN(t.getTime())?``:t.toISOString().split(`T`)[0]}function M({currentFilter:e,onApply:n,onClose:i}){let[a,s]=o(r(()=>{if(!e?.conditions.length)return[{operator:`=`,value:``,valueTo:``,nextOperator:`and`}];let t=e.combination??`and`;return e.conditions.map(e=>{let n=e;return{operator:n.operator,value:j(n.value),valueTo:j(n.valueTo),nextOperator:n.nextOperator??t}})},[e])),c=t((e,t)=>{s(n=>{let r=[...n];return r[e]={...r[e],...t},r})},[]),l=t(()=>{s(e=>[...e,{operator:`=`,value:``,valueTo:``,nextOperator:`and`}])},[]),u=t(e=>{s(t=>t.filter((t,n)=>n!==e))},[]),d=t(()=>{let e=a.filter(e=>e.operator===`blank`||e.operator===`notBlank`?!0:e.operator===`between`?e.value!==``&&e.valueTo!==``:e.value!==``);if(e.length===0){n(null);return}n({conditions:e.map(e=>({type:`date`,operator:e.operator,value:e.value||void 0,valueTo:e.valueTo||void 0,nextOperator:e.nextOperator})),combination:`and`})},[a,n]),f=t(()=>{n(null)},[n]);return T(`div`,{className:`gp-grid-filter-content gp-grid-filter-date`,children:[a.map((e,t)=>T(`div`,{className:`gp-grid-filter-condition`,children:[t>0&&T(`div`,{className:`gp-grid-filter-combination`,children:[w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`and`?`active`:``,onClick:()=>c(t-1,{nextOperator:`and`}),children:`AND`}),w(`button`,{type:`button`,className:a[t-1]?.nextOperator===`or`?`active`:``,onClick:()=>c(t-1,{nextOperator:`or`}),children:`OR`})]}),T(`div`,{className:`gp-grid-filter-row`,children:[w(`select`,{value:e.operator,onChange:e=>c(t,{operator:e.target.value}),children:A.map(e=>w(`option`,{value:e.value,children:e.label},e.value))}),e.operator!==`blank`&&e.operator!==`notBlank`&&w(`input`,{type:`date`,value:e.value,onChange:e=>c(t,{value:e.target.value})}),e.operator===`between`&&T(C,{children:[w(`span`,{className:`gp-grid-filter-to`,children:`to`}),w(`input`,{type:`date`,value:e.valueTo,onChange:e=>c(t,{valueTo:e.target.value})})]}),a.length>1&&w(`button`,{type:`button`,className:`gp-grid-filter-remove`,onClick:()=>u(t),children:`×`})]})]},t)),w(`button`,{type:`button`,className:`gp-grid-filter-add`,onClick:l,children:`+ Add condition`}),T(`div`,{className:`gp-grid-filter-buttons`,children:[w(`button`,{type:`button`,className:`gp-grid-filter-btn-clear`,onClick:f,children:`Clear`}),w(`button`,{type:`button`,className:`gp-grid-filter-btn-apply`,onClick:d,children:`Apply`})]})]})}function N({column:e,colIndex:r,anchorRect:i,distinctValues:o,currentFilter:s,onApply:c,onClose:l}){let u=a(null);n(()=>{let e=e=>{let t=e.target;t.closest(`.gp-grid-filter-icon`)||u.current&&!u.current.contains(t)&&l()},t=e=>{e.key===`Escape`&&l()};return requestAnimationFrame(()=>{document.addEventListener(`mousedown`,e),document.addEventListener(`keydown`,t)}),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`keydown`,t)}},[l]);let d=t(t=>{c(e.colId??e.field,t),l()},[e,c,l]),f={position:`fixed`,top:i.top+i.height+4,left:i.left,minWidth:Math.max(200,i.width),zIndex:1e4},p=e.cellDataType,m=p===`text`||p===`object`,h=p===`number`,g=p===`date`||p===`dateString`||p===`dateTime`||p===`dateTimeString`;return T(`div`,{ref:u,className:`gp-grid-filter-popup`,style:f,children:[T(`div`,{className:`gp-grid-filter-header`,children:[`Filter: `,e.headerName??e.field]}),m&&w(D,{distinctValues:o,currentFilter:s,onApply:d,onClose:l}),h&&w(k,{currentFilter:s,onApply:d,onClose:l}),g&&w(M,{currentFilter:s,onApply:d,onClose:l}),!m&&!h&&!g&&w(D,{distinctValues:o,currentFilter:s,onApply:d,onClose:l})]})}function P(e){return{slots:new Map,activeCell:null,selectionRange:null,editingCell:null,contentWidth:0,contentHeight:e?.initialHeight??0,viewportWidth:e?.initialWidth??0,headers:new Map,filterPopup:null,isLoading:!1,error:null,totalRows:0,visibleRowRange:null,hoverPosition:null}}function F(e,t,n){switch(e.type){case`CREATE_SLOT`:return t.set(e.slotId,{slotId:e.slotId,rowIndex:-1,rowData:{},translateY:0}),null;case`DESTROY_SLOT`:return t.delete(e.slotId),null;case`ASSIGN_SLOT`:{let n=t.get(e.slotId);return n&&t.set(e.slotId,{...n,rowIndex:e.rowIndex,rowData:e.rowData}),null}case`MOVE_SLOT`:{let n=t.get(e.slotId);return n&&t.set(e.slotId,{...n,translateY:e.translateY}),null}case`SET_ACTIVE_CELL`:return{activeCell:e.position};case`SET_SELECTION_RANGE`:return{selectionRange:e.range};case`UPDATE_VISIBLE_RANGE`:return{visibleRowRange:{start:e.start,end:e.end}};case`SET_HOVER_POSITION`:return{hoverPosition:e.position};case`START_EDIT`:return{editingCell:{row:e.row,col:e.col,initialValue:e.initialValue}};case`STOP_EDIT`:return{editingCell:null};case`SET_CONTENT_SIZE`:return{contentWidth:e.width,contentHeight:e.height,viewportWidth:e.viewportWidth};case`UPDATE_HEADER`:return n.set(e.colIndex,{column:e.column,sortDirection:e.sortDirection,sortIndex:e.sortIndex,sortable:e.sortable,filterable:e.filterable,hasFilter:e.hasFilter}),null;case`OPEN_FILTER_POPUP`:return{filterPopup:{isOpen:!0,colIndex:e.colIndex,column:e.column,anchorRect:e.anchorRect,distinctValues:e.distinctValues,currentFilter:e.currentFilter}};case`CLOSE_FILTER_POPUP`:return{filterPopup:null};case`DATA_LOADING`:return{isLoading:!0,error:null};case`DATA_LOADED`:return{isLoading:!1,totalRows:e.totalRows};case`DATA_ERROR`:return{isLoading:!1,error:e.error};case`ROWS_ADDED`:case`ROWS_REMOVED`:return{totalRows:e.totalRows};case`ROWS_UPDATED`:case`TRANSACTION_PROCESSED`:return null;default:return null}}function I(e,t){if(t.type===`RESET`)return P();let{instructions:n}=t;if(n.length===0)return e;let r=new Map(e.slots),i=new Map(e.headers),a={};for(let e of n){let t=F(e,r,i);t&&(a={...a,...t})}return{...e,...a,slots:r,headers:i}}function L(e,t){for(let n of e.values())if(n.rowIndex===t)return n;return null}function ee(e,t,n,r,i,a){let o=L(a,n),s=(o?o.translateY:i+n*r)-t.scrollTop,c=s+r,l=i,u=t.clientHeight;if(s<l)t.scrollTop=e.getScrollTopForRow(n);else if(c>u){let a=t.clientHeight-i,o=Math.floor(a/r),s=Math.max(0,n-o+1);t.scrollTop=e.getScrollTopForRow(s)}}function te(e,r,i,s){let{activeCell:c,selectionRange:l,editingCell:u,filterPopupOpen:d,rowHeight:f,headerHeight:p,columnPositions:m,visibleColumnsWithIndices:h,slots:g}=s,_=a(null),[v,y]=o({isDragging:!1,dragType:null,fillSourceRange:null,fillTarget:null});n(()=>{let t=e.current;t?.input&&t.input.updateDeps({getHeaderHeight:()=>p,getRowHeight:()=>f,getColumnPositions:()=>m,getColumnCount:()=>h.length,getOriginalColumnIndex:e=>{let t=h[e];return t?t.originalIndex:e}})},[e,p,f,m,h]);let b=t((e,t)=>{_.current&&clearInterval(_.current),_.current=setInterval(()=>{let n=r.current;n&&(n.scrollTop+=t,n.scrollLeft+=e)},16)},[r]),x=t(()=>{_.current&&=(clearInterval(_.current),null)},[]),S=t(()=>{let e=r.current;if(!e)return null;let t=e.getBoundingClientRect();return{top:t.top,left:t.left,width:t.width,height:t.height,scrollTop:e.scrollTop,scrollLeft:e.scrollLeft}},[r]),C=e=>({clientX:e.clientX,clientY:e.clientY,button:e.button,shiftKey:e.shiftKey,ctrlKey:e.ctrlKey,metaKey:e.metaKey}),w=t(()=>{let t=t=>{let n=e.current,r=S();if(!n?.input||!r)return;let i=n.input.handleDragMove(C(t),r);i&&(i.autoScroll?b(i.autoScroll.dx,i.autoScroll.dy):x(),y(n.input.getDragState()))},n=()=>{let r=e.current;r?.input&&(r.input.handleDragEnd(),y(r.input.getDragState())),x(),document.removeEventListener(`mousemove`,t),document.removeEventListener(`mouseup`,n)};document.addEventListener(`mousemove`,t),document.addEventListener(`mouseup`,n)},[e,S,b,x]),T=t((t,n,i)=>{let a=e.current;if(!a?.input)return;let o=a.input.handleCellMouseDown(t,n,C(i));o.focusContainer&&r.current?.focus(),o.startDrag===`selection`&&(a.input.startSelectionDrag(),y(a.input.getDragState()),w())},[e,r,w]),E=t((t,n)=>{let r=e.current;r?.input&&r.input.handleCellDoubleClick(t,n)},[e]),D=t(t=>{let n=e.current;if(!n?.input)return;let r=n.input.handleFillHandleMouseDown(c,l,C(t));r.preventDefault&&t.preventDefault(),r.stopPropagation&&t.stopPropagation(),r.startDrag===`fill`&&(y(n.input.getDragState()),w())},[e,c,l,w]),O=t((t,n)=>{let r=e.current;if(!r?.input)return;let a=i[t];if(!a)return;let o=a.colId??a.field;r.input.handleHeaderClick(o,n.shiftKey)},[e,i]),k=t(t=>{let n=e.current,i=r.current;if(!n?.input)return;let a=n.input.handleKeyDown({key:t.key,shiftKey:t.shiftKey,ctrlKey:t.ctrlKey,metaKey:t.metaKey},c,u,d);a.preventDefault&&t.preventDefault(),a.scrollToCell&&i&&ee(n,i,a.scrollToCell.row,f,p,g)},[e,r,c,u,d,f,p,g]),A=t((t,n)=>{let i=e.current,a=r.current;if(!i?.input||!a)return;let o=i.input.handleWheel(t.deltaY,t.deltaX,n);o&&(t.preventDefault(),a.scrollTop+=o.dy,a.scrollLeft+=o.dx)},[e,r]);return n(()=>()=>{x()},[x]),{handleCellMouseDown:T,handleCellDoubleClick:E,handleFillHandleMouseDown:D,handleHeaderClick:O,handleKeyDown:k,handleWheel:A,dragState:v}}function R(e,t){let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null}function ne(e){let{column:t,rowData:n,rowIndex:r,colIndex:i,isActive:a,isSelected:o,isEditing:s,cellRenderers:c,globalCellRenderer:l}=e,u=R(n,t.field),d={value:u,rowData:n,column:t,rowIndex:r,colIndex:i,isActive:a,isSelected:o,isEditing:s};if(t.cellRenderer&&typeof t.cellRenderer==`string`){let e=c[t.cellRenderer];if(e)return e(d)}return l?l(d):u==null?``:String(u)}function re(e){let{column:t,rowData:n,rowIndex:r,colIndex:i,initialValue:a,coreRef:o,editRenderers:s,globalEditRenderer:c}=e,l=o.current;if(!l)return null;let u={value:R(n,t.field),rowData:n,column:t,rowIndex:r,colIndex:i,isActive:!0,isSelected:!0,isEditing:!0,initialValue:a,onValueChange:e=>l.updateEditValue(e),onCommit:()=>l.commitEdit(),onCancel:()=>l.cancelEdit()};if(t.editRenderer&&typeof t.editRenderer==`string`){let e=s[t.editRenderer];if(e)return e(u)}return c?c(u):w(`input`,{className:`gp-grid-edit-input`,type:`text`,defaultValue:a==null?``:String(a),autoFocus:!0,onFocus:e=>e.target.select(),onChange:e=>l.updateEditValue(e.target.value),onKeyDown:e=>{e.stopPropagation(),e.key===`Enter`?l.commitEdit():e.key===`Escape`?l.cancelEdit():e.key===`Tab`&&(e.preventDefault(),l.commitEdit(),l.selection.moveFocus(e.shiftKey?`left`:`right`,!1))},onBlur:()=>l.commitEdit()})}function ie(e){let{column:t,colIndex:n,sortDirection:r,sortIndex:i,sortable:a,filterable:o,hasFilter:s,coreRef:c,containerRef:l,headerRenderers:u,globalHeaderRenderer:d}=e,f=c.current,p={column:t,colIndex:n,sortDirection:r,sortIndex:i,sortable:a,filterable:o,hasFilter:s,onSort:(e,n)=>{f&&a&&f.setSort(t.colId??t.field,e,n)},onFilterClick:()=>{if(f&&o){let e=l.current?.querySelector(`[data-col-index="${n}"]`);if(e){let t=e.getBoundingClientRect();f.openFilterPopup(n,{top:t.top,left:t.left,width:t.width,height:t.height})}}}};if(t.headerRenderer&&typeof t.headerRenderer==`string`){let e=u[t.headerRenderer];if(e)return e(p)}return d?d(p):T(C,{children:[w(`span`,{className:`gp-grid-header-text`,children:t.headerName??t.field}),T(`span`,{className:`gp-grid-header-icons`,children:[a&&T(`span`,{className:`gp-grid-sort-arrows`,children:[T(`span`,{className:`gp-grid-sort-arrows-stack`,children:[w(`svg`,{className:`gp-grid-sort-arrow-up${r===`asc`?` active`:``}`,width:`8`,height:`6`,viewBox:`0 0 8 6`,children:w(`path`,{d:`M4 0L8 6H0L4 0Z`,fill:`currentColor`})}),w(`svg`,{className:`gp-grid-sort-arrow-down${r===`desc`?` active`:``}`,width:`8`,height:`6`,viewBox:`0 0 8 6`,children:w(`path`,{d:`M4 6L0 0H8L4 6Z`,fill:`currentColor`})})]}),i!==void 0&&i>0&&w(`span`,{className:`gp-grid-sort-index`,children:i})]}),o&&w(`span`,{className:`gp-grid-filter-icon${s?` active`:``}`,onMouseDown:e=>{e.stopPropagation(),e.preventDefault(),p.onFilterClick()},onClick:e=>{e.stopPropagation()},children:w(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`currentColor`,children:w(`path`,{d:`M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z`})})})]})]})}function z(e){v();let{columns:o,dataSource:s,rowData:d,rowHeight:p,headerHeight:h=p,overscan:g=3,sortingEnabled:C=!0,darkMode:E=!1,wheelDampening:D=.1,cellRenderers:O={},editRenderers:k={},headerRenderers:A={},cellRenderer:j,editRenderer:M,headerRenderer:F,initialWidth:L,initialHeight:ee,gridRef:R,highlighting:z}=e,B=a(null),V=a(null),H=a(null),[U,W]=i(I,{initialWidth:L,initialHeight:ee},P),G=h,{dataSource:K,ownsDataSource:ae}=r(()=>s?{dataSource:s,ownsDataSource:!1}:d?{dataSource:m(d),ownsDataSource:!0}:{dataSource:f([]),ownsDataSource:!0},[s,d]);n(()=>{let e=H.current;e&&e!==K&&W({type:`RESET`}),H.current=K},[K]);let q=r(()=>o.map((e,t)=>({column:e,originalIndex:t})).filter(({column:e})=>!e.hidden),[o]),{positions:J,widths:Y}=r(()=>u(q.map(e=>e.column),U.viewportWidth),[q,U.viewportWidth]),X=_(J),{handleCellMouseDown:oe,handleCellDoubleClick:se,handleFillHandleMouseDown:ce,handleHeaderClick:le,handleKeyDown:ue,handleWheel:de,dragState:Z}=te(V,B,o,{activeCell:U.activeCell,selectionRange:U.selectionRange,editingCell:U.editingCell,filterPopupOpen:U.filterPopup?.isOpen??!1,rowHeight:p,headerHeight:G,columnPositions:J,visibleColumnsWithIndices:q,slots:U.slots});n(()=>{let e=new c({columns:o,dataSource:K,rowHeight:p,headerHeight:G,overscan:g,sortingEnabled:C,highlighting:z});V.current=e,R&&(R.current={core:e});let t=e.onBatchInstruction(e=>{W({type:`BATCH_INSTRUCTIONS`,instructions:e})});return e.initialize(),()=>{t(),e.destroy(),ae&&K.destroy?.(),V.current=null,R&&(R.current=null)}},[o,K,ae,p,G,g,C,R,z]),n(()=>{let e=K;if(e.subscribe)return e.subscribe(()=>{V.current?.refresh()})},[K]);let Q=t(()=>{let e=B.current,t=V.current;!e||!t||t.setViewport(e.scrollTop,e.scrollLeft,e.clientWidth,e.clientHeight)},[]);n(()=>{let e=B.current,t=V.current;if(!e||!t)return;if(typeof ResizeObserver>`u`){Q();return}let n=new ResizeObserver(()=>{t.setViewport(e.scrollTop,e.scrollLeft,e.clientWidth,e.clientHeight)});return n.observe(e),Q(),()=>n.disconnect()},[Q]);let fe=t((e,t)=>{let n=V.current;n&&n.setFilter(e,t)},[]),pe=t(()=>{let e=V.current;e&&e.closeFilterPopup()},[]),me=t((e,t)=>{V.current?.input.handleCellMouseEnter(e,t)},[]),he=t(()=>{V.current?.input.handleCellMouseLeave()},[]),ge=r(()=>Array.from(U.slots.values()),[U.slots]),$=r(()=>{let{activeCell:e,selectionRange:t,slots:n}=U;if(!e&&!t)return null;let r,i,a,s;if(t)r=Math.max(t.startRow,t.endRow),i=Math.max(t.startCol,t.endCol),a=Math.min(t.startCol,t.endCol),s=Math.max(t.startCol,t.endCol);else if(e)r=e.row,i=e.col,a=i,s=i;else return null;for(let e=a;e<=s;e++){let t=o[e];if(!(!t||t.hidden)&&t.editable!==!0)return null}let c=q.findIndex(e=>e.originalIndex===i);if(c===-1)return null;let l=null;for(let e of n.values())if(e.rowIndex===r){l=e.translateY;break}if(l===null)return null;let u=J[c]??0,d=Y[c]??0;return{top:l+p-5,left:u+d-20}},[U.activeCell,U.selectionRange,U.slots,p,J,Y,o,q]);return T(`div`,{ref:B,className:`gp-grid-container${E?` gp-grid-container--dark`:``}`,style:{width:`100%`,height:`100%`,overflow:`auto`,position:`relative`},onScroll:Q,onWheel:e=>de(e,D),onKeyDown:ue,tabIndex:0,children:[T(`div`,{style:{width:Math.max(U.contentWidth,X),height:Math.max(U.contentHeight,G),position:`relative`,minWidth:`100%`},children:[w(`div`,{className:`gp-grid-header`,style:{position:`sticky`,top:0,left:0,height:h,width:Math.max(U.contentWidth,X),minWidth:`100%`},children:q.map(({column:e,originalIndex:t},n)=>{let r=U.headers.get(t);return w(`div`,{className:`gp-grid-header-cell`,"data-col-index":t,style:{position:`absolute`,left:`${J[n]}px`,top:0,width:`${Y[n]}px`,height:`${h}px`,background:`transparent`},onClick:e=>le(t,e),children:ie({column:e,colIndex:t,sortDirection:r?.sortDirection,sortIndex:r?.sortIndex,sortable:r?.sortable??!0,filterable:r?.filterable??!0,hasFilter:r?.hasFilter??!1,coreRef:V,containerRef:B,headerRenderers:A,globalHeaderRenderer:F})},e.colId??e.field)})}),ge.map(e=>e.rowIndex<0?null:w(`div`,{className:[`gp-grid-row`,...V.current?.highlight?.computeRowClasses(e.rowIndex,e.rowData)??[]].filter(Boolean).join(` `),style:{position:`absolute`,top:0,left:0,transform:`translateY(${e.translateY}px)`,width:`${Math.max(U.contentWidth,X)}px`,height:`${p}px`},children:q.map(({column:t,originalIndex:n},r)=>{let i=b(e.rowIndex,n,U.editingCell),a=y(e.rowIndex,n,U.activeCell),o=S(e.rowIndex,n,U.selectionRange);return w(`div`,{className:[l(a,o,i,x(e.rowIndex,n,Z.dragType===`fill`,Z.fillSourceRange,Z.fillTarget)),...V.current?.highlight?.computeCombinedCellClasses(e.rowIndex,n,t,e.rowData)??[]].filter(Boolean).join(` `),style:{position:`absolute`,left:`${J[r]}px`,top:0,width:`${Y[r]}px`,height:`${p}px`},onMouseDown:t=>oe(e.rowIndex,n,t),onDoubleClick:()=>se(e.rowIndex,n),onMouseEnter:()=>me(e.rowIndex,n),onMouseLeave:he,children:i&&U.editingCell?re({column:t,rowData:e.rowData,rowIndex:e.rowIndex,colIndex:n,initialValue:U.editingCell.initialValue,coreRef:V,editRenderers:k,globalEditRenderer:M}):ne({column:t,rowData:e.rowData,rowIndex:e.rowIndex,colIndex:n,isActive:a,isSelected:o,isEditing:i,cellRenderers:O,globalCellRenderer:j})},`${e.slotId}-${n}`)})},e.slotId)),$&&!U.editingCell&&w(`div`,{className:`gp-grid-fill-handle`,style:{position:`absolute`,top:$.top,left:$.left,zIndex:200},onMouseDown:ce}),U.isLoading&&T(`div`,{className:`gp-grid-loading`,children:[w(`div`,{className:`gp-grid-loading-spinner`}),`Loading...`]}),U.error&&T(`div`,{className:`gp-grid-error`,children:[`Error: `,U.error]}),!U.isLoading&&!U.error&&U.totalRows===0&&w(`div`,{className:`gp-grid-empty`,children:`No data to display`})]}),U.filterPopup?.isOpen&&U.filterPopup.column&&U.filterPopup.anchorRect&&w(N,{column:U.filterPopup.column,colIndex:U.filterPopup.colIndex,anchorRect:U.filterPopup.anchorRect,distinctValues:U.filterPopup.distinctValues,currentFilter:U.filterPopup.currentFilter,onApply:fe,onClose:pe})]})}export{z as Grid,s as GridCore,d as createClientDataSource,p as createDataSourceFromArray,h as createMutableClientDataSource,g as createServerDataSource};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gp-grid-react",
3
3
  "description": "A high-performance React data grid component with virtual scrolling, cell selection, sorting, filtering, and Excel-like editing",
4
- "version": "0.5.3",
4
+ "version": "0.7.0",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -45,7 +45,7 @@
45
45
  "link-workspace-packages": false
46
46
  },
47
47
  "dependencies": {
48
- "gp-grid-core": "0.5.3"
48
+ "gp-grid-core": "0.7.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": "^19.0.0",