Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/11070-grid-form-face-round10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@object-ui/types': minor
---

A form field of `type: 'grid'` declares the grid widget's field-level keys (objectui#11070, round 10).

A `form`'s `fields[]` entry of `type: 'grid'` is the authored path to the `grid` widget, which reads its field-level keys off that entry. `GridFieldMetadata` declares them and the grid docs teach them, but the form-field face declared none of them, so the strict authoring face (`StrictAnyComponentSchema`) refused each one by name on a form field while the widget read it.

- **`FormField` (TypeScript)** declares `min_rows`, `max_rows`, `allow_add`, `allow_delete`, `allow_reorder`, `total_field`, `add_label` and `sort_field`, each as `GridFieldMetadata`'s own member by reference (`GridFieldMetadata['min_rows']`, and so on), so the two faces cannot drift. Before, each one resolved to the interface's `[key: string]: any` index signature.
- **`FormFieldSchema` (the zod mirror)** declares the same eight keys with the same value types: numbers for the two row limits, booleans for the three switches, and strings for the total column, the Add label and the sort field. Each `.describe()` says that only the `grid` widget reads it.
- On any other field type the keys are accepted and read by nothing, as `columns` already was.

**Clause-②: yes (widening).** `FormFieldSchema`, a published accept set, accepts eight more keys on a form field, so the strict authoring face now accepts a grid entry that writes them. The tolerant face narrows on their values: `FormFieldSchema` strips an undeclared key, so before this change a wrong-typed value (for example `allow_add: "false"` or `min_rows: "1"`) was dropped from the parsed field in silence, and now it is refused.

## ⚠️ BREAKING, priced as minor under the fixed group's version policy

- **Validation.** A form field whose grid key holds a value of the wrong type, which the tolerant face (`safeValidateSchema`, and so `objectui validate`) accepted by stripping the key, is refused. Fix: write the key with its declared type (`allow_add: false`, `min_rows: 1`).
- **TypeScript.** A `FormField` literal that writes one of the eight keys with the wrong type is a compile error; before, the index signature accepted any value.

Rendering does not change: the `grid` widget reads the same keys as before.
33 changes: 24 additions & 9 deletions content/docs/fields/grid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The Grid Field component provides an inline table for managing related records o

<SchemaExample id="fields-grid/read-only-grid" />

## Field-Level Keys

<SchemaExample id="fields-grid/line-items-grid" />

## Field Schema

A grid field is authored as `GridFieldMetadata` (`@object-ui/types`), which is the
Expand Down Expand Up @@ -73,6 +77,12 @@ The field-level keys, each read under exactly this one spelling:
`line_no`, `line_number` or `sort`; there is no master-detail key to override
that pick.

The same keys are accepted on a `form` field entry of `type: 'grid'`, as the
Field-Level Keys example above writes them. `FormField` declares each one by
reference to `GridFieldMetadata`, and its zod mirror judges each one by that type,
so the strict authoring face accepts them there and refuses a value of the wrong
type. Only the `grid` widget reads them.

