@alaarab/ogrid-react 2.0.2 → 2.0.3

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.
@@ -22,4 +22,6 @@ export { useColumnResize } from './useColumnResize';
22
22
  export { useRichSelectState } from './useRichSelectState';
23
23
  export { useSideBarState } from './useSideBarState';
24
24
  export { useTableLayout } from './useTableLayout';
25
+ export { useColumnReorder } from './useColumnReorder';
26
+ export { useVirtualScroll } from './useVirtualScroll';
25
27
  export { useLatestRef } from './useLatestRef';
@@ -0,0 +1,136 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { calculateDropTarget, reorderColumnArray, getPinStateForColumn, } from '@alaarab/ogrid-core';
3
+ /** Width of the resize handle zone on the right edge of each header cell. */
4
+ const RESIZE_HANDLE_ZONE = 8;
5
+ /**
6
+ * Convert Record<string, 'left' | 'right'> to the { left?, right? } shape core expects.
7
+ */
8
+ function toPinnedColumnsShape(pinned) {
9
+ if (!pinned)
10
+ return undefined;
11
+ const left = [];
12
+ const right = [];
13
+ for (const [id, side] of Object.entries(pinned)) {
14
+ if (side === 'left')
15
+ left.push(id);
16
+ else if (side === 'right')
17
+ right.push(id);
18
+ }
19
+ if (left.length === 0 && right.length === 0)
20
+ return undefined;
21
+ return {
22
+ ...(left.length > 0 ? { left } : {}),
23
+ ...(right.length > 0 ? { right } : {}),
24
+ };
25
+ }
26
+ /**
27
+ * Manages column reorder drag interactions with RAF-throttled updates.
28
+ * @param params - Columns, order, change callback, enabled flag, and wrapper ref.
29
+ * @returns Drag state and mousedown handler for header cells.
30
+ */
31
+ export function useColumnReorder(params) {
32
+ const { columns, columnOrder, onColumnOrderChange, enabled = true, pinnedColumns, wrapperRef, } = params;
33
+ const [isDragging, setIsDragging] = useState(false);
34
+ const [dropIndicatorX, setDropIndicatorX] = useState(null);
35
+ const rafRef = useRef(0);
36
+ // Refs for latest values so the window listeners capture current state
37
+ const columnsRef = useRef(columns);
38
+ columnsRef.current = columns;
39
+ const columnOrderRef = useRef(columnOrder);
40
+ columnOrderRef.current = columnOrder;
41
+ const onColumnOrderChangeRef = useRef(onColumnOrderChange);
42
+ onColumnOrderChangeRef.current = onColumnOrderChange;
43
+ const pinnedColumnsRef = useRef(pinnedColumns);
44
+ pinnedColumnsRef.current = pinnedColumns;
45
+ // Track active drag state for cleanup on unmount
46
+ const cleanupRef = useRef(null);
47
+ useEffect(() => {
48
+ return () => {
49
+ if (cleanupRef.current) {
50
+ cleanupRef.current();
51
+ cleanupRef.current = null;
52
+ }
53
+ };
54
+ }, []);
55
+ const handleHeaderMouseDown = useCallback((columnId, event) => {
56
+ if (!enabled)
57
+ return;
58
+ if (!onColumnOrderChangeRef.current)
59
+ return;
60
+ // Gate on left-click only
61
+ if (event.button !== 0)
62
+ return;
63
+ // Skip if in resize handle zone (right 8px of the header cell)
64
+ const target = event.currentTarget;
65
+ const rect = target.getBoundingClientRect();
66
+ if (event.clientX > rect.right - RESIZE_HANDLE_ZONE)
67
+ return;
68
+ // Skip column groups — only reorder leaf columns
69
+ const cols = columnsRef.current;
70
+ const colIndex = cols.findIndex((c) => c.columnId === columnId);
71
+ if (colIndex === -1)
72
+ return;
73
+ event.preventDefault();
74
+ const startX = event.clientX;
75
+ let hasMoved = false;
76
+ let latestDropTargetIndex = null;
77
+ // Determine pin state of the dragged column
78
+ const pinnedShape = toPinnedColumnsShape(pinnedColumnsRef.current);
79
+ const draggedPinState = getPinStateForColumn(columnId, pinnedShape);
80
+ // Lock text selection and set grabbing cursor during drag
81
+ const prevUserSelect = document.body.style.userSelect;
82
+ const prevCursor = document.body.style.cursor;
83
+ document.body.style.userSelect = 'none';
84
+ document.body.style.cursor = 'grabbing';
85
+ const onMove = (moveEvent) => {
86
+ // Require a small minimum drag distance before activating
87
+ if (!hasMoved && Math.abs(moveEvent.clientX - startX) < 5)
88
+ return;
89
+ if (!hasMoved) {
90
+ hasMoved = true;
91
+ setIsDragging(true);
92
+ }
93
+ if (rafRef.current)
94
+ cancelAnimationFrame(rafRef.current);
95
+ rafRef.current = requestAnimationFrame(() => {
96
+ rafRef.current = 0;
97
+ const wrapper = wrapperRef.current;
98
+ if (!wrapper)
99
+ return;
100
+ const currentOrder = columnOrderRef.current ?? columnsRef.current.map((c) => c.columnId);
101
+ const result = calculateDropTarget(moveEvent.clientX, currentOrder, columnId, draggedPinState, wrapper, pinnedShape);
102
+ if (result) {
103
+ latestDropTargetIndex = result.targetIndex;
104
+ setDropIndicatorX(result.indicatorX);
105
+ }
106
+ });
107
+ };
108
+ const cleanup = () => {
109
+ window.removeEventListener('mousemove', onMove, true);
110
+ window.removeEventListener('mouseup', onUp, true);
111
+ cleanupRef.current = null;
112
+ // Restore user-select and cursor
113
+ document.body.style.userSelect = prevUserSelect;
114
+ document.body.style.cursor = prevCursor;
115
+ // Cancel pending RAF
116
+ if (rafRef.current) {
117
+ cancelAnimationFrame(rafRef.current);
118
+ rafRef.current = 0;
119
+ }
120
+ };
121
+ const onUp = () => {
122
+ cleanup();
123
+ if (hasMoved && latestDropTargetIndex != null) {
124
+ const currentOrder = columnOrderRef.current ?? columnsRef.current.map((c) => c.columnId);
125
+ const newOrder = reorderColumnArray(currentOrder, columnId, latestDropTargetIndex);
126
+ onColumnOrderChangeRef.current?.(newOrder);
127
+ }
128
+ setIsDragging(false);
129
+ setDropIndicatorX(null);
130
+ };
131
+ window.addEventListener('mousemove', onMove, true);
132
+ window.addEventListener('mouseup', onUp, true);
133
+ cleanupRef.current = cleanup;
134
+ }, [enabled, wrapperRef]);
135
+ return { isDragging, dropIndicatorX, handleHeaderMouseDown };
136
+ }
@@ -11,7 +11,7 @@ const EMPTY_LOADING_OPTIONS = {};
11
11
  * @returns Grouped props for DataGridTable, pagination controls, column chooser, layout, and filters.
