@alaarab/ogrid 1.3.1 → 1.4.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.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import * as Popover from '@radix-ui/react-popover';
4
4
  import { useColumnHeaderFilterState } from '@alaarab/ogrid-core';
@@ -15,7 +15,7 @@ function FilterIcon() {
15
15
  return _jsx("span", { "aria-hidden": true, children: '\u25BE' });
16
16
  }
17
17
  export const ColumnHeaderFilter = React.memo((props) => {
18
- const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options = [], isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, } = props;
18
+ const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options = [], isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, dateValue, onDateChange, } = props;
19
19
  const state = useColumnHeaderFilterState({
20
20
  filterType,
21
21
  isSorted,
@@ -30,6 +30,8 @@ export const ColumnHeaderFilter = React.memo((props) => {
30
30
  selectedUser,
31
31
  onUserChange,
32
32
  peopleSearch,
33
+ dateValue,
34
+ onDateChange,
33
35
  });
34
36
  const { headerRef, popoverRef, peopleInputRef, isFilterOpen, setFilterOpen, tempSelected, tempTextValue, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, handlers, } = state;
35
37
  const safeOptions = options ?? [];
@@ -43,6 +45,9 @@ export const ColumnHeaderFilter = React.memo((props) => {
43
45
  if (filterType === 'people') {
44
46
  return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, inputRef: peopleInputRef }));
45
47
  }
48
+ if (filterType === 'date') {
49
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["From:", _jsx("input", { type: "date", value: state.tempDateFrom, onChange: (e) => state.setTempDateFrom(e.target.value), style: { flex: 1 } })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["To:", _jsx("input", { type: "date", value: state.tempDateTo, onChange: (e) => state.setTempDateTo(e.target.value), style: { flex: 1 } })] })] }), _jsxs("div", { className: styles.popoverActions, children: [_jsx("button", { className: styles.clearButton, onClick: handlers.handleDateClear, disabled: !state.tempDateFrom && !state.tempDateTo, children: "Clear" }), _jsx("button", { className: styles.applyButton, onClick: handlers.handleDateApply, children: "Apply" })] })] }));
50
+ }
46
51
  return null;
47
52
  };
