@alaarab/ogrid-react 2.1.0 → 2.1.2
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/components/OGridLayout.js +48 -3
- package/dist/esm/components/createOGrid.js +1 -1
- package/dist/esm/hooks/useDataGridTableOrchestration.js +2 -1
- package/dist/esm/hooks/useFilterOptions.js +21 -4
- package/dist/esm/hooks/useOGrid.js +5 -4
- package/dist/esm/hooks/useOGridDataFetching.js +3 -3
- package/dist/types/components/OGridLayout.d.ts +2 -0
- package/dist/types/hooks/useDataGridTableOrchestration.d.ts +1 -0
- package/dist/types/hooks/useOGrid.d.ts +1 -0
- package/dist/types/hooks/useOGridDataFetching.d.ts +0 -1
- package/dist/types/types/dataGridTypes.d.ts +6 -0
- package/package.json +3 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
3
|
import { SideBar } from './SideBar';
|
|
3
4
|
import { GRID_BORDER_RADIUS } from '@alaarab/ogrid-core';
|
|
4
5
|
// Stable style objects (avoid re-creating on every render)
|
|
@@ -12,6 +13,11 @@ const borderedContainerStyle = {
|
|
|
12
13
|
minHeight: 0,
|
|
13
14
|
background: 'var(--ogrid-bg, #fff)',
|
|
14
15
|
};
|
|
16
|
+
const fullscreenContainerStyle = {
|
|
17
|
+
...borderedContainerStyle,
|
|
18
|
+
borderRadius: 0,
|
|
19
|
+
border: 'none',
|
|
20
|
+
};
|
|
15
21
|
const toolbarStripBase = {
|
|
16
22
|
display: 'flex',
|
|
17
23
|
justifyContent: 'space-between',
|
|
@@ -72,6 +78,29 @@ const rootStyle = {
|
|
|
72
78
|
flexDirection: 'column',
|
|
73
79
|
height: '100%',
|
|
74
80
|
};
|
|
81
|
+
const fullscreenRootStyle = {
|
|
82
|
+
position: 'fixed',
|
|
83
|
+
inset: 0,
|
|
84
|
+
zIndex: 9999,
|
|
85
|
+
display: 'flex',
|
|
86
|
+
flexDirection: 'column',
|
|
87
|
+
background: 'var(--ogrid-bg, #fff)',
|
|
88
|
+
};
|
|
89
|
+
const fullscreenBtnStyle = {
|
|
90
|
+
background: 'none',
|
|
91
|
+
border: '1px solid var(--ogrid-border, #e0e0e0)',
|
|
92
|
+
borderRadius: 4,
|
|
93
|
+
padding: '4px 6px',
|
|
94
|
+
cursor: 'pointer',
|
|
95
|
+
display: 'flex',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
color: 'var(--ogrid-fg, #242424)',
|
|
99
|
+
};
|
|
100
|
+
// SVG expand icon (enter fullscreen)
|
|
101
|
+
const ExpandIcon = () => (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "10 2 14 2 14 6" }), _jsx("polyline", { points: "6 14 2 14 2 10" }), _jsx("line", { x1: "14", y1: "2", x2: "10", y2: "6" }), _jsx("line", { x1: "2", y1: "14", x2: "6", y2: "10" })] }));
|
|
102
|
+
// SVG collapse icon (exit fullscreen)
|
|
103
|
+
const CollapseIcon = () => (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "4 10 0 10 0 14" }), _jsx("polyline", { points: "12 6 16 6 16 2" }), _jsx("line", { x1: "0", y1: "10", x2: "4", y2: "6" }), _jsx("line", { x1: "16", y1: "6", x2: "12", y2: "10" })] }));
|
|
75
104
|
/**
|
|
76
105
|
* Renders OGrid layout as a unified bordered container:
|
|
77
106
|
* ┌────────────────────────────────────┐
|
|
@@ -83,9 +112,25 @@ const rootStyle = {
|
|
|
83
112
|
* └────────────────────────────────────┘
|
|
84
113
|
*/
|
|
85
114
|
export function OGridLayout(props) {
|
|
86
|
-
const { containerComponent: Container = 'div', containerProps = {}, className, toolbar, toolbarEnd, toolbarBelow, children, pagination, sideBar, } = props;
|
|
115
|
+
const { containerComponent: Container = 'div', containerProps = {}, className, toolbar, toolbarEnd, toolbarBelow, children, pagination, sideBar, fullScreen, } = props;
|
|
116
|
+
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
117
|
+
const toggleFullScreen = useCallback(() => {
|
|
118
|
+
setIsFullScreen((prev) => !prev);
|
|
119
|
+
}, []);
|
|
120
|
+
// ESC key to exit fullscreen
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!isFullScreen)
|
|
123
|
+
return;
|
|
124
|
+
const handleKeyDown = (e) => {
|
|
125
|
+
if (e.key === 'Escape')
|
|
126
|
+
setIsFullScreen(false);
|
|
127
|
+
};
|
|
128
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
129
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
130
|
+
}, [isFullScreen]);
|
|
87
131
|
const hasSideBar = sideBar != null;
|
|
88
132
|
const sideBarPosition = sideBar?.position ?? 'right';
|
|
89
|
-
const hasToolbar = toolbar != null || toolbarEnd != null;
|
|
90
|
-
|
|
133
|
+
const hasToolbar = toolbar != null || toolbarEnd != null || fullScreen;
|
|
134
|
+
const fullscreenButton = fullScreen ? (_jsx("button", { type: "button", style: fullscreenBtnStyle, onClick: toggleFullScreen, title: isFullScreen ? 'Exit fullscreen' : 'Fullscreen', "aria-label": isFullScreen ? 'Exit fullscreen' : 'Fullscreen', children: isFullScreen ? _jsx(CollapseIcon, {}) : _jsx(ExpandIcon, {}) })) : null;
|
|
135
|
+
return (_jsx(Container, { className: className, style: isFullScreen ? fullscreenRootStyle : rootStyle, ...containerProps, children: _jsxs("div", { style: isFullScreen ? fullscreenContainerStyle : borderedContainerStyle, children: [hasToolbar && (_jsxs("div", { style: toolbarBelow ? toolbarStripNoBorderStyle : toolbarStripStyle, children: [_jsx("div", { style: toolbarSectionStyle, children: toolbar }), _jsxs("div", { style: toolbarSectionStyle, children: [toolbarEnd, fullscreenButton] })] })), toolbarBelow && (_jsx("div", { style: toolbarBelowStyle, children: toolbarBelow })), hasSideBar ? (_jsxs("div", { style: gridAreaFlexStyle, children: [sideBarPosition === 'left' && _jsx(SideBar, { ...sideBar }), _jsx("div", { style: gridChildStyle, children: children }), sideBarPosition !== 'left' && _jsx(SideBar, { ...sideBar })] })) : (_jsx("div", { style: gridAreaSoloStyle, children: children })), pagination && (_jsx("div", { style: footerStripStyle, children: pagination }))] }) }));
|
|
91
136
|
}
|
|
@@ -12,7 +12,7 @@ export function createOGrid(components) {
|
|
|
12
12
|
const { DataGridTable, ColumnChooser, PaginationControls, containerComponent, containerProps, } = components;
|
|
13
13
|
const OGridInner = forwardRef(function OGridInner(props, ref) {
|
|
14
14
|
const { dataGridProps, pagination, columnChooser, layout } = useOGrid(props, ref);
|
|
15
|
-
return (_jsx(OGridLayout, { containerComponent: containerComponent, containerProps: containerProps, className: layout.className, sideBar: layout.sideBarProps, toolbar: layout.toolbar, toolbarBelow: layout.toolbarBelow, toolbarEnd: columnChooser.placement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooser.columns, visibleColumns: columnChooser.visibleColumns, onVisibilityChange: columnChooser.onVisibilityChange, onSetVisibleColumns: columnChooser.onSetVisibleColumns })) : undefined, pagination: _jsx(PaginationControls, { currentPage: pagination.page, pageSize: pagination.pageSize, totalCount: pagination.displayTotalCount, onPageChange: pagination.setPage, onPageSizeChange: pagination.setPageSize, pageSizeOptions: pagination.pageSizeOptions, entityLabelPlural: pagination.entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
|
|
15
|
+
return (_jsx(OGridLayout, { containerComponent: containerComponent, containerProps: containerProps, className: layout.className, sideBar: layout.sideBarProps, toolbar: layout.toolbar, toolbarBelow: layout.toolbarBelow, fullScreen: layout.fullScreen, toolbarEnd: columnChooser.placement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooser.columns, visibleColumns: columnChooser.visibleColumns, onVisibilityChange: columnChooser.onVisibilityChange, onSetVisibleColumns: columnChooser.onSetVisibleColumns })) : undefined, pagination: _jsx(PaginationControls, { currentPage: pagination.page, pageSize: pagination.pageSize, totalCount: pagination.displayTotalCount, onPageChange: pagination.setPage, onPageSizeChange: pagination.setPageSize, pageSizeOptions: pagination.pageSizeOptions, entityLabelPlural: pagination.entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
|
|
16
16
|
});
|
|
17
17
|
OGridInner.displayName = 'OGrid';
|
|
18
18
|
return React.memo(OGridInner);
|
|
@@ -35,7 +35,7 @@ export function useDataGridTableOrchestration(params) {
|
|
|
35
35
|
const { headerMenu } = pinning;
|
|
36
36
|
const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
|
|
37
37
|
// ── Props destructuring ─────────────────────────────────────────────────
|
|
38
|
-
const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, rowHeight, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
|
|
38
|
+
const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', suppressHorizontalScroll, stickyHeader = true, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, rowHeight, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
|
|
39
39
|
// ── Derived values ──────────────────────────────────────────────────────
|
|
40
40
|
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * propPageSize : 0;
|
|
41
41
|
const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
|
|
@@ -116,6 +116,7 @@ export function useDataGridTableOrchestration(params) {
|
|
|
116
116
|
layoutMode,
|
|
117
117
|
rowSelection,
|
|
118
118
|
suppressHorizontalScroll,
|
|
119
|
+
stickyHeader,
|
|
119
120
|
isLoading,
|
|
120
121
|
loadingMessage,
|
|
121
122
|
ariaLabel,
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
/** Shallow-compare two string arrays by value. */
|
|
3
|
+
function fieldsEqual(a, b) {
|
|
4
|
+
if (a.length !== b.length)
|
|
5
|
+
return false;
|
|
6
|
+
for (let i = 0; i < a.length; i++) {
|
|
7
|
+
if (a[i] !== b[i])
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* Load filter options for the given fields from a data source.
|
|
4
14
|
*
|
|
5
15
|
* Accepts `IDataSource<T>` or a plain `{ fetchFilterOptions }` object.
|
|
6
16
|
*/
|
|
7
17
|
export function useFilterOptions(dataSource, fields) {
|
|
18
|
+
// Stabilize the fields array so inline literals (e.g. ['a','b']) don't
|
|
19
|
+
// cause infinite re-render loops via useCallback/useEffect deps.
|
|
20
|
+
const fieldsRef = useRef(fields);
|
|
21
|
+
if (!fieldsEqual(fieldsRef.current, fields)) {
|
|
22
|
+
fieldsRef.current = fields;
|
|
23
|
+
}
|
|
24
|
+
const stableFields = fieldsRef.current;
|
|
8
25
|
const [filterOptions, setFilterOptions] = useState({});
|
|
9
26
|
const [loadingOptions, setLoadingOptions] = useState({});
|
|
10
27
|
const load = useCallback(async () => {
|
|
@@ -17,10 +34,10 @@ export function useFilterOptions(dataSource, fields) {
|
|
|
17
34
|
return;
|
|
18
35
|
}
|
|
19
36
|
const loading = {};
|
|
20
|
-
|
|
37
|
+
stableFields.forEach((f) => { loading[f] = true; });
|
|
21
38
|
setLoadingOptions(loading);
|
|
22
39
|
const results = {};
|
|
23
|
-
await Promise.all(
|
|
40
|
+
await Promise.all(stableFields.map(async (field) => {
|
|
24
41
|
try {
|
|
25
42
|
results[field] = await fetcher(field);
|
|
26
43
|
}
|
|
@@ -30,7 +47,7 @@ export function useFilterOptions(dataSource, fields) {
|
|
|
30
47
|
}));
|
|
31
48
|
setFilterOptions(results);
|
|
32
49
|
setLoadingOptions({});
|
|
33
|
-
}, [dataSource,
|
|
50
|
+
}, [dataSource, stableFields]);
|
|
34
51
|
useEffect(() => {
|
|
35
52
|
load().catch(() => { });
|
|
36
53
|
}, [load]);
|
|
@@ -17,7 +17,7 @@ const EMPTY_LOADING_OPTIONS = {};
|
|
|
17
17
|
* @returns Grouped props for DataGridTable, pagination controls, column chooser, layout, and filters.
|
|
18
18
|
*/
|
|
19
19
|
export function useOGrid(props, ref) {
|
|
20
|
-
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, 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, showRowNumbers, statusBar, pageSizeOptions, sideBar, onFirstDataRendered, onError, columnChooser: columnChooserProp, columnReorder, virtualScroll, rowHeight, density = 'normal', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
20
|
+
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, 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, showRowNumbers, statusBar, pageSizeOptions, sideBar, stickyHeader, fullScreen, onFirstDataRendered, onError, columnChooser: columnChooserProp, columnReorder, virtualScroll, rowHeight, density = 'normal', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
21
21
|
// --- Derived column state ---
|
|
22
22
|
const columnChooserPlacement = columnChooserProp === false ? 'none'
|
|
23
23
|
: columnChooserProp === 'sidebar' ? 'sidebar'
|
|
@@ -53,7 +53,6 @@ export function useOGrid(props, ref) {
|
|
|
53
53
|
const dataFetchingState = useOGridDataFetching({
|
|
54
54
|
isServerSide, dataSource, displayData, columns,
|
|
55
55
|
stableFilters: filtersState.stableFilters,
|
|
56
|
-
filters: filtersState.filters,
|
|
57
56
|
sort: sortingState.sort,
|
|
58
57
|
page: paginationState.page,
|
|
59
58
|
pageSize: paginationState.pageSize,
|
|
@@ -291,6 +290,7 @@ export function useOGrid(props, ref) {
|
|
|
291
290
|
getUserByEmail: dataSource?.getUserByEmail,
|
|
292
291
|
layoutMode,
|
|
293
292
|
suppressHorizontalScroll,
|
|
293
|
+
stickyHeader: stickyHeader ?? true,
|
|
294
294
|
columnReorder,
|
|
295
295
|
virtualScroll,
|
|
296
296
|
rowHeight,
|
|
@@ -313,7 +313,7 @@ export function useOGrid(props, ref) {
|
|
|
313
313
|
paginationState.page, paginationState.pageSize, statusBarConfig,
|
|
314
314
|
isLoadingResolved, filtersState.filters, filtersState.handleFilterChange,
|
|
315
315
|
filtersState.clientFilterOptions, dataSource, filtersState.loadingFilterOptions,
|
|
316
|
-
layoutMode, suppressHorizontalScroll, columnReorder, virtualScroll,
|
|
316
|
+
layoutMode, suppressHorizontalScroll, stickyHeader, columnReorder, virtualScroll,
|
|
317
317
|
rowHeight, density, ariaLabel, ariaLabelledBy,
|
|
318
318
|
filtersState.hasActiveFilters, clearAllFilters, emptyState,
|
|
319
319
|
]);
|
|
@@ -339,7 +339,8 @@ export function useOGrid(props, ref) {
|
|
|
339
339
|
className,
|
|
340
340
|
emptyState,
|
|
341
341
|
sideBarProps,
|
|
342
|
-
|
|
342
|
+
fullScreen,
|
|
343
|
+
}), [toolbar, toolbarBelow, className, emptyState, sideBarProps, fullScreen]);
|
|
343
344
|
const filtersResult = useMemo(() => ({
|
|
344
345
|
hasActiveFilters: filtersState.hasActiveFilters,
|
|
345
346
|
setFilters: filtersState.setFilters,
|
|
@@ -5,7 +5,7 @@ import { processClientSideData } from '../utils';
|
|
|
5
5
|
* Fires onFirstDataRendered once when items first appear.
|
|
6
6
|
*/
|
|
7
7
|
export function useOGridDataFetching(params) {
|
|
8
|
-
const { isServerSide, dataSource, displayData, columns, stableFilters,
|
|
8
|
+
const { isServerSide, dataSource, displayData, columns, stableFilters, sort, page, pageSize, onError, onFirstDataRendered, } = params;
|
|
9
9
|
const isClientSide = !isServerSide;
|
|
10
10
|
// --- Client-side filtering & sorting ---
|
|
11
11
|
const clientItemsAndTotal = useMemo(() => {
|
|
@@ -35,7 +35,7 @@ export function useOGridDataFetching(params) {
|
|
|
35
35
|
.fetchPage({
|
|
36
36
|
page, pageSize,
|
|
37
37
|
sort: { field: sort.field, direction: sort.direction },
|
|
38
|
-
filters,
|
|
38
|
+
filters: stableFilters,
|
|
39
39
|
})
|
|
40
40
|
.then((res) => {
|
|
41
41
|
if (id !== fetchIdRef.current)
|
|
@@ -54,7 +54,7 @@ export function useOGridDataFetching(params) {
|
|
|
54
54
|
if (id === fetchIdRef.current)
|
|
55
55
|
setServerLoading(false);
|
|
56
56
|
});
|
|
57
|
-
}, [isServerSide, dataSource, page, pageSize, sort.field, sort.direction,
|
|
57
|
+
}, [isServerSide, dataSource, page, pageSize, sort.field, sort.direction, stableFilters, onError, refreshCounter]);
|
|
58
58
|
const displayItems = isClientSide && clientItemsAndTotal ? clientItemsAndTotal.items : serverItems;
|
|
59
59
|
const displayTotalCount = isClientSide && clientItemsAndTotal ? clientItemsAndTotal.totalCount : serverTotalCount;
|
|
60
60
|
// Fire onFirstDataRendered once when the grid first has data
|
|
@@ -23,6 +23,8 @@ export interface OGridLayoutProps {
|
|
|
23
23
|
pagination?: React.ReactNode;
|
|
24
24
|
/** Side bar props. When provided, renders SideBar alongside the grid. */
|
|
25
25
|
sideBar?: SideBarProps | null;
|
|
26
|
+
/** When true, render a fullscreen toggle button in the toolbar. */
|
|
27
|
+
fullScreen?: boolean;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Renders OGrid layout as a unified bordered container:
|
|
@@ -37,6 +37,7 @@ export interface UseDataGridTableOrchestrationResult<T> {
|
|
|
37
37
|
layoutMode: 'fill' | 'content';
|
|
38
38
|
rowSelection: IOGridDataGridProps<T>['rowSelection'];
|
|
39
39
|
suppressHorizontalScroll: IOGridDataGridProps<T>['suppressHorizontalScroll'];
|
|
40
|
+
stickyHeader: boolean;
|
|
40
41
|
isLoading: boolean;
|
|
41
42
|
loadingMessage: string;
|
|
42
43
|
ariaLabel: string | undefined;
|
|
@@ -64,6 +64,10 @@ interface IOGridBaseProps<T> {
|
|
|
64
64
|
layoutMode?: 'content' | 'fill';
|
|
65
65
|
/** When true, horizontal scrolling is suppressed (overflow-x hidden). */
|
|
66
66
|
suppressHorizontalScroll?: boolean;
|
|
67
|
+
/** When true (default), header row sticks to the top of the scroll container. */
|
|
68
|
+
stickyHeader?: boolean;
|
|
69
|
+
/** When true, shows a fullscreen toggle button in the toolbar. Default: false. */
|
|
70
|
+
fullScreen?: boolean;
|
|
67
71
|
/** Side bar configuration. `true` shows default panels (columns + filters). Pass ISideBarDef for options. */
|
|
68
72
|
sideBar?: boolean | ISideBarDef;
|
|
69
73
|
/** Page size options shown in the pagination dropdown. Default: [10, 20, 50, 100]. */
|
|
@@ -123,6 +127,8 @@ export interface IOGridDataGridProps<T> {
|
|
|
123
127
|
layoutMode?: 'content' | 'fill';
|
|
124
128
|
/** When true, horizontal scrolling is suppressed (overflow-x hidden). */
|
|
125
129
|
suppressHorizontalScroll?: boolean;
|
|
130
|
+
/** When true (default), header row sticks to the top of the scroll container. */
|
|
131
|
+
stickyHeader?: boolean;
|
|
126
132
|
isLoading?: boolean;
|
|
127
133
|
loadingMessage?: string;
|
|
128
134
|
editable?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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",
|
|
@@ -36,18 +36,13 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@alaarab/ogrid-core": "2.1.
|
|
39
|
+
"@alaarab/ogrid-core": "2.1.2",
|
|
40
|
+
"@tanstack/react-virtual": "^3.0.0"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"@tanstack/react-virtual": "^3.0.0",
|
|
43
43
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
45
45
|
},
|
|
46
|
-
"peerDependenciesMeta": {
|
|
47
|
-
"@tanstack/react-virtual": {
|
|
48
|
-
"optional": true
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
46
|
"devDependencies": {
|
|
52
47
|
"@testing-library/jest-dom": "^6.9.1",
|
|
53
48
|
"@testing-library/react": "^16.3.2",
|