@object-ui/plugin-aggrid 0.5.0 → 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 (36) hide show
  1. package/.turbo/turbo-build.log +90 -12
  2. package/CHANGELOG.md +16 -0
  3. package/QUICKSTART.md +1 -1
  4. package/dist/AddressField-Bntpynvd.js +95 -0
  5. package/dist/AgGridImpl-3Mmf2qrR.js +229 -0
  6. package/dist/AutoNumberField-C1kBJaxh.js +8 -0
  7. package/dist/FileField-BDwbJvor.js +101 -0
  8. package/dist/FormulaField-BXNiyGoh.js +9 -0
  9. package/dist/GeolocationField-Df3yYcM9.js +141 -0
  10. package/dist/GridField-CcjQp4WM.js +29 -0
  11. package/dist/LocationField-BIfN5QIq.js +33 -0
  12. package/dist/MasterDetailField-CAEmxbIT.js +117 -0
  13. package/dist/ObjectAgGridImpl-EjifM4aY.js +28727 -0
  14. package/dist/ObjectField-BpkQpIF-.js +51 -0
  15. package/dist/QRCodeField-VCBewTDG.js +96 -0
  16. package/dist/RichTextField-CyQwSi2C.js +37 -0
  17. package/dist/SignatureField-Cr4tsEbj.js +96 -0
  18. package/dist/SummaryField-CnEJ_GZI.js +9 -0
  19. package/dist/UserField-DJjaVyrV.js +49 -0
  20. package/dist/VectorField-cPYmcKnV.js +25 -0
  21. package/dist/{index-CLKYMco3.js → index-B87wd1E0.js} +57 -33
  22. package/dist/index.js +1 -1
  23. package/dist/index.umd.cjs +225 -5
  24. package/dist/src/AgGridImpl.d.ts +5 -2
  25. package/dist/src/field-renderers.d.ts +67 -0
  26. package/dist/src/index.d.ts +5 -2
  27. package/dist/src/types.d.ts +48 -1
  28. package/package.json +8 -7
  29. package/src/AgGridImpl.tsx +95 -10
  30. package/src/ObjectAgGridImpl.tsx +67 -169
  31. package/src/field-renderers.test.tsx +383 -0
  32. package/src/field-renderers.tsx +224 -0
  33. package/src/index.tsx +30 -3
  34. package/src/types.ts +57 -1
  35. package/dist/AgGridImpl-BQ6tBvrq.js +0 -175
  36. package/dist/ObjectAgGridImpl-CGFeGvOH.js +0 -372
@@ -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.tsx CHANGED
@@ -19,10 +19,10 @@ import 'ag-grid-community/styles/ag-theme-balham.css';
19
19
  import 'ag-grid-community/styles/ag-theme-material.css';
20
20
 
21
21
  // Export types for external use
22
- export type { AgGridSchema, SimpleColumnDef, AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
22
+ export type { AgGridSchema, SimpleColumnDef, AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
23
23
  export type { ObjectAgGridSchema } from './object-aggrid.types';
24
24
 
25
- import type { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
25
+ import type { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
26
26
  import type { DataSource } from '@object-ui/types';
27
27
 
28
28
  // 🚀 Lazy load the implementation file
@@ -56,6 +56,9 @@ export interface AgGridRendererProps {
56
56
  enableRangeSelection?: boolean;
57
57
  enableCharts?: boolean;
58
58
  contextMenu?: ContextMenuConfig;
59
+ treeData?: TreeDataConfig;
60
+ rowGrouping?: RowGroupingConfig;
61
+ excelExport?: ExcelExportConfig;
59
62
  };
60
63
  }
61
64
 
@@ -89,6 +92,9 @@ export const AgGridRenderer: React.FC<AgGridRendererProps> = ({ schema }) => {
89
92
  enableRangeSelection={schema.enableRangeSelection}
90
93
  enableCharts={schema.enableCharts}
91
94
  contextMenu={schema.contextMenu}
95
+ treeData={schema.treeData}
96
+ rowGrouping={schema.rowGrouping}
97
+ excelExport={schema.excelExport}
92
98
  />
93
99
  </Suspense>
94
100
  );
@@ -247,6 +253,27 @@ ComponentRegistry.register(
247
253
  description: 'Configure right-click menu: { enabled: true, items: ["copy", "export"] }',
248
254
  advanced: true
249
255
  },
256
+ {
257
+ name: 'treeData',
258
+ type: 'code',
259
+ label: 'Tree Data Config (JSON)',
260
+ description: 'Configure tree data: { enabled: true, pathField: "orgHierarchy" }',
261
+ advanced: true
262
+ },
263
+ {
264
+ name: 'rowGrouping',
265
+ type: 'code',
266
+ label: 'Row Grouping Config (JSON)',
267
+ description: 'Configure row grouping: { enabled: true, groupByFields: ["category"] }',
268
+ advanced: true
269
+ },
270
+ {
271
+ name: 'excelExport',
272
+ type: 'code',
273
+ label: 'Excel Export Config (JSON)',
274
+ description: 'Configure Excel export: { enabled: true, fileName: "data.xlsx" }',
275
+ advanced: true
276
+ },
250
277
  {
251
278
  name: 'gridOptions',
252
279
  type: 'code',
@@ -390,7 +417,7 @@ ComponentRegistry.register(
390
417
  namespace: 'plugin-aggrid',
391
418
  label: 'Object AG Grid',
392
419
  icon: 'Table',
393
- category: 'plugin',
420
+ category: 'view',
394
421
  inputs: [
395
422
  {
396
423
  name: 'objectName',