@object-ui/plugin-form 11.3.0 → 11.5.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 CHANGED
@@ -1,5 +1,152 @@
1
1
  # @object-ui/plugin-form
2
2
 
3
+ ## 11.5.0
4
+
5
+ ### Patch Changes
6
+
7
+ - fae75e2: Fix two bugs verified still-present after #2254 claimed to resolve them (framework#2620 / framework#2616 Showcase UX pass, tracked in #2268):
8
+
9
+ - **Wizard/form `submitBehavior: 'thank-you'` allowed duplicate resubmission.** #2254 fixed the spec-bridge dropping `submitBehavior` before it reached the renderer, so the configured toast message started appearing — but `WizardForm`'s last step and `ObjectForm`'s submit handler only ever called `toast.success(...)` for `thank-you`/`next-record`; the form stayed mounted and fully filled with its submit button re-enabled once the request settled, so a second click created a second record. Both components now track a terminal `submitted` state and, when set, replace the form with a confirmation panel (using the behavior's `title`/`message`, which were also never read before) — mirroring the pattern `apps/console/src/components/FormPage.tsx` already used for its own standalone forms.
10
+
11
+ - **Command Center-style 3-up chart bands stayed collapsed to ~100-130px, and a dataset-bound chart's measure leaked its raw field name.**
12
+ - `responsiveStyles` (and `style`) were declared on the page-spec `PageComponent` bridge input type but never copied onto the `SchemaNode` in `spec-bridge/bridges/page.ts::mapComponent()` — so a page author's ADR-0065 layout override (e.g. forcing `display: 'grid'` on a `type: 'flex'` band) never reached `SchemaRenderer`, and the node silently fell back to its default flex layout. Both fields are now mapped through.
13
+ - `ObjectChart`'s dataset-bound fetch path (`schema.dataset` + `ds.queryDataset(...)`) discarded the response's `fields` array (which carries each measure's `label`, e.g. `{ name: 'task_count', label: 'Tasks' }`) before it ever reached `buildChartSeries()` — whose `fields` param already resolves this correctly (see `chart-series.test.ts`) — so the legend/tooltip always fell back to the raw field name. The fetched `fields` are now captured and threaded through.
14
+
15
+ - ec9c8ee: Fix master-detail record create: stop double success toast + localize the Cancel button.
16
+
17
+ Objects with inline subforms (master-detail, e.g. a Lead with product line items)
18
+ render `MasterDetailForm` inside `ModalForm`/`DrawerForm` instead of the plain
19
+ footer, which exposed two mismatches with the host contract:
20
+
21
+ - **Double success toast.** Flat `ObjectForm` delegates confirmation to the host
22
+ when an `onSuccess` is supplied (skips its own default toast), but
23
+ `MasterDetailForm.handleSaved` ALWAYS toasted `Created`/`Saved` AND ran
24
+ `onSuccess`. In the console the host's `onSuccess` chains into the `crud_success`
25
+ handler, which toasts a localized message — so create fired both `Created` and
26
+ e.g. `线索创建成功`. `handleSaved` now only toasts as a fallback when no host
27
+ `onSuccess` is provided, matching the `ObjectForm` contract; saves without a host
28
+ handler stay non-silent.
29
+
30
+ - **Hardcoded English `Cancel`.** The master-detail action bar wrote `Cancel` as a
31
+ literal and accepted no `cancelText`, so the button stayed English while the
32
+ submit button was localized (`submitText` was already forwarded).
33
+ `MasterDetailForm` now takes `cancelText`, and `ModalForm`/`DrawerForm`/`ObjectForm`
34
+ forward the host's localized label down the subforms branch.
35
+
36
+ Adds regression tests: create with a host `onSuccess` fires no built-in toast (no
37
+ double-confirm), and the Cancel button renders the host-supplied `cancelText`.
38
+
39
+ - 6c1ad9e: Record task flows open as derived overlays with lossless return (framework#2604, extends framework#2578).
40
+
41
+ - **Create/Edit never route** — the global record form is URL-driven (`?form=new` / `?form=<id>`): browser Back closes the overlay with the origin (list scroll/filters, detail state) intact; field-heavy objects derive a full-screen modal (`modalSize:'full'`) via the new `deriveRecordFlowSurface` mirror in plugin-view, light ones keep the auto-sized modal. `editMode:'page'` opt-in unchanged.
42
+ - **Save invariant** — _edit never moves you_ (origin refetches in place); _create lands on the new record's detail_ on its derived surface (drawer over the still-intact list for light objects, detail route for heavy), with `replace:true` so Back skips the transient form entry.
43
+ - **Subtable child create/edit = overlay over the parent detail, never a route** — related-list New/Edit push `?form=…&formObject=<child>&formLink=<fk>:<parentId>`; the one global overlay pre-links the parent (refresh-safe), sizes to the CHILD object, and on save stays on the parent while only the child's related lists refetch. ModalForm now forwards `initialValues` into its master-detail (subforms) branch so pre-links survive for children with inline line items.
44
+
45
+ - Updated dependencies [544d8eb]
46
+ - Updated dependencies [6fffd3d]
47
+ - Updated dependencies [9255686]
48
+ - Updated dependencies [fae75e2]
49
+ - Updated dependencies [1072701]
50
+ - @object-ui/i18n@11.5.0
51
+ - @object-ui/react@11.5.0
52
+ - @object-ui/components@11.5.0
53
+ - @object-ui/types@11.5.0
54
+ - @object-ui/fields@11.5.0
55
+ - @object-ui/core@11.5.0
56
+ - @object-ui/permissions@11.5.0
57
+
58
+ ## 11.4.0
59
+
60
+ ### Minor Changes
61
+
62
+ - 8bf6295: feat: adaptive record surface + semantic field span + responsive columns (framework#2578)
63
+
64
+ Field-heavy objects (all metadata is AI-authored) now present themselves without
65
+ any authored presentation config:
66
+
67
+ - **Adaptive surface** — a record's create/edit/detail opens as a full page when
68
+ the object is field-heavy, or a drawer when it is light. Derived from field
69
+ count (`deriveRecordSurface`), not authored; mobile always pages. Wired into the
70
+ app-shell ObjectView detail navigation (an authored view/object `navigation`
71
+ still wins).
72
+ - **Semantic field span** — `FormField.span` (`auto`/`full`) is a width primitive
73
+ decoupled from the (per-surface derived) column count; legacy `colSpan` is
74
+ clamped so it never overflows. `ObjectForm` now honours per-section `columns`
75
+ and carries `span`/`colSpan` from section defs — fixes the bug where
76
+ `type:'simple'` ignored `section.columns` and grouped fields rendered single
77
+ column.
78
+ - **Responsive columns** — `inferColumns` scales the column CAP with field count
79
+ (≤3→1, ≤8→2, ≤15→3, 16+→4); the ACTUAL column count follows the form's real
80
+ width via CSS container queries, so the same form goes 1→2→3→4 columns as a
81
+ drawer widens or becomes a page.
82
+ - **Runtime overlay width** — `NavigationConfig.size` bucket is resolved to a
83
+ viewport-clamped width at runtime (`overlayWidthFor`); a pixel width is never
84
+ authored (the author cannot know the client viewport).
85
+
86
+ - 144ab55: Consume the ADR-0085 object semantic roles from `@objectstack/spec@11.7.0`, retiring the per-surface hint dialects:
87
+
88
+ - **Single-source fieldGroups derivation**: `plugin-form`'s `deriveFieldGroupSections` and `plugin-detail`'s `deriveFieldGroupDetailSections` are now thin adapters over the spec's `deriveFieldGroupLayout` (ADR-0085 §5) — forms, modals and detail pages render the SAME grouping from one implementation. The canonical `collapse: 'none' | 'expanded' | 'collapsed'` enum is honoured everywhere (deprecated `collapsible`/`collapsed` and `defaultExpanded` spellings still read for pre-11.7 metadata).
89
+ - **`stageField` semantic role**: the detail stepper reads the top-level `stageField`; `stageField: false` now actually suppresses stage detection (previously the `false` handling was wired to the removed `detail.stageField` key, so spec-authored `false` fell through to the name heuristic).
90
+ - **`highlightFields` rename**: default grid columns, card compact views, the detail highlight strip, child-record preview fields and interface-page default columns read the object's `highlightFields` (deprecated `compactLayout` spelling read as fallback for pre-11.7 metadata).
91
+ - **Removed dead reads**: the never-spec-writable `objectDef.views.*` UI hints and the ADR-0085-removed `detail.*` block (`sections`, `sectionGroups`, `highlightFields`, `stageField`, `useFieldGroups`, `showReferenceRail`, `hideReferenceRail`, `hideRelatedTab`, `relatedLayout`) are no longer consulted. Per-page customization goes through an assigned Page schema (`record:reference_rail` remains available there as a renderer capability). `detail.renderViaSchema` survives only as the legacy-renderer kill-switch and is removed together with that path.
92
+
93
+ ### Patch Changes
94
+
95
+ - c38d107: Fix view-level `FormField.visibleOn` (CEL) never taking effect (#2212).
96
+
97
+ The spec ships `visibleOn` as an Expression object `{ dialect: 'cel', source }`
98
+ (what the `P` template emits) or a bare string, but the whole chain dropped it:
99
+
100
+ - `sectionFields.ts` / `ObjectForm.tsx` only accepted the bare-string shape and
101
+ attached a dead `visible()` closure no renderer ever called — the Expression
102
+ object shape was silently discarded.
103
+ - The form renderer destructured `visibleOn` out of the field config and never
104
+ evaluated it.
105
+ - `RecordFormPage` dropped a `simple` form view's `sections` entirely, so
106
+ page-mode create/edit fell back to the raw schema (every field, no authored
107
+ selection/grouping) while the modal path honored the same view.
108
+ - `ObjectForm`'s grouped-sections path matched section fields by name only,
109
+ dropping per-field `visibleOn` overrides.
110
+
111
+ `visibleOn` now flows through normalization verbatim (both wire shapes) and is
112
+ evaluated reactively by the form renderer with the canonical expression engine
113
+ (`evalFieldPredicate` — same engine, record scope, and fail-open semantics as
114
+ field-level `visibleWhen`; both predicates must allow a field for it to show).
115
+ Sectioned/flat normalization also copies field-level `visibleWhen` /
116
+ `readonlyWhen` / `requiredWhen` rules it previously lost.
117
+
118
+ - 1e9145d: Hydrate widget types on hand-authored master-detail subform columns. A view can
119
+ list a child grid's columns as bare `{ field, label }` (the common authoring
120
+ form); previously such untyped columns were passed straight to the grid, so a
121
+ `select` / `lookup` / `date` / `number` field silently rendered as a plain text
122
+ cell. `MasterDetailForm` (and `deriveDetail`) now resolve each untyped column's
123
+ `type` (plus `options` / `reference` / computed `expr`) from the child object's
124
+ schema via the new `hydrateColumns` helper — a picklist becomes a dropdown, a
125
+ lookup a record picker, a date a date input — while preserving the author's
126
+ exact column set, order and labels. Columns that already declare a `type` are
127
+ left untouched (the author's explicit choice still wins).
128
+ - Updated dependencies [8bf6295]
129
+ - Updated dependencies [1948c5b]
130
+ - Updated dependencies [bce581a]
131
+ - Updated dependencies [9cd9be1]
132
+ - Updated dependencies [5160832]
133
+ - Updated dependencies [69d6b94]
134
+ - Updated dependencies [c38d107]
135
+ - Updated dependencies [243a9ba]
136
+ - Updated dependencies [289be5b]
137
+ - Updated dependencies [7782698]
138
+ - Updated dependencies [19f2533]
139
+ - Updated dependencies [790558b]
140
+ - Updated dependencies [09e1b26]
141
+ - Updated dependencies [e84d64d]
142
+ - @object-ui/types@11.4.0
143
+ - @object-ui/components@11.4.0
144
+ - @object-ui/fields@11.4.0
145
+ - @object-ui/i18n@11.4.0
146
+ - @object-ui/core@11.4.0
147
+ - @object-ui/permissions@11.4.0
148
+ - @object-ui/react@11.4.0
149
+
3
150
  ## 11.3.0
4
151
 
5
152
  ### Patch Changes
@@ -48,6 +48,9 @@ export interface MasterDetailFormSchema {
48
48
  formType?: 'simple' | 'tabbed';
49
49
  title?: string;
50
50
  submitText?: string;
51
+ /** Label for the Cancel button in the action bar. i18n is the host's job
52
+ * (this plugin is locale-agnostic); defaults to English 'Cancel'. */
53
+ cancelText?: string;
51
54
  /** Hide the bottom Save/Cancel action bar — e.g. a non-persisting design
52
55
  * preview. Defaults to shown (the form owns the only Save in this layout). */
53
56
  showSubmit?: boolean;
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { DataSource } from '@object-ui/types';
3
+ import { SubmitBehavior } from './successBehavior';
3
4
  import { FormSectionConfig } from './TabbedForm';
4
5
  export interface WizardFormSchema {
5
6
  type: 'object-form';
@@ -47,15 +48,24 @@ export interface WizardFormSchema {
47
48
  /**
48
49
  * Declarative success toast text shown when no `onSuccess` handler is given
49
50
  * (metadata pages cannot pass a function). Falls back to 'Created'/'Saved'.
51
+ * Ignored when `submitBehavior` is set.
50
52
  */
51
53
  successMessage?: string;
52
54
  /** Navigate here after a successful create/update (declarative; metadata
53
55
  * pages can't pass onSuccess). Supports `{id}`/`{recordId}` interpolation
54
- * from the saved record. Same-origin-guarded. Takes precedence over the toast. */
56
+ * from the saved record. Same-origin-guarded. Takes precedence over the toast.
57
+ * Ignored when `submitBehavior` is set. */
55
58
  navigateOnSuccess?: string;
56
59
  /** Reset the wizard (back to step 1, cleared) after a successful create so the
57
- * user can enter another. Ignored when `navigateOnSuccess` is set. */
60
+ * user can enter another. Ignored when `navigateOnSuccess` or `submitBehavior`
61
+ * is set. */
58
62
  resetOnSuccess?: boolean;
63
+ /**
64
+ * Declarative post-submit behavior aligned with `@objectstack/spec`'s
65
+ * `FormView.submitBehavior`. When present, takes precedence over
66
+ * `successMessage` / `navigateOnSuccess` / `resetOnSuccess`.
67
+ */
68
+ submitBehavior?: SubmitBehavior;
59
69
  /**
60
70
  * Show cancel button
61
71
  * @default true
@@ -8,21 +8,49 @@ export declare function isWideFieldType(type: string): boolean;
8
8
  */
9
9
  export declare function isAutoGeneratedFieldType(type: string): boolean;
10
10
  /**
11
- * Infer optimal number of columns based on the number of visible fields
12
- * and whether any wide fields are present.
11
+ * Infer the MAX column count from the field count (#2578). This is an UPPER
12
+ * BOUND — the actual column count at render time is decided by the CSS
13
+ * container queries in {@link CONTAINER_GRID_COLS} against the form's REAL
14
+ * width, so a narrow drawer stays 1–2 columns while the SAME form on a wide
15
+ * page spreads out to this cap. The cap scales with field count so a light
16
+ * form is never thinned across too many columns, while a field-heavy record
17
+ * can use the full width.
13
18
  *
14
- * Rules:
15
- * - 0-3 fields → 1 column
16
- * - 4+ fields → 2 columns
19
+ * Rules (upper bound; container queries clamp to the actual width):
20
+ * - 0–3 fields → 1 column
21
+ * - 4–8 fields → 2 columns
22
+ * - 9–15 fields → 3 columns
23
+ * - 16+ fields → 4 columns
17
24
  */
18
25
  export declare function inferColumns(fieldCount: number): number;
19
26
  /**
20
- * Apply colSpan to wide fields so they span the full row.
21
- * Only sets colSpan if the field does not already have one explicitly set.
27
+ * Resolve the effective colSpan (grid cells) a field occupies, given the form's
28
+ * grid width and the section's desired column count (#2578).
22
29
  *
30
+ * The column count is derived per surface (mobile 1 / modal 2 / page 3-4), so
31
+ * the robust field-width primitive is the relative `span`, NOT an absolute
32
+ * colSpan. Precedence:
33
+ * 1. `span: 'full'` → whole row (grid cells).
34
+ * 2. legacy `colSpan` → honoured but CLAMPED to the grid (never overflows).
35
+ * 3. wide widget type → whole row (textarea/markdown/… — the `span:'auto'` default).
36
+ * 4. section density → grid / sectionColumns, so `sectionColumns` fields fill a row.
37
+ *
38
+ * `sectionColumns` defaults to the grid width, i.e. one field per cell (the flat
39
+ * form's historical behaviour) when there is no narrower section.
40
+ */
41
+ export declare function resolveColSpan(field: FormField, gridColumns: number, sectionColumns?: number): number;
42
+ /**
43
+ * Apply the resolved colSpan to each field so the form's CSS grid lays them out
44
+ * at the intended density. Span-aware and clamping (#2578); wide fields still
45
+ * span the full row (the `span: 'auto'` default). `section-divider` rows are
46
+ * left untouched (they carry their own full-row span).
47
+ *
48
+ * @param gridColumns The form's grid column count (1-4).
49
+ * @param sectionColumns The desired columns for THIS group of fields (defaults
50
+ * to the grid width = one field per cell).
23
51
  * @returns A new array of fields with colSpan applied where needed.
24
52
  */
25
- export declare function applyAutoColSpan(fields: FormField[], columns: number): FormField[];
53
+ export declare function applyAutoColSpan(fields: FormField[], gridColumns: number, sectionColumns?: number): FormField[];
26
54
  /**
27
55
  * Filter out auto-managed system fields (id, audit timestamps, tenant/owner ids).
28
56
  * Applied in BOTH create and edit modes, because these fields are always
@@ -54,6 +82,37 @@ export declare function filterCreateModeFields(fields: FormField[], objectSchema
54
82
  * their current computed values rendered as readonly.
55
83
  */
56
84
  export declare function filterAutoGeneratedFields(fields: FormField[], objectSchema?: any): FormField[];
85
+ /**
86
+ * Container-query-based grid classes for laying out form FIELDS inside a
87
+ * section. Uses `@container` / `@md:` / `@2xl:` / `@4xl:` variants so the grid
88
+ * responds to the form's actual width (modal / drawer / split panel) instead of
89
+ * the viewport — a narrow modal stays single-column regardless of viewport size.
90
+ *
91
+ * `1` maps to `undefined` so the caller falls back to the form renderer's
92
+ * default single-column stack (`space-y-4`).
93
+ */
94
+ export declare const CONTAINER_GRID_COLS: Record<number, string | undefined>;
95
+ /** Clamp an arbitrary column count into the 1–4 keys of {@link CONTAINER_GRID_COLS}. */
96
+ export declare function containerGridColsFor(columns: number | undefined): string | undefined;
97
+ /**
98
+ * Build the layout-related props for a *section's* inner form (`SchemaRenderer`).
99
+ *
100
+ * A section groups its fields and lays them out in `columns` columns. That grid
101
+ * must be applied to the FIELD container INSIDE the form — not wrapped around
102
+ * the whole form element. Wrapping the single `<form>` in a multi-column grid
103
+ * (as the section variants used to) puts the entire form in the first grid cell
104
+ * and leaves every other column permanently empty. Here we instead thread
105
+ * `columns` + `fieldContainerClass` into the form so its own field container
106
+ * becomes the multi-column grid — mirroring the flat-fields layout path.
107
+ *
108
+ * @param fields Already-built (and permission-filtered) section fields.
109
+ * @param columns Section column count (1–4).
110
+ */
111
+ export declare function sectionFormLayout(fields: FormField[], columns: number | undefined): {
112
+ fields: FormField[];
113
+ columns: number;
114
+ fieldContainerClass?: string;
115
+ };
57
116
  /**
58
117
  * Infer an appropriate modal size based on the number of layout columns.
59
118
  * Used to auto-upgrade the modal width when auto-layout detects multi-column forms.
@@ -34,6 +34,18 @@ export declare function deriveColumns(childSchema: ObjectSchemaLike | undefined,
34
34
  exclude?: string[];
35
35
  maxColumns?: number;
36
36
  }): GridColumn[];
37
+ /**
38
+ * Fill in missing widget metadata on author-supplied grid columns from the
39
+ * child object's field definitions. A view often lists columns as bare
40
+ * `{ field, label }` (the common, ergonomic authoring form) — without a `type`
41
+ * those cells fall back to a plain text `<Input>`, so a lookup, date, number or
42
+ * picklist field silently renders as free text. This resolves each such
43
+ * column's `type` (plus `options` / `reference` / computed `expr`) from the
44
+ * schema, exactly as {@link deriveColumns} would, while preserving the author's
45
+ * column set, order and labels. A column that already declares a `type` is left
46
+ * untouched — the author's explicit choice always wins.
47
+ */
48
+ export declare function hydrateColumns(columns: GridColumn[] | undefined, childSchema: ObjectSchemaLike | undefined): GridColumn[];
37
49
  /**
38
50
  * Field names for a child's full "row form" (the per-row expand editor) — every
39
51
  * editable business field, skipping system/audit fields, the back-reference FK,
@@ -1,27 +1,21 @@
1
1
  import { FormField, ObjectFormSection } from '@object-ui/types';
2
- /** A declared field group on the object metadata. */
3
- export interface DeclaredFieldGroup {
4
- key: string;
5
- label?: string;
6
- /** Render the group's section header with a collapse toggle. */
7
- collapsible?: boolean;
8
- /** Start the (collapsible) group collapsed. */
9
- collapsed?: boolean;
10
- }
11
- /** Read an object's `fieldGroups` into a normalized, well-typed list. */
12
- export declare function readObjectFieldGroups(input: unknown): DeclaredFieldGroup[];
13
2
  /**
14
3
  * Derive form sections from an object's declared `fieldGroups` and each
15
4
  * rendered field's `group`.
16
5
  *
17
6
  * Returns `null` when grouping does not apply — no declared groups, or no
18
7
  * rendered field opts into a declared group — so callers fall back to a flat
19
- * form. Sections come back in declared order; fields without a declared group
20
- * collect into a trailing untitled section (no `name`/`label`) so they render
21
- * as a plain block rather than a card.
8
+ * form.
22
9
  *
23
- * Section `fields` are returned as field *names* (strings) so the result plugs
24
- * straight into ObjectForm's existing section-render path, which resolves names
25
- * back to generated `FormField`s and applies field-level permissions.
10
+ * The derivation runs against the RENDERED field list (post permission /
11
+ * visibility filtering), not the raw object def, so a section never names a
12
+ * field the form isn't showing. Any rendered field the shared derivation
13
+ * excludes from its default buckets (audit/system fields) is re-appended to
14
+ * the trailing bucket: the form was already told to render it, and a layout
15
+ * helper silently dropping a rendered input is exactly the failure mode
16
+ * ADR-0085 exists to kill.
17
+ *
18
+ * Section `fields` are field *names* (strings) so the result plugs straight
19
+ * into ObjectForm's existing section-render path.
26
20
  */
27
21
  export declare function deriveFieldGroupSections(fields: FormField[], fieldGroups: unknown): ObjectFormSection[] | null;