@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,147 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
3
- import type { BaseSchema } from '@object-ui/types';
4
- import { createStorybookDataSource } from '@storybook-config/datasource';
5
-
6
- const meta = {
7
- title: 'Plugins/ObjectGrid/Edge Cases',
8
- component: SchemaRenderer,
9
- parameters: {
10
- layout: 'padded',
11
- },
12
- tags: ['autodocs'],
13
- argTypes: {
14
- schema: { table: { disable: true } },
15
- },
16
- } satisfies Meta<any>;
17
-
18
- export default meta;
19
- type Story = StoryObj<typeof meta>;
20
-
21
- const dataSource = createStorybookDataSource();
22
-
23
- const renderStory = (args: any) => (
24
- <SchemaRendererProvider dataSource={dataSource}>
25
- <SchemaRenderer schema={args as unknown as BaseSchema} />
26
- </SchemaRendererProvider>
27
- );
28
-
29
- // ── Empty Data ────────────────────────────────────────────────
30
-
31
- export const EmptyData: Story = {
32
- name: 'Empty – No Rows',
33
- render: renderStory,
34
- args: {
35
- type: 'object-grid',
36
- objectName: 'Employee',
37
- columns: [
38
- { field: 'id', header: 'ID', width: 80 },
39
- { field: 'name', header: 'Name' },
40
- { field: 'email', header: 'Email' },
41
- { field: 'department', header: 'Department' },
42
- ],
43
- data: [],
44
- pagination: false,
45
- className: 'w-full',
46
- } as any,
47
- };
48
-
49
- // ── Single Row ────────────────────────────────────────────────
50
-
51
- export const SingleRow: Story = {
52
- name: 'Single Row',
53
- render: renderStory,
54
- args: {
55
- type: 'object-grid',
56
- objectName: 'Employee',
57
- columns: [
58
- { field: 'id', header: 'ID', width: 80 },
59
- { field: 'name', header: 'Name', sortable: true },
60
- { field: 'email', header: 'Email' },
61
- { field: 'department', header: 'Department' },
62
- ],
63
- data: [
64
- { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering' },
65
- ],
66
- pagination: false,
67
- className: 'w-full',
68
- } as any,
69
- };
70
-
71
- // ── Many Columns ──────────────────────────────────────────────
72
-
73
- export const ManyColumns: Story = {
74
- name: 'Many Columns (15+)',
75
- render: renderStory,
76
- args: {
77
- type: 'object-grid',
78
- objectName: 'WideTable',
79
- columns: Array.from({ length: 18 }, (_, i) => ({
80
- field: `col${i + 1}`,
81
- header: `Column ${i + 1}`,
82
- sortable: i < 5,
83
- })),
84
- data: Array.from({ length: 5 }, (_, row) => {
85
- const record: Record<string, any> = {};
86
- for (let c = 1; c <= 18; c++) {
87
- record[`col${c}`] = `R${row + 1}-C${c}`;
88
- }
89
- return record;
90
- }),
91
- pagination: false,
92
- className: 'w-full',
93
- } as any,
94
- };
95
-
96
- // ── Very Long Cell Values ─────────────────────────────────────
97
-
98
- const LONG_VALUE =
99
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
100
-
101
- export const LongCellValues: Story = {
102
- name: 'Very Long Cell Values',
103
- render: renderStory,
104
- args: {
105
- type: 'object-grid',
106
- objectName: 'Article',
107
- columns: [
108
- { field: 'id', header: 'ID', width: 60 },
109
- { field: 'title', header: 'Title' },
110
- { field: 'abstract', header: 'Abstract' },
111
- { field: 'author', header: 'Author' },
112
- ],
113
- data: [
114
- { id: 1, title: LONG_VALUE, abstract: LONG_VALUE + ' ' + LONG_VALUE, author: 'Dr. Extremely Long Author Name The Third Junior' },
115
- { id: 2, title: 'Short', abstract: 'Brief.', author: 'Bob' },
116
- { id: 3, title: LONG_VALUE, abstract: LONG_VALUE, author: LONG_VALUE },
117
- ],
118
- pagination: false,
119
- className: 'w-full',
120
- } as any,
121
- };
122
-
123
- // ── Null / Undefined Values ───────────────────────────────────
124
-
125
- export const NullAndUndefinedValues: Story = {
126
- name: 'Null / Undefined Cell Values',
127
- render: renderStory,
128
- args: {
129
- type: 'object-grid',
130
- objectName: 'Sparse',
131
- columns: [
132
- { field: 'id', header: 'ID', width: 60 },
133
- { field: 'name', header: 'Name' },
134
- { field: 'email', header: 'Email' },
135
- { field: 'phone', header: 'Phone' },
136
- { field: 'notes', header: 'Notes' },
137
- ],
138
- data: [
139
- { id: 1, name: 'Alice', email: null, phone: undefined, notes: '' },
140
- { id: 2, name: null, email: 'bob@example.com', phone: null, notes: undefined },
141
- { id: 3, name: undefined, email: undefined, phone: undefined, notes: null },
142
- { id: 4, name: 'Dave', email: 'dave@example.com', phone: '+1-555-0100', notes: 'Complete record' },
143
- ],
144
- pagination: false,
145
- className: 'w-full',
146
- } as any,
147
- };
@@ -1,130 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from 'vitest';
2
- import { render, screen, waitFor, within } from '@testing-library/react';
3
- import '@testing-library/jest-dom';
4
- import { ObjectGrid } from './ObjectGrid';
5
- import { ObjectStackAdapter } from '@object-ui/data-objectstack';
6
- import { setupServer } from 'msw/node';
7
- import { http, HttpResponse } from 'msw';
8
- import { registerAllFields } from '@object-ui/fields';
9
- import React from 'react';
10
- import { ContactObject } from '../../../examples/crm/src/objects/contact.object';
11
-
12
- registerAllFields();
13
-
14
- const BASE_URL = process.env.OBJECTSTACK_API_URL || 'http://localhost';
15
-
16
- // --- Mock Data ---
17
-
18
- const mockSchema = ContactObject;
19
-
20
- const mockData = {
21
- value: [
22
- { id: '1', name: 'John Doe', email: 'john@example.com', company: 'Acme Inc' },
23
- { id: '2', name: 'Jane Smith', email: 'jane@test.com', company: 'Globex' },
24
- { id: '3', name: 'Bob Wilson', email: 'bob@test.com', company: 'Acme Inc' }
25
- ],
26
- count: 3,
27
- '@odata.count': 3
28
- };
29
-
30
- // --- MSW Setup ---
31
-
32
- const handlers = [
33
- // .well-known discovery endpoint (used by client.connect())
34
- http.get(`${BASE_URL}/.well-known/objectstack`, () => {
35
- return HttpResponse.json({
36
- name: 'ObjectStack API',
37
- version: '1.0',
38
- endpoints: {
39
- data: '/api/v1/data',
40
- metadata: '/api/v1/meta'
41
- },
42
- capabilities: {
43
- graphql: false,
44
- search: false,
45
- websockets: false,
46
- files: true,
47
- analytics: false,
48
- hub: false
49
- }
50
- });
51
- }),
52
-
53
- // OPTIONS handler for CORS preflight
54
- http.options(`${BASE_URL}/*`, () => {
55
- return new HttpResponse(null, {
56
- status: 200,
57
- headers: {
58
- 'Access-Control-Allow-Origin': '*',
59
- 'Access-Control-Allow-Methods': 'GET,HEAD,POST,PUT,DELETE,CONNECT,OPTIONS,TRACE,PATCH',
60
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
61
- },
62
- });
63
- }),
64
-
65
- // Health check / Connection check
66
- http.get(`${BASE_URL}/api/v1`, () => {
67
- return HttpResponse.json({ status: 'ok', version: '1.0.0' });
68
- }),
69
-
70
- // Schema: /api/v1/metadata/object/:name and /api/v1/meta/object/:name (client uses /meta)
71
- http.get(`${BASE_URL}/api/v1/metadata/object/contact`, () => {
72
- return HttpResponse.json(mockSchema);
73
- }),
74
- http.get(`${BASE_URL}/api/v1/meta/object/contact`, () => {
75
- return HttpResponse.json(mockSchema);
76
- }),
77
-
78
- // Data Query: /api/v1/data/contact
79
- http.get(`${BASE_URL}/api/v1/data/contact`, () => {
80
- return HttpResponse.json(mockData);
81
- })
82
- ];
83
-
84
- const server = setupServer(...handlers);
85
-
86
- // --- Test Suite ---
87
-
88
- describe('ObjectGrid with ObjectStack/MSW', () => {
89
- // Only start MSW if we are NOT using a real server
90
- if (!process.env.OBJECTSTACK_API_URL) {
91
- beforeAll(() => server.listen());
92
- afterEach(() => server.resetHandlers());
93
- afterAll(() => server.close());
94
- }
95
-
96
- const dataSource = new ObjectStackAdapter({
97
- baseUrl: BASE_URL,
98
- });
99
-
100
- it('loads schema and data rows', async () => {
101
- render(
102
- <ObjectGrid
103
- schema={{
104
- type: 'object-grid',
105
- objectName: 'contact',
106
- columns: ['name', 'email', 'company'] // Explicit columns or auto-derived
107
- }}
108
- dataSource={dataSource}
109
- />
110
- );
111
-
112
- // Verify Column Headers (from schema or props)
113
- await waitFor(() => {
114
- // Changed from 'Full Name' to 'Name' to match CRM example schema
115
- expect(screen.getByText('Name')).toBeInTheDocument();
116
- });
117
- expect(screen.getByText('Email')).toBeInTheDocument();
118
- expect(screen.getByText('Company')).toBeInTheDocument();
119
-
120
- // Verify Data Rows
121
- await waitFor(() => {
122
- expect(screen.getByText('John Doe')).toBeInTheDocument();
123
- });
124
- expect(screen.getByText('jane@test.com')).toBeInTheDocument();
125
- expect(screen.getByText('Globex')).toBeInTheDocument();
126
-
127
- // Check Row Count (if grid displays it)
128
- // expect(screen.getByText(/3 records/i)).toBeInTheDocument();
129
- });
130
- });
@@ -1,139 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
3
- import type { BaseSchema } from '@object-ui/types';
4
- import { createStorybookDataSource } from '@storybook-config/datasource';
5
-
6
- const meta = {
7
- title: 'Plugins/ObjectGrid',
8
- component: SchemaRenderer,
9
- parameters: {
10
- layout: 'padded',
11
- },
12
- tags: ['autodocs'],
13
- argTypes: {
14
- schema: { table: { disable: true } },
15
- },
16
- } satisfies Meta<any>;
17
-
18
- export default meta;
19
- type Story = StoryObj<typeof meta>;
20
-
21
- const dataSource = createStorybookDataSource();
22
-
23
- const renderStory = (args: any) => (
24
- <SchemaRendererProvider dataSource={dataSource}>
25
- <SchemaRenderer schema={args as unknown as BaseSchema} />
26
- </SchemaRendererProvider>
27
- );
28
-
29
- export const Default: Story = {
30
- render: renderStory,
31
- args: {
32
- type: 'object-grid',
33
- objectName: 'Employee',
34
- columns: [
35
- { field: 'id', header: 'ID', width: 80 },
36
- { field: 'name', header: 'Name', sortable: true, filterable: true },
37
- { field: 'email', header: 'Email', sortable: true, filterable: true },
38
- { field: 'department', header: 'Department', sortable: true },
39
- { field: 'status', header: 'Status', sortable: true },
40
- ],
41
- data: [
42
- { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', status: 'Active' },
43
- { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Marketing', status: 'Active' },
44
- { id: 3, name: 'Carol White', email: 'carol@example.com', department: 'Sales', status: 'Inactive' },
45
- { id: 4, name: 'Dave Brown', email: 'dave@example.com', department: 'Engineering', status: 'Active' },
46
- { id: 5, name: 'Eve Davis', email: 'eve@example.com', department: 'HR', status: 'Active' },
47
- ],
48
- pagination: true,
49
- pageSize: 10,
50
- className: 'w-full',
51
- } as any,
52
- };
53
-
54
- export const WithRowActions: Story = {
55
- render: renderStory,
56
- args: {
57
- type: 'object-grid',
58
- objectName: 'Task',
59
- columns: [
60
- { field: 'id', header: 'ID', width: 80 },
61
- { field: 'title', header: 'Title', sortable: true, filterable: true },
62
- { field: 'assignee', header: 'Assignee', sortable: true },
63
- { field: 'priority', header: 'Priority', sortable: true },
64
- { field: 'status', header: 'Status', sortable: true },
65
- ],
66
- actions: [
67
- { label: 'View', action: 'view' },
68
- { label: 'Edit', action: 'edit' },
69
- { label: 'Delete', action: 'delete', variant: 'destructive' },
70
- ],
71
- data: [
72
- { id: 1, title: 'Fix login bug', assignee: 'Alice', priority: 'High', status: 'In Progress' },
73
- { id: 2, title: 'Add dark mode', assignee: 'Bob', priority: 'Medium', status: 'To Do' },
74
- { id: 3, title: 'Update docs', assignee: 'Carol', priority: 'Low', status: 'Done' },
75
- ],
76
- pagination: true,
77
- pageSize: 10,
78
- className: 'w-full',
79
- } as any,
80
- };
81
-
82
- /**
83
- * CRM Deals Pipeline — demonstrates professional data formatting:
84
- * - Currency with thousand separators (Amount column, right-aligned)
85
- * - Percentage with progress bar (Probability column, right-aligned)
86
- * - Formatted dates (Close Date column)
87
- * - Colored badges for stage/status (Stage column)
88
- * - Bold clickable name as primary link (Name column)
89
- * - Empty value placeholder (Account column)
90
- */
91
- export const CRMDeals: Story = {
92
- name: 'CRM Deals Pipeline',
93
- render: renderStory,
94
- args: {
95
- type: 'object-grid',
96
- objectName: 'Deal',
97
- columns: [
98
- { field: 'name', label: 'Name', link: true, sortable: true },
99
- { field: 'account', label: 'Account' },
100
- { field: 'stage', label: 'Stage', type: 'select', sortable: true },
101
- { field: 'amount', label: 'Amount', type: 'currency', sortable: true },
102
- { field: 'probability', label: 'Probability', type: 'percent', sortable: true },
103
- { field: 'close_date', label: 'Close Date', type: 'date', sortable: true },
104
- ],
105
- data: [
106
- { id: '1', name: 'ObjectStack Enterprise License', account: '', stage: 'Closed Won', amount: 150000, probability: 100, close_date: '2024-01-15T00:00:00.000Z' },
107
- { id: '2', name: 'Cloud Migration Project', account: 'Acme Corp', stage: 'Negotiation', amount: 85000, probability: 60, close_date: '2024-03-20T00:00:00.000Z' },
108
- { id: '3', name: 'Annual Support Renewal', account: '', stage: 'Proposal', amount: 42000, probability: 80, close_date: '2024-02-28T00:00:00.000Z' },
109
- { id: '4', name: 'Custom Integration Development', account: 'TechFlow Inc', stage: 'Qualification', amount: 230000, probability: 30, close_date: '2024-06-15T00:00:00.000Z' },
110
- { id: '5', name: 'Data Analytics Platform', account: '', stage: 'Closed Lost', amount: 95000, probability: 0, close_date: '2024-01-10T00:00:00.000Z' },
111
- { id: '6', name: 'Security Audit Contract', account: 'SecureNet', stage: 'Closed Won', amount: 67500, probability: 100, close_date: '2024-02-01T00:00:00.000Z' },
112
- { id: '7', name: 'Mobile App Development', account: '', stage: 'Discovery', amount: 180000, probability: 15, close_date: '2024-08-30T00:00:00.000Z' },
113
- ],
114
- pagination: false,
115
- className: 'w-full',
116
- } as any,
117
- };
118
-
119
- export const EditableGrid: Story = {
120
- render: renderStory,
121
- args: {
122
- type: 'object-grid',
123
- objectName: 'Product',
124
- columns: [
125
- { field: 'sku', header: 'SKU', width: 100, editable: false },
126
- { field: 'name', header: 'Name', sortable: true },
127
- { field: 'price', header: 'Price', sortable: true },
128
- { field: 'stock', header: 'Stock', sortable: true },
129
- ],
130
- data: [
131
- { sku: 'SKU-001', name: 'Widget A', price: '$19.99', stock: 50 },
132
- { sku: 'SKU-002', name: 'Widget B', price: '$29.99', stock: 30 },
133
- { sku: 'SKU-003', name: 'Widget C', price: '$9.99', stock: 120 },
134
- ],
135
- editable: true,
136
- pagination: false,
137
- className: 'w-full',
138
- } as any,
139
- };