@object-ui/plugin-grid 3.1.5 → 3.3.1

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 (66) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +649 -623
  5. package/dist/index.umd.cjs +8 -8
  6. package/package.json +45 -13
  7. package/.turbo/turbo-build.log +0 -32
  8. package/src/FormulaBar.tsx +0 -151
  9. package/src/GroupRow.tsx +0 -69
  10. package/src/ImportWizard.tsx +0 -412
  11. package/src/InlineEditing.tsx +0 -235
  12. package/src/ListColumnExtensions.test.tsx +0 -373
  13. package/src/ListColumnSchema.test.ts +0 -88
  14. package/src/ObjectGrid.EdgeCases.stories.tsx +0 -147
  15. package/src/ObjectGrid.msw.test.tsx +0 -130
  16. package/src/ObjectGrid.stories.tsx +0 -139
  17. package/src/ObjectGrid.tsx +0 -1596
  18. package/src/SplitPaneGrid.tsx +0 -120
  19. package/src/VirtualGrid.tsx +0 -183
  20. package/src/__tests__/GroupRow.test.tsx +0 -206
  21. package/src/__tests__/ImportPreview.test.tsx +0 -171
  22. package/src/__tests__/InlineEditing.test.tsx +0 -360
  23. package/src/__tests__/VirtualGrid.test.tsx +0 -438
  24. package/src/__tests__/accessibility.test.tsx +0 -254
  25. package/src/__tests__/accessorKey-inference.test.tsx +0 -132
  26. package/src/__tests__/airtable-style.test.tsx +0 -508
  27. package/src/__tests__/column-features.test.tsx +0 -490
  28. package/src/__tests__/grid-export.test.tsx +0 -121
  29. package/src/__tests__/mobile-card-view.test.tsx +0 -355
  30. package/src/__tests__/objectdef-enrichment.test.tsx +0 -566
  31. package/src/__tests__/performance-benchmark.test.tsx +0 -182
  32. package/src/__tests__/phase11-features.test.tsx +0 -418
  33. package/src/__tests__/row-bulk-actions.test.tsx +0 -413
  34. package/src/__tests__/row-height.test.tsx +0 -160
  35. package/src/__tests__/useGroupedData.test.ts +0 -165
  36. package/src/__tests__/view-states.test.tsx +0 -203
  37. package/src/components/BulkActionBar.tsx +0 -66
  38. package/src/components/RowActionMenu.tsx +0 -91
  39. package/src/index.test.tsx +0 -29
  40. package/src/index.tsx +0 -99
  41. package/src/useCellClipboard.ts +0 -136
  42. package/src/useColumnSummary.ts +0 -128
  43. package/src/useGradientColor.ts +0 -103
  44. package/src/useGroupReorder.ts +0 -123
  45. package/src/useGroupedData.ts +0 -187
  46. package/src/useRowColor.ts +0 -74
  47. package/tsconfig.json +0 -9
  48. package/vite.config.ts +0 -57
  49. package/vitest.config.ts +0 -13
  50. package/vitest.setup.ts +0 -1
  51. /package/dist/{plugin-grid → packages/plugin-grid}/src/FormulaBar.d.ts +0 -0
  52. /package/dist/{plugin-grid → packages/plugin-grid}/src/GroupRow.d.ts +0 -0
  53. /package/dist/{plugin-grid → packages/plugin-grid}/src/ImportWizard.d.ts +0 -0
  54. /package/dist/{plugin-grid → packages/plugin-grid}/src/InlineEditing.d.ts +0 -0
  55. /package/dist/{plugin-grid → packages/plugin-grid}/src/ObjectGrid.d.ts +0 -0
  56. /package/dist/{plugin-grid → packages/plugin-grid}/src/SplitPaneGrid.d.ts +0 -0
  57. /package/dist/{plugin-grid → packages/plugin-grid}/src/VirtualGrid.d.ts +0 -0
  58. /package/dist/{plugin-grid → packages/plugin-grid}/src/components/BulkActionBar.d.ts +0 -0
  59. /package/dist/{plugin-grid → packages/plugin-grid}/src/components/RowActionMenu.d.ts +0 -0
  60. /package/dist/{plugin-grid → packages/plugin-grid}/src/index.d.ts +0 -0
  61. /package/dist/{plugin-grid → packages/plugin-grid}/src/useCellClipboard.d.ts +0 -0
  62. /package/dist/{plugin-grid → packages/plugin-grid}/src/useColumnSummary.d.ts +0 -0
  63. /package/dist/{plugin-grid → packages/plugin-grid}/src/useGradientColor.d.ts +0 -0
  64. /package/dist/{plugin-grid → packages/plugin-grid}/src/useGroupReorder.d.ts +0 -0
  65. /package/dist/{plugin-grid → packages/plugin-grid}/src/useGroupedData.d.ts +0 -0
  66. /package/dist/{plugin-grid → packages/plugin-grid}/src/useRowColor.d.ts +0 -0