48
53
  return (_jsxs("div", { className: styles.columnHeader, ref: headerRef, children: [_jsx("div", { className: styles.headerContent, children: _jsx("span", { className: styles.columnName, title: columnName, "data-header-label": true, children: columnName }) }), _jsxs("div", { className: styles.headerActions, children: [onSort && (_jsx("button", { type: "button", className: `${styles.sortIcon} ${isSorted ? styles.sortActive : ''}`, onClick: handlers.handleSortClick, "aria-label": `Sort by ${columnName}`, title: isSorted ? (isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort', children: _jsx(SortIcon, { isSorted: isSorted, isDesc: isSortedDescending }) })), filterType !== 'none' && (_jsxs(Popover.Root, { open: isFilterOpen, onOpenChange: setFilterOpen, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: `${styles.filterIcon} ${hasActiveFilter ? styles.filterActive : ''} ${isFilterOpen ? styles.filterOpen : ''}`, onClick: handlers.handleFilterIconClick, "aria-label": `Filter ${columnName}`, title: `Filter ${columnName}`, children: [_jsx(FilterIcon, {}), hasActiveFilter && _jsx("span", { className: styles.filterBadge })] }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { ref: popoverRef, className: styles.popoverContent, sideOffset: 4, align: "start", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs("div", { className: styles.popoverHeader, children: ["Filter: ", columnName] }), renderPopoverContent()] }) })] }))] })] }));
@@ -8,60 +8,38 @@ import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
8
8
  import { InlineCellEditor } from './InlineCellEditor';
9
9
  import { StatusBar } from './StatusBar';
10
10
  import { GridContextMenu } from './GridContextMenu';
11
- import { useDataGridState, useColumnResize, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, } from '@alaarab/ogrid-core';
11
+ import { useDataGridState, useColumnResize, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from '@alaarab/ogrid-core';
12
12
  import styles from './DataGridTable.module.css';
13
13
  function DataGridTableInner(props) {
14
14
  const wrapperRef = useRef(null);
15
15
  const tableContainerRef = useRef(null);
16
16
  const state = useDataGridState({ props, wrapperRef });
17
17
  const lastMouseShiftRef = useRef(false);
18
- const { visibleCols, totalColCount, hasCheckboxCol, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, setContextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
19
- const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
20
- const allowOverflowX = containerWidth > 0 && minTableWidth > containerWidth;
18
+ const { visibleCols, totalColCount, hasCheckboxCol, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
19
+ const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, } = props;
20
+ const headerRows = buildHeaderRows(columns, visibleColumns);
21
+ const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
21
22
  const fitToContent = layoutMode === 'content';
22
23
  const { handleResizeStart, getColumnWidth } = useColumnResize({
23
24
  columnSizingOverrides,
24
25
  setColumnSizingOverrides,
25
26
  });
27
+ const editCallbacks = React.useMemo(() => ({ commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit }), [commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit]);
28
+ const interactionHandlers = React.useMemo(() => ({ handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu }), [handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu]);
26
29
  const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
27
30
  const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
28
31
  if (descriptor.mode === 'editing-inline') {
29
- return (_jsx(InlineCellEditor, { value: descriptor.value, item: item, column: col, rowIndex: descriptor.rowIndex, editorType: descriptor.editorType ?? 'text', onCommit: (newValue) => commitCellEdit(item, col.columnId, descriptor.value, newValue, descriptor.rowIndex, descriptor.globalColIndex), onCancel: () => setEditingCell(null) }));
32
+ return _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, editCallbacks) });
30
33
  }
31
34
  if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
32
- const oldValue = descriptor.value;
33
- const displayValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
35
+ const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, editCallbacks);
34
36
  const CustomEditor = col.cellEditor;
35
- const editorProps = {
36
- value: displayValue,
37
- onValueChange: setPendingEditorValue,
38
- onCommit: () => {
39
- const newValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
40
- commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
41
- },
42
- onCancel: cancelPopoverEdit,
43
- item,
44
- column: col,
45
- cellEditorParams: col.cellEditorParams,
46
- };
47
37
  return (_jsxs(Popover.Root, { open: !!popoverAnchorEl, onOpenChange: (open) => { if (!open)
48
38
  cancelPopoverEdit(); }, children: [_jsx(Popover.Anchor, { asChild: true, children: _jsx("div", { ref: (el) => el && setPopoverAnchorEl(el), style: { minHeight: '100%', minWidth: 40 }, "aria-hidden": true }) }), _jsx(Popover.Portal, { children: _jsx(Popover.Content, { sideOffset: 4, onOpenAutoFocus: (e) => e.preventDefault(), children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
49
39
  }
50
- let content;
51
- if (col.renderCell)
52
- content = col.renderCell(item);
53
- else {
54
- const value = descriptor.displayValue;
55
- if (col.valueFormatter)
56
- content = col.valueFormatter(value, item);
57
- else if (value !== null && value !== undefined)
58
- content = String(value);
59
- else
60
- content = null;
61
- }
62
- const cellStyle = col.cellStyle ? (typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle) : undefined;
63
- if (cellStyle)
64
- content = _jsx("span", { style: cellStyle, children: content });
40
+ const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
41
+ const cellStyle = resolveCellStyle(col, item);
42
+ const styledContent = cellStyle ? _jsx("span", { style: cellStyle, children: content }) : content;
65
43
  const cellClassNames = [
66
44
  styles.cellContent,
67
45
  descriptor.isActive && !descriptor.isInRange ? styles.activeCellContent : '',
@@ -69,59 +47,66 @@ function DataGridTableInner(props) {
69
47
  descriptor.isInCutRange ? styles.cellCut : '',
70
48
  descriptor.isInCopyRange ? styles.cellCopied : '',
71
49
  ].filter(Boolean).join(' ');
72
- if (descriptor.canEditAny) {
73
- return (_jsxs("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, "data-in-range": descriptor.isInRange ? 'true' : undefined, role: "button", tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onDoubleClick: () => setEditingCell({ rowId: descriptor.rowId, columnId: col.columnId }), onContextMenu: handleCellContextMenu, style: { cursor: 'cell' }, children: [content, descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
74
- }
75
- return (_jsx("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, "data-in-range": descriptor.isInRange ? 'true' : undefined, tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onContextMenu: handleCellContextMenu, children: content }));
76
- }, [cellDescriptorInput, pendingEditorValue, popoverAnchorEl, handleCellMouseDown, handleCellContextMenu, handleFillHandleMouseDown, setActiveCell, setEditingCell, setPendingEditorValue, setPopoverAnchorEl, commitCellEdit, cancelPopoverEdit]);
50
+ const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
51
+ return (_jsxs("div", { className: cellClassNames, ...interactionProps, style: descriptor.canEditAny ? { cursor: 'cell' } : undefined, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
52
+ }, [cellDescriptorInput, pendingEditorValue, popoverAnchorEl, editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit]);
77
53
  return (_jsxs("div", { ref: wrapperRef, tabIndex: 0, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: (e) => { e.preventDefault(); }, onKeyDown: handleGridKeyDown, style: {
78
54
  ['--data-table-column-count']: totalColCount,
79
55
  ['--data-table-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'fit-content' : fitToContent ? 'fit-content' : '100%',
80
56
  ['--data-table-min-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'max-content' : fitToContent ? 'max-content' : '100%',
81
57
  ['--data-table-total-min-width']: `${minTableWidth}px`,
82
- }, children: [_jsxs("div", { className: styles.tableScrollContent, children: [_jsxs("div", { className: isLoading && items.length > 0 ? styles.loadingOverlayContainer : undefined, children: [isLoading && items.length > 0 && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx("div", { className: styles.spinner }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) })), _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs("table", { className: styles.dataTable, children: [_jsx("thead", { className: freezeRows != null && freezeRows >= 1 ? styles.stickyHeader : undefined, children: _jsxs("tr", { children: [hasCheckboxCol && (_jsx("th", { className: styles.selectionHeaderCell, scope: "col", children: _jsx("div", { className: styles.selectionHeaderCellInner, children: _jsx(Checkbox.Root, { className: styles.rowCheckbox, checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: (c) => handleSelectAll(!!c), "aria-label": "Select all rows", children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: someSelected && !allSelected ? '–' : '✓' }) }) }) })), visibleCols.map((col, colIdx) => {
83
- const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
84
- const isPinnedLeft = col.pinned === 'left';
85
- const isPinnedRight = col.pinned === 'right';
86
- const columnWidth = getColumnWidth(col);
87
- const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
88
- return (_jsxs("th", { scope: "col", "data-column-id": col.columnId, className: [
89
- isFreezeCol ? styles.freezeCol : '',
90
- isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
91
- isPinnedLeft ? styles.pinnedColLeft : '',
92
- isPinnedRight ? styles.pinnedColRight : '',
93
- ].filter(Boolean).join(' '), style: {
94
- minWidth: col.minWidth ?? 80,
95
- width: hasExplicitWidth ? columnWidth : undefined,
96
- maxWidth: hasExplicitWidth ? columnWidth : undefined,
97
- position: 'relative',
98
- }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => handleResizeStart(e, col), "aria-label": `Resize ${col.name}` })] }, col.columnId));
99
- })] }) }), !showEmptyInGrid && (_jsx("tbody", { children: items.map((item, rowIndex) => {
100
- const rowIdStr = getRowId(item);
101
- const isSelected = selectedRowIds.has(rowIdStr);
102
- return (_jsxs("tr", { className: isSelected ? styles.selectedRow : '', onClick: () => {
103
- if (rowSelection === 'single') {
104
- const id = getRowId(item);
105
- updateSelection(selectedRowIds.has(id) ? new Set() : new Set([id]));
106
- }
107
- }, children: [hasCheckboxCol && (_jsx("td", { className: styles.selectionCell, children: _jsx("div", { className: styles.selectionCellInner, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox.Root, { className: styles.rowCheckbox, checked: selectedRowIds.has(rowIdStr), onCheckedChange: (c) => handleRowCheckboxChange(rowIdStr, !!c, rowIndex, lastMouseShiftRef.current), "aria-label": `Select row ${rowIndex + 1}`, children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: "\u2713" }) }) }) })), visibleCols.map((col, colIdx) => {
108
- const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
109
- const isPinnedLeft = col.pinned === 'left';
110
- const isPinnedRight = col.pinned === 'right';
111
- const columnWidth = getColumnWidth(col);
112
- const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
113
- return (_jsx("td", { className: [
114
- isFreezeCol ? styles.freezeCol : '',
115
- isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
116
- isPinnedLeft ? styles.pinnedColLeft : '',
117
- isPinnedRight ? styles.pinnedColRight : '',
118
- ].filter(Boolean).join(' '), style: {
119
- minWidth: col.minWidth ?? 80,
120
- width: hasExplicitWidth ? columnWidth : undefined,
121
- maxWidth: hasExplicitWidth ? columnWidth : undefined,
122
- }, children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId));
123
- })] }, rowIdStr));
124
- }) }))] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined }))] }) })] }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }), contextMenu &&
58
+ }, children: [isLoading && items.length > 0 && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx("div", { className: styles.spinner }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) })), _jsxs("div", { className: styles.tableScrollContent, children: [_jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs("table", { className: styles.dataTable, children: [_jsx("thead", { className: styles.stickyHeader, children: headerRows.map((row, rowIdx) => (_jsxs("tr", { children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx("th", { className: styles.selectionHeaderCell, scope: "col", rowSpan: 1, children: _jsx("div", { className: styles.selectionHeaderCellInner, children: _jsx(Checkbox.Root, { className: styles.rowCheckbox, checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: (c) => handleSelectAll(!!c), "aria-label": "Select all rows", children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: someSelected && !allSelected ? '–' : '✓' }) }) }) })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1 })), row.map((cell, cellIdx) => {
59
+ if (cell.isGroup) {
60
+ return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
61
+ }
62
+ // Leaf cell
63
+ const col = cell.columnDef;
64
+ const colIdx = visibleCols.indexOf(col);
65
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
66
+ const isPinnedLeft = col.pinned === 'left';
67
+ const isPinnedRight = col.pinned === 'right';
68
+ const columnWidth = getColumnWidth(col);
69
+ const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
70
+ const leafRowSpan = headerRows.length > 1 && rowIdx < headerRows.length - 1
71
+ ? headerRows.length - rowIdx
72
+ : undefined;
73
+ return (_jsxs("th", { scope: "col", "data-column-id": col.columnId, rowSpan: leafRowSpan, className: [
74
+ isFreezeCol ? styles.freezeCol : '',
75
+ isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
76
+ isPinnedLeft ? styles.pinnedColLeft : '',
77
+ isPinnedRight ? styles.pinnedColRight : '',
78
+ ].filter(Boolean).join(' '), style: {
79
+ minWidth: col.minWidth ?? 80,
80
+ width: hasExplicitWidth ? columnWidth : undefined,
81
+ maxWidth: hasExplicitWidth ? columnWidth : undefined,
82
+ }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => handleResizeStart(e, col), "aria-label": `Resize ${col.name}` })] }, col.columnId));
83
+ })] }, rowIdx))) }), !showEmptyInGrid && (_jsx("tbody", { children: items.map((item, rowIndex) => {
84
+ const rowIdStr = getRowId(item);
85
+ const isSelected = selectedRowIds.has(rowIdStr);
86
+ return (_jsxs("tr", { className: isSelected ? styles.selectedRow : '', onClick: () => {
87
+ if (rowSelection === 'single') {
88
+ const id = getRowId(item);
89
+ updateSelection(selectedRowIds.has(id) ? new Set() : new Set([id]));
90
+ }
91
+ }, children: [hasCheckboxCol && (_jsx("td", { className: styles.selectionCell, children: _jsx("div", { className: styles.selectionCellInner, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox.Root, { className: styles.rowCheckbox, checked: selectedRowIds.has(rowIdStr), onCheckedChange: (c) => handleRowCheckboxChange(rowIdStr, !!c, rowIndex, lastMouseShiftRef.current), "aria-label": `Select row ${rowIndex + 1}`, children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: "\u2713" }) }) }) })), visibleCols.map((col, colIdx) => {
92
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
93
+ const isPinnedLeft = col.pinned === 'left';
94
+ const isPinnedRight = col.pinned === 'right';
95
+ const columnWidth = getColumnWidth(col);
96
+ const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
97
+ return (_jsx("td", { className: [
98
+ isFreezeCol ? styles.freezeCol : '',
99
+ isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
100
+ isPinnedLeft ? styles.pinnedColLeft : '',
101
+ isPinnedRight ? styles.pinnedColRight : '',
102
+ ].filter(Boolean).join(' '), style: {
103
+ minWidth: col.minWidth ?? 80,
104
+ width: hasExplicitWidth ? columnWidth : undefined,
105
+ maxWidth: hasExplicitWidth ? columnWidth : undefined,
106
+ textAlign: col.type === 'numeric' ? 'right' : col.type === 'boolean' ? 'center' : undefined,
107
+ }, children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId));
108
+ })] }, rowIdStr));
109
+ }) }))] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }) }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined, aggregation: statusBarConfig.aggregation }))] }), contextMenu &&
125
110
  createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
