@object-ui/plugin-tree 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # @object-ui/plugin-tree
2
+
3
+ ## 7.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4eb9cb6: feat(plugin-tree): add a `tree` / tree-grid object view type
8
+
9
+ Renders a self-referencing object as an indented, expand/collapse tree-grid —
10
+ the right view for arbitrary-depth hierarchies (business unit / org chart,
11
+ category trees, BOMs, nested comments) that fixed-depth grouping can't express.
12
+ New `@object-ui/plugin-tree` package (`object-tree`/`tree`), `tree` added to the
13
+ `ViewType` union, and dispatch wired through plugin-list `ListView` +
14
+ app-shell `ObjectView` (the console path).
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [5976ba3]
19
+ - Updated dependencies [a00e16d]
20
+ - Updated dependencies [eaccefd]
21
+ - Updated dependencies [f7f325d]
22
+ - Updated dependencies [c12986e]
23
+ - Updated dependencies [71d7ce0]
24
+ - Updated dependencies [053c948]
25
+ - Updated dependencies [ddbe4a2]
26
+ - Updated dependencies [2d47e94]
27
+ - Updated dependencies [9049bbe]
28
+ - Updated dependencies [6c0c92c]
29
+ - Updated dependencies [cb2fdb1]
30
+ - Updated dependencies [c3749eb]
31
+ - Updated dependencies [6cfa330]
32
+ - Updated dependencies [ad8ade6]
33
+ - Updated dependencies [d54346c]
34
+ - Updated dependencies [3870c20]
35
+ - Updated dependencies [2eb3096]
36
+ - Updated dependencies [b88c560]
37
+ - Updated dependencies [d16566f]
38
+ - Updated dependencies [90acb7f]
39
+ - Updated dependencies [7913390]
40
+ - Updated dependencies [1394e34]
41
+ - Updated dependencies [e95cc25]
42
+ - Updated dependencies [abe8ebc]
43
+ - Updated dependencies [300d755]
44
+ - Updated dependencies [bd8b054]
45
+ - Updated dependencies [4eb9cb6]
46
+ - Updated dependencies [7c239fd]
47
+ - Updated dependencies [858ad94]
48
+ - Updated dependencies [2270239]
49
+ - Updated dependencies [8d1195d]
50
+ - @object-ui/core@7.0.0
51
+ - @object-ui/components@7.0.0
52
+ - @object-ui/react@7.0.0
53
+ - @object-ui/types@7.0.0
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,46 @@
1
+ # @object-ui/plugin-tree
2
+
3
+ Tree / tree-grid view plugin for Object UI.
4
+
5
+ Renders a **self-referencing object** as an indented, expand/collapse tree-grid —
6
+ the right view for hierarchies of unbounded depth such as **business unit /
7
+ org chart**, category trees, menu trees, BOMs, or nested comments. (Grouping
8
+ handles *fixed-depth* hierarchies; a tree handles arbitrary depth.)
9
+
10
+ It registers two component types via the `ComponentRegistry`:
11
+
12
+ - `object-tree` — the object-bound renderer
13
+ - `tree` — the view-type alias used by `ObjectView` / `ViewSwitcher`
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ // As a view inside an ObjectView
19
+ {
20
+ type: 'object-view',
21
+ objectName: 'business_unit',
22
+ views: [
23
+ {
24
+ type: 'tree',
25
+ tree: {
26
+ parentField: 'parent', // single-parent pointer (auto-detected if omitted)
27
+ labelField: 'name', // indented first column
28
+ fields: ['name', 'manager'], // additional flat columns
29
+ defaultExpandedDepth: 1, // 0 = roots only; omit = expand all
30
+ },
31
+ },
32
+ ],
33
+ }
34
+ ```
35
+
36
+ ### Config
37
+
38
+ | Key | Default | Description |
39
+ | --- | --- | --- |
40
+ | `parentField` | auto-detected | Field holding the parent reference. When omitted, the renderer picks the object's `tree` field (or a lookup/master_detail that references the same object). |
41
+ | `labelField` | `name` | Field rendered indented in the first column. |
42
+ | `fields` | `[]` | Additional fields rendered as flat columns. |
43
+ | `defaultExpandedDepth` | _unset_ | Initial expansion depth. `0` = roots only; unset = expand everything. |
44
+
45
+ Records whose parent is missing (or points outside the result set) are kept as
46
+ roots, so nothing is silently dropped.
@@ -0,0 +1,14 @@
1
+ import { default as React } from 'react';
2
+ import { DataSource } from '@object-ui/types';
3
+ export interface ObjectTreeProps {
4
+ schema: any;
5
+ dataSource?: DataSource;
6
+ className?: string;
7
+ onRowClick?: (record: any) => void;
8
+ /** Inline data (passed by ListView/ObjectView for non-grid views). */
9
+ data?: any[];
10
+ loading?: boolean;
11
+ }
12
+ export declare const ObjectTree: React.FC<ObjectTreeProps>;
13
+ export default ObjectTree;
14
+ //# sourceMappingURL=ObjectTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectTree.d.ts","sourceRoot":"","sources":["../src/ObjectTree.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAM7D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,GAAG,CAAC;IACZ,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,sEAAsE;IACtE,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA8JD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA8PhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { default as React } from 'react';
2
+ import { ObjectTree, ObjectTreeProps } from './ObjectTree';
3
+ export { ObjectTree };
4
+ export type { ObjectTreeProps };
5
+ export declare const ObjectTreeRenderer: React.FC<any>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,CAAC;AAIhC,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAG5C,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,263 @@
1
+ import { useEffect as e, useMemo as t, useState as n } from "react";
2
+ import { ComponentRegistry as r, buildExpandFields as i, extractRecords as a } from "@object-ui/core";
3
+ import { useNavigationOverlay as o, useSchemaContext as s } from "@object-ui/react";
4
+ import { NavigationOverlay as c, cn as l } from "@object-ui/components";
5
+ import { ChevronDown as u, ChevronRight as d } from "lucide-react";
6
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
7
+ //#region src/ObjectTree.tsx
8
+ function m(e) {
9
+ return e.data ? e.data : e.staticData ? {
10
+ provider: "value",
11
+ items: e.staticData
12
+ } : e.objectName ? {
13
+ provider: "object",
14
+ object: e.objectName
15
+ } : null;
16
+ }
17
+ function h(e) {
18
+ let t = e.tree || e.filter?.tree || {};
19
+ return {
20
+ parentField: e.parentField ?? t.parentField,
21
+ labelField: e.labelField ?? t.labelField ?? e.titleField ?? "name",
22
+ fields: Array.isArray(e.fields) ? e.fields : Array.isArray(t.fields) ? t.fields : [],
23
+ defaultExpandedDepth: e.defaultExpandedDepth ?? t.defaultExpandedDepth
24
+ };
25
+ }
26
+ function g(e, t) {
27
+ let n = e?.fields;
28
+ if (!n || typeof n != "object") return;
29
+ let r;
30
+ for (let [e, i] of Object.entries(n)) {
31
+ if (i?.type === "tree") return e;
32
+ let n = i?.reference || i?.reference_to || i?.referenceTo;
33
+ !r && (i?.type === "lookup" || i?.type === "master_detail") && n && t && n === t && (r = e);
34
+ }
35
+ return r;
36
+ }
37
+ function _(e) {
38
+ let t = e?.id ?? e?._id;
39
+ return t == null ? void 0 : String(t);
40
+ }
41
+ function v(e, t) {
42
+ if (!t) return;
43
+ let n = e?.[t];
44
+ if (n != null) {
45
+ if (typeof n == "object") {
46
+ let e = n.id ?? n._id;
47
+ return e == null ? void 0 : String(e);
48
+ }
49
+ return String(n);
50
+ }
51
+ }
52
+ function y(e, t) {
53
+ let n = /* @__PURE__ */ new Map(), r = [];
54
+ for (let t of e) {
55
+ let e = _(t);
56
+ e != null && (n.set(e, {
57
+ id: e,
58
+ record: t,
59
+ depth: 0,
60
+ children: []
61
+ }), r.push(e));
62
+ }
63
+ let i = [];
64
+ for (let e of r) {
65
+ let r = n.get(e), a = v(r.record, t), o = a == null ? void 0 : n.get(a);
66
+ o && o !== r ? o.children.push(r) : i.push(r);
67
+ }
68
+ let a = (e, t) => {
69
+ for (let n of e) n.depth = t, a(n.children, t + 1);
70
+ };
71
+ return a(i, 0), i;
72
+ }
73
+ function b(e, t) {
74
+ let n = [], r = (e) => {
75
+ for (let i of e) n.push(i), i.children.length > 0 && t.has(i.id) && r(i.children);
76
+ };
77
+ return r(e), n;
78
+ }
79
+ function x(e, t) {
80
+ let n = /* @__PURE__ */ new Set(), r = (e) => {
81
+ for (let i of e) i.children.length !== 0 && (t == null || i.depth < t) && (n.add(i.id), r(i.children));
82
+ };
83
+ return r(e), n;
84
+ }
85
+ function S(e) {
86
+ return e == null ? "" : String(typeof e == "object" ? e.name ?? e.label ?? e.id ?? e._id ?? "" : e);
87
+ }
88
+ var C = ({ schema: r, dataSource: s, className: _, onRowClick: v, ...C }) => {
89
+ let [w, T] = n([]), [E, D] = n(!0), [O, k] = n(null), [A, j] = n(null), M = t(() => m(r), [r]), N = Array.isArray(C.data) || Array.isArray(r.data) || M?.provider === "value";
90
+ e(() => {
91
+ let e = !1;
92
+ return N || (async () => {
93
+ try {
94
+ if (!s || typeof s.getObjectSchema != "function") return;
95
+ let t = M?.provider === "object" ? M.object : r.objectName;
96
+ if (!t) return;
97
+ let n = await s.getObjectSchema(t);
98
+ e || j(n);
99
+ } catch (e) {
100
+ console.error("[ObjectTree] Failed to fetch object schema:", e);
101
+ }
102
+ })(), () => {
103
+ e = !0;
104
+ };
105
+ }, [
106
+ r.objectName,
107
+ s,
108
+ M,
109
+ N
110
+ ]), e(() => {
111
+ let e = !1;
112
+ return (async () => {
113
+ try {
114
+ if (D(!0), k(null), M?.provider === "object" && s && typeof s.find == "function") {
115
+ let t = i(A?.fields), n = await s.find(M.object, {
116
+ $filter: r.filter,
117
+ ...t.length > 0 ? { $expand: t } : {}
118
+ });
119
+ e || (T(a(n)), D(!1));
120
+ return;
121
+ }
122
+ let t = C.data ?? r.data;
123
+ if (Array.isArray(t)) {
124
+ e || (T(t), D(!1));
125
+ return;
126
+ }
127
+ if (M?.provider === "value") {
128
+ e || (T(M.items ?? []), D(!1));
129
+ return;
130
+ }
131
+ e || (T([]), D(!1));
132
+ } catch (t) {
133
+ e || (k(t), D(!1));
134
+ }
135
+ })(), () => {
136
+ e = !0;
137
+ };
138
+ }, [
139
+ M,
140
+ s,
141
+ r.filter,
142
+ A,
143
+ C.data
144
+ ]);
145
+ let P = t(() => h(r), [r]), F = t(() => P.parentField ?? g(A, r.objectName), [
146
+ P.parentField,
147
+ A,
148
+ r.objectName
149
+ ]), I = t(() => y(w, F), [w, F]), [L, R] = n(/* @__PURE__ */ new Set());
150
+ e(() => {
151
+ R(x(I, P.defaultExpandedDepth));
152
+ }, [I, P.defaultExpandedDepth]);
153
+ let z = (e) => R((t) => {
154
+ let n = new Set(t);
155
+ return n.has(e) ? n.delete(e) : n.add(e), n;
156
+ }), B = t(() => b(I, L), [I, L]), V = (e) => A?.fields?.[e]?.label || e.replace(/_/g, " ").replace(/\b\w/g, (e) => e.toUpperCase()), H = o({
157
+ navigation: r.navigation,
158
+ objectName: r.objectName,
159
+ onRowClick: v
160
+ });
161
+ return O ? /* @__PURE__ */ f("div", {
162
+ className: l("flex items-center justify-center h-40 text-destructive", _),
163
+ children: /* @__PURE__ */ p("p", { children: ["Failed to load tree: ", O.message] })
164
+ }) : E ? /* @__PURE__ */ f("div", {
165
+ className: l("flex items-center justify-center h-40 text-muted-foreground", _),
166
+ children: /* @__PURE__ */ f("p", { children: "Loading…" })
167
+ }) : w.length === 0 ? /* @__PURE__ */ f("div", {
168
+ className: l("flex items-center justify-center h-40 text-muted-foreground", _),
169
+ children: /* @__PURE__ */ f("p", { children: "No records" })
170
+ }) : /* @__PURE__ */ p("div", {
171
+ className: l("w-full overflow-auto", _),
172
+ "data-testid": "object-tree",
173
+ children: [/* @__PURE__ */ p("table", {
174
+ className: "w-full border-collapse text-sm",
175
+ children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", {
176
+ className: "border-b text-left text-muted-foreground",
177
+ children: [/* @__PURE__ */ f("th", {
178
+ className: "px-3 py-2 font-medium",
179
+ children: V(P.labelField)
180
+ }), P.fields.filter((e) => e !== P.labelField).map((e) => /* @__PURE__ */ f("th", {
181
+ className: "px-3 py-2 font-medium",
182
+ children: V(e)
183
+ }, e))]
184
+ }) }), /* @__PURE__ */ f("tbody", { children: B.map((e) => {
185
+ let t = e.children.length > 0, n = L.has(e.id);
186
+ return /* @__PURE__ */ p("tr", {
187
+ className: "border-b hover:bg-accent/50 cursor-pointer",
188
+ "data-testid": "object-tree-row",
189
+ "data-depth": e.depth,
190
+ onClick: (t) => H.handleClick(e.record, t),
191
+ children: [/* @__PURE__ */ f("td", {
192
+ className: "px-3 py-2",
193
+ children: /* @__PURE__ */ p("div", {
194
+ className: "flex items-center gap-1",
195
+ style: { paddingLeft: `${e.depth * 20}px` },
196
+ children: [t ? /* @__PURE__ */ f("button", {
197
+ type: "button",
198
+ "aria-label": n ? "Collapse" : "Expand",
199
+ className: "flex h-5 w-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted",
200
+ onClick: (t) => {
201
+ t.stopPropagation(), z(e.id);
202
+ },
203
+ children: f(n ? u : d, { className: "h-4 w-4" })
204
+ }) : /* @__PURE__ */ f("span", { className: "inline-block h-5 w-5" }), /* @__PURE__ */ f("span", {
205
+ className: "truncate",
206
+ children: S(e.record[P.labelField]) || "—"
207
+ })]
208
+ })
209
+ }), P.fields.filter((e) => e !== P.labelField).map((t) => /* @__PURE__ */ f("td", {
210
+ className: "px-3 py-2 text-muted-foreground",
211
+ children: S(e.record[t])
212
+ }, t))]
213
+ }, e.id);
214
+ }) })]
215
+ }), H.isOverlay && /* @__PURE__ */ f(c, {
216
+ ...H,
217
+ title: "Record Details",
218
+ children: (e) => /* @__PURE__ */ f("div", {
219
+ className: "space-y-3",
220
+ children: Object.entries(e).map(([e, t]) => /* @__PURE__ */ p("div", {
221
+ className: "flex flex-col",
222
+ children: [/* @__PURE__ */ f("span", {
223
+ className: "text-xs font-medium text-muted-foreground uppercase tracking-wide",
224
+ children: e.replace(/_/g, " ")
225
+ }), /* @__PURE__ */ f("span", {
226
+ className: "text-sm",
227
+ children: S(t) || "—"
228
+ })]
229
+ }, e))
230
+ })
231
+ })]
232
+ });
233
+ }, w = ({ schema: e, ...t }) => {
234
+ let { dataSource: n } = s() || {};
235
+ return /* @__PURE__ */ f(C, {
236
+ schema: e,
237
+ dataSource: n,
238
+ ...t
239
+ });
240
+ }, T = [{
241
+ name: "objectName",
242
+ type: "string",
243
+ label: "Object Name",
244
+ required: !0
245
+ }, {
246
+ name: "tree",
247
+ type: "object",
248
+ label: "Tree Config",
249
+ description: "parentField, labelField, fields, defaultExpandedDepth"
250
+ }];
251
+ r.register("object-tree", w, {
252
+ namespace: "plugin-tree",
253
+ label: "Object Tree",
254
+ category: "view",
255
+ inputs: T
256
+ }), r.register("tree", w, {
257
+ namespace: "view",
258
+ label: "Tree View",
259
+ category: "view",
260
+ inputs: T
261
+ });
262
+ //#endregion
263
+ export { C as ObjectTree, w as ObjectTreeRenderer };
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("@object-ui/core"),require("@object-ui/react"),require("@object-ui/components"),require("lucide-react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`@object-ui/core`,`@object-ui/react`,`@object-ui/components`,`lucide-react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ObjectUIPluginTree={},e.React,e.ObjectUICore,e.ObjectUIReact,e.ObjectUIComponents,e.LucideReact,e.react_jsx_runtime))})(this,function(e,t,n,r,i,a,o){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var s=Object.create,c=Object.defineProperty,l=Object.getOwnPropertyDescriptor,u=Object.getOwnPropertyNames,d=Object.getPrototypeOf,f=Object.prototype.hasOwnProperty,p=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=u(t),a=0,o=i.length,s;a<o;a++)s=i[a],!f.call(e,s)&&s!==n&&c(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=l(t,s))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:s(d(e)),p(t||!e||!e.__esModule?c(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);function m(e){return e.data?e.data:e.staticData?{provider:`value`,items:e.staticData}:e.objectName?{provider:`object`,object:e.objectName}:null}function h(e){let t=e.tree||e.filter?.tree||{};return{parentField:e.parentField??t.parentField,labelField:e.labelField??t.labelField??e.titleField??`name`,fields:Array.isArray(e.fields)?e.fields:Array.isArray(t.fields)?t.fields:[],defaultExpandedDepth:e.defaultExpandedDepth??t.defaultExpandedDepth}}function g(e,t){let n=e?.fields;if(!n||typeof n!=`object`)return;let r;for(let[e,i]of Object.entries(n)){if(i?.type===`tree`)return e;let n=i?.reference||i?.reference_to||i?.referenceTo;!r&&(i?.type===`lookup`||i?.type===`master_detail`)&&n&&t&&n===t&&(r=e)}return r}function _(e){let t=e?.id??e?._id;return t==null?void 0:String(t)}function v(e,t){if(!t)return;let n=e?.[t];if(n!=null){if(typeof n==`object`){let e=n.id??n._id;return e==null?void 0:String(e)}return String(n)}}function y(e,t){let n=new Map,r=[];for(let t of e){let e=_(t);e!=null&&(n.set(e,{id:e,record:t,depth:0,children:[]}),r.push(e))}let i=[];for(let e of r){let r=n.get(e),a=v(r.record,t),o=a==null?void 0:n.get(a);o&&o!==r?o.children.push(r):i.push(r)}let a=(e,t)=>{for(let n of e)n.depth=t,a(n.children,t+1)};return a(i,0),i}function b(e,t){let n=[],r=e=>{for(let i of e)n.push(i),i.children.length>0&&t.has(i.id)&&r(i.children)};return r(e),n}function x(e,t){let n=new Set,r=e=>{for(let i of e)i.children.length!==0&&(t==null||i.depth<t)&&(n.add(i.id),r(i.children))};return r(e),n}function S(e){return e==null?``:String(typeof e==`object`?e.name??e.label??e.id??e._id??``:e)}var C=({schema:e,dataSource:s,className:c,onRowClick:l,...u})=>{let[d,f]=(0,t.useState)([]),[p,_]=(0,t.useState)(!0),[v,C]=(0,t.useState)(null),[w,T]=(0,t.useState)(null),E=(0,t.useMemo)(()=>m(e),[e]),D=Array.isArray(u.data)||Array.isArray(e.data)||E?.provider===`value`;(0,t.useEffect)(()=>{let t=!1;return D||(async()=>{try{if(!s||typeof s.getObjectSchema!=`function`)return;let n=E?.provider===`object`?E.object:e.objectName;if(!n)return;let r=await s.getObjectSchema(n);t||T(r)}catch(e){console.error(`[ObjectTree] Failed to fetch object schema:`,e)}})(),()=>{t=!0}},[e.objectName,s,E,D]),(0,t.useEffect)(()=>{let t=!1;return(async()=>{try{if(_(!0),C(null),E?.provider===`object`&&s&&typeof s.find==`function`){let r=(0,n.buildExpandFields)(w?.fields),i=await s.find(E.object,{$filter:e.filter,...r.length>0?{$expand:r}:{}});t||(f((0,n.extractRecords)(i)),_(!1));return}let r=u.data??e.data;if(Array.isArray(r)){t||(f(r),_(!1));return}if(E?.provider===`value`){t||(f(E.items??[]),_(!1));return}t||(f([]),_(!1))}catch(e){t||(C(e),_(!1))}})(),()=>{t=!0}},[E,s,e.filter,w,u.data]);let O=(0,t.useMemo)(()=>h(e),[e]),k=(0,t.useMemo)(()=>O.parentField??g(w,e.objectName),[O.parentField,w,e.objectName]),A=(0,t.useMemo)(()=>y(d,k),[d,k]),[j,M]=(0,t.useState)(new Set);(0,t.useEffect)(()=>{M(x(A,O.defaultExpandedDepth))},[A,O.defaultExpandedDepth]);let N=e=>M(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n}),P=(0,t.useMemo)(()=>b(A,j),[A,j]),F=e=>w?.fields?.[e]?.label||e.replace(/_/g,` `).replace(/\b\w/g,e=>e.toUpperCase()),I=(0,r.useNavigationOverlay)({navigation:e.navigation,objectName:e.objectName,onRowClick:l});return v?(0,o.jsx)(`div`,{className:(0,i.cn)(`flex items-center justify-center h-40 text-destructive`,c),children:(0,o.jsxs)(`p`,{children:[`Failed to load tree: `,v.message]})}):p?(0,o.jsx)(`div`,{className:(0,i.cn)(`flex items-center justify-center h-40 text-muted-foreground`,c),children:(0,o.jsx)(`p`,{children:`Loading…`})}):d.length===0?(0,o.jsx)(`div`,{className:(0,i.cn)(`flex items-center justify-center h-40 text-muted-foreground`,c),children:(0,o.jsx)(`p`,{children:`No records`})}):(0,o.jsxs)(`div`,{className:(0,i.cn)(`w-full overflow-auto`,c),"data-testid":`object-tree`,children:[(0,o.jsxs)(`table`,{className:`w-full border-collapse text-sm`,children:[(0,o.jsx)(`thead`,{children:(0,o.jsxs)(`tr`,{className:`border-b text-left text-muted-foreground`,children:[(0,o.jsx)(`th`,{className:`px-3 py-2 font-medium`,children:F(O.labelField)}),O.fields.filter(e=>e!==O.labelField).map(e=>(0,o.jsx)(`th`,{className:`px-3 py-2 font-medium`,children:F(e)},e))]})}),(0,o.jsx)(`tbody`,{children:P.map(e=>{let t=e.children.length>0,n=j.has(e.id);return(0,o.jsxs)(`tr`,{className:`border-b hover:bg-accent/50 cursor-pointer`,"data-testid":`object-tree-row`,"data-depth":e.depth,onClick:t=>I.handleClick(e.record,t),children:[(0,o.jsx)(`td`,{className:`px-3 py-2`,children:(0,o.jsxs)(`div`,{className:`flex items-center gap-1`,style:{paddingLeft:`${e.depth*20}px`},children:[t?(0,o.jsx)(`button`,{type:`button`,"aria-label":n?`Collapse`:`Expand`,className:`flex h-5 w-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted`,onClick:t=>{t.stopPropagation(),N(e.id)},children:n?(0,o.jsx)(a.ChevronDown,{className:`h-4 w-4`}):(0,o.jsx)(a.ChevronRight,{className:`h-4 w-4`})}):(0,o.jsx)(`span`,{className:`inline-block h-5 w-5`}),(0,o.jsx)(`span`,{className:`truncate`,children:S(e.record[O.labelField])||`—`})]})}),O.fields.filter(e=>e!==O.labelField).map(t=>(0,o.jsx)(`td`,{className:`px-3 py-2 text-muted-foreground`,children:S(e.record[t])},t))]},e.id)})})]}),I.isOverlay&&(0,o.jsx)(i.NavigationOverlay,{...I,title:`Record Details`,children:e=>(0,o.jsx)(`div`,{className:`space-y-3`,children:Object.entries(e).map(([e,t])=>(0,o.jsxs)(`div`,{className:`flex flex-col`,children:[(0,o.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wide`,children:e.replace(/_/g,` `)}),(0,o.jsx)(`span`,{className:`text-sm`,children:S(t)||`—`})]},e))})})]})},w=({schema:e,...t})=>{let{dataSource:n}=(0,r.useSchemaContext)()||{};return(0,o.jsx)(C,{schema:e,dataSource:n,...t})},T=[{name:`objectName`,type:`string`,label:`Object Name`,required:!0},{name:`tree`,type:`object`,label:`Tree Config`,description:`parentField, labelField, fields, defaultExpandedDepth`}];n.ComponentRegistry.register(`object-tree`,w,{namespace:`plugin-tree`,label:`Object Tree`,category:`view`,inputs:T}),n.ComponentRegistry.register(`tree`,w,{namespace:`view`,label:`Tree View`,category:`view`,inputs:T}),e.ObjectTree=C,e.ObjectTreeRenderer=w});
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@object-ui/plugin-tree",
3
+ "version": "7.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Tree / tree-grid visualization plugin for Object UI",
7
+ "homepage": "https://www.objectui.org/docs/plugins/plugin-tree",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/objectstack-ai/objectui.git",
11
+ "directory": "packages/plugin-tree"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/objectstack-ai/objectui/issues"
15
+ },
16
+ "main": "dist/index.umd.cjs",
17
+ "module": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.umd.cjs"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@objectstack/spec": "^10.0.0",
28
+ "lucide-react": "^1.21.0",
29
+ "@object-ui/components": "7.0.0",
30
+ "@object-ui/core": "7.0.0",
31
+ "@object-ui/react": "7.0.0",
32
+ "@object-ui/types": "7.0.0"
33
+ },
34
+ "peerDependencies": {
35
+ "react": "^18.0.0 || ^19.0.0",
36
+ "react-dom": "^18.0.0 || ^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "19.2.17",
40
+ "@types/react-dom": "19.2.3",
41
+ "@vitejs/plugin-react": "^6.0.2",
42
+ "typescript": "^6.0.3",
43
+ "vite": "^8.0.16",
44
+ "vite-plugin-dts": "^5.0.2"
45
+ },
46
+ "keywords": [
47
+ "objectui",
48
+ "sdui",
49
+ "schema-driven-ui",
50
+ "react",
51
+ "tailwind",
52
+ "shadcn",
53
+ "objectstack",
54
+ "plugin",
55
+ "tree",
56
+ "tree-grid",
57
+ "hierarchy"
58
+ ],
59
+ "author": "ObjectStack Team <team@objectstack.ai>",
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "files": [
64
+ "dist",
65
+ "README.md",
66
+ "CHANGELOG.md",
67
+ "LICENSE"
68
+ ],
69
+ "scripts": {
70
+ "build": "vite build",
71
+ "test": "vitest run",
72
+ "test:watch": "vitest",
73
+ "type-check": "tsc --noEmit",
74
+ "lint": "eslint ."
75
+ }
76
+ }