You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ce1d8ce
Browse filesBrowse the repository at this point in the historyBrowse files
docs(spec): regenerate component.mdx on the merged tree
The merge of main brought the object-grid typed members; component.mdx is
generated, so it is regenerated from the merged tree rather than text-merged.
Against main's copy: 14 rows move, every one adding `?` to a defaulted member,
none removing one. Every shared def on the page now renders one face.
Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+69-9Lines changed: 69 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -704,28 +704,28 @@ Sort field and direction pair
704
704
|**showPagination**|`boolean`| optional | Show the pager (read only when `pagination` is absent) |
705
705
|**searchableFields**|`string[]`| optional | Fields the toolbar search queries; a non-empty list enables search |
706
706
|**showSearch**|`boolean`| optional | Show the search box (read only when `searchableFields` is absent) |
707
-
|**rowHeight**|`any`| optional | Row density mode (e.g. compact / comfortable)|
707
+
|**rowHeight**|`Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>`| optional | Row height — one of `compact`, `short`, `medium`, `tall`, `extra_tall`; the same values a list view's `rowHeight` takes|
|**aggregations**|`{ field: string; type: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'> }[]`| optional |Per-group aggregations drawn in a grouped grid's group headers — `[{ field, type }]`, `type` one of `count`, `sum`, `avg`, `min`, `max`, `count_distinct` (`count` is the group's row count, whatever `field` names) |
710
+
|**conditionalFormatting**|`{ condition: string \| object; style: Record<string, string> }[]`| optional | Conditional formatting rules — `[{ condition, style }]`, the same rules a list view declares: the first rule whose CEL `condition` holds applies its CSS `style` map to the row|
711
+
|**rowColor**|`{ field: string; colors?: Record<string, string> }`| optional | Row colour by field value — `{ field, colors }`, the same block a list view's `rowColor` declares|
|**navigation**|`any`| optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema|
717
+
|**bulkActionDefs**|`{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]`| optional | Inline bulk-action definitions (full defs, not names) — the same `BulkActionDef` entries a list view's `bulkActionDefs` declares|
|**singleClickEdit**|`boolean`| optional | Enter cell edit on single click (default true when editable) |
721
721
|**keyboardNavigation**|`boolean`| optional |[EXPERIMENTAL — not enforced] Arrow-key cell navigation on the WAI-ARIA grid pattern. Defaults to on when `editable` is set; a read-only grid keeps its Tab behaviour unless this is `true`. No renderer reads it yet: it is declared ahead of the grid's keyboard-navigation build, so authoring it changes nothing today |
722
-
|**resizable**|`boolean`| optional | Allow column resize (read before `resizableColumns`) |
723
-
|**resizableColumns**|`boolean`| optional |Alternate spelling of `resizable` (the renderer reads`resizable ?? resizableColumns`)|
722
+
|**resizable**|`boolean`| optional | Allow column resize (the renderer default is on) |
723
+
|**resizableColumns**|`never`| optional |[REMOVED]`object-grid` property `resizableColumns` was removed in @objectstack/spec 17.7.0 (ADR-0049) — it was the legacy second spelling of `resizable`, read only when`resizable` was absent, so one switch had two spellings and a grid authoring both silently ignored this one. Use `resizable`. Rename the key; the value (a boolean) is unchanged. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.|
|**frozenColumns**|`number`| optional | How many leading columns stay frozen (default 1) |
726
726
|**showColumnTypeIcons**|`boolean`| optional | Show field-type icons in column headers |
727
727
|**exportOptions**|`{ formats?: Enum<'csv' \| 'xlsx' \| 'json'>[]; maxRecords?: integer; includeHeaders?: boolean; fileNamePrefix?: string; … }`| optional | Export config — the object `{ formats?, maxRecords?, includeHeaders?, fileNamePrefix?, streaming? }`, the same block a list view's `exportOptions` declares, with `formats` drawn from `csv`, `xlsx` and `json`. A bare format array is refused: the grid reads `exportOptions.formats`, so write `{ formats: ['csv', 'xlsx'] }`|
|**operations**|`{ create?: boolean; update?: boolean; delete?: boolean; export?: boolean }`| optional |Built-in affordance toggles `{ create?, update?, delete?, export? }` (booleans). A declared block replaces the grid's default: `update` / `delete` not named are withheld from the row menu, `create` enables the add-row affordance, and `export: false` hides the export menu|
729
729
|**data**|`{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record<string, any> }`| optional | Data source binding (ViewDataSchema — discriminated on `provider`: object \| api \| value \| schema). Static inline rows live at `{ provider: 'value', items: [...] }`; the bare-array shortcut is refused — see migration `object-grid-data-view-data-converged`|
730
730
|**staticData**|`any[]`| optional | Deprecated bare-array static-rows shortcut the renderer still reads. Prefer `data: { provider: 'value', items: [...] }`|
731
731
@@ -772,6 +772,57 @@ Sort field and direction pair
772
772
| :--- | :--- | :--- | :--- |
773
773
|**fields**|`{ field: string; order?: Enum<'asc' \| 'desc'>; collapsed?: boolean }[]`| ✅ | Fields to group by, in nesting order — the first entry is the outermost group and each later entry nests one level deeper (at least one field); the same order as the group header query's `groupBy`|
|**style**|`Record<string, string>`| ✅ | CSS styles to apply when condition is true |
788
+
789
+
### Nested Shape: `ObjectGridProps.rowColor`
790
+
791
+
| Property | Type | Required | Description |
792
+
| :--- | :--- | :--- | :--- |
793
+
|**field**|`string`| ✅ | Field whose value is looked up in the `colors` map below to pick a row colour (typically a select/status field). The map is what does the colouring — with no `colors`, no row is ever coloured, whatever this field holds. Author-time diagnostic `view/row-color-without-colors` reports that combination. |
794
+
|**colors**|`Record<string, string>`| optional | Map of field value to row colour. The spellings that actually paint a row are not free-form: objectui `plugin-grid`'s `useRowColor` hands a value already written as a complete Tailwind background class (`bg-red-200`) straight through, otherwise lower-cases and trims it and resolves it through its own closed vocabulary of colour NAMES (`red`, `blue`, `slate`, … each mapping to `bg-NAME-100`), and returns undefined for anything else. A hex, an `rgb()` or a CSS variable parses here, publishes, and colours no row — Tailwind v4 has no runtime, so no class can be fabricated from one. Author-time diagnostic `view/row-color-unresolvable-value` reports a value that cannot resolve. |
|**name**|`string`| ✅ | Stable identifier — the audit-log action key, and (for an aggregate def) the name of the object action to dispatch. |
801
+
|**label**|`string`| optional | Button + dialog-header text. Plain string: an authored def is not i18n-resolved (declare a real action and name it in `bulkActions` to get localization). |
802
+
|**icon**|`string`| optional | Lucide icon name (e.g. "user-check", "trash"). |
803
+
|**variant**|`Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>`| optional | Visual treatment of the button. |
804
+
|**operation**|`Enum<'update' \| 'delete' \| 'custom'>`| ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
805
+
|**execution**|`Enum<'perRecord' \| 'aggregate'>`| optional | For `operation: 'custom'` — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in `params._selectedIds`. Required on a custom def: the per-record form is declared as `bulkActions: ['<name>']` instead. |
806
+
|**patch**|`Record<string, any>`| optional | For `operation: 'update'` — static field values applied to every selected record, merged UNDER the user-supplied params so a fixed value can be declared without exposing it in the dialog. |
807
+
|**params**|`{ name: string; label?: string; help?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| …>; … }[]`| optional | Inputs collected once before the run. Omit to skip the params step and go straight to confirm. |
808
+
|**confirmText**|`string`| optional | Confirmation text shown above the affected-record summary. |
|**visible**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }`| optional | Eligibility predicate (CEL) — a string or a `{dialect, source}` envelope, i.e. `action.visible` without its boolean-literal arm: a per-record predicate has nothing to say as a constant. Evaluated once PER SELECTED RECORD with that record bound: the button is offered when at least one passes, the run covers only those, and the rest are reported as skipped. A record-free predicate (`features.x`, `current_user.y`) therefore behaves as a plain button-level gate. Fail-closed — a predicate that faults excludes the record. |
811
+
|**requiredPermissions**|`string[]`| optional |[ADR-0066 D4] Capability gate on the button, `action.requiredPermissions` semantics verbatim: absent or empty always passes, several are AND-ed, and a client that cannot resolve the caller's capabilities fails OPEN (the server stays the authority). This key exists for INLINE defs — notably the `update`/`delete` data-plane forms, which dispatch no action and so have nothing to inherit a gate from; a def promoted from `bulkActions: ['<name>']` (or an aggregate def naming a declared action) inherits the action's own declaration instead. On a data-plane def the gate governs visibility only — the write itself is still authorized by the data API's object permissions and server hooks. |
812
+
|**maxRecords**|`integer`| optional | Selection size above which the run is blocked. Set it on defs whose server work is expensive — an aggregate def carries every selected id in one request. |
813
+
|**batchSize**|`integer`| optional | Records per executor batch (default 200). Data-plane operations only — an aggregate run is a single call by definition. |
|**view**|`never`| optional |[REMOVED]`view.list.navigation.view` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the form view to open for a record detail, and no layer resolved a view by that name: the value was passed straight into the navigation-MODE argument of the console's `onNavigate`, where anything other than `edit` or `view` matched no branch, so the key selected nothing and could silently deaden the row click. Delete the key; to choose what opens for a record, assign a `record` page to the object and let `isDefault` pick the one that opens — page assignment is the machinery that resolves a detail layout, and a list view's navigation block only decides HOW the detail is surfaced (`mode`, `size`). |
821
+
|**preventNavigation**|`boolean`| optional (default: `false`) | Disable standard navigation entirely |
822
+
|**openNewTab**|`boolean`| optional (default: `false`) | Force open in new tab (applies to page mode) |
823
+
|**size**|`Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>`| optional (default: `"auto"`) | Overlay size bucket for drawer/modal detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it. |
824
+
|**width**|`string \| number`| optional |[DEPRECATED → size] Pixel/percent width of the drawer/modal (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket. |
825
+
775
826
### Nested Shape: `ObjectGridProps.exportOptions`
776
827
777
828
| Property | Type | Required | Description |
@@ -782,6 +833,15 @@ Sort field and direction pair
782
833
|**fileNamePrefix**|`string`| optional | Download file name prefix — replaces the object label and suppresses the view label in the generated file name |
783
834
|**streaming**|`boolean`| optional | Set false to force the client-side export path (csv/json only) instead of the server stream |
784
835
836
+
### Nested Shape: `ObjectGridProps.operations`
837
+
838
+
| Property | Type | Required | Description |
839
+
| :--- | :--- | :--- | :--- |
840
+
|**create**|`boolean`| optional | Show the add-row affordance (also gated by the user's create permission) |
0 commit comments