@object-ui/plugin-form 11.2.0 → 11.4.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,112 @@
1
1
  # @object-ui/plugin-form
2
2
 
3
+ ## 11.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8bf6295: feat: adaptive record surface + semantic field span + responsive columns (framework#2578)
8
+
9
+ Field-heavy objects (all metadata is AI-authored) now present themselves without
10
+ any authored presentation config:
11
+
12
+ - **Adaptive surface** — a record's create/edit/detail opens as a full page when
13
+ the object is field-heavy, or a drawer when it is light. Derived from field
14
+ count (`deriveRecordSurface`), not authored; mobile always pages. Wired into the
15
+ app-shell ObjectView detail navigation (an authored view/object `navigation`
16
+ still wins).
17
+ - **Semantic field span** — `FormField.span` (`auto`/`full`) is a width primitive
18
+ decoupled from the (per-surface derived) column count; legacy `colSpan` is
19
+ clamped so it never overflows. `ObjectForm` now honours per-section `columns`
20
+ and carries `span`/`colSpan` from section defs — fixes the bug where
21
+ `type:'simple'` ignored `section.columns` and grouped fields rendered single
22
+ column.
23
+ - **Responsive columns** — `inferColumns` scales the column CAP with field count
24
+ (≤3→1, ≤8→2, ≤15→3, 16+→4); the ACTUAL column count follows the form's real
25
+ width via CSS container queries, so the same form goes 1→2→3→4 columns as a
26
+ drawer widens or becomes a page.
27
+ - **Runtime overlay width** — `NavigationConfig.size` bucket is resolved to a
28
+ viewport-clamped width at runtime (`overlayWidthFor`); a pixel width is never
29
+ authored (the author cannot know the client viewport).
30
+
31
+ - 144ab55: Consume the ADR-0085 object semantic roles from `@objectstack/spec@11.7.0`, retiring the per-surface hint dialects:
32
+
33
+ - **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).
34
+ - **`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).
35
+ - **`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).
36
+ - **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.
37
+
38
+ ### Patch Changes
39
+
40
+ - c38d107: Fix view-level `FormField.visibleOn` (CEL) never taking effect (#2212).
41
+
42
+ The spec ships `visibleOn` as an Expression object `{ dialect: 'cel', source }`
43
+ (what the `P` template emits) or a bare string, but the whole chain dropped it:
44
+
45
+ - `sectionFields.ts` / `ObjectForm.tsx` only accepted the bare-string shape and
46
+ attached a dead `visible()` closure no renderer ever called — the Expression
47
+ object shape was silently discarded.
48
+ - The form renderer destructured `visibleOn` out of the field config and never
49
+ evaluated it.
50
+ - `RecordFormPage` dropped a `simple` form view's `sections` entirely, so
51
+ page-mode create/edit fell back to the raw schema (every field, no authored
52
+ selection/grouping) while the modal path honored the same view.
53
+ - `ObjectForm`'s grouped-sections path matched section fields by name only,
54
+ dropping per-field `visibleOn` overrides.
55
+
56
+ `visibleOn` now flows through normalization verbatim (both wire shapes) and is
57
+ evaluated reactively by the form renderer with the canonical expression engine
58
+ (`evalFieldPredicate` — same engine, record scope, and fail-open semantics as
59
+ field-level `visibleWhen`; both predicates must allow a field for it to show).
60
+ Sectioned/flat normalization also copies field-level `visibleWhen` /
61
+ `readonlyWhen` / `requiredWhen` rules it previously lost.
62
+
63
+ - 1e9145d: Hydrate widget types on hand-authored master-detail subform columns. A view can
64
+ list a child grid's columns as bare `{ field, label }` (the common authoring
65
+ form); previously such untyped columns were passed straight to the grid, so a
66
+ `select` / `lookup` / `date` / `number` field silently rendered as a plain text
67
+ cell. `MasterDetailForm` (and `deriveDetail`) now resolve each untyped column's
68
+ `type` (plus `options` / `reference` / computed `expr`) from the child object's
69
+ schema via the new `hydrateColumns` helper — a picklist becomes a dropdown, a
70
+ lookup a record picker, a date a date input — while preserving the author's
71
+ exact column set, order and labels. Columns that already declare a `type` are
72
+ left untouched (the author's explicit choice still wins).
73
+ - Updated dependencies [8bf6295]
74
+ - Updated dependencies [1948c5b]
75
+ - Updated dependencies [bce581a]
76
+ - Updated dependencies [9cd9be1]
77
+ - Updated dependencies [5160832]
78
+ - Updated dependencies [69d6b94]
79
+ - Updated dependencies [c38d107]
80
+ - Updated dependencies [243a9ba]
81
+ - Updated dependencies [289be5b]
82
+ - Updated dependencies [7782698]
83
+ - Updated dependencies [19f2533]
84
+ - Updated dependencies [790558b]
85
+ - Updated dependencies [09e1b26]
86
+ - Updated dependencies [e84d64d]
87
+ - @object-ui/types@11.4.0
88
+ - @object-ui/components@11.4.0
89
+ - @object-ui/fields@11.4.0
90
+ - @object-ui/i18n@11.4.0
91
+ - @object-ui/core@11.4.0
92
+ - @object-ui/permissions@11.4.0
93
+ - @object-ui/react@11.4.0
94
+
95
+ ## 11.3.0
96
+
97
+ ### Patch Changes
98
+
99
+ - Updated dependencies [d88c8ec]
100
+ - Updated dependencies [b7237bb]
101
+ - Updated dependencies [d23d6eb]
102
+ - @object-ui/components@11.3.0
103
+ - @object-ui/i18n@11.3.0
104
+ - @object-ui/core@11.3.0
105
+ - @object-ui/fields@11.3.0
106
+ - @object-ui/react@11.3.0
107
+ - @object-ui/types@11.3.0
108
+ - @object-ui/permissions@11.3.0
109
+
3
110
  ## 11.2.0
4
111
 
5
112
  ### Patch Changes
@@ -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;