@object-ui/plugin-form 6.2.2 → 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 +323 -0
- package/dist/{packages/plugin-form/src/DrawerForm.d.ts → DrawerForm.d.ts} +14 -1
- package/dist/{packages/plugin-form/src/EmbeddableForm.d.ts → EmbeddableForm.d.ts} +1 -1
- package/dist/LineItemsPanel.d.ts +20 -0
- package/dist/MasterDetailForm.d.ts +67 -0
- package/dist/{packages/plugin-form/src/ModalForm.d.ts → ModalForm.d.ts} +17 -1
- package/dist/{packages/plugin-form/src/ObjectForm.d.ts → ObjectForm.d.ts} +1 -1
- package/dist/{packages/plugin-form/src/SplitForm.d.ts → SplitForm.d.ts} +1 -1
- package/dist/{packages/plugin-form/src/TabbedForm.d.ts → TabbedForm.d.ts} +1 -1
- package/dist/{packages/plugin-form/src/WizardForm.d.ts → WizardForm.d.ts} +13 -1
- package/dist/{packages/plugin-form/src/autoLayout.d.ts → autoLayout.d.ts} +1 -1
- package/dist/deriveMasterDetail.d.ts +86 -0
- package/dist/fieldGroups.d.ts +27 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.js +1964 -875
- package/dist/index.umd.cjs +1 -1
- package/dist/masterDetailTx.d.ts +80 -0
- package/dist/sectionFields.d.ts +22 -0
- package/dist/successBehavior.d.ts +21 -0
- package/package.json +11 -11
- package/dist/packages/plugin-form/src/index.d.ts +0 -20
- /package/dist/{packages/plugin-form/src/FormAnalytics.d.ts → FormAnalytics.d.ts} +0 -0
- /package/dist/{packages/plugin-form/src/FormSection.d.ts → FormSection.d.ts} +0 -0
- /package/dist/{packages/plugin-form/src/sanitize.d.ts → sanitize.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,328 @@
|
|
|
1
1
|
# @object-ui/plugin-form
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5332639: feat(app-shell): render full object forms (incl. master-detail) in screen-flow wizard steps
|
|
8
|
+
|
|
9
|
+
`FlowRunner` now renders an `object-form` screen step: when the paused screen
|
|
10
|
+
carries `kind: 'object-form'`, it mounts the real `<ObjectForm>` for the named
|
|
11
|
+
object (auto-routing to `MasterDetailForm` for inline child collections),
|
|
12
|
+
prefilled from the step's `defaults`. The form persists itself (atomic
|
|
13
|
+
master-detail batch), then resumes the run with the saved record id bound to the
|
|
14
|
+
step's `idVariable`. `dataSource`/`objects` are threaded through all three
|
|
15
|
+
`FlowRunner` mount points.
|
|
16
|
+
|
|
17
|
+
Also fixes three pre-existing bugs this surfaced (each affects normal forms too):
|
|
18
|
+
|
|
19
|
+
- **plugin-form**: `ObjectForm` now forwards `initialValues`/`initialData` when
|
|
20
|
+
routing to `MasterDetailForm`, so prefilled header values are no longer
|
|
21
|
+
dropped on master-detail create forms.
|
|
22
|
+
- **fields**: `PercentField` treated values as `0–1` fractions (`value × 100`),
|
|
23
|
+
so a `0–100` field (e.g. `probability` default `50`) rendered as `5000%` —
|
|
24
|
+
exceeding `max=100`, which makes HTML5 constraint validation mark the field
|
|
25
|
+
`:invalid` and silently block the whole form's submit. It now treats a field
|
|
26
|
+
declaring `max > 1` as the `0–100` whole-number convention, matching the
|
|
27
|
+
read-side formatter.
|
|
28
|
+
- **data-objectstack**: `ObjectStackAdapter.batchTransaction` now sends
|
|
29
|
+
`credentials: 'include'`, so master-detail batch saves authenticate under the
|
|
30
|
+
console's cookie session (previously every batch save 401'd).
|
|
31
|
+
|
|
32
|
+
- 80c133c: Spreadsheet-style line-item grid editor.
|
|
33
|
+
|
|
34
|
+
`GridField`'s editable grid mode is reworked into an enterprise line-item editor (the QuickBooks / Stripe / NetSuite pattern), generalised across every inline grid:
|
|
35
|
+
|
|
36
|
+
- **Computed read-only columns** — a child field with an arithmetic `expression` (e.g. `amount = quantity * unit_price`) renders read-only, recomputes live as its inputs change, and writes the result back into the row so it persists and the running total reflects it. A small safe arithmetic evaluator (`+ - * / %`, parens, `record.<field>` refs; no `eval`) powers it.
|
|
37
|
+
- **Trailing "ghost" row** — start-with-one + auto-append: typing in the ghost materialises a real row (index-stable, so focus/caret survive), so you keep entering lines without clicking "Add".
|
|
38
|
+
- **Borderless click-to-focus cells** + role-based column widths (description flexes; qty/price/amount stay narrow).
|
|
39
|
+
- **Keyboard navigation** — Enter / ArrowUp / ArrowDown move between rows in the same column.
|
|
40
|
+
- Per-row "expand to full form" is gated to grids that omit fields (no redundant expand on thin lines).
|
|
41
|
+
- `deriveColumns` surfaces a field `expression` as a computed column; the running-total column prefers the computed/last-currency column. Blank/ghost rows are filtered from the persisted batch (`isBlankRow`).
|
|
42
|
+
|
|
43
|
+
- d16566f: Atomic master-detail create via the cross-object transactional batch endpoint (ObjectStack #1604).
|
|
44
|
+
|
|
45
|
+
When the server exposes the transactional batch endpoint, a NEW parent record and its child line items are now persisted in ONE server transaction — commit all or roll back all — instead of the previous client-orchestrated "create parent → create children → best-effort cleanup on failure" sequence.
|
|
46
|
+
|
|
47
|
+
**`@object-ui/data-objectstack` — `ObjectStackAdapter.batchTransaction(operations)`**
|
|
48
|
+
|
|
49
|
+
- New method posting `{ operations }` to `POST /api/v1/batch`. Operations run in one server transaction. A field value of `{ $ref: <earlier op index> }` resolves to that op's generated id, so a child can reference its parent created earlier in the same batch (master-detail FK). Throws `ObjectStackError('BATCH_ERROR')` on a non-2xx response.
|
|
50
|
+
|
|
51
|
+
**`@object-ui/plugin-form`**
|
|
52
|
+
|
|
53
|
+
- `MasterDetailForm` now detects `dataSource.batchTransaction` and, on a NEW parent, builds one atomic batch (parent at index 0, each child FK set to `{ $ref: 0 }`) via the new pure helper `buildMasterDetailBatch`. Client-side total rollups are merged into the parent payload before the batch. Edit mode and adapters without `batchTransaction` keep the existing client-orchestrated path.
|
|
54
|
+
- `ObjectForm` gained a `submitHandler` hook: when supplied, the form validates and hands the collected values to the host instead of calling `dataSource.create` / `dataSource.update`. `MasterDetailForm` uses it to own the atomic parent+children write while the parent fields are still rendered by `ObjectForm`.
|
|
55
|
+
|
|
56
|
+
**`@object-ui/types`**
|
|
57
|
+
|
|
58
|
+
- `ObjectFormSchema.submitHandler?: (values) => any | Promise<any>` — typed override for host-owned persistence.
|
|
59
|
+
|
|
60
|
+
Pairs with the framework-side ambient-transaction fix (ObjectQL `AsyncLocalStorage` transaction propagation) and the `/api/v1/batch` endpoint added in `@objectstack/rest`.
|
|
61
|
+
|
|
62
|
+
- 69510df: feat(master-detail): derive child columns + relationship FK from metadata
|
|
63
|
+
|
|
64
|
+
A master-detail child collection can now be configured with **just the child
|
|
65
|
+
object name** — the relationship FK and the editable grid columns are derived
|
|
66
|
+
from the child object's schema (via `DataSource.getObjectSchema`), instead of a
|
|
67
|
+
hand-authored columns block.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// before: ~40 lines of columns + relationshipField
|
|
71
|
+
details: [{ childObject: 'task', relationshipField: 'project', columns: [ ...12 lines... ] }]
|
|
72
|
+
// after:
|
|
73
|
+
details: [{ childObject: 'task' }]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `relationshipField` is auto-detected from the child's `master_detail`/`lookup`
|
|
77
|
+
field that references the parent (master_detail preferred).
|
|
78
|
+
- `columns` are derived from the child's fields, skipping system/audit fields,
|
|
79
|
+
the back-reference FK, and non-editable types (formula/summary/autonumber/
|
|
80
|
+
file/json/…); select options and lookup references carry through.
|
|
81
|
+
- `amountField` (running-total source) defaults to the first numeric/currency
|
|
82
|
+
column.
|
|
83
|
+
- Any of these can still be set explicitly to override the derived defaults.
|
|
84
|
+
- Save is gated until derivation resolves; new pure helpers
|
|
85
|
+
(`deriveDetail`/`deriveColumns`/`findRelationshipField`) are unit-tested.
|
|
86
|
+
|
|
87
|
+
- b148daf: feat(master-detail): atomic EDIT via the cross-object batch endpoint
|
|
88
|
+
|
|
89
|
+
Edit mode now persists the parent update together with its child line-item
|
|
90
|
+
create/update/delete diffs in ONE server transaction (commit all or roll back
|
|
91
|
+
all), matching what create already did. Previously only create used the atomic
|
|
92
|
+
`/api/v1/batch` path; edit fell back to client-orchestrated writes with
|
|
93
|
+
best-effort cleanup.
|
|
94
|
+
|
|
95
|
+
- New pure helper `buildMasterDetailEditBatch(parentObject, parentId,
|
|
96
|
+
parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
97
|
+
child collection against its loaded snapshot into `create` / `update` /
|
|
98
|
+
`delete` ops (children reference the known parent id directly, no `$ref`).
|
|
99
|
+
- `MasterDetailForm` now treats `canBatch` as available whenever the data
|
|
100
|
+
source exposes `batchTransaction` (create AND edit). `submitViaBatch` builds
|
|
101
|
+
create-ops or edit-ops by mode; `onSuccess` → `handleSaved` ("saved" toast,
|
|
102
|
+
no form reset in edit).
|
|
103
|
+
|
|
104
|
+
The server `/api/v1/batch` handler already supports `update`/`delete` actions,
|
|
105
|
+
and the adapter already forwards `action`/`id`, so this is a front-end change.
|
|
106
|
+
Unit-tested (parent update + child create/update/delete diff); the create path
|
|
107
|
+
remains verified by the live e2e.
|
|
108
|
+
|
|
109
|
+
- 90acb7f: Master-detail subform + lightweight list primitives (SDUI).
|
|
110
|
+
|
|
111
|
+
- `MasterDetailForm` (`object-master-detail-form`): enter a parent record and its child line items together; client-orchestrated transactional create (parent → FK → bulk children → rollup → cleanup). Enterprise-convention layout (header on top, line grid, single Save bar at the bottom).
|
|
112
|
+
- `LineItemsField` editable child grid (line numbers, right-aligned numerics, running total) and `LineItemsPanel` (`record:line_items`) for detail-page inline edit.
|
|
113
|
+
- `element:definition-list` and `element:repeater` — lightweight, low-chrome list primitives for simple data.
|
|
114
|
+
|
|
115
|
+
- 00f8d2d: Master-detail form: live Subtotal / Tax / Total stack.
|
|
116
|
+
|
|
117
|
+
`MasterDetailForm` now renders a right-aligned document totals stack under the line items when the parent form has a tax-rate field (`taxRateField`, default `tax_rate`): **Subtotal** (Σ line amounts) → **Tax** (header rate %) → **Total**, recomputed live as lines and the rate change. The header rate is read via scoped event delegation on the form host (no coupling into `ObjectForm` internals). When the stack is shown, the per-grid footer total is subsumed.
|
|
118
|
+
|
|
119
|
+
- 300d755: feat(form): inline master-detail in a plain ObjectForm via `subforms`
|
|
120
|
+
|
|
121
|
+
`ObjectFormSchema` gains a `subforms` array. When set, a regular `object-form`
|
|
122
|
+
renders as a master-detail form — the object's own fields on top, an editable
|
|
123
|
+
grid per child collection below, persisted together in one atomic transaction —
|
|
124
|
+
without a bespoke `object-master-detail-form` page.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
{ type: 'object-form', objectName: 'expense_claim',
|
|
128
|
+
subforms: [{ childObject: 'expense_line' }] } // FK + columns auto-derived
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each subform needs only `childObject` (relationship FK and columns are derived
|
|
132
|
+
from the child object's metadata; override with `relationshipField`/`columns`).
|
|
133
|
+
This is the config-driven, page-less way to express master-detail entry — a form
|
|
134
|
+
view can declare its child collections directly.
|
|
135
|
+
|
|
136
|
+
- 18728c1: Master-detail entry: lighter layout, compact lookup cells, persisted line order.
|
|
137
|
+
|
|
138
|
+
- **De-framed line-item section** — the subform no longer double-frames the grid in a `Card` (border + `p-6`); it renders as a light label + the grid's own bordered table, reclaiming the width the line table needs.
|
|
139
|
+
- **Compact lookup cells** — `LookupField` gains a `compact` mode (used by grid cells): the selected value shows inline in a borderless single-line trigger instead of a chip stacked above a separate "Select…" button.
|
|
140
|
+
- **Persisted drag-reorder** — `deriveMasterDetail` detects a sort field (`position`/`sort_order`/…), excludes it from the editable columns/row-form, and threads it as the grid's `sort_field` so reordering stamps `row[position] = index` and survives a reload.
|
|
141
|
+
|
|
142
|
+
- 8426db7: feat(form): standard New/Edit modal renders form-view subforms (Tier 0)
|
|
143
|
+
|
|
144
|
+
The console's standard create/edit record modal now renders inline child
|
|
145
|
+
collections when the object's form view declares `subforms` — master-detail
|
|
146
|
+
entry with **no bespoke page**, persisted as one atomic transaction.
|
|
147
|
+
|
|
148
|
+
- `ModalForm` (and the create/edit modal in app-shell `AppContent`) detects
|
|
149
|
+
`subforms` and renders `MasterDetailForm` inside the dialog (it owns its Save
|
|
150
|
+
bar; the modal footer is suppressed); on success the modal closes + refreshes.
|
|
151
|
+
- `AppContent` sources `subforms` from the object's default form view
|
|
152
|
+
(`form.subforms` / `formViews.default.subforms`).
|
|
153
|
+
- `ModalFormSchema` gains `subforms`.
|
|
154
|
+
|
|
155
|
+
With this, declaring `formViews.default.subforms: [{ childObject }]` is enough
|
|
156
|
+
to make an object's standard New/Edit screen a master-detail form — completing
|
|
157
|
+
the config-driven master-detail story (Tier 0 → derive everything from the
|
|
158
|
+
relationship + child metadata).
|
|
159
|
+
|
|
160
|
+
### Patch Changes
|
|
161
|
+
|
|
162
|
+
- ddbe4a2: B2 step 3: client-side field-level conditional rules (`visibleWhen` / `readonlyWhen` / `requiredWhen`). The form renderer now evaluates these CEL predicates reactively against the live record and gates each field's visibility, read-only state, and required-ness accordingly. Evaluation delegates to the canonical `@objectstack/formula` `ExpressionEngine` — the _same_ dialect the server enforces (`requiredWhen` in the rule-validator, `readonlyWhen` in `stripReadonlyWhenFields`) — so the UX and the persisted verdict always agree. New core helpers `evalFieldPredicate` / `resolveFieldRuleState` (zero-React, fail-open). `FormField` gains `visibleWhen` / `readonlyWhen` / `requiredWhen` (+ deprecated `conditionalRequired` alias), and `ObjectForm` carries them through from object metadata.
|
|
163
|
+
- 2d47e94: B2 follow-ups (A): field conditional rules in inline grids + submit-time enforcement.
|
|
164
|
+
|
|
165
|
+
- **Grids**: a line-item column's `readonlyWhen` / `requiredWhen` CEL rule is now honored per row — `deriveMasterDetail` carries the props onto the `GridColumn` and `GridField` evaluates them against each row via `resolveFieldRuleState` (a `readonlyWhen`-TRUE cell locks; a `requiredWhen`-TRUE empty cell flags inline-invalid). Rules are row-scoped (`record.*`); the core helpers gained an optional `scope` (and `GridField` a `contextRecord` prop) so a future header-driven lock can bind `parent.*` — that wiring is deferred (it needs the master-detail header's re-renders isolated).
|
|
166
|
+
- **Submit enforcement**: `requiredWhen` already drove react-hook-form's `required` rule, so submit is blocked with a field error when the predicate is TRUE and the value is empty. Added a reactive cleanup so a stale _required_ error clears when the predicate flips FALSE (and all errors clear when a field is hidden by `visibleWhen`).
|
|
167
|
+
|
|
168
|
+
- f6044fa: feat(form): subforms in DrawerForm + full-page record form (Tier 0 everywhere)
|
|
169
|
+
|
|
170
|
+
Completes config-driven master-detail across all standard create/edit entry
|
|
171
|
+
points (after the modal in the previous change):
|
|
172
|
+
|
|
173
|
+
- `DrawerForm` now hosts `MasterDetailForm` inside the drawer when the schema
|
|
174
|
+
declares `subforms` (its own Save bar; closes + refreshes on success).
|
|
175
|
+
- `RecordFormPage` (full-page New/Edit) sources `subforms` from the object's
|
|
176
|
+
form view, so the full-page form renders inline child collections too.
|
|
177
|
+
- `ObjectForm`'s subforms shortcut now defers to the drawer/modal variants for
|
|
178
|
+
those formTypes (so they keep their envelope), and only renders the
|
|
179
|
+
master-detail form directly for inline/simple forms.
|
|
180
|
+
|
|
181
|
+
Declaring `formViews.default.subforms: [{ childObject }]` now yields a
|
|
182
|
+
master-detail experience in the modal, drawer, AND full-page form — no bespoke
|
|
183
|
+
page anywhere.
|
|
184
|
+
|
|
185
|
+
- ad8ade6: feat(components): metadata-derived field locators on generated forms (ADR-0054 Phase 4)
|
|
186
|
+
|
|
187
|
+
The form renderer now emits a stable `data-testid="field:{objectName}.{field}"`
|
|
188
|
+
(plus `data-field`) on every field wrapper, derived from the form's `objectName`
|
|
189
|
+
and each field's name — closing the locator gap at the source so every generated
|
|
190
|
+
form (`ObjectForm`/`ModalForm`/`DrawerForm`/`SplitForm`/`WizardForm`) inherits
|
|
191
|
+
testable fields with zero per-app work (ADR-0054 C4). `FormSchema` gains an
|
|
192
|
+
optional `objectName`; the object prefix is omitted (`field:{field}`) when a form
|
|
193
|
+
has none. `FormItem` now accepts `data-*` attributes.
|
|
194
|
+
|
|
195
|
+
- 3870c20: feat(forms): declarative `navigateOnSuccess` + `resetOnSuccess` on object-form
|
|
196
|
+
|
|
197
|
+
Rounds out declarative success behavior for metadata-only forms (which can't
|
|
198
|
+
pass an `onSuccess` function), complementing `successMessage`:
|
|
199
|
+
|
|
200
|
+
- **`navigateOnSuccess`** — after a successful create/update, navigate here.
|
|
201
|
+
Supports `{id}`/`{recordId}` interpolation from the saved record and is
|
|
202
|
+
same-origin-guarded; takes precedence over the toast (landing on the record
|
|
203
|
+
is the confirmation).
|
|
204
|
+
- **`resetOnSuccess`** — after a successful create, reset the form for another
|
|
205
|
+
entry (the wizard returns to a cleared step 1). Ignored when navigating.
|
|
206
|
+
|
|
207
|
+
Wired in both ObjectForm and WizardForm via a small shared `successBehavior`
|
|
208
|
+
helper (kept dependency-free to avoid an EmbeddableForm import cycle).
|
|
209
|
+
|
|
210
|
+
- b88c560: feat(forms): declarative `successMessage` on object-form
|
|
211
|
+
|
|
212
|
+
Metadata-only forms (a wizard/object-form authored as JSON) cannot pass an
|
|
213
|
+
`onSuccess` function, so the post-create/update feedback was a fixed
|
|
214
|
+
"Created"/"Saved" toast. `ObjectFormSchema` now accepts `successMessage`, which
|
|
215
|
+
ObjectForm and WizardForm use for the default success toast when no `onSuccess`
|
|
216
|
+
handler is supplied. Falls back to "Created"/"Saved".
|
|
217
|
+
|
|
218
|
+
- 7913390: fix(master-detail): never silent on save — feedback, reset, and a duplicate-submit guard
|
|
219
|
+
|
|
220
|
+
`MasterDetailForm`'s "Create" submitted successfully but gave **no feedback**: no toast, no form reset, no navigation. A successful create looked broken, and re-clicking created duplicate records.
|
|
221
|
+
|
|
222
|
+
- On success: a `toast.success`, and on create the form clears (line items reset + parent `<ObjectForm>` remounts) ready for the next entry. A page-supplied `onSuccess` still runs afterwards (e.g. to navigate).
|
|
223
|
+
- On failure (validation / network / atomic rollback): a `toast.error` surfaces the message instead of failing silently.
|
|
224
|
+
- In-flight guard: the Create button shows "Saving…" and is disabled while a submit is running, preventing duplicate submissions, with a safety release if client-side validation blocks the submit.
|
|
225
|
+
- `@object-ui/components` now re-exports `toast` (alongside `Toaster`) from its sonner wrapper.
|
|
226
|
+
|
|
227
|
+
Tests: two new `MasterDetailForm` tests assert success → toast + form clear, and failure → error toast.
|
|
228
|
+
|
|
229
|
+
- 514f426: fix(master-detail): reliable submit + stable e2e hooks
|
|
230
|
+
|
|
231
|
+
Fixes the "click Create, nothing happens" report, surfaced by a new live browser
|
|
232
|
+
e2e harness that drives the form with real input.
|
|
233
|
+
|
|
234
|
+
- **MasterDetailForm `handleSave`** now triggers the button-less parent form's
|
|
235
|
+
submit from a deferred macrotask and re-queries the live `<form>` inside it.
|
|
236
|
+
Calling `requestSubmit()` synchronously inside the click handler (right after
|
|
237
|
+
the `setSaving` state update) intermittently dropped the nested submit event,
|
|
238
|
+
so react-hook-form's `onSubmit` never ran and the click appeared to do nothing
|
|
239
|
+
— only the occasional click got through. Deferring makes it fire every time.
|
|
240
|
+
- **Stable `data-testid`s** so automation/e2e can drive the widgets
|
|
241
|
+
deterministically (Radix Select + react-hook-form cannot be driven by
|
|
242
|
+
synthetic DOM events): `select-trigger-{field}` / `select-option-{value}`
|
|
243
|
+
(SelectField), `lookup-trigger-{field}` (LookupField), `line-items-add`
|
|
244
|
+
(GridField), `md-form-submit` / `md-form-cancel` (MasterDetailForm).
|
|
245
|
+
|
|
246
|
+
- 586a027: B2 follow-up (#1581): parent-scoped conditional rules in inline grids — "paid invoice → lock lines". `MasterDetailForm` now binds the live header record to every line-item grid as `parent`, so a column's `readonlyWhen` / `requiredWhen` CEL rule can react to the header (e.g. `parent.status == 'paid'` locks quantity / unit price / product when the invoice is paid). The line grids + document totals moved into a dedicated `<MasterDetailLines>` child that owns the scraped header record, so a header edit re-renders only the lines and never resets the header `ObjectForm`'s react-hook-form state mid-edit; the scrape is deduped by value to avoid needless churn. (`@object-ui/fields`' `GridField.contextRecord` and column-rule derivation already existed — this wires the last link.)
|
|
247
|
+
- 9aac2b8: feat(form): modal forms can host a tabbed layout (modal + tabbed composes)
|
|
248
|
+
|
|
249
|
+
`ModalForm` rendered sections as a flat vertical stack — a modal create/edit
|
|
250
|
+
form could never be tabbed, because `formType` (one field) couldn't be both
|
|
251
|
+
`modal` (container) and `tabbed` (layout). Per ADR-0050 (additive first), the
|
|
252
|
+
modal container now accepts a `contentLayout` ('simple' | 'tabbed'): when
|
|
253
|
+
`tabbed`, sections render as tabs inside the dialog. The console record
|
|
254
|
+
New/Edit modal (`AppContent`) forwards the default form view's layout, so a
|
|
255
|
+
`type:'tabbed'` form view now renders tabbed in the modal too — not just on the
|
|
256
|
+
full-page route (#1762). Non-breaking; `FormView.type` enum unchanged.
|
|
257
|
+
|
|
258
|
+
Refs objectstack-ai/framework#1890, ADR-0050
|
|
259
|
+
|
|
260
|
+
- 650bd1f: fix(forms/dashboard/related-list): four business-facing rendering fixes found while QA-ing a showcase workspace
|
|
261
|
+
|
|
262
|
+
- **plugin-form / WizardForm**: a multi-step `object-form` with `formType: 'wizard'` posted an empty/partial body on submit, so the server rejected every required field. Two causes: (1) the footer Next/Create buttons bypassed the inner form and submitted the wizard's own (never-collected) `formData`; (2) the create-mode data-seeding effect re-ran on `dataSource`/`objectSchema` identity churn and reset `formData` to `{}` mid-wizard. Now the buttons submit the inner form natively (`<form id>` + `type="submit"`, which validates each step and collects values via `getValues()`), and the create seed is made idempotent.
|
|
263
|
+
- **plugin-dashboard / DashboardRenderer**: chart widgets rendered as empty cards (recharts logged `width(-1) height(-1)`) because the positioned grid used `auto-rows-min`, collapsing any widget with no intrinsic height. The explicit-columns grid now uses `gridAutoRows: minmax(5rem, auto)` so spanned chart rows get a real height while tables can still grow.
|
|
264
|
+
- **plugin-detail / RelatedList**: auto-derived related-list columns led with system audit fields (`created_at`, `updated_at`, …) for child objects without a name/title field, pushing business columns past the column cap. System audit fields are now sorted last.
|
|
265
|
+
- **plugin-form / ObjectForm + WizardForm**: a successful create/update gave no feedback for metadata-only pages (which can't pass an `onSuccess` function). They now show a default `toast.success('Created'/'Saved')` when no `onSuccess` handler is supplied (guarded so a `submitHandler` host like MasterDetailForm never double-toasts).
|
|
266
|
+
|
|
267
|
+
- Updated dependencies [5976ba3]
|
|
268
|
+
- Updated dependencies [a00e16d]
|
|
269
|
+
- Updated dependencies [eaccefd]
|
|
270
|
+
- Updated dependencies [f7f325d]
|
|
271
|
+
- Updated dependencies [c12986e]
|
|
272
|
+
- Updated dependencies [71d7ce0]
|
|
273
|
+
- Updated dependencies [053c948]
|
|
274
|
+
- Updated dependencies [ddbe4a2]
|
|
275
|
+
- Updated dependencies [2d47e94]
|
|
276
|
+
- Updated dependencies [9049bbe]
|
|
277
|
+
- Updated dependencies [6c0c92c]
|
|
278
|
+
- Updated dependencies [cb2fdb1]
|
|
279
|
+
- Updated dependencies [c3749eb]
|
|
280
|
+
- Updated dependencies [6cfa330]
|
|
281
|
+
- Updated dependencies [ad8ade6]
|
|
282
|
+
- Updated dependencies [d54346c]
|
|
283
|
+
- Updated dependencies [5332639]
|
|
284
|
+
- Updated dependencies [3870c20]
|
|
285
|
+
- Updated dependencies [2eb3096]
|
|
286
|
+
- Updated dependencies [b88c560]
|
|
287
|
+
- Updated dependencies [bd398df]
|
|
288
|
+
- Updated dependencies [66ed3ad]
|
|
289
|
+
- Updated dependencies [c6445b6]
|
|
290
|
+
- Updated dependencies [80c133c]
|
|
291
|
+
- Updated dependencies [5e1b838]
|
|
292
|
+
- Updated dependencies [d16566f]
|
|
293
|
+
- Updated dependencies [90acb7f]
|
|
294
|
+
- Updated dependencies [7913390]
|
|
295
|
+
- Updated dependencies [514f426]
|
|
296
|
+
- Updated dependencies [1394e34]
|
|
297
|
+
- Updated dependencies [e95cc25]
|
|
298
|
+
- Updated dependencies [abe8ebc]
|
|
299
|
+
- Updated dependencies [300d755]
|
|
300
|
+
- Updated dependencies [bd8b054]
|
|
301
|
+
- Updated dependencies [4eb9cb6]
|
|
302
|
+
- Updated dependencies [7c239fd]
|
|
303
|
+
- Updated dependencies [858ad94]
|
|
304
|
+
- Updated dependencies [2270239]
|
|
305
|
+
- Updated dependencies [db8cd00]
|
|
306
|
+
- Updated dependencies [18728c1]
|
|
307
|
+
- Updated dependencies [8d1195d]
|
|
308
|
+
- @object-ui/core@7.0.0
|
|
309
|
+
- @object-ui/components@7.0.0
|
|
310
|
+
- @object-ui/react@7.0.0
|
|
311
|
+
- @object-ui/types@7.0.0
|
|
312
|
+
- @object-ui/fields@7.0.0
|
|
313
|
+
- @object-ui/permissions@7.0.0
|
|
314
|
+
|
|
315
|
+
## 6.2.3
|
|
316
|
+
|
|
317
|
+
### Patch Changes
|
|
318
|
+
|
|
319
|
+
- @object-ui/types@6.2.3
|
|
320
|
+
- @object-ui/core@6.2.3
|
|
321
|
+
- @object-ui/react@6.2.3
|
|
322
|
+
- @object-ui/components@6.2.3
|
|
323
|
+
- @object-ui/fields@6.2.3
|
|
324
|
+
- @object-ui/permissions@6.2.3
|
|
325
|
+
|
|
3
326
|
## 6.2.2
|
|
4
327
|
|
|
5
328
|
### Patch Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { FormField, DataSource } from '
|
|
2
|
+
import { FormField, DataSource } from '@object-ui/types';
|
|
3
3
|
export interface DrawerFormSectionConfig {
|
|
4
4
|
name?: string;
|
|
5
5
|
label?: string;
|
|
@@ -53,6 +53,19 @@ export interface DrawerFormSchema {
|
|
|
53
53
|
onError?: (error: Error) => void;
|
|
54
54
|
onCancel?: () => void;
|
|
55
55
|
className?: string;
|
|
56
|
+
/** Inline child collections — renders the drawer as an atomic master-detail
|
|
57
|
+
* form. Each entry needs only `childObject` (FK + columns derived). */
|
|
58
|
+
subforms?: Array<{
|
|
59
|
+
childObject: string;
|
|
60
|
+
relationshipField?: string;
|
|
61
|
+
columns?: any[];
|
|
62
|
+
amountField?: string;
|
|
63
|
+
totalField?: string;
|
|
64
|
+
title?: string;
|
|
65
|
+
addLabel?: string;
|
|
66
|
+
minRows?: number;
|
|
67
|
+
maxRows?: number;
|
|
68
|
+
}>;
|
|
56
69
|
}
|
|
57
70
|
export interface DrawerFormProps {
|
|
58
71
|
schema: DrawerFormSchema;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { GridColumn } from '@object-ui/fields';
|
|
3
|
+
export interface LineItemsPanelSchema {
|
|
4
|
+
type?: 'record:line_items';
|
|
5
|
+
childObject: string;
|
|
6
|
+
relationshipField: string;
|
|
7
|
+
columns: GridColumn[];
|
|
8
|
+
parentObject?: string;
|
|
9
|
+
parentId?: string;
|
|
10
|
+
recordId?: string;
|
|
11
|
+
amountField?: string;
|
|
12
|
+
totalField?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
minRows?: number;
|
|
16
|
+
maxRows?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const LineItemsPanel: React.FC<{
|
|
19
|
+
schema: LineItemsPanelSchema;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { DataSource } from '@object-ui/types';
|
|
3
|
+
import { GridColumn } from '@object-ui/fields';
|
|
4
|
+
import { InlineMode } from './deriveMasterDetail';
|
|
5
|
+
export interface MasterDetailDetailConfig {
|
|
6
|
+
/** Child object name, e.g. 'expense_line'. */
|
|
7
|
+
childObject: string;
|
|
8
|
+
/** FK field on the child pointing back to the parent, e.g. 'expense_claim'.
|
|
9
|
+
* Optional — auto-detected from the child's master_detail/lookup field that
|
|
10
|
+
* references the parent object when omitted. */
|
|
11
|
+
relationshipField?: string;
|
|
12
|
+
/** Editable columns for the child grid. Optional — derived from the child
|
|
13
|
+
* object's fields (via DataSource.getObjectSchema) when omitted. */
|
|
14
|
+
columns?: GridColumn[];
|
|
15
|
+
/** Field names for the per-row expand form. Optional — derived from the child
|
|
16
|
+
* object's fields (broader than `columns`: includes rich types) when omitted. */
|
|
17
|
+
formFields?: string[];
|
|
18
|
+
/** Inline-edit form factor: 'grid' = editable cells; 'form' = read-only list +
|
|
19
|
+
* per-row full form. Optional — resolved from the relationship's `inlineEdit`
|
|
20
|
+
* (incl. the smart default) when omitted. */
|
|
21
|
+
inlineMode?: InlineMode;
|
|
22
|
+
/** Numeric child column to sum, e.g. 'amount'. */
|
|
23
|
+
amountField?: string;
|
|
24
|
+
/** Child field holding the line sort position — stamped on drag-reorder so
|
|
25
|
+
* order persists. Auto-derived from a `position`/`sort_order`/… field. */
|
|
26
|
+
sortField?: string;
|
|
27
|
+
/** Parent field to receive the rolled-up sum, e.g. 'total_amount'. */
|
|
28
|
+
totalField?: string;
|
|
29
|
+
/** Section title. */
|
|
30
|
+
title?: string;
|
|
31
|
+
minRows?: number;
|
|
32
|
+
maxRows?: number;
|
|
33
|
+
addLabel?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface MasterDetailFormSchema {
|
|
36
|
+
type?: 'object-master-detail-form';
|
|
37
|
+
/** Parent object name, e.g. 'expense_claim'. */
|
|
38
|
+
objectName: string;
|
|
39
|
+
mode?: 'create' | 'edit';
|
|
40
|
+
recordId?: string;
|
|
41
|
+
/** Prefilled parent header values (create mode) — seeds the parent form's
|
|
42
|
+
* initial values, e.g. a conversion wizard carrying the lead/account over. */
|
|
43
|
+
initialValues?: Record<string, any>;
|
|
44
|
+
initialData?: Record<string, any>;
|
|
45
|
+
/** Parent form sections/fields — passed straight through to ObjectForm. */
|
|
46
|
+
sections?: any[];
|
|
47
|
+
fields?: any[];
|
|
48
|
+
formType?: 'simple' | 'tabbed';
|
|
49
|
+
title?: string;
|
|
50
|
+
submitText?: string;
|
|
51
|
+
/** One or more child collections. */
|
|
52
|
+
details: MasterDetailDetailConfig[];
|
|
53
|
+
/** Parent header field holding a tax rate (percent). When the parent form has
|
|
54
|
+
* this field, a live Subtotal / Tax / Total stack renders under the lines.
|
|
55
|
+
* Defaults to `tax_rate`; the stack only appears if the field is present. */
|
|
56
|
+
taxRateField?: string;
|
|
57
|
+
onSuccess?: (parent: any) => void | Promise<void>;
|
|
58
|
+
onError?: (err: Error) => void;
|
|
59
|
+
onCancel?: () => void;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface MasterDetailFormProps {
|
|
63
|
+
schema: MasterDetailFormSchema;
|
|
64
|
+
dataSource?: DataSource;
|
|
65
|
+
className?: string;
|
|
66
|
+
}
|
|
67
|
+
export declare const MasterDetailForm: React.FC<MasterDetailFormProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { FormField, DataSource } from '
|
|
2
|
+
import { FormField, DataSource } from '@object-ui/types';
|
|
3
3
|
export interface ModalFormSectionConfig {
|
|
4
4
|
name?: string;
|
|
5
5
|
label?: string;
|
|
@@ -16,6 +16,9 @@ export interface ModalFormSchema {
|
|
|
16
16
|
title?: string;
|
|
17
17
|
description?: string;
|
|
18
18
|
sections?: ModalFormSectionConfig[];
|
|
19
|
+
/** Internal content layout (ADR-0050, #1890): 'tabbed' renders sections as
|
|
20
|
+
* tabs inside the modal, so "modal + tabbed" composes. Default stacks them. */
|
|
21
|
+
contentLayout?: 'simple' | 'tabbed';
|
|
19
22
|
fields?: string[];
|
|
20
23
|
customFields?: FormField[];
|
|
21
24
|
/**
|
|
@@ -50,6 +53,19 @@ export interface ModalFormSchema {
|
|
|
50
53
|
onError?: (error: Error) => void;
|
|
51
54
|
onCancel?: () => void;
|
|
52
55
|
className?: string;
|
|
56
|
+
/** Inline child collections — renders the modal as an atomic master-detail
|
|
57
|
+
* form. Each entry needs only `childObject` (FK + columns derived). */
|
|
58
|
+
subforms?: Array<{
|
|
59
|
+
childObject: string;
|
|
60
|
+
relationshipField?: string;
|
|
61
|
+
columns?: any[];
|
|
62
|
+
amountField?: string;
|
|
63
|
+
totalField?: string;
|
|
64
|
+
title?: string;
|
|
65
|
+
addLabel?: string;
|
|
66
|
+
minRows?: number;
|
|
67
|
+
maxRows?: number;
|
|
68
|
+
}>;
|
|
53
69
|
}
|
|
54
70
|
export interface ModalFormProps {
|
|
55
71
|
schema: ModalFormSchema;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { DataSource } from '
|
|
2
|
+
import { DataSource } from '@object-ui/types';
|
|
3
3
|
import { FormSectionConfig } from './TabbedForm';
|
|
4
4
|
export interface WizardFormSchema {
|
|
5
5
|
type: 'object-form';
|
|
@@ -44,6 +44,18 @@ export interface WizardFormSchema {
|
|
|
44
44
|
* Submit button text (shown on last step)
|
|
45
45
|
*/
|
|
46
46
|
submitText?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Declarative success toast text shown when no `onSuccess` handler is given
|
|
49
|
+
* (metadata pages cannot pass a function). Falls back to 'Created'/'Saved'.
|
|
50
|
+
*/
|
|
51
|
+
successMessage?: string;
|
|
52
|
+
/** Navigate here after a successful create/update (declarative; metadata
|
|
53
|
+
* pages can't pass onSuccess). Supports `{id}`/`{recordId}` interpolation
|
|
54
|
+
* from the saved record. Same-origin-guarded. Takes precedence over the toast. */
|
|
55
|
+
navigateOnSuccess?: string;
|
|
56
|
+
/** Reset the wizard (back to step 1, cleared) after a successful create so the
|
|
57
|
+
* user can enter another. Ignored when `navigateOnSuccess` is set. */
|
|
58
|
+
resetOnSuccess?: boolean;
|
|
47
59
|
/**
|
|
48
60
|
* Show cancel button
|
|
49
61
|
* @default true
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { GridColumn } from '@object-ui/fields';
|
|
2
|
+
/** Minimal shape of an object schema as returned by `DataSource.getObjectSchema`. */
|
|
3
|
+
export interface ObjectSchemaLike {
|
|
4
|
+
name?: string;
|
|
5
|
+
fields?: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
/** Map an ObjectQL field type to a LineItems grid column type. */
|
|
8
|
+
export declare function fieldTypeToColumnType(type: string | undefined): GridColumn['type'];
|
|
9
|
+
/**
|
|
10
|
+
* Find the field on the child object that points back to the parent — the
|
|
11
|
+
* master_detail/lookup field whose `reference` is the parent object. Prefer
|
|
12
|
+
* `master_detail` over `lookup` when both exist.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findRelationshipField(childSchema: ObjectSchemaLike | undefined, parentObjectName: string): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Default-visible column budget for an auto-derived inline grid. An inline
|
|
17
|
+
* line-item grid lives in a constrained width (modal / detail card), so we show
|
|
18
|
+
* a focused set by default and mark the rest `defaultHidden` — they are NOT
|
|
19
|
+
* dropped: the grid's column chooser reveals them on demand (the mainstream
|
|
20
|
+
* "personalize columns" pattern; cf. Odoo `optional` / Salesforce column
|
|
21
|
+
* personalization). Required columns are always visible. Authors can override
|
|
22
|
+
* with explicit `columns` / `inlineColumns` (no curation), or `maxColumns: 0`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_MAX_INLINE_COLUMNS = 6;
|
|
25
|
+
/**
|
|
26
|
+
* Derive editable grid columns from a child object's fields, skipping system /
|
|
27
|
+
* audit fields, non-editable types, and the back-reference FK to the parent.
|
|
28
|
+
* Every editable column is returned; those beyond {@link DEFAULT_MAX_INLINE_COLUMNS}
|
|
29
|
+
* are flagged `defaultHidden` (collapsed into the grid's column chooser, not
|
|
30
|
+
* dropped). Pass `maxColumns: 0` to flag none.
|
|
31
|
+
*/
|
|
32
|
+
export declare function deriveColumns(childSchema: ObjectSchemaLike | undefined, opts?: {
|
|
33
|
+
relationshipField?: string;
|
|
34
|
+
exclude?: string[];
|
|
35
|
+
maxColumns?: number;
|
|
36
|
+
}): GridColumn[];
|
|
37
|
+
/**
|
|
38
|
+
* Field names for a child's full "row form" (the per-row expand editor) — every
|
|
39
|
+
* editable business field, skipping system/audit fields, the back-reference FK,
|
|
40
|
+
* and computed types. Broader than {@link deriveColumns} (which only returns
|
|
41
|
+
* grid-friendly types): the form has room for textarea/richtext/file/etc.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deriveFormFields(childSchema: ObjectSchemaLike | undefined, opts?: {
|
|
44
|
+
relationshipField?: string;
|
|
45
|
+
exclude?: string[];
|
|
46
|
+
}): string[];
|
|
47
|
+
/** Inline-edit form factor. */
|
|
48
|
+
export type InlineMode = 'grid' | 'form';
|
|
49
|
+
/** Above this many editable business fields, the grid gets cramped → `form`. */
|
|
50
|
+
export declare const SMART_FORM_FIELD_THRESHOLD = 8;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the inline-edit form factor for a child collection.
|
|
53
|
+
* - explicit `'grid'` / `'form'` win;
|
|
54
|
+
* - otherwise (`true` / undefined) pick by the child's shape: a `form` when it
|
|
55
|
+
* has rich/form-only fields or more than {@link SMART_FORM_FIELD_THRESHOLD}
|
|
56
|
+
* editable business fields, else a `grid`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveInlineMode(childSchema: ObjectSchemaLike | undefined, inlineEdit: boolean | InlineMode | undefined, opts?: {
|
|
59
|
+
relationshipField?: string;
|
|
60
|
+
}): InlineMode;
|
|
61
|
+
export interface DerivedDetail {
|
|
62
|
+
childObject: string;
|
|
63
|
+
relationshipField: string;
|
|
64
|
+
columns: GridColumn[];
|
|
65
|
+
/** Field names for the per-row expand form (broader than `columns`). */
|
|
66
|
+
formFields: string[];
|
|
67
|
+
/** Inline-edit form factor (grid = editable cells; form = list + per-row form). */
|
|
68
|
+
mode: InlineMode;
|
|
69
|
+
/** First numeric column, used as the running-total source when none is set. */
|
|
70
|
+
amountField?: string;
|
|
71
|
+
/** Child field holding the line sort position, if any — the grid stamps it on
|
|
72
|
+
* drag-reorder so order persists (excluded from the editable columns). */
|
|
73
|
+
sortField?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a child collection's full config (FK + columns) from its object
|
|
77
|
+
* schema. Throws when no relationship to the parent can be found. The caller
|
|
78
|
+
* supplies any explicit overrides (relationshipField / columns / amountField),
|
|
79
|
+
* which win over the derived values.
|
|
80
|
+
*/
|
|
81
|
+
export declare function deriveDetail(childObject: string, childSchema: ObjectSchemaLike | undefined, parentObjectName: string, override?: {
|
|
82
|
+
relationshipField?: string;
|
|
83
|
+
columns?: GridColumn[];
|
|
84
|
+
amountField?: string;
|
|
85
|
+
inlineEdit?: boolean | InlineMode;
|
|
86
|
+
}): DerivedDetail;
|