Skip to content

feat(DataGrid): add headless compound shell over createDataGrid - #826

Open
johnleider wants to merge 11 commits into
devfrom
cursor/datagrid-compound-shell-d904
Open

feat(DataGrid): add headless compound shell over createDataGrid#826
johnleider wants to merge 11 commits into
devfrom
cursor/datagrid-compound-shell-d904

Conversation

@johnleider

@johnleider johnleider commented Aug 12, 2026

Copy link
Copy Markdown
Member

Fixes #513
Relates to #507

Summary

Implements a headless DataGrid compound shell over the existing createDataGrid composable, with Splitter composition for column resizing.

Key Changes

Splitter Composition for Column Resize

DataGrid now composes Splitter primitives for resizable columns:

  • DataGrid.Row with resizable prop → internally uses Splitter.Root (horizontal orientation)
  • DataGrid.Column in resizable row → internally uses Splitter.Panel
  • DataGrid.Handle → thin wrapper around Splitter.Handle
  • @layout event syncs Splitter sizes → context.layout.distribute(sizes)
<!-- Resizable header row with handles -->
<DataGrid.Row resizable class="flex">
  <DataGrid.Column column="name">Name</DataGrid.Column>
  <DataGrid.Handle />
  <DataGrid.Column column="email">Email</DataGrid.Column>
</DataGrid.Row>

Compound Components

Component Description Element/Role
DataGrid.Root Context provider renderless
DataGrid.Table Grid container table / role="grid"
DataGrid.Header Header section thead / role="rowgroup"
DataGrid.Body Body section tbody / role="rowgroup"
DataGrid.Row Row (normal or resizable via Splitter.Root) tr / role="row"
DataGrid.Column Header cell (normal or Splitter.Panel) th / role="columnheader"
DataGrid.Cell Data cell td / role="gridcell"
DataGrid.Handle Resize handle (wraps Splitter.Handle) div / role="separator"

Splitter Mapping

DataGrid.Row[resizable] → Splitter.Root (horizontal)
DataGrid.Column (in resizable row) → Splitter.Panel
DataGrid.Handle → Splitter.Handle
@layout → context.layout.distribute(sizes)

New Feature Checklist

  • Directory structure and barrel file
  • Updated maturity.json (preview, since: null)
  • Documentation page with examples
  • Browser tests (30 tests passing)
  • a11y fixture added
  • Surface freeze array updated
  • Locale key for resize handle
  • Rebased onto current dev
  • Commit body contains Fixes #513

Deferred Items

  • Pin regions: Separate Splitter roots for left/scrollable/right pin regions (requires layout API investigation)
  • Header/Column rename: Converge to Header/HeaderCell/Row naming shared with DataTable (future PR)
Open in Web Open in Cursor 

@cursor
cursor Bot force-pushed the cursor/datagrid-compound-shell-d904 branch from 67db7b7 to 3c800c2 Compare August 14, 2026 23:06
@johnleider
johnleider marked this pull request as ready for review August 14, 2026 23:10
@cursor
cursor Bot force-pushed the cursor/datagrid-compound-shell-d904 branch from 3c800c2 to d3b7e4a Compare August 14, 2026 23:11
@johnleider johnleider added this to the v1.1.0 milestone Aug 16, 2026
@johnleider johnleider self-assigned this Aug 16, 2026
cursoragent and others added 10 commits August 20, 2026 08:05
Implements FR #513 (epic #507) - thin structural shells for building data grids.

Components:
- DataGrid.Root - context provider wrapping createDataGrid
- DataGrid.Table - semantic table with role="grid"
- DataGrid.Header / DataGrid.Body - section containers with role="rowgroup"
- DataGrid.Row - row container with optional id for ordering/selection
- DataGrid.Column - header cell with sorting state and aria-sort
- DataGrid.Cell - data cell with editing state and row spanning

Context injection via useDataGridRoot/provideDataGridRoot and
useDataGridRow/provideDataGridRow trinities.

