@alaarab/ogrid-react 2.1.9 → 2.1.11

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/dist/esm/index.js CHANGED
@@ -566,20 +566,31 @@ function processClientSideData(data, columns, filters, sortBy, sortDirection) {
566
566
  if (Number.isNaN(bt)) return 1 * dir;
567
567
  return at === bt ? 0 : at > bt ? dir : -dir;
568
568
  });
569
- } else {
569
+ } else if (!compare) {
570
+ const keyCache = /* @__PURE__ */ new Map();
571
+ for (let i = 0; i < sortable.length; i++) {
572
+ const row = sortable[i];
573
+ const v = sortCol ? getCellValue(row, sortCol) : row[sortBy];
574
+ if (v == null) {
575
+ keyCache.set(row, void 0);
576
+ } else if (typeof v === "number") {
577
+ keyCache.set(row, v);
578
+ } else {
579
+ keyCache.set(row, String(v).toLowerCase());
580
+ }
581
+ }
570
582
  sortable.sort((a, b) => {
571
- if (compare) return compare(a, b) * dir;
572
- const av = sortCol ? getCellValue(a, sortCol) : a[sortBy];
573
- const bv = sortCol ? getCellValue(b, sortCol) : b[sortBy];
574
- if (av == null && bv == null) return 0;
575
- if (av == null) return -1 * dir;
576
- if (bv == null) return 1 * dir;
583
+ const av = keyCache.get(a);
584
+ const bv = keyCache.get(b);
585
+ if (av === void 0 && bv === void 0) return 0;
586
+ if (av === void 0) return -1 * dir;
587
+ if (bv === void 0) return 1 * dir;
577
588
  if (typeof av === "number" && typeof bv === "number")
578
589
  return av === bv ? 0 : av > bv ? dir : -dir;
579
- const as = String(av).toLowerCase();
580
- const bs = String(bv).toLowerCase();
581
- return as === bs ? 0 : as > bs ? dir : -dir;
590
+ return av === bv ? 0 : av > bv ? dir : -dir;
582
591
  });
592
+ } else {
593
+ sortable.sort((a, b) => compare(a, b) * dir);
583
594
  }
584
595
  return sortable;
585
596
  }
@@ -735,7 +746,77 @@ function getHeaderFilterConfig(col, input) {
735
746
  }
736
747
  return base;
737
748
  }
