react-open-source-grid 1.5.3 → 1.5.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/lib/App.d.ts +3 -0
- package/dist/lib/chunk-FG3FLQAE.js +296 -0
- package/dist/lib/components/AccessibilityDemo.d.ts +8 -0
- package/dist/lib/components/ApiReferencePage.d.ts +2 -0
- package/dist/lib/components/BenchmarkDemo.d.ts +2 -0
- package/dist/lib/components/CellRenderersDemo.d.ts +16 -0
- package/dist/lib/components/CodeBlock.d.ts +10 -0
- package/dist/lib/components/ColumnFiltersDemo.d.ts +5 -0
- package/dist/lib/components/CompleteApiReferencePage.d.ts +2 -0
- package/dist/lib/components/ContextMenuDemo.d.ts +12 -0
- package/dist/lib/components/DataGrid/ARCHITECTURE.md.d.ts +288 -0
- package/dist/lib/components/DataGrid/AdvancedFilterBuilder.d.ts +12 -0
- package/dist/lib/components/DataGrid/CellRenderers.d.ts +64 -0
- package/dist/lib/components/DataGrid/ColumnChooser.d.ts +12 -0
- package/dist/lib/components/DataGrid/ColumnFilters.d.ts +16 -0
- package/dist/lib/components/DataGrid/ContextMenu.d.ts +10 -0
- package/dist/lib/components/DataGrid/DataGrid.d.ts +22 -0
- package/dist/lib/components/DataGrid/DensityToggle.d.ts +23 -0
- package/dist/lib/components/DataGrid/DragHandle.d.ts +7 -0
- package/dist/lib/components/DataGrid/DraggableRow.d.ts +14 -0
- package/dist/lib/components/DataGrid/ExportMenu.d.ts +12 -0
- package/dist/lib/components/DataGrid/FacetedSearch.d.ts +29 -0
- package/dist/lib/components/DataGrid/FilteredSearchBar.d.ts +36 -0
- package/dist/lib/components/DataGrid/FocusTrap.d.ts +12 -0
- package/dist/lib/components/DataGrid/GridApiDemo.d.ts +6 -0
- package/dist/lib/components/DataGrid/GridBody.d.ts +42 -0
- package/dist/lib/components/DataGrid/GridFooter.d.ts +18 -0
- package/dist/lib/components/DataGrid/GridHeader.d.ts +18 -0
- package/dist/lib/components/DataGrid/GridPagination.d.ts +10 -0
- package/dist/lib/components/DataGrid/GroupByPanel.d.ts +9 -0
- package/dist/lib/components/DataGrid/GroupRow.d.ts +31 -0
- package/dist/lib/components/DataGrid/InfiniteScrollDataGrid.d.ts +39 -0
- package/dist/lib/components/DataGrid/LayoutPresetsManager.d.ts +11 -0
- package/dist/lib/components/DataGrid/MarketDataEngine.d.ts +165 -0
- package/dist/lib/components/DataGrid/MarketDataGrid.d.ts +33 -0
- package/dist/lib/components/DataGrid/MarketDataGridUtils.d.ts +13 -0
- package/dist/lib/components/DataGrid/ScreenReaderAnnouncer.d.ts +8 -0
- package/dist/lib/components/DataGrid/ServerSideDataSource.d.ts +136 -0
- package/dist/lib/components/DataGrid/ThemeSelector.d.ts +12 -0
- package/dist/lib/components/DataGrid/Tooltip.d.ts +15 -0
- package/dist/lib/components/DataGrid/TreeRow.d.ts +31 -0
- package/dist/lib/components/DataGrid/VirtualScroller.d.ts +35 -0
- package/dist/lib/components/DataGrid/WebSocketMockFeed.d.ts +121 -0
- package/dist/lib/components/DataGrid/aggregationUtils.d.ts +25 -0
- package/dist/lib/components/DataGrid/contextMenuUtils.d.ts +36 -0
- package/dist/lib/components/DataGrid/demos/TooltipDemo.d.ts +1 -0
- package/dist/lib/components/DataGrid/densityModes.d.ts +42 -0
- package/dist/lib/components/DataGrid/dragRowUtils.d.ts +98 -0
- package/dist/lib/components/DataGrid/exportUtils.d.ts +30 -0
- package/dist/lib/components/DataGrid/filterUtils.d.ts +17 -0
- package/dist/lib/components/DataGrid/gridApi.d.ts +142 -0
- package/dist/lib/components/DataGrid/gridApi.types.d.ts +348 -0
- package/dist/lib/components/DataGrid/gridReducer.d.ts +4 -0
- package/dist/lib/components/DataGrid/groupingUtils.d.ts +17 -0
- package/dist/lib/components/DataGrid/index.d.ts +41 -0
- package/dist/lib/components/DataGrid/layoutPersistence.d.ts +95 -0
- package/dist/lib/components/DataGrid/themes.d.ts +113 -0
- package/dist/lib/components/DataGrid/treeDataUtils.d.ts +97 -0
- package/dist/lib/components/DataGrid/types.d.ts +536 -0
- package/dist/lib/components/DataGrid/useContextMenu.d.ts +31 -0
- package/dist/lib/components/DataGrid/useDensityMode.d.ts +36 -0
- package/dist/lib/components/DataGrid/useFocusTrap.d.ts +14 -0
- package/dist/lib/components/DataGrid/useMarketData.d.ts +57 -0
- package/dist/lib/components/DataGrid/useScreenReaderAnnouncements.d.ts +23 -0
- package/dist/lib/components/DataGrid/useTooltip.d.ts +21 -0
- package/dist/lib/components/DemoGridPage.d.ts +2 -0
- package/dist/lib/components/DensityModeDemo.d.ts +12 -0
- package/dist/lib/components/FacetedSearchDemo.d.ts +8 -0
- package/dist/lib/components/FeatureGallery.d.ts +2 -0
- package/dist/lib/components/FilteredSearchDemo.d.ts +7 -0
- package/dist/lib/components/GridApiDemoPage.d.ts +2 -0
- package/dist/lib/components/HomePage.d.ts +1 -0
- package/dist/lib/components/InfiniteScrollDemo.d.ts +13 -0
- package/dist/lib/components/LayoutPersistenceDemo.d.ts +2 -0
- package/dist/lib/components/LiveMarketDemo.d.ts +18 -0
- package/dist/lib/components/MarketDataExamples.d.ts +42 -0
- package/dist/lib/components/RowDraggingDemo.d.ts +3 -0
- package/dist/lib/components/RowPinningDemo.d.ts +12 -0
- package/dist/lib/components/ThemesDemo.d.ts +17 -0
- package/dist/lib/components/TooltipDemo.d.ts +1 -0
- package/dist/lib/components/TreeDataDemo.d.ts +3 -0
- package/dist/lib/components/VirtualScrollDemo.d.ts +13 -0
- package/dist/lib/index.cjs +12233 -0
- package/dist/lib/index.css +465 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +11827 -0
- package/dist/lib/layoutPersistence-2MPTAEYI.js +20 -0
- package/dist/lib/main.d.ts +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// src/components/DataGrid/layoutPersistence.ts
|
|
2
|
+
var LocalStorageAdapter = class {
|
|
3
|
+
async save(key, preset) {
|
|
4
|
+
try {
|
|
5
|
+
const existingData = localStorage.getItem(key);
|
|
6
|
+
const presets = existingData ? JSON.parse(existingData) : [];
|
|
7
|
+
const index = presets.findIndex((p) => p.id === preset.id);
|
|
8
|
+
if (index !== -1) {
|
|
9
|
+
presets[index] = preset;
|
|
10
|
+
} else {
|
|
11
|
+
presets.push(preset);
|
|
12
|
+
}
|
|
13
|
+
localStorage.setItem(key, JSON.stringify(presets));
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error("Failed to save preset to localStorage:", error);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async load(key, presetId) {
|
|
20
|
+
try {
|
|
21
|
+
const data = localStorage.getItem(key);
|
|
22
|
+
if (!data) return null;
|
|
23
|
+
const presets = JSON.parse(data);
|
|
24
|
+
if (presetId) {
|
|
25
|
+
return presets.find((p) => p.id === presetId) || null;
|
|
26
|
+
}
|
|
27
|
+
return presets;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("Failed to load preset from localStorage:", error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async delete(key, presetId) {
|
|
34
|
+
try {
|
|
35
|
+
const data = localStorage.getItem(key);
|
|
36
|
+
if (!data) return;
|
|
37
|
+
const presets = JSON.parse(data);
|
|
38
|
+
const filtered = presets.filter((p) => p.id !== presetId);
|
|
39
|
+
localStorage.setItem(key, JSON.stringify(filtered));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("Failed to delete preset from localStorage:", error);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async list(key) {
|
|
46
|
+
try {
|
|
47
|
+
const data = localStorage.getItem(key);
|
|
48
|
+
if (!data) return [];
|
|
49
|
+
return JSON.parse(data);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("Failed to list presets from localStorage:", error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var ServerAdapter = class {
|
|
57
|
+
config;
|
|
58
|
+
constructor(config) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
}
|
|
61
|
+
async save(key, preset) {
|
|
62
|
+
const endpoint = this.config.saveEndpoint || `${this.config.baseUrl}/layouts`;
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(endpoint, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
...this.config.headers
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({ key, preset })
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Failed to save preset to server:", error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async load(key, presetId) {
|
|
81
|
+
const endpoint = this.config.loadEndpoint || `${this.config.baseUrl}/layouts`;
|
|
82
|
+
const url = presetId ? `${endpoint}/${key}/${presetId}` : `${endpoint}/${key}`;
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
...this.config.headers
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (response.status === 404) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
96
|
+
}
|
|
97
|
+
return await response.json();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("Failed to load preset from server:", error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async delete(key, presetId) {
|
|
104
|
+
const endpoint = this.config.deleteEndpoint || `${this.config.baseUrl}/layouts`;
|
|
105
|
+
const url = `${endpoint}/${key}/${presetId}`;
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch(url, {
|
|
108
|
+
method: "DELETE",
|
|
109
|
+
headers: this.config.headers
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error("Failed to delete preset from server:", error);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async list(key) {
|
|
120
|
+
const endpoint = this.config.listEndpoint || `${this.config.baseUrl}/layouts`;
|
|
121
|
+
const url = `${endpoint}/${key}`;
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch(url, {
|
|
124
|
+
method: "GET",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
...this.config.headers
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (response.status === 404) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
135
|
+
}
|
|
136
|
+
return await response.json();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Failed to list presets from server:", error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var UserProfileAdapter = class {
|
|
144
|
+
config;
|
|
145
|
+
baseAdapter;
|
|
146
|
+
constructor(config) {
|
|
147
|
+
this.config = config;
|
|
148
|
+
this.baseAdapter = config.adapter || new LocalStorageAdapter();
|
|
149
|
+
}
|
|
150
|
+
getUserKey(key) {
|
|
151
|
+
const profileKey = this.config.profileKey || "user-layouts";
|
|
152
|
+
return `${profileKey}:${this.config.userId}:${key}`;
|
|
153
|
+
}
|
|
154
|
+
async save(key, preset) {
|
|
155
|
+
return this.baseAdapter.save(this.getUserKey(key), preset);
|
|
156
|
+
}
|
|
157
|
+
async load(key, presetId) {
|
|
158
|
+
return this.baseAdapter.load(this.getUserKey(key), presetId);
|
|
159
|
+
}
|
|
160
|
+
async delete(key, presetId) {
|
|
161
|
+
return this.baseAdapter.delete(this.getUserKey(key), presetId);
|
|
162
|
+
}
|
|
163
|
+
async list(key) {
|
|
164
|
+
return this.baseAdapter.list(this.getUserKey(key));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function getStorageAdapter(config) {
|
|
168
|
+
if (config.customAdapter) {
|
|
169
|
+
return config.customAdapter;
|
|
170
|
+
}
|
|
171
|
+
switch (config.strategy) {
|
|
172
|
+
case "server":
|
|
173
|
+
if (!config.serverConfig) {
|
|
174
|
+
throw new Error("Server configuration is required for server storage strategy");
|
|
175
|
+
}
|
|
176
|
+
return new ServerAdapter(config.serverConfig);
|
|
177
|
+
case "userProfile":
|
|
178
|
+
if (!config.userProfileConfig) {
|
|
179
|
+
throw new Error("User profile configuration is required for userProfile storage strategy");
|
|
180
|
+
}
|
|
181
|
+
return new UserProfileAdapter(config.userProfileConfig);
|
|
182
|
+
case "localStorage":
|
|
183
|
+
default:
|
|
184
|
+
return new LocalStorageAdapter();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function generatePresetId() {
|
|
188
|
+
return `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
189
|
+
}
|
|
190
|
+
function createPreset(name, layout, description, id) {
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
return {
|
|
193
|
+
id: id || generatePresetId(),
|
|
194
|
+
name,
|
|
195
|
+
description,
|
|
196
|
+
createdAt: now,
|
|
197
|
+
updatedAt: now,
|
|
198
|
+
layout
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
var LayoutPersistenceManager = class {
|
|
202
|
+
adapter;
|
|
203
|
+
storageKey;
|
|
204
|
+
constructor(config) {
|
|
205
|
+
this.adapter = getStorageAdapter(config);
|
|
206
|
+
this.storageKey = config.storageKey;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Save a layout preset
|
|
210
|
+
*/
|
|
211
|
+
async savePreset(preset) {
|
|
212
|
+
const updatedPreset = {
|
|
213
|
+
...preset,
|
|
214
|
+
updatedAt: Date.now()
|
|
215
|
+
};
|
|
216
|
+
await this.adapter.save(this.storageKey, updatedPreset);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Load a specific preset by ID
|
|
220
|
+
*/
|
|
221
|
+
async loadPreset(presetId) {
|
|
222
|
+
const result = await this.adapter.load(this.storageKey, presetId);
|
|
223
|
+
if (Array.isArray(result)) {
|
|
224
|
+
return result.find((p) => p.id === presetId) || null;
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Load the most recently updated preset
|
|
230
|
+
*/
|
|
231
|
+
async loadLastPreset() {
|
|
232
|
+
const presets = await this.listPresets();
|
|
233
|
+
if (presets.length === 0) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return presets.reduce(
|
|
237
|
+
(latest, current) => current.updatedAt > latest.updatedAt ? current : latest
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Delete a preset
|
|
242
|
+
*/
|
|
243
|
+
async deletePreset(presetId) {
|
|
244
|
+
await this.adapter.delete(this.storageKey, presetId);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* List all presets
|
|
248
|
+
*/
|
|
249
|
+
async listPresets() {
|
|
250
|
+
return await this.adapter.list(this.storageKey);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if a preset exists
|
|
254
|
+
*/
|
|
255
|
+
async hasPreset(presetId) {
|
|
256
|
+
const preset = await this.loadPreset(presetId);
|
|
257
|
+
return preset !== null;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Save current layout as auto-save (with special ID)
|
|
261
|
+
*/
|
|
262
|
+
async autoSave(layout) {
|
|
263
|
+
const autoSaveId = "__autosave__";
|
|
264
|
+
const preset = createPreset("Auto-saved Layout", layout, "Automatically saved layout", autoSaveId);
|
|
265
|
+
await this.savePreset(preset);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Load auto-saved layout
|
|
269
|
+
*/
|
|
270
|
+
async loadAutoSave() {
|
|
271
|
+
return await this.loadPreset("__autosave__");
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function debounce(func, delay) {
|
|
275
|
+
let timeoutId = null;
|
|
276
|
+
return function(...args) {
|
|
277
|
+
if (timeoutId) {
|
|
278
|
+
clearTimeout(timeoutId);
|
|
279
|
+
}
|
|
280
|
+
timeoutId = setTimeout(() => {
|
|
281
|
+
func.apply(this, args);
|
|
282
|
+
timeoutId = null;
|
|
283
|
+
}, delay);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
LocalStorageAdapter,
|
|
289
|
+
ServerAdapter,
|
|
290
|
+
UserProfileAdapter,
|
|
291
|
+
getStorageAdapter,
|
|
292
|
+
generatePresetId,
|
|
293
|
+
createPreset,
|
|
294
|
+
LayoutPersistenceManager,
|
|
295
|
+
debounce
|
|
296
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccessibilityDemo.tsx
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive demonstration of DataGrid accessibility features
|
|
5
|
+
* including keyboard navigation, ARIA support, and screen reader compatibility.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
export declare const AccessibilityDemo: React.FC;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* CellRenderersDemo - Showcase of Custom Cell Renderer Framework
|
|
4
|
+
*
|
|
5
|
+
* This demo demonstrates all available custom cell renderers:
|
|
6
|
+
* - StatusChip: Color-coded status badges
|
|
7
|
+
* - ProgressBar: Visual progress indicators
|
|
8
|
+
* - IconCell: Cells with icons
|
|
9
|
+
* - ImageCell: Cells with images/avatars
|
|
10
|
+
* - ButtonCell: Actionable buttons
|
|
11
|
+
* - BadgeCell: Generic badge component
|
|
12
|
+
* - PriorityIndicator: Priority levels
|
|
13
|
+
* - Rating: Star ratings
|
|
14
|
+
* - CurrencyCell: Formatted currency values
|
|
15
|
+
*/
|
|
16
|
+
export declare const CellRenderersDemo: React.FC;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* ContextMenuDemo Component
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates the context menu feature with:
|
|
6
|
+
* - Right-click on cells: Copy, Copy with Headers, Export Selected Range, Filter by Value
|
|
7
|
+
* - Right-click on headers: Pin/Unpin, Auto-size, Resize to Fit, Hide Column, Filter by Value
|
|
8
|
+
* - Custom menu items
|
|
9
|
+
* - Enable/disable context menu options
|
|
10
|
+
*/
|
|
11
|
+
export declare const ContextMenuDemo: React.FC;
|
|
12
|
+
export default ContextMenuDemo;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMPONENT ARCHITECTURE GUIDE
|
|
3
|
+
*
|
|
4
|
+
* This document explains the internal design and architecture of the DataGrid component.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The DataGrid uses useReducer for centralized state management.
|
|
8
|
+
* This provides several benefits:
|
|
9
|
+
* - Single source of truth for all grid state
|
|
10
|
+
* - Predictable state updates through actions
|
|
11
|
+
* - Easier debugging and testing
|
|
12
|
+
* - Better performance (fewer re-renders)
|
|
13
|
+
*
|
|
14
|
+
* State Structure:
|
|
15
|
+
* - columns: Column configuration
|
|
16
|
+
* - sortConfig: Current sort field and direction
|
|
17
|
+
* - filterConfig: Filter values for each column
|
|
18
|
+
* - currentPage: Active page number (0-indexed)
|
|
19
|
+
* - pageSize: Rows per page
|
|
20
|
+
* - selection: Selected row IDs and tracking
|
|
21
|
+
* - editState: Currently editing cell info
|
|
22
|
+
* - focusState: Keyboard focus position
|
|
23
|
+
* - columnOrder: Display order of columns
|
|
24
|
+
* - columnWidths: Width of each column in pixels
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Data flows through the component in the following order:
|
|
28
|
+
*
|
|
29
|
+
* Raw Data (props.rows)
|
|
30
|
+
* ↓
|
|
31
|
+
* Apply Sorting (sortedRows)
|
|
32
|
+
* ↓
|
|
33
|
+
* Apply Filtering (filteredRows)
|
|
34
|
+
* ↓
|
|
35
|
+
* Apply Pagination (paginatedRows)
|
|
36
|
+
* ↓
|
|
37
|
+
* Render in GridBody
|
|
38
|
+
*
|
|
39
|
+
* Each transformation is done with useMemo for performance optimization.
|
|
40
|
+
* The transformations only recalculate when their dependencies change.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* DataGrid (Parent)
|
|
44
|
+
* │
|
|
45
|
+
* ├── GridHeader
|
|
46
|
+
* │ ├── Column Headers (sortable, draggable)
|
|
47
|
+
* │ ├── Column Resizers
|
|
48
|
+
* │ └── Filter Inputs
|
|
49
|
+
* │
|
|
50
|
+
* ├── GridBody
|
|
51
|
+
* │ └── Rows
|
|
52
|
+
* │ └── Cells (editable, focusable, selectable)
|
|
53
|
+
* │
|
|
54
|
+
* └── GridPagination
|
|
55
|
+
* ├── Page Size Selector
|
|
56
|
+
* ├── Row Count Info
|
|
57
|
+
* └── Page Navigation Buttons
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* A) Compound Component Pattern
|
|
61
|
+
* - Main DataGrid orchestrates sub-components
|
|
62
|
+
* - Each sub-component has a specific responsibility
|
|
63
|
+
* - State and dispatch are passed down as props
|
|
64
|
+
*
|
|
65
|
+
* B) Reducer Pattern
|
|
66
|
+
* - All state updates go through the reducer
|
|
67
|
+
* - Actions are dispatched from child components
|
|
68
|
+
* - Reducer handles state transitions
|
|
69
|
+
*
|
|
70
|
+
* C) Controlled Components
|
|
71
|
+
* - Parent component (DataGrid) owns the data
|
|
72
|
+
* - Child components receive data and callbacks
|
|
73
|
+
* - Edits are communicated back via callbacks
|
|
74
|
+
*
|
|
75
|
+
* D) Memoization Strategy
|
|
76
|
+
* - useMemo for expensive computations (sorting, filtering)
|
|
77
|
+
* - useCallback for event handlers passed to children
|
|
78
|
+
* - Prevents unnecessary re-renders
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Column resizing uses a custom implementation:
|
|
82
|
+
*
|
|
83
|
+
* 1. Each column has a resize handle (1px div at right edge)
|
|
84
|
+
* 2. On mousedown:
|
|
85
|
+
* - Store starting X position and current width
|
|
86
|
+
* - Set resizing state
|
|
87
|
+
* 3. On mousemove (document level):
|
|
88
|
+
* - Calculate difference from start position
|
|
89
|
+
* - Update column width (with minimum constraint)
|
|
90
|
+
* 4. On mouseup (document level):
|
|
91
|
+
* - Clear resizing state
|
|
92
|
+
* - Clean up event listeners
|
|
93
|
+
*
|
|
94
|
+
* This approach allows dragging outside the column without losing the resize.
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* Column reordering uses HTML5 Drag & Drop API:
|
|
98
|
+
*
|
|
99
|
+
* 1. Each column header is draggable
|
|
100
|
+
* 2. On dragstart:
|
|
101
|
+
* - Store which column is being dragged
|
|
102
|
+
* - Set drag effect to 'move'
|
|
103
|
+
* 3. On dragover:
|
|
104
|
+
* - Prevent default to allow drop
|
|
105
|
+
* - Set drop effect to 'move'
|
|
106
|
+
* 4. On drop:
|
|
107
|
+
* - Calculate new position
|
|
108
|
+
* - Dispatch REORDER_COLUMNS action
|
|
109
|
+
* - Update columnOrder array in state
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Three selection modes:
|
|
113
|
+
*
|
|
114
|
+
* A) Single Selection (regular click)
|
|
115
|
+
* - Clear all selections
|
|
116
|
+
* - Select clicked row
|
|
117
|
+
*
|
|
118
|
+
* B) Multi Selection (Ctrl/Cmd + click)
|
|
119
|
+
* - Toggle clicked row
|
|
120
|
+
* - Preserve other selections
|
|
121
|
+
*
|
|
122
|
+
* C) Range Selection (Shift + click)
|
|
123
|
+
* - Find last selected row
|
|
124
|
+
* - Select all rows between last and current
|
|
125
|
+
* - Add to existing selection
|
|
126
|
+
*
|
|
127
|
+
* Selection state is stored as a Set for O(1) lookups.
|
|
128
|
+
*/
|
|
129
|
+
/**
|
|
130
|
+
* Cell editing workflow:
|
|
131
|
+
*
|
|
132
|
+
* 1. Enter Edit Mode:
|
|
133
|
+
* - Double-click cell OR
|
|
134
|
+
* - Press Enter on focused cell
|
|
135
|
+
* - Dispatch START_EDIT action
|
|
136
|
+
* - Replace cell content with input element
|
|
137
|
+
*
|
|
138
|
+
* 2. During Editing:
|
|
139
|
+
* - Input is focused and selected
|
|
140
|
+
* - Value changes update editState
|
|
141
|
+
* - Click outside or press Enter/Escape to exit
|
|
142
|
+
*
|
|
143
|
+
* 3. Exit Edit Mode:
|
|
144
|
+
* - Enter: Save changes, call onCellEdit callback
|
|
145
|
+
* - Escape: Discard changes
|
|
146
|
+
* - Blur: Save changes
|
|
147
|
+
* - Dispatch END_EDIT action
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* Keyboard navigation with focus tracking:
|
|
151
|
+
*
|
|
152
|
+
* 1. Focus State:
|
|
153
|
+
* - Stored as { rowIndex, columnIndex }
|
|
154
|
+
* - Rendered cell gets tabIndex={0}, others get tabIndex={-1}
|
|
155
|
+
* - Visual indicator (ring) shows focused cell
|
|
156
|
+
*
|
|
157
|
+
* 2. Arrow Key Handling:
|
|
158
|
+
* - Calculate new position
|
|
159
|
+
* - Apply boundary constraints (0 to max)
|
|
160
|
+
* - Update focus state
|
|
161
|
+
* - Focused cell automatically receives keyboard focus
|
|
162
|
+
*
|
|
163
|
+
* 3. Enter Key:
|
|
164
|
+
* - Starts editing if cell is editable
|
|
165
|
+
* - During editing, saves and exits
|
|
166
|
+
*
|
|
167
|
+
* 4. Escape Key:
|
|
168
|
+
* - During editing, cancels and exits
|
|
169
|
+
* - Otherwise, clears focus
|
|
170
|
+
*/
|
|
171
|
+
/**
|
|
172
|
+
* A) useMemo for derived data:
|
|
173
|
+
* - sortedRows, filteredRows, paginatedRows
|
|
174
|
+
* - Only recalculate when dependencies change
|
|
175
|
+
*
|
|
176
|
+
* B) useCallback for event handlers:
|
|
177
|
+
* - Prevents child component re-renders
|
|
178
|
+
* - Stable function references
|
|
179
|
+
*
|
|
180
|
+
* C) Early returns in reducer:
|
|
181
|
+
* - No-op actions don't create new state
|
|
182
|
+
*
|
|
183
|
+
* D) Set for selection tracking:
|
|
184
|
+
* - O(1) lookups for "is selected" checks
|
|
185
|
+
*
|
|
186
|
+
* E) Column map in GridHeader/GridBody:
|
|
187
|
+
* - Quick column lookup by field name
|
|
188
|
+
* - Avoids O(n) array.find() in render
|
|
189
|
+
*
|
|
190
|
+
* Future optimization opportunities:
|
|
191
|
+
* - Virtual scrolling for large datasets
|
|
192
|
+
* - Web Workers for sorting/filtering
|
|
193
|
+
* - React.memo for sub-components
|
|
194
|
+
* - Debounced filter updates
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* Recommended testing approach:
|
|
198
|
+
*
|
|
199
|
+
* A) Unit Tests:
|
|
200
|
+
* - gridReducer.ts: Test all action types
|
|
201
|
+
* - Utility functions: Test sorting, filtering logic
|
|
202
|
+
*
|
|
203
|
+
* B) Component Tests:
|
|
204
|
+
* - GridHeader: Test sorting, filtering, resizing
|
|
205
|
+
* - GridBody: Test cell editing, selection, navigation
|
|
206
|
+
* - GridPagination: Test page changes, size changes
|
|
207
|
+
*
|
|
208
|
+
* C) Integration Tests:
|
|
209
|
+
* - Full DataGrid: Test feature interactions
|
|
210
|
+
* - Example: Sort → Filter → Edit → Page change
|
|
211
|
+
*
|
|
212
|
+
* D) E2E Tests:
|
|
213
|
+
* - User workflows: Select → Edit → Sort → Filter
|
|
214
|
+
* - Keyboard navigation flows
|
|
215
|
+
*/
|
|
216
|
+
/**
|
|
217
|
+
* Current accessibility features:
|
|
218
|
+
* - Keyboard navigation (arrow keys, Enter, Escape)
|
|
219
|
+
* - Focus indicators (visible ring)
|
|
220
|
+
* - Semantic HTML where possible
|
|
221
|
+
*
|
|
222
|
+
* Recommended improvements:
|
|
223
|
+
* - ARIA labels for interactive elements
|
|
224
|
+
* - aria-sort on column headers
|
|
225
|
+
* - aria-selected on selected rows
|
|
226
|
+
* - Screen reader announcements for state changes
|
|
227
|
+
* - Focus management when editing
|
|
228
|
+
* - Proper role attributes (grid, row, columnheader, gridcell)
|
|
229
|
+
* - Keyboard shortcuts documentation
|
|
230
|
+
*/
|
|
231
|
+
/**
|
|
232
|
+
* How to add new features:
|
|
233
|
+
*
|
|
234
|
+
* A) Add new state:
|
|
235
|
+
* 1. Update GridState interface in types.ts
|
|
236
|
+
* 2. Add to createInitialState in gridReducer.ts
|
|
237
|
+
* 3. Create new action type(s)
|
|
238
|
+
* 4. Handle in reducer switch statement
|
|
239
|
+
*
|
|
240
|
+
* B) Add new column feature:
|
|
241
|
+
* 1. Update Column interface in types.ts
|
|
242
|
+
* 2. Handle in GridHeader.tsx
|
|
243
|
+
* 3. Update rendering logic in GridBody.tsx
|
|
244
|
+
*
|
|
245
|
+
* C) Add new UI component:
|
|
246
|
+
* 1. Create new component file
|
|
247
|
+
* 2. Import in DataGrid.tsx
|
|
248
|
+
* 3. Pass necessary state and dispatch
|
|
249
|
+
* 4. Add to component hierarchy
|
|
250
|
+
*
|
|
251
|
+
* Example: Adding column pinning
|
|
252
|
+
* 1. Add pinnedColumns: string[] to GridState
|
|
253
|
+
* 2. Add PIN_COLUMN/UNPIN_COLUMN actions
|
|
254
|
+
* 3. Update GridHeader with pin button
|
|
255
|
+
* 4. Update GridBody to render pinned columns separately
|
|
256
|
+
* 5. Update column ordering logic
|
|
257
|
+
*/
|
|
258
|
+
/**
|
|
259
|
+
* A) Column Lookup Pattern:
|
|
260
|
+
* const columnMap = new Map(columns.map(col => [col.field, col]));
|
|
261
|
+
* const column = columnMap.get(field);
|
|
262
|
+
* // Faster than columns.find(col => col.field === field)
|
|
263
|
+
*
|
|
264
|
+
* B) Conditional Rendering Pattern:
|
|
265
|
+
* if (!column || column.editable === false) return;
|
|
266
|
+
* // Handle missing or disabled features
|
|
267
|
+
*
|
|
268
|
+
* C) Event Handler Pattern:
|
|
269
|
+
* const handleAction = (data) => {
|
|
270
|
+
* dispatch({ type: 'ACTION_TYPE', payload: data });
|
|
271
|
+
* if (onAction) onAction(data);
|
|
272
|
+
* };
|
|
273
|
+
* // Update internal state + notify parent
|
|
274
|
+
*
|
|
275
|
+
* D) Style Computation Pattern:
|
|
276
|
+
* const className = `base-class ${condition ? 'conditional-class' : ''}`;
|
|
277
|
+
* // Dynamic class names based on state
|
|
278
|
+
*
|
|
279
|
+
* E) Ref Usage Pattern:
|
|
280
|
+
* const ref = useRef<HTMLElement>(null);
|
|
281
|
+
* useEffect(() => {
|
|
282
|
+
* if (ref.current) {
|
|
283
|
+
* ref.current.focus();
|
|
284
|
+
* }
|
|
285
|
+
* }, [dependency]);
|
|
286
|
+
* // Imperative DOM operations when needed
|
|
287
|
+
*/
|
|
288
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Column, AdvancedFilterValue, Row } from './types';
|
|
3
|
+
interface AdvancedFilterBuilderProps {
|
|
4
|
+
column: Column;
|
|
5
|
+
filterValue: AdvancedFilterValue | null;
|
|
6
|
+
onApply: (value: AdvancedFilterValue | null) => void;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
rows: Row[];
|
|
9
|
+
anchorEl: HTMLElement | null;
|
|
10
|
+
}
|
|
11
|
+
export declare const AdvancedFilterBuilder: React.FC<AdvancedFilterBuilderProps>;
|
|
12
|
+
export {};
|