12
12
  */
13
13
  export function useOGrid(props, ref) {
14
- const { columns: columnsProp, getRowId, data, dataSource, page: controlledPage, pageSize: controlledPageSize, sort: controlledSort, filters: controlledFilters, visibleColumns: controlledVisibleColumns, isLoading: controlledLoading, onPageChange, onPageSizeChange, onSortChange, onFiltersChange, onVisibleColumnsChange, columnOrder, onColumnOrderChange, onColumnResized, onColumnPinned, freezeRows, freezeCols, defaultPageSize = DEFAULT_PAGE_SIZE, defaultSortBy, defaultSortDirection = 'asc', toolbar, toolbarBelow, emptyState, entityLabelPlural = 'items', className, layoutMode = 'fill', suppressHorizontalScroll, editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo, rowSelection = 'none', selectedRows, onSelectionChange, statusBar, pageSizeOptions, sideBar, onFirstDataRendered, onError, columnChooser: columnChooserProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
14
+ const { columns: columnsProp, getRowId, data, dataSource, page: controlledPage, pageSize: controlledPageSize, sort: controlledSort, filters: controlledFilters, visibleColumns: controlledVisibleColumns, isLoading: controlledLoading, onPageChange, onPageSizeChange, onSortChange, onFiltersChange, onVisibleColumnsChange, columnOrder, onColumnOrderChange, onColumnResized, onColumnPinned, freezeRows, freezeCols, defaultPageSize = DEFAULT_PAGE_SIZE, defaultSortBy, defaultSortDirection = 'asc', toolbar, toolbarBelow, emptyState, entityLabelPlural = 'items', className, layoutMode = 'fill', suppressHorizontalScroll, editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo, rowSelection = 'none', selectedRows, onSelectionChange, statusBar, pageSizeOptions, sideBar, onFirstDataRendered, onError, columnChooser: columnChooserProp, columnReorder, virtualScroll, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
15
15
  // Resolve column chooser placement
16
16
  const columnChooserPlacement = columnChooserProp === false ? 'none'
17
17
  : columnChooserProp === 'sidebar' ? 'sidebar'
@@ -265,6 +265,14 @@ export function useOGrid(props, ref) {
265
265
  setRefreshCounter(refreshCounterRef.current);
266
266
  }
267
267
  },
268
+ getColumnOrder: () => columnOrder ?? columns.map((c) => c.columnId),
269
+ setColumnOrder: (order) => {
270
+ onColumnOrderChange?.(order);
271
+ },
272
+ scrollToRow: () => {
273
+ // No-op at orchestration level — DataGridTable components implement
274
+ // this via useVirtualScroll.scrollToIndex when virtual scrolling is active.
275
+ },
268
276
  }), [
269
277
  visibleColumns,
270
278
  sort,
@@ -411,6 +419,8 @@ export function useOGrid(props, ref) {
411
419
  getUserByEmail: dataSource?.getUserByEmail,
412
420
  layoutMode,
413
421
  suppressHorizontalScroll,
422
+ columnReorder,
423
+ virtualScroll,
414
424
  'aria-label': ariaLabel,
415
425
  'aria-labelledby': ariaLabelledBy,
416
426
  emptyState: {
@@ -426,7 +436,8 @@ export function useOGrid(props, ref) {
426
436
  editable, cellSelection, onCellValueChanged, onUndo, onRedo, canUndo, canRedo,
427
437
  rowSelection, effectiveSelectedRows, handleSelectionChange, statusBarConfig,
428
438
  isLoadingResolved, filters, handleFilterChange, clientFilterOptions, dataSource,
429
- loadingFilterOptions, layoutMode, suppressHorizontalScroll, ariaLabel, ariaLabelledBy,
439
+ loadingFilterOptions, layoutMode, suppressHorizontalScroll, columnReorder, virtualScroll,
440
+ ariaLabel, ariaLabelledBy,
430
441
  hasActiveFilters, clearAllFilters, emptyState,
431
442
  ]);
432
443
  const pagination = useMemo(() => ({
@@ -0,0 +1,69 @@
1
+ import { useMemo, useCallback, useRef } from 'react';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ /** Threshold below which virtual scrolling is a no-op (all rows rendered). */
4
+ const PASSTHROUGH_THRESHOLD = 100;
5
+ /**
6
+ * Wraps TanStack Virtual for row virtualization.
7
+ * When disabled or when totalRows < threshold, returns a pass-through (all rows visible).
8
+ * @param params - Total rows, row height, enabled flag, overscan, and container ref.
9
+ * @returns Virtualizer instance, total height, visible range, and scrollToIndex helper.
10
+ */
11
+ export function useVirtualScroll(params) {
12
+ const { totalRows, rowHeight, enabled, overscan = 5, containerRef, } = params;
13
+ const isActive = enabled && totalRows >= PASSTHROUGH_THRESHOLD;
14
+ const getScrollElement = useCallback(() => containerRef.current, [containerRef]);
15
+ const virtualizer = useVirtualizer({
16
+ count: isActive ? totalRows : 0,
17
+ getScrollElement,
18
+ estimateSize: () => rowHeight,
19
+ overscan,
20
+ enabled: isActive,
21
+ });
22
+ const passthroughRange = useMemo(() => ({
23
+ startIndex: 0,
24
+ endIndex: Math.max(0, totalRows - 1),
25
+ offsetTop: 0,
26
+ offsetBottom: 0,
27
+ }), [totalRows]);
28
+ const activeRange = useMemo(() => {
29
+ if (!isActive)
30
+ return passthroughRange;
31
+ const virtualItems = virtualizer.getVirtualItems();
32
+ if (virtualItems.length === 0) {
33
+ return { startIndex: 0, endIndex: -1, offsetTop: 0, offsetBottom: 0 };
34
+ }
35
+ const first = virtualItems[0];
36
+ const last = virtualItems[virtualItems.length - 1];
37
+ const totalSize = virtualizer.getTotalSize();
38
+ return {
39
+ startIndex: first.index,
40
+ endIndex: last.index,
41
+ offsetTop: first.start,
42
+ offsetBottom: Math.max(0, totalSize - last.end),
43
+ };
44
+ }, [isActive, virtualizer, passthroughRange]);
45
+ const totalHeight = isActive
46
+ ? virtualizer.getTotalSize()
47
+ : totalRows * rowHeight;
48
+ const scrollToIndexRef = useRef(virtualizer);
49
+ scrollToIndexRef.current = virtualizer;
50
+ const scrollToIndex = useCallback((index) => {
51
+ if (isActive) {
52
+ scrollToIndexRef.current?.scrollToIndex(index, { align: 'auto' });
53
+ }
54
+ else {
55
+ // When not virtualized, scroll the container directly
56
+ const container = containerRef.current;
57
+ if (container) {
58
+ const top = index * rowHeight;
59
+ container.scrollTo({ top, behavior: 'auto' });
60
+ }
61
+ }
62
+ }, [isActive, containerRef, rowHeight]);
63
+ return {
64
+ virtualizer: isActive ? virtualizer : null,
65
+ totalHeight,
66
+ visibleRange: activeRange,
67
+ scrollToIndex,
68
+ };
69
+ }
package/dist/esm/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  export { CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from '@alaarab/ogrid-core';
3
3
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
4
4
  // Hooks
5
- export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, useTableLayout, useLatestRef, } from './hooks';
5
+ export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, useTableLayout, useColumnReorder, useVirtualScroll, useLatestRef, } from './hooks';
6
6
  // Components
7
7
  export { OGridLayout } from './components/OGridLayout';
8
8
  export { StatusBar } from './components/StatusBar';
@@ -45,4 +45,8 @@ export { useSideBarState } from './useSideBarState';
45
45
  export type { UseSideBarStateParams, UseSideBarStateResult } from './useSideBarState';
46
46
  export { useTableLayout } from './useTableLayout';
47
47
  export type { UseTableLayoutParams, UseTableLayoutResult } from './useTableLayout';
48
+ export { useColumnReorder } from './useColumnReorder';
49
+ export type { UseColumnReorderParams, UseColumnReorderResult, } from './useColumnReorder';
50
+ export { useVirtualScroll } from './useVirtualScroll';
51
+ export type { IVirtualScrollConfig, UseVirtualScrollParams, UseVirtualScrollResult, } from './useVirtualScroll';
48
52
  export { useLatestRef } from './useLatestRef';
@@ -0,0 +1,22 @@
1
+ import type { RefObject } from 'react';
2
+ import type { IColumnDef } from '../types';
3
+ export interface UseColumnReorderParams<T> {
4
+ columns: IColumnDef<T>[];
5
+ columnOrder?: string[];
6
+ onColumnOrderChange?: (order: string[]) => void;
7
+ enabled?: boolean;
8
+ /** Pinned column configuration for zone constraints. */
9
+ pinnedColumns?: Record<string, 'left' | 'right'>;
10
+ wrapperRef: RefObject<HTMLElement | null>;
11
+ }
12
+ export interface UseColumnReorderResult {
13
+ isDragging: boolean;
14
+ dropIndicatorX: number | null;
15
+ handleHeaderMouseDown: (columnId: string, event: React.MouseEvent) => void;
16
+ }
17
+ /**
18
+ * Manages column reorder drag interactions with RAF-throttled updates.
19
+ * @param params - Columns, order, change callback, enabled flag, and wrapper ref.
20
+ * @returns Drag state and mousedown handler for header cells.
21
+ */
22
+ export declare function useColumnReorder<T>(params: UseColumnReorderParams<T>): UseColumnReorderResult;
@@ -0,0 +1,33 @@
1
+ import type { Virtualizer } from '@tanstack/react-virtual';
2
+ import type { RefObject } from 'react';
3
+ import type { IVisibleRange } from '@alaarab/ogrid-core';
4
+ export type { IVirtualScrollConfig } from '@alaarab/ogrid-core';
5
+ export interface UseVirtualScrollParams {
6
+ /** Total number of rows in the data set. */
7
+ totalRows: number;
8
+ /** Row height in pixels. */
9
+ rowHeight: number;
10
+ /** Whether virtual scrolling is enabled. */
11
+ enabled: boolean;
12
+ /** Number of extra rows to render outside the visible area. Default: 5. */
13
+ overscan?: number;
14
+ /** Ref to the scrollable container element. */
15
+ containerRef: RefObject<HTMLElement | null>;
16
+ }
17
+ export interface UseVirtualScrollResult {
18
+ /** The TanStack virtualizer instance (null when disabled). */
19
+ virtualizer: Virtualizer<HTMLElement, Element> | null;
20
+ /** Total height of all rows in pixels. */
21
+ totalHeight: number;
22
+ /** The range of visible rows with spacer offsets. */
23
+ visibleRange: IVisibleRange;
24
+ /** Scroll to a specific row index. */
25
+ scrollToIndex: (index: number) => void;
26
+ }
27
+ /**
28
+ * Wraps TanStack Virtual for row virtualization.
29
+ * 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.
31
+ * @returns Virtualizer instance, total height, visible range, and scrollToIndex helper.
32
+ */
33
+ export declare function useVirtualScroll(params: UseVirtualScrollParams): UseVirtualScrollResult;
@@ -1,8 +1,8 @@
1
1
  export { CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from '@alaarab/ogrid-core';
2
- export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridDataGridProps, RowSelectionMode, RowId, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, HeaderCell, HeaderRow, SideBarPanelId, ISideBarDef, IDateFilterValue, } from './types';
2
+ export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridDataGridProps, RowSelectionMode, RowId, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, HeaderCell, HeaderRow, SideBarPanelId, ISideBarDef, IDateFilterValue, IVirtualScrollConfig, IColumnReorderConfig, } from './types';
3
3
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
4
- export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, useTableLayout, useLatestRef, } from './hooks';
5
- export type { UseFilterOptionsResult, UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseActiveCellResult, UseCellEditingResult, EditingCell, UseContextMenuResult, ContextMenuPosition, UseCellSelectionResult, UseCellSelectionParams, UseClipboardResult, UseClipboardParams, UseRowSelectionResult, UseRowSelectionParams, UseKeyboardNavigationResult, UseKeyboardNavigationParams, UseUndoRedoResult, UseUndoRedoParams, UseFillHandleResult, UseFillHandleParams, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, InlineCellEditorType, UseColumnResizeParams, UseColumnResizeResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, UseTableLayoutParams, UseTableLayoutResult, } from './hooks';
4
+ export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, useTableLayout, useColumnReorder, useVirtualScroll, useLatestRef, } from './hooks';
5
+ export type { UseFilterOptionsResult, UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseActiveCellResult, UseCellEditingResult, EditingCell, UseContextMenuResult, ContextMenuPosition, UseCellSelectionResult, UseCellSelectionParams, UseClipboardResult, UseClipboardParams, UseRowSelectionResult, UseRowSelectionParams, UseKeyboardNavigationResult, UseKeyboardNavigationParams, UseUndoRedoResult, UseUndoRedoParams, UseFillHandleResult, UseFillHandleParams, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, InlineCellEditorType, UseColumnResizeParams, UseColumnResizeResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, } from './hooks';
6
6
  export { OGridLayout } from './components/OGridLayout';
7
7
  export type { OGridLayoutProps } from './components/OGridLayout';
8
8
  export { StatusBar } from './components/StatusBar';
@@ -1,8 +1,8 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from './columnTypes';
3
- export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IOGridApi, } from '@alaarab/ogrid-core';
3
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IColumnReorderConfig, IOGridApi, } from '@alaarab/ogrid-core';
4
4
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from '@alaarab/ogrid-core';
5
- import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef } from '@alaarab/ogrid-core';
5
+ import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig } from '@alaarab/ogrid-core';
6
6
  /** Base props shared by both client-side and server-side OGrid modes. */
