@alaarab/ogrid-core 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +1426 -54
- package/package.json +3 -3
- package/dist/esm/constants/index.js +0 -3
- package/dist/esm/constants/layout.js +0 -13
- package/dist/esm/constants/timing.js +0 -10
- package/dist/esm/constants/zIndex.js +0 -16
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/dataGridTypes.js +0 -27
- package/dist/esm/types/index.js +0 -2
- package/dist/esm/utils/aggregationUtils.js +0 -48
- package/dist/esm/utils/cellValue.js +0 -14
- package/dist/esm/utils/clientSideData.js +0 -155
- package/dist/esm/utils/clipboardHelpers.js +0 -142
- package/dist/esm/utils/columnAutosize.js +0 -38
- package/dist/esm/utils/columnReorder.js +0 -99
- package/dist/esm/utils/columnUtils.js +0 -122
- package/dist/esm/utils/dataGridStatusBar.js +0 -15
- package/dist/esm/utils/dataGridViewModel.js +0 -206
- package/dist/esm/utils/debounce.js +0 -40
- package/dist/esm/utils/dom.js +0 -53
- package/dist/esm/utils/exportToCsv.js +0 -50
- package/dist/esm/utils/fillHelpers.js +0 -47
- package/dist/esm/utils/gridContextMenuHelpers.js +0 -80
- package/dist/esm/utils/gridRowComparator.js +0 -78
- package/dist/esm/utils/index.js +0 -25
- package/dist/esm/utils/keyboardNavigation.js +0 -181
- package/dist/esm/utils/ogridHelpers.js +0 -67
- package/dist/esm/utils/paginationHelpers.js +0 -58
- package/dist/esm/utils/selectionHelpers.js +0 -94
- package/dist/esm/utils/sortHelpers.js +0 -28
- package/dist/esm/utils/statusBarHelpers.js +0 -27
- package/dist/esm/utils/undoRedoStack.js +0 -130
- package/dist/esm/utils/validation.js +0 -43
- package/dist/esm/utils/valueParsers.js +0 -121
- package/dist/esm/utils/virtualScroll.js +0 -46
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checks whether a given row index falls within a selection range.
|
|
3
|
-
* O(1) — used by React.memo comparators to skip unchanged rows.
|
|
4
|
-
*/
|
|
5
|
-
export function isRowInRange(range, rowIndex) {
|
|
6
|
-
if (!range)
|
|
7
|
-
return false;
|
|
8
|
-
const minR = Math.min(range.startRow, range.endRow);
|
|
9
|
-
const maxR = Math.max(range.startRow, range.endRow);
|
|
10
|
-
return rowIndex >= minR && rowIndex <= maxR;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Shared React.memo comparator for GridRow components across all 3 UI packages.
|
|
14
|
-
* Skips re-render for rows unaffected by selection/editing/interaction changes.
|
|
15
|
-
*
|
|
16
|
-
* Used by:
|
|
17
|
-
* - packages/radix/src/DataGridTable/DataGridTable.tsx
|
|
18
|
-
* - packages/fluent/src/DataGridTable/DataGridTable.tsx
|
|
19
|
-
* - packages/material/src/DataGridTable/DataGridTable.tsx
|
|
20
|
-
*/
|
|
21
|
-
export function areGridRowPropsEqual(prev, next) {
|
|
22
|
-
// Data / structure changes — always re-render
|
|
23
|
-
if (prev.item !== next.item)
|
|
24
|
-
return false;
|
|
25
|
-
if (prev.isSelected !== next.isSelected)
|
|
26
|
-
return false;
|
|
27
|
-
if (prev.hasCheckboxCol !== next.hasCheckboxCol)
|
|
28
|
-
return false;
|
|
29
|
-
// Framework-specific structure props (compared by identity)
|
|
30
|
-
if (prev.visibleCols !== next.visibleCols)
|
|
31
|
-
return false;
|
|
32
|
-
if (prev.columnMeta !== next.columnMeta)
|
|
33
|
-
return false;
|
|
34
|
-
if (prev.cellClassMap !== next.cellClassMap)
|
|
35
|
-
return false;
|
|
36
|
-
if (prev.columnLayouts !== next.columnLayouts)
|
|
37
|
-
return false;
|
|
38
|
-
const ri = prev.rowIndex;
|
|
39
|
-
// Editing cell in this row?
|
|
40
|
-
if (prev.editingRowId !== next.editingRowId) {
|
|
41
|
-
if (prev.editingRowId === prev.rowId || next.editingRowId === next.rowId)
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
// Active cell in this row?
|
|
45
|
-
const prevActive = prev.activeCell?.rowIndex === ri;
|
|
46
|
-
const nextActive = next.activeCell?.rowIndex === ri;
|
|
47
|
-
if (prevActive !== nextActive)
|
|
48
|
-
return false;
|
|
49
|
-
if (prevActive && nextActive && prev.activeCell?.columnIndex !== next.activeCell?.columnIndex)
|
|
50
|
-
return false;
|
|
51
|
-
// Selection range touches this row?
|
|
52
|
-
const prevInSel = isRowInRange(prev.selectionRange, ri);
|
|
53
|
-
const nextInSel = isRowInRange(next.selectionRange, ri);
|
|
54
|
-
if (prevInSel !== nextInSel)
|
|
55
|
-
return false;
|
|
56
|
-
if (prevInSel && nextInSel) {
|
|
57
|
-
if (prev.selectionRange?.startCol !== next.selectionRange?.startCol ||
|
|
58
|
-
prev.selectionRange?.endCol !== next.selectionRange?.endCol)
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
// Fill handle (selection end row) + isDragging
|
|
62
|
-
const prevIsEnd = prev.selectionRange?.endRow === ri;
|
|
63
|
-
const nextIsEnd = next.selectionRange?.endRow === ri;
|
|
64
|
-
if (prevIsEnd !== nextIsEnd)
|
|
65
|
-
return false;
|
|
66
|
-
if ((prevIsEnd || nextIsEnd) && prev.isDragging !== next.isDragging)
|
|
67
|
-
return false;
|
|
68
|
-
// Cut/copy ranges touch this row?
|
|
69
|
-
if (prev.cutRange !== next.cutRange) {
|
|
70
|
-
if (isRowInRange(prev.cutRange, ri) || isRowInRange(next.cutRange, ri))
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
if (prev.copyRange !== next.copyRange) {
|
|
74
|
-
if (isRowInRange(prev.copyRange, ri) || isRowInRange(next.copyRange, ri))
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
return true;
|
|
78
|
-
}
|
package/dist/esm/utils/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './exportToCsv';
|
|
2
|
-
export { getCellValue } from './cellValue';
|
|
3
|
-
export { flattenColumns, buildHeaderRows } from './columnUtils';
|
|
4
|
-
export { isFilterConfig, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from './ogridHelpers';
|
|
5
|
-
export { getStatusBarParts } from './statusBarHelpers';
|
|
6
|
-
export { getDataGridStatusBarConfig } from './dataGridStatusBar';
|
|
7
|
-
export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
|
|
8
|
-
export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut } from './gridContextMenuHelpers';
|
|
9
|
-
export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './valueParsers';
|
|
10
|
-
export { computeAggregations } from './aggregationUtils';
|
|
11
|
-
export { processClientSideData } from './clientSideData';
|
|
12
|
-
export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';
|
|
13
|
-
export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './columnReorder';
|
|
14
|
-
export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './virtualScroll';
|
|
15
|
-
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
|
|
16
|
-
export { debounce } from './debounce';
|
|
17
|
-
export { measureRange, injectGlobalStyles } from './dom';
|
|
18
|
-
export { computeNextSortState } from './sortHelpers';
|
|
19
|
-
export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './columnAutosize';
|
|
20
|
-
export { findCtrlArrowTarget, computeTabNavigation, computeArrowNavigation, applyCellDeletion } from './keyboardNavigation';
|
|
21
|
-
export { rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, applyRangeRowSelection, computeRowSelectionState } from './selectionHelpers';
|
|
22
|
-
export { formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, } from './clipboardHelpers';
|
|
23
|
-
export { applyFillValues } from './fillHelpers';
|
|
24
|
-
export { UndoRedoStack } from './undoRedoStack';
|
|
25
|
-
export { validateColumns, validateRowIds } from './validation';
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { normalizeSelectionRange } from '../types/dataGridTypes';
|
|
2
|
-
import { getCellValue } from './cellValue';
|
|
3
|
-
import { parseValue } from './valueParsers';
|
|
4
|
-
/**
|
|
5
|
-
* Excel-style Ctrl+Arrow: find the target position along a 1D axis.
|
|
6
|
-
* - Non-empty current + non-empty next → scan through non-empties, stop at last before empty/edge.
|
|
7
|
-
* - Otherwise → skip empties, land on next non-empty or edge.
|
|
8
|
-
*
|
|
9
|
-
* @param pos Current position (row or column index).
|
|
10
|
-
* @param edge The boundary position (0 for backward, max for forward).
|
|
11
|
-
* @param step Direction: +1 (forward) or -1 (backward).
|
|
12
|
-
* @param isEmpty Predicate: returns true if the cell at this index is empty.
|
|
13
|
-
* @returns The target position after the jump.
|
|
14
|
-
*/
|
|
15
|
-
export function findCtrlArrowTarget(pos, edge, step, isEmpty) {
|
|
16
|
-
if (pos === edge)
|
|
17
|
-
return pos;
|
|
18
|
-
const next = pos + step;
|
|
19
|
-
if (!isEmpty(pos) && !isEmpty(next)) {
|
|
20
|
-
// Scan forward through non-empties; stop at the last before an empty or edge
|
|
21
|
-
let p = next;
|
|
22
|
-
while (p !== edge) {
|
|
23
|
-
if (isEmpty(p + step))
|
|
24
|
-
return p;
|
|
25
|
-
p += step;
|
|
26
|
-
}
|
|
27
|
-
return edge;
|
|
28
|
-
}
|
|
29
|
-
// Skip empties; land on first non-empty or edge
|
|
30
|
-
let p = next;
|
|
31
|
-
while (p !== edge) {
|
|
32
|
-
if (!isEmpty(p))
|
|
33
|
-
return p;
|
|
34
|
-
p += step;
|
|
35
|
-
}
|
|
36
|
-
return edge;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Compute the new Tab navigation position given the current position and direction.
|
|
40
|
-
*
|
|
41
|
-
* @param rowIndex Current row index.
|
|
42
|
-
* @param columnIndex Current absolute column index (includes checkbox offset).
|
|
43
|
-
* @param maxRowIndex Maximum row index (items.length - 1). Must be >= 0.
|
|
44
|
-
* @param maxColIndex Maximum absolute column index. Must be >= 0.
|
|
45
|
-
* @param colOffset Number of non-data leading columns (checkbox column offset).
|
|
46
|
-
* @param shiftKey True if Shift is held (backward tab).
|
|
47
|
-
* @returns New { rowIndex, columnIndex } after tab. Caller must ensure maxRowIndex and maxColIndex are non-negative.
|
|
48
|
-
*/
|
|
49
|
-
export function computeTabNavigation(rowIndex, columnIndex, maxRowIndex, maxColIndex, colOffset, shiftKey) {
|
|
50
|
-
let newRow = rowIndex;
|
|
51
|
-
let newCol = columnIndex;
|
|
52
|
-
if (shiftKey) {
|
|
53
|
-
if (columnIndex > colOffset) {
|
|
54
|
-
newCol = columnIndex - 1;
|
|
55
|
-
}
|
|
56
|
-
else if (rowIndex > 0) {
|
|
57
|
-
newRow = rowIndex - 1;
|
|
58
|
-
newCol = maxColIndex;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
if (columnIndex < maxColIndex) {
|
|
63
|
-
newCol = columnIndex + 1;
|
|
64
|
-
}
|
|
65
|
-
else if (rowIndex < maxRowIndex) {
|
|
66
|
-
newRow = rowIndex + 1;
|
|
67
|
-
newCol = colOffset;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return { rowIndex: newRow, columnIndex: newCol };
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Computes the next active cell position and selection range for a single arrow key press.
|
|
74
|
-
* Handles Ctrl+Arrow (jump to edge), Shift+Arrow (extend selection), and plain Arrow (move).
|
|
75
|
-
*
|
|
76
|
-
* Pure function — no framework dependencies.
|
|
77
|
-
*
|
|
78
|
-
* @param ctx Arrow navigation context with current position, direction, modifiers, and grid bounds.
|
|
79
|
-
* @returns The new row/column indices and selection range.
|
|
80
|
-
*/
|
|
81
|
-
export function computeArrowNavigation(ctx) {
|
|
82
|
-
const { direction, rowIndex, columnIndex, dataColIndex, colOffset, maxRowIndex, maxColIndex, visibleColCount, isCtrl, isShift, selectionRange, isEmptyAt, } = ctx;
|
|
83
|
-
let newRowIndex = rowIndex;
|
|
84
|
-
let newColumnIndex = columnIndex;
|
|
85
|
-
if (direction === 'ArrowDown') {
|
|
86
|
-
newRowIndex = isCtrl
|
|
87
|
-
? findCtrlArrowTarget(rowIndex, maxRowIndex, 1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
|
|
88
|
-
: Math.min(rowIndex + 1, maxRowIndex);
|
|
89
|
-
}
|
|
90
|
-
else if (direction === 'ArrowUp') {
|
|
91
|
-
newRowIndex = isCtrl
|
|
92
|
-
? findCtrlArrowTarget(rowIndex, 0, -1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
|
|
93
|
-
: Math.max(rowIndex - 1, 0);
|
|
94
|
-
}
|
|
95
|
-
else if (direction === 'ArrowRight') {
|
|
96
|
-
if (isCtrl && dataColIndex >= 0) {
|
|
97
|
-
newColumnIndex = findCtrlArrowTarget(dataColIndex, visibleColCount - 1, 1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
newColumnIndex = Math.min(columnIndex + 1, maxColIndex);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else { // ArrowLeft
|
|
104
|
-
if (isCtrl && dataColIndex >= 0) {
|
|
105
|
-
newColumnIndex = findCtrlArrowTarget(dataColIndex, 0, -1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
newColumnIndex = Math.max(columnIndex - 1, colOffset);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const newDataColIndex = newColumnIndex - colOffset;
|
|
112
|
-
const isVertical = direction === 'ArrowDown' || direction === 'ArrowUp';
|
|
113
|
-
let newRange;
|
|
114
|
-
if (isShift) {
|
|
115
|
-
if (isVertical) {
|
|
116
|
-
newRange = normalizeSelectionRange({
|
|
117
|
-
startRow: selectionRange?.startRow ?? rowIndex,
|
|
118
|
-
startCol: selectionRange?.startCol ?? dataColIndex,
|
|
119
|
-
endRow: newRowIndex,
|
|
120
|
-
endCol: selectionRange?.endCol ?? dataColIndex,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
newRange = normalizeSelectionRange({
|
|
125
|
-
startRow: selectionRange?.startRow ?? rowIndex,
|
|
126
|
-
startCol: selectionRange?.startCol ?? dataColIndex,
|
|
127
|
-
endRow: selectionRange?.endRow ?? rowIndex,
|
|
128
|
-
endCol: newDataColIndex,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
newRange = {
|
|
134
|
-
startRow: newRowIndex,
|
|
135
|
-
startCol: newDataColIndex,
|
|
136
|
-
endRow: newRowIndex,
|
|
137
|
-
endCol: newDataColIndex,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return { newRowIndex, newColumnIndex, newDataColIndex, newRange };
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Apply cell deletion (Delete/Backspace key) across a selection range.
|
|
144
|
-
* For each editable cell in the range, parses an empty string as the new value
|
|
145
|
-
* and emits a cell value changed event.
|
|
146
|
-
*
|
|
147
|
-
* Pure function — no framework dependencies.
|
|
148
|
-
*
|
|
149
|
-
* @param range The normalized selection range to clear.
|
|
150
|
-
* @param items Array of all row data objects.
|
|
151
|
-
* @param visibleCols Visible column definitions.
|
|
152
|
-
* @returns Array of cell value changed events to apply.
|
|
153
|
-
*/
|
|
154
|
-
export function applyCellDeletion(range, items, visibleCols) {
|
|
155
|
-
const norm = normalizeSelectionRange(range);
|
|
156
|
-
const events = [];
|
|
157
|
-
for (let r = norm.startRow; r <= norm.endRow; r++) {
|
|
158
|
-
for (let c = norm.startCol; c <= norm.endCol; c++) {
|
|
159
|
-
if (r >= items.length || c >= visibleCols.length)
|
|
160
|
-
continue;
|
|
161
|
-
const item = items[r];
|
|
162
|
-
const col = visibleCols[c];
|
|
163
|
-
const colEditable = col.editable === true ||
|
|
164
|
-
(typeof col.editable === 'function' && col.editable(item));
|
|
165
|
-
if (!colEditable)
|
|
166
|
-
continue;
|
|
167
|
-
const oldValue = getCellValue(item, col);
|
|
168
|
-
const result = parseValue('', oldValue, item, col);
|
|
169
|
-
if (!result.valid)
|
|
170
|
-
continue;
|
|
171
|
-
events.push({
|
|
172
|
-
item,
|
|
173
|
-
columnId: col.columnId,
|
|
174
|
-
oldValue,
|
|
175
|
-
newValue: result.value,
|
|
176
|
-
rowIndex: r,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return events;
|
|
181
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { getCellValue } from './cellValue';
|
|
2
|
-
/** Type guard: returns true if val is an IColumnFilterDef (an object with a filter type). */
|
|
3
|
-
export function isFilterConfig(val) {
|
|
4
|
-
return val != null && typeof val === 'object' && 'type' in val;
|
|
5
|
-
}
|
|
6
|
-
/** Resolve the filter field key for a column (filterField or columnId). */
|
|
7
|
-
export function getFilterField(col) {
|
|
8
|
-
const f = isFilterConfig(col.filterable) ? col.filterable : null;
|
|
9
|
-
return (f?.filterField ?? col.columnId);
|
|
10
|
-
}
|
|
11
|
-
/** Merge a single filter change into a full IFilters object. Strips empty values automatically. */
|
|
12
|
-
export function mergeFilter(prev, key, value) {
|
|
13
|
-
const isEmpty = value === undefined ||
|
|
14
|
-
(value.type === 'text' && value.value.trim() === '') ||
|
|
15
|
-
(value.type === 'multiSelect' && value.value.length === 0) ||
|
|
16
|
-
(value.type === 'date' && !value.value.from && !value.value.to) ||
|
|
17
|
-
(value.type === 'people' && !value.value);
|
|
18
|
-
if (isEmpty) {
|
|
19
|
-
const { [key]: _, ...rest } = prev;
|
|
20
|
-
return rest;
|
|
21
|
-
}
|
|
22
|
-
return { ...prev, [key]: value };
|
|
23
|
-
}
|
|
24
|
-
/** Derive filter options for multiSelect columns from client-side data. */
|
|
25
|
-
export function deriveFilterOptionsFromData(items, columns) {
|
|
26
|
-
// Collect multiSelect columns upfront
|
|
27
|
-
const filterCols = [];
|
|
28
|
-
for (let i = 0; i < columns.length; i++) {
|
|
29
|
-
const col = columns[i];
|
|
30
|
-
const f = isFilterConfig(col.filterable) ? col.filterable : null;
|
|
31
|
-
if (f?.type === 'multiSelect') {
|
|
32
|
-
filterCols.push({ col, field: getFilterField(col) });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (filterCols.length === 0)
|
|
36
|
-
return {};
|
|
37
|
-
// Single pass through items, collecting values for all filter columns simultaneously
|
|
38
|
-
const valueSets = new Map();
|
|
39
|
-
for (let i = 0; i < filterCols.length; i++) {
|
|
40
|
-
valueSets.set(filterCols[i].field, new Set());
|
|
41
|
-
}
|
|
42
|
-
for (let i = 0; i < items.length; i++) {
|
|
43
|
-
const item = items[i];
|
|
44
|
-
for (let j = 0; j < filterCols.length; j++) {
|
|
45
|
-
const v = getCellValue(item, filterCols[j].col);
|
|
46
|
-
const set = valueSets.get(filterCols[j].field);
|
|
47
|
-
if (v != null && v !== '' && set)
|
|
48
|
-
set.add(String(v));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const out = {};
|
|
52
|
-
for (let i = 0; i < filterCols.length; i++) {
|
|
53
|
-
const set = valueSets.get(filterCols[i].field);
|
|
54
|
-
out[filterCols[i].field] = set ? Array.from(set).sort() : [];
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
/** Get list of filter fields that use multiSelect (for useFilterOptions). */
|
|
59
|
-
export function getMultiSelectFilterFields(columns) {
|
|
60
|
-
const fields = [];
|
|
61
|
-
for (const col of columns) {
|
|
62
|
-
const f = isFilterConfig(col.filterable) ? col.filterable : null;
|
|
63
|
-
if (f?.type === 'multiSelect')
|
|
64
|
-
fields.push(getFilterField(col));
|
|
65
|
-
}
|
|
66
|
-
return fields;
|
|
67
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared pagination view model for Fluent, Material, and Radix PaginationControls.
|
|
3
|
-
* UI packages use this and render only presentation.
|
|
4
|
-
*/
|
|
5
|
-
export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
6
|
-
/** Ensures the active pageSize is included in the options list, inserting it in sorted order if missing. */
|
|
7
|
-
function ensurePageSizeInOptions(pageSize, options) {
|
|
8
|
-
if (options.includes(pageSize))
|
|
9
|
-
return options;
|
|
10
|
-
return [...options, pageSize].sort((a, b) => a - b);
|
|
11
|
-
}
|
|
12
|
-
export const MAX_PAGE_BUTTONS = 5;
|
|
13
|
-
/**
|
|
14
|
-
* Returns a view model for pagination UI. Use in Fluent/Material/Radix PaginationControls
|
|
15
|
-
* so page math lives in one place and components only render.
|
|
16
|
-
*/
|
|
17
|
-
export function getPaginationViewModel(currentPage, pageSize, totalCount, options) {
|
|
18
|
-
if (totalCount <= 0)
|
|
19
|
-
return null;
|
|
20
|
-
const maxPageButtons = options?.maxPageButtons ?? MAX_PAGE_BUTTONS;
|
|
21
|
-
const totalPages = Math.ceil(totalCount / pageSize);
|
|
22
|
-
let pageNumbers;
|
|
23
|
-
let showStartEllipsis;
|
|
24
|
-
let showEndEllipsis;
|
|
25
|
-
if (totalPages <= maxPageButtons) {
|
|
26
|
-
pageNumbers = [];
|
|
27
|
-
for (let i = 1; i <= totalPages; i++)
|
|
28
|
-
pageNumbers.push(i);
|
|
29
|
-
showStartEllipsis = false;
|
|
30
|
-
showEndEllipsis = false;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
let start = Math.max(1, currentPage - 2);
|
|
34
|
-
let end = Math.min(totalPages, currentPage + 2);
|
|
35
|
-
if (end - start + 1 < maxPageButtons) {
|
|
36
|
-
if (start === 1)
|
|
37
|
-
end = Math.min(totalPages, start + maxPageButtons - 1);
|
|
38
|
-
else if (end === totalPages)
|
|
39
|
-
start = Math.max(1, end - maxPageButtons + 1);
|
|
40
|
-
}
|
|
41
|
-
pageNumbers = [];
|
|
42
|
-
for (let i = start; i <= end; i++)
|
|
43
|
-
pageNumbers.push(i);
|
|
44
|
-
showStartEllipsis = start > 1;
|
|
45
|
-
showEndEllipsis = end < totalPages;
|
|
46
|
-
}
|
|
47
|
-
const startItem = Math.max(1, (currentPage - 1) * pageSize + 1);
|
|
48
|
-
const endItem = Math.min(currentPage * pageSize, totalCount);
|
|
49
|
-
return {
|
|
50
|
-
totalPages,
|
|
51
|
-
pageNumbers,
|
|
52
|
-
showStartEllipsis,
|
|
53
|
-
showEndEllipsis,
|
|
54
|
-
startItem,
|
|
55
|
-
endItem,
|
|
56
|
-
pageSizeOptions: ensurePageSizeInOptions(pageSize, options?.pageSizeOptions ?? PAGE_SIZE_OPTIONS),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// Re-export normalizeSelectionRange from its canonical location for convenience.
|
|
2
|
-
// The original definition lives in dataGridTypes.ts and is preserved there
|
|
3
|
-
// to avoid breaking existing imports.
|
|
4
|
-
export { normalizeSelectionRange } from '../types/dataGridTypes';
|
|
5
|
-
/**
|
|
6
|
-
* Compare two selection ranges by value (deep equality).
|
|
7
|
-
* Returns true if both ranges are equal, including when both are null.
|
|
8
|
-
*/
|
|
9
|
-
export function rangesEqual(a, b) {
|
|
10
|
-
if (a === b)
|
|
11
|
-
return true;
|
|
12
|
-
if (!a || !b)
|
|
13
|
-
return false;
|
|
14
|
-
return (a.startRow === b.startRow &&
|
|
15
|
-
a.endRow === b.endRow &&
|
|
16
|
-
a.startCol === b.startCol &&
|
|
17
|
-
a.endCol === b.endCol);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Clamp a selection range to the grid bounds (0-based, inclusive).
|
|
21
|
-
*
|
|
22
|
-
* @param range The selection range to clamp.
|
|
23
|
-
* @param maxRow Maximum valid row index (items.length - 1).
|
|
24
|
-
* @param maxCol Maximum valid column index (visibleCols.length - 1).
|
|
25
|
-
* @returns The clamped range, or null if the grid is empty.
|
|
26
|
-
*/
|
|
27
|
-
export function clampSelectionToBounds(range, maxRow, maxCol) {
|
|
28
|
-
if (maxRow < 0 || maxCol < 0)
|
|
29
|
-
return null;
|
|
30
|
-
return {
|
|
31
|
-
startRow: Math.max(0, Math.min(range.startRow, maxRow)),
|
|
32
|
-
endRow: Math.max(0, Math.min(range.endRow, maxRow)),
|
|
33
|
-
startCol: Math.max(0, Math.min(range.startCol, maxCol)),
|
|
34
|
-
endCol: Math.max(0, Math.min(range.endCol, maxCol)),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Auto-scroll speed: proportional to how far past the scroll edge the pointer is.
|
|
39
|
-
* Used by drag-selection auto-scroll in both React and Vue.
|
|
40
|
-
*
|
|
41
|
-
* @param distance Distance past the edge threshold (pixels).
|
|
42
|
-
* @param edgePx Scroll edge threshold in pixels (default: 40).
|
|
43
|
-
* @param minSpeed Minimum scroll speed (default: 2).
|
|
44
|
-
* @param maxSpeed Maximum scroll speed (default: 20).
|
|
45
|
-
* @returns Scroll speed in pixels per interval tick.
|
|
46
|
-
*/
|
|
47
|
-
export function computeAutoScrollSpeed(distance, edgePx = 40, minSpeed = 2, maxSpeed = 20) {
|
|
48
|
-
const t = Math.min(distance / edgePx, 1);
|
|
49
|
-
return minSpeed + t * (maxSpeed - minSpeed);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Apply a shift-click range selection to a set of row IDs.
|
|
53
|
-
* Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
|
|
54
|
-
*
|
|
55
|
-
* @param start Start index of the range (inclusive).
|
|
56
|
-
* @param end End index of the range (inclusive).
|
|
57
|
-
* @param checked Whether to add (true) or remove (false) the rows.
|
|
58
|
-
* @param items Array of all row data objects.
|
|
59
|
-
* @param getRowId Function to extract a unique row ID from an item.
|
|
60
|
-
* @param currentSelection Current set of selected row IDs (will be shallow-copied).
|
|
61
|
-
* @returns A new Set of selected row IDs after applying the range.
|
|
62
|
-
*/
|
|
63
|
-
export function applyRangeRowSelection(start, end, checked, items, getRowId, currentSelection) {
|
|
64
|
-
const next = new Set(currentSelection);
|
|
65
|
-
const lo = Math.min(start, end);
|
|
66
|
-
const hi = Math.max(start, end);
|
|
67
|
-
for (let i = lo; i <= hi; i++) {
|
|
68
|
-
if (i < items.length) {
|
|
69
|
-
const id = getRowId(items[i]);
|
|
70
|
-
if (checked)
|
|
71
|
-
next.add(id);
|
|
72
|
-
else
|
|
73
|
-
next.delete(id);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return next;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Compute the allSelected / someSelected state from a set of selected row IDs.
|
|
80
|
-
* Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
|
|
81
|
-
*
|
|
82
|
-
* @param selectedIds Current set of selected row IDs.
|
|
83
|
-
* @param items Array of all row data objects.
|
|
84
|
-
* @param getRowId Function to extract a unique row ID from an item.
|
|
85
|
-
* @returns An object with `allSelected` and `someSelected` booleans.
|
|
86
|
-
*/
|
|
87
|
-
export function computeRowSelectionState(selectedIds, items, getRowId) {
|
|
88
|
-
if (selectedIds.size === 0 || items.length === 0) {
|
|
89
|
-
return { allSelected: false, someSelected: false };
|
|
90
|
-
}
|
|
91
|
-
const allSelected = items.every((item) => selectedIds.has(getRowId(item)));
|
|
92
|
-
const someSelected = !allSelected && selectedIds.size > 0;
|
|
93
|
-
return { allSelected, someSelected };
|
|
94
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sort state computation helpers shared across all frameworks.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Compute the next sort state given the current state and a sort request.
|
|
6
|
-
*
|
|
7
|
-
* @param current - Current sort state
|
|
8
|
-
* @param columnKey - Column being sorted
|
|
9
|
-
* @param direction - Explicit direction, `null` to clear, or `undefined` to toggle
|
|
10
|
-
* @returns New sort state
|
|
11
|
-
*/
|
|
12
|
-
export function computeNextSortState(current, columnKey, direction) {
|
|
13
|
-
if (direction === null) {
|
|
14
|
-
// Clear sort
|
|
15
|
-
return { field: '', direction: 'asc' };
|
|
16
|
-
}
|
|
17
|
-
else if (direction) {
|
|
18
|
-
// Explicit direction (from column menu)
|
|
19
|
-
return { field: columnKey, direction };
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
// Toggle (existing behavior for header click)
|
|
23
|
-
return {
|
|
24
|
-
field: columnKey,
|
|
25
|
-
direction: current.field === columnKey && current.direction === 'asc' ? 'desc' : 'asc',
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns an array of status bar parts (Rows, Filtered, Selected) for consistent rendering across packages.
|
|
3
|
-
*/
|
|
4
|
-
export function getStatusBarParts(input) {
|
|
5
|
-
const { totalCount, filteredCount, selectedCount, selectedCellCount, aggregation, suppressRowCount } = input;
|
|
6
|
-
const parts = [];
|
|
7
|
-
if (!suppressRowCount) {
|
|
8
|
-
parts.push({ key: 'total', label: 'Rows:', value: totalCount });
|
|
9
|
-
}
|
|
10
|
-
if (filteredCount !== undefined && filteredCount !== totalCount) {
|
|
11
|
-
parts.push({ key: 'filtered', label: 'Filtered:', value: filteredCount });
|
|
12
|
-
}
|
|
13
|
-
if (selectedCount !== undefined && selectedCount > 0) {
|
|
14
|
-
parts.push({ key: 'selected', label: 'Selected:', value: selectedCount });
|
|
15
|
-
}
|
|
16
|
-
if (selectedCellCount !== undefined && selectedCellCount > 1) {
|
|
17
|
-
parts.push({ key: 'cells', label: 'Cells:', value: selectedCellCount });
|
|
18
|
-
}
|
|
19
|
-
if (aggregation) {
|
|
20
|
-
parts.push({ key: 'sum', label: 'Sum:', value: aggregation.sum });
|
|
21
|
-
parts.push({ key: 'avg', label: 'Avg:', value: Math.round(aggregation.avg * 100) / 100 });
|
|
22
|
-
parts.push({ key: 'min', label: 'Min:', value: aggregation.min });
|
|
23
|
-
parts.push({ key: 'max', label: 'Max:', value: aggregation.max });
|
|
24
|
-
parts.push({ key: 'count', label: 'Count:', value: aggregation.count });
|
|
25
|
-
}
|
|
26
|
-
return parts;
|
|
27
|
-
}
|