@alaarab/ogrid-core 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/OGridLayout.js +2 -7
- package/dist/esm/components/SideBar.js +14 -12
- package/dist/esm/hooks/useColumnResize.js +10 -4
- package/dist/esm/hooks/useDataGridState.js +73 -73
- package/dist/esm/hooks/useOGrid.js +15 -117
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/dataGridTypes.js +0 -24
- package/dist/esm/types/index.js +1 -1
- package/dist/esm/utils/clientSideData.js +94 -0
- package/dist/esm/utils/dataGridViewModel.js +9 -14
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/ogridHelpers.js +5 -7
- package/dist/types/components/OGridLayout.d.ts +0 -7
- package/dist/types/components/SideBar.d.ts +3 -7
- package/dist/types/hooks/index.d.ts +1 -1
- package/dist/types/hooks/useDataGridState.d.ts +63 -39
- package/dist/types/hooks/useOGrid.d.ts +0 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/types/dataGridTypes.d.ts +19 -20
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/clientSideData.d.ts +13 -0
- package/dist/types/utils/dataGridViewModel.d.ts +4 -10
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/ogridHelpers.d.ts +1 -1
- package/package.json +1 -1
|
@@ -56,7 +56,6 @@ const gridChildStyle = {
|
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
58
|
* Renders OGrid layout as a unified bordered container:
|
|
59
|
-
* [deprecated title above]
|
|
60
59
|
* ┌────────────────────────────────────┐
|
|
61
60
|
* │ [toolbar strip] │
|
|
62
61
|
* ├────────────────────────────────────┤
|
|
@@ -66,18 +65,14 @@ const gridChildStyle = {
|
|
|
66
65
|
* └────────────────────────────────────┘
|
|
67
66
|
*/
|
|
68
67
|
export function OGridLayout(props) {
|
|
69
|
-
const { containerComponent: Container = 'div', containerProps = {},
|
|
68
|
+
const { containerComponent: Container = 'div', containerProps = {}, className, toolbar, toolbarEnd, children, pagination, sideBar, } = props;
|
|
70
69
|
const hasSideBar = sideBar != null;
|
|
71
70
|
const sideBarPosition = sideBar?.position ?? 'right';
|
|
72
|
-
// Backward compat: columnChooser prop → toolbarEnd
|
|
73
|
-
const toolbarEnd = toolbarEndProp ?? columnChooser;
|
|
74
71
|
const hasToolbar = toolbar != null || toolbarEnd != null;
|
|
75
|
-
// Root styles: flex column, fill parent height, gap for deprecated title spacing
|
|
76
72
|
const rootStyle = {
|
|
77
73
|
display: 'flex',
|
|
78
74
|
flexDirection: 'column',
|
|
79
75
|
height: '100%',
|
|
80
|
-
gap: title != null ? (typeof gap === 'number' ? `${gap}px` : gap) : undefined,
|
|
81
76
|
};
|
|
82
|
-
return (
|
|
77
|
+
return (_jsx(Container, { className: className, style: rootStyle, ...containerProps, children: _jsxs("div", { style: borderedContainerStyle, children: [hasToolbar && (_jsxs("div", { style: toolbarStripStyle, children: [_jsx("div", { style: toolbarSectionStyle, children: toolbar }), _jsx("div", { style: toolbarSectionStyle, children: toolbarEnd })] })), 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 }))] }) }));
|
|
83
78
|
}
|
|
@@ -6,7 +6,7 @@ const PANEL_LABELS = {
|
|
|
6
6
|
filters: 'Filters',
|
|
7
7
|
};
|
|
8
8
|
export function SideBar(props) {
|
|
9
|
-
const { activePanel, onPanelChange, panels, position, columns, visibleColumns, onVisibilityChange, onSetVisibleColumns, filterableColumns,
|
|
9
|
+
const { activePanel, onPanelChange, panels, position, columns, visibleColumns, onVisibilityChange, onSetVisibleColumns, filterableColumns, filters, onFilterChange, filterOptions, } = props;
|
|
10
10
|
const isOpen = activePanel !== null;
|
|
11
11
|
const handleTabClick = (panel) => {
|
|
12
12
|
onPanelChange(activePanel === panel ? null : panel);
|
|
@@ -47,7 +47,7 @@ export function SideBar(props) {
|
|
|
47
47
|
padding: '8px 12px',
|
|
48
48
|
borderBottom: '1px solid var(--ogrid-border, #e0e0e0)',
|
|
49
49
|
fontWeight: 600,
|
|
50
|
-
}, children: [_jsx("span", { children: PANEL_LABELS[activePanel] }), _jsx("button", { onClick: () => onPanelChange(null), style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 16, color: 'var(--ogrid-fg, #242424)' }, "aria-label": "Close panel", children: "\u00D7" })] }), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '8px 12px' }, children: [activePanel === 'columns' && (_jsx(ColumnsPanel, { columns: columns, visibleColumns: visibleColumns, onVisibilityChange: onVisibilityChange, onSetVisibleColumns: onSetVisibleColumns })), activePanel === 'filters' && (_jsx(FiltersPanel, { filterableColumns: filterableColumns,
|
|
50
|
+
}, children: [_jsx("span", { children: PANEL_LABELS[activePanel] }), _jsx("button", { onClick: () => onPanelChange(null), style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 16, color: 'var(--ogrid-fg, #242424)' }, "aria-label": "Close panel", children: "\u00D7" })] }), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '8px 12px' }, children: [activePanel === 'columns' && (_jsx(ColumnsPanel, { columns: columns, visibleColumns: visibleColumns, onVisibilityChange: onVisibilityChange, onSetVisibleColumns: onSetVisibleColumns })), activePanel === 'filters' && (_jsx(FiltersPanel, { filterableColumns: filterableColumns, filters: filters, onFilterChange: onFilterChange, filterOptions: filterOptions }))] })] })) : null;
|
|
51
51
|
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'row', flexShrink: 0 }, role: "complementary", "aria-label": "Side bar", children: [position === 'left' && tabStrip, position === 'left' && panelContent, position === 'right' && panelContent, position === 'right' && tabStrip] }));
|
|
52
52
|
}
|
|
53
53
|
// --- Internal sub-components ---
|
|
@@ -70,28 +70,30 @@ function ColumnsPanel(props) {
|
|
|
70
70
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: 'flex', gap: 8, marginBottom: 8 }, children: [_jsx("button", { onClick: handleSelectAll, disabled: allVisible, style: { flex: 1, cursor: 'pointer', background: 'var(--ogrid-bg-subtle, #f3f2f1)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: 4, padding: '4px 8px' }, children: "Select All" }), _jsx("button", { onClick: handleClearAll, style: { flex: 1, cursor: 'pointer', background: 'var(--ogrid-bg-subtle, #f3f2f1)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: 4, padding: '4px 8px' }, children: "Clear All" })] }), columns.map((col) => (_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '2px 0', cursor: 'pointer' }, children: [_jsx("input", { type: "checkbox", checked: visibleColumns.has(col.columnId), onChange: (e) => onVisibilityChange(col.columnId, e.target.checked), disabled: col.required }), _jsx("span", { children: col.name })] }, col.columnId)))] }));
|
|
71
71
|
}
|
|
72
72
|
function FiltersPanel(props) {
|
|
73
|
-
const { filterableColumns,
|
|
73
|
+
const { filterableColumns, filters, onFilterChange, filterOptions } = props;
|
|
74
74
|
if (filterableColumns.length === 0) {
|
|
75
75
|
return _jsx("div", { style: { color: 'var(--ogrid-muted, #999)', fontStyle: 'italic' }, children: "No filterable columns" });
|
|
76
76
|
}
|
|
77
77
|
return (_jsx(_Fragment, { children: filterableColumns.map((col) => {
|
|
78
78
|
const filterKey = col.filterField;
|
|
79
|
-
return (_jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 4, fontSize: 13 }, children: col.name }), col.filterType === 'text' && (_jsx("input", { type: "text", value:
|
|
79
|
+
return (_jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("div", { style: { fontWeight: 500, marginBottom: 4, fontSize: 13 }, children: col.name }), col.filterType === 'text' && (_jsx("input", { type: "text", value: filters[filterKey]?.type === 'text' ? filters[filterKey].value : '', onChange: (e) => onFilterChange(filterKey, e.target.value ? { type: 'text', value: e.target.value } : undefined), placeholder: `Filter ${col.name}...`, "aria-label": `Filter ${col.name}`, style: { width: '100%', boxSizing: 'border-box', padding: '4px 6px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: 4 } })), col.filterType === 'date' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 }, children: ["From:", _jsx("input", { type: "date", value: filters[filterKey]?.type === 'date' ? (filters[filterKey].value.from ?? '') : '', onChange: (e) => {
|
|
80
80
|
const from = e.target.value || undefined;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const existingValue = filters[filterKey]?.type === 'date' ? filters[filterKey].value : {};
|
|
82
|
+
const to = existingValue.to;
|
|
83
|
+
onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
84
|
+
}, "aria-label": `${col.name} from date`, style: { flex: 1, padding: '2px 4px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: 4 } })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 }, children: ["To:", _jsx("input", { type: "date", value: filters[filterKey]?.type === 'date' ? (filters[filterKey].value.to ?? '') : '', onChange: (e) => {
|
|
84
85
|
const to = e.target.value || undefined;
|
|
85
|
-
const
|
|
86
|
-
|
|
86
|
+
const existingValue = filters[filterKey]?.type === 'date' ? filters[filterKey].value : {};
|
|
87
|
+
const from = existingValue.from;
|
|
88
|
+
onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
87
89
|
}, "aria-label": `${col.name} to date`, style: { flex: 1, padding: '2px 4px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: 4 } })] })] })), col.filterType === 'multiSelect' && (_jsx("div", { style: { maxHeight: 120, overflowY: 'auto' }, role: "group", "aria-label": `${col.name} options`, children: (filterOptions[filterKey] ?? []).map((opt) => {
|
|
88
|
-
const selected =
|
|
90
|
+
const selected = filters[filterKey]?.type === 'multiSelect' ? filters[filterKey].value.includes(opt) : false;
|
|
89
91
|
return (_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, padding: '1px 0', cursor: 'pointer', fontSize: 13 }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: (e) => {
|
|
90
|
-
const current =
|
|
92
|
+
const current = filters[filterKey]?.type === 'multiSelect' ? filters[filterKey].value : [];
|
|
91
93
|
const next = e.target.checked
|
|
92
94
|
? [...current, opt]
|
|
93
95
|
: current.filter((v) => v !== opt);
|
|
94
|
-
|
|
96
|
+
onFilterChange(filterKey, next.length > 0 ? { type: 'multiSelect', value: next } : undefined);
|
|
95
97
|
} }), _jsx("span", { children: opt })] }, opt));
|
|
96
98
|
}) }))] }, col.columnId));
|
|
97
99
|
}) }));
|
|
@@ -8,10 +8,16 @@ export function useColumnResize({ columnSizingOverrides, setColumnSizingOverride
|
|
|
8
8
|
e.stopPropagation();
|
|
9
9
|
const startX = e.clientX;
|
|
10
10
|
const columnId = col.columnId;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Measure the actual rendered width from the DOM. With table-layout: auto,
|
|
12
|
+
// the browser may have auto-sized the column wider than the config values.
|
|
13
|
+
// The resize handle is a direct child of <th>, so parentElement is the header cell.
|
|
14
|
+
const thEl = e.currentTarget.parentElement;
|
|
15
|
+
const startWidth = thEl
|
|
16
|
+
? thEl.getBoundingClientRect().width
|
|
17
|
+
: columnSizingOverrides[columnId]?.widthPx
|
|
18
|
+
?? col.idealWidth
|
|
19
|
+
?? col.defaultWidth
|
|
20
|
+
?? defaultWidth;
|
|
15
21
|
let latestWidth = startWidth;
|
|
16
22
|
// Lock cursor and prevent text selection during drag
|
|
17
23
|
const prevCursor = document.body.style.cursor;
|
|
@@ -209,37 +209,25 @@ export function useDataGridState(params) {
|
|
|
209
209
|
const showEmptyInGrid = items.length === 0 && !!emptyState;
|
|
210
210
|
const hasCellSelection = selectionRange != null || activeCell != null;
|
|
211
211
|
// --- View-model inputs (shared across all 3 DataGridTables) ---
|
|
212
|
-
const { sortBy, sortDirection, onColumnSort,
|
|
212
|
+
const { sortBy, sortDirection, onColumnSort, filters, onFilterChange, filterOptions, loadingFilterOptions, peopleSearch, } = props;
|
|
213
213
|
const headerFilterInput = useMemo(() => ({
|
|
214
214
|
sortBy,
|
|
215
215
|
sortDirection,
|
|
216
216
|
onColumnSort,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
peopleFilters,
|
|
220
|
-
onPeopleFilterChange,
|
|
221
|
-
peopleSearch,
|
|
217
|
+
filters,
|
|
218
|
+
onFilterChange,
|
|
222
219
|
filterOptions,
|
|
223
220
|
loadingFilterOptions,
|
|
224
|
-
|
|
225
|
-
onMultiSelectFilterChange,
|
|
226
|
-
dateFilters,
|
|
227
|
-
onDateFilterChange,
|
|
221
|
+
peopleSearch,
|
|
228
222
|
}), [
|
|
229
223
|
sortBy,
|
|
230
224
|
sortDirection,
|
|
231
225
|
onColumnSort,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
peopleFilters,
|
|
235
|
-
onPeopleFilterChange,
|
|
236
|
-
peopleSearch,
|
|
226
|
+
filters,
|
|
227
|
+
onFilterChange,
|
|
237
228
|
filterOptions,
|
|
238
229
|
loadingFilterOptions,
|
|
239
|
-
|
|
240
|
-
onMultiSelectFilterChange,
|
|
241
|
-
dateFilters,
|
|
242
|
-
onDateFilterChange,
|
|
230
|
+
peopleSearch,
|
|
243
231
|
]);
|
|
244
232
|
const cellDescriptorInput = useMemo(() => ({
|
|
245
233
|
editingCell,
|
|
@@ -305,59 +293,71 @@ export function useDataGridState(params) {
|
|
|
305
293
|
setPendingEditorValue(undefined);
|
|
306
294
|
}, [setEditingCell, setPendingEditorValue]);
|
|
307
295
|
return {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
296
|
+
layout: {
|
|
297
|
+
flatColumns,
|
|
298
|
+
visibleCols,
|
|
299
|
+
visibleColumnCount,
|
|
300
|
+
totalColCount,
|
|
301
|
+
colOffset,
|
|
302
|
+
hasCheckboxCol,
|
|
303
|
+
rowIndexByRowId,
|
|
304
|
+
containerWidth,
|
|
305
|
+
minTableWidth,
|
|
306
|
+
desiredTableWidth,
|
|
307
|
+
columnSizingOverrides,
|
|
308
|
+
setColumnSizingOverrides,
|
|
309
|
+
onColumnResized,
|
|
310
|
+
},
|
|
311
|
+
rowSelection: {
|
|
312
|
+
selectedRowIds,
|
|
313
|
+
updateSelection,
|
|
314
|
+
handleRowCheckboxChange,
|
|
315
|
+
handleSelectAll,
|
|
316
|
+
allSelected,
|
|
317
|
+
someSelected,
|
|
318
|
+
},
|
|
319
|
+
editing: {
|
|
320
|
+
editingCell,
|
|
321
|
+
setEditingCell,
|
|
322
|
+
pendingEditorValue,
|
|
323
|
+
setPendingEditorValue,
|
|
324
|
+
commitCellEdit,
|
|
325
|
+
cancelPopoverEdit,
|
|
326
|
+
popoverAnchorEl,
|
|
327
|
+
setPopoverAnchorEl,
|
|
328
|
+
},
|
|
329
|
+
interaction: {
|
|
330
|
+
activeCell: cellSelection ? activeCell : null,
|
|
331
|
+
setActiveCell: cellSelection ? setActiveCell : NOOP,
|
|
332
|
+
selectionRange: cellSelection ? selectionRange : null,
|
|
333
|
+
setSelectionRange: cellSelection ? setSelectionRange : NOOP,
|
|
334
|
+
handleCellMouseDown: cellSelection ? handleCellMouseDown : NOOP_MOUSE,
|
|
335
|
+
handleSelectAllCells: cellSelection ? handleSelectAllCells : NOOP,
|
|
336
|
+
hasCellSelection: cellSelection ? hasCellSelection : false,
|
|
337
|
+
handleGridKeyDown: cellSelection ? handleGridKeyDown : NOOP_KEY,
|
|
338
|
+
handleFillHandleMouseDown: cellSelection ? handleFillHandleMouseDown : NOOP,
|
|
339
|
+
handleCopy: cellSelection ? handleCopy : NOOP,
|
|
340
|
+
handleCut: cellSelection ? handleCut : NOOP,
|
|
341
|
+
handlePaste: cellSelection ? handlePaste : NOOP_ASYNC,
|
|
342
|
+
cutRange: cellSelection ? cutRange : null,
|
|
343
|
+
copyRange: cellSelection ? copyRange : null,
|
|
344
|
+
clearClipboardRanges: cellSelection ? clearClipboardRanges : NOOP,
|
|
345
|
+
canUndo: undoRedo.canUndo,
|
|
346
|
+
canRedo: undoRedo.canRedo,
|
|
347
|
+
onUndo: undoRedo.undo,
|
|
348
|
+
onRedo: undoRedo.redo,
|
|
349
|
+
},
|
|
350
|
+
contextMenu: {
|
|
351
|
+
menuPosition: cellSelection ? contextMenu : null,
|
|
352
|
+
setMenuPosition: cellSelection ? setContextMenu : NOOP,
|
|
353
|
+
handleCellContextMenu: cellSelection ? handleCellContextMenu : NOOP_CTX,
|
|
354
|
+
closeContextMenu: cellSelection ? closeContextMenu : NOOP,
|
|
355
|
+
},
|
|
356
|
+
viewModels: {
|
|
357
|
+
headerFilterInput,
|
|
358
|
+
cellDescriptorInput,
|
|
359
|
+
statusBarConfig,
|
|
360
|
+
showEmptyInGrid,
|
|
361
|
+
},
|
|
362
362
|
};
|
|
363
363
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { useMemo, useCallback, useState, useEffect, useRef, useImperativeHandle, } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { toDataGridFilterProps } from '../types';
|
|
2
|
+
import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from '../utils/ogridHelpers';
|
|
3
|
+
import { flattenColumns, processClientSideData } from '../utils';
|
|
5
4
|
import { useFilterOptions } from './useFilterOptions';
|
|
6
5
|
import { useSideBarState } from './useSideBarState';
|
|
7
6
|
const DEFAULT_PAGE_SIZE = 25;
|
|
8
7
|
export function useOGrid(props, ref) {
|
|
9
|
-
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, emptyState, entityLabelPlural = 'items', className,
|
|
8
|
+
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, 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;
|
|
10
9
|
// Resolve column chooser placement
|
|
11
10
|
const columnChooserPlacement = columnChooserProp === false ? 'none'
|
|
12
11
|
: columnChooserProp === 'sidebar' ? 'sidebar'
|
|
@@ -70,23 +69,14 @@ export function useOGrid(props, ref) {
|
|
|
70
69
|
setInternalVisibleColumns(cols);
|
|
71
70
|
onVisibleColumnsChange?.(cols);
|
|
72
71
|
}, [controlledVisibleColumns, onVisibleColumnsChange]);
|
|
73
|
-
const { multiSelectFilters, textFilters, peopleFilters, dateFilters } = useMemo(() => toDataGridFilterProps(filters), [filters]);
|
|
74
72
|
const handleSort = useCallback((columnKey) => {
|
|
75
73
|
setSort({
|
|
76
74
|
field: columnKey,
|
|
77
75
|
direction: sort.field === columnKey && sort.direction === 'asc' ? 'desc' : 'asc',
|
|
78
76
|
});
|
|
79
77
|
}, [sort, setSort]);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}, [filters, setFilters]);
|
|
83
|
-
const handleTextFilterChange = useCallback((key, value) => {
|
|
84
|
-
setFilters(mergeFilter(filters, key, value.trim() || undefined));
|
|
85
|
-
}, [filters, setFilters]);
|
|
86
|
-
const handlePeopleFilterChange = useCallback((key, user) => {
|
|
87
|
-
setFilters(mergeFilter(filters, key, user ?? undefined));
|
|
88
|
-
}, [filters, setFilters]);
|
|
89
|
-
const handleDateFilterChange = useCallback((key, value) => {
|
|
78
|
+
/** Single filter change handler — wraps discriminated FilterValue into mergeFilter. */
|
|
79
|
+
const handleFilterChange = useCallback((key, value) => {
|
|
90
80
|
setFilters(mergeFilter(filters, key, value));
|
|
91
81
|
}, [filters, setFilters]);
|
|
92
82
|
const handleVisibilityChange = useCallback((columnKey, isVisible) => {
|
|
@@ -113,88 +103,11 @@ export function useOGrid(props, ref) {
|
|
|
113
103
|
return serverFilterOptions;
|
|
114
104
|
return deriveFilterOptionsFromData(displayData, columns);
|
|
115
105
|
}, [dataSource, displayData, columns, serverFilterOptions]);
|
|
106
|
+
// --- Client-side filtering & sorting ---
|
|
116
107
|
const clientItemsAndTotal = useMemo(() => {
|
|
117
108
|
if (!isClientSide)
|
|
118
109
|
return null;
|
|
119
|
-
|
|
120
|
-
columns.forEach((col) => {
|
|
121
|
-
const filterKey = getFilterField(col);
|
|
122
|
-
const f = col.filterable && typeof col.filterable === 'object'
|
|
123
|
-
? col.filterable
|
|
124
|
-
: null;
|
|
125
|
-
const type = f?.type;
|
|
126
|
-
const val = filters[filterKey];
|
|
127
|
-
if (type === 'multiSelect' && Array.isArray(val) && val.length > 0) {
|
|
128
|
-
rows = rows.filter((r) => val.includes(String(getCellValue(r, col))));
|
|
129
|
-
}
|
|
130
|
-
else if (type === 'text' &&
|
|
131
|
-
typeof val === 'string' &&
|
|
132
|
-
val.trim()) {
|
|
133
|
-
const lower = val.trim().toLowerCase();
|
|
134
|
-
rows = rows.filter((r) => String(getCellValue(r, col) ?? '').toLowerCase().includes(lower));
|
|
135
|
-
}
|
|
136
|
-
else if (type === 'people' &&
|
|
137
|
-
val &&
|
|
138
|
-
typeof val === 'object' &&
|
|
139
|
-
'email' in val) {
|
|
140
|
-
const email = val.email.toLowerCase();
|
|
141
|
-
rows = rows.filter((r) => String(getCellValue(r, col) ?? '').toLowerCase() === email);
|
|
142
|
-
}
|
|
143
|
-
else if (type === 'date' &&
|
|
144
|
-
val &&
|
|
145
|
-
typeof val === 'object' &&
|
|
146
|
-
!Array.isArray(val) &&
|
|
147
|
-
('from' in val || 'to' in val)) {
|
|
148
|
-
const dv = val;
|
|
149
|
-
rows = rows.filter((r) => {
|
|
150
|
-
const cellVal = getCellValue(r, col);
|
|
151
|
-
if (cellVal == null)
|
|
152
|
-
return false;
|
|
153
|
-
const cellDate = new Date(String(cellVal));
|
|
154
|
-
if (Number.isNaN(cellDate.getTime()))
|
|
155
|
-
return false;
|
|
156
|
-
const cellDateStr = cellDate.toISOString().split('T')[0];
|
|
157
|
-
if (dv.from && cellDateStr < dv.from)
|
|
158
|
-
return false;
|
|
159
|
-
if (dv.to && cellDateStr > dv.to)
|
|
160
|
-
return false;
|
|
161
|
-
return true;
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
if (sort.field) {
|
|
166
|
-
const sortCol = columns.find((c) => c.columnId === sort.field);
|
|
167
|
-
const compare = sortCol?.compare;
|
|
168
|
-
const dir = sort.direction === 'asc' ? 1 : -1;
|
|
169
|
-
rows.sort((a, b) => {
|
|
170
|
-
if (compare)
|
|
171
|
-
return compare(a, b) * dir;
|
|
172
|
-
const av = sortCol
|
|
173
|
-
? getCellValue(a, sortCol)
|
|
174
|
-
: a[sort.field];
|
|
175
|
-
const bv = sortCol
|
|
176
|
-
? getCellValue(b, sortCol)
|
|
177
|
-
: b[sort.field];
|
|
178
|
-
if (av == null && bv == null)
|
|
179
|
-
return 0;
|
|
180
|
-
if (av == null)
|
|
181
|
-
return -1 * dir;
|
|
182
|
-
if (bv == null)
|
|
183
|
-
return 1 * dir;
|
|
184
|
-
if (sortCol?.type === 'date') {
|
|
185
|
-
const at = new Date(String(av)).getTime();
|
|
186
|
-
const bt = new Date(String(bv)).getTime();
|
|
187
|
-
const aN = Number.isNaN(at) ? 0 : at;
|
|
188
|
-
const bN = Number.isNaN(bt) ? 0 : bt;
|
|
189
|
-
return aN === bN ? 0 : aN > bN ? dir : -dir;
|
|
190
|
-
}
|
|
191
|
-
if (typeof av === 'number' && typeof bv === 'number')
|
|
192
|
-
return av === bv ? 0 : av > bv ? dir : -dir;
|
|
193
|
-
const as = String(av).toLowerCase();
|
|
194
|
-
const bs = String(bv).toLowerCase();
|
|
195
|
-
return as === bs ? 0 : as > bs ? dir : -dir;
|
|
196
|
-
});
|
|
197
|
-
}
|
|
110
|
+
const rows = processClientSideData(displayData, columns, filters, sort.field, sort.direction);
|
|
198
111
|
const total = rows.length;
|
|
199
112
|
const start = (page - 1) * pageSize;
|
|
200
113
|
const paged = rows.slice(start, start + pageSize);
|
|
@@ -344,9 +257,9 @@ export function useOGrid(props, ref) {
|
|
|
344
257
|
getRowId,
|
|
345
258
|
onSelectionChange,
|
|
346
259
|
]);
|
|
260
|
+
// With discriminated union, any defined value is active (mergeFilter already strips empties)
|
|
347
261
|
const hasActiveFilters = useMemo(() => {
|
|
348
|
-
return Object.values(filters).some((v) => v !== undefined
|
|
349
|
-
(Array.isArray(v) ? v.length > 0 : typeof v === 'string' ? v.trim() !== '' : true));
|
|
262
|
+
return Object.values(filters).some((v) => v !== undefined);
|
|
350
263
|
}, [filters]);
|
|
351
264
|
const columnChooserColumns = useMemo(() => columns.map((c) => ({
|
|
352
265
|
columnId: c.columnId,
|
|
@@ -413,12 +326,8 @@ export function useOGrid(props, ref) {
|
|
|
413
326
|
onVisibilityChange: handleVisibilityChange,
|
|
414
327
|
onSetVisibleColumns: setVisibleColumns,
|
|
415
328
|
filterableColumns,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
onMultiSelectFilterChange: handleMultiSelectFilterChange,
|
|
419
|
-
onTextFilterChange: handleTextFilterChange,
|
|
420
|
-
dateFilters,
|
|
421
|
-
onDateFilterChange: handleDateFilterChange,
|
|
329
|
+
filters,
|
|
330
|
+
onFilterChange: handleFilterChange,
|
|
422
331
|
filterOptions: clientFilterOptions,
|
|
423
332
|
};
|
|
424
333
|
}, [
|
|
@@ -432,12 +341,8 @@ export function useOGrid(props, ref) {
|
|
|
432
341
|
handleVisibilityChange,
|
|
433
342
|
setVisibleColumns,
|
|
434
343
|
filterableColumns,
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
handleMultiSelectFilterChange,
|
|
438
|
-
handleTextFilterChange,
|
|
439
|
-
dateFilters,
|
|
440
|
-
handleDateFilterChange,
|
|
344
|
+
filters,
|
|
345
|
+
handleFilterChange,
|
|
441
346
|
clientFilterOptions,
|
|
442
347
|
]);
|
|
443
348
|
const dataGridProps = {
|
|
@@ -468,14 +373,8 @@ export function useOGrid(props, ref) {
|
|
|
468
373
|
onSelectionChange: handleSelectionChange,
|
|
469
374
|
statusBar: statusBarConfig,
|
|
470
375
|
isLoading: (isServerSide && loading) || displayLoading,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
textFilters,
|
|
474
|
-
onTextFilterChange: handleTextFilterChange,
|
|
475
|
-
peopleFilters,
|
|
476
|
-
onPeopleFilterChange: handlePeopleFilterChange,
|
|
477
|
-
dateFilters,
|
|
478
|
-
onDateFilterChange: handleDateFilterChange,
|
|
376
|
+
filters,
|
|
377
|
+
onFilterChange: handleFilterChange,
|
|
479
378
|
filterOptions: clientFilterOptions,
|
|
480
379
|
loadingFilterOptions: dataSource?.fetchFilterOptions ? loadingFilterOptions : {},
|
|
481
380
|
peopleSearch: dataSource?.searchPeople,
|
|
@@ -502,7 +401,6 @@ export function useOGrid(props, ref) {
|
|
|
502
401
|
visibleColumns,
|
|
503
402
|
handleVisibilityChange,
|
|
504
403
|
columnChooserPlacement,
|
|
505
|
-
title,
|
|
506
404
|
toolbar,
|
|
507
405
|
className,
|
|
508
406
|
entityLabelPlural,
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { toUserLike,
|
|
1
|
+
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
|
|
2
2
|
// Hooks
|
|
3
3
|
export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, } from './hooks';
|
|
4
4
|
// Components
|
|
@@ -8,4 +8,4 @@ export { GridContextMenu } from './components/GridContextMenu';
|
|
|
8
8
|
export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
|
|
9
9
|
export { SideBar } from './components/SideBar';
|
|
10
10
|
// Utilities
|
|
11
|
-
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, } from './utils';
|
|
11
|
+
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
|
|
@@ -8,30 +8,6 @@ export function toUserLike(u) {
|
|
|
8
8
|
photo: u.photo
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
/** Type guard for IDateFilterValue. */
|
|
12
|
-
function isDateFilterValue(value) {
|
|
13
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value) && !('email' in value) && ('from' in value || 'to' in value);
|
|
14
|
-
}
|
|
15
|
-
/** Split IFilters into DataGridTable's multiSelect, text, people, and date props. */
|
|
16
|
-
export function toDataGridFilterProps(filters) {
|
|
17
|
-
const multiSelectFilters = {};
|
|
18
|
-
const textFilters = {};
|
|
19
|
-
const peopleFilters = {};
|
|
20
|
-
const dateFilters = {};
|
|
21
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
22
|
-
if (value === undefined)
|
|
23
|
-
continue;
|
|
24
|
-
if (Array.isArray(value))
|
|
25
|
-
multiSelectFilters[key] = value;
|
|
26
|
-
else if (typeof value === 'string')
|
|
27
|
-
textFilters[key] = value;
|
|
28
|
-
else if (typeof value === 'object' && value !== null && 'email' in value)
|
|
29
|
-
peopleFilters[key] = value;
|
|
30
|
-
else if (isDateFilterValue(value))
|
|
31
|
-
dateFilters[key] = value;
|
|
32
|
-
}
|
|
33
|
-
return { multiSelectFilters, textFilters, peopleFilters, dateFilters };
|
|
34
|
-
}
|
|
35
11
|
/** Returns true if (row, col) is inside the range (inclusive). */
|
|
36
12
|
export function isInSelectionRange(range, row, col) {
|
|
37
13
|
const minR = Math.min(range.startRow, range.endRow);
|
package/dist/esm/types/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { toUserLike,
|
|
1
|
+
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getCellValue } from './cellValue';
|
|
2
|
+
import { getFilterField } from './ogridHelpers';
|
|
3
|
+
/**
|
|
4
|
+
* Apply client-side filtering and sorting to data.
|
|
5
|
+
* Extracted from useOGrid for testability and reuse.
|
|
6
|
+
*
|
|
7
|
+
* @param data - The full dataset to process
|
|
8
|
+
* @param columns - Column definitions (used for filtering and sorting)
|
|
9
|
+
* @param filters - Current filter state (discriminated FilterValue union)
|
|
10
|
+
* @param sortBy - Column ID to sort by (optional)
|
|
11
|
+
* @param sortDirection - Sort direction (optional)
|
|
12
|
+
* @returns Filtered and sorted array
|
|
13
|
+
*/
|
|
14
|
+
export function processClientSideData(data, columns, filters, sortBy, sortDirection) {
|
|
15
|
+
let rows = data.slice();
|
|
16
|
+
// --- Filtering ---
|
|
17
|
+
columns.forEach((col) => {
|
|
18
|
+
const filterKey = getFilterField(col);
|
|
19
|
+
const val = filters[filterKey];
|
|
20
|
+
if (!val)
|
|
21
|
+
return;
|
|
22
|
+
switch (val.type) {
|
|
23
|
+
case 'multiSelect':
|
|
24
|
+
if (val.value.length > 0) {
|
|
25
|
+
rows = rows.filter((r) => val.value.includes(String(getCellValue(r, col))));
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
case 'text':
|
|
29
|
+
if (val.value.trim()) {
|
|
30
|
+
const lower = val.value.trim().toLowerCase();
|
|
31
|
+
rows = rows.filter((r) => String(getCellValue(r, col) ?? '').toLowerCase().includes(lower));
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
case 'people': {
|
|
35
|
+
const email = val.value.email.toLowerCase();
|
|
36
|
+
rows = rows.filter((r) => String(getCellValue(r, col) ?? '').toLowerCase() === email);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'date': {
|
|
40
|
+
const dv = val.value;
|
|
41
|
+
rows = rows.filter((r) => {
|
|
42
|
+
const cellVal = getCellValue(r, col);
|
|
43
|
+
if (cellVal == null)
|
|
44
|
+
return false;
|
|
45
|
+
const cellDate = new Date(String(cellVal));
|
|
46
|
+
if (Number.isNaN(cellDate.getTime()))
|
|
47
|
+
return false;
|
|
48
|
+
const cellDateStr = cellDate.toISOString().split('T')[0];
|
|
49
|
+
if (dv.from && cellDateStr < dv.from)
|
|
50
|
+
return false;
|
|
51
|
+
if (dv.to && cellDateStr > dv.to)
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// --- Sorting ---
|
|
60
|
+
if (sortBy) {
|
|
61
|
+
const sortCol = columns.find((c) => c.columnId === sortBy);
|
|
62
|
+
const compare = sortCol?.compare;
|
|
63
|
+
const dir = sortDirection === 'asc' ? 1 : -1;
|
|
64
|
+
rows.sort((a, b) => {
|
|
65
|
+
if (compare)
|
|
66
|
+
return compare(a, b) * dir;
|
|
67
|
+
const av = sortCol
|
|
68
|
+
? getCellValue(a, sortCol)
|
|
69
|
+
: a[sortBy];
|
|
70
|
+
const bv = sortCol
|
|
71
|
+
? getCellValue(b, sortCol)
|
|
72
|
+
: b[sortBy];
|
|
73
|
+
if (av == null && bv == null)
|
|
74
|
+
return 0;
|
|
75
|
+
if (av == null)
|
|
76
|
+
return -1 * dir;
|
|
77
|
+
if (bv == null)
|
|
78
|
+
return 1 * dir;
|
|
79
|
+
if (sortCol?.type === 'date') {
|
|
80
|
+
const at = new Date(String(av)).getTime();
|
|
81
|
+
const bt = new Date(String(bv)).getTime();
|
|
82
|
+
const aN = Number.isNaN(at) ? 0 : at;
|
|
83
|
+
const bN = Number.isNaN(bt) ? 0 : bt;
|
|
84
|
+
return aN === bN ? 0 : aN > bN ? dir : -dir;
|
|
85
|
+
}
|
|
86
|
+
if (typeof av === 'number' && typeof bv === 'number')
|
|
87
|
+
return av === bv ? 0 : av > bv ? dir : -dir;
|
|
88
|
+
const as = String(av).toLowerCase();
|
|
89
|
+
const bs = String(bv).toLowerCase();
|
|
90
|
+
return as === bs ? 0 : as > bs ? dir : -dir;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return rows;
|
|
94
|
+
}
|
|
@@ -12,6 +12,7 @@ export function getHeaderFilterConfig(col, input) {
|
|
|
12
12
|
const filterType = (filterable?.type ?? 'none');
|
|
13
13
|
const filterField = filterable?.filterField ?? col.columnId;
|
|
14
14
|
const sortable = col.sortable !== false;
|
|
15
|
+
const filterValue = input.filters[filterField];
|
|
15
16
|
const base = {
|
|
16
17
|
columnKey: col.columnId,
|
|
17
18
|
columnName: col.name,
|
|
@@ -23,19 +24,15 @@ export function getHeaderFilterConfig(col, input) {
|
|
|
23
24
|
if (filterType === 'text') {
|
|
24
25
|
return {
|
|
25
26
|
...base,
|
|
26
|
-
textValue:
|
|
27
|
-
onTextChange: input.
|
|
28
|
-
? (v) => input.onTextFilterChange(filterField, v)
|
|
29
|
-
: undefined,
|
|
27
|
+
textValue: filterValue?.type === 'text' ? filterValue.value : '',
|
|
28
|
+
onTextChange: (v) => input.onFilterChange(filterField, v.trim() ? { type: 'text', value: v } : undefined),
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
31
|
if (filterType === 'people') {
|
|
33
32
|
return {
|
|
34
33
|
...base,
|
|
35
|
-
selectedUser:
|
|
36
|
-
onUserChange: input.
|
|
37
|
-
? (u) => input.onPeopleFilterChange(filterField, u)
|
|
38
|
-
: undefined,
|
|
34
|
+
selectedUser: filterValue?.type === 'people' ? filterValue.value : undefined,
|
|
35
|
+
onUserChange: (u) => input.onFilterChange(filterField, u ? { type: 'people', value: u } : undefined),
|
|
39
36
|
peopleSearch: input.peopleSearch,
|
|
40
37
|
};
|
|
41
38
|
}
|
|
@@ -44,17 +41,15 @@ export function getHeaderFilterConfig(col, input) {
|
|
|
44
41
|
...base,
|
|
45
42
|
options: input.filterOptions[filterField] ?? [],
|
|
46
43
|
isLoadingOptions: input.loadingFilterOptions[filterField] ?? false,
|
|
47
|
-
selectedValues:
|
|
48
|
-
onFilterChange: (values) => input.
|
|
44
|
+
selectedValues: filterValue?.type === 'multiSelect' ? filterValue.value : [],
|
|
45
|
+
onFilterChange: (values) => input.onFilterChange(filterField, values.length ? { type: 'multiSelect', value: values } : undefined),
|
|
49
46
|
};
|
|
50
47
|
}
|
|
51
48
|
if (filterType === 'date') {
|
|
52
49
|
return {
|
|
53
50
|
...base,
|
|
54
|
-
dateValue:
|
|
55
|
-
onDateChange: input.
|
|
56
|
-
? (v) => input.onDateFilterChange(filterField, v)
|
|
57
|
-
: undefined,
|
|
51
|
+
dateValue: filterValue?.type === 'date' ? filterValue.value : undefined,
|
|
52
|
+
onDateChange: (v) => input.onFilterChange(filterField, v ? { type: 'date', value: v } : undefined),
|
|
58
53
|
};
|
|
59
54
|
}
|
|
60
55
|
return base;
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from
|
|
|
9
9
|
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
|
|
10
10
|
export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './valueParsers';
|
|
11
11
|
export { computeAggregations } from './aggregationUtils';
|
|
12
|
+
export { processClientSideData } from './clientSideData';
|
|
@@ -4,16 +4,14 @@ export function getFilterField(col) {
|
|
|
4
4
|
const f = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
|
|
5
5
|
return (f?.filterField ?? col.columnId);
|
|
6
6
|
}
|
|
7
|
-
/** Merge a single filter change into a full IFilters object. */
|
|
7
|
+
/** Merge a single filter change into a full IFilters object. Strips empty values automatically. */
|
|
8
8
|
export function mergeFilter(prev, key, value) {
|
|
9
9
|
const next = { ...prev };
|
|
10
10
|
const isEmpty = value === undefined ||
|
|
11
|
-
(
|
|
12
|
-
(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
!(value.from || value.to);
|
|
16
|
-
if (isEmpty || isEmptyDate) {
|
|
11
|
+
(value.type === 'text' && value.value.trim() === '') ||
|
|
12
|
+
(value.type === 'multiSelect' && value.value.length === 0) ||
|
|
13
|
+
(value.type === 'date' && !value.value.from && !value.value.to);
|
|
14
|
+
if (isEmpty) {
|
|
17
15
|
delete next[key];
|
|
18
16
|
}
|
|
19
17
|
else {
|
|
@@ -10,15 +10,9 @@ export interface OGridLayoutProps {
|
|
|
10
10
|
containerComponent?: React.ElementType;
|
|
11
11
|
/** Extra props for the root container (e.g. sx for MUI Box). */
|
|
12
12
|
containerProps?: Record<string, unknown>;
|
|
13
|
-
/** Gap between deprecated title and the bordered container in px (default: 8). */
|
|
14
|
-
gap?: number | string;
|
|
15
13
|
className?: string;
|
|
16
|
-
/** @deprecated Render title outside OGrid. Renders above the bordered container during transition. */
|
|
17
|
-
title?: React.ReactNode;
|
|
18
14
|
/** Custom toolbar content (left-aligned in toolbar strip). */
|
|
19
15
|
toolbar?: React.ReactNode;
|
|
20
|
-
/** @deprecated Use toolbarEnd instead. */
|
|
21
|
-
columnChooser?: React.ReactNode;
|
|
22
16
|
/** Built-in toolbar items rendered on the right side (column chooser, etc.). */
|
|
23
17
|
toolbarEnd?: React.ReactNode;
|
|
24
18
|
/** Grid content (DataGridTable). */
|
|
@@ -30,7 +24,6 @@ export interface OGridLayoutProps {
|
|
|
30
24
|
}
|
|
31
25
|
/**
|
|
32
26
|
* Renders OGrid layout as a unified bordered container:
|
|
33
|
-
* [deprecated title above]
|
|
34
27
|
* ┌────────────────────────────────────┐
|
|
35
28
|
* │ [toolbar strip] │
|
|
36
29
|
* ├────────────────────────────────────┤
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses inline styles for framework-agnostic rendering.
|
|
5
5
|
*/
|
|
6
6
|
import * as React from 'react';
|
|
7
|
-
import type { IColumnDefinition,
|
|
7
|
+
import type { IColumnDefinition, SideBarPanelId, IFilters, FilterValue } from '../types';
|
|
8
8
|
/** Describes a filterable column for the sidebar filters panel. */
|
|
9
9
|
export interface SideBarFilterColumn {
|
|
10
10
|
columnId: string;
|
|
@@ -23,12 +23,8 @@ export interface SideBarProps {
|
|
|
23
23
|
/** Batch-set all visible columns at once (used by Select All / Clear All). */
|
|
24
24
|
onSetVisibleColumns: (columns: Set<string>) => void;
|
|
25
25
|
filterableColumns: SideBarFilterColumn[];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
onMultiSelectFilterChange: (key: string, values: string[]) => void;
|
|
29
|
-
onTextFilterChange: (key: string, value: string) => void;
|
|
30
|
-
dateFilters: Record<string, IDateFilterValue>;
|
|
31
|
-
onDateFilterChange: (key: string, value: IDateFilterValue | undefined) => void;
|
|
26
|
+
filters: IFilters;
|
|
27
|
+
onFilterChange: (key: string, value: FilterValue | undefined) => void;
|
|
32
28
|
filterOptions: Record<string, string[]>;
|
|
33
29
|
}
|
|
34
30
|
export declare function SideBar(props: SideBarProps): React.ReactElement;
|
|
@@ -22,7 +22,7 @@ export { useDebounce } from './useDebounce';
|
|
|
22
22
|
export { useFillHandle } from './useFillHandle';
|
|
23
23
|
export type { UseFillHandleResult, UseFillHandleParams } from './useFillHandle';
|
|
24
24
|
export { useDataGridState } from './useDataGridState';
|
|
25
|
-
export type { UseDataGridStateParams, UseDataGridStateResult } from './useDataGridState';
|
|
25
|
+
export type { UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, } from './useDataGridState';
|
|
26
26
|
export { useColumnHeaderFilterState } from './useColumnHeaderFilterState';
|
|
27
27
|
export type { UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, } from './useColumnHeaderFilterState';
|
|
28
28
|
export { useColumnChooserState } from './useColumnChooserState';
|
|
@@ -5,7 +5,8 @@ export interface UseDataGridStateParams<T> {
|
|
|
5
5
|
props: IOGridDataGridProps<T>;
|
|
6
6
|
wrapperRef: RefObject<HTMLDivElement | null>;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
/** Column layout, visibility, and sizing state. */
|
|
9
|
+
export interface DataGridLayoutState<T> {
|
|
9
10
|
flatColumns: IColumnDef<T>[];
|
|
10
11
|
visibleCols: IColumnDef<T>[];
|
|
11
12
|
visibleColumnCount: number;
|
|
@@ -13,12 +14,28 @@ export interface UseDataGridStateResult<T> {
|
|
|
13
14
|
colOffset: number;
|
|
14
15
|
hasCheckboxCol: boolean;
|
|
15
16
|
rowIndexByRowId: Map<RowId, number>;
|
|
17
|
+
containerWidth: number;
|
|
18
|
+
minTableWidth: number;
|
|
19
|
+
desiredTableWidth: number;
|
|
20
|
+
columnSizingOverrides: Record<string, {
|
|
21
|
+
widthPx: number;
|
|
22
|
+
}>;
|
|
23
|
+
setColumnSizingOverrides: React.Dispatch<React.SetStateAction<Record<string, {
|
|
24
|
+
widthPx: number;
|
|
25
|
+
}>>>;
|
|
26
|
+
onColumnResized?: (columnId: string, width: number) => void;
|
|
27
|
+
}
|
|
28
|
+
/** Row selection (checkboxes, single-row click). */
|
|
29
|
+
export interface DataGridRowSelectionState {
|
|
16
30
|
selectedRowIds: Set<RowId>;
|
|
17
31
|
updateSelection: (newSelectedIds: Set<RowId>) => void;
|
|
18
32
|
handleRowCheckboxChange: (rowId: RowId, checked: boolean, rowIndex: number, shiftKey: boolean) => void;
|
|
19
33
|
handleSelectAll: (checked: boolean) => void;
|
|
20
34
|
allSelected: boolean;
|
|
21
35
|
someSelected: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Cell editing, popover editor, and commit/cancel helpers. */
|
|
38
|
+
export interface DataGridEditingState<T> {
|
|
22
39
|
editingCell: {
|
|
23
40
|
rowId: RowId;
|
|
24
41
|
columnId: string;
|
|
@@ -29,6 +46,13 @@ export interface UseDataGridStateResult<T> {
|
|
|
29
46
|
} | null) => void;
|
|
30
47
|
pendingEditorValue: unknown;
|
|
31
48
|
setPendingEditorValue: (value: unknown) => void;
|
|
49
|
+
commitCellEdit: (item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number) => void;
|
|
50
|
+
cancelPopoverEdit: () => void;
|
|
51
|
+
popoverAnchorEl: HTMLElement | null;
|
|
52
|
+
setPopoverAnchorEl: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
|
53
|
+
}
|
|
54
|
+
/** Cell selection, active cell, keyboard, clipboard, fill handle, undo/redo. */
|
|
55
|
+
export interface DataGridCellInteractionState {
|
|
32
56
|
activeCell: {
|
|
33
57
|
rowIndex: number;
|
|
34
58
|
columnIndex: number;
|
|
@@ -43,27 +67,12 @@ export interface UseDataGridStateResult<T> {
|
|
|
43
67
|
endRow: number;
|
|
44
68
|
endCol: number;
|
|
45
69
|
} | null;
|
|
46
|
-
setSelectionRange: (range:
|
|
70
|
+
setSelectionRange: (range: DataGridCellInteractionState['selectionRange']) => void;
|
|
47
71
|
handleCellMouseDown: (e: React.MouseEvent, rowIndex: number, globalColIndex: number) => void;
|
|
48
72
|
handleSelectAllCells: () => void;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} | null;
|
|
53
|
-
setContextMenu: (pos: {
|
|
54
|
-
x: number;
|
|
55
|
-
y: number;
|
|
56
|
-
} | null) => void;
|
|
57
|
-
handleCellContextMenu: (e: {
|
|
58
|
-
clientX: number;
|
|
59
|
-
clientY: number;
|
|
60
|
-
preventDefault?: () => void;
|
|
61
|
-
}) => void;
|
|
62
|
-
closeContextMenu: () => void;
|
|
63
|
-
canUndo: boolean;
|
|
64
|
-
canRedo: boolean;
|
|
65
|
-
onUndo?: () => void;
|
|
66
|
-
onRedo?: () => void;
|
|
73
|
+
hasCellSelection: boolean;
|
|
74
|
+
handleGridKeyDown: (e: React.KeyboardEvent) => void;
|
|
75
|
+
handleFillHandleMouseDown: (e: React.MouseEvent) => void;
|
|
67
76
|
handleCopy: () => void;
|
|
68
77
|
handleCut: () => void;
|
|
69
78
|
handlePaste: () => Promise<void>;
|
|
@@ -79,29 +88,44 @@ export interface UseDataGridStateResult<T> {
|
|
|
79
88
|
endRow: number;
|
|
80
89
|
endCol: number;
|
|
81
90
|
} | null;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
clearClipboardRanges: () => void;
|
|
92
|
+
canUndo: boolean;
|
|
93
|
+
canRedo: boolean;
|
|
94
|
+
onUndo?: () => void;
|
|
95
|
+
onRedo?: () => void;
|
|
96
|
+
}
|
|
97
|
+
/** Context menu position and handlers. */
|
|
98
|
+
export interface DataGridContextMenuState {
|
|
99
|
+
menuPosition: {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
} | null;
|
|
103
|
+
setMenuPosition: (pos: {
|
|
104
|
+
x: number;
|
|
105
|
+
y: number;
|
|
106
|
+
} | null) => void;
|
|
107
|
+
handleCellContextMenu: (e: {
|
|
108
|
+
clientX: number;
|
|
109
|
+
clientY: number;
|
|
110
|
+
preventDefault?: () => void;
|
|
111
|
+
}) => void;
|
|
112
|
+
closeContextMenu: () => void;
|
|
113
|
+
}
|
|
114
|
+
/** View model inputs and derived display state. */
|
|
115
|
+
export interface DataGridViewModelState<T> {
|
|
95
116
|
headerFilterInput: HeaderFilterConfigInput;
|
|
96
117
|
cellDescriptorInput: CellRenderDescriptorInput<T>;
|
|
97
|
-
commitCellEdit: (item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number) => void;
|
|
98
|
-
cancelPopoverEdit: () => void;
|
|
99
|
-
popoverAnchorEl: HTMLElement | null;
|
|
100
|
-
setPopoverAnchorEl: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
|
101
|
-
clearClipboardRanges: () => void;
|
|
102
118
|
statusBarConfig: IStatusBarProps | null;
|
|
103
119
|
showEmptyInGrid: boolean;
|
|
104
|
-
|
|
120
|
+
}
|
|
121
|
+
/** Grouped result from useDataGridState. */
|
|
122
|
+
export interface UseDataGridStateResult<T> {
|
|
123
|
+
layout: DataGridLayoutState<T>;
|
|
124
|
+
rowSelection: DataGridRowSelectionState;
|
|
125
|
+
editing: DataGridEditingState<T>;
|
|
126
|
+
interaction: DataGridCellInteractionState;
|
|
127
|
+
contextMenu: DataGridContextMenuState;
|
|
128
|
+
viewModels: DataGridViewModelState<T>;
|
|
105
129
|
}
|
|
106
130
|
/**
|
|
107
131
|
* Single orchestration hook for DataGridTable. Takes grid props and wrapper ref,
|
|
@@ -15,7 +15,6 @@ export interface UseOGridResult<T> {
|
|
|
15
15
|
handleVisibilityChange: (columnKey: string, isVisible: boolean) => void;
|
|
16
16
|
/** Resolved placement of the column chooser. */
|
|
17
17
|
columnChooserPlacement: ColumnChooserPlacement;
|
|
18
|
-
title: React.ReactNode;
|
|
19
18
|
toolbar: React.ReactNode;
|
|
20
19
|
className?: string;
|
|
21
20
|
entityLabelPlural: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, HeaderCell, HeaderRow, SideBarPanelId, ISideBarDef, IDateFilterValue, } from './types';
|
|
2
|
-
export { toUserLike,
|
|
1
|
+
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 { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
|
|
3
3
|
export { useFilterOptions, useOGrid, useActiveCell, useCellEditing, useContextMenu, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useUndoRedo, useDebounce, useFillHandle, useDataGridState, useColumnHeaderFilterState, useColumnChooserState, useInlineCellEditorState, useColumnResize, useRichSelectState, useSideBarState, } from './hooks';
|
|
4
|
-
export type { UseFilterOptionsResult, UseOGridResult, ColumnChooserPlacement, UseActiveCellResult, UseCellEditingResult, EditingCell, UseContextMenuResult, ContextMenuPosition, UseCellSelectionResult, UseCellSelectionParams, UseClipboardResult, UseClipboardParams, UseRowSelectionResult, UseRowSelectionParams, UseKeyboardNavigationResult, UseKeyboardNavigationParams, UseUndoRedoResult, UseUndoRedoParams, UseFillHandleResult, UseFillHandleParams, UseDataGridStateParams, UseDataGridStateResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, InlineCellEditorType, UseColumnResizeParams, UseColumnResizeResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, } from './hooks';
|
|
4
|
+
export type { UseFilterOptionsResult, UseOGridResult, 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, UseColumnChooserStateParams, UseColumnChooserStateResult, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, InlineCellEditorType, UseColumnResizeParams, UseColumnResizeResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, } from './hooks';
|
|
5
5
|
export { OGridLayout } from './components/OGridLayout';
|
|
6
6
|
export type { OGridLayoutProps } from './components/OGridLayout';
|
|
7
7
|
export { StatusBar } from './components/StatusBar';
|
|
@@ -12,5 +12,5 @@ export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
|
|
|
12
12
|
export type { MarchingAntsOverlayProps } from './components/MarchingAntsOverlay';
|
|
13
13
|
export { SideBar } from './components/SideBar';
|
|
14
14
|
export type { SideBarProps, SideBarFilterColumn } from './components/SideBar';
|
|
15
|
-
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, } from './utils';
|
|
15
|
+
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from './utils';
|
|
16
16
|
export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, ParseValueResult, AggregationResult, } from './utils';
|
|
@@ -18,19 +18,24 @@ export type UserLikeInput = {
|
|
|
18
18
|
photo?: string;
|
|
19
19
|
};
|
|
20
20
|
export declare function toUserLike(u: UserLikeInput | undefined): UserLike | undefined;
|
|
21
|
-
/**
|
|
22
|
-
export type FilterValue =
|
|
23
|
-
|
|
21
|
+
/** Discriminated filter value. The `type` field identifies the filter kind. */
|
|
22
|
+
export type FilterValue = {
|
|
23
|
+
type: 'text';
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'multiSelect';
|
|
27
|
+
value: string[];
|
|
28
|
+
} | {
|
|
29
|
+
type: 'people';
|
|
30
|
+
value: UserLike;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'date';
|
|
33
|
+
value: IDateFilterValue;
|
|
34
|
+
};
|
|
35
|
+
/** Unified filter model: field id -> discriminated filter value. */
|
|
24
36
|
export interface IFilters {
|
|
25
37
|
[field: string]: FilterValue | undefined;
|
|
26
38
|
}
|
|
27
|
-
/** Split IFilters into DataGridTable's multiSelect, text, people, and date props. */
|
|
28
|
-
export declare function toDataGridFilterProps(filters: IFilters): {
|
|
29
|
-
multiSelectFilters: Record<string, string[]>;
|
|
30
|
-
textFilters: Record<string, string>;
|
|
31
|
-
peopleFilters: Record<string, UserLike | undefined>;
|
|
32
|
-
dateFilters: Record<string, IDateFilterValue>;
|
|
33
|
-
};
|
|
34
39
|
export interface IFetchParams {
|
|
35
40
|
page: number;
|
|
36
41
|
pageSize: number;
|
|
@@ -198,8 +203,6 @@ export interface IOGridProps<T> {
|
|
|
198
203
|
};
|
|
199
204
|
entityLabelPlural?: string;
|
|
200
205
|
className?: string;
|
|
201
|
-
/** @deprecated Render your title outside the OGrid component. Will be removed in next major. */
|
|
202
|
-
title?: ReactNode;
|
|
203
206
|
/** Where the column chooser renders.
|
|
204
207
|
* - `true` or `'toolbar'` (default): column chooser button in the toolbar strip.
|
|
205
208
|
* - `'sidebar'`: column chooser only available via the sidebar columns panel.
|
|
@@ -260,14 +263,10 @@ export interface IOGridDataGridProps<T> {
|
|
|
260
263
|
selectedRows?: Set<RowId>;
|
|
261
264
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
262
265
|
statusBar?: IStatusBarProps;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
peopleFilters?: Record<string, UserLike | undefined>;
|
|
268
|
-
onPeopleFilterChange?: (key: string, user: UserLike | undefined) => void;
|
|
269
|
-
dateFilters?: Record<string, IDateFilterValue>;
|
|
270
|
-
onDateFilterChange?: (key: string, value: IDateFilterValue | undefined) => void;
|
|
266
|
+
/** Unified filter model (discriminated union values). */
|
|
267
|
+
filters: IFilters;
|
|
268
|
+
/** Single callback for all filter changes. Pass undefined to clear. */
|
|
269
|
+
onFilterChange: (key: string, value: FilterValue | undefined) => void;
|
|
271
270
|
filterOptions: Record<string, string[]>;
|
|
272
271
|
loadingFilterOptions: Record<string, boolean>;
|
|
273
272
|
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, } from './columnTypes';
|
|
2
2
|
export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, } from './dataGridTypes';
|
|
3
|
-
export { toUserLike,
|
|
3
|
+
export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IColumnDef, IFilters } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Apply client-side filtering and sorting to data.
|
|
4
|
+
* Extracted from useOGrid for testability and reuse.
|
|
5
|
+
*
|
|
6
|
+
* @param data - The full dataset to process
|
|
7
|
+
* @param columns - Column definitions (used for filtering and sorting)
|
|
8
|
+
* @param filters - Current filter state (discriminated FilterValue union)
|
|
9
|
+
* @param sortBy - Column ID to sort by (optional)
|
|
10
|
+
* @param sortDirection - Sort direction (optional)
|
|
11
|
+
* @returns Filtered and sorted array
|
|
12
|
+
*/
|
|
13
|
+
export declare function processClientSideData<T>(data: T[], columns: IColumnDef<T>[], filters: IFilters, sortBy?: string, sortDirection?: 'asc' | 'desc'): T[];
|
|
@@ -4,22 +4,16 @@
|
|
|
4
4
|
import type * as React from 'react';
|
|
5
5
|
import type { ColumnFilterType, ICellEditorProps, IDateFilterValue } from '../types/columnTypes';
|
|
6
6
|
import type { IColumnDef } from '../types/columnTypes';
|
|
7
|
-
import type { RowId, UserLike } from '../types/dataGridTypes';
|
|
7
|
+
import type { RowId, UserLike, IFilters, FilterValue } from '../types/dataGridTypes';
|
|
8
8
|
export interface HeaderFilterConfigInput {
|
|
9
9
|
sortBy?: string;
|
|
10
10
|
sortDirection: 'asc' | 'desc';
|
|
11
11
|
onColumnSort: (columnKey: string) => void;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
peopleFilters?: Record<string, UserLike | undefined>;
|
|
15
|
-
onPeopleFilterChange?: (key: string, user: UserLike | undefined) => void;
|
|
16
|
-
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
12
|
+
filters: IFilters;
|
|
13
|
+
onFilterChange: (key: string, value: FilterValue | undefined) => void;
|
|
17
14
|
filterOptions: Record<string, string[]>;
|
|
18
15
|
loadingFilterOptions: Record<string, boolean>;
|
|
19
|
-
|
|
20
|
-
onMultiSelectFilterChange: (key: string, values: string[]) => void;
|
|
21
|
-
dateFilters?: Record<string, IDateFilterValue>;
|
|
22
|
-
onDateFilterChange?: (key: string, value: IDateFilterValue | undefined) => void;
|
|
16
|
+
peopleSearch?: (query: string) => Promise<UserLike[]>;
|
|
23
17
|
}
|
|
24
18
|
/** Props to pass to ColumnHeaderFilter. Matches IColumnHeaderFilterProps. */
|
|
25
19
|
export interface HeaderFilterConfig {
|
|
@@ -16,3 +16,4 @@ export { parseValue, numberParser, currencyParser, dateParser, emailParser, bool
|
|
|
16
16
|
export type { ParseValueResult } from './valueParsers';
|
|
17
17
|
export { computeAggregations } from './aggregationUtils';
|
|
18
18
|
export type { AggregationResult } from './aggregationUtils';
|
|
19
|
+
export { processClientSideData } from './clientSideData';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IColumnDef, IFilters, FilterValue } from '../types';
|
|
2
2
|
/** Resolve the filter field key for a column (filterField or columnId). */
|
|
3
3
|
export declare function getFilterField<T>(col: IColumnDef<T>): string;
|
|
4
|
-
/** Merge a single filter change into a full IFilters object. */
|
|
4
|
+
/** Merge a single filter change into a full IFilters object. Strips empty values automatically. */
|
|
5
5
|
export declare function mergeFilter(prev: IFilters, key: string, value: FilterValue | undefined): IFilters;
|
|
6
6
|
/** Derive filter options for multiSelect columns from client-side data. */
|
|
7
7
|
export declare function deriveFilterOptionsFromData<T>(items: T[], columns: IColumnDef<T>[]): Record<string, string[]>;
|
package/package.json
CHANGED