7
7
  interface IOGridBaseProps<T> {
8
8
  columns: (IColumnDef<T> | IColumnGroupDef<T>)[];
@@ -68,6 +68,10 @@ interface IOGridBaseProps<T> {
68
68
  sideBar?: boolean | ISideBarDef;
69
69
  /** Page size options shown in the pagination dropdown. Default: [10, 20, 50, 100]. */
70
70
  pageSizeOptions?: number[];
71
+ /** Enable column reordering via drag-and-drop on header cells. Default: false. */
72
+ columnReorder?: boolean;
73
+ /** Virtual scrolling configuration. When provided, only visible rows are rendered for large datasets. */
74
+ virtualScroll?: IVirtualScrollConfig;
71
75
  /** Fires once when the grid first renders with data (useful for restoring column state). */
72
76
  onFirstDataRendered?: () => void;
73
77
  /** Called when server-side fetchPage fails. */
@@ -145,6 +149,10 @@ export interface IOGridDataGridProps<T> {
145
149
  message?: ReactNode;
146
150
  render?: () => ReactNode;
147
151
  };
152
+ /** Enable column reordering via drag-and-drop on header cells. Default: false. */
153
+ columnReorder?: boolean;
154
+ /** Virtual scrolling configuration. When provided, only visible rows are rendered for large datasets. */
155
+ virtualScroll?: IVirtualScrollConfig;
148
156
  /** Called when a cell renderer or custom editor throws an error. */
149
157
  onCellError?: (error: Error, errorInfo: React.ErrorInfo) => void;
150
158
  'aria-label'?: string;
@@ -1,3 +1,3 @@
1
1
  export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, } from './columnTypes';
2
- export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, } from './dataGridTypes';
2
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IColumnReorderConfig, } from './dataGridTypes';
3
3
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
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",
@@ -35,7 +35,8 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "dependencies": {
38
- "@alaarab/ogrid-core": "2.0.2"
38
+ "@alaarab/ogrid-core": "2.0.3",
39
+ "@tanstack/react-virtual": "^3.11.0"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0"