@opencorestack/opengridx 0.1.5 → 0.1.7
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 +27 -0
- package/README.md +9 -0
- package/dist/index.d.ts +6 -0
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +319 -319
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +7 -7
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +177 -0
- package/docs/README.md +60 -0
- package/docs/components/cell.md +44 -0
- package/docs/components/column-group-header.md +57 -0
- package/docs/components/column-resize.md +63 -0
- package/docs/components/column-visibility.md +61 -0
- package/docs/components/datagrid.md +70 -0
- package/docs/components/filter-panel.md +26 -0
- package/docs/components/header.md +39 -0
- package/docs/components/pagination.md +34 -0
- package/docs/components/row.md +31 -0
- package/docs/components/toolbar.md +40 -0
- package/docs/components/tooltip.md +68 -0
- package/docs/customization/slots-api.md +227 -0
- package/docs/customization/theming.md +293 -0
- package/docs/features/aggregation-pivot.md +79 -0
- package/docs/features/cell-spanning.md +520 -0
- package/docs/features/clipboard.md +76 -0
- package/docs/features/custom-pagination.md +143 -0
- package/docs/features/data-source.md +72 -0
- package/docs/features/editing-reordering.md +99 -0
- package/docs/features/export-guide.md +434 -0
- package/docs/features/filtering.md +83 -0
- package/docs/features/infinite-scroll.md +87 -0
- package/docs/features/list-view.md +65 -0
- package/docs/features/loading-states.md +68 -0
- package/docs/features/master-detail.md +46 -0
- package/docs/features/pinning.md +67 -0
- package/docs/features/selection.md +70 -0
- package/docs/features/sorting-pagination.md +66 -0
- package/docs/features/state-persistence.md +67 -0
- package/docs/features/tree-data-grouping.md +67 -0
- package/docs/features/virtualization.md +188 -0
- package/docs/roadmap.md +71 -0
- package/lib/components/Cell/Cell.css +119 -0
- package/lib/components/Cell/Cell.tsx +264 -0
- package/lib/components/Cell/GridEditInputCell.css +62 -0
- package/lib/components/Cell/GridEditInputCell.tsx +152 -0
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +67 -0
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +220 -0
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +31 -0
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +85 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +137 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +190 -0
- package/lib/components/DataGrid/DataGrid.css +224 -0
- package/lib/components/DataGrid/DataGrid.tsx +2253 -0
- package/lib/components/DetailPanel/DetailPanel.css +36 -0
- package/lib/components/DetailPanel/DetailPanel.tsx +34 -0
- package/lib/components/FilterPanel/FilterPanel.css +196 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +183 -0
- package/lib/components/FilterPanel/index.ts +1 -0
- package/lib/components/Header/ColumnMenu.css +68 -0
- package/lib/components/Header/ColumnMenu.tsx +249 -0
- package/lib/components/Header/Header.css +224 -0
- package/lib/components/Header/Header.tsx +516 -0
- package/lib/components/ListView/ListView.css +112 -0
- package/lib/components/ListView/ListViewRow.tsx +63 -0
- package/lib/components/Pagination/Pagination.css +91 -0
- package/lib/components/Pagination/Pagination.tsx +151 -0
- package/lib/components/QuickFilter/QuickFilter.css +23 -0
- package/lib/components/QuickFilter/QuickFilter.tsx +79 -0
- package/lib/components/Row/Row.css +126 -0
- package/lib/components/Row/Row.tsx +377 -0
- package/lib/components/Skeleton/Skeleton.css +55 -0
- package/lib/components/Skeleton/Skeleton.tsx +22 -0
- package/lib/components/SkeletonRow/SkeletonRow.css +32 -0
- package/lib/components/SkeletonRow/SkeletonRow.tsx +89 -0
- package/lib/components/SkeletonRow/index.ts +1 -0
- package/lib/components/Toolbar/GlobalSearch.css +113 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +120 -0
- package/lib/components/Toolbar/GridToolbar.css +204 -0
- package/lib/components/Toolbar/GridToolbar.tsx +647 -0
- package/lib/components/Toolbar/PivotPanel.css +212 -0
- package/lib/components/Toolbar/PivotPanel.tsx +239 -0
- package/lib/components/Tooltip/Tooltip.css +59 -0
- package/lib/components/Tooltip/Tooltip.tsx +106 -0
- package/lib/components/ui/Button.css +89 -0
- package/lib/components/ui/Button.tsx +39 -0
- package/lib/components/ui/Checkbox.css +71 -0
- package/lib/components/ui/Checkbox.tsx +66 -0
- package/lib/components/ui/DragHandleIcon.css +19 -0
- package/lib/components/ui/DragHandleIcon.tsx +18 -0
- package/lib/components/ui/ExpandIcon.css +38 -0
- package/lib/components/ui/ExpandIcon.tsx +75 -0
- package/lib/components/ui/Input.css +59 -0
- package/lib/components/ui/Input.tsx +45 -0
- package/lib/components/ui/index.ts +15 -0
- package/lib/hooks/core/useDataGrid.ts +392 -0
- package/lib/hooks/core/useGridApiRef.ts +10 -0
- package/lib/hooks/features/useAggregation.ts +143 -0
- package/lib/hooks/features/useGridClipboard.ts +133 -0
- package/lib/hooks/features/useGridDataSource.ts +190 -0
- package/lib/hooks/features/useGridEditing.ts +100 -0
- package/lib/hooks/features/useGridSpanning.ts +349 -0
- package/lib/hooks/features/usePivot.ts +149 -0
- package/lib/hooks/useColumnReorder.ts +114 -0
- package/lib/hooks/useRowGrouping.ts +302 -0
- package/lib/hooks/useRowReorder.ts +83 -0
- package/lib/hooks/useTreeData.ts +323 -0
- package/lib/index.ts +106 -0
- package/lib/state/index.ts +17 -0
- package/lib/state/types.ts +48 -0
- package/lib/state/useGridStateStorage.ts +113 -0
- package/lib/styles/base/reset.css +21 -0
- package/lib/styles/base/variables.css +161 -0
- package/lib/styles/opengridx.css +27 -0
- package/lib/styles/opengridx.css.d.ts +4 -0
- package/lib/theme/DataGridThemeProvider.tsx +276 -0
- package/lib/theme/defaultStyles.ts +0 -0
- package/lib/theme/index.ts +21 -0
- package/lib/theme/presets.ts +148 -0
- package/lib/theme/types.ts +155 -0
- package/lib/types/index.ts +764 -0
- package/lib/utils/export/exportToExcelAdvanced.ts +516 -0
- package/lib/utils/export/index.ts +550 -0
- package/lib/utils/filtering/index.ts +240 -0
- package/lib/utils/pinning/index.ts +282 -0
- package/lib/utils/sorting/index.ts +55 -0
- package/lib/utils/virtualization/index.ts +106 -0
- package/lib/vite-env.d.ts +1 -0
- package/llms.txt +12 -2
- package/package.json +13 -4
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Cell Spanning - OpenGridX
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Cell spanning allows cells in the DataGrid to span across multiple columns (column spanning) or multiple rows (row spanning). This is useful for creating complex table layouts, summary rows, merged headers, and hierarchical data presentations.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Column Spanning**: Cells can span horizontally across multiple columns
|
|
10
|
+
- **Row Spanning**: Cells can span vertically across multiple rows
|
|
11
|
+
- **Dynamic Spanning**: Span values can be calculated dynamically based on cell data
|
|
12
|
+
- **Static Spanning**: Span values can be set as fixed numbers
|
|
13
|
+
- **Accessibility**: Automatic ARIA attributes (`aria-colspan`, `aria-rowspan`)
|
|
14
|
+
- **CSS Grid Integration**: Uses CSS Grid for proper layout
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ⚠️ Important: Feature Conflicts
|
|
19
|
+
|
|
20
|
+
When using `colSpan` or `rowSpan`, some other DataGrid features may not work as expected or may create a confusing grid layout. **To avoid layout issues and unexpected behavior, consider disabling the following features for any columns that use cell spanning:**
|
|
21
|
+
|
|
22
|
+
### Features to Disable
|
|
23
|
+
|
|
24
|
+
1. **Sorting** (`sortable: false`)
|
|
25
|
+
- When rows are reordered by sorting, spanned cells may break visual continuity
|
|
26
|
+
- Subtotal/summary rows with spanning should remain in fixed positions
|
|
27
|
+
|
|
28
|
+
2. **Filtering** (when implemented)
|
|
29
|
+
- Hidden rows can cause spanned cells to appear disconnected or incomplete
|
|
30
|
+
- Filtering may hide parts of a spanned cell group
|
|
31
|
+
|
|
32
|
+
3. **Column Reordering** (`disableReorder: true`)
|
|
33
|
+
- Moving columns can break the spanning logic
|
|
34
|
+
- Spanned cells depend on specific column order
|
|
35
|
+
|
|
36
|
+
4. **Column Hiding** (`hideable: false`)
|
|
37
|
+
- Hiding columns within a span creates layout gaps and visual artifacts
|
|
38
|
+
- The grid cannot properly render partial spans
|
|
39
|
+
|
|
40
|
+
5. **Column Pinning** (avoid pinning spanned columns)
|
|
41
|
+
- Pinned columns with spanning can cause rendering conflicts
|
|
42
|
+
- Spanning across pinned and non-pinned columns is not supported
|
|
43
|
+
|
|
44
|
+
### Recommended Configuration
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
const columns: GridColDef<Row>[] = [
|
|
48
|
+
{
|
|
49
|
+
field: 'summary',
|
|
50
|
+
headerName: 'Summary',
|
|
51
|
+
width: 200,
|
|
52
|
+
colSpan: (params) => params.row.isTotal ? 3 : 1,
|
|
53
|
+
// Disable conflicting features
|
|
54
|
+
sortable: false, // ✅ Disable sorting
|
|
55
|
+
hideable: false, // ✅ Disable column hiding
|
|
56
|
+
disableReorder: true, // ✅ Disable column reordering
|
|
57
|
+
// Note: Avoid pinning this column
|
|
58
|
+
renderCell: (params) => {
|
|
59
|
+
if (params.row.isTotal) {
|
|
60
|
+
return <strong>Total: {params.value}</strong>;
|
|
61
|
+
}
|
|
62
|
+
return params.value;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
// Other columns...
|
|
66
|
+
];
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Why These Limitations Exist
|
|
70
|
+
|
|
71
|
+
Cell spanning fundamentally changes the grid's layout model:
|
|
72
|
+
- **Sorting/Filtering** change row order/visibility, breaking span relationships
|
|
73
|
+
- **Column operations** (reorder/hide/pin) modify the column structure that spans depend on
|
|
74
|
+
- **CSS Grid** requires stable column positions for proper spanning
|
|
75
|
+
|
|
76
|
+
**Best Practice**: Use cell spanning primarily for static summary rows, headers, or fixed layout sections where these features are not needed.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Column Spanning
|
|
81
|
+
|
|
82
|
+
### Basic Usage
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { DataGrid, GridColDef } from '@opencorestack/opengridx';
|
|
86
|
+
|
|
87
|
+
interface Row {
|
|
88
|
+
id: number;
|
|
89
|
+
name: string;
|
|
90
|
+
value: number;
|
|
91
|
+
isTotal?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const columns: GridColDef<Row>[] = [
|
|
95
|
+
{
|
|
96
|
+
field: 'name',
|
|
97
|
+
headerName: 'Name',
|
|
98
|
+
width: 200,
|
|
99
|
+
// Static column span
|
|
100
|
+
colSpan: 2 // This cell will span 2 columns
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
field: 'value',
|
|
104
|
+
headerName: 'Value',
|
|
105
|
+
width: 150
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Dynamic Column Spanning
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
const columns: GridColDef<Row>[] = [
|
|
114
|
+
{
|
|
115
|
+
field: 'name',
|
|
116
|
+
headerName: 'Name',
|
|
117
|
+
width: 200,
|
|
118
|
+
// Dynamic column span based on row data
|
|
119
|
+
colSpan: (params) => {
|
|
120
|
+
// Span across both columns for total rows
|
|
121
|
+
if (params.row.isTotal) {
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
return 1; // Normal span for regular rows
|
|
125
|
+
},
|
|
126
|
+
renderCell: (params) => {
|
|
127
|
+
if (params.row.isTotal) {
|
|
128
|
+
return <strong>Total: {params.row.name}</strong>;
|
|
129
|
+
}
|
|
130
|
+
return params.value;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
field: 'value',
|
|
135
|
+
headerName: 'Value',
|
|
136
|
+
width: 150
|
|
137
|
+
}
|
|
138
|
+
];
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Row Spanning
|
|
144
|
+
|
|
145
|
+
### Basic Usage
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const columns: GridColDef<Row>[] = [
|
|
149
|
+
{
|
|
150
|
+
field: 'category',
|
|
151
|
+
headerName: 'Category',
|
|
152
|
+
width: 150,
|
|
153
|
+
// Static row span
|
|
154
|
+
rowSpan: 3 // This cell will span 3 rows
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
field: 'item',
|
|
158
|
+
headerName: 'Item',
|
|
159
|
+
width: 200
|
|
160
|
+
}
|
|
161
|
+
];
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Dynamic Row Spanning
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
const columns: GridColDef<Row>[] = [
|
|
168
|
+
{
|
|
169
|
+
field: 'category',
|
|
170
|
+
headerName: 'Category',
|
|
171
|
+
width: 150,
|
|
172
|
+
// Dynamic row span based on data
|
|
173
|
+
rowSpan: (params) => {
|
|
174
|
+
// Calculate how many rows this category spans
|
|
175
|
+
const categoryRows = rows.filter(
|
|
176
|
+
r => r.category === params.row.category
|
|
177
|
+
);
|
|
178
|
+
return categoryRows.length;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
field: 'item',
|
|
183
|
+
headerName: 'Item',
|
|
184
|
+
width: 200
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Combined Column and Row Spanning
|
|
192
|
+
|
|
193
|
+
You can use both `colSpan` and `rowSpan` together:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const columns: GridColDef<Row>[] = [
|
|
197
|
+
{
|
|
198
|
+
field: 'header',
|
|
199
|
+
headerName: 'Header',
|
|
200
|
+
width: 200,
|
|
201
|
+
colSpan: (params) => params.row.isHeader ? 3 : 1,
|
|
202
|
+
rowSpan: (params) => params.row.isHeader ? 2 : 1,
|
|
203
|
+
renderCell: (params) => {
|
|
204
|
+
if (params.row.isHeader) {
|
|
205
|
+
return (
|
|
206
|
+
<div style={{
|
|
207
|
+
fontSize: '1.2em',
|
|
208
|
+
fontWeight: 'bold',
|
|
209
|
+
textAlign: 'center',
|
|
210
|
+
padding: '20px'
|
|
211
|
+
}}>
|
|
212
|
+
{params.value}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return params.value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Complete Example: Sales Report with Subtotals
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { useState } from 'react';
|
|
228
|
+
import { DataGrid, GridColDef } from '@opencorestack/opengridx';
|
|
229
|
+
|
|
230
|
+
interface SalesData {
|
|
231
|
+
id: number;
|
|
232
|
+
region: string;
|
|
233
|
+
product: string;
|
|
234
|
+
q1: number;
|
|
235
|
+
q2: number;
|
|
236
|
+
q3: number;
|
|
237
|
+
q4: number;
|
|
238
|
+
total?: number;
|
|
239
|
+
isSubtotal?: boolean;
|
|
240
|
+
isGrandTotal?: boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default function SalesReport() {
|
|
244
|
+
const [rows] = useState<SalesData[]>([
|
|
245
|
+
// Regular rows
|
|
246
|
+
{ id: 1, region: 'North', product: 'Laptops', q1: 45000, q2: 52000, q3: 48000, q4: 55000 },
|
|
247
|
+
{ id: 2, region: 'North', product: 'Tablets', q1: 23000, q2: 28000, q3: 25000, q4: 30000 },
|
|
248
|
+
|
|
249
|
+
// Subtotal row - Region column spans across Region and Product
|
|
250
|
+
{
|
|
251
|
+
id: 3,
|
|
252
|
+
region: 'North',
|
|
253
|
+
product: '',
|
|
254
|
+
q1: 68000,
|
|
255
|
+
q2: 80000,
|
|
256
|
+
q3: 73000,
|
|
257
|
+
q4: 85000,
|
|
258
|
+
total: 306000,
|
|
259
|
+
isSubtotal: true
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
// Grand total row
|
|
263
|
+
{
|
|
264
|
+
id: 4,
|
|
265
|
+
region: '',
|
|
266
|
+
product: '',
|
|
267
|
+
q1: 68000,
|
|
268
|
+
q2: 80000,
|
|
269
|
+
q3: 73000,
|
|
270
|
+
q4: 85000,
|
|
271
|
+
total: 306000,
|
|
272
|
+
isGrandTotal: true
|
|
273
|
+
}
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
const columns: GridColDef<SalesData>[] = [
|
|
277
|
+
{
|
|
278
|
+
field: 'region',
|
|
279
|
+
headerName: 'Region',
|
|
280
|
+
width: 150,
|
|
281
|
+
// Span across Region and Product columns for subtotal/total rows
|
|
282
|
+
colSpan: (params) => {
|
|
283
|
+
if (params.row.isSubtotal || params.row.isGrandTotal) {
|
|
284
|
+
return 2;
|
|
285
|
+
}
|
|
286
|
+
return 1;
|
|
287
|
+
},
|
|
288
|
+
renderCell: (params) => {
|
|
289
|
+
if (params.row.isGrandTotal) {
|
|
290
|
+
return <strong style={{ fontSize: '1.1em' }}>Grand Total</strong>;
|
|
291
|
+
}
|
|
292
|
+
if (params.row.isSubtotal) {
|
|
293
|
+
return <em>{params.row.region} Subtotal</em>;
|
|
294
|
+
}
|
|
295
|
+
return params.value;
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
field: 'product',
|
|
300
|
+
headerName: 'Product',
|
|
301
|
+
width: 150
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
field: 'q1',
|
|
305
|
+
headerName: 'Q1',
|
|
306
|
+
width: 120,
|
|
307
|
+
type: 'number',
|
|
308
|
+
valueFormatter: ({ value }) => `$${value.toLocaleString()}`
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
field: 'q2',
|
|
312
|
+
headerName: 'Q2',
|
|
313
|
+
width: 120,
|
|
314
|
+
type: 'number',
|
|
315
|
+
valueFormatter: ({ value }) => `$${value.toLocaleString()}`
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
field: 'q3',
|
|
319
|
+
headerName: 'Q3',
|
|
320
|
+
width: 120,
|
|
321
|
+
type: 'number',
|
|
322
|
+
valueFormatter: ({ value }) => `$${value.toLocaleString()}`
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
field: 'q4',
|
|
326
|
+
headerName: 'Q4',
|
|
327
|
+
width: 120,
|
|
328
|
+
type: 'number',
|
|
329
|
+
valueFormatter: ({ value }) => `$${value.toLocaleString()}`
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
field: 'total',
|
|
333
|
+
headerName: 'Total',
|
|
334
|
+
width: 140,
|
|
335
|
+
type: 'number',
|
|
336
|
+
valueFormatter: ({ value }) => value ? `$${value.toLocaleString()}` : ''
|
|
337
|
+
}
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div style={{ height: 600, width: '100%' }}>
|
|
342
|
+
<DataGrid
|
|
343
|
+
rows={rows}
|
|
344
|
+
columns={columns}
|
|
345
|
+
rowHeight={48}
|
|
346
|
+
headerHeight={56}
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## API Reference
|
|
356
|
+
|
|
357
|
+
### GridColDef Properties
|
|
358
|
+
|
|
359
|
+
#### `colSpan`
|
|
360
|
+
|
|
361
|
+
Defines how many columns a cell should span.
|
|
362
|
+
|
|
363
|
+
**Type**: `number | ((params: GridRenderCellParams<R>) => number)`
|
|
364
|
+
|
|
365
|
+
**Default**: `undefined` (no spanning)
|
|
366
|
+
|
|
367
|
+
**Examples**:
|
|
368
|
+
```tsx
|
|
369
|
+
// Static spanning
|
|
370
|
+
colSpan: 2
|
|
371
|
+
|
|
372
|
+
// Dynamic spanning
|
|
373
|
+
colSpan: (params) => params.row.isTotal ? 3 : 1
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### `rowSpan`
|
|
377
|
+
|
|
378
|
+
Defines how many rows a cell should span.
|
|
379
|
+
|
|
380
|
+
**Type**: `number | ((params: GridRenderCellParams<R>) => number)`
|
|
381
|
+
|
|
382
|
+
**Default**: `undefined` (no spanning)
|
|
383
|
+
|
|
384
|
+
**Examples**:
|
|
385
|
+
```tsx
|
|
386
|
+
// Static spanning
|
|
387
|
+
rowSpan: 2
|
|
388
|
+
|
|
389
|
+
// Dynamic spanning
|
|
390
|
+
rowSpan: (params) => params.row.categorySize || 1
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### GridRenderCellParams
|
|
394
|
+
|
|
395
|
+
Parameters passed to the `colSpan` and `rowSpan` functions:
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
interface GridRenderCellParams<R> {
|
|
399
|
+
value: any; // Cell value
|
|
400
|
+
row: R; // Complete row data
|
|
401
|
+
field: string; // Column field name
|
|
402
|
+
colDef: GridColDef<R>; // Column definition
|
|
403
|
+
rowIndex: number; // Row index
|
|
404
|
+
colIndex: number; // Column index
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Styling Spanned Cells
|
|
411
|
+
|
|
412
|
+
Spanned cells can be styled using custom `renderCell`:
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
{
|
|
416
|
+
field: 'name',
|
|
417
|
+
headerName: 'Name',
|
|
418
|
+
colSpan: (params) => params.row.isHeader ? 3 : 1,
|
|
419
|
+
renderCell: (params) => {
|
|
420
|
+
if (params.row.isHeader) {
|
|
421
|
+
return (
|
|
422
|
+
<div style={{
|
|
423
|
+
width: '100%',
|
|
424
|
+
height: '100%',
|
|
425
|
+
display: 'flex',
|
|
426
|
+
alignItems: 'center',
|
|
427
|
+
justifyContent: 'center',
|
|
428
|
+
backgroundColor: '#e3f2fd',
|
|
429
|
+
fontWeight: 'bold',
|
|
430
|
+
fontSize: '1.2em',
|
|
431
|
+
borderBottom: '2px solid #1976d2'
|
|
432
|
+
}}>
|
|
433
|
+
{params.value}
|
|
434
|
+
</div>
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return params.value;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Accessibility
|
|
445
|
+
|
|
446
|
+
The DataGrid automatically adds ARIA attributes for spanned cells:
|
|
447
|
+
|
|
448
|
+
- `aria-colspan`: Added when `colSpan > 1`
|
|
449
|
+
- `aria-rowspan`: Added when `rowSpan > 1`
|
|
450
|
+
|
|
451
|
+
This ensures screen readers properly announce the spanning behavior.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Best Practices
|
|
456
|
+
|
|
457
|
+
1. **Use Dynamic Spanning**: Prefer dynamic spanning functions over static values for flexibility
|
|
458
|
+
2. **Combine with renderCell**: Always provide custom rendering for spanned cells to improve visual clarity
|
|
459
|
+
3. **Consider Performance**: Spanning calculations run on every render, so keep them lightweight
|
|
460
|
+
4. **Test with Virtualization**: Ensure spanning works correctly with virtual scrolling
|
|
461
|
+
5. **Accessibility**: Provide meaningful content in spanned cells for screen readers
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Limitations
|
|
466
|
+
|
|
467
|
+
### Feature Conflicts
|
|
468
|
+
|
|
469
|
+
**⚠️ See the [Feature Conflicts](#️-important-feature-conflicts) section above for important information about which features should be disabled when using cell spanning.**
|
|
470
|
+
|
|
471
|
+
### Technical Limitations
|
|
472
|
+
|
|
473
|
+
1. **Virtualization**:
|
|
474
|
+
- Row spanning may have visual artifacts with virtualization if spans cross virtual boundaries
|
|
475
|
+
- Large row spans (>10 rows) may cause performance issues with virtual scrolling
|
|
476
|
+
- Consider using row pinning for summary rows instead of row spanning
|
|
477
|
+
|
|
478
|
+
2. **Pinned Columns**:
|
|
479
|
+
- Column spanning with pinned columns requires careful consideration
|
|
480
|
+
- Spanning across pinned and non-pinned columns is not supported
|
|
481
|
+
- Use spanning only within pinned or non-pinned column groups
|
|
482
|
+
|
|
483
|
+
3. **Sorting/Filtering**:
|
|
484
|
+
- Spanned cells may break visual continuity when rows are reordered
|
|
485
|
+
- Always disable sorting on columns with dynamic spanning
|
|
486
|
+
- Filtering can hide parts of spanned cell groups
|
|
487
|
+
|
|
488
|
+
4. **Column Operations**:
|
|
489
|
+
- Column reordering can break spanning logic
|
|
490
|
+
- Hiding columns within a span creates layout gaps
|
|
491
|
+
- Column resizing may not work optimally with spanned cells
|
|
492
|
+
|
|
493
|
+
5. **Editing**:
|
|
494
|
+
- Cell editing on spanned cells may have unexpected behavior
|
|
495
|
+
- Consider disabling editing for columns with spanning
|
|
496
|
+
|
|
497
|
+
### Workarounds
|
|
498
|
+
|
|
499
|
+
- **For Summary Rows**: Use [Row Pinning](./pinning.md) with pinned bottom rows instead of row spanning
|
|
500
|
+
- **For Headers**: Consider using column grouping instead of cell spanning
|
|
501
|
+
- **For Hierarchical Data**: Use [Tree Data](./tree-data-grouping.md) instead of row spanning
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Browser Support
|
|
506
|
+
|
|
507
|
+
Cell spanning uses CSS Grid, which is supported in:
|
|
508
|
+
- Chrome 57+
|
|
509
|
+
- Firefox 52+
|
|
510
|
+
- Safari 10.1+
|
|
511
|
+
- Edge 16+
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## See Also
|
|
516
|
+
|
|
517
|
+
- [Row Pinning](./pinning.md)
|
|
518
|
+
- [Column Pinning](./pinning.md)
|
|
519
|
+
- [Custom Cell Rendering](./cell.md)
|
|
520
|
+
- [Aggregation](./aggregation-pivot.md)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 📋 Clipboard & Copy/Paste
|
|
2
|
+
|
|
3
|
+
OpenGridX supports copying grid data directly to the clipboard. This feature allows users to effortlessly move data from the grid to external applications like Microsoft Excel, Google Sheets, or text editors.
|
|
4
|
+
|
|
5
|
+
## 📑 Overview
|
|
6
|
+
- **Zero Configuration**: Enabled by default with the standard `DataGrid`.
|
|
7
|
+
- **Selected Rows**: Only selected rows are copied when selection is active.
|
|
8
|
+
- **TSV Format**: Data is copied in Tab-Separated Values (TSV) format for maximum compatibility.
|
|
9
|
+
- **Value Formatters**: Respects `valueFormatter` for accurate data representation.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⌨️ Keyboard Shortcuts
|
|
14
|
+
Selection and copying follow standard spreadsheet metaphors:
|
|
15
|
+
| Shortcut | Action |
|
|
16
|
+
| :--- | :--- |
|
|
17
|
+
| `Ctrl + C` / `Cmd + C` | Copy currently selected rows to the clipboard. |
|
|
18
|
+
|
|
19
|
+
> **Note**: The copy action is ignored if an input, textarea, or another editable element currently has focus.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🛠️ Usage
|
|
24
|
+
|
|
25
|
+
### Auto-Integration
|
|
26
|
+
If `checkboxSelection` is enabled and rows are selected, pressing `Ctrl + C` will copy those rows.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<DataGrid
|
|
30
|
+
rows={rows}
|
|
31
|
+
columns={columns}
|
|
32
|
+
checkboxSelection
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Programmatic Copy
|
|
37
|
+
You can trigger a copy action programmatically using the exported hook if building a custom toolbar.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { useGridClipboard } from '@opencorestack/opengridx';
|
|
41
|
+
|
|
42
|
+
function MyCustomToolbar({ state, columns, getVisibleRows }) {
|
|
43
|
+
const { copySelectedRows } = useGridClipboard({ state, columns, getVisibleRows });
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<button onClick={copySelectedRows}>
|
|
47
|
+
Copy to Clipboard
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## ⚙️ How it Works
|
|
56
|
+
|
|
57
|
+
1. **Header Row**: The first line of the copied content includes the column header names.
|
|
58
|
+
2. **Data Transformation**: The grid iterates through the selected rows and columns:
|
|
59
|
+
- **Exclusions**: Special internal columns (checkboxes, expanders, etc.) are excluded.
|
|
60
|
+
- **Formatting**: If a column has a `valueFormatter`, it is applied to the raw value before copying.
|
|
61
|
+
3. **TSV Generation**: Fields are joined by tabs (`\t`), and rows are joined by newlines (`\n`).
|
|
62
|
+
4. **Clipboard API**: Uses the modern `navigator.clipboard.writeText` API.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📊 Example Output
|
|
67
|
+
If you copy a grid with three rows, the clipboard content will look like this:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
ID Name Email Status
|
|
71
|
+
1 Jon Snow jon@winterfell.com Active
|
|
72
|
+
2 Cersei Lannister cersei@kingslanding.com Inactive
|
|
73
|
+
3 Jaime Lannister jaime@kingslanding.com Active
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When pasted into Excel or Google Sheets, this content will automatically be distributed into the correct cells.
|