A read-only or disabled grid offers no add, delete, duplicate or reorder, whatever
these keys say. The line-number column always shows. There is no `reorderable`,
`amount_field`, `amountField`, `allow_duplicate` or `show_line_numbers` key: none
Expand Down Expand Up @@ -308,12 +318,17 @@ const validateGridData = (data: any[], columns: InlineGridColumn[]) => {

## Integration with Advanced Grid

For full-featured grids, use the `@object-ui/plugin-grid` package which provides:

- Inline editing
- Add/remove rows
- Sorting and filtering
- Drag-and-drop reordering
- Export functionality
- Formula columns
- Aggregation rows
The `grid` field and `@object-ui/plugin-grid`'s `object-grid` node do different jobs.
The `grid` field edits an array stored on the parent record, through the field-level
keys above. `object-grid` lists records (fetched for its `objectName`, or handed to
it) and acts on them as records. What each one reads:

| Feature | `grid` field (`@object-ui/fields`) | `object-grid` (`@object-ui/plugin-grid`) |
| --- | --- | --- |
| Inline editing | Every cell but a computed one is its column's control, unless the field is read-only or disabled | `editable: true` (off by default), only where the current user may edit the object |
| Add and remove rows | `allow_add` and `allow_delete`, each on unless `false` | An add-record row with `operations.create`, and a row's Delete with `operations.delete` and the host's `onDelete`, each only where the current user may create or delete the object's records |
| Sorting and filtering | None | Column-header sorting (a column opts out with `sortable: false`), the query's `filter` and `sort`, and search over `searchableFields` |
| Drag-and-drop reordering | Rows, with `allow_reorder` (on unless `false`); `sort_field` keeps the order on save | Columns only, with `reorderableColumns: true`; rows are not reordered |
| Export | None | `exportOptions` |
| Computed columns | A column with `computed: true` and an arithmetic `expr` over the row's other cells | None: a formula field's value is shown as the server computed it |
| Totals | One footer total, the sum of the `total_field` column | A footer summary per column (`columns[].summary`), and aggregates on group headers (`aggregations`) |
10 changes: 10 additions & 0 deletions examples/schema-catalog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ import fields_formula_numeric_formula from './schemas/fields-formula/numeric-for
import fields_formula_text_concatenation from './schemas/fields-formula/text-concatenation.json' with { type: 'json' };
import fields_grid_basic_grid from './schemas/fields-grid/basic-grid.json' with { type: 'json' };
import fields_grid_grid_with_data from './schemas/fields-grid/grid-with-data.json' with { type: 'json' };
import fields_grid_line_items_grid from './schemas/fields-grid/line-items-grid.json' with { type: 'json' };
import fields_grid_read_only_grid from './schemas/fields-grid/read-only-grid.json' with { type: 'json' };
import fields_image_basic_image_upload from './schemas/fields-image/basic-image-upload.json' with { type: 'json' };
import fields_image_multiple_image_upload from './schemas/fields-image/multiple-image-upload.json' with { type: 'json' };
Expand Down Expand Up @@ -3392,6 +3393,15 @@ const REGISTRY: Record<string, Example> = {
},
schema: fields_grid_grid_with_data,
},
'fields-grid/line-items-grid': {
id: 'fields-grid/line-items-grid',
meta: {
title: "Line Items Grid",
description: "",
category: 'fields-grid',
},
schema: fields_grid_line_items_grid,
},
'fields-grid/read-only-grid': {
id: 'fields-grid/read-only-grid',
meta: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"type": "form",
"showSubmit": false,
"showCancel": false,
"defaultValues": {
"line_items": [
{
"product": "Widget A",
"quantity": 2,
"amount": 59.98,
"position": 0
},
{
"product": "Widget B",
"quantity": 1,
"amount": 49.99,
"position": 1
}
]
},
"fields": [
{
"name": "line_items",
"label": "Line Items",
"type": "grid",
"columns": [
{
"name": "product",
"label": "Product",
"type": "text"
},
{
"name": "quantity",
"label": "Qty",
"type": "number"
},
{
"name": "amount",
"label": "Amount",
"type": "currency"
}
],
"min_rows": 1,
"max_rows": 10,
"allow_add": true,
"allow_delete": true,
"allow_reorder": true,
"total_field": "amount",
"add_label": "Add line item",
"sort_field": "position"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ const NODE_CENSUS: Readonly<Record<string, { rendered: number; noElement: number
slider: { rendered: 1, noElement: 0 },
toggle: { rendered: 17, noElement: 0 },
'sidebar-menu-button': { rendered: 15, noElement: 0 },
grid: { rendered: 26, noElement: 0 },
// 26 -> 27 with objectui#11070 round 10: the new `fields-grid/line-items-grid`
// fixture's form field entry is `type: 'grid'`, which this structural walk
// collects, as it collects the other `fields-grid` entries. Catalog
// authoring, not a renderer change.
grid: { rendered: 27, noElement: 0 },
};

function collect(node: unknown, out: Node[] = []): Node[] {
Expand Down
13 changes: 11 additions & 2 deletions examples/schema-catalog/test/layout-dom-leak-5574.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ const NODE_CENSUS: Readonly<Record<string, { rendered: number; noElement: number
flex: { rendered: 248, noElement: 0 },
stack: { rendered: 153, noElement: 0 },
container: { rendered: 15, noElement: 0 },
grid: { rendered: 26, noElement: 0 },
// 26 -> 27 with objectui#11070 round 10: the new `fields-grid/line-items-grid`
// fixture's form field entry is `type: 'grid'`, which this structural walk
// collects, as it collects the other `fields-grid` entries. Catalog
// authoring, not a renderer change.
grid: { rendered: 27, noElement: 0 },
// 699 -> 702: the two components-layout-box exemplars author 3 text nodes
// (objectui#3965). 702 -> 701 and 176 -> 162 with objectui#6942, and the two
// moves have different causes: `components-basic-text/muted.json` was deleted
Expand All @@ -129,7 +133,12 @@ const NODE_CENSUS: Readonly<Record<string, { rendered: number; noElement: number
// the `div` -> `box` before/after the migration guide was missing, and its
// two `text` nodes both carry a className, so both need an element — the
// no-element count is unmoved. Catalog authoring, not a renderer change.
text: { rendered: 698, noElement: 154 },
// 698 -> 699 and 154 -> 155 with objectui#11070 round 10: the new
// `fields-grid/line-items-grid` fixture's `product` grid column is
// `type: 'text'`, which this structural walk collects as a `text` node;
// rendered on its own it draws no element. Catalog authoring, not a
// renderer change.
text: { rendered: 699, noElement: 155 },
// The objectui#3965 migration population (80 nodes retyped from `div`) plus
// the 4 nodes of the components-layout-box exemplars. `box` is born on
// `toDomProps` and class-transparency, so it joins the measured set as a
Expand Down
6 changes: 5 additions & 1 deletion examples/schema-catalog/test/svg-host-dom-leak-5632.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ const MEASURED_TYPES = ['icon', 'spinner', 'grid'] as const;
const NODE_CENSUS: Readonly<Record<string, { rendered: number; noElement: number }>> = {
icon: { rendered: 71, noElement: 0 },
spinner: { rendered: 6, noElement: 0 },
grid: { rendered: 26, noElement: 0 },
// 26 -> 27 with objectui#11070 round 10: the new `fields-grid/line-items-grid`
// fixture's form field entry is `type: 'grid'`, which this structural walk
// collects, as it collects the other `fields-grid` entries. Catalog
// authoring, not a renderer change.
grid: { rendered: 27, noElement: 0 },
};

function collect(node: unknown, out: Node[] = []): Node[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
* objectui#3951 — grid columns have ONE key spelling, and it is the declared
* one: `name`, the key of `GridFieldMetadata['columns']` (`@object-ui/types`),
* which is `@objectstack/spec`'s inline grid column by reference since
* objectui#11070 — the same key the grid docs page and the three `fields-grid`
* catalog examples author.
* objectui#11070 — the same key the grid docs page and every `fields-grid`
* catalog example author.
*
* `GridField` used to declare its own local column interface keyed by `field`
* and read `c.field` everywhere — `key={c.field}`, `row[c.field]`,
Expand Down
10 changes: 10 additions & 0 deletions packages/types/src/__tests__/form-field-zod-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ const DECLARED_KEYS = [
// objectui#11070 round 7 — the `grid` widget's columns: the spec's
// `inlineColumns` list (its strict inline grid column), by reference.
'columns',
// objectui#11070 round 10 — the `grid` widget's field-level keys, the
// members of `GridFieldMetadata` (the TS twin carries each by reference).
'min_rows',
'max_rows',
'allow_add',
'allow_delete',
'allow_reorder',
'total_field',
'add_label',
'sort_field',
];

describe('FormFieldSchema covers the FormField contract', () => {
Expand Down
Loading
Loading