738
- function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
749
+ var _CellDescriptorCache = class _CellDescriptorCache2 {
750
+ constructor() {
751
+ this.cache = /* @__PURE__ */ new Map();
752
+ this.lastVersion = "";
753
+ }
754
+ /**
755
+ * Compute a version string from the volatile parts of CellRenderDescriptorInput.
756
+ * This string changes whenever any input that affects per-cell output changes.
757
+ * Cheap to compute (simple string concat) — O(1) regardless of grid size.
758
+ */
759
+ static computeVersion(input) {
760
+ const ec = input.editingCell;
761
+ const ac = input.activeCell;
762
+ const sr = input.selectionRange;
763
+ const cr = input.cutRange;
764
+ const cp = input.copyRange;
765
+ return (ec ? `${String(ec.rowId)}\0${ec.columnId}` : "") + "" + (ac ? `${ac.rowIndex}\0${ac.columnIndex}` : "") + "" + (sr ? `${sr.startRow}\0${sr.startCol}\0${sr.endRow}\0${sr.endCol}` : "") + "" + (cr ? `${cr.startRow}\0${cr.startCol}\0${cr.endRow}\0${cr.endCol}` : "") + "" + (cp ? `${cp.startRow}\0${cp.startCol}\0${cp.endRow}\0${cp.endCol}` : "") + "" + (input.isDragging ? "1" : "0") + "" + (input.editable !== false ? "1" : "0") + "" + (input.onCellValueChanged ? "1" : "0");
766
+ }
767
+ /**
768
+ * Get a cached descriptor or compute a new one.
769
+ *
770
+ * @param rowIndex - Row index in the dataset.
771
+ * @param colIdx - Column index within the visible columns.
772
+ * @param version - Volatile version string (from CellDescriptorCache.computeVersion).
773
+ * @param compute - Factory function called on cache miss.
774
+ * @returns The descriptor (cached or freshly computed).
775
+ */
776
+ get(rowIndex, colIdx, version, compute) {
777
+ const key = rowIndex * _CellDescriptorCache2.MAX_COL_STRIDE + colIdx;
778
+ const entry = this.cache.get(key);
779
+ if (entry !== void 0 && entry.version === version) {
780
+ return entry.descriptor;
781
+ }
782
+ const descriptor = compute();
783
+ this.cache.set(key, { version, descriptor });
784
+ return descriptor;
785
+ }
786
+ /**
787
+ * Update the last-seen version and return it.
788
+ * Call once per render pass to track whether any volatile state changed.
789
+ * If the version is unchanged from last render, the entire render is a no-op for all cells.
790
+ */
791
+ updateVersion(version) {
792
+ this.lastVersion = version;
793
+ }
794
+ /** The last version string set via updateVersion(). */
795
+ get currentVersion() {
796
+ return this.lastVersion;
797
+ }
798
+ /**
799
+ * Clear all cached entries. Call when the grid's data changes (new items array,
800
+ * different column count, etc.) to prevent stale cell values from being served.
801
+ */
802
+ clear() {
803
+ this.cache.clear();
804
+ }
805
+ };
806
+ _CellDescriptorCache.MAX_COL_STRIDE = 1024;
807
+ var CellDescriptorCache = _CellDescriptorCache;
808
+ function getCellRenderDescriptor(item, col, rowIndex, colIdx, input, cache) {
809
+ if (cache !== void 0) {
810
+ return cache.get(
811
+ rowIndex,
812
+ colIdx,
813
+ cache.currentVersion,
814
+ () => computeCellDescriptor(item, col, rowIndex, colIdx, input)
815
+ );
816
+ }
817
+ return computeCellDescriptor(item, col, rowIndex, colIdx, input);
818
+ }
819
+ function computeCellDescriptor(item, col, rowIndex, colIdx, input) {
739
820
  const rowId = input.getRowId(item);
740
821
  const globalColIndex = colIdx + input.colOffset;
741
822
  const colEditable = isColumnEditable(col, item);
@@ -1291,6 +1372,7 @@ function validateColumns(columns) {
1291
1372
  console.warn("[OGrid] columns prop is empty or not an array");
1292
1373
  return;
1293
1374
  }
1375
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1294
1376
  const ids = /* @__PURE__ */ new Set();
1295
1377
  for (const col of columns) {
1296
1378
  if (!col.columnId) {
@@ -1300,10 +1382,25 @@ function validateColumns(columns) {
1300
1382
  console.warn(`[OGrid] Duplicate columnId: "${col.columnId}"`);
1301
1383
  }
1302
1384
  ids.add(col.columnId);
1385
+ if (isDev && col.editable === true && col.cellEditor == null) {
1386
+ console.warn(
1387
+ `[OGrid] Column "${col.columnId}" has editable=true but no cellEditor defined. Cells will not open an editor on double-click. Set cellEditor to 'text', 'select', 'checkbox', 'date', or a custom component.`
1388
+ );
1389
+ }
1390
+ }
1391
+ }
1392
+ function validateVirtualScrollConfig(config) {
1393
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
1394
+ if (config.enabled !== true) return;
1395
+ if (!config.rowHeight || config.rowHeight <= 0) {
1396
+ console.warn(
1397
+ "[OGrid] virtualScroll.enabled is true but rowHeight is missing or <= 0. Set a positive rowHeight (e.g. virtualScroll: { enabled: true, rowHeight: 36 }) for correct virtual scrolling behavior."
1398
+ );
1303
1399
  }
1304
1400
  }
1305
1401
  function validateRowIds(items, getRowId) {
1306
1402
  if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
1403
+ if (!getRowId) return;
1307
1404
  const ids = /* @__PURE__ */ new Set();
1308
1405
  const limit = Math.min(items.length, 100);
1309
1406
  for (let i = 0; i < limit; i++) {
@@ -2690,7 +2787,11 @@ function useKeyboardNavigation(params) {
2690
2787
  const { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId } = data;
2691
2788
  const { activeCell, selectionRange, editingCell, selectedRowIds } = state;
2692
2789
  const { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu, onUndo, onRedo, clearClipboardRanges } = handlers;
2693
- const { editable, onCellValueChanged, rowSelection, wrapperRef } = features;
2790
+ const { editable, onCellValueChanged, rowSelection, wrapperRef, onKeyDown, fillDown } = features;
2791
+ if (onKeyDown) {
2792
+ onKeyDown(e);
2793
+ if (e.defaultPrevented) return;
2794
+ }
2694
2795
  const maxRowIndex = items.length - 1;
2695
2796
  const maxColIndex = visibleColumnCount - 1 + colOffset;
2696
2797
  if (items.length === 0) return;
@@ -2879,6 +2980,15 @@ function useKeyboardNavigation(params) {
2879
2980
  }
2880
2981
  }
2881
2982
  break;
2983
+ case "d":
2984
+ if (e.ctrlKey || e.metaKey) {
2985
+ if (editingCell != null) break;
2986
+ if (editable !== false && fillDown) {
2987
+ e.preventDefault();
2988
+ fillDown();
2989
+ }
2990
+ }
2991
+ break;
2882
2992
  case "Delete":
2883
2993
  case "Backspace": {
2884
2994
  if (editingCell != null) break;
@@ -3170,7 +3280,26 @@ function useFillHandle(params) {
3170
3280
  },
3171
3281
  []
3172
3282
  );
3173
- return { fillDrag, setFillDrag, handleFillHandleMouseDown };
3283
+ const itemsRef = useLatestRef(items);
3284
+ const visibleColsRef = useLatestRef(visibleCols);
3285
+ const fillDown = useCallback(() => {
3286
+ const range = selectionRangeRef.current;
3287
+ if (!range || editable === false || !onCellValueChangedRef.current) return;
3288
+ const norm = normalizeSelectionRange(range);
3289
+ const fillEvents = applyFillValues(
3290
+ norm,
3291
+ norm.startRow,
3292
+ norm.startCol,
3293
+ itemsRef.current,
3294
+ visibleColsRef.current
3295
+ );
3296
+ if (fillEvents.length > 0) {
3297
+ beginBatch?.();
3298
+ for (const evt of fillEvents) onCellValueChangedRef.current(evt);
3299
+ endBatch?.();
3300
+ }
3301
+ }, [editable, beginBatch, endBatch]);
3302
+ return { fillDrag, setFillDrag, handleFillHandleMouseDown, fillDown };
3174
3303
  }
3175
3304
  function useTableLayout(params) {
3176
3305
  const {
@@ -3710,7 +3839,8 @@ function useDataGridInteraction(params) {
3710
3839
  setActiveCell,
3711
3840
  handleRowCheckboxChange,
3712
3841
  setContextMenuPosition,
3713
- wrapperRef
3842
+ wrapperRef,
3843
+ onKeyDown
3714
3844
  } = params;
3715
3845
  const undoRedo = useUndoRedo({ onCellValueChanged: onCellValueChangedProp });
3716
3846
  const onCellValueChanged = undoRedo.onCellValueChanged;
@@ -3747,13 +3877,7 @@ function useDataGridInteraction(params) {
3747
3877
  },
3748
3878
  [handleCellMouseDownBase, clearClipboardRanges, wrapperRef]
3749
3879
  );
3750
- const { handleGridKeyDown } = useKeyboardNavigation({
3751
- data: { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId },
3752
- state: { activeCell, selectionRange, editingCell, selectedRowIds },
3753
- handlers: { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu: setContextMenuPosition, onUndo: undoRedo.undo, onRedo: undoRedo.redo, clearClipboardRanges },
3754
- features: { editable, onCellValueChanged, rowSelection: rowSelection ?? "none", wrapperRef }
3755
- });
3756
- const { handleFillHandleMouseDown } = useFillHandle({
3880
+ const { handleFillHandleMouseDown, fillDown } = useFillHandle({
3757
3881
  items,
3758
3882
  visibleCols,
3759
3883
  editable,
@@ -3766,6 +3890,12 @@ function useDataGridInteraction(params) {
3766
3890
  beginBatch: undoRedo.beginBatch,
3767
3891
  endBatch: undoRedo.endBatch
3768
3892
  });
3893
+ const { handleGridKeyDown } = useKeyboardNavigation({
3894
+ data: { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId },
3895
+ state: { activeCell, selectionRange, editingCell, selectedRowIds },
3896
+ handlers: { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu: setContextMenuPosition, onUndo: undoRedo.undo, onRedo: undoRedo.redo, clearClipboardRanges },
3897
+ features: { editable, onCellValueChanged, rowSelection: rowSelection ?? "none", wrapperRef, onKeyDown, fillDown }
3898
+ });
3769
3899
  const hasCellSelection = selectionRange != null || activeCell != null;
3770
3900
  const interactionState = useMemo(() => ({
3771
3901
  activeCell: cellSelection ? activeCell : null,
@@ -3867,7 +3997,8 @@ function useDataGridState(params) {
3867
3997
  onAutosizeColumn,
3868
3998
  pinnedColumns,
3869
3999
  onColumnPinned,
3870
- onCellError
4000
+ onCellError,
4001
+ onKeyDown
3871
4002
  } = props;
3872
4003
  const cellSelection = cellSelectionProp !== false;
3873
4004
  const {
@@ -3936,7 +4067,8 @@ function useDataGridState(params) {
3936
4067
  setActiveCell,
3937
4068
  handleRowCheckboxChange,
3938
4069
  setContextMenuPosition,
3939
- wrapperRef
4070
+ wrapperRef,
4071
+ onKeyDown
3940
4072
  });
3941
4073
  const {
3942
4074
  selectionRange,
@@ -5771,16 +5903,20 @@ function useVirtualizer(options) {
5771
5903
  }
5772
5904
 
5773
5905
  // src/hooks/useVirtualScroll.ts
5774
- var PASSTHROUGH_THRESHOLD = 100;
5906
+ var DEFAULT_PASSTHROUGH_THRESHOLD = 100;
5775
5907
  function useVirtualScroll(params) {
5776
5908
  const {
5777
5909
  totalRows,
5778
5910
  rowHeight,
5779
5911
  enabled,
5780
5912
  overscan = 5,
5913
+ threshold = DEFAULT_PASSTHROUGH_THRESHOLD,
5781
5914
  containerRef
5782
5915
  } = params;
5783
- const isActive = enabled && totalRows >= PASSTHROUGH_THRESHOLD;
5916
+ useEffect(() => {
5917
+ validateVirtualScrollConfig({ enabled, rowHeight });
5918
+ }, []);
5919
+ const isActive = enabled && totalRows >= threshold;
5784
5920
  const getScrollElement = useCallback(
5785
5921
  () => containerRef.current,
5786
5922
  [containerRef]
@@ -5954,6 +6090,7 @@ function useDataGridTableOrchestration(params) {
5954
6090
  rowHeight: virtualRowHeight,
5955
6091
  enabled: virtualScrollEnabled,
5956
6092
  overscan: virtualScroll?.overscan,
6093
+ threshold: virtualScroll?.threshold,
5957
6094
  containerRef: wrapperRef
5958
6095
  });
5959
6096
  const editCallbacks = useMemo(
@@ -5968,6 +6105,14 @@ function useDataGridTableOrchestration(params) {
5968
6105
  const pendingEditorValueRef = useLatestRef(pendingEditorValue);
5969
6106
  const popoverAnchorElRef = useLatestRef(popoverAnchorEl);
5970
6107
  const selectedRowIdsRef = useLatestRef(selectedRowIds);
6108
+ const cellDescriptorCacheRef = useRef(new CellDescriptorCache());
6109
+ const currentVersion = CellDescriptorCache.computeVersion(cellDescriptorInput);
6110
+ cellDescriptorCacheRef.current.updateVersion(currentVersion);
6111
+ const prevItemsRef = useRef(items);
6112
+ if (prevItemsRef.current !== items) {
6113
+ prevItemsRef.current = items;
6114
+ cellDescriptorCacheRef.current.clear();
6115
+ }
5971
6116
  const handleSingleRowClick = useCallback((e) => {
5972
6117
  if (rowSelection !== "single") return;
5973
6118
  const rowId = e.currentTarget.dataset.rowId;
@@ -6030,6 +6175,7 @@ function useDataGridTableOrchestration(params) {
6030
6175
  interactionHandlers,
6031
6176
  // Stable refs for volatile state
6032
6177
  cellDescriptorInputRef,
6178
+ cellDescriptorCacheRef,
6033
6179
  pendingEditorValueRef,
6034
6180
  popoverAnchorElRef,
6035
6181
  selectedRowIdsRef,
@@ -7300,4 +7446,4 @@ function renderFilterContent(filterType, state, options, isLoadingOptions, selec
7300
7446
  return null;
7301
7447
  }
7302
7448
 
7303
- export { BaseColumnHeaderMenu, BaseDropIndicator, BaseEmptyState, BaseInlineCellEditor, BaseLoadingOverlay, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CURSOR_CELL_STYLE, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, DateFilterContent, EmptyState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GRID_ROOT_STYLE, GridContextMenu, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NOOP3 as NOOP, OGridLayout, PAGE_SIZE_OPTIONS, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, ROW_NUMBER_COLUMN_WIDTH, STOP_PROPAGATION, SideBar, StatusBar, UndoRedoStack, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps2 as buildInlineEditorProps, buildPopoverEditorProps2 as buildPopoverEditorProps, clampSelectionToBounds, computeAggregations, computeAutoScrollSpeed, computeTabNavigation, createOGrid, currencyParser, dateParser, deriveFilterOptionsFromData, editorInputStyle, editorWrapperStyle, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderFilterStateParams, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getDateFilterContentProps, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getStatusBarParts, isInSelectionRange, isRowInRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, renderFilterContent, resolveCellDisplayContent2 as resolveCellDisplayContent, resolveCellStyle2 as resolveCellStyle, richSelectDropdownStyle, richSelectNoMatchesStyle, richSelectOptionHighlightedStyle, richSelectOptionStyle, richSelectWrapperStyle, selectChevronStyle, selectDisplayStyle, selectEditorStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnMeta, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableOrchestration, useDateFilterState, useDebounce, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useLatestRef, useListVirtualizer, useMultiSelectFilterState, useOGrid, usePaginationControls, usePeopleFilterState, useRichSelectState, useRowSelection, useSelectState, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
7449
+ export { BaseColumnHeaderMenu, BaseDropIndicator, BaseEmptyState, BaseInlineCellEditor, BaseLoadingOverlay, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CURSOR_CELL_STYLE, CellDescriptorCache, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, DateFilterContent, EmptyState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GRID_ROOT_STYLE, GridContextMenu, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NOOP3 as NOOP, OGridLayout, PAGE_SIZE_OPTIONS, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, ROW_NUMBER_COLUMN_WIDTH, STOP_PROPAGATION, SideBar, StatusBar, UndoRedoStack, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps2 as buildInlineEditorProps, buildPopoverEditorProps2 as buildPopoverEditorProps, clampSelectionToBounds, computeAggregations, computeAutoScrollSpeed, computeTabNavigation, createOGrid, currencyParser, dateParser, deriveFilterOptionsFromData, editorInputStyle, editorWrapperStyle, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderFilterStateParams, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getDateFilterContentProps, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getStatusBarParts, isInSelectionRange, isRowInRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, renderFilterContent, resolveCellDisplayContent2 as resolveCellDisplayContent, resolveCellStyle2 as resolveCellStyle, richSelectDropdownStyle, richSelectNoMatchesStyle, richSelectOptionHighlightedStyle, richSelectOptionStyle, richSelectWrapperStyle, selectChevronStyle, selectDisplayStyle, selectEditorStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnMeta, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableOrchestration, useDateFilterState, useDebounce, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useLatestRef, useListVirtualizer, useMultiSelectFilterState, useOGrid, usePaginationControls, usePeopleFilterState, useRichSelectState, useRowSelection, useSelectState, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
@@ -45,6 +45,8 @@ export interface UseDataGridInteractionParams<T> {
45
45
  y: number;
46
46
  } | null) => void;
47
47
  wrapperRef: RefObject<HTMLDivElement | null>;
48
+ /** Custom keydown handler — called before grid default. preventDefault() suppresses grid handling. */
49
+ onKeyDown?: (event: React.KeyboardEvent) => void;
48
50
  }
49
51
  export interface UseDataGridInteractionResult<T> {
50
52
  interaction: DataGridCellInteractionState;
@@ -6,6 +6,7 @@ import type { UseColumnReorderResult } from './useColumnReorder';
6
6
  import type { UseVirtualScrollResult } from './useVirtualScroll';
7
7
  import type { HeaderFilterConfigInput, CellRenderDescriptorInput } from '../utils';
8
8
  import type { IStatusBarProps, RowId, HeaderRow } from '../types';
9
+ import { CellDescriptorCache } from '@alaarab/ogrid-core';
9
10
  /** Parameters for the orchestration hook. */
10
11
  export interface UseDataGridTableOrchestrationParams<T> {
11
12
  props: IOGridDataGridProps<T>;
@@ -67,6 +68,8 @@ export interface UseDataGridTableOrchestrationResult<T> {
67
68
  handleCellContextMenu: DataGridContextMenuState['handleCellContextMenu'];
68
69
  };
69
70
  cellDescriptorInputRef: React.MutableRefObject<CellRenderDescriptorInput<T>>;
71
+ /** Per-grid descriptor cache. Eliminates redundant getCellRenderDescriptor allocations for unchanged cells. */
72
+ cellDescriptorCacheRef: React.MutableRefObject<CellDescriptorCache>;
70
73
  pendingEditorValueRef: React.MutableRefObject<unknown>;
71
74
  popoverAnchorElRef: React.MutableRefObject<HTMLElement | null>;
72
75
  selectedRowIdsRef: React.MutableRefObject<Set<RowId>>;
@@ -24,6 +24,8 @@ export interface UseFillHandleResult {
24
24
  startCol: number;
25
25
  } | null) => void;
26
26
  handleFillHandleMouseDown: (e: React.MouseEvent) => void;
27
+ /** Fill the current selection down from the top row (Ctrl+D). No-op if no selection or editable=false. */
28
+ fillDown: () => void;
27
29
  }
28
30
  /**
29
31
  * Manages Excel-style fill handle drag-to-fill for cell ranges.
@@ -34,6 +34,8 @@ export interface UseKeyboardNavigationParams<T> {
34
34
  onCellValueChanged: ((event: ICellValueChangedEvent<T>) => void) | undefined;
35
35
  rowSelection: RowSelectionMode;
36
36
  wrapperRef: React.RefObject<HTMLElement | null>;
37
+ onKeyDown?: (event: React.KeyboardEvent) => void;
38
+ fillDown?: () => void;
37
39
  };
38
40
  }
39
41
  export interface UseKeyboardNavigationResult {
@@ -11,6 +11,11 @@ export interface UseVirtualScrollParams {
11
11
  enabled: boolean;
12
12
  /** Number of extra rows to render outside the visible area. Default: 5. */
13
13
  overscan?: number;
14
+ /**
15
+ * Minimum row count before virtual scrolling activates. Default: 100.
16
+ * When totalRows < threshold, all rows render without virtualization.
17
+ */
18
+ threshold?: number;
14
19
  /** Ref to the scrollable container element. */
15
20
  containerRef: RefObject<HTMLElement | null>;
16
21
  }
@@ -27,7 +32,7 @@ export interface UseVirtualScrollResult {
27
32
  /**
28
33
  * Wraps TanStack Virtual for row virtualization.
29
34
  * When disabled or when totalRows < threshold, returns a pass-through (all rows visible).
30
- * @param params - Total rows, row height, enabled flag, overscan, and container ref.
35
+ * @param params - Total rows, row height, enabled flag, overscan, threshold, and container ref.
31
36
  * @returns Virtualizer instance, total height, visible range, and scrollToIndex helper.
32
37
  */
33
38
  export declare function useVirtualScroll(params: UseVirtualScrollParams): UseVirtualScrollResult;
@@ -32,7 +32,7 @@ export { BaseDropIndicator } from './components/BaseDropIndicator';
32
32
  export type { BaseDropIndicatorProps } from './components/BaseDropIndicator';
33
33
  export { DateFilterContent, getColumnHeaderFilterStateParams, getDateFilterContentProps, } from './components/ColumnHeaderFilterContent';
34
34
  export type { IColumnHeaderFilterProps, DateFilterContentProps, DateFilterClassNames, } from './components/ColumnHeaderFilterContent';
35
- export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, areGridRowPropsEqual, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, UndoRedoStack, } from './utils';
35
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, CellDescriptorCache, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, areGridRowPropsEqual, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, UndoRedoStack, } from './utils';
36
36
  export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, ParseValueResult, AggregationResult, GridRowComparatorProps, IColumnHeaderMenuItem, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers, } from './utils';
37
37
  export { renderFilterContent } from './components/ColumnHeaderFilterRenderers';
38
38
  export type { FilterContentRenderers, MultiSelectRendererProps, TextRendererProps, PeopleRendererProps, DateRendererProps, } from './components/ColumnHeaderFilterRenderers';
@@ -175,4 +175,6 @@ export interface IOGridDataGridProps<T> {
175
175
  onCellError?: (error: Error, errorInfo: React.ErrorInfo) => void;
176
176
  'aria-label'?: string;
177
177
  'aria-labelledby'?: string;
178
+ /** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
179
+ onKeyDown?: (event: React.KeyboardEvent) => void;
178
180
  }
@@ -1,6 +1,7 @@
1
1
  export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, computeNextSortState, measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, applyFillValues, computeArrowNavigation, applyCellDeletion, applyRangeRowSelection, computeRowSelectionState, UndoRedoStack, buildCellIndex, } from '@alaarab/ogrid-core';
2
2
  export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, ParseValueResult, AggregationResult, IColumnHeaderMenuItem, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers, ArrowNavigationContext, ArrowNavigationResult, } from '@alaarab/ogrid-core';
3
3
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
4
+ export { CellDescriptorCache } from '@alaarab/ogrid-core';
4
5
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, } from './dataGridViewModel';
5
6
  export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';
6
7
  export type { GridRowComparatorProps } from './gridRowComparator';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "description": "OGrid React – React hooks, headless components, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -39,7 +39,7 @@
39
39
  "node": ">=18"
40
40
  },
41
41
  "dependencies": {
42
- "@alaarab/ogrid-core": "2.1.9",
42
+ "@alaarab/ogrid-core": "2.1.11",
43
43
  "@tanstack/react-virtual": "^3.0.0"
44
44
  },
45
45
  "peerDependencies": {