@@ -1,165 +0,0 @@
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 } from 'vitest';
10
- import { renderHook, act } from '@testing-library/react';
11
- import { useGroupedData } from '../useGroupedData';
12
-
13
- const sampleData = [
14
- { category: 'A', priority: 'High', amount: 10 },
15
- { category: 'A', priority: 'Low', amount: 20 },
16
- { category: 'B', priority: 'High', amount: 30 },
17
- { category: 'B', priority: 'Medium', amount: 40 },
18
- { category: 'C', priority: 'Low', amount: 50 },
19
- ];
20
-
21
- describe('useGroupedData – collapsed state management', () => {
22
- it('returns isGrouped=false when config is undefined', () => {
23
- const { result } = renderHook(() => useGroupedData(undefined, sampleData));
24
- expect(result.current.isGrouped).toBe(false);
25
- expect(result.current.groups).toEqual([]);
26
- });
27
-
28
- it('returns isGrouped=false when config has empty fields', () => {
29
- const { result } = renderHook(() => useGroupedData({ fields: [] }, sampleData));
30
- expect(result.current.isGrouped).toBe(false);
31
- expect(result.current.groups).toEqual([]);
32
- });
33
-
34
- it('groups data correctly with single field', () => {
35
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
36
- const { result } = renderHook(() => useGroupedData(config, sampleData));
37
-
38
- expect(result.current.isGrouped).toBe(true);
39
- expect(result.current.groups).toHaveLength(3);
40
- expect(result.current.groups[0].key).toBe('A');
41
- expect(result.current.groups[0].rows).toHaveLength(2);
42
- expect(result.current.groups[1].key).toBe('B');
43
- expect(result.current.groups[1].rows).toHaveLength(2);
44
- expect(result.current.groups[2].key).toBe('C');
45
- expect(result.current.groups[2].rows).toHaveLength(1);
46
- });
47
-
48
- it('all groups default to expanded when collapsed=false', () => {
49
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
50
- const { result } = renderHook(() => useGroupedData(config, sampleData));
51
-
52
- result.current.groups.forEach((group) => {
53
- expect(group.collapsed).toBe(false);
54
- });
55
- });
56
-
57
- it('all groups default to collapsed when collapsed=true', () => {
58
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: true }] };
59
- const { result } = renderHook(() => useGroupedData(config, sampleData));
60
-
61
- result.current.groups.forEach((group) => {
62
- expect(group.collapsed).toBe(true);
63
- });
64
- });
65
-
66
- it('toggleGroup toggles a group from expanded to collapsed', () => {
67
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
68
- const { result } = renderHook(() => useGroupedData(config, sampleData));
69
-
70
- // Initially all expanded
71
- expect(result.current.groups[0].collapsed).toBe(false);
72
-
73
- // Toggle group A
74
- act(() => {
75
- result.current.toggleGroup('A');
76
- });
77
-
78
- expect(result.current.groups[0].collapsed).toBe(true);
79
- // Other groups remain expanded
80
- expect(result.current.groups[1].collapsed).toBe(false);
81
- expect(result.current.groups[2].collapsed).toBe(false);
82
- });
83
-
84
- it('toggleGroup toggles a group from collapsed back to expanded', () => {
85
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
86
- const { result } = renderHook(() => useGroupedData(config, sampleData));
87
-
88
- // Toggle twice: expand -> collapse -> expand
89
- act(() => {
90
- result.current.toggleGroup('A');
91
- });
92
- expect(result.current.groups[0].collapsed).toBe(true);
93
-
94
- act(() => {
95
- result.current.toggleGroup('A');
96
- });
97
- expect(result.current.groups[0].collapsed).toBe(false);
98
- });
99
-
100
- it('toggleGroup expands a group that defaults to collapsed', () => {
101
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: true }] };
102
- const { result } = renderHook(() => useGroupedData(config, sampleData));
103
-
104
- // Initially all collapsed
105
- expect(result.current.groups[0].collapsed).toBe(true);
106
-
107
- // Toggle group A to expand
108
- act(() => {
109
- result.current.toggleGroup('A');
110
- });
111
-
112
- expect(result.current.groups[0].collapsed).toBe(false);
113
- // Other groups remain collapsed
114
- expect(result.current.groups[1].collapsed).toBe(true);
115
- });
116
-
117
- it('sorts groups in descending order when configured', () => {
118
- const config = { fields: [{ field: 'category', order: 'desc' as const, collapsed: false }] };
119
- const { result } = renderHook(() => useGroupedData(config, sampleData));
120
-
121
- expect(result.current.groups[0].key).toBe('C');
122
- expect(result.current.groups[1].key).toBe('B');
123
- expect(result.current.groups[2].key).toBe('A');
124
- });
125
-
126
- it('builds correct labels for groups', () => {
127
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
128
- const { result } = renderHook(() => useGroupedData(config, sampleData));
129
-
130
- expect(result.current.groups[0].label).toBe('A');
131
- expect(result.current.groups[1].label).toBe('B');
132
- expect(result.current.groups[2].label).toBe('C');
133
- });
134
-
135
- it('shows (empty) label for rows with missing grouping field', () => {
136
- const data = [
137
- { category: 'A', amount: 10 },
138
- { amount: 20 }, // no category
139
- { category: '', amount: 30 }, // empty category
140
- ];
141
- const config = { fields: [{ field: 'category', order: 'asc' as const, collapsed: false }] };
142
- const { result } = renderHook(() => useGroupedData(config, data));
143
-
144
- const emptyGroup = result.current.groups.find((g) => g.label === '(empty)');
145
- expect(emptyGroup).toBeDefined();
146
- expect(emptyGroup!.rows).toHaveLength(2);
147
- });
148
-
149
- it('supports multi-field grouping', () => {
150
- const config = {
151
- fields: [
152
- { field: 'category', order: 'asc' as const, collapsed: false },
153
- { field: 'priority', order: 'asc' as const, collapsed: false },
154
- ],
155
- };
156
- const { result } = renderHook(() => useGroupedData(config, sampleData));
157
-
158
- expect(result.current.isGrouped).toBe(true);
159
- // Each unique combination of category + priority should be a group
160
- expect(result.current.groups.length).toBeGreaterThanOrEqual(4);
161
- // Check label format is "A / High"
162
- const firstGroup = result.current.groups[0];
163
- expect(firstGroup.label).toContain(' / ');
164
- });
165
- });
@@ -1,203 +0,0 @@
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
- * P3.3 Plugin View Robustness - Grid View States
11
- *
12
- * Tests empty, loading, error states for VirtualGrid component,
13
- * and edge cases like single-row data, many columns, and missing fields.
14
- */
15
-
16
- import { describe, it, expect, vi, beforeEach } from 'vitest';
17
- import { render, screen, cleanup } from '@testing-library/react';
18
- import '@testing-library/jest-dom';
19
- import React from 'react';
20
- import type { VirtualGridColumn, VirtualGridProps } from '../VirtualGrid';
21
-
22
- // Mock @tanstack/react-virtual
23
- vi.mock('@tanstack/react-virtual', () => ({
24
- useVirtualizer: (opts: any) => {
25
- const count: number = opts.count;
26
- const size: number = opts.estimateSize();
27
- const items = [];
28
- for (let i = 0; i < count; i++) {
29
- items.push({ index: i, key: String(i), start: i * size, size });
30
- }
31
- return {
32
- getVirtualItems: () => items,
33
- getTotalSize: () => count * size,
34
- };
35
- },
36
- }));
37
-
38
- const defaultColumns: VirtualGridColumn[] = [
39
- { header: 'Name', accessorKey: 'name' },
40
- { header: 'Email', accessorKey: 'email' },
41
- { header: 'Status', accessorKey: 'status' },
42
- ];
43
-
44
- type VirtualGridComponent = React.FC<VirtualGridProps>;
45
- let VirtualGrid: VirtualGridComponent;
46
-
47
- beforeEach(async () => {
48
- cleanup();
49
- vi.resetModules();
50
- const mod = await import('../VirtualGrid');
51
- VirtualGrid = mod.VirtualGrid;
52
- });
53
-
54
- function renderGrid(overrides: Partial<VirtualGridProps> = {}) {
55
- return render(
56
- <VirtualGrid
57
- data={[]}
58
- columns={defaultColumns}
59
- {...overrides}
60
- />
61
- );
62
- }
63
-
64
- describe('P3.3 Grid View States', () => {
65
- // ---------------------------------------------------------------
66
- // Empty state
67
- // ---------------------------------------------------------------
68
- describe('empty state', () => {
69
- it('renders column headers with empty data', () => {
70
- renderGrid({ data: [] });
71
- expect(screen.getByText('Name')).toBeInTheDocument();
72
- expect(screen.getByText('Email')).toBeInTheDocument();
73
- expect(screen.getByText('Status')).toBeInTheDocument();
74
- });
75
-
76
- it('shows 0 rows in footer for empty data', () => {
77
- renderGrid({ data: [] });
78
- expect(screen.getByText(/Showing 0 of 0 rows/)).toBeInTheDocument();
79
- });
80
-
81
- it('does not render any data cells for empty data', () => {
82
- renderGrid({ data: [] });
83
- expect(screen.queryByText('Alice')).not.toBeInTheDocument();
84
- });
85
-
86
- it('renders with empty columns array', () => {
87
- const { container } = renderGrid({ data: [], columns: [] });
88
- expect(container.firstElementChild).toBeInTheDocument();
89
- });
90
- });
91
-
92
- // ---------------------------------------------------------------
93
- // Normal data rendering
94
- // ---------------------------------------------------------------
95
- describe('normal data rendering', () => {
96
- const sampleData = [
97
- { name: 'Alice', email: 'alice@test.com', status: 'active' },
98
- { name: 'Bob', email: 'bob@test.com', status: 'inactive' },
99
- ];
100
-
101
- it('renders all rows', () => {
102
- renderGrid({ data: sampleData });
103
- expect(screen.getByText('Alice')).toBeInTheDocument();
104
- expect(screen.getByText('Bob')).toBeInTheDocument();
105
- });
106
-
107
- it('shows correct row count', () => {
108
- renderGrid({ data: sampleData });
109
- expect(screen.getByText(/Showing 2 of 2 rows/)).toBeInTheDocument();
110
- });
111
-
112
- it('renders all column values', () => {
113
- renderGrid({ data: sampleData });
114
- expect(screen.getByText('alice@test.com')).toBeInTheDocument();
115
- expect(screen.getByText('active')).toBeInTheDocument();
116
- });
117
- });
118
-
119
- // ---------------------------------------------------------------
120
- // Edge cases
121
- // ---------------------------------------------------------------
122
- describe('edge cases', () => {
123
- it('renders single row', () => {
124
- renderGrid({ data: [{ name: 'Solo', email: 'solo@test.com', status: 'ok' }] });
125
- expect(screen.getByText('Solo')).toBeInTheDocument();
126
- expect(screen.getByText(/Showing 1 of 1 rows/)).toBeInTheDocument();
127
- });
128
-
129
- it('handles row with missing fields gracefully', () => {
130
- renderGrid({
131
- data: [{ name: 'Partial' }],
132
- });
133
- expect(screen.getByText('Partial')).toBeInTheDocument();
134
- // Missing email and status should not crash
135
- });
136
-
137
- it('handles many columns', () => {
138
- const cols: VirtualGridColumn[] = Array.from({ length: 20 }, (_, i) => ({
139
- header: `Col${i}`,
140
- accessorKey: `field${i}`,
141
- }));
142
- const data = [Object.fromEntries(cols.map((c, i) => [c.accessorKey, `val${i}`]))];
143
-
144
- renderGrid({ columns: cols, data });
145
- expect(screen.getByText('Col0')).toBeInTheDocument();
146
- expect(screen.getByText('Col19')).toBeInTheDocument();
147
- expect(screen.getByText('val0')).toBeInTheDocument();
148
- expect(screen.getByText('val19')).toBeInTheDocument();
149
- });
150
-
151
- it('handles data with null/undefined field values', () => {
152
- renderGrid({
153
- data: [{ name: null, email: undefined, status: 'ok' }],
154
- });
155
- expect(screen.getByText('ok')).toBeInTheDocument();
156
- });
157
-
158
- it('handles data with numeric values', () => {
159
- const cols: VirtualGridColumn[] = [
160
- { header: 'ID', accessorKey: 'id' },
161
- { header: 'Score', accessorKey: 'score' },
162
- ];
163
- renderGrid({
164
- columns: cols,
165
- data: [{ id: 1, score: 99.5 }],
166
- });
167
- expect(screen.getByText('1')).toBeInTheDocument();
168
- expect(screen.getByText('99.5')).toBeInTheDocument();
169
- });
170
-
171
- it('handles data with boolean values', () => {
172
- const cols: VirtualGridColumn[] = [
173
- { header: 'Name', accessorKey: 'name' },
174
- { header: 'Active', accessorKey: 'active' },
175
- ];
176
- renderGrid({
177
- columns: cols,
178
- data: [{ name: 'Test', active: true }],
179
- });
180
- expect(screen.getByText('Test')).toBeInTheDocument();
181
- });
182
- });
183
-
184
- // ---------------------------------------------------------------
185
- // Custom className support
186
- // ---------------------------------------------------------------
187
- describe('className support in states', () => {
188
- it('applies className to empty grid', () => {
189
- const { container } = renderGrid({ data: [], className: 'my-grid' });
190
- const root = container.firstElementChild as HTMLElement;
191
- expect(root).toHaveClass('my-grid');
192
- });
193
-
194
- it('applies className to populated grid', () => {
195
- const { container } = renderGrid({
196
- data: [{ name: 'A', email: 'a@t.com', status: 'ok' }],
197
- className: 'styled-grid',
198
- });
199
- const root = container.firstElementChild as HTMLElement;
200
- expect(root).toHaveClass('styled-grid');
201
- });
202
- });
203
- });
@@ -1,66 +0,0 @@
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 { Button } from '@object-ui/components';
11
- import { formatActionLabel } from './RowActionMenu';
12
-
13
- export interface BulkActionBarProps {
14
- /** Array of selected row records */
15
- selectedRows: any[];
16
- /** Bulk/batch action identifiers */
17
- actions: string[];
18
- /** Callback when a bulk action button is clicked */
19
- onAction?: (action: string, selectedRows: any[]) => void;
20
- /** Callback to clear selection */
21
- onClearSelection?: () => void;
22
- }
23
-
24
- export const BulkActionBar: React.FC<BulkActionBarProps> = ({
25
- selectedRows,
26
- actions,
27
- onAction,
28
- onClearSelection,
29
- }) => {
30
- if (!actions || actions.length === 0 || selectedRows.length === 0) {
31
- return null;
32
- }
33
-
34
- return (
35
- <div
36
- className="border-t px-4 py-1.5 flex items-center gap-2 text-xs bg-primary/5 shrink-0"
37
- data-testid="bulk-actions-bar"
38
- >
39
- <span className="text-muted-foreground font-medium">
40
- {selectedRows.length} selected
41
- </span>
42
- <div className="flex items-center gap-1 ml-2">
43
- {actions.map(action => (
44
- <Button
45
- key={action}
46
- variant="outline"
47
- size="sm"
48
- className="h-6 px-2 text-xs"
49
- onClick={() => onAction?.(action, selectedRows)}
50
- data-testid={`bulk-action-${action}`}
51
- >
52
- {formatActionLabel(action)}
53
- </Button>
54
- ))}
55
- </div>
56
- <Button
57
- variant="ghost"
58
- size="sm"
59
- className="h-6 px-2 text-xs ml-auto"
60
- onClick={onClearSelection}
61
- >
62
- Clear
63
- </Button>
64
- </div>
65
- );
66
- };
@@ -1,91 +0,0 @@
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 {
11
- Button,
12
- DropdownMenu,
13
- DropdownMenuContent,
14
- DropdownMenuItem,
15
- DropdownMenuTrigger,
16
- } from '@object-ui/components';
17
- import { Edit, Trash2, MoreVertical } from 'lucide-react';
18
-
19
- /**
20
- * Format an action identifier string into a human-readable label.
21
- * e.g., 'send_email' → 'Send Email'
22
- */
23
- export function formatActionLabel(action: string): string {
24
- return action.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
25
- }
26
-
27
- export interface RowActionMenuProps {
28
- /** The row data record */
29
- row: any;
30
- /** Custom row action identifiers */
31
- rowActions?: string[];
32
- /** Whether edit operation is available */
33
- canEdit?: boolean;
34
- /** Whether delete operation is available */
35
- canDelete?: boolean;
36
- /** Callback when edit is clicked */
37
- onEdit?: (row: any) => void;
38
- /** Callback when delete is clicked */
39
- onDelete?: (row: any) => void;
40
- /** Callback when a custom row action is clicked */
41
- onAction?: (action: string, row: any) => void;
42
- }
43
-
44
- export const RowActionMenu: React.FC<RowActionMenuProps> = ({
45
- row,
46
- rowActions,
47
- canEdit,
48
- canDelete,
49
- onEdit,
50
- onDelete,
51
- onAction,
52
- }) => {
53
- return (
54
- <DropdownMenu>
55
- <DropdownMenuTrigger asChild>
56
- <Button
57
- variant="ghost"
58
- size="icon"
59
- className="h-8 w-8 min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0"
60
- data-testid="row-action-trigger"
61
- >
62
- <MoreVertical className="h-4 w-4" />
63
- <span className="sr-only">Open menu</span>
64
- </Button>
65
- </DropdownMenuTrigger>
66
- <DropdownMenuContent align="end">
67
- {canEdit && onEdit && (
68
- <DropdownMenuItem onClick={() => onEdit(row)}>
69
- <Edit className="mr-2 h-4 w-4" />
70
- Edit
71
- </DropdownMenuItem>
72
- )}
73
- {canDelete && onDelete && (
74
- <DropdownMenuItem onClick={() => onDelete(row)}>
75
- <Trash2 className="mr-2 h-4 w-4" />
76
- Delete
77
- </DropdownMenuItem>
78
- )}
79
- {rowActions?.map(action => (
80
- <DropdownMenuItem
81
- key={action}
82
- onClick={() => onAction?.(action, row)}
83
- data-testid={`row-action-${action}`}
84
- >
85
- {formatActionLabel(action)}
86
- </DropdownMenuItem>
87
- ))}
88
- </DropdownMenuContent>
89
- </DropdownMenu>
90
- );
91
- };
@@ -1,29 +0,0 @@
1
- import { vi, describe, it, expect, beforeEach } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import React from 'react';
4
- import { SchemaRendererProvider } from '@object-ui/react';
5
- import * as ObjectGridModule from './ObjectGrid';
6
- import { ObjectGridRenderer } from './index';
7
-
8
- describe('Plugin Grid Registration', () => {
9
- it('renderer passes dataSource from context', async () => {
10
- // Spy and mock implementation
11
- vi.spyOn(ObjectGridModule, 'ObjectGrid').mockImplementation(
12
- (({ dataSource }: any) => (
13
- <div data-testid="grid-mock">
14
- {dataSource ? `DataSource: ${dataSource.type}` : 'No DataSource'}
15
- </div>
16
- )) as any
17
- );
18
-
19
- render(
20
- <SchemaRendererProvider dataSource={{ type: 'mock-datasource' } as any}>
21
- <ObjectGridRenderer schema={{ type: 'object-grid' }} />
22
- </SchemaRendererProvider>
23
- );
24
-
25
- // Use findByTestId for async safety
26
- const element = await screen.findByTestId('grid-mock', {}, { timeout: 5000 });
27
- expect(element).toHaveTextContent('DataSource: mock-datasource');
28
- });
29
- });
package/src/index.tsx DELETED
@@ -1,99 +0,0 @@
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 { ComponentRegistry } from '@object-ui/core';
11
- import { useSchemaContext } from '@object-ui/react';
12
- import { ObjectGrid } from './ObjectGrid';
13
- import { VirtualGrid } from './VirtualGrid';
14
- import { ImportWizard } from './ImportWizard';
15
-
16
- export { ObjectGrid, VirtualGrid, ImportWizard };
17
- export { InlineEditing } from './InlineEditing';
18
- export { useRowColor } from './useRowColor';
19
- export { useGroupedData } from './useGroupedData';
20
- export { GroupRow } from './GroupRow';
21
- export { RowActionMenu, formatActionLabel } from './components/RowActionMenu';
22
- export { BulkActionBar } from './components/BulkActionBar';
23
- export { useCellClipboard } from './useCellClipboard';
24
- export { useGradientColor } from './useGradientColor';
25
- export { useGroupReorder } from './useGroupReorder';
26
- export { useColumnSummary } from './useColumnSummary';
27
- export { FormulaBar } from './FormulaBar';
28
- export { SplitPaneGrid } from './SplitPaneGrid';
29
- export type { ObjectGridProps } from './ObjectGrid';
30
- export type { VirtualGridProps, VirtualGridColumn } from './VirtualGrid';
31
- export type { InlineEditingProps } from './InlineEditing';
32
- export type { ImportWizardProps, ImportResult } from './ImportWizard';
33
- export type { GroupEntry, UseGroupedDataResult, AggregationType, AggregationConfig, AggregationResult } from './useGroupedData';
34
- export type { GroupRowProps } from './GroupRow';
35
- export type { RowActionMenuProps } from './components/RowActionMenu';
36
- export type { BulkActionBarProps } from './components/BulkActionBar';
37
- export type { CellRange, UseCellClipboardOptions, UseCellClipboardResult } from './useCellClipboard';
38
- export type { GradientStop, UseGradientColorOptions } from './useGradientColor';
39
- export type { UseGroupReorderOptions, UseGroupReorderResult } from './useGroupReorder';
40
- export type { ColumnSummaryConfig, ColumnSummaryResult } from './useColumnSummary';
41
- export type { FormulaBarProps } from './FormulaBar';
42
- export type { SplitPaneGridProps } from './SplitPaneGrid';
43
-
44
- // Register object-grid component
45
- export const ObjectGridRenderer: React.FC<{ schema: any; [key: string]: any }> = ({ schema, ...props }) => {
46
- const { dataSource } = useSchemaContext() || {};
47
- return <ObjectGrid schema={schema} dataSource={dataSource} {...props} />;
48
- };
49
-
50
- ComponentRegistry.register('object-grid', ObjectGridRenderer, {
51
- namespace: 'plugin-grid',
52
- label: 'Object Grid',
53
- category: 'plugin',
54
- inputs: [
55
- { name: 'objectName', type: 'string', label: 'Object Name', required: true },
56
- { name: 'columns', type: 'array', label: 'Columns' },
57
- { name: 'filters', type: 'array', label: 'Filters' },
58
- ]
59
- });
60
-
61
- // Alias for view namespace - this allows using { type: 'view:grid' } in schemas
62
- // which is semantically meaningful for data display components
63
- ComponentRegistry.register('grid', ObjectGridRenderer, {
64
- namespace: 'view',
65
- label: 'Data Grid',
66
- category: 'view',
67
- inputs: [
68
- { name: 'objectName', type: 'string', label: 'Object Name', required: true },
69
- { name: 'columns', type: 'array', label: 'Columns' },
70
- { name: 'filters', type: 'array', label: 'Filters' },
71
- ]
72
- });
73
-
74
- // Register import-wizard component
75
- const ImportWizardRenderer: React.FC<{ schema: any; [key: string]: any }> = ({ schema, ...props }) => {
76
- const { dataSource } = useSchemaContext() || {};
77
- return (
78
- <ImportWizard
79
- objectName={schema.objectName}
80
- objectLabel={schema.objectLabel}
81
- fields={schema.fields ?? []}
82
- dataSource={dataSource}
83
- {...props}
84
- />
85
- );
86
- };
87
-
88
- ComponentRegistry.register('import-wizard', ImportWizardRenderer, {
89
- namespace: 'plugin-grid',
90
- label: 'Import Wizard',
91
- category: 'plugin',
92
- inputs: [
93
- { name: 'objectName', type: 'string', label: 'Object Name', required: true },
94
- { name: 'fields', type: 'array', label: 'Fields', required: true },
95
- ]
96
- });
97
-
98
- // Note: 'grid' type is handled by @object-ui/components Grid layout component
99
- // This plugin only handles 'object-grid' which integrates with ObjectQL/ObjectStack