@object-ui/plugin-tree 17.6.0 → 18.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/README.md CHANGED
@@ -7,44 +7,177 @@ the right view for hierarchies of unbounded depth such as **business unit /
7
7
  org chart**, category trees, menu trees, BOMs, or nested comments. (Grouping
8
8
  handles *fixed-depth* hierarchies; a tree handles arbitrary depth.)
9
9
 
10
- It registers two component types via the `ComponentRegistry`:
10
+ It registers one component type via the `ComponentRegistry`:
11
11
 
12
- - `object-tree` — the object-bound renderer
13
- - `tree` — the view-type alias used by `ObjectView` / `ViewSwitcher`
12
+ - `object-tree` — the object-bound renderer, and the one an author selects
13
+
14
+ The bare `tree` registry key (`view:tree`), a second key on the same renderer,
15
+ is RETIRED (objectui#10859 batch 8): `objectui validate` refused it at `type`,
16
+ and nothing authored it. `tree` survives as a host-composition VIEW type (see
17
+ below), which `ObjectView` and `ListView` compose into an `object-tree` node.
14
18
 
15
19
  ## Usage
16
20
 
21
+ Author an `object-tree` node. `ObjectQLComponentSchema` narrows on `type`, so
22
+ each key is checked against `ObjectTreeSchema`, which mirrors the `object-tree`
23
+ row of `@objectstack/spec` (17.5.0 and later):
24
+
17
25
  ```ts
18
- // As a view inside an ObjectView
19
- {
20
- type: 'object-view',
26
+ import type { ObjectQLComponentSchema } from '@object-ui/types';
27
+
28
+ const schema: ObjectQLComponentSchema = {
29
+ type: 'object-tree',
21
30
  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
- }
31
+ tree: {
32
+ parentField: 'parent', // single-parent pointer (auto-detected if omitted)
33
+ labelField: 'name', // indented first column
34
+ fields: ['manager'], // additional flat columns
35
+ defaultExpandedDepth: 1, // 0 = roots only; omit = expand all
36
+ },
37
+ filter: [{ field: 'status', operator: 'equals', value: 'active' }],
38
+ navigation: { mode: 'drawer' },
39
+ };
34
40
  ```
35
41
 
36
- ### Config
42
+ ### Keys
43
+
44
+ | Key | Description |
45
+ | --- | --- |
46
+ | `objectName` | The object to query. One of `data`, `staticData` and `objectName` is the record source, read in that order; `objectui validate` refuses a node with none of the three. |
47
+ | `data` | A `{ provider, … }` configuration, read first. `{ provider: 'value', items }` draws those records and `{ provider: 'object', object }` queries that object. The `api` and `schema` providers draw no rows on the tree. A bare array is not a record source (objectui#8348): inline rows go under `staticData`. |
48
+ | `staticData` | Inline records, read second: a `data` configuration wins, and a tree carrying both `staticData` and `objectName` draws these rows and never queries the object. |
49
+ | `tree` | The field configuration — the spec's `TreeConfig` (table below). |
50
+ | `filter` | Rule array `[{ field, operator, value }]`, lowered to `$filter` and applied to inline rows as well. Context tokens such as `{current_user_id}` are resolved first. A record whose parent the filter removed is drawn as a root. |
51
+ | `navigation` | What a row click opens (below). |
52
+
53
+ The node's `dataSource` binding is **not** a record source on this block: the
54
+ registration is not wrapped in `ElementDataSourceGate`, so nothing lands a
55
+ binding's object on `objectName`, and `objectui validate` does not count it.
56
+
57
+ ### The `tree` block
37
58
 
38
59
  | Key | Default | Description |
39
60
  | --- | --- | --- |
40
61
  | `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
62
  | `labelField` | `name` | Field rendered indented in the first column. |
42
- | `fields` | `[]` | Additional fields rendered as flat columns. |
63
+ | `fields` | `[]` | Additional fields rendered as flat columns after the label. |
43
64
  | `defaultExpandedDepth` | _unset_ | Initial expansion depth. `0` = roots only; unset = expand everything. |
44
65
 
45
66
  Records whose parent is missing (or points outside the result set) are kept as
46
67
  roots, so nothing is silently dropped.
47
68
 
69
+ ⚠️ The same four keys written **flat** on the node are still read, and a flat
70
+ key outranks the block's member of the same name. They are the form a host
71
+ composes; the spec row declares only the `tree` block, so author that.
72
+
73
+ ### `navigation`
74
+
75
+ `navigation` takes the spec's `NavigationConfig` (`{ mode, size, width,
76
+ openNewTab, preventNavigation }`), the block a list view declares. On a tree no
77
+ parent view navigates for:
78
+
79
+ - **Absent**: a click opens nothing. This renderer supplies no drawer default.
80
+ - `drawer`, `modal` and `popover` open the row's record in that overlay; `split`
81
+ opens it beside the tree, which stays drawn.
82
+ - `new_window` opens `/{objectName}/record/{id}` in a new tab; `none` opens
83
+ nothing.
84
+ - `page`, and a block without `mode` (it takes the spec's `page` default), open
85
+ a record page through the record navigator the host publishes (the console
86
+ publishes one on its custom pages, record pages and list views). The object
87
+ is `data.object` when `data` is the object provider, so it wins when both
88
+ are written, and the tree's `objectName` otherwise. Under a host that
89
+ publishes none, such as an embedded renderer, or on a tree that names neither
90
+ (inline rows with no `objectName`), there is no record page to open and the
91
+ click opens nothing.
92
+ - `preventNavigation: true` opens nothing whatever the mode. `openNewTab: true`
93
+ opens the record page in a new tab and outranks every mode except `none`.
94
+ - `size` picks the overlay width bucket; the deprecated `width` wins over it.
95
+
96
+ A click handler from a parent view outranks the whole key.
97
+
98
+ ### Expansion: the seed and the user's answer
99
+
100
+ `defaultExpandedDepth` **seeds** expansion; it does not own it. The seeded set is
101
+ derived from the forest during render rather than mirrored into component state,
102
+ so a tree that expands by default is painted expanded in the first commit that
103
+ has rows — there is no frame in which the forest is drawn collapsed
104
+ (objectui#8666).
105
+
106
+ When the record set changes — a refetch, a filter, a host that reallocates the
107
+ rows — the seed is recomputed for the new forest. A node the user opened or
108
+ closed by clicking its chevron, **and which is still in the forest**, keeps the
109
+ user's answer; every other node, a genuinely new one included, takes the seed.
110
+ Expansion is per-mount session state: it is not addressable and is not persisted.
111
+
112
+ ### The `tree` view type is host composition, not authoring
113
+
114
+ `tree` is **not** an authorable view type. Neither `ObjectViewSchema.defaultViewType`
115
+ nor `NamedListView.type` admits it — both are the same seven-value union that stops
116
+ at `map` — so no authored document selects a tree view, and `ObjectViewSchema`
117
+ declares no `views` member at all. The `tree` branch runs only when a **host**
118
+ composes `ObjectView` with a `views` prop, whose entries carry `id` and `label`
119
+ and are typed `ViewType`. That was ruled deliberate on objectui#5321
120
+ (2026-08-20): `tree` and `chart` are recorded as host-composition-only surfaces
121
+ rather than added to the authored unions.
122
+
123
+ The live consumer is the console: it passes stored view records to `ObjectView`
124
+ as `views`, and its create-view dialog offers `tree` among the types a console
125
+ user can create. To render a tree from authored metadata, write the
126
+ `object-tree` node above.
127
+
128
+ #### The host config has a type — `TreeViewConfig`
129
+
130
+ Host config is **not** untyped config. A block a host stores and re-writes is a
131
+ contract, so the per-view `tree` block is exported from `@object-ui/types` and
132
+ the renderer imports it rather than keeping a private copy (objectui#8253,
133
+ ruled 2026-09-07).
134
+
135
+ ⚠️ It is **not the single declaration of that shape**, and saying so was itself
136
+ the defect objectui#8841 fixed. `@objectstack/spec` owns this block — it
137
+ declares it as `TreeConfig` and hangs it on `ListView.tree` — and
138
+ `@object-ui/types` already publishes it a second way, derived, as
139
+ `ListViewSchema['tree']`. `TreeViewConfig` is now a **derivation of the
140
+ protocol's block** rather than a copy of it — in `packages/types/src/views.ts`
141
+ it is a one-line alias of `NonNullable<ListView['tree']>`, taken from
142
+ `@objectstack/spec/ui`. So the accurate claim is the narrower one: this is the
143
+ name a host writes against, and it tracks the protocol by construction rather
144
+ than by anyone remembering to update it.
145
+
146
+ ```ts
147
+ import type { TreeViewConfig } from '@object-ui/types';
148
+
149
+ // The block a host writes as `tree` on a `views` entry, or as `options.tree`
150
+ // on a stored view record. Every key is optional; a host writes the subset it
151
+ // means.
152
+ const treeConfig: TreeViewConfig = {
153
+ parentField: 'parent', // single-parent pointer (auto-detected if omitted)
154
+ labelField: 'name', // indented first column
155
+ fields: ['name', 'manager'], // additional flat columns
156
+ defaultExpandedDepth: 1, // 0 = roots only; omit = expand all
157
+ };
158
+ ```
159
+
160
+ Annotating the block is what turns a typo into a diagnostic: `parentFeild` used
161
+ to be stored, read by nobody and reported by nothing, because the `views` entry
162
+ admits any key. Against this type it is a compile error.
163
+
164
+ ⛔ `titleField` is **not** part of this block. objectui#8253 declared it as a
165
+ legacy second rung for `labelField`; `@objectstack/spec@17.4.0` refuses
166
+ `tree.titleField` by name (`TreeConfigSchema` is strict since spec #15469), so
167
+ declaring it published a key the protocol rejects — an author who followed this
168
+ type was refused at publish. objectui#8841 removed it.
169
+
170
+ The renderers still *tolerate* a `titleField` already stored on a view record:
171
+ `plugin-view`, `plugin-list` and the console's own composition each fall back to
172
+ it when `labelField` is absent, so nothing that renders today stops rendering.
173
+ Those reads are untyped tolerance awaiting a follow-up, ⛔ not a declaration —
174
+ write `labelField`, which is the protocol's spelling and wins wherever both are
175
+ present.
176
+
177
+ ⛔ This does not make `tree` an authorable view type. objectui#5321 is
178
+ unchanged: the block is written by a **host**, never by a document author, and
179
+ the authored node is the `object-tree` schema at the top of this file.
180
+
48
181
  ## License
49
182
 
50
183
  MIT — see [LICENSE](./LICENSE).
@@ -1,10 +1,52 @@
1
1
  import { default as React } from 'react';
2
- import { DataSource } from '@object-ui/types';
2
+ import { DataSource, ObjectTreeSchema } from '@object-ui/types';
3
3
  export interface ObjectTreeProps {
4
- schema: any;
4
+ /**
5
+ * The `object-tree` node this renderer draws.
6
+ *
7
+ * Both of this package's registrations (`object-tree` and `tree`, in this
8
+ * package's `index.tsx`) publish one `treeInputs` list and one renderer, so one
9
+ * props type covers both tags — the shape `plugin-map` already has under two
10
+ * tags, and the shape objectui#8651 gave `ObjectCalendar`.
11
+ *
12
+ * ## What typing this BUYS, stated rather than assumed (objectui#8655)
13
+ *
14
+ * It was `any`, and an `any` is not a weaker answer than a type — it is NO
15
+ * answer. `checker.getPropertyOfType` through `any` returns `undefined` for
16
+ * `objectName`, which this schema certainly declares, exactly as it does for a
17
+ * nonsense token; so every "is this key declared?" asked of this prop came back
18
+ * "unknown" while LOOKING like "undeclared". That ordering is the card's — type
19
+ * FIRST, classify second — and it is objectui#8410's standing ruling that an
20
+ * absence returned by an instrument structurally unable to see a declaration is
21
+ * not a reading at all.
22
+ *
23
+ * ## ⚠️ The CEILING, so nobody reads this as more than it is
24
+ *
25
+ * `BaseSchema` ended in `[key: string]: any` when this was written, so an
26
+ * UNDECLARED key read off this type still compiled and typed `any`. What the
27
+ * annotation bought was that the question became ANSWERABLE by the checker, ⛔ not
28
+ * that an undeclared read was refused — the same ceiling objectui#5155 /
29
+ * objectui#7927 recorded for the mirror. objectui#8347 removed the signature, so
30
+ * an undeclared read off this type no longer compiles.
31
+ */
32
+ schema: ObjectTreeSchema;
5
33
  dataSource?: DataSource;
6
34
  className?: string;
7
- onRowClick?: (record: any) => void;
35
+ /**
36
+ * TWO parameters since objectui#9357, and the second is not decoration: this
37
+ * prop reaches `useNavigationOverlay` as its `onRowClick`, and `handleClick`
38
+ * invokes it as `onRowClick(record, event)` — the modifier payload a host
39
+ * needs to implement Cmd/Ctrl/middle-click for itself. Declaring one
40
+ * parameter hid the second on the ONE line a host reads. Spelled `any` and
41
+ * not `HandleClickModifiers` for the reason objectui#9341 measured on
42
+ * `ObjectKanbanSchema.onCardClick`: that interface lives in
43
+ * `@object-ui/react`, the published twins in `@object-ui/types` may not name
44
+ * it, and a host that discovered the payload from the implementation
45
+ * annotated it `React.MouseEvent` — which a narrower declaration refuses
46
+ * contravariantly. `BaseSchema`'s own `onClick` / `onChange` / `onSubmit`
47
+ * already use this spelling for exactly this situation.
48
+ */
49
+ onRowClick?: (record: any, event?: any) => void;
8
50
  /** Inline data (passed by ListView/ObjectView for non-grid views). */
9
51
  data?: any[];
10
52
  loading?: boolean;
@@ -1 +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;AA+B7D,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;AA6KD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAgRhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAkB,MAAM,kBAAkB,CAAC;AA4ErF,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAChD,sEAAsE;IACtE,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAggBD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwtBhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,32 @@
1
1
  import { default as React } from 'react';
2
- import { ObjectTree, ObjectTreeProps } from './ObjectTree';
2
+ import { ObjectTree, ObjectTreeProps } from './ObjectTree.js';
3
3
  export { ObjectTree };
4
4
  export type { ObjectTreeProps };
5
5
  export declare const ObjectTreeRenderer: React.FC<any>;
6
+ /**
7
+ * ⛔ The bare `tree` node type key is RETIRED (objectui#10859 batch 8, the
8
+ * seat's ruling on that card, by the objectui#10393 / objectui#8760 route).
9
+ * `object-tree` is the one spelling this plugin serves.
10
+ *
11
+ * ## What was here, and why it went
12
+ *
13
+ * `ComponentRegistry.register('tree', ObjectTreeRenderer, { namespace: 'view',
14
+ * ... })` — a second key on the SAME renderer with the SAME `inputs`, which
15
+ * stored both `view:tree` and the bare `tree` fallback. Only `object-tree` has
16
+ * a declaration: `@objectstack/spec`'s `ComponentPropsMap` row, which
17
+ * `AnyComponentSchema` arms, while no arm claims `tree`. So `objectui validate`
18
+ * refused a node authored `type: 'tree'` at `type` while the registry mounted
19
+ * it. `PUBLIC_BLOCKS` already declined to curate it as "a second spelling of
20
+ * this one block".
21
+ *
22
+ * ## Why unregistering is the whole retirement
23
+ *
24
+ * Nothing wrote the node: 0 producers in source, docs, examples or the
25
+ * catalog, measured for the card. The other `tree` spellings in the stack are
26
+ * different layers and stay untouched — the FIELD type `tree` (a self-reference
27
+ * that `@object-ui/fields` renders as `field:lookup`) and a STORED list-view
28
+ * type, which `@object-ui/plugin-list`'s `ListView` already composes into an
29
+ * `object-tree` node. The console's lazy `tree` stub (`apps/console/src/register-plugins.ts`)
30
+ * went in the same change, so no host keeps the key alive.
31
+ */
6
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;AA+DF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}