Summary
Add a typed extension slot to column leaves. ColumnLeafProps declares no place for per-column data that the code consuming useDataViewContext().columns needs, so an extra prop on a custom leaf reaches columns only through the undocumented ...leaf spread in resolveColumnNames, and the built-in column factories drop it.
Environment
Reproduction
A downstream spreadsheet export needs each computed column to say how its cell is written as text. The column type owns that knowledge, so it has to travel on the leaf:
declare module '@contember/bindx-dataview' {
interface ColumnLeafProps {
// Not part of the bindx contract: survives only because resolveColumnNames spreads `...leaf`.
readonly exportCell?: ((row: ExportRow) => string | null) | null
}
}
const leafProps: ColumnLeafProps = {
name: 'composed',
fieldName: null,
fieldRef: null,
sortingField: null,
filterName: null,
filterHandler: undefined,
isTextSearchable: false,
header: 'Composed',
renderCell: accessor => <ComposedCell accessor={accessor} />,
exportCell: row => composedText(row),
}
return <ColumnLeaf {...leafProps} />
// consumer
const { columns } = useDataViewContext()
columns.map(column => column.exportCell)
The failing test expresses the contract we would like instead, a meta slot typed by an interface the consumer augments:
declare module '@contember/bindx-dataview' {
interface ColumnLeafMeta {
readonly exportHeader?: string
}
}
<ColumnLeaf {...leafProps} meta={{ exportHeader }} />
<DataGridTextColumn field={it.title} header="Title" meta={{ exportHeader: 'Title (export)' }} />
const { columns } = useDataViewContext()
columns.map(it => it.meta?.exportHeader)
Expected behavior
ColumnLeafProps has a documented slot for consumer data, typed by an augmentable interface, so no consumer has to augment ColumnLeafProps itself.
- The slot reaches
DataViewColumn through resolveColumnNames and the runtime-bound columns built in useDataGridSetup.
- Every built-in column component (
createColumn, createRelationColumn, the enum columns) accepts the slot and forwards it to its leaf.
Actual behavior
-
bun run typecheck rejects the test:
tests/react/dataview/columnLeafMeta.test.tsx(54,5): error TS2353: Object literal may only specify known properties, and 'meta' does not exist in type 'ColumnLeafProps'.
tests/react/dataview/columnLeafMeta.test.tsx(63,79): error TS2339: Property 'meta' does not exist on type 'DataViewColumn'.
tests/react/dataview/columnLeafMeta.test.tsx(100,59): error TS2322: Type '{ field: FieldRef<string> & FieldAccessor<string>; header: string; meta: { exportHeader: string; }; }' is not assignable to type 'IntrinsicAttributes & ColumnComponentProps<string> & object'.
Property 'meta' does not exist on type 'IntrinsicAttributes & ColumnComponentProps<string> & object'.
-
At runtime the custom leaf's extra prop does reach columns (the first test passes), but only as a side effect of the spread. The built-in column drops it (the second test fails):
Expected: "title:Title (export)"
Received: "title:-"
Suspected root cause
packages/bindx-dataview/src/columnLeaf.ts: ColumnLeafProps is a closed interface with no consumer slot. resolveColumnNames passes unknown props through only because it builds each column with { ...leaf, name }.
packages/bindx-dataview/src/createColumn.ts (createColumnStaticRender) and packages/bindx-dataview/src/createRelationColumn.tsx build leafProps field by field from the component props, so a prop they do not list never reaches the leaf.
Suggested fix
- In
columnLeaf.ts, export interface ColumnLeafMeta {} and add readonly meta?: ColumnLeafMeta to ColumnLeafProps (and so to DataViewColumn), documented as the place for consumer data.
- Add
meta?: ColumnLeafMeta to ColumnComponentProps and forward props['meta'] in createColumnStaticRender, in createRelationColumn and in the enum columns in columns.tsx.
A documented guarantee that resolveColumnNames and useDataGridSetup keep unknown leaf props would also work, but it leaves the consumer augmenting a core interface and does not cover the built-in columns.
Workaround shipped downstream
We applied a temporary workaround in our project, marked
TODO [BindX] (<this-issue-url>): <description>. The workaround augments ColumnLeafProps with an exportCell property and relies on resolveColumnNames spreading ...leaf; only our own column components set it. We will remove it once this issue is resolved.
Summary
Add a typed extension slot to column leaves.
ColumnLeafPropsdeclares no place for per-column data that the code consuminguseDataViewContext().columnsneeds, so an extra prop on a custom leaf reachescolumnsonly through the undocumented...leafspread inresolveColumnNames, and the built-in column factories drop it.Environment
@contember/bindx-dataview@0.1.52(version installed in the reporting project)contember/bindx@mainas of78c1712tests/react/dataview/columnLeafMeta.test.tsxbug/column-leaf-extension-propsReproduction
A downstream spreadsheet export needs each computed column to say how its cell is written as text. The column type owns that knowledge, so it has to travel on the leaf:
The failing test expresses the contract we would like instead, a
metaslot typed by an interface the consumer augments:Expected behavior
ColumnLeafPropshas a documented slot for consumer data, typed by an augmentable interface, so no consumer has to augmentColumnLeafPropsitself.DataViewColumnthroughresolveColumnNamesand the runtime-bound columns built inuseDataGridSetup.createColumn,createRelationColumn, the enum columns) accepts the slot and forwards it to its leaf.Actual behavior
bun run typecheckrejects the test:At runtime the custom leaf's extra prop does reach
columns(the first test passes), but only as a side effect of the spread. The built-in column drops it (the second test fails):Suspected root cause
packages/bindx-dataview/src/columnLeaf.ts:ColumnLeafPropsis a closed interface with no consumer slot.resolveColumnNamespasses unknown props through only because it builds each column with{ ...leaf, name }.packages/bindx-dataview/src/createColumn.ts(createColumnStaticRender) andpackages/bindx-dataview/src/createRelationColumn.tsxbuildleafPropsfield by field from the component props, so a prop they do not list never reaches the leaf.Suggested fix
columnLeaf.ts, exportinterface ColumnLeafMeta {}and addreadonly meta?: ColumnLeafMetatoColumnLeafProps(and so toDataViewColumn), documented as the place for consumer data.meta?: ColumnLeafMetatoColumnComponentPropsand forwardprops['meta']increateColumnStaticRender, increateRelationColumnand in the enum columns incolumns.tsx.A documented guarantee that
resolveColumnNamesanduseDataGridSetupkeep unknown leaf props would also work, but it leaves the consumer augmenting a core interface and does not cover the built-in columns.Workaround shipped downstream
We applied a temporary workaround in our project, marked
TODO [BindX] (<this-issue-url>): <description>. The workaround augmentsColumnLeafPropswith anexportCellproperty and relies onresolveColumnNamesspreading...leaf; only our own column components set it. We will remove it once this issue is resolved.