@opencorestack/opengridx 0.1.8 → 1.0.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/CHANGELOG.md +59 -0
- package/dist/index.d.ts +60 -41
- package/dist/opengridx.es.js +5271 -4660
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +12 -12
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/README.md +14 -0
- package/docs/components/aggregation-footer.md +48 -0
- package/docs/components/datagrid.md +3 -0
- package/docs/components/empty-state.md +50 -0
- package/docs/components/error-overlay.md +40 -0
- package/docs/roadmap.md +20 -1
- package/lib/components/Cell/Cell.tsx +61 -64
- package/lib/components/Cell/GridEditInputCell.tsx +4 -4
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.regression.test.tsx +132 -0
- package/lib/components/DataGrid/DataGrid.smoke.test.tsx +58 -0
- package/lib/components/DataGrid/DataGrid.tsx +396 -1654
- package/lib/components/DataGrid/GridAggregationFooter.tsx +68 -0
- package/lib/components/DataGrid/GridEmptyState.tsx +16 -0
- package/lib/components/DataGrid/GridErrorOverlay.tsx +33 -0
- package/lib/components/DataGrid/GridListView.tsx +116 -0
- package/lib/components/DataGrid/GridPinnedRows.tsx +98 -0
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +124 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +202 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +18 -10
- package/lib/components/Header/ColumnMenu.tsx +2 -2
- package/lib/components/Header/Header.tsx +4 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Row/Row.tsx +46 -45
- package/lib/components/SkeletonRow/SkeletonRow.tsx +9 -2
- package/lib/components/Toolbar/GlobalSearch.tsx +1 -1
- package/lib/components/Toolbar/GridToolbar.tsx +26 -5
- package/lib/components/Toolbar/PivotPanel.tsx +7 -2
- package/lib/components/ui/DragHandleIcon.tsx +1 -1
- package/lib/hooks/core/useDataGrid.regression.test.ts +66 -0
- package/lib/hooks/core/useDataGrid.ts +15 -35
- package/lib/hooks/core/useGridColumns.tsx +228 -0
- package/lib/hooks/core/useGridControlledState.ts +192 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +371 -0
- package/lib/hooks/core/useGridRowPipeline.ts +109 -0
- package/lib/hooks/core/useGridScrollSync.ts +52 -0
- package/lib/hooks/core/useGridStateSnapshot.ts +63 -0
- package/lib/hooks/core/useGridVirtualization.ts +186 -0
- package/lib/hooks/core/useGridVisibleRows.ts +66 -0
- package/lib/hooks/core/useLayout.ts +348 -0
- package/lib/hooks/features/useAggregation.test.ts +135 -0
- package/lib/hooks/features/useAggregation.ts +7 -7
- package/lib/hooks/features/useGridClipboard.ts +1 -1
- package/lib/hooks/features/useGridDataSource.ts +2 -2
- package/lib/hooks/features/useGridEditing.ts +31 -32
- package/lib/hooks/features/usePivot.test.ts +202 -0
- package/lib/hooks/features/usePivot.ts +0 -0
- package/lib/hooks/useColumnReorder.ts +1 -1
- package/lib/hooks/useRowGrouping.ts +14 -14
- package/lib/hooks/useTreeData.ts +1 -1
- package/lib/state/types.ts +1 -1
- package/lib/state/useGridStateStorage.ts +2 -2
- package/lib/types/index.ts +48 -32
- package/lib/utils/export/exportToExcelAdvanced.ts +14 -14
- package/lib/utils/export/index.ts +23 -18
- package/lib/utils/filtering/filtering.test.ts +178 -0
- package/lib/utils/filtering/index.ts +20 -20
- package/lib/utils/sorting/index.ts +1 -1
- package/lib/utils/sorting/sorting.test.ts +88 -0
- package/package.json +12 -5
- package/lib/utils/virtualization/index.ts +0 -106
|
@@ -2,36 +2,36 @@
|
|
|
2
2
|
import type { GridRowModel, GridFilterItem, GridFilterModel, GridFilterGroup } from '../../types';
|
|
3
3
|
|
|
4
4
|
export const FILTER_OPERATORS = {
|
|
5
|
-
contains: (value:
|
|
5
|
+
contains: (value: unknown, filterValue: unknown): boolean => {
|
|
6
6
|
if (value == null || filterValue == null) return false;
|
|
7
7
|
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
|
|
8
8
|
},
|
|
9
9
|
|
|
10
|
-
equals: (value:
|
|
10
|
+
equals: (value: unknown, filterValue: unknown): boolean => {
|
|
11
11
|
if (value == null && filterValue == null) return true;
|
|
12
12
|
if (value == null || filterValue == null) return false;
|
|
13
13
|
return String(value).toLowerCase() === String(filterValue).toLowerCase();
|
|
14
14
|
},
|
|
15
15
|
|
|
16
|
-
startsWith: (value:
|
|
16
|
+
startsWith: (value: unknown, filterValue: unknown): boolean => {
|
|
17
17
|
if (value == null || filterValue == null) return false;
|
|
18
18
|
return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase());
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
-
endsWith: (value:
|
|
21
|
+
endsWith: (value: unknown, filterValue: unknown): boolean => {
|
|
22
22
|
if (value == null || filterValue == null) return false;
|
|
23
23
|
return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase());
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
-
isEmpty: (value:
|
|
26
|
+
isEmpty: (value: unknown): boolean => {
|
|
27
27
|
return value == null || String(value).trim() === '';
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
-
isNotEmpty: (value:
|
|
30
|
+
isNotEmpty: (value: unknown): boolean => {
|
|
31
31
|
return value != null && String(value).trim() !== '';
|
|
32
32
|
},
|
|
33
33
|
|
|
34
|
-
'>': (value:
|
|
34
|
+
'>': (value: unknown, filterValue: unknown): boolean => {
|
|
35
35
|
if (value == null || filterValue == null) return false;
|
|
36
36
|
const numValue = Number(value);
|
|
37
37
|
const numFilter = Number(filterValue);
|
|
@@ -39,7 +39,7 @@ export const FILTER_OPERATORS = {
|
|
|
39
39
|
return numValue > numFilter;
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
'>=': (value:
|
|
42
|
+
'>=': (value: unknown, filterValue: unknown): boolean => {
|
|
43
43
|
if (value == null || filterValue == null) return false;
|
|
44
44
|
const numValue = Number(value);
|
|
45
45
|
const numFilter = Number(filterValue);
|
|
@@ -47,7 +47,7 @@ export const FILTER_OPERATORS = {
|
|
|
47
47
|
return numValue >= numFilter;
|
|
48
48
|
},
|
|
49
49
|
|
|
50
|
-
'<': (value:
|
|
50
|
+
'<': (value: unknown, filterValue: unknown): boolean => {
|
|
51
51
|
if (value == null || filterValue == null) return false;
|
|
52
52
|
const numValue = Number(value);
|
|
53
53
|
const numFilter = Number(filterValue);
|
|
@@ -55,7 +55,7 @@ export const FILTER_OPERATORS = {
|
|
|
55
55
|
return numValue < numFilter;
|
|
56
56
|
},
|
|
57
57
|
|
|
58
|
-
'<=': (value:
|
|
58
|
+
'<=': (value: unknown, filterValue: unknown): boolean => {
|
|
59
59
|
if (value == null || filterValue == null) return false;
|
|
60
60
|
const numValue = Number(value);
|
|
61
61
|
const numFilter = Number(filterValue);
|
|
@@ -63,7 +63,7 @@ export const FILTER_OPERATORS = {
|
|
|
63
63
|
return numValue <= numFilter;
|
|
64
64
|
},
|
|
65
65
|
|
|
66
|
-
'!=': (value:
|
|
66
|
+
'!=': (value: unknown, filterValue: unknown): boolean => {
|
|
67
67
|
if (filterValue == null || filterValue === '') return true;
|
|
68
68
|
// Numeric comparison if both are numbers, otherwise string
|
|
69
69
|
const numV = Number(value);
|
|
@@ -72,28 +72,28 @@ export const FILTER_OPERATORS = {
|
|
|
72
72
|
return String(value).toLowerCase() !== String(filterValue).toLowerCase();
|
|
73
73
|
},
|
|
74
74
|
|
|
75
|
-
isAnyOf: (value:
|
|
75
|
+
isAnyOf: (value: unknown, filterValue: unknown[]): boolean => {
|
|
76
76
|
if (value == null || !Array.isArray(filterValue)) return false;
|
|
77
|
-
return filterValue.some(v =>
|
|
77
|
+
return filterValue.some(v =>
|
|
78
78
|
String(value).toLowerCase() === String(v).toLowerCase()
|
|
79
79
|
);
|
|
80
80
|
},
|
|
81
81
|
|
|
82
|
-
not: (value:
|
|
82
|
+
not: (value: unknown, filterValue: unknown): boolean => {
|
|
83
83
|
if (value == null && filterValue == null) return false;
|
|
84
84
|
if (value == null || filterValue == null) return true;
|
|
85
85
|
return String(value).toLowerCase() !== String(filterValue).toLowerCase();
|
|
86
86
|
},
|
|
87
87
|
|
|
88
88
|
// Used by boolean and singleSelect — case-insensitive string comparison
|
|
89
|
-
is: (value:
|
|
89
|
+
is: (value: unknown, filterValue: unknown): boolean => {
|
|
90
90
|
if (filterValue == null || filterValue === '') return true;
|
|
91
91
|
const rowStr = String(value).toLowerCase();
|
|
92
92
|
const filterStr = String(filterValue).toLowerCase();
|
|
93
93
|
return rowStr === filterStr;
|
|
94
94
|
},
|
|
95
95
|
|
|
96
|
-
'=': (value:
|
|
96
|
+
'=': (value: unknown, filterValue: unknown): boolean => {
|
|
97
97
|
if (value == null || filterValue == null || filterValue === '') return false;
|
|
98
98
|
return Number(value) === Number(filterValue);
|
|
99
99
|
},
|
|
@@ -119,7 +119,7 @@ export function applyFilterItem<R extends GridRowModel>(
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Apply the operator
|
|
122
|
-
return operatorFn(rowValue, value);
|
|
122
|
+
return (operatorFn as (a: unknown, b: unknown) => boolean)(rowValue, value);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -155,9 +155,9 @@ export function isRowMatchingFilter<R extends GridRowModel>(
|
|
|
155
155
|
): boolean {
|
|
156
156
|
// Normalize input: GridFilterModel matches GridFilterGroup shape largely, but let's be safe
|
|
157
157
|
|
|
158
|
-
const items =
|
|
159
|
-
const logicOperator =
|
|
160
|
-
const quickFilterValues = (filterModel
|
|
158
|
+
const items = filterModel.items ?? [];
|
|
159
|
+
const logicOperator = filterModel.logicOperator ?? 'and';
|
|
160
|
+
const quickFilterValues = ('quickFilterValues' in filterModel ? filterModel.quickFilterValues : undefined) ?? [];
|
|
161
161
|
|
|
162
162
|
if (quickFilterValues.length > 0 && !applyQuickFilter(row, quickFilterValues)) {
|
|
163
163
|
return false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import type { GridRowModel, GridSortItem } from '../../types';
|
|
3
3
|
|
|
4
|
-
export function compareValues(a:
|
|
4
|
+
export function compareValues(a: unknown, b: unknown, direction: 'asc' | 'desc'): number {
|
|
5
5
|
|
|
6
6
|
if (a == null && b == null) return 0;
|
|
7
7
|
if (a == null) return direction === 'asc' ? 1 : -1;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { sortRows, compareValues } from './index';
|
|
3
|
+
|
|
4
|
+
describe('compareValues', () => {
|
|
5
|
+
describe('null handling', () => {
|
|
6
|
+
it('null === null → 0', () => expect(compareValues(null, null, 'asc')).toBe(0));
|
|
7
|
+
it('null sorts last in asc', () => expect(compareValues(null, 1, 'asc')).toBeGreaterThan(0));
|
|
8
|
+
it('null sorts first in desc', () => expect(compareValues(null, 1, 'desc')).toBeLessThan(0));
|
|
9
|
+
it('non-null before null in asc', () => expect(compareValues(1, null, 'asc')).toBeLessThan(0));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('numbers', () => {
|
|
13
|
+
it('asc: smaller first', () => expect(compareValues(1, 2, 'asc')).toBeLessThan(0));
|
|
14
|
+
it('desc: larger first', () => expect(compareValues(2, 1, 'desc')).toBeLessThan(0));
|
|
15
|
+
it('equal numbers → 0', () => expect(compareValues(5, 5, 'asc')).toBe(0));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('dates', () => {
|
|
19
|
+
const d1 = new Date('2023-01-01');
|
|
20
|
+
const d2 = new Date('2024-01-01');
|
|
21
|
+
it('asc: earlier date first', () => expect(compareValues(d1, d2, 'asc')).toBeLessThan(0));
|
|
22
|
+
it('desc: later date first', () => expect(compareValues(d2, d1, 'desc')).toBeLessThan(0));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('strings', () => {
|
|
26
|
+
it('asc: lexicographic order', () => expect(compareValues('apple', 'banana', 'asc')).toBeLessThan(0));
|
|
27
|
+
it('desc: reversed order', () => expect(compareValues('banana', 'apple', 'desc')).toBeLessThan(0));
|
|
28
|
+
it('case-insensitive', () => expect(compareValues('APPLE', 'apple', 'asc')).toBe(0));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('sortRows', () => {
|
|
33
|
+
const ROWS = [
|
|
34
|
+
{ id: 3, name: 'Charlie', age: 35, dept: 'Engineering' },
|
|
35
|
+
{ id: 1, name: 'Alice', age: 30, dept: 'Marketing' },
|
|
36
|
+
{ id: 4, name: 'Diana', age: 25, dept: 'Engineering' },
|
|
37
|
+
{ id: 2, name: 'Bob', age: 30, dept: 'Marketing' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
it('returns same reference when sortModel is empty', () => {
|
|
41
|
+
const result = sortRows(ROWS, []);
|
|
42
|
+
expect(result).toBe(ROWS);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not mutate the original array', () => {
|
|
46
|
+
const original = [...ROWS];
|
|
47
|
+
sortRows(ROWS, [{ field: 'name', sort: 'asc' }]);
|
|
48
|
+
expect(ROWS).toEqual(original);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('sorts by single field asc', () => {
|
|
52
|
+
const result = sortRows(ROWS, [{ field: 'name', sort: 'asc' }]);
|
|
53
|
+
expect(result.map(r => r.name)).toEqual(['Alice', 'Bob', 'Charlie', 'Diana']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('sorts by single field desc', () => {
|
|
57
|
+
const result = sortRows(ROWS, [{ field: 'age', sort: 'desc' }]);
|
|
58
|
+
expect(result.map(r => r.age)).toEqual([35, 30, 30, 25]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('applies multi-field sort (age asc, name asc for ties)', () => {
|
|
62
|
+
const result = sortRows(ROWS, [
|
|
63
|
+
{ field: 'age', sort: 'asc' },
|
|
64
|
+
{ field: 'name', sort: 'asc' },
|
|
65
|
+
]);
|
|
66
|
+
expect(result.map(r => r.name)).toEqual(['Diana', 'Alice', 'Bob', 'Charlie']);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles null values — nulls sort last in asc', () => {
|
|
70
|
+
const rowsWithNull = [
|
|
71
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
72
|
+
{ id: 2, name: null, age: 25 },
|
|
73
|
+
{ id: 3, name: 'Bob', age: 35 },
|
|
74
|
+
];
|
|
75
|
+
const result = sortRows(rowsWithNull, [{ field: 'name', sort: 'asc' }]);
|
|
76
|
+
expect(result[result.length - 1].name).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('handles rows with identical values — stable relative order preserved', () => {
|
|
80
|
+
const rows = [
|
|
81
|
+
{ id: 1, score: 10 },
|
|
82
|
+
{ id: 2, score: 10 },
|
|
83
|
+
{ id: 3, score: 10 },
|
|
84
|
+
];
|
|
85
|
+
const result = sortRows(rows, [{ field: 'score', sort: 'asc' }]);
|
|
86
|
+
expect(result.map(r => r.id)).toEqual([1, 2, 3]);
|
|
87
|
+
});
|
|
88
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencorestack/opengridx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/opengridx.umd.js",
|
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
"build:lib": "tsc --project tsconfig.build.json && vite build --config vite.config.lib.js && cp lib/styles/opengridx.css.d.ts dist/opengridx.css.d.ts",
|
|
42
42
|
"lint": "eslint .",
|
|
43
43
|
"preview": "vite preview",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"coverage": "vitest run --coverage",
|
|
44
47
|
"prepublishOnly": "npm run build:lib"
|
|
45
48
|
},
|
|
46
49
|
"keywords": [
|
|
@@ -85,6 +88,9 @@
|
|
|
85
88
|
},
|
|
86
89
|
"devDependencies": {
|
|
87
90
|
"@eslint/js": "^9.39.1",
|
|
91
|
+
"@testing-library/jest-dom": "^7.0.0",
|
|
92
|
+
"@testing-library/react": "^16.3.2",
|
|
93
|
+
"@testing-library/user-event": "^14.6.1",
|
|
88
94
|
"@types/node": "^25.0.8",
|
|
89
95
|
"@types/react": "^19.2.5",
|
|
90
96
|
"@types/react-dom": "^19.2.3",
|
|
@@ -94,13 +100,14 @@
|
|
|
94
100
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
95
101
|
"eslint-plugin-react-refresh": "^0.4.24",
|
|
96
102
|
"globals": "^16.5.0",
|
|
103
|
+
"jsdom": "^29.1.1",
|
|
97
104
|
"react": "^19.2.0",
|
|
98
105
|
"react-dom": "^19.2.0",
|
|
106
|
+
"react-router-dom": "^7.13.1",
|
|
99
107
|
"typescript": "^5.9.3",
|
|
108
|
+
"typescript-eslint": "^8.65.0",
|
|
100
109
|
"vite": "^7.2.4",
|
|
101
110
|
"vite-plugin-dts": "^4.5.4",
|
|
102
|
-
"vitest": "^4.0.18"
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
"dependencies": {}
|
|
111
|
+
"vitest": "^4.0.18"
|
|
112
|
+
}
|
|
106
113
|
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { GridRenderContext } from '../../types';
|
|
3
|
-
|
|
4
|
-
export interface VirtualizationParams {
|
|
5
|
-
scrollTop: number;
|
|
6
|
-
scrollLeft: number;
|
|
7
|
-
viewportHeight: number;
|
|
8
|
-
viewportWidth: number;
|
|
9
|
-
rowHeight: number;
|
|
10
|
-
totalRows: number;
|
|
11
|
-
columnWidths: number[];
|
|
12
|
-
overscan?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface VirtualizationResult {
|
|
16
|
-
renderContext: GridRenderContext;
|
|
17
|
-
offsetTop: number;
|
|
18
|
-
offsetLeft: number;
|
|
19
|
-
totalHeight: number;
|
|
20
|
-
totalWidth: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function calculateVirtualization(params: VirtualizationParams): VirtualizationResult {
|
|
24
|
-
const {
|
|
25
|
-
scrollTop,
|
|
26
|
-
scrollLeft,
|
|
27
|
-
viewportHeight,
|
|
28
|
-
viewportWidth,
|
|
29
|
-
rowHeight,
|
|
30
|
-
totalRows,
|
|
31
|
-
columnWidths,
|
|
32
|
-
overscan = 3
|
|
33
|
-
} = params;
|
|
34
|
-
|
|
35
|
-
const firstVisibleRow = Math.floor(scrollTop / rowHeight);
|
|
36
|
-
const lastVisibleRow = Math.min(
|
|
37
|
-
totalRows - 1,
|
|
38
|
-
Math.ceil((scrollTop + viewportHeight) / rowHeight)
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const firstRowIndex = Math.max(0, firstVisibleRow - overscan);
|
|
42
|
-
const lastRowIndex = Math.min(totalRows - 1, lastVisibleRow + overscan);
|
|
43
|
-
|
|
44
|
-
let accumulatedWidth = 0;
|
|
45
|
-
let firstColumnIndex = 0;
|
|
46
|
-
let lastColumnIndex = columnWidths.length - 1;
|
|
47
|
-
let offsetLeft = 0;
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < columnWidths.length; i++) {
|
|
50
|
-
if (accumulatedWidth + columnWidths[i] > scrollLeft) {
|
|
51
|
-
firstColumnIndex = Math.max(0, i - overscan);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
accumulatedWidth += columnWidths[i];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
offsetLeft = columnWidths.slice(0, firstColumnIndex).reduce((sum, width) => sum + width, 0);
|
|
58
|
-
|
|
59
|
-
accumulatedWidth = offsetLeft;
|
|
60
|
-
for (let i = firstColumnIndex; i < columnWidths.length; i++) {
|
|
61
|
-
accumulatedWidth += columnWidths[i];
|
|
62
|
-
if (accumulatedWidth >= scrollLeft + viewportWidth) {
|
|
63
|
-
lastColumnIndex = Math.min(columnWidths.length - 1, i + overscan);
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const totalHeight = totalRows * rowHeight;
|
|
69
|
-
const totalWidth = columnWidths.reduce((sum, width) => sum + width, 0);
|
|
70
|
-
const offsetTop = firstRowIndex * rowHeight;
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
renderContext: {
|
|
74
|
-
firstRowIndex,
|
|
75
|
-
lastRowIndex,
|
|
76
|
-
firstColumnIndex,
|
|
77
|
-
lastColumnIndex
|
|
78
|
-
},
|
|
79
|
-
offsetTop,
|
|
80
|
-
offsetLeft,
|
|
81
|
-
totalHeight,
|
|
82
|
-
totalWidth
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function getRowPosition(rowIndex: number, rowHeight: number): number {
|
|
87
|
-
return rowIndex * rowHeight;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getColumnPosition(colIndex: number, columnWidths: number[]): number {
|
|
91
|
-
return columnWidths.slice(0, colIndex).reduce((sum, width) => sum + width, 0);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function isRowVisible(
|
|
95
|
-
rowIndex: number,
|
|
96
|
-
renderContext: GridRenderContext
|
|
97
|
-
): boolean {
|
|
98
|
-
return rowIndex >= renderContext.firstRowIndex && rowIndex <= renderContext.lastRowIndex;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function isColumnVisible(
|
|
102
|
-
colIndex: number,
|
|
103
|
-
renderContext: GridRenderContext
|
|
104
|
-
): boolean {
|
|
105
|
-
return colIndex >= renderContext.firstColumnIndex && colIndex <= renderContext.lastColumnIndex;
|
|
106
|
-
}
|