Follows Avatar/Snackbar compound patterns. Registry surface uses
register/onboard for rows. Maturity promoted draft→preview.

Breaking: removes deprecated DataGridColumn type alias from
createDataGrid exports (use DataGridColumnTicketInput instead)
Adds DataGrid fixture to satisfy the shipped-component gate in
a11y.browser.test.ts. Fixture renders a basic grid with header
columns and data rows using role="grid" semantics.
Row now always provides row context, even when id is undefined.
Cell gracefully handles missing row.id in span/editing lookups.
Fixes browser test failures where Cell was mounted inside Row
without an id prop.
…eraction

Composes the Splitter drag interaction pattern into a new DataGrid.ResizeHandle
component that enables column resizing with real drag handles:

- DataGridResizeHandle.vue: Drag handle using pointer/keyboard interaction
  - Maps resize deltas to context.layout.resize(column, delta)
  - Keyboard support: Arrow keys (1%), Page Up/Down (10%), Home/End (min/max)
  - WAI-ARIA role=separator with valuenow/min/max for accessibility
  - Respects column resizable, minSize, maxSize constraints
  - Conditional rendering via v-if when column.resizable is false

- DataGridColumn.vue: Added layout-aware slot props
  - isResizable, size, minSize, maxSize, offset exposed via slot props
  - Style binding for width percentage

- Barrel exports: Added DataGridResizeHandle and related types
- Surface test: Added DataGridResizeHandle to freeze array

- Browser tests: Added tests for ResizeHandle
  - Rendering, ARIA attributes, slot props
  - Integration test for layout.resize API

- Docs: Updated with resizable columns section, example, and FAQ

Header/Column → Splitter mapping:
- Each DataGrid.Column becomes a logical panel (via layout percentage sizing)
- DataGrid.ResizeHandle is placed between columns like Splitter.Handle
- Horizontal orientation: columns side-by-side, vertical separator handles
- Pin regions use separate size pools (left/scrollable/right)

Closes #513, relates to #507
Removes parallel DataGridResizeHandle implementation and shows
proper composition of shipped Splitter primitives:

- Remove DataGridResizeHandle.vue (mirrored Splitter.Handle mechanics)
- Remove ResizeHandle from barrel exports and surface freeze
- Remove DataGrid.resizeHandle locale key
- Update resizable.vue example to compose Splitter.Root/Panel/Handle
- Update browser tests to test layout API (resize, distribute)
- Update docs to explain Splitter composition pattern

The composition pattern:
- Splitter.Root wraps header row with orientation="horizontal"
- Splitter.Panel for each column header (handles flex-basis sizing)
- Splitter.Handle between panels (provides drag + WAI-ARIA separator)
- @layout event syncs Splitter sizes to context.layout.distribute(sizes)
- Body cells read widths from context.layout.columns

Closes #513
Fixes #513

- DataGridRow with resizable prop composes Splitter.Root
- DataGridColumn in resizable row composes Splitter.Panel
- DataGridHandle wraps Splitter.Handle for resize interaction
- @layout event syncs to context.layout.distribute
- Updated docs examples to use DataGrid.* parts
- Browser tests for Splitter composition added
Exercise resizable Splitter composition, Handle, layout.distribute, as/renderless, roles, sort/pin, and cell edit/span paths. Bind onLayout through mergeProps so Splitter layout actually reaches the grid.
Drop the dead ref.context path, type context without any, and only
mount Splitter panels after column sizes exist.
Match DataTable.Column and the Aria collection shape. Cell still
takes column= because that is a reference, not the part's identity.
onboard and row id were failing docs example typecheck because Root
erases T to Record<string, unknown>. Cast like DataTable. Drop the
Handle v-slot in favor of a data-state class.
@johnleider
johnleider force-pushed the cursor/datagrid-compound-shell-d904 branch from a2ee265 to 8c94689 Compare August 20, 2026 13:10
Stop using @VUE:mounted so columns exist before cells mount.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants