@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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +99 -0
  2. package/CHANGELOG.md +16 -0
  3. package/OBJECT_AGGRID_CN.md +483 -0
  4. package/QUICKSTART.md +186 -0
  5. package/README.md +221 -1
  6. package/dist/AddressField-Bntpynvd.js +95 -0
  7. package/dist/AgGridImpl-3Mmf2qrR.js +229 -0
  8. package/dist/AutoNumberField-C1kBJaxh.js +8 -0
  9. package/dist/FileField-BDwbJvor.js +101 -0
  10. package/dist/FormulaField-BXNiyGoh.js +9 -0
  11. package/dist/GeolocationField-Df3yYcM9.js +141 -0
  12. package/dist/GridField-CcjQp4WM.js +29 -0
  13. package/dist/LocationField-BIfN5QIq.js +33 -0
  14. package/dist/MasterDetailField-CAEmxbIT.js +117 -0
  15. package/dist/ObjectAgGridImpl-EjifM4aY.js +28727 -0
  16. package/dist/ObjectField-BpkQpIF-.js +51 -0
  17. package/dist/QRCodeField-VCBewTDG.js +96 -0
  18. package/dist/RichTextField-CyQwSi2C.js +37 -0
  19. package/dist/SignatureField-Cr4tsEbj.js +96 -0
  20. package/dist/SummaryField-CnEJ_GZI.js +9 -0
  21. package/dist/UserField-DJjaVyrV.js +49 -0
  22. package/dist/VectorField-cPYmcKnV.js +25 -0
  23. package/dist/{index-B6NPAFZx.js → index-B87wd1E0.js} +301 -143
  24. package/dist/index.css +1 -1
  25. package/dist/index.js +4 -3
  26. package/dist/index.umd.cjs +225 -2
  27. package/dist/src/AgGridImpl.d.ts +5 -2
  28. package/dist/src/ObjectAgGridImpl.d.ts +6 -0
  29. package/dist/src/VirtualScrolling.d.ts +72 -0
  30. package/dist/src/field-renderers.d.ts +67 -0
  31. package/dist/src/index.d.ts +47 -2
  32. package/dist/src/object-aggrid.types.d.ts +74 -0
  33. package/dist/src/types.d.ts +48 -1
  34. package/package.json +11 -9
  35. package/src/AgGridImpl.tsx +100 -11
  36. package/src/ObjectAgGridImpl.tsx +501 -0
  37. package/src/VirtualScrolling.ts +74 -0
  38. package/src/field-renderers.test.tsx +383 -0
  39. package/src/field-renderers.tsx +224 -0
  40. package/src/index.test.ts +1 -1
  41. package/src/index.tsx +211 -2
  42. package/src/object-aggrid.test.ts +99 -0
  43. package/src/object-aggrid.types.ts +123 -0
  44. package/src/types.ts +57 -1
  45. package/vite.config.ts +13 -0
  46. package/dist/AgGridImpl-DKkq6v1B.js +0 -171
@@ -0,0 +1,383 @@
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 { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import type { ICellRendererParams, ICellEditorParams } from 'ag-grid-community';
11
+ import type { FieldMetadata } from '@object-ui/types';
12
+ import {
13
+ FieldWidgetCellRenderer,
14
+ FieldWidgetCellEditor,
15
+ createFieldCellRenderer,
16
+ createFieldCellEditor
17
+ } from './field-renderers';
18
+
19
+ describe('field-renderers', () => {
20
+ describe('FieldWidgetCellRenderer', () => {
21
+ let renderer: FieldWidgetCellRenderer;
22
+
23
+ beforeEach(() => {
24
+ renderer = new FieldWidgetCellRenderer();
25
+ });
26
+
27
+ afterEach(() => {
28
+ renderer.destroy();
29
+ });
30
+
31
+ it('should initialize with a field widget for supported types', () => {
32
+ const field: FieldMetadata = {
33
+ name: 'testField',
34
+ label: 'Test Field',
35
+ type: 'text',
36
+ };
37
+
38
+ const params = {
39
+ value: 'test value',
40
+ field,
41
+ } as ICellRendererParams & { field: FieldMetadata };
42
+
43
+ renderer.init(params);
44
+
45
+ expect(renderer.eGui).toBeDefined();
46
+ expect(renderer.eGui.className).toBe('field-widget-cell');
47
+ expect(renderer.root).toBeDefined();
48
+ });
49
+
50
+ it('should initialize with fallback for unsupported types', () => {
51
+ const field = {
52
+ name: 'testField',
53
+ label: 'Test Field',
54
+ type: 'unsupported_type',
55
+ } as unknown as FieldMetadata;
56
+
57
+ const params = {
58
+ value: 'fallback value',
59
+ field,
60
+ } as ICellRendererParams & { field: FieldMetadata };
61
+
62
+ renderer.init(params);
63
+
64
+ expect(renderer.eGui).toBeDefined();
65
+ expect(renderer.eGui.textContent).toBe('fallback value');
66
+ expect(renderer.root).toBeNull();
67
+ });
68
+
69
+ it('should handle null values in fallback mode', () => {
70
+ const field = {
71
+ name: 'testField',
72
+ label: 'Test Field',
73
+ type: 'unsupported_type',
74
+ } as unknown as FieldMetadata;
75
+
76
+ const params = {
77
+ value: null,
78
+ field,
79
+ } as ICellRendererParams & { field: FieldMetadata };
80
+
81
+ renderer.init(params);
82
+
83
+ expect(renderer.eGui.textContent).toBe('');
84
+ });
85
+
86
+ it('should return the GUI element', () => {
87
+ const field: FieldMetadata = {
88
+ name: 'testField',
89
+ label: 'Test Field',
90
+ type: 'text',
91
+ };
92
+
93
+ const params = {
94
+ value: 'test',
95
+ field,
96
+ } as ICellRendererParams & { field: FieldMetadata };
97
+
98
+ renderer.init(params);
99
+ const gui = renderer.getGui();
100
+
101
+ expect(gui).toBe(renderer.eGui);
102
+ });
103
+
104
+ it('should refresh with new value for supported field type', () => {
105
+ const field: FieldMetadata = {
106
+ name: 'testField',
107
+ label: 'Test Field',
108
+ type: 'text',
109
+ };
110
+
111
+ const initParams = {
112
+ value: 'initial value',
113
+ field,
114
+ } as ICellRendererParams & { field: FieldMetadata };
115
+
116
+ renderer.init(initParams);
117
+
118
+ const refreshParams = {
119
+ value: 'updated value',
120
+ field,
121
+ } as ICellRendererParams & { field: FieldMetadata };
122
+
123
+ const result = renderer.refresh(refreshParams);
124
+
125
+ expect(result).toBe(true);
126
+ });
127
+
128
+ it('should refresh with new value for unsupported field type', () => {
129
+ const field = {
130
+ name: 'testField',
131
+ label: 'Test Field',
132
+ type: 'unsupported_type',
133
+ } as unknown as FieldMetadata;
134
+
135
+ const initParams = {
136
+ value: 'initial value',
137
+ field,
138
+ } as ICellRendererParams & { field: FieldMetadata };
139
+
140
+ renderer.init(initParams);
141
+
142
+ const refreshParams = {
143
+ value: 'updated value',
144
+ field,
145
+ } as ICellRendererParams & { field: FieldMetadata };
146
+
147
+ const result = renderer.refresh(refreshParams);
148
+
149
+ expect(result).toBe(true);
150
+ expect(renderer.eGui.textContent).toBe('updated value');
151
+ });
152
+
153
+ it('should clean up root on destroy', () => {
154
+ const field: FieldMetadata = {
155
+ name: 'testField',
156
+ label: 'Test Field',
157
+ type: 'text',
158
+ };
159
+
160
+ const params = {
161
+ value: 'test',
162
+ field,
163
+ } as ICellRendererParams & { field: FieldMetadata };
164
+
165
+ renderer.init(params);
166
+
167
+ const unmountSpy = vi.spyOn(renderer.root!, 'unmount');
168
+ renderer.destroy();
169
+
170
+ expect(unmountSpy).toHaveBeenCalled();
171
+ });
172
+
173
+ it('should handle destroy when root is null', () => {
174
+ const field = {
175
+ name: 'testField',
176
+ label: 'Test Field',
177
+ type: 'unsupported_type',
178
+ } as unknown as FieldMetadata;
179
+
180
+ const params = {
181
+ value: 'test',
182
+ field,
183
+ } as ICellRendererParams & { field: FieldMetadata };
184
+
185
+ renderer.init(params);
186
+
187
+ // Should not throw
188
+ expect(() => renderer.destroy()).not.toThrow();
189
+ });
190
+ });
191
+
192
+ describe('FieldWidgetCellEditor', () => {
193
+ let editor: FieldWidgetCellEditor;
194
+
195
+ beforeEach(() => {
196
+ editor = new FieldWidgetCellEditor();
197
+ });
198
+
199
+ afterEach(() => {
200
+ editor.destroy();
201
+ });
202
+
203
+ it('should initialize with a field widget for supported types', () => {
204
+ const field: FieldMetadata = {
205
+ name: 'testField',
206
+ label: 'Test Field',
207
+ type: 'text',
208
+ };
209
+
210
+ const params = {
211
+ value: 'initial value',
212
+ field,
213
+ } as ICellEditorParams & { field: FieldMetadata };
214
+
215
+ editor.init(params);
216
+
217
+ expect(editor.eGui).toBeDefined();
218
+ expect(editor.eGui.className).toBe('field-widget-editor');
219
+ expect(editor.root).toBeDefined();
220
+ expect(editor.currentValue).toBe('initial value');
221
+ });
222
+
223
+ it('should initialize with fallback input for unsupported types', () => {
224
+ const field = {
225
+ name: 'testField',
226
+ label: 'Test Field',
227
+ type: 'unsupported_type',
228
+ } as unknown as FieldMetadata;
229
+
230
+ const params = {
231
+ value: 'fallback value',
232
+ field,
233
+ } as ICellEditorParams & { field: FieldMetadata };
234
+
235
+ editor.init(params);
236
+
237
+ expect(editor.eGui).toBeDefined();
238
+ expect(editor.root).toBeNull();
239
+ const input = editor.eGui.querySelector('input');
240
+ expect(input).toBeDefined();
241
+ expect(input?.value).toBe('fallback value');
242
+ });
243
+
244
+ it('should return current value', () => {
245
+ const field: FieldMetadata = {
246
+ name: 'testField',
247
+ label: 'Test Field',
248
+ type: 'text',
249
+ };
250
+
251
+ const params = {
252
+ value: 'test value',
253
+ field,
254
+ } as ICellEditorParams & { field: FieldMetadata };
255
+
256
+ editor.init(params);
257
+
258
+ expect(editor.getValue()).toBe('test value');
259
+ });
260
+
261
+ it('should return the GUI element', () => {
262
+ const field: FieldMetadata = {
263
+ name: 'testField',
264
+ label: 'Test Field',
265
+ type: 'text',
266
+ };
267
+
268
+ const params = {
269
+ value: 'test',
270
+ field,
271
+ } as ICellEditorParams & { field: FieldMetadata };
272
+
273
+ editor.init(params);
274
+ const gui = editor.getGui();
275
+
276
+ expect(gui).toBe(editor.eGui);
277
+ });
278
+
279
+ it('should return true for popup editors for specific field types', () => {
280
+ const popupTypes = ['date', 'datetime', 'select', 'lookup', 'color'] as const;
281
+
282
+ popupTypes.forEach(type => {
283
+ const field: FieldMetadata = {
284
+ name: 'testField',
285
+ label: 'Test Field',
286
+ type,
287
+ };
288
+
289
+ const params = {
290
+ value: 'test',
291
+ field,
292
+ } as ICellEditorParams & { field: FieldMetadata };
293
+
294
+ const testEditor = new FieldWidgetCellEditor();
295
+ testEditor.init(params);
296
+
297
+ expect(testEditor.isPopup()).toBe(true);
298
+ testEditor.destroy();
299
+ });
300
+ });
301
+
302
+ it('should return false for popup editors for non-popup field types', () => {
303
+ const field: FieldMetadata = {
304
+ name: 'testField',
305
+ label: 'Test Field',
306
+ type: 'text',
307
+ };
308
+
309
+ const params = {
310
+ value: 'test',
311
+ field,
312
+ } as ICellEditorParams & { field: FieldMetadata };
313
+
314
+ editor.init(params);
315
+
316
+ expect(editor.isPopup()).toBe(false);
317
+ });
318
+
319
+ it('should clean up root on destroy', () => {
320
+ const field: FieldMetadata = {
321
+ name: 'testField',
322
+ label: 'Test Field',
323
+ type: 'text',
324
+ };
325
+
326
+ const params = {
327
+ value: 'test',
328
+ field,
329
+ } as ICellEditorParams & { field: FieldMetadata };
330
+
331
+ editor.init(params);
332
+
333
+ const unmountSpy = vi.spyOn(editor.root!, 'unmount');
334
+ editor.destroy();
335
+
336
+ expect(unmountSpy).toHaveBeenCalled();
337
+ });
338
+ });
339
+
340
+ describe('createFieldCellRenderer', () => {
341
+ it('should create a renderer class with field metadata', () => {
342
+ const field: FieldMetadata = {
343
+ name: 'testField',
344
+ label: 'Test Field',
345
+ type: 'text',
346
+ };
347
+
348
+ const RendererClass = createFieldCellRenderer(field);
349
+ const renderer = new RendererClass();
350
+
351
+ const params = {
352
+ value: 'test value',
353
+ } as ICellRendererParams;
354
+
355
+ renderer.init(params);
356
+
357
+ expect(renderer.eGui).toBeDefined();
358
+ renderer.destroy();
359
+ });
360
+ });
361
+
362
+ describe('createFieldCellEditor', () => {
363
+ it('should create an editor class with field metadata', () => {
364
+ const field: FieldMetadata = {
365
+ name: 'testField',
366
+ label: 'Test Field',
367
+ type: 'text',
368
+ };
369
+
370
+ const EditorClass = createFieldCellEditor(field);
371
+ const editor = new EditorClass();
372
+
373
+ const params = {
374
+ value: 'test value',
375
+ } as ICellEditorParams;
376
+
377
+ editor.init(params);
378
+
379
+ expect(editor.eGui).toBeDefined();
380
+ editor.destroy();
381
+ });
382
+ });
383
+ });
@@ -0,0 +1,224 @@
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 from 'react';
10
+ import { createRoot, type Root } from 'react-dom/client';
11
+ import type { ICellRendererParams, ICellEditorParams } from 'ag-grid-community';
12
+ import type { FieldMetadata } from '@object-ui/types';
13
+
14
+ // Import field widgets
15
+ import {
16
+ TextField,
17
+ NumberField,
18
+ BooleanField,
19
+ SelectField,
20
+ DateField,
21
+ DateTimeField,
22
+ TimeField,
23
+ EmailField,
24
+ PhoneField,
25
+ UrlField,
26
+ CurrencyField,
27
+ PercentField,
28
+ PasswordField,
29
+ TextAreaField,
30
+ ColorField,
31
+ RatingField,
32
+ ImageField,
33
+ AvatarField,
34
+ LookupField,
35
+ SliderField,
36
+ CodeField,
37
+ } from '@object-ui/fields';
38
+
39
+ /**
40
+ * Map field type to field widget component
41
+ * Defined at module level to avoid recreating on every call
42
+ */
43
+ const widgetMap: Record<string, React.ComponentType<any>> = {
44
+ text: TextField,
45
+ textarea: TextAreaField,
46
+ number: NumberField,
47
+ currency: CurrencyField,
48
+ percent: PercentField,
49
+ boolean: BooleanField,
50
+ select: SelectField,
51
+ date: DateField,
52
+ datetime: DateTimeField,
53
+ time: TimeField,
54
+ email: EmailField,
55
+ phone: PhoneField,
56
+ url: UrlField,
57
+ password: PasswordField,
58
+ color: ColorField,
59
+ rating: RatingField,
60
+ image: ImageField,
61
+ avatar: AvatarField,
62
+ lookup: LookupField,
63
+ slider: SliderField,
64
+ code: CodeField,
65
+ };
66
+
67
+ function getFieldWidget(fieldType: string): React.ComponentType<any> | null {
68
+ return widgetMap[fieldType] || null;
69
+ }
70
+
71
+ /**
72
+ * AG Grid Cell Renderer using Field Widgets (Read-only mode)
73
+ */
74
+ export class FieldWidgetCellRenderer {
75
+ public eGui!: HTMLDivElement;
76
+ public root: Root | null = null;
77
+
78
+ init(params: ICellRendererParams & { field: FieldMetadata }) {
79
+ const { value, field } = params;
80
+ const FieldWidget = getFieldWidget(field.type);
81
+
82
+ this.eGui = document.createElement('div');
83
+ this.eGui.className = 'field-widget-cell';
84
+
85
+ if (FieldWidget) {
86
+ this.root = createRoot(this.eGui);
87
+ this.root.render(
88
+ <FieldWidget
89
+ value={value}
90
+ onChange={() => {}} // No-op for read-only mode
91
+ field={field}
92
+ readonly={true}
93
+ />
94
+ );
95
+ } else {
96
+ // Fallback to text display
97
+ this.eGui.textContent = value != null ? String(value) : '';
98
+ }
99
+ }
100
+
101
+ getGui() {
102
+ return this.eGui;
103
+ }
104
+
105
+ refresh(params: ICellRendererParams & { field: FieldMetadata }): boolean {
106
+ const { value, field } = params;
107
+ const FieldWidget = getFieldWidget(field.type);
108
+
109
+ if (FieldWidget && this.root) {
110
+ this.root.render(
111
+ <FieldWidget
112
+ value={value}
113
+ onChange={() => {}} // No-op for read-only mode
114
+ field={field}
115
+ readonly={true}
116
+ />
117
+ );
118
+ return true;
119
+ }
120
+
121
+ // Fallback to text display when no FieldWidget is available
122
+ if (this.eGui) {
123
+ this.eGui.textContent = value != null ? String(value) : '';
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+
130
+ destroy() {
131
+ if (this.root) {
132
+ this.root.unmount();
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * AG Grid Cell Editor using Field Widgets (Edit mode)
139
+ */
140
+ export class FieldWidgetCellEditor {
141
+ public eGui!: HTMLDivElement;
142
+ public root: Root | null = null;
143
+ public currentValue: unknown;
144
+ public params!: ICellEditorParams & { field: FieldMetadata };
145
+
146
+ init(params: ICellEditorParams & { field: FieldMetadata }) {
147
+ this.params = params;
148
+ this.currentValue = params.value;
149
+ const { field } = params;
150
+ const FieldWidget = getFieldWidget(field.type);
151
+
152
+ this.eGui = document.createElement('div');
153
+ this.eGui.className = 'field-widget-editor';
154
+
155
+ if (FieldWidget) {
156
+ this.root = createRoot(this.eGui);
157
+ this.root.render(
158
+ <FieldWidget
159
+ value={this.currentValue}
160
+ onChange={(newValue: any) => {
161
+ this.currentValue = newValue;
162
+ }}
163
+ field={field}
164
+ readonly={false}
165
+ />
166
+ );
167
+ } else {
168
+ // Fallback to input element
169
+ const input = document.createElement('input');
170
+ input.value = this.currentValue != null ? String(this.currentValue) : '';
171
+ input.className = 'ag-input-field-input ag-text-field-input';
172
+ input.addEventListener('input', (e) => {
173
+ this.currentValue = (e.target as HTMLInputElement).value;
174
+ });
175
+ this.eGui.appendChild(input);
176
+ setTimeout(() => input.focus(), 0);
177
+ }
178
+ }
179
+
180
+ getGui() {
181
+ return this.eGui;
182
+ }
183
+
184
+ getValue() {
185
+ return this.currentValue;
186
+ }
187
+
188
+ destroy() {
189
+ if (this.root) {
190
+ this.root.unmount();
191
+ }
192
+ }
193
+
194
+ isPopup(): boolean {
195
+ // Return true for complex widgets that need more space
196
+ const popupTypes = ['date', 'datetime', 'select', 'lookup', 'color'];
197
+ return popupTypes.includes(this.params.field.type);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Factory function to create cell renderer with field metadata
203
+ */
204
+ export function createFieldCellRenderer(field: FieldMetadata) {
205
+ return class extends FieldWidgetCellRenderer {
206
+ init(params: ICellRendererParams) {
207
+ super.init({ ...params, field });
208
+ }
209
+ refresh(params: ICellRendererParams): boolean {
210
+ return super.refresh({ ...params, field });
211
+ }
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Factory function to create cell editor with field metadata
217
+ */
218
+ export function createFieldCellEditor(field: FieldMetadata) {
219
+ return class extends FieldWidgetCellEditor {
220
+ init(params: ICellEditorParams) {
221
+ super.init({ ...params, field });
222
+ }
223
+ };
224
+ }
package/src/index.test.ts CHANGED
@@ -13,7 +13,7 @@ describe('Plugin AgGrid', () => {
13
13
  // Import all renderers to register them
14
14
  beforeAll(async () => {
15
15
  await import('./index');
16
- });
16
+ }, 15000); // Increase timeout to 15 seconds for async import
17
17
 
18
18
  describe('aggrid component', () => {
19
19
  it('should be registered in ComponentRegistry', () => {