diff --git a/.changeset/spec-bridge-listview-types.md b/.changeset/spec-bridge-listview-types.md new file mode 100644 index 000000000..fe60e9d08 --- /dev/null +++ b/.changeset/spec-bridge-listview-types.md @@ -0,0 +1,15 @@ +--- +"@object-ui/react": patch +--- + +refactor(spec-bridge): retire the hand-written `ListViewSpec`/`ListColumn` mirrors in the list-view bridge (#2231 follow-up) + +The SpecBridge's list-view bridge kept a third hand-written copy of the ListView shape +(after the zod schema and the TS interface unified in the previous #2231 PR). It now +derives its input type from `@objectstack/spec/ui` (`Partial`, spec `ListColumn`), +so the bridge can no longer drift from the protocol. + +Behavior fix surfaced by the real types: spec `columns` is `string[] | ListColumn[]`, but +the old local interface only admitted `ListColumn[]` — a bare field-name column would have +produced a broken `{ accessorKey: undefined }` mapping. String columns now map to a default +column (`{ accessorKey: field, header: field }`). diff --git a/packages/react/src/spec-bridge/bridges/list-view.ts b/packages/react/src/spec-bridge/bridges/list-view.ts index 111d2dbdf..69a58a96e 100644 --- a/packages/react/src/spec-bridge/bridges/list-view.ts +++ b/packages/react/src/spec-bridge/bridges/list-view.ts @@ -8,74 +8,23 @@ import type { SchemaNode } from '@object-ui/core'; import type { BridgeContext, BridgeFn } from '../types'; -import type { ListViewGalleryConfig, ListViewTimelineConfig } from '@object-ui/types'; +import type { ListView, ListColumn } from '@objectstack/spec/ui'; -interface ListColumn { - field: string; - label?: string; - width?: number; - align?: string; - hidden?: boolean; - sortable?: boolean; - resizable?: boolean; - wrap?: boolean; - type?: string; - pinned?: string; - summary?: any; - link?: any; - action?: any; -} +/** + * Bridge input: the spec-canonical ListView (#2231 — the former hand-written + * `ListViewSpec`/`ListColumn` mirrors are retired; the shape now derives from + * `@objectstack/spec/ui` and can no longer drift). Relaxed to `Partial` because + * hosts routinely hand the bridge fragmentary view-configs (every field was + * optional in the old mirror too, including spec-required `columns`). + */ +type ListViewSpec = Partial; -interface ListViewSpec { - name?: string; - label?: string; - type?: string; - data?: any; - columns?: ListColumn[]; - filter?: any; - sort?: any; - searchableFields?: string[]; - filterableFields?: string[]; - selection?: any; - pagination?: any; - rowHeight?: string; - resizable?: boolean; - striped?: boolean; - bordered?: boolean; - grouping?: any; - rowColor?: any; - navigation?: any; - kanban?: any; - calendar?: any; - gantt?: any; - gallery?: ListViewGalleryConfig; - timeline?: ListViewTimelineConfig; - // P1.1 additions - rowActions?: string[]; - bulkActions?: string[]; - bulkActionDefs?: any[]; - virtualScroll?: boolean; - conditionalFormatting?: Array<{ condition: string; style: Record }>; - inlineEdit?: boolean; - exportOptions?: any; - emptyState?: { title?: string; message?: string; icon?: string }; - userActions?: any; - appearance?: any; - /** UX-only hint: collapse appearance/grouping cluster into a single popover. */ - compactToolbar?: boolean; - tabs?: any[]; - addRecord?: any; - showRecordCount?: boolean; - allowPrinting?: boolean; - // P1.6 i18n & ARIA - aria?: { ariaLabel?: string; ariaDescribedBy?: string; role?: string }; - sharing?: any; - hiddenFields?: string[]; - fieldOrder?: string[]; - description?: string; -} +function mapColumn(col: ListColumn | string): Record { + // Spec-legacy shorthand: a bare field name stands for a default column. + if (typeof col === 'string') { + return { accessorKey: col, header: col }; + } -function mapColumn(col: ListColumn): Record { const mapped: Record = { accessorKey: col.field, header: col.label ?? col.field,