@object-ui/plugin-aggrid 0.4.1 → 2.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/.turbo/turbo-build.log +99 -0
- package/CHANGELOG.md +16 -0
- package/OBJECT_AGGRID_CN.md +483 -0
- package/QUICKSTART.md +186 -0
- package/README.md +221 -1
- package/dist/AddressField-Bntpynvd.js +95 -0
- package/dist/AgGridImpl-3Mmf2qrR.js +229 -0
- package/dist/AutoNumberField-C1kBJaxh.js +8 -0
- package/dist/FileField-BDwbJvor.js +101 -0
- package/dist/FormulaField-BXNiyGoh.js +9 -0
- package/dist/GeolocationField-Df3yYcM9.js +141 -0
- package/dist/GridField-CcjQp4WM.js +29 -0
- package/dist/LocationField-BIfN5QIq.js +33 -0
- package/dist/MasterDetailField-CAEmxbIT.js +117 -0
- package/dist/ObjectAgGridImpl-EjifM4aY.js +28727 -0
- package/dist/ObjectField-BpkQpIF-.js +51 -0
- package/dist/QRCodeField-VCBewTDG.js +96 -0
- package/dist/RichTextField-CyQwSi2C.js +37 -0
- package/dist/SignatureField-Cr4tsEbj.js +96 -0
- package/dist/SummaryField-CnEJ_GZI.js +9 -0
- package/dist/UserField-DJjaVyrV.js +49 -0
- package/dist/VectorField-cPYmcKnV.js +25 -0
- package/dist/{index-B6NPAFZx.js → index-B87wd1E0.js} +301 -143
- package/dist/index.css +1 -1
- package/dist/index.js +4 -3
- package/dist/index.umd.cjs +225 -2
- package/dist/src/AgGridImpl.d.ts +5 -2
- package/dist/src/ObjectAgGridImpl.d.ts +6 -0
- package/dist/src/VirtualScrolling.d.ts +72 -0
- package/dist/src/field-renderers.d.ts +67 -0
- package/dist/src/index.d.ts +47 -2
- package/dist/src/object-aggrid.types.d.ts +74 -0
- package/dist/src/types.d.ts +48 -1
- package/package.json +11 -9
- package/src/AgGridImpl.tsx +100 -11
- package/src/ObjectAgGridImpl.tsx +501 -0
- package/src/VirtualScrolling.ts +74 -0
- package/src/field-renderers.test.tsx +383 -0
- package/src/field-renderers.tsx +224 -0
- package/src/index.test.ts +1 -1
- package/src/index.tsx +211 -2
- package/src/object-aggrid.test.ts +99 -0
- package/src/object-aggrid.types.ts +123 -0
- package/src/types.ts +57 -1
- package/vite.config.ts +13 -0
- package/dist/AgGridImpl-DKkq6v1B.js +0 -171
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
10
|
+
import { AgGridReact } from 'ag-grid-react';
|
|
11
|
+
import type {
|
|
12
|
+
ColDef,
|
|
13
|
+
GridReadyEvent,
|
|
14
|
+
CellClickedEvent,
|
|
15
|
+
RowClickedEvent,
|
|
16
|
+
SelectionChangedEvent,
|
|
17
|
+
CellValueChangedEvent,
|
|
18
|
+
StatusPanelDef,
|
|
19
|
+
GetContextMenuItemsParams,
|
|
20
|
+
MenuItemDef,
|
|
21
|
+
} from 'ag-grid-community';
|
|
22
|
+
import type { FieldMetadata, ObjectSchemaMetadata } from '@object-ui/types';
|
|
23
|
+
import type { ObjectAgGridImplProps } from './object-aggrid.types';
|
|
24
|
+
import { FIELD_TYPE_TO_FILTER_TYPE } from './object-aggrid.types';
|
|
25
|
+
import { createFieldCellRenderer, createFieldCellEditor } from './field-renderers';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ObjectAgGridImpl - Metadata-driven AG Grid implementation
|
|
29
|
+
* Fetches object metadata and data from ObjectStack and renders the grid
|
|
30
|
+
*/
|
|
31
|
+
export default function ObjectAgGridImpl({
|
|
32
|
+
objectName,
|
|
33
|
+
dataSource,
|
|
34
|
+
fields: providedFields,
|
|
35
|
+
fieldNames,
|
|
36
|
+
filters,
|
|
37
|
+
sort,
|
|
38
|
+
pageSize = 10,
|
|
39
|
+
pagination = true,
|
|
40
|
+
domLayout = 'normal',
|
|
41
|
+
animateRows = true,
|
|
42
|
+
rowSelection,
|
|
43
|
+
theme = 'quartz',
|
|
44
|
+
height = 500,
|
|
45
|
+
className = '',
|
|
46
|
+
editable = false,
|
|
47
|
+
editType,
|
|
48
|
+
singleClickEdit = false,
|
|
49
|
+
stopEditingWhenCellsLoseFocus = true,
|
|
50
|
+
exportConfig,
|
|
51
|
+
statusBar,
|
|
52
|
+
callbacks,
|
|
53
|
+
columnConfig,
|
|
54
|
+
enableRangeSelection = false,
|
|
55
|
+
enableCharts = false,
|
|
56
|
+
contextMenu,
|
|
57
|
+
}: ObjectAgGridImplProps) {
|
|
58
|
+
const gridRef = useRef<any>(null);
|
|
59
|
+
const [loading, setLoading] = useState(true);
|
|
60
|
+
const [error, setError] = useState<Error | null>(null);
|
|
61
|
+
const [objectSchema, setObjectSchema] = useState<ObjectSchemaMetadata | null>(null);
|
|
62
|
+
const [rowData, setRowData] = useState<any[]>([]);
|
|
63
|
+
|
|
64
|
+
// Fetch object metadata
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!dataSource) {
|
|
67
|
+
setError(new Error('DataSource is required'));
|
|
68
|
+
setLoading(false);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fetchMetadata = async () => {
|
|
73
|
+
try {
|
|
74
|
+
setLoading(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
|
|
77
|
+
// Fetch object schema/metadata
|
|
78
|
+
const schema = await (dataSource as any).getObjectSchema(objectName);
|
|
79
|
+
setObjectSchema(schema);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
82
|
+
setError(error);
|
|
83
|
+
callbacks?.onDataError?.(error);
|
|
84
|
+
} finally {
|
|
85
|
+
setLoading(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
fetchMetadata();
|
|
90
|
+
}, [objectName, dataSource, callbacks]);
|
|
91
|
+
|
|
92
|
+
// Fetch data
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!dataSource || !objectSchema) return;
|
|
95
|
+
|
|
96
|
+
const fetchData = async () => {
|
|
97
|
+
try {
|
|
98
|
+
setLoading(true);
|
|
99
|
+
setError(null);
|
|
100
|
+
|
|
101
|
+
const queryParams: any = {
|
|
102
|
+
$top: pageSize,
|
|
103
|
+
$skip: 0,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (filters) {
|
|
107
|
+
queryParams.$filter = filters;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (sort) {
|
|
111
|
+
queryParams.$orderby = sort;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const result = await dataSource.find(objectName, queryParams);
|
|
115
|
+
setRowData(result.data || []);
|
|
116
|
+
callbacks?.onDataLoaded?.(result.data || []);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
119
|
+
setError(error);
|
|
120
|
+
callbacks?.onDataError?.(error);
|
|
121
|
+
} finally {
|
|
122
|
+
setLoading(false);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
fetchData();
|
|
127
|
+
}, [objectName, dataSource, objectSchema, filters, sort, pageSize, callbacks]);
|
|
128
|
+
|
|
129
|
+
// Generate column definitions from metadata
|
|
130
|
+
const columnDefs = useMemo((): ColDef[] => {
|
|
131
|
+
if (!objectSchema?.fields) return [];
|
|
132
|
+
|
|
133
|
+
// Use provided fields or get from schema
|
|
134
|
+
const fieldMetadata = providedFields || Object.values(objectSchema.fields);
|
|
135
|
+
|
|
136
|
+
// Filter fields if fieldNames is provided
|
|
137
|
+
const fieldsToShow = fieldNames
|
|
138
|
+
? fieldMetadata.filter(field => fieldNames.includes(field.name))
|
|
139
|
+
: fieldMetadata;
|
|
140
|
+
|
|
141
|
+
return fieldsToShow.map(field => {
|
|
142
|
+
const colDef: ColDef = {
|
|
143
|
+
field: field.name,
|
|
144
|
+
headerName: field.label || field.name,
|
|
145
|
+
sortable: field.sortable !== false,
|
|
146
|
+
filter: getFilterType(field),
|
|
147
|
+
editable: editable && !field.readonly,
|
|
148
|
+
// visible_on will be evaluated by the core renderer
|
|
149
|
+
// For now, we just show all fields. Conditional visibility
|
|
150
|
+
// should be handled at a higher level or via dynamic column updates
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Apply column config defaults
|
|
154
|
+
if (columnConfig) {
|
|
155
|
+
if (columnConfig.resizable !== undefined) {
|
|
156
|
+
colDef.resizable = columnConfig.resizable;
|
|
157
|
+
}
|
|
158
|
+
if (columnConfig.sortable !== undefined && colDef.sortable === undefined) {
|
|
159
|
+
colDef.sortable = columnConfig.sortable;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add custom renderers and formatters based on field type
|
|
164
|
+
applyFieldTypeFormatting(colDef, field);
|
|
165
|
+
|
|
166
|
+
return colDef;
|
|
167
|
+
});
|
|
168
|
+
}, [objectSchema, providedFields, fieldNames, editable, columnConfig]);
|
|
169
|
+
|
|
170
|
+
// Build status bar panels
|
|
171
|
+
const statusPanels = useMemo((): StatusPanelDef[] | undefined => {
|
|
172
|
+
if (!statusBar?.enabled) return undefined;
|
|
173
|
+
|
|
174
|
+
const aggregations = statusBar.aggregations || ['count', 'sum', 'avg'];
|
|
175
|
+
const panels: StatusPanelDef[] = [];
|
|
176
|
+
|
|
177
|
+
if (aggregations.includes('count')) {
|
|
178
|
+
panels.push({ statusPanel: 'agAggregationComponent', statusPanelParams: { aggFuncs: ['count'] } });
|
|
179
|
+
}
|
|
180
|
+
if (aggregations.includes('sum')) {
|
|
181
|
+
panels.push({ statusPanel: 'agAggregationComponent', statusPanelParams: { aggFuncs: ['sum'] } });
|
|
182
|
+
}
|
|
183
|
+
if (aggregations.includes('avg')) {
|
|
184
|
+
panels.push({ statusPanel: 'agAggregationComponent', statusPanelParams: { aggFuncs: ['avg'] } });
|
|
185
|
+
}
|
|
186
|
+
if (aggregations.includes('min')) {
|
|
187
|
+
panels.push({ statusPanel: 'agAggregationComponent', statusPanelParams: { aggFuncs: ['min'] } });
|
|
188
|
+
}
|
|
189
|
+
if (aggregations.includes('max')) {
|
|
190
|
+
panels.push({ statusPanel: 'agAggregationComponent', statusPanelParams: { aggFuncs: ['max'] } });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return panels;
|
|
194
|
+
}, [statusBar]);
|
|
195
|
+
|
|
196
|
+
// CSV Export handler
|
|
197
|
+
const handleExportCSV = useCallback(() => {
|
|
198
|
+
if (!gridRef.current?.api) return;
|
|
199
|
+
|
|
200
|
+
const params = {
|
|
201
|
+
fileName: exportConfig?.fileName || `${objectName}-export.csv`,
|
|
202
|
+
skipColumnHeaders: exportConfig?.skipColumnHeaders || false,
|
|
203
|
+
allColumns: exportConfig?.allColumns || false,
|
|
204
|
+
onlySelected: exportConfig?.onlySelected || false,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
gridRef.current.api.exportDataAsCsv(params);
|
|
208
|
+
|
|
209
|
+
if (callbacks?.onExport) {
|
|
210
|
+
const data = exportConfig?.onlySelected
|
|
211
|
+
? gridRef.current.api.getSelectedRows()
|
|
212
|
+
: rowData;
|
|
213
|
+
callbacks.onExport(data || [], 'csv');
|
|
214
|
+
}
|
|
215
|
+
}, [exportConfig, callbacks, rowData, objectName]);
|
|
216
|
+
|
|
217
|
+
// Context Menu handler
|
|
218
|
+
const getContextMenuItems = useCallback((params: GetContextMenuItemsParams): (string | MenuItemDef)[] => {
|
|
219
|
+
if (!contextMenu?.enabled) return [];
|
|
220
|
+
|
|
221
|
+
const items: (string | MenuItemDef)[] = [];
|
|
222
|
+
const defaultItems = contextMenu.items || ['copy', 'copyWithHeaders', 'separator', 'export'];
|
|
223
|
+
|
|
224
|
+
defaultItems.forEach(item => {
|
|
225
|
+
if (item === 'export') {
|
|
226
|
+
items.push({
|
|
227
|
+
name: 'Export CSV',
|
|
228
|
+
icon: '<span>📥</span>',
|
|
229
|
+
action: () => handleExportCSV(),
|
|
230
|
+
});
|
|
231
|
+
} else if (item === 'autoSizeAll') {
|
|
232
|
+
items.push({
|
|
233
|
+
name: 'Auto-size All Columns',
|
|
234
|
+
action: () => {
|
|
235
|
+
if (gridRef.current?.api) {
|
|
236
|
+
gridRef.current.api.autoSizeAllColumns();
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
} else if (item === 'resetColumns') {
|
|
241
|
+
items.push({
|
|
242
|
+
name: 'Reset Columns',
|
|
243
|
+
action: () => {
|
|
244
|
+
if (gridRef.current?.api) {
|
|
245
|
+
gridRef.current.api.resetColumnState();
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
items.push(item);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Add custom items
|
|
255
|
+
if (contextMenu.customItems) {
|
|
256
|
+
if (items.length > 0) {
|
|
257
|
+
items.push('separator');
|
|
258
|
+
}
|
|
259
|
+
contextMenu.customItems.forEach(customItem => {
|
|
260
|
+
items.push({
|
|
261
|
+
name: customItem.name,
|
|
262
|
+
disabled: customItem.disabled,
|
|
263
|
+
action: () => {
|
|
264
|
+
if (callbacks?.onContextMenuAction) {
|
|
265
|
+
callbacks.onContextMenuAction(customItem.action, params.node?.data);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return items;
|
|
273
|
+
}, [contextMenu, handleExportCSV, callbacks]);
|
|
274
|
+
|
|
275
|
+
// Event handlers
|
|
276
|
+
const handleCellClicked = useCallback((event: CellClickedEvent) => {
|
|
277
|
+
callbacks?.onCellClicked?.(event);
|
|
278
|
+
}, [callbacks]);
|
|
279
|
+
|
|
280
|
+
const handleRowClicked = useCallback((event: RowClickedEvent) => {
|
|
281
|
+
callbacks?.onRowClicked?.(event);
|
|
282
|
+
}, [callbacks]);
|
|
283
|
+
|
|
284
|
+
const handleSelectionChanged = useCallback((event: SelectionChangedEvent) => {
|
|
285
|
+
callbacks?.onSelectionChanged?.(event);
|
|
286
|
+
}, [callbacks]);
|
|
287
|
+
|
|
288
|
+
const handleCellValueChanged = useCallback(async (event: CellValueChangedEvent) => {
|
|
289
|
+
callbacks?.onCellValueChanged?.(event);
|
|
290
|
+
|
|
291
|
+
// Save changes to backend if dataSource supports update
|
|
292
|
+
// Note: Assumes records have an 'id' field as primary key
|
|
293
|
+
// TODO: Make the ID field name configurable via schema
|
|
294
|
+
if (dataSource && event.data && event.data.id) {
|
|
295
|
+
try {
|
|
296
|
+
await dataSource.update(objectName, event.data.id, {
|
|
297
|
+
[event.colDef.field!]: event.newValue
|
|
298
|
+
});
|
|
299
|
+
} catch (err) {
|
|
300
|
+
console.error('Failed to update record:', err);
|
|
301
|
+
// Revert the change
|
|
302
|
+
event.node.setDataValue(event.colDef.field!, event.oldValue);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}, [callbacks, dataSource, objectName]);
|
|
306
|
+
|
|
307
|
+
const onGridReady = useCallback((params: GridReadyEvent) => {
|
|
308
|
+
gridRef.current = params;
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
311
|
+
// Merge grid options with props
|
|
312
|
+
const gridOptions = useMemo(() => ({
|
|
313
|
+
pagination,
|
|
314
|
+
paginationPageSize: pageSize,
|
|
315
|
+
domLayout,
|
|
316
|
+
animateRows,
|
|
317
|
+
rowSelection,
|
|
318
|
+
editType,
|
|
319
|
+
singleClickEdit,
|
|
320
|
+
stopEditingWhenCellsLoseFocus,
|
|
321
|
+
statusBar: statusPanels ? { statusPanels } : undefined,
|
|
322
|
+
enableRangeSelection,
|
|
323
|
+
enableCharts,
|
|
324
|
+
getContextMenuItems: contextMenu?.enabled ? getContextMenuItems : undefined,
|
|
325
|
+
suppressCellFocus: !editable,
|
|
326
|
+
enableCellTextSelection: true,
|
|
327
|
+
ensureDomOrder: true,
|
|
328
|
+
onCellClicked: handleCellClicked,
|
|
329
|
+
onRowClicked: handleRowClicked,
|
|
330
|
+
onSelectionChanged: handleSelectionChanged,
|
|
331
|
+
onCellValueChanged: handleCellValueChanged,
|
|
332
|
+
onGridReady,
|
|
333
|
+
}), [
|
|
334
|
+
pagination,
|
|
335
|
+
pageSize,
|
|
336
|
+
domLayout,
|
|
337
|
+
animateRows,
|
|
338
|
+
rowSelection,
|
|
339
|
+
editType,
|
|
340
|
+
singleClickEdit,
|
|
341
|
+
stopEditingWhenCellsLoseFocus,
|
|
342
|
+
statusPanels,
|
|
343
|
+
enableRangeSelection,
|
|
344
|
+
enableCharts,
|
|
345
|
+
contextMenu,
|
|
346
|
+
getContextMenuItems,
|
|
347
|
+
editable,
|
|
348
|
+
handleCellClicked,
|
|
349
|
+
handleRowClicked,
|
|
350
|
+
handleSelectionChanged,
|
|
351
|
+
handleCellValueChanged,
|
|
352
|
+
onGridReady,
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
// Compute container style
|
|
356
|
+
const containerStyle = useMemo(() => ({
|
|
357
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
358
|
+
width: '100%',
|
|
359
|
+
}), [height]);
|
|
360
|
+
|
|
361
|
+
// Determine theme class and build complete class list
|
|
362
|
+
const themeClass = `ag-theme-${theme}`;
|
|
363
|
+
const classList = [
|
|
364
|
+
themeClass,
|
|
365
|
+
'rounded-xl',
|
|
366
|
+
'border',
|
|
367
|
+
'border-border',
|
|
368
|
+
'overflow-hidden',
|
|
369
|
+
'shadow-lg',
|
|
370
|
+
className
|
|
371
|
+
].filter(Boolean).join(' ');
|
|
372
|
+
|
|
373
|
+
if (loading) {
|
|
374
|
+
return (
|
|
375
|
+
<div className="flex items-center justify-center" style={containerStyle}>
|
|
376
|
+
<div className="text-muted-foreground">Loading {objectName}...</div>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (error) {
|
|
382
|
+
return (
|
|
383
|
+
<div className="flex items-center justify-center" style={containerStyle}>
|
|
384
|
+
<div className="text-destructive">Error loading data: {error.message}</div>
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<div className="object-aggrid-container">
|
|
391
|
+
{exportConfig?.enabled && (
|
|
392
|
+
<div className="mb-2 flex gap-2">
|
|
393
|
+
<button
|
|
394
|
+
onClick={handleExportCSV}
|
|
395
|
+
className="px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
|
396
|
+
>
|
|
397
|
+
Export CSV
|
|
398
|
+
</button>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
<div
|
|
402
|
+
className={classList}
|
|
403
|
+
style={containerStyle}
|
|
404
|
+
>
|
|
405
|
+
<AgGridReact
|
|
406
|
+
ref={gridRef}
|
|
407
|
+
rowData={rowData}
|
|
408
|
+
columnDefs={columnDefs}
|
|
409
|
+
gridOptions={gridOptions}
|
|
410
|
+
/>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get filter type based on field metadata
|
|
418
|
+
*/
|
|
419
|
+
function getFilterType(field: FieldMetadata): string | boolean {
|
|
420
|
+
if (field.filterable === false) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return FIELD_TYPE_TO_FILTER_TYPE[field.type] || 'agTextColumnFilter';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Apply field type-specific formatting to column definition
|
|
429
|
+
* Uses field widgets from @object-ui/fields for consistent rendering
|
|
430
|
+
*/
|
|
431
|
+
function applyFieldTypeFormatting(colDef: ColDef, field: FieldMetadata): void {
|
|
432
|
+
// Define field types that should use field widgets for rendering
|
|
433
|
+
const fieldWidgetTypes = [
|
|
434
|
+
'text', 'textarea', 'number', 'currency', 'percent',
|
|
435
|
+
'boolean', 'select', 'date', 'datetime', 'time',
|
|
436
|
+
'email', 'phone', 'url', 'password', 'color',
|
|
437
|
+
'rating', 'image', 'avatar', 'lookup', 'slider', 'code'
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
// Use field widget renderer if the type is supported
|
|
441
|
+
if (fieldWidgetTypes.includes(field.type)) {
|
|
442
|
+
colDef.cellRenderer = createFieldCellRenderer(field);
|
|
443
|
+
|
|
444
|
+
// Add cell editor for editable fields
|
|
445
|
+
if (colDef.editable) {
|
|
446
|
+
colDef.cellEditor = createFieldCellEditor(field);
|
|
447
|
+
|
|
448
|
+
// Configure editor based on field type
|
|
449
|
+
if (['date', 'datetime', 'select', 'lookup', 'color'].includes(field.type)) {
|
|
450
|
+
colDef.cellEditorPopup = true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Fallback to simple rendering for unsupported types
|
|
455
|
+
switch (field.type) {
|
|
456
|
+
case 'master_detail':
|
|
457
|
+
colDef.valueFormatter = (params: any) => {
|
|
458
|
+
if (!params.value) return '';
|
|
459
|
+
// Handle lookup values - could be an object or just an ID
|
|
460
|
+
if (typeof params.value === 'object') {
|
|
461
|
+
return params.value.name || params.value.label || params.value.id || '';
|
|
462
|
+
}
|
|
463
|
+
return String(params.value);
|
|
464
|
+
};
|
|
465
|
+
break;
|
|
466
|
+
|
|
467
|
+
case 'object':
|
|
468
|
+
colDef.cellRenderer = () => {
|
|
469
|
+
const span = document.createElement('span');
|
|
470
|
+
span.className = 'text-gray-500 italic';
|
|
471
|
+
span.textContent = '[Object]';
|
|
472
|
+
return span;
|
|
473
|
+
};
|
|
474
|
+
break;
|
|
475
|
+
|
|
476
|
+
case 'vector':
|
|
477
|
+
colDef.cellRenderer = () => {
|
|
478
|
+
const span = document.createElement('span');
|
|
479
|
+
span.className = 'text-gray-500 italic';
|
|
480
|
+
span.textContent = '[Vector]';
|
|
481
|
+
return span;
|
|
482
|
+
};
|
|
483
|
+
break;
|
|
484
|
+
|
|
485
|
+
case 'grid':
|
|
486
|
+
colDef.cellRenderer = () => {
|
|
487
|
+
const span = document.createElement('span');
|
|
488
|
+
span.className = 'text-gray-500 italic';
|
|
489
|
+
span.textContent = '[Grid]';
|
|
490
|
+
return span;
|
|
491
|
+
};
|
|
492
|
+
break;
|
|
493
|
+
|
|
494
|
+
default:
|
|
495
|
+
// Default text rendering
|
|
496
|
+
colDef.valueFormatter = (params: any) => {
|
|
497
|
+
return params.value != null ? String(params.value) : '';
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Virtual Scrolling in AG Grid
|
|
11
|
+
*
|
|
12
|
+
* AG Grid provides built-in virtual scrolling by default, which renders only
|
|
13
|
+
* the visible rows in the viewport. This is a core feature of AG Grid and
|
|
14
|
+
* requires no additional configuration.
|
|
15
|
+
*
|
|
16
|
+
* ## How It Works
|
|
17
|
+
*
|
|
18
|
+
* - AG Grid automatically virtualizes rows by rendering only visible rows
|
|
19
|
+
* - As you scroll, rows are recycled and reused for new data
|
|
20
|
+
* - This provides excellent performance even with datasets of 100,000+ rows
|
|
21
|
+
*
|
|
22
|
+
* ## Performance Tips
|
|
23
|
+
*
|
|
24
|
+
* 1. **Row Buffer**: Adjust `rowBuffer` to control how many extra rows are rendered
|
|
25
|
+
* ```ts
|
|
26
|
+
* gridOptions: { rowBuffer: 10 } // Render 10 extra rows above/below viewport
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* 2. **Suppress Animations**: Disable animations for very large datasets
|
|
30
|
+
* ```ts
|
|
31
|
+
* animateRows: false
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* 3. **Debounce Vertical Scroll**: Add delay to vertical scroll updates
|
|
35
|
+
* ```ts
|
|
36
|
+
* gridOptions: { debounceVerticalScrollbar: true }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* 4. **Row Height**: Use fixed row heights for better performance
|
|
40
|
+
* ```ts
|
|
41
|
+
* gridOptions: { rowHeight: 40 }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Example Usage
|
|
45
|
+
*
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <AgGrid
|
|
48
|
+
* rowData={largeDataset} // 10,000+ items
|
|
49
|
+
* columnDefs={columns}
|
|
50
|
+
* gridOptions={{
|
|
51
|
+
* rowBuffer: 10,
|
|
52
|
+
* rowHeight: 40,
|
|
53
|
+
* debounceVerticalScrollbar: true,
|
|
54
|
+
* }}
|
|
55
|
+
* animateRows={false}
|
|
56
|
+
* />
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* ## References
|
|
60
|
+
*
|
|
61
|
+
* - [AG Grid Row Virtualisation](https://www.ag-grid.com/javascript-data-grid/dom-virtualisation/)
|
|
62
|
+
* - [Performance Best Practices](https://www.ag-grid.com/javascript-data-grid/performance/)
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
export const VIRTUAL_SCROLLING_DOCS = {
|
|
66
|
+
enabled: true,
|
|
67
|
+
automatic: true,
|
|
68
|
+
recommendedSettings: {
|
|
69
|
+
rowBuffer: 10,
|
|
70
|
+
rowHeight: 40,
|
|
71
|
+
debounceVerticalScrollbar: true,
|
|
72
|
+
animateRows: false,
|
|
73
|
+
},
|
|
74
|
+
};
|