126
111
  }
127
112
  export const DataGridTable = React.memo(DataGridTableInner);
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  .tableScrollContent {
3
2
  display: flex;
4
3
  flex-direction: column;
@@ -8,13 +7,16 @@
8
7
  }
9
8
 
10
9
  .tableWrapper {
10
+ position: relative;
11
+ flex: 1;
12
+ min-height: 0;
11
13
  overflow-x: hidden;
12
- overflow-y: visible;
14
+ overflow-y: auto;
13
15
  width: 100%;
14
16
  min-width: 0;
15
17
  max-width: 100%;
16
- border-radius: 6px;
17
18
  box-sizing: border-box;
19
+ background: var(--ogrid-bg, #fff);
18
20
  }
19
21
  .tableWrapper[data-overflow-x=true] {
20
22
  overflow-x: auto;
@@ -26,8 +28,9 @@
26
28
  .tableWidthAnchor {
27
29
  position: relative;
28
30
  width: max-content;
29
- /* No min-width: 100% anchor sizes to grid content so status bar aligns with the table border.
30
- .tableScrollContent provides the full-width background so no gap is visible. */
31
+ /* min-width uses the same CSS var as the table: 100% in fill mode, max-content otherwise.
32
+ Safe now that StatusBar is outside this anchor. */
33
+ min-width: var(--data-table-min-width, max-content);
31
34
  background: var(--ogrid-bg, #fff);
32
35
  }
33
36
 
@@ -37,8 +40,6 @@
37
40
  min-width: var(--data-table-min-width, max-content);
38
41
  border-collapse: separate;
39
42
  border-spacing: 0;
40
- border: 1px solid var(--ogrid-border, #e0e0e0);
41
- border-radius: 6px;
42
43
  box-sizing: border-box;
43
44
  table-layout: auto;
44
45
  }
@@ -65,12 +66,6 @@
65
66
  border-right: none;
66
67
  }
67
68
 
68
- /* When status bar follows, remove bottom border-radius so the frame connects */
69
- .tableWidthAnchor:has(.statusBar) .dataTable {
70
- border-bottom-left-radius: 0;
71
- border-bottom-right-radius: 0;
72
- }
73
-
74
69
  .dataTable thead {
75
70
  background: var(--ogrid-bg-subtle, #f3f2f1);
76
71
  }
@@ -99,14 +94,16 @@
99
94
  background: var(--ogrid-bg-subtle, #f3f2f1);
100
95
  }
101
96
 
102
- /* Pinned columns: sticky positioning based on column pinned property */
103
- .pinnedColLeft {
97
+ /* Pinned columns: sticky positioning based on column pinned property.
98
+ Selectors use .dataTable qualifier to beat .dataTable thead th / .dataTable tbody td
99
+ which set position: relative. */
100
+ .dataTable .pinnedColLeft {
104
101
  position: sticky;
105
102
  left: 0;
106
103
  z-index: 2;
107
104
  background: var(--ogrid-bg, #ffffff);
108
105
  }
109
- .pinnedColLeft::after {
106
+ .dataTable .pinnedColLeft::after {
110
107
  content: "";
111
108
  position: absolute;
112
109
  top: 0;
@@ -121,13 +118,13 @@
121
118
  background: var(--ogrid-bg-subtle, #f3f2f1);
122
119
  }
123
120
 
124
- .pinnedColRight {
121
+ .dataTable .pinnedColRight {
125
122
  position: sticky;
126
123
  right: 0;
127
124
  z-index: 2;
128
125
  background: var(--ogrid-bg, #ffffff);
129
126
  }
130
- .pinnedColRight::before {
127
+ .dataTable .pinnedColRight::before {
131
128
  content: "";
132
129
  position: absolute;
133
130
  top: 0;
@@ -151,6 +148,14 @@
151
148
  border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
152
149
  }
153
150
 
151
+ .groupHeaderCell {
152
+ text-align: center;
153
+ font-weight: 600;
154
+ border-bottom: 2px solid var(--ogrid-border, #e0e0e0);
155
+ padding: 6px 10px;
156
+ background: var(--ogrid-header-bg, #f5f5f5);
157
+ }
158
+
154
159
  /* Column resize handle */
155
160
  .resizeHandle {
156
161
  position: absolute;
@@ -221,6 +226,10 @@
221
226
  background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
222
227
  }
223
228
 
229
+ :global([data-drag-range]) {
230
+ background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
231
+ }
232
+
224
233
  .cellCut {
225
234
  background: var(--ogrid-bg-hover, rgba(0, 0, 0, 0.04)) !important;
226
235
  opacity: 0.7;
@@ -294,9 +303,7 @@
294
303
  font-size: 12px;
295
304
  color: var(--ogrid-muted, #616161);
296
305
  background: var(--ogrid-bg-subtle, #f3f2f1);
297
- border: 1px solid var(--ogrid-border, #e0e0e0);
298
- border-top: none;
299
- border-radius: 0 0 6px 6px;
306
+ border-top: 1px solid var(--ogrid-border, #e0e0e0);
300
307
  min-height: 28px;
301
308
  }
302
309
 
@@ -370,10 +377,6 @@
370
377
  background: var(--ogrid-border, #e0e0e0);
371
378
  }
372
379
 
373
- .loadingOverlayContainer {
374
- position: relative;
375
- }
376
-
377
380
  .loadingOverlay {
378
381
  position: absolute;
379
382
  inset: 0;
@@ -384,7 +387,6 @@
384
387
  background: rgba(255, 255, 255, 0.7);
385
388
  backdrop-filter: blur(1px);
386
389
  pointer-events: all;
387
- border-radius: 6px;
388
390
  }
389
391
 
390
392
  .loadingOverlayContent {
@@ -1,22 +1,85 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import * as Checkbox from '@radix-ui/react-checkbox';
4
- import { useInlineCellEditorState } from '@alaarab/ogrid-core';
4
+ import { useInlineCellEditorState, useRichSelectState } from '@alaarab/ogrid-core';
5
+ // Match .cellContent layout so column width doesn't shift during editing
6
+ const editorWrapperStyle = {
7
+ width: '100%',
8
+ height: '100%',
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ padding: '6px 10px',
12
+ boxSizing: 'border-box',
13
+ overflow: 'hidden',
14
+ minWidth: 0,
15
+ };
16
+ const editorInputStyle = {
17
+ width: '100%',
18
+ padding: 0,
19
+ border: 'none',
20
+ background: 'transparent',
21
+ color: 'inherit',
22
+ font: 'inherit',
23
+ fontSize: '13px',
24
+ outline: 'none',
25
+ minWidth: 0,
26
+ };
5
27
  export function InlineCellEditor(props) {
6
28
  const { value, column, editorType, onCommit, onCancel } = props;
7
29
  const wrapperRef = React.useRef(null);
8
30
  const { localValue, setLocalValue, handleKeyDown, handleBlur, commit, cancel } = useInlineCellEditorState({ value, editorType, onCommit, onCancel });
31
+ const richSelectValues = column.cellEditorParams?.values ?? [];
32
+ const richSelectFormatValue = column.cellEditorParams?.formatValue;
33
+ const richSelect = useRichSelectState({
34
+ values: richSelectValues,
35
+ formatValue: richSelectFormatValue,
36
+ initialValue: value,
37
+ onCommit,
38
+ onCancel,
39
+ });
9
40
  React.useEffect(() => {
10
41
  const input = wrapperRef.current?.querySelector('input');
11
42
  input?.focus();
12
43
  }, []);
44
+ if (editorType === 'richSelect') {
45
+ return (_jsxs("div", { ref: wrapperRef, style: { ...editorWrapperStyle, position: 'relative' }, children: [_jsx("input", { type: "text", value: richSelect.searchText, onChange: (e) => richSelect.setSearchText(e.target.value), onKeyDown: richSelect.handleKeyDown, placeholder: "Search...", autoFocus: true, style: editorInputStyle }), _jsxs("div", { style: {
46
+ position: 'absolute',
47
+ top: '100%',
48
+ left: 0,
49
+ right: 0,
50
+ maxHeight: 200,
51
+ overflowY: 'auto',
52
+ background: 'var(--ogrid-bg, #fff)',
53
+ border: '1px solid var(--ogrid-border, #ccc)',
54
+ zIndex: 10,
55
+ boxShadow: '0 4px 16px rgba(0,0,0,0.2)',
56
+ }, role: "listbox", children: [richSelect.filteredValues.map((v, i) => (_jsx("div", { role: "option", "aria-selected": i === richSelect.highlightedIndex, onClick: () => richSelect.selectValue(v), style: {
57
+ padding: '6px 8px',
58
+ cursor: 'pointer',
59
+ color: 'var(--ogrid-fg, #242424)',
60
+ background: i === richSelect.highlightedIndex ? 'var(--ogrid-bg-hover, #e8f0fe)' : undefined,
61
+ }, children: richSelect.getDisplayText(v) }, String(v)))), richSelect.filteredValues.length === 0 && (_jsx("div", { style: { padding: '6px 8px', color: 'var(--ogrid-muted, #999)' }, children: "No matches" }))] })] }));
62
+ }
13
63
  if (editorType === 'checkbox') {
14
64
  const checked = value === true;
15
65
  return (_jsx(Checkbox.Root, { checked: checked, onCheckedChange: (c) => commit(c === true), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), children: _jsx(Checkbox.Indicator, { children: "\u2713" }) }));
16
66
  }
17
67
  if (editorType === 'select') {
18
68
  const values = column.cellEditorParams?.values ?? [];
19
- return (_jsx("select", { value: value !== null && value !== undefined ? String(value) : '', onChange: (e) => commit(e.target.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), autoFocus: true, children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }));
69
+ return (_jsx("div", { style: editorWrapperStyle, children: _jsx("select", { value: value !== null && value !== undefined ? String(value) : '', onChange: (e) => commit(e.target.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), autoFocus: true, style: {
70
+ width: '100%',
71
+ padding: 0,
72
+ border: 'none',
73
+ background: 'transparent',
74
+ color: 'inherit',
75
+ font: 'inherit',
76
+ fontSize: '13px',
77
+ cursor: 'pointer',
78
+ outline: 'none',
79
+ }, children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }) }));
80
+ }
81
+ if (editorType === 'date') {
82
+ return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "date", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
20
83
  }
21
- return (_jsx("div", { ref: wrapperRef, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: { minWidth: 60 }, autoFocus: true }) }));
84
+ return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
22
85
  }
@@ -6,11 +6,11 @@ import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
6
6
  import { PaginationControls } from '../PaginationControls/PaginationControls';
7
7
  import { useOGrid, OGridLayout, } from '@alaarab/ogrid-core';
8
8
  const OGridInner = forwardRef(function OGridInner(props, ref) {
9
- const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, title, toolbar, className, entityLabelPlural, } = useOGrid(props, ref);
10
- return (_jsx(OGridLayout, { className: className, gap: 16, title: title, toolbar: toolbar, columnChooser: _jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange }), pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
9
+ const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, columnChooserPlacement, title, toolbar, className, entityLabelPlural, pageSizeOptions, sideBarProps, } = useOGrid(props, ref);
10
+ return (_jsx(OGridLayout, { className: className, gap: 8, sideBar: sideBarProps, title: title, toolbar: toolbar, toolbarEnd: columnChooserPlacement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
11
11
  setPageSize(size);
12
12
  setPage(1);
13
- }, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
13
+ }, pageSizeOptions: pageSizeOptions, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
14
14
  });
15
15
  OGridInner.displayName = 'OGrid';
16
16
  export const OGrid = React.memo(OGridInner);
@@ -16,9 +16,9 @@ function ChevronDoubleRight() {
16
16
  return _jsx("span", { "aria-hidden": true, children: "\u00BB" });
17
17
  }
18
18
  export const PaginationControls = React.memo((props) => {
19
- const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, entityLabelPlural, className } = props;
19
+ const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className } = props;
20
20
  const labelPlural = entityLabelPlural ?? 'items';
21
- const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount), [currentPage, pageSize, totalCount]);
21
+ const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
22
22
  const handlePageSizeChange = useCallback((e) => {
23
23
  onPageSizeChange(Number(e.target.value));
24
24
  }, [onPageSizeChange]);
@@ -7,9 +7,7 @@
7
7
  width: 100%;
8
8
  min-width: 0;
9
9
  box-sizing: border-box;
10
- padding: 14px 0;
11
- margin-top: 16px;
12
- border-top: 1px solid var(--ogrid-border, #e5e5e5);
10
+ padding: 0;
13
11
  }
14
12
 
15
13
  .paginationInfo {
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { UserLike, ColumnFilterType } from '@alaarab/ogrid-core';
2
+ import type { UserLike, ColumnFilterType, IDateFilterValue } from '@alaarab/ogrid-core';
3
3
  export interface IColumnHeaderFilterProps {
4
4
  columnKey: string;
5
5
  columnName: string;
@@ -16,5 +16,7 @@ export interface IColumnHeaderFilterProps {
16
16
  selectedUser?: UserLike;
17
17
  onUserChange?: (user: UserLike | undefined) => void;
18
18
  peopleSearch?: (query: string) => Promise<UserLike[]>;
19
+ dateValue?: IDateFilterValue;
20
+ onDateChange?: (value: IDateFilterValue | undefined) => void;
19
21
  }
20
22
  export declare const ColumnHeaderFilter: React.FC<IColumnHeaderFilterProps>;
@@ -5,7 +5,7 @@ export interface InlineCellEditorProps<T> {
5
5
  item: T;
6
6
  column: IColumnDef<T>;
7
7
  rowIndex: number;
8
- editorType: 'text' | 'select' | 'checkbox';
8
+ editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
9
9
  onCommit: (value: unknown) => void;
10
10
  onCancel: () => void;
11
11
  }
@@ -4,5 +4,12 @@ export interface StatusBarProps {
4
4
  filteredCount?: number;
5
5
  selectedCount?: number;
6
6
  selectedCellCount?: number;
7
+ aggregation?: {
8
+ sum: number;
9
+ avg: number;
10
+ min: number;
11
+ max: number;
12
+ count: number;
13
+ } | null;
7
14
  }
8
15
  export declare function StatusBar(props: StatusBarProps): React.ReactElement;
@@ -5,6 +5,7 @@ export interface IPaginationControlsProps {
5
5
  totalCount: number;
6
6
  onPageChange: (page: number) => void;
7
7
  onPageSizeChange: (pageSize: number) => void;
8
+ pageSizeOptions?: number[];
8
9
  entityLabelPlural?: string;
9
10
  className?: string;
10
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "OGrid default (Radix) – Data grid with sorting, filtering, pagination, column chooser, and CSV export. Packed with Radix UI; no Fluent or Material required.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -55,8 +55,7 @@
55
55
  "identity-obj-proxy": "^3.0.0",
56
56
  "ts-jest": "^29.2.5",
57
57
  "sass": "^1.83.4",
58
- "@storybook/react": "^8.5.3",
59
- "@storybook/react-vite": "^8.5.3",
58
+ "@storybook/react-vite": "10.2.8",
60
59
  "@types/react": "^18.3.18",
61
60
  "@types/react-dom": "^18.3.5",
62
61
  "jest": "^29.7.0",
@@ -64,9 +63,10 @@
64
63
  "react": "^18.3.1",
65
64
  "react-dom": "^18.3.1",
66
65
  "rimraf": "^6.0.1",
67
- "storybook": "^8.5.3",
66
+ "storybook": "10.2.8",
68
67
  "typescript": "^5.7.3",
69
- "vite": "^6.1.0"
68
+ "vite": "^7.0.0",
69
+ "eslint-plugin-storybook": "10.2.8"
70
70
  },
71
71
  "publishConfig": {
72
72
  "access": "public"