@object-ui/plugin-grid 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ObjectQL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # @object-ui/plugin-grid
2
+
3
+ Grid plugin for Object UI - Advanced data grid with sorting, filtering, and pagination.
4
+
5
+ ## Features
6
+
7
+ - **Data Grid** - Enterprise-grade data grid component
8
+ - **Sorting** - Multi-column sorting support
9
+ - **Filtering** - Column-level filtering
10
+ - **Pagination** - Built-in pagination controls
11
+ - **Row Selection** - Single and multi-row selection
12
+ - **Customizable** - Tailwind CSS styling support
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @object-ui/plugin-grid
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Automatic Registration (Side-Effect Import)
23
+
24
+ ```typescript
25
+ // In your app entry point (e.g., App.tsx or main.tsx)
26
+ import '@object-ui/plugin-grid';
27
+
28
+ // Now you can use grid types in your schemas
29
+ const schema = {
30
+ type: 'grid',
31
+ columns: [
32
+ { header: 'Name', accessorKey: 'name' },
33
+ { header: 'Email', accessorKey: 'email' }
34
+ ],
35
+ data: []
36
+ };
37
+ ```
38
+
39
+ ### Manual Registration
40
+
41
+ ```typescript
42
+ import { gridComponents } from '@object-ui/plugin-grid';
43
+ import { ComponentRegistry } from '@object-ui/core';
44
+
45
+ // Register grid components
46
+ Object.entries(gridComponents).forEach(([type, component]) => {
47
+ ComponentRegistry.register(type, component);
48
+ });
49
+ ```
50
+
51
+ ## Schema API
52
+
53
+ ### Grid
54
+
55
+ Data grid with advanced features:
56
+
57
+ ```typescript
58
+ {
59
+ type: 'grid',
60
+ columns: GridColumn[],
61
+ data?: any[],
62
+ sortable?: boolean,
63
+ filterable?: boolean,
64
+ pagination?: boolean | PaginationConfig,
65
+ selectable?: boolean,
66
+ onRowClick?: (row) => void,
67
+ className?: string
68
+ }
69
+ ```
70
+
71
+ ### Column Definition
72
+
73
+ ```typescript
74
+ interface GridColumn {
75
+ header: string;
76
+ accessorKey: string;
77
+ sortable?: boolean;
78
+ filterable?: boolean;
79
+ width?: number | string;
80
+ align?: 'left' | 'center' | 'right';
81
+ cell?: (value, row) => ReactNode;
82
+ }
83
+ ```
84
+
85
+ ## Examples
86
+
87
+ ### Basic Grid
88
+
89
+ ```typescript
90
+ const schema = {
91
+ type: 'grid',
92
+ columns: [
93
+ { header: 'ID', accessorKey: 'id', width: 80 },
94
+ { header: 'Name', accessorKey: 'name' },
95
+ { header: 'Email', accessorKey: 'email' },
96
+ { header: 'Role', accessorKey: 'role' },
97
+ { header: 'Status', accessorKey: 'status' }
98
+ ],
99
+ data: [
100
+ { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'Active' },
101
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User', status: 'Active' }
102
+ ],
103
+ sortable: true,
104
+ filterable: true,
105
+ pagination: true
106
+ };
107
+ ```
108
+
109
+ ### Grid with Custom Cells
110
+
111
+ ```typescript
112
+ const schema = {
113
+ type: 'grid',
114
+ columns: [
115
+ { header: 'Name', accessorKey: 'name' },
116
+ {
117
+ header: 'Status',
118
+ accessorKey: 'status',
119
+ cell: (value) => ({
120
+ type: 'badge',
121
+ label: value,
122
+ variant: value === 'Active' ? 'success' : 'default'
123
+ })
124
+ },
125
+ {
126
+ header: 'Actions',
127
+ accessorKey: 'id',
128
+ cell: (value, row) => ({
129
+ type: 'button-group',
130
+ buttons: [
131
+ { label: 'Edit', onClick: () => console.log('Edit', row) },
132
+ { label: 'Delete', variant: 'destructive', onClick: () => console.log('Delete', row) }
133
+ ]
134
+ })
135
+ }
136
+ ],
137
+ data: [/* data */]
138
+ };
139
+ ```
140
+
141
+ ### Selectable Grid
142
+
143
+ ```typescript
144
+ const schema = {
145
+ type: 'grid',
146
+ columns: [/* columns */],
147
+ data: [/* data */],
148
+ selectable: true,
149
+ onRowClick: (row) => {
150
+ console.log('Row clicked:', row);
151
+ },
152
+ onSelectionChange: (selectedRows) => {
153
+ console.log('Selection changed:', selectedRows);
154
+ }
155
+ };
156
+ ```
157
+
158
+ ### Grid with Pagination
159
+
160
+ ```typescript
161
+ const schema = {
162
+ type: 'grid',
163
+ columns: [/* columns */],
164
+ data: [/* data */],
165
+ pagination: {
166
+ pageSize: 10,
167
+ showSizeChanger: true,
168
+ pageSizeOptions: [10, 20, 50, 100]
169
+ }
170
+ };
171
+ ```
172
+
173
+ ## Integration with Data Sources
174
+
175
+ Connect grid to backend APIs:
176
+
177
+ ```typescript
178
+ import { createObjectStackAdapter } from '@object-ui/data-objectstack';
179
+
180
+ const dataSource = createObjectStackAdapter({
181
+ baseUrl: 'https://api.example.com',
182
+ token: 'your-auth-token'
183
+ });
184
+
185
+ const schema = {
186
+ type: 'object-grid',
187
+ dataSource,
188
+ object: 'users',
189
+ columns: [
190
+ { header: 'Name', accessorKey: 'name' },
191
+ { header: 'Email', accessorKey: 'email' },
192
+ { header: 'Created', accessorKey: 'created_at' }
193
+ ],
194
+ pagination: true,
195
+ sortable: true,
196
+ filterable: true
197
+ };
198
+ ```
199
+
200
+ ## Features
201
+
202
+ ### Sorting
203
+
204
+ Enable sorting on columns:
205
+
206
+ ```typescript
207
+ const schema = {
208
+ type: 'grid',
209
+ sortable: true, // Enable sorting on all columns
210
+ columns: [
211
+ { header: 'Name', accessorKey: 'name', sortable: true },
212
+ { header: 'Email', accessorKey: 'email', sortable: false } // Disable for specific column
213
+ ]
214
+ };
215
+ ```
216
+
217
+ ### Filtering
218
+
219
+ Enable column filtering:
220
+
221
+ ```typescript
222
+ const schema = {
223
+ type: 'grid',
224
+ filterable: true,
225
+ columns: [
226
+ { header: 'Status', accessorKey: 'status', filterable: true }
227
+ ]
228
+ };
229
+ ```
230
+
231
+ ### Row Actions
232
+
233
+ Add actions to rows:
234
+
235
+ ```typescript
236
+ const schema = {
237
+ type: 'grid',
238
+ columns: [/* columns */],
239
+ rowActions: [
240
+ { label: 'View', onClick: (row) => console.log('View', row) },
241
+ { label: 'Edit', onClick: (row) => console.log('Edit', row) },
242
+ { label: 'Delete', onClick: (row) => console.log('Delete', row) }
243
+ ]
244
+ };
245
+ ```
246
+
247
+ ## TypeScript Support
248
+
249
+ ```typescript
250
+ import type { GridSchema, GridColumn } from '@object-ui/plugin-grid';
251
+
252
+ const nameColumn: GridColumn = {
253
+ header: 'Name',
254
+ accessorKey: 'name',
255
+ sortable: true
256
+ };
257
+
258
+ const grid: GridSchema = {
259
+ type: 'grid',
260
+ columns: [nameColumn],
261
+ data: [],
262
+ sortable: true,
263
+ pagination: true
264
+ };
265
+ ```
266
+
267
+ ## License
268
+
269
+ MIT
@@ -0,0 +1,2 @@
1
+ export * from './plugin-grid/src/index'
2
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,425 @@
1
+ import le, { useState as z, useEffect as J, useCallback as Z } from "react";
2
+ import { ComponentRegistry as oe } from "@object-ui/core";
3
+ import { SchemaRenderer as se } from "@object-ui/react";
4
+ import { getCellRenderer as ue } from "@object-ui/fields";
5
+ import { DropdownMenu as ce, DropdownMenuTrigger as fe, Button as de, DropdownMenuContent as pe, DropdownMenuItem as Q } from "@object-ui/components";
6
+ import { MoreVertical as be, Edit as ge, Trash2 as ve } from "lucide-react";
7
+ var I = { exports: {} }, h = {};
8
+ var ee;
9
+ function me() {
10
+ if (ee) return h;
11
+ ee = 1;
12
+ var r = /* @__PURE__ */ Symbol.for("react.transitional.element"), g = /* @__PURE__ */ Symbol.for("react.fragment");
13
+ function j(_, p, b) {
14
+ var y = null;
15
+ if (b !== void 0 && (y = "" + b), p.key !== void 0 && (y = "" + p.key), "key" in p) {
16
+ b = {};
17
+ for (var T in p)
18
+ T !== "key" && (b[T] = p[T]);
19
+ } else b = p;
20
+ return p = b.ref, {
21
+ $$typeof: r,
22
+ type: _,
23
+ key: y,
24
+ ref: p !== void 0 ? p : null,
25
+ props: b
26
+ };
27
+ }
28
+ return h.Fragment = g, h.jsx = j, h.jsxs = j, h;
29
+ }
30
+ var C = {};
31
+ var re;
32
+ function Ee() {
33
+ return re || (re = 1, process.env.NODE_ENV !== "production" && (function() {
34
+ function r(e) {
35
+ if (e == null) return null;
36
+ if (typeof e == "function")
37
+ return e.$$typeof === v ? null : e.displayName || e.name || null;
38
+ if (typeof e == "string") return e;
39
+ switch (e) {
40
+ case w:
41
+ return "Fragment";
42
+ case R:
43
+ return "Profiler";
44
+ case $:
45
+ return "StrictMode";
46
+ case U:
47
+ return "Suspense";
48
+ case W:
49
+ return "SuspenseList";
50
+ case c:
51
+ return "Activity";
52
+ }
53
+ if (typeof e == "object")
54
+ switch (typeof e.tag == "number" && console.error(
55
+ "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
56
+ ), e.$$typeof) {
57
+ case M:
58
+ return "Portal";
59
+ case L:
60
+ return e.displayName || "Context";
61
+ case D:
62
+ return (e._context.displayName || "Context") + ".Consumer";
63
+ case O:
64
+ var t = e.render;
65
+ return e = e.displayName, e || (e = t.displayName || t.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
66
+ case q:
67
+ return t = e.displayName || null, t !== null ? t : r(e.type) || "Memo";
68
+ case N:
69
+ t = e._payload, e = e._init;
70
+ try {
71
+ return r(e(t));
72
+ } catch {
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ function g(e) {
78
+ return "" + e;
79
+ }
80
+ function j(e) {
81
+ try {
82
+ g(e);
83
+ var t = !1;
84
+ } catch {
85
+ t = !0;
86
+ }
87
+ if (t) {
88
+ t = console;
89
+ var o = t.error, a = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
90
+ return o.call(
91
+ t,
92
+ "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
93
+ a
94
+ ), g(e);
95
+ }
96
+ }
97
+ function _(e) {
98
+ if (e === w) return "<>";
99
+ if (typeof e == "object" && e !== null && e.$$typeof === N)
100
+ return "<...>";
101
+ try {
102
+ var t = r(e);
103
+ return t ? "<" + t + ">" : "<...>";
104
+ } catch {
105
+ return "<...>";
106
+ }
107
+ }
108
+ function p() {
109
+ var e = f.A;
110
+ return e === null ? null : e.getOwner();
111
+ }
112
+ function b() {
113
+ return Error("react-stack-top-frame");
114
+ }
115
+ function y(e) {
116
+ if (n.call(e, "key")) {
117
+ var t = Object.getOwnPropertyDescriptor(e, "key").get;
118
+ if (t && t.isReactWarning) return !1;
119
+ }
120
+ return e.key !== void 0;
121
+ }
122
+ function T(e, t) {
123
+ function o() {
124
+ F || (F = !0, console.error(
125
+ "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
126
+ t
127
+ ));
128
+ }
129
+ o.isReactWarning = !0, Object.defineProperty(e, "key", {
130
+ get: o,
131
+ configurable: !0
132
+ });
133
+ }
134
+ function S() {
135
+ var e = r(this.type);
136
+ return X[e] || (X[e] = !0, console.error(
137
+ "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
138
+ )), e = this.props.ref, e !== void 0 ? e : null;
139
+ }
140
+ function P(e, t, o, a, Y, V) {
141
+ var i = o.ref;
142
+ return e = {
143
+ $$typeof: d,
144
+ type: e,
145
+ key: t,
146
+ props: o,
147
+ _owner: a
148
+ }, (i !== void 0 ? i : null) !== null ? Object.defineProperty(e, "ref", {
149
+ enumerable: !1,
150
+ get: S
151
+ }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
152
+ configurable: !1,
153
+ enumerable: !1,
154
+ writable: !0,
155
+ value: 0
156
+ }), Object.defineProperty(e, "_debugInfo", {
157
+ configurable: !1,
158
+ enumerable: !1,
159
+ writable: !0,
160
+ value: null
161
+ }), Object.defineProperty(e, "_debugStack", {
162
+ configurable: !1,
163
+ enumerable: !1,
164
+ writable: !0,
165
+ value: Y
166
+ }), Object.defineProperty(e, "_debugTask", {
167
+ configurable: !1,
168
+ enumerable: !1,
169
+ writable: !0,
170
+ value: V
171
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
172
+ }
173
+ function A(e, t, o, a, Y, V) {
174
+ var i = t.children;
175
+ if (i !== void 0)
176
+ if (a)
177
+ if (u(i)) {
178
+ for (a = 0; a < i.length; a++)
179
+ m(i[a]);
180
+ Object.freeze && Object.freeze(i);
181
+ } else
182
+ console.error(
183
+ "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
184
+ );
185
+ else m(i);
186
+ if (n.call(t, "key")) {
187
+ i = r(e);
188
+ var x = Object.keys(t).filter(function(ie) {
189
+ return ie !== "key";
190
+ });
191
+ a = 0 < x.length ? "{key: someKey, " + x.join(": ..., ") + ": ...}" : "{key: someKey}", H[i + a] || (x = 0 < x.length ? "{" + x.join(": ..., ") + ": ...}" : "{}", console.error(
192
+ `A props object containing a "key" prop is being spread into JSX:
193
+ let props = %s;
194
+ <%s {...props} />
195
+ React keys must be passed directly to JSX without using spread:
196
+ let props = %s;
197
+ <%s key={someKey} {...props} />`,
198
+ a,
199
+ i,
200
+ x,
201
+ i
202
+ ), H[i + a] = !0);
203
+ }
204
+ if (i = null, o !== void 0 && (j(o), i = "" + o), y(t) && (j(t.key), i = "" + t.key), "key" in t) {
205
+ o = {};
206
+ for (var G in t)
207
+ G !== "key" && (o[G] = t[G]);
208
+ } else o = t;
209
+ return i && T(
210
+ o,
211
+ typeof e == "function" ? e.displayName || e.name || "Unknown" : e
212
+ ), P(
213
+ e,
214
+ i,
215
+ o,
216
+ p(),
217
+ Y,
218
+ V
219
+ );
220
+ }
221
+ function m(e) {
222
+ k(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === N && (e._payload.status === "fulfilled" ? k(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
223
+ }
224
+ function k(e) {
225
+ return typeof e == "object" && e !== null && e.$$typeof === d;
226
+ }
227
+ var s = le, d = /* @__PURE__ */ Symbol.for("react.transitional.element"), M = /* @__PURE__ */ Symbol.for("react.portal"), w = /* @__PURE__ */ Symbol.for("react.fragment"), $ = /* @__PURE__ */ Symbol.for("react.strict_mode"), R = /* @__PURE__ */ Symbol.for("react.profiler"), D = /* @__PURE__ */ Symbol.for("react.consumer"), L = /* @__PURE__ */ Symbol.for("react.context"), O = /* @__PURE__ */ Symbol.for("react.forward_ref"), U = /* @__PURE__ */ Symbol.for("react.suspense"), W = /* @__PURE__ */ Symbol.for("react.suspense_list"), q = /* @__PURE__ */ Symbol.for("react.memo"), N = /* @__PURE__ */ Symbol.for("react.lazy"), c = /* @__PURE__ */ Symbol.for("react.activity"), v = /* @__PURE__ */ Symbol.for("react.client.reference"), f = s.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, n = Object.prototype.hasOwnProperty, u = Array.isArray, E = console.createTask ? console.createTask : function() {
228
+ return null;
229
+ };
230
+ s = {
231
+ react_stack_bottom_frame: function(e) {
232
+ return e();
233
+ }
234
+ };
235
+ var F, X = {}, B = s.react_stack_bottom_frame.bind(
236
+ s,
237
+ b
238
+ )(), K = E(_(b)), H = {};
239
+ C.Fragment = w, C.jsx = function(e, t, o) {
240
+ var a = 1e4 > f.recentlyCreatedOwnerStacks++;
241
+ return A(
242
+ e,
243
+ t,
244
+ o,
245
+ !1,
246
+ a ? Error("react-stack-top-frame") : B,
247
+ a ? E(_(e)) : K
248
+ );
249
+ }, C.jsxs = function(e, t, o) {
250
+ var a = 1e4 > f.recentlyCreatedOwnerStacks++;
251
+ return A(
252
+ e,
253
+ t,
254
+ o,
255
+ !0,
256
+ a ? Error("react-stack-top-frame") : B,
257
+ a ? E(_(e)) : K
258
+ );
259
+ };
260
+ })()), C;
261
+ }
262
+ var te;
263
+ function je() {
264
+ return te || (te = 1, process.env.NODE_ENV === "production" ? I.exports = me() : I.exports = Ee()), I.exports;
265
+ }
266
+ var l = je();
267
+ function _e(r) {
268
+ return r.data ? r.data : r.staticData ? {
269
+ provider: "value",
270
+ items: r.staticData
271
+ } : r.objectName ? {
272
+ provider: "object",
273
+ object: r.objectName
274
+ } : null;
275
+ }
276
+ function ne(r) {
277
+ if (!(!r || r.length === 0))
278
+ return typeof r[0] == "object" && r[0] !== null, r;
279
+ }
280
+ const ye = ({
281
+ schema: r,
282
+ dataSource: g,
283
+ onEdit: j,
284
+ onDelete: _,
285
+ onRowSelect: p
286
+ }) => {
287
+ const [b, y] = z([]), [T, S] = z(!0), [P, A] = z(null), [m, k] = z(null), s = _e(r), d = s?.provider === "value";
288
+ J(() => {
289
+ d && s?.provider === "value" && (y(s.items), S(!1));
290
+ }, [d, s]), J(() => {
291
+ const c = async () => {
292
+ try {
293
+ if (!g)
294
+ throw new Error("DataSource required");
295
+ const f = s?.provider === "object" ? s.object : r.objectName;
296
+ if (!f)
297
+ throw new Error("Object name required for object provider");
298
+ const n = await g.getObjectSchema(f);
299
+ k(n);
300
+ } catch (f) {
301
+ A(f);
302
+ }
303
+ }, v = ne(r.columns) || r.fields;
304
+ d && v ? k({ name: r.objectName, fields: {} }) : r.objectName && !d && g && c();
305
+ }, [r.objectName, r.columns, r.fields, g, d, s]);
306
+ const M = Z(() => {
307
+ const c = ne(r.columns);
308
+ if (c)
309
+ return c.length > 0 && typeof c[0] == "object" && c[0] !== null ? c.filter((n) => n?.field && typeof n.field == "string").map((n) => ({
310
+ header: n.label || n.field.charAt(0).toUpperCase() + n.field.slice(1).replace(/_/g, " "),
311
+ accessorKey: n.field,
312
+ ...n.width && { width: n.width },
313
+ ...n.align && { align: n.align },
314
+ sortable: n.sortable !== !1
315
+ })) : c.filter((n) => typeof n == "string" && n.trim().length > 0).map((n) => ({
316
+ header: n.charAt(0).toUpperCase() + n.slice(1).replace(/_/g, " "),
317
+ accessorKey: n
318
+ }));
319
+ if (d) {
320
+ const n = s?.provider === "value" ? s.items : [];
321
+ if (n.length > 0)
322
+ return (r.fields || Object.keys(n[0])).map((E) => ({
323
+ header: E.charAt(0).toUpperCase() + E.slice(1).replace(/_/g, " "),
324
+ accessorKey: E
325
+ }));
326
+ }
327
+ if (!m) return [];
328
+ const v = [];
329
+ return (r.fields || Object.keys(m.fields || {})).forEach((n) => {
330
+ const u = m.fields?.[n];
331
+ if (!u || u.permissions && u.permissions.read === !1) return;
332
+ const E = ue(u.type);
333
+ v.push({
334
+ header: u.label || n,
335
+ accessorKey: n,
336
+ cell: (F) => /* @__PURE__ */ l.jsx(E, { value: F, field: u }),
337
+ sortable: u.sortable !== !1
338
+ });
339
+ }), v;
340
+ }, [m, r.fields, r.columns, s, d]), w = Z(async () => {
341
+ if (!(d || !g)) {
342
+ S(!0);
343
+ try {
344
+ const c = s?.provider === "object" ? s.object : r.objectName;
345
+ if (!c)
346
+ throw new Error("Object name required for data fetching");
347
+ const f = {
348
+ $select: (() => {
349
+ if (r.fields) return r.fields;
350
+ if (r.columns && Array.isArray(r.columns))
351
+ return r.columns.map((u) => typeof u == "string" ? u : u.field);
352
+ })(),
353
+ $top: r.pagination?.pageSize || r.pageSize || 50
354
+ };
355
+ r.filter && Array.isArray(r.filter) ? f.$filter = r.filter : "defaultFilters" in r && r.defaultFilters && (f.$filter = r.defaultFilters), r.sort ? typeof r.sort == "string" ? f.$orderby = r.sort : Array.isArray(r.sort) && (f.$orderby = r.sort.map((u) => `${u.field} ${u.order}`).join(", ")) : "defaultSort" in r && r.defaultSort && (f.$orderby = `${r.defaultSort.field} ${r.defaultSort.order}`);
356
+ const n = await g.find(c, f);
357
+ y(n.data || []);
358
+ } catch (c) {
359
+ A(c);
360
+ } finally {
361
+ S(!1);
362
+ }
363
+ }
364
+ }, [r, g, d, s]);
365
+ if (J(() => {
366
+ (m || d) && w();
367
+ }, [m, d, w]), P)
368
+ return /* @__PURE__ */ l.jsxs("div", { className: "p-4 border border-red-300 bg-red-50 rounded-md", children: [
369
+ /* @__PURE__ */ l.jsx("h3", { className: "text-red-800 font-semibold", children: "Error loading grid" }),
370
+ /* @__PURE__ */ l.jsx("p", { className: "text-red-600 text-sm mt-1", children: P.message })
371
+ ] });
372
+ if (T && b.length === 0)
373
+ return /* @__PURE__ */ l.jsxs("div", { className: "p-8 text-center", children: [
374
+ /* @__PURE__ */ l.jsx("div", { className: "inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900" }),
375
+ /* @__PURE__ */ l.jsx("p", { className: "mt-2 text-sm text-gray-600", children: "Loading grid..." })
376
+ ] });
377
+ const $ = M(), R = "operations" in r ? r.operations : void 0, D = R && (R.update || R.delete), L = D ? [
378
+ ...$,
379
+ {
380
+ header: "Actions",
381
+ accessorKey: "_actions",
382
+ cell: (c, v) => /* @__PURE__ */ l.jsxs(ce, { children: [
383
+ /* @__PURE__ */ l.jsx(fe, { asChild: !0, children: /* @__PURE__ */ l.jsxs(de, { variant: "ghost", size: "icon", className: "h-8 w-8", children: [
384
+ /* @__PURE__ */ l.jsx(be, { className: "h-4 w-4" }),
385
+ /* @__PURE__ */ l.jsx("span", { className: "sr-only", children: "Open menu" })
386
+ ] }) }),
387
+ /* @__PURE__ */ l.jsxs(pe, { align: "end", children: [
388
+ R?.update && j && /* @__PURE__ */ l.jsxs(Q, { onClick: () => j(v), children: [
389
+ /* @__PURE__ */ l.jsx(ge, { className: "mr-2 h-4 w-4" }),
390
+ "Edit"
391
+ ] }),
392
+ R?.delete && _ && /* @__PURE__ */ l.jsxs(Q, { variant: "destructive", onClick: () => _(v), children: [
393
+ /* @__PURE__ */ l.jsx(ve, { className: "mr-2 h-4 w-4" }),
394
+ "Delete"
395
+ ] })
396
+ ] })
397
+ ] }),
398
+ sortable: !1
399
+ }
400
+ ] : $;
401
+ let O = !1;
402
+ r.selection?.type ? O = r.selection.type === "none" ? !1 : r.selection.type : r.selectable !== void 0 && (O = r.selectable);
403
+ const U = r.pagination !== void 0 ? !0 : r.showPagination !== void 0 ? r.showPagination : !0, W = r.pagination?.pageSize || r.pageSize || 10, q = r.searchableFields !== void 0 ? r.searchableFields.length > 0 : r.showSearch !== void 0 ? r.showSearch : !0, N = {
404
+ type: "data-table",
405
+ caption: r.label || r.title,
406
+ columns: L,
407
+ data: b,
408
+ pagination: U,
409
+ pageSize: W,
410
+ searchable: q,
411
+ selectable: O,
412
+ sortable: !0,
413
+ exportable: R?.export,
414
+ rowActions: D,
415
+ resizableColumns: r.resizable ?? r.resizableColumns ?? !0,
416
+ className: r.className,
417
+ onSelectionChange: p
418
+ };
419
+ return /* @__PURE__ */ l.jsx(se, { schema: N });
420
+ }, ae = ({ schema: r }) => /* @__PURE__ */ l.jsx(ye, { schema: r, dataSource: null });
421
+ oe.register("object-grid", ae);
422
+ oe.register("grid", ae);
423
+ export {
424
+ ye as ObjectGrid
425
+ };
@@ -0,0 +1,6 @@
1
+ (function(v,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("react"),require("@object-ui/core"),require("@object-ui/react"),require("@object-ui/fields"),require("@object-ui/components"),require("lucide-react")):typeof define=="function"&&define.amd?define(["exports","react","@object-ui/core","@object-ui/react","@object-ui/fields","@object-ui/components","lucide-react"],d):(v=typeof globalThis<"u"?globalThis:v||self,d(v.ObjectUIPluginGrid={},v.React,v.core,v.react,v.fields,v.components,v.lucideReact))})(this,(function(v,d,K,se,le,x,W){"use strict";var Y={exports:{}},A={};var H;function ce(){if(H)return A;H=1;var r=Symbol.for("react.transitional.element"),g=Symbol.for("react.fragment");function y(R,p,j){var T=null;if(j!==void 0&&(T=""+j),p.key!==void 0&&(T=""+p.key),"key"in p){j={};for(var S in p)S!=="key"&&(j[S]=p[S])}else j=p;return p=j.ref,{$$typeof:r,type:R,key:T,ref:p!==void 0?p:null,props:j}}return A.Fragment=g,A.jsx=y,A.jsxs=y,A}var h={};var Z;function ue(){return Z||(Z=1,process.env.NODE_ENV!=="production"&&(function(){function r(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===E?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case O:return"Fragment";case w:return"Profiler";case M:return"StrictMode";case J:return"Suspense";case $:return"SuspenseList";case u:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case G:return"Portal";case V:return e.displayName||"Context";case z:return(e._context.displayName||"Context")+".Consumer";case D:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case q:return t=e.displayName||null,t!==null?t:r(e.type)||"Memo";case F:t=e._payload,e=e._init;try{return r(e(t))}catch{}}return null}function g(e){return""+e}function y(e){try{g(e);var t=!1}catch{t=!0}if(t){t=console;var o=t.error,i=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return o.call(t,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",i),g(e)}}function R(e){if(e===O)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===F)return"<...>";try{var t=r(e);return t?"<"+t+">":"<...>"}catch{return"<...>"}}function p(){var e=f.A;return e===null?null:e.getOwner()}function j(){return Error("react-stack-top-frame")}function T(e){if(n.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function S(e,t){function o(){U||(U=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}o.isReactWarning=!0,Object.defineProperty(e,"key",{get:o,configurable:!0})}function C(){var e=r(this.type);return ne[e]||(ne[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function I(e,t,o,i,L,X){var s=o.ref;return e={$$typeof:b,type:e,key:t,props:o,_owner:i},(s!==void 0?s:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:C}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:L}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:X}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function N(e,t,o,i,L,X){var s=t.children;if(s!==void 0)if(i)if(c(s)){for(i=0;i<s.length;i++)_(s[i]);Object.freeze&&Object.freeze(s)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else _(s);if(n.call(t,"key")){s=r(e);var k=Object.keys(t).filter(function(be){return be!=="key"});i=0<k.length?"{key: someKey, "+k.join(": ..., ")+": ...}":"{key: someKey}",ie[s+i]||(k=0<k.length?"{"+k.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
2
+ let props = %s;
3
+ <%s {...props} />
4
+ React keys must be passed directly to JSX without using spread:
5
+ let props = %s;
6
+ <%s key={someKey} {...props} />`,i,s,k,s),ie[s+i]=!0)}if(s=null,o!==void 0&&(y(o),s=""+o),T(t)&&(y(t.key),s=""+t.key),"key"in t){o={};for(var B in t)B!=="key"&&(o[B]=t[B])}else o=t;return s&&S(o,typeof e=="function"?e.displayName||e.name||"Unknown":e),I(e,s,o,p(),L,X)}function _(e){P(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===F&&(e._payload.status==="fulfilled"?P(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function P(e){return typeof e=="object"&&e!==null&&e.$$typeof===b}var l=d,b=Symbol.for("react.transitional.element"),G=Symbol.for("react.portal"),O=Symbol.for("react.fragment"),M=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),z=Symbol.for("react.consumer"),V=Symbol.for("react.context"),D=Symbol.for("react.forward_ref"),J=Symbol.for("react.suspense"),$=Symbol.for("react.suspense_list"),q=Symbol.for("react.memo"),F=Symbol.for("react.lazy"),u=Symbol.for("react.activity"),E=Symbol.for("react.client.reference"),f=l.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,n=Object.prototype.hasOwnProperty,c=Array.isArray,m=console.createTask?console.createTask:function(){return null};l={react_stack_bottom_frame:function(e){return e()}};var U,ne={},oe=l.react_stack_bottom_frame.bind(l,j)(),ae=m(R(j)),ie={};h.Fragment=O,h.jsx=function(e,t,o){var i=1e4>f.recentlyCreatedOwnerStacks++;return N(e,t,o,!1,i?Error("react-stack-top-frame"):oe,i?m(R(e)):ae)},h.jsxs=function(e,t,o){var i=1e4>f.recentlyCreatedOwnerStacks++;return N(e,t,o,!0,i?Error("react-stack-top-frame"):oe,i?m(R(e)):ae)}})()),h}var Q;function fe(){return Q||(Q=1,process.env.NODE_ENV==="production"?Y.exports=ce():Y.exports=ue()),Y.exports}var a=fe();function de(r){return r.data?r.data:r.staticData?{provider:"value",items:r.staticData}:r.objectName?{provider:"object",object:r.objectName}:null}function ee(r){if(!(!r||r.length===0))return typeof r[0]=="object"&&r[0]!==null,r}const re=({schema:r,dataSource:g,onEdit:y,onDelete:R,onRowSelect:p})=>{const[j,T]=d.useState([]),[S,C]=d.useState(!0),[I,N]=d.useState(null),[_,P]=d.useState(null),l=de(r),b=l?.provider==="value";d.useEffect(()=>{b&&l?.provider==="value"&&(T(l.items),C(!1))},[b,l]),d.useEffect(()=>{const u=async()=>{try{if(!g)throw new Error("DataSource required");const f=l?.provider==="object"?l.object:r.objectName;if(!f)throw new Error("Object name required for object provider");const n=await g.getObjectSchema(f);P(n)}catch(f){N(f)}},E=ee(r.columns)||r.fields;b&&E?P({name:r.objectName,fields:{}}):r.objectName&&!b&&g&&u()},[r.objectName,r.columns,r.fields,g,b,l]);const G=d.useCallback(()=>{const u=ee(r.columns);if(u)return u.length>0&&typeof u[0]=="object"&&u[0]!==null?u.filter(n=>n?.field&&typeof n.field=="string").map(n=>({header:n.label||n.field.charAt(0).toUpperCase()+n.field.slice(1).replace(/_/g," "),accessorKey:n.field,...n.width&&{width:n.width},...n.align&&{align:n.align},sortable:n.sortable!==!1})):u.filter(n=>typeof n=="string"&&n.trim().length>0).map(n=>({header:n.charAt(0).toUpperCase()+n.slice(1).replace(/_/g," "),accessorKey:n}));if(b){const n=l?.provider==="value"?l.items:[];if(n.length>0)return(r.fields||Object.keys(n[0])).map(m=>({header:m.charAt(0).toUpperCase()+m.slice(1).replace(/_/g," "),accessorKey:m}))}if(!_)return[];const E=[];return(r.fields||Object.keys(_.fields||{})).forEach(n=>{const c=_.fields?.[n];if(!c||c.permissions&&c.permissions.read===!1)return;const m=le.getCellRenderer(c.type);E.push({header:c.label||n,accessorKey:n,cell:U=>a.jsx(m,{value:U,field:c}),sortable:c.sortable!==!1})}),E},[_,r.fields,r.columns,l,b]),O=d.useCallback(async()=>{if(!(b||!g)){C(!0);try{const u=l?.provider==="object"?l.object:r.objectName;if(!u)throw new Error("Object name required for data fetching");const f={$select:(()=>{if(r.fields)return r.fields;if(r.columns&&Array.isArray(r.columns))return r.columns.map(c=>typeof c=="string"?c:c.field)})(),$top:r.pagination?.pageSize||r.pageSize||50};r.filter&&Array.isArray(r.filter)?f.$filter=r.filter:"defaultFilters"in r&&r.defaultFilters&&(f.$filter=r.defaultFilters),r.sort?typeof r.sort=="string"?f.$orderby=r.sort:Array.isArray(r.sort)&&(f.$orderby=r.sort.map(c=>`${c.field} ${c.order}`).join(", ")):"defaultSort"in r&&r.defaultSort&&(f.$orderby=`${r.defaultSort.field} ${r.defaultSort.order}`);const n=await g.find(u,f);T(n.data||[])}catch(u){N(u)}finally{C(!1)}}},[r,g,b,l]);if(d.useEffect(()=>{(_||b)&&O()},[_,b,O]),I)return a.jsxs("div",{className:"p-4 border border-red-300 bg-red-50 rounded-md",children:[a.jsx("h3",{className:"text-red-800 font-semibold",children:"Error loading grid"}),a.jsx("p",{className:"text-red-600 text-sm mt-1",children:I.message})]});if(S&&j.length===0)return a.jsxs("div",{className:"p-8 text-center",children:[a.jsx("div",{className:"inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"}),a.jsx("p",{className:"mt-2 text-sm text-gray-600",children:"Loading grid..."})]});const M=G(),w="operations"in r?r.operations:void 0,z=w&&(w.update||w.delete),V=z?[...M,{header:"Actions",accessorKey:"_actions",cell:(u,E)=>a.jsxs(x.DropdownMenu,{children:[a.jsx(x.DropdownMenuTrigger,{asChild:!0,children:a.jsxs(x.Button,{variant:"ghost",size:"icon",className:"h-8 w-8",children:[a.jsx(W.MoreVertical,{className:"h-4 w-4"}),a.jsx("span",{className:"sr-only",children:"Open menu"})]})}),a.jsxs(x.DropdownMenuContent,{align:"end",children:[w?.update&&y&&a.jsxs(x.DropdownMenuItem,{onClick:()=>y(E),children:[a.jsx(W.Edit,{className:"mr-2 h-4 w-4"}),"Edit"]}),w?.delete&&R&&a.jsxs(x.DropdownMenuItem,{variant:"destructive",onClick:()=>R(E),children:[a.jsx(W.Trash2,{className:"mr-2 h-4 w-4"}),"Delete"]})]})]}),sortable:!1}]:M;let D=!1;r.selection?.type?D=r.selection.type==="none"?!1:r.selection.type:r.selectable!==void 0&&(D=r.selectable);const J=r.pagination!==void 0?!0:r.showPagination!==void 0?r.showPagination:!0,$=r.pagination?.pageSize||r.pageSize||10,q=r.searchableFields!==void 0?r.searchableFields.length>0:r.showSearch!==void 0?r.showSearch:!0,F={type:"data-table",caption:r.label||r.title,columns:V,data:j,pagination:J,pageSize:$,searchable:q,selectable:D,sortable:!0,exportable:w?.export,rowActions:z,resizableColumns:r.resizable??r.resizableColumns??!0,className:r.className,onSelectionChange:p};return a.jsx(se.SchemaRenderer,{schema:F})},te=({schema:r})=>a.jsx(re,{schema:r,dataSource:null});K.ComponentRegistry.register("object-grid",te),K.ComponentRegistry.register("grid",te),v.ObjectGrid=re,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,14 @@
1
+ import { default as React } from 'react';
2
+ import { ObjectGridSchema, DataSource } from '../../types/src';
3
+ export interface ObjectGridProps {
4
+ schema: ObjectGridSchema;
5
+ dataSource?: DataSource;
6
+ className?: string;
7
+ onRowClick?: (record: any) => void;
8
+ onEdit?: (record: any) => void;
9
+ onDelete?: (record: any) => void;
10
+ onBulkDelete?: (records: any[]) => void;
11
+ onCellChange?: (rowIndex: number, columnKey: string, newValue: any) => void;
12
+ onRowSelect?: (selectedRows: any[]) => void;
13
+ }
14
+ export declare const ObjectGrid: React.FC<ObjectGridProps>;
@@ -0,0 +1,3 @@
1
+ import { ObjectGrid } from './ObjectGrid';
2
+ export { ObjectGrid };
3
+ export type { ObjectGridProps } from './ObjectGrid';
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@object-ui/plugin-grid",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Grid plugin for Object UI",
7
+ "main": "dist/index.umd.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.umd.cjs"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "lucide-react": "^0.563.0",
19
+ "@object-ui/components": "0.3.0",
20
+ "@object-ui/core": "0.3.0",
21
+ "@object-ui/fields": "0.3.0",
22
+ "@object-ui/react": "0.3.0",
23
+ "@object-ui/types": "0.3.0"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^18.0.0 || ^19.0.0",
27
+ "react-dom": "^18.0.0 || ^19.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@vitejs/plugin-react": "^4.2.1",
31
+ "typescript": "^5.9.3",
32
+ "vite": "^7.3.1",
33
+ "vite-plugin-dts": "^4.5.4"
34
+ },
35
+ "scripts": {
36
+ "build": "vite build",
37
+ "test": "vitest run",
38
+ "lint": "eslint ."
39
+ }
40
+ }
@@ -0,0 +1,377 @@
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
+ * ObjectGrid Component
11
+ *
12
+ * A specialized grid component built on top of data-table.
13
+ * Auto-generates columns from ObjectQL schema with type-aware rendering.
14
+ * Implements the grid view type from @objectstack/spec view.zod ListView schema.
15
+ *
16
+ * Features:
17
+ * - Traditional table/grid with CRUD operations
18
+ * - Search, filters, pagination
19
+ * - Column resizing, sorting
20
+ * - Row selection
21
+ * - Inline editing support
22
+ */
23
+
24
+ import React, { useEffect, useState, useCallback } from 'react';
25
+ import type { ObjectGridSchema, DataSource, ListColumn, ViewData } from '@object-ui/types';
26
+ import { SchemaRenderer } from '@object-ui/react';
27
+ import { getCellRenderer } from '@object-ui/fields';
28
+ import { Button } from '@object-ui/components';
29
+ import {
30
+ DropdownMenu,
31
+ DropdownMenuContent,
32
+ DropdownMenuItem,
33
+ DropdownMenuTrigger,
34
+ } from '@object-ui/components';
35
+ import { Edit, Trash2, MoreVertical } from 'lucide-react';
36
+
37
+ export interface ObjectGridProps {
38
+ schema: ObjectGridSchema;
39
+ dataSource?: DataSource;
40
+ className?: string;
41
+ onRowClick?: (record: any) => void;
42
+ onEdit?: (record: any) => void;
43
+ onDelete?: (record: any) => void;
44
+ onBulkDelete?: (records: any[]) => void;
45
+ onCellChange?: (rowIndex: number, columnKey: string, newValue: any) => void;
46
+ onRowSelect?: (selectedRows: any[]) => void;
47
+ }
48
+
49
+ /**
50
+ * Helper to get data configuration from schema
51
+ * Handles both new ViewData format and legacy inline data
52
+ */
53
+ function getDataConfig(schema: ObjectGridSchema): ViewData | null {
54
+ // New format: explicit data configuration
55
+ if (schema.data) {
56
+ return schema.data;
57
+ }
58
+
59
+ // Legacy format: staticData field
60
+ if (schema.staticData) {
61
+ return {
62
+ provider: 'value',
63
+ items: schema.staticData,
64
+ };
65
+ }
66
+
67
+ // Default: use object provider with objectName
68
+ if (schema.objectName) {
69
+ return {
70
+ provider: 'object',
71
+ object: schema.objectName,
72
+ };
73
+ }
74
+
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Helper to normalize columns configuration
80
+ * Handles both string[] and ListColumn[] formats
81
+ */
82
+ function normalizeColumns(
83
+ columns: string[] | ListColumn[] | undefined
84
+ ): ListColumn[] | string[] | undefined {
85
+ if (!columns || columns.length === 0) return undefined;
86
+
87
+ // Already in ListColumn format - check for object type with optional chaining
88
+ if (typeof columns[0] === 'object' && columns[0] !== null) {
89
+ return columns as ListColumn[];
90
+ }
91
+
92
+ // String array format
93
+ return columns as string[];
94
+ }
95
+
96
+ export const ObjectGrid: React.FC<ObjectGridProps> = ({
97
+ schema,
98
+ dataSource,
99
+ onEdit,
100
+ onDelete,
101
+ onRowSelect,
102
+ }) => {
103
+ const [data, setData] = useState<any[]>([]);
104
+ const [loading, setLoading] = useState(true);
105
+ const [error, setError] = useState<Error | null>(null);
106
+ const [objectSchema, setObjectSchema] = useState<any>(null);
107
+
108
+ // Get data configuration (supports both new and legacy formats)
109
+ const dataConfig = getDataConfig(schema);
110
+ const hasInlineData = dataConfig?.provider === 'value';
111
+
112
+ useEffect(() => {
113
+ if (hasInlineData && dataConfig?.provider === 'value') {
114
+ setData(dataConfig.items as any[]);
115
+ setLoading(false);
116
+ }
117
+ }, [hasInlineData, dataConfig]);
118
+
119
+ useEffect(() => {
120
+ const fetchObjectSchema = async () => {
121
+ try {
122
+ if (!dataSource) {
123
+ throw new Error('DataSource required');
124
+ }
125
+
126
+ // For object provider, get the object name
127
+ const objectName = dataConfig?.provider === 'object'
128
+ ? dataConfig.object
129
+ : schema.objectName;
130
+
131
+ if (!objectName) {
132
+ throw new Error('Object name required for object provider');
133
+ }
134
+
135
+ const schemaData = await dataSource.getObjectSchema(objectName);
136
+ setObjectSchema(schemaData);
137
+ } catch (err) {
138
+ setError(err as Error);
139
+ }
140
+ };
141
+
142
+ // Normalize columns (support both legacy 'fields' and new 'columns')
143
+ const cols = normalizeColumns(schema.columns) || schema.fields;
144
+
145
+ if (hasInlineData && cols) {
146
+ setObjectSchema({ name: schema.objectName, fields: {} });
147
+ } else if (schema.objectName && !hasInlineData && dataSource) {
148
+ fetchObjectSchema();
149
+ }
150
+ }, [schema.objectName, schema.columns, schema.fields, dataSource, hasInlineData, dataConfig]);
151
+
152
+ const generateColumns = useCallback(() => {
153
+ // Use normalized columns (support both new and legacy)
154
+ const cols = normalizeColumns(schema.columns);
155
+
156
+ if (cols) {
157
+ // If columns are already ListColumn objects, convert them to data-table format
158
+ if (cols.length > 0 && typeof cols[0] === 'object' && cols[0] !== null) {
159
+ return (cols as ListColumn[])
160
+ .filter((col) => col?.field && typeof col.field === 'string') // Filter out invalid column objects
161
+ .map((col) => ({
162
+ header: col.label || col.field.charAt(0).toUpperCase() + col.field.slice(1).replace(/_/g, ' '),
163
+ accessorKey: col.field,
164
+ ...(col.width && { width: col.width }),
165
+ ...(col.align && { align: col.align }),
166
+ sortable: col.sortable !== false,
167
+ }));
168
+ }
169
+
170
+ // String array format - filter out invalid entries
171
+ return (cols as string[])
172
+ .filter((fieldName) => typeof fieldName === 'string' && fieldName.trim().length > 0)
173
+ .map((fieldName) => ({
174
+ header: fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/_/g, ' '),
175
+ accessorKey: fieldName,
176
+ }));
177
+ }
178
+
179
+ // Legacy support: use 'fields' if columns not provided
180
+ if (hasInlineData) {
181
+ const inlineData = dataConfig?.provider === 'value' ? dataConfig.items as any[] : [];
182
+ if (inlineData.length > 0) {
183
+ const fieldsToShow = schema.fields || Object.keys(inlineData[0]);
184
+ return fieldsToShow.map((fieldName) => ({
185
+ header: fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/_/g, ' '),
186
+ accessorKey: fieldName,
187
+ }));
188
+ }
189
+ }
190
+
191
+ if (!objectSchema) return [];
192
+
193
+ const generatedColumns: any[] = [];
194
+ const fieldsToShow = schema.fields || Object.keys(objectSchema.fields || {});
195
+
196
+ fieldsToShow.forEach((fieldName) => {
197
+ const field = objectSchema.fields?.[fieldName];
198
+ if (!field) return;
199
+
200
+ if (field.permissions && field.permissions.read === false) return;
201
+
202
+ const CellRenderer = getCellRenderer(field.type);
203
+ generatedColumns.push({
204
+ header: field.label || fieldName,
205
+ accessorKey: fieldName,
206
+ cell: (value: any) => <CellRenderer value={value} field={field} />,
207
+ sortable: field.sortable !== false,
208
+ });
209
+ });
210
+
211
+ return generatedColumns;
212
+ }, [objectSchema, schema.fields, schema.columns, dataConfig, hasInlineData]);
213
+
214
+ const fetchData = useCallback(async () => {
215
+ if (hasInlineData || !dataSource) return;
216
+
217
+ setLoading(true);
218
+ try {
219
+ // Get object name from data config or schema
220
+ const objectName = dataConfig?.provider === 'object'
221
+ ? dataConfig.object
222
+ : schema.objectName;
223
+
224
+ if (!objectName) {
225
+ throw new Error('Object name required for data fetching');
226
+ }
227
+
228
+ // Helper to get select fields
229
+ const getSelectFields = () => {
230
+ if (schema.fields) return schema.fields;
231
+ if (schema.columns && Array.isArray(schema.columns)) {
232
+ return schema.columns.map(c => typeof c === 'string' ? c : c.field);
233
+ }
234
+ return undefined;
235
+ };
236
+
237
+ const params: any = {
238
+ $select: getSelectFields(),
239
+ $top: schema.pagination?.pageSize || schema.pageSize || 50,
240
+ };
241
+
242
+ // Support new filter format
243
+ if (schema.filter && Array.isArray(schema.filter)) {
244
+ params.$filter = schema.filter;
245
+ } else if ('defaultFilters' in schema && schema.defaultFilters) {
246
+ // Legacy support
247
+ params.$filter = schema.defaultFilters;
248
+ }
249
+
250
+ // Support new sort format
251
+ if (schema.sort) {
252
+ if (typeof schema.sort === 'string') {
253
+ // Legacy string format
254
+ params.$orderby = schema.sort;
255
+ } else if (Array.isArray(schema.sort)) {
256
+ // New array format
257
+ params.$orderby = schema.sort
258
+ .map(s => `${s.field} ${s.order}`)
259
+ .join(', ');
260
+ }
261
+ } else if ('defaultSort' in schema && schema.defaultSort) {
262
+ // Legacy support
263
+ params.$orderby = `${schema.defaultSort.field} ${schema.defaultSort.order}`;
264
+ }
265
+
266
+ const result = await dataSource.find(objectName, params);
267
+ setData(result.data || []);
268
+ } catch (err) {
269
+ setError(err as Error);
270
+ } finally {
271
+ setLoading(false);
272
+ }
273
+ }, [schema, dataSource, hasInlineData, dataConfig]);
274
+
275
+ useEffect(() => {
276
+ if (objectSchema || hasInlineData) {
277
+ fetchData();
278
+ }
279
+ }, [objectSchema, hasInlineData, fetchData]);
280
+
281
+ if (error) {
282
+ return (
283
+ <div className="p-4 border border-red-300 bg-red-50 rounded-md">
284
+ <h3 className="text-red-800 font-semibold">Error loading grid</h3>
285
+ <p className="text-red-600 text-sm mt-1">{error.message}</p>
286
+ </div>
287
+ );
288
+ }
289
+
290
+ if (loading && data.length === 0) {
291
+ return (
292
+ <div className="p-8 text-center">
293
+ <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
294
+ <p className="mt-2 text-sm text-gray-600">Loading grid...</p>
295
+ </div>
296
+ );
297
+ }
298
+
299
+ const columns = generateColumns();
300
+ const operations = 'operations' in schema ? schema.operations : undefined;
301
+ const hasActions = operations && (operations.update || operations.delete);
302
+
303
+ const columnsWithActions = hasActions ? [
304
+ ...columns,
305
+ {
306
+ header: 'Actions',
307
+ accessorKey: '_actions',
308
+ cell: (_value: any, row: any) => (
309
+ <DropdownMenu>
310
+ <DropdownMenuTrigger asChild>
311
+ <Button variant="ghost" size="icon" className="h-8 w-8">
312
+ <MoreVertical className="h-4 w-4" />
313
+ <span className="sr-only">Open menu</span>
314
+ </Button>
315
+ </DropdownMenuTrigger>
316
+ <DropdownMenuContent align="end">
317
+ {operations?.update && onEdit && (
318
+ <DropdownMenuItem onClick={() => onEdit(row)}>
319
+ <Edit className="mr-2 h-4 w-4" />
320
+ Edit
321
+ </DropdownMenuItem>
322
+ )}
323
+ {operations?.delete && onDelete && (
324
+ <DropdownMenuItem variant="destructive" onClick={() => onDelete(row)}>
325
+ <Trash2 className="mr-2 h-4 w-4" />
326
+ Delete
327
+ </DropdownMenuItem>
328
+ )}
329
+ </DropdownMenuContent>
330
+ </DropdownMenu>
331
+ ),
332
+ sortable: false,
333
+ },
334
+ ] : columns;
335
+
336
+ // Determine selection mode (support both new and legacy formats)
337
+ let selectionMode: 'none' | 'single' | 'multiple' | boolean = false;
338
+ if (schema.selection?.type) {
339
+ selectionMode = schema.selection.type === 'none' ? false : schema.selection.type;
340
+ } else if (schema.selectable !== undefined) {
341
+ // Legacy support
342
+ selectionMode = schema.selectable;
343
+ }
344
+
345
+ // Determine pagination settings (support both new and legacy formats)
346
+ const paginationEnabled = schema.pagination !== undefined
347
+ ? true
348
+ : (schema.showPagination !== undefined ? schema.showPagination : true);
349
+
350
+ const pageSize = schema.pagination?.pageSize
351
+ || schema.pageSize
352
+ || 10;
353
+
354
+ // Determine search settings
355
+ const searchEnabled = schema.searchableFields !== undefined
356
+ ? schema.searchableFields.length > 0
357
+ : (schema.showSearch !== undefined ? schema.showSearch : true);
358
+
359
+ const dataTableSchema: any = {
360
+ type: 'data-table',
361
+ caption: schema.label || schema.title,
362
+ columns: columnsWithActions,
363
+ data,
364
+ pagination: paginationEnabled,
365
+ pageSize: pageSize,
366
+ searchable: searchEnabled,
367
+ selectable: selectionMode,
368
+ sortable: true,
369
+ exportable: operations?.export,
370
+ rowActions: hasActions,
371
+ resizableColumns: schema.resizable ?? schema.resizableColumns ?? true,
372
+ className: schema.className,
373
+ onSelectionChange: onRowSelect,
374
+ };
375
+
376
+ return <SchemaRenderer schema={dataTableSchema} />;
377
+ };
package/src/index.tsx ADDED
@@ -0,0 +1,22 @@
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 { ObjectGrid } from './ObjectGrid';
12
+
13
+ export { ObjectGrid };
14
+ export type { ObjectGridProps } from './ObjectGrid';
15
+
16
+ // Register object-grid component
17
+ const ObjectGridRenderer: React.FC<{ schema: any }> = ({ schema }) => {
18
+ return <ObjectGrid schema={schema} dataSource={null as any} />;
19
+ };
20
+
21
+ ComponentRegistry.register('object-grid', ObjectGridRenderer);
22
+ ComponentRegistry.register('grid', ObjectGridRenderer); // Alias
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "jsx": "react-jsx"
6
+ },
7
+ "include": ["src"]
8
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import dts from 'vite-plugin-dts';
4
+ import { resolve } from 'path';
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ dts({
10
+ insertTypesEntry: true,
11
+ include: ['src'],
12
+ }),
13
+ ],
14
+ build: {
15
+ lib: {
16
+ entry: resolve(__dirname, 'src/index.tsx'),
17
+ name: 'ObjectUIPluginGrid',
18
+ fileName: 'index',
19
+ },
20
+ rollupOptions: {
21
+ external: [
22
+ 'react',
23
+ 'react-dom',
24
+ '@object-ui/components',
25
+ '@object-ui/core',
26
+ '@object-ui/fields',
27
+ '@object-ui/react',
28
+ '@object-ui/types',
29
+ 'lucide-react'
30
+ ],
31
+ output: {
32
+ globals: {
33
+ react: 'React',
34
+ 'react-dom': 'ReactDOM',
35
+ },
36
+ },
37
+ },
38
+ },
39
+ });