@box-kite/react 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/rules/box-kite-rules.md +1 -1
  2. package/.claude/skills/box-kite/SKILL.md +1 -1
  3. package/.claude/skills/box-kite/references/components.md +3 -3
  4. package/.claude/skills/box-kite/references/styling.md +2 -1
  5. package/.cursor/rules/box-kite.mdc +1 -1
  6. package/AGENTS.md +1 -1
  7. package/BOX_AI_CONTEXT.md +95 -77
  8. package/BOX_KITE_AI_CONTEXT.md +95 -77
  9. package/README.md +1 -1
  10. package/client.cjs +1 -1
  11. package/client.mjs +61 -51
  12. package/components/dataGrid/components/dataGridBottomBarSelection.d.ts +7 -0
  13. package/components/dataGrid/components/dataGridColumnsPanel.d.ts +10 -0
  14. package/components/dataGrid/components/dataGridDetailRow.d.ts +3 -3
  15. package/components/dataGrid/components/dataGridGroupRow.d.ts +3 -3
  16. package/components/dataGrid/components/dataGridHeader.d.ts +6 -2
  17. package/components/dataGrid/components/dataGridRow.d.ts +3 -3
  18. package/components/dataGrid/contracts/dataGridContract.d.ts +13 -0
  19. package/components/dataGrid/gridNavigationContext.d.ts +7 -2
  20. package/components/dataGrid/models/columnModel.d.ts +2 -0
  21. package/components/dataGrid/models/columnVisibilityModel.d.ts +16 -2
  22. package/components/dataGrid/models/editModel.d.ts +6 -4
  23. package/components/dataGrid/models/exportModel.d.ts +1 -1
  24. package/components/dataGrid/models/gridModel.d.ts +60 -2
  25. package/components/dataGrid/models/groupRowModel.d.ts +2 -2
  26. package/components/dataGrid/models/headerCellModel.d.ts +10 -0
  27. package/components/dataGrid/models/localeText.d.ts +108 -0
  28. package/components/dataGrid/models/rangeModel.d.ts +2 -0
  29. package/components/dataGrid/models/rowModel.d.ts +1 -1
  30. package/components/dataGrid/useFilterDraft.d.ts +9 -0
  31. package/components/dataGrid.cjs +3 -3
  32. package/components/dataGrid.d.ts +1 -0
  33. package/components/dataGrid.mjs +1631 -1120
  34. package/components/semantics.d.ts +29 -29
  35. package/docs/components.md +1 -1
  36. package/docs/props.md +1 -1
  37. package/layer.cjs +1 -1
  38. package/layer.mjs +4 -1
  39. package/package.json +76 -46
  40. package/react/theme/theme.d.ts +5 -3
  41. package/utils/dataGrid/activeCellStore.d.ts +14 -0
  42. package/utils/dataGrid/filterDraftUtils.d.ts +19 -0
  43. package/utils/dataGrid/headerCellUtils.d.ts +50 -0
  44. package/components/dataGrid/components/dataGridTopBarContextMenu.d.ts +0 -6
@@ -80,7 +80,7 @@ globs: '**/*.{ts,tsx,jsx}'
80
80
 
81
81
  46. **A scroll-driven animation is a timeline prop, and it costs no JavaScript at all.** `animationTimeline="scroll()"` runs an animation off the nearest scrollport's progress and `"view()"` off this element's own pass across it — no listener, no rAF, no state, and the browser drives it on the compositor. Where the scroller is not an ancestor, name one: `scrollTimeline="page block"` on the scroller (or `viewTimeline="card block"` on the subject), `animationTimeline="page"` on the animated element, and `timelineScope="page"` on their common ancestor when the two are not nested — the `--` on a name is optional and added for you, the axis is `block`/`inline`/`x`/`y` (logical, so it mirrors), and the longhands `scrollTimelineName`/`Axis` and `viewTimelineName`/`Axis` exist to override half a shorthand. **`animationRange` is which part of the pass the animation occupies**: `cover`, `contain`, `entry`, `exit`, `entry-crossing`, `exit-crossing`, each taking an offset — `animationRange="entry 0% entry 50%"`, or the `animationRangeStart`/`animationRangeEnd` pair — and `viewTimelineInset` shrinks the scrollport the pass is measured against, which is how a reveal fires before the element really reaches the edge. **Three traps.** The CSS `animation` shorthand **resets `animation-timeline`**, so a timeline declared before it is silently undone; the registry's order settles that for the props, but not for an `animation` written inside `css`. A scroll-driven animation has **no duration**, so `--transitionTime` cannot zero it and it is the one motion here that does _not_ stop itself under `prefers-reduced-motion` — `motionReduce={{ animation: 'none' }}` is the opt-in, and it is not optional. And where the browser has none (Safari, and Firefox behind a flag) the declaration is dropped rather than the animation: it runs on the **document timeline** over whatever duration it was given, so make the end state the resting state and a missing timeline degrades to "already arrived" instead of a loop.
82
82
 
83
- 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
83
+ 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has — `viewTransition="reveal"` grows the new theme out of the control that was pressed as a circle instead of cross-fading. **A theme change pauses every CSS transition** until it has painted (and until the view transition has finished), because `color` is inherited and the base class transitions `all`, so each element would otherwise start one of its own; the view transition is the animation, and it costs the same on any size of page. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
84
84
 
85
85
  48. **In development the console is part of the answer: read it.** Three mistakes write nothing, or the wrong thing, and say so in one `[box-kite]` warning that names what rendered and the line to write instead. **A value a prop does not take** — `bgColor="bleu-500"` → `wrote no CSS … Did you mean "blue-500"?`, `opacity={50}` → `It takes one of 0, 0.1, …`. **An HTML attribute at the top level** — `href`, `onClick`, `data-*`, `aria-*` → `write props={{ href: ... }}`. **A number in a unit it almost certainly did not mean**, which is valid CSS and so the one mistake nothing else catches: `fontSize={4}` is 4px text (`For 16px write fontSize={16}`), `lineHeight={1.5}` a 1.5px line, `transitionDuration={0.3}` 0.3ms, `brightness={1.1}` 1.1%. Each prints once per value, in any environment whose `NODE_ENV` is not `production` — a test run included, which is the cheapest place to see them — and never in a production build, where a rejected value is still dropped with nothing said. `Box.configure({ warnings: false })` turns them off; `check_styles` in `@box-kite/mcp` gives the same three verdicts before anything renders.
86
86
 
@@ -153,7 +153,7 @@ Detect the package manager from the lock file rather than asking:
153
153
 
154
154
  46. **A scroll-driven animation is a timeline prop, and it costs no JavaScript at all.** `animationTimeline="scroll()"` runs an animation off the nearest scrollport's progress and `"view()"` off this element's own pass across it — no listener, no rAF, no state, and the browser drives it on the compositor. Where the scroller is not an ancestor, name one: `scrollTimeline="page block"` on the scroller (or `viewTimeline="card block"` on the subject), `animationTimeline="page"` on the animated element, and `timelineScope="page"` on their common ancestor when the two are not nested — the `--` on a name is optional and added for you, the axis is `block`/`inline`/`x`/`y` (logical, so it mirrors), and the longhands `scrollTimelineName`/`Axis` and `viewTimelineName`/`Axis` exist to override half a shorthand. **`animationRange` is which part of the pass the animation occupies**: `cover`, `contain`, `entry`, `exit`, `entry-crossing`, `exit-crossing`, each taking an offset — `animationRange="entry 0% entry 50%"`, or the `animationRangeStart`/`animationRangeEnd` pair — and `viewTimelineInset` shrinks the scrollport the pass is measured against, which is how a reveal fires before the element really reaches the edge. **Three traps.** The CSS `animation` shorthand **resets `animation-timeline`**, so a timeline declared before it is silently undone; the registry's order settles that for the props, but not for an `animation` written inside `css`. A scroll-driven animation has **no duration**, so `--transitionTime` cannot zero it and it is the one motion here that does _not_ stop itself under `prefers-reduced-motion` — `motionReduce={{ animation: 'none' }}` is the opt-in, and it is not optional. And where the browser has none (Safari, and Firefox behind a flag) the declaration is dropped rather than the animation: it runs on the **document timeline** over whatever duration it was given, so make the end state the resting state and a missing timeline degrades to "already arrived" instead of a loop.
155
155
 
156
- 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
156
+ 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has — `viewTransition="reveal"` grows the new theme out of the control that was pressed as a circle instead of cross-fading. **A theme change pauses every CSS transition** until it has painted (and until the view transition has finished), because `color` is inherited and the base class transitions `all`, so each element would otherwise start one of its own; the view transition is the animation, and it costs the same on any size of page. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
157
157
 
158
158
  48. **In development the console is part of the answer: read it.** Three mistakes write nothing, or the wrong thing, and say so in one `[box-kite]` warning that names what rendered and the line to write instead. **A value a prop does not take** — `bgColor="bleu-500"` → `wrote no CSS … Did you mean "blue-500"?`, `opacity={50}` → `It takes one of 0, 0.1, …`. **An HTML attribute at the top level** — `href`, `onClick`, `data-*`, `aria-*` → `write props={{ href: ... }}`. **A number in a unit it almost certainly did not mean**, which is valid CSS and so the one mistake nothing else catches: `fontSize={4}` is 4px text (`For 16px write fontSize={16}`), `lineHeight={1.5}` a 1.5px line, `transitionDuration={0.3}` 0.3ms, `brightness={1.1}` 1.1%. Each prints once per value, in any environment whose `NODE_ENV` is not `production` — a test run included, which is the cheapest place to see them — and never in a production build, where a rejected value is still dropped with nothing said. `Box.configure({ warnings: false })` turns them off; `check_styles` in `@box-kite/mcp` gives the same three verdicts before anything renders.
159
159
 
@@ -229,12 +229,12 @@ import DataGrid from '@box-kite/react/components/dataGrid';
229
229
 
230
230
  **DataGridProps**: `data`, `def`, `component` (default `'datagrid'`), `loading`, `filters` (predicate[]), `page`/`onPageChange`, `onSortChange`, `onServerStateChange` (`{ page, pageSize, sortColumn, sortDirection, columnFilters, globalFilterValue }`), `onSelectionChange` (`{ action, selectedRowKeys, affectedRowKeys, isAllSelected }`), `expandedRowKeys`/`onExpandedRowKeysChange`, `globalFilterValue`/`onGlobalFilterChange`, `columnFilters`/`onColumnFiltersChange`, `onCellEditsChange` (`edit`/`paste`/`clear`), `onRangeChange` (`(range, { reason })` with `select`/`extend`/`clear`; `range` is `{ startRow, endRow, columns, values() }` or `undefined`), `onPaste` (`{ range, applied, rejected, skipped }`, once per Ctrl+V).
231
231
 
232
- **GridDefinition**: `columns` (required), `rowKey`, `rowHeight` (px, default 48), `visibleRowsCount` (number/`'all'`), `showRowNumber` (bool/`{ pinned?, width? }`), `rowSelection` (bool/`{ pinned? }`), `rowDetail` (`{ content, height?, expandOnRowClick?, pinned?, expandColumnWidth?, expandColumnHeader? }`), `pagination` (`{ totalCount, pageSize? }`), `topBar`/`bottomBar`, `title`/`topBarContent`, `globalFilter`, `globalFilterKeys`, `sortable`/`resizable` (default true), `contextMenu` (bool/`{ sort?, pin?, group? }`, default true), `resizerStyle` (`'visible'`/`'hover'`/`'hidden'`), `noDataComponent`, `editable`/`onCellEdit`, `tabNavigation` (`'none'` default — APG's single tab stop — or `'cells'`, the spreadsheet reading), `rangeSelection` (a drag or Shift+arrow marks a block of cells; `Ctrl+C` copies it as TSV and `Ctrl+V` fills it back through `onCellEdit`, one judgement per cell — both need no opt-in for the current cell alone).
232
+ **GridDefinition**: `columns` (required), `rowKey`, `rowHeight` (px, default 48), `visibleRowsCount` (number/`'all'`), `showRowNumber` (bool/`{ pinned?, width? }`), `rowSelection` (bool/`{ pinned? }` — the header checkbox acts on the rows the filters leave on screen, and the bottom bar counts selected rows the filters hide, with Show selected and Clear selection), `rowDetail` (`{ content, height?, expandOnRowClick?, pinned?, expandColumnWidth?, expandColumnHeader? }`), `pagination` (`{ totalCount, pageSize? }`), `topBar`/`bottomBar`, `title`/`topBarContent`, `globalFilter`, `globalFilterKeys`, `sortable`/`resizable` (default true), `contextMenu` (bool/`{ sort?, pin?, group? }`, default true), `resizerStyle` (`'visible'`/`'hover'`/`'hidden'`), `headerHover` (`'static'` — an inset pill per sortable cell — or `'sliding'`, one pill that flows between cells), `localeText` (any subset of `DataGridLocaleText` — every word the grid writes, for a translation; values with a number or a name in them are functions, and `DATA_GRID_LOCALE_TEXT` is the English to spread over), `noDataComponent`, `editable`/`onCellEdit`, `tabNavigation` (`'none'` default — APG's single tab stop — or `'cells'`, the spreadsheet reading), `rangeSelection` (a drag or Shift+arrow marks a block of cells; `Ctrl+C` copies it as TSV and `Ctrl+V` fills it back through `onCellEdit`, one judgement per cell — both need no opt-in for the current cell alone).
233
233
 
234
234
  **ColumnType**: `key`, `header`, `width` (px, default 200), `align` (`'start'`/`'end'`/`'center'`, or the physical `'left'`/`'right'`), `pin` (`'START'`/`'END'` — an edge of the **inline axis**, so a pinned column holds under either `dir`; `'LEFT'`/`'RIGHT'` are the older spelling of the same two), `columns` (grouped headers), `Cell` (`({ cell }) => ReactNode`), `sortable`/`resizable` (override grid), `flexible`, `filterable` (`true`=text, `{ type: 'number' }`, `{ type: 'multiselect' }`), `contextMenu` (override grid).
235
235
 
236
236
  **Server-side**: `def={{ pagination: { totalCount }, bottomBar: true }}` + `page={page}` + `onServerStateChange={fetchData}`.
237
237
 
238
- **Accessibility (A7, A10)**: the APG grid pattern, over a virtualized body — supplied whole, so add no roles and no `tabIndex`. The scrolling element is `role="grid"` (header and body are `rowgroup`s, rows `role="row"`, cells `role="gridcell"`/`columnheader`, scroll spacers `presentation`). `aria-rowcount`/`-colcount` describe the _whole_ grid and `aria-rowindex`/`-colindex` place each rendered cell in it, header rows first. Also `aria-sort` on sortable headers, `aria-selected` on rows only with `rowSelection`, `aria-busy` while `loading`, `aria-expanded` on group and expanded rows, and a live region announcing the selection count. Keyboard: one cell in the tab order; arrows move it, Home/End along the row, Ctrl+Home/End to the corners (scrolling to unrendered rows), PageUp/Down by a screenful, Enter/Space sorts a header or steps into the cell’s control, F2 always steps in, Escape steps back out. Down/Up keep the _column_ rather than the cell ordinal, so they land under where they started even through a grouped header. The column resizer is APG's window splitter — `role="separator"` with `aria-valuenow`/`-valuemin`/`-valuemax` in pixels; Tab or F2 reaches it, the arrows move it 16px, Home/End go to the narrowest the grid allows and to the grid's own width. Space on any cell selects its row, and on the header cell the select-all box sits in it toggles all of them. Every cell carries the **current-cell mark**, which survives the grid losing focus so `Ctrl+C` has something to act on; with `def.rangeSelection` the cells of a marked block carry `aria-selected` and the grid is `aria-multiselectable`, while a lone current cell carries neither. Every control the grid draws names itself after what it acts on ("Select row 4", "Column options for Age"); the column menu is the APG menu button. **Give it `def.title`** — a grid is not named by its rows. Every widget a cell holds is out of the page tab sequence, so the grid is one tab stop: Enter or F2 steps in, Tab walks the widgets **within that cell** and wraps, and Escape is the way out.
238
+ **Accessibility (A7, A10)**: the APG grid pattern, over a virtualized body — supplied whole, so add no roles and no `tabIndex`. The scrolling element is `role="grid"` (header and body are `rowgroup`s, rows `role="row"`, cells `role="gridcell"`/`columnheader`, scroll spacers `presentation`). `aria-rowcount`/`-colcount` describe the _whole_ grid and `aria-rowindex`/`-colindex` place each rendered cell in it, header rows first. Also `aria-sort` on sortable headers, `aria-selected` on rows only with `rowSelection`, `aria-busy` while `loading`, `aria-expanded` on group and expanded rows, and a live region announcing the selection count. Keyboard: one cell in the tab order; arrows move it, Home/End along the row, Ctrl+Home/End to the corners (scrolling to unrendered rows), PageUp/Down by a screenful, Enter/Space sorts a header or steps into the cell’s control, F2 always steps in, Escape steps back out. Down/Up keep the _column_ rather than the cell ordinal, so they land under where they started even through a grouped header. The column resizer is APG's window splitter — `role="separator"` with `aria-valuenow`/`-valuemin`/`-valuemax` in pixels; Tab or F2 reaches it, the arrows move it 16px, Home/End go to the narrowest the grid allows and to the grid's own width. Space on any cell selects its row, and on the header cell the select-all box sits in it toggles every row the filters leave on screen. Every cell carries the **current-cell mark**, which survives the grid losing focus so `Ctrl+C` has something to act on; with `def.rangeSelection` the cells of a marked block carry `aria-selected` and the grid is `aria-multiselectable`, while a lone current cell carries neither. Every control the grid draws names itself after what it acts on ("Select row 4", "Column options for Age"); the column menu is the APG menu button. **Give it `def.title`** — a grid is not named by its rows. Every widget a cell holds is out of the page tab sequence, so the grid is one tab stop: Enter or F2 steps in, Tab walks the widgets **within that cell** and wraps, and Escape is the way out.
239
239
 
240
- **Style tree**: `datagrid` > `content`, `topBar` > (`globalFilter` > `stats`, `columnGroups` > `icon`|`separator`|`item` > `icon`, `columnVisibility` > `badge`), `filter` > `cell` > `input`, `header` > `cell` > (`contextMenu` > `icon`|`tooltip` > `item` > `icon`|`separator`, `resizer`), `body` > (`cell` > `text`|`rowDetail`, `row`, `groupRow` > `expandButton`, `detailRow`, `empty`), `emptyColumns`, `bottomBar` > (`info`, `clearFilters`, `pagination` > `button`|`info`).
240
+ **Style tree**: `datagrid` > `content`, `topBar` > (`globalFilter` > `stats`, `columnGroups` > `icon`|`separator`|`item` > `icon`, `columnVisibility` > `badge`|`panel`|`search`|`searchIcon`|`searchInput`|`toolbar`|`count`|`action`|`list`|`section`|`group`|`item`|`empty`), `filter` > `cell` > `input`, `header` > `cell` > (`contextMenu` > `icon`|`tooltip` > `item` > `icon`|`separator`, `resizer`), `body` > (`cell` > `text`|`rowDetail`, `row`, `groupRow` > `expandButton`, `detailRow`, `empty`), `emptyColumns`, `bottomBar` > (`info`, `clearFilters`, `pagination` > `button`|`info`).
@@ -107,7 +107,8 @@ while the transition runs) and `viewTransitionClass` (a shared name, so `::view-
107
107
  `Box.viewTransition(update, { reducedMotion, types })` around the change itself: it feature-detects, hands back the same
108
108
  `ready`/`finished`/`updateCallbackDone`/`skip()` handle whether or not a transition ran, and **skips under reduced motion while still
109
109
  applying the update**. In React the update must be flushed inside the callback — `Box.viewTransition(() => flushSync(() => setTab(next)))` —
110
- or the screenshot catches the old state twice; `<Box.Theme viewTransition>` is that already done for a theme switch. A name _per list item_
110
+ or the screenshot catches the old state twice; `<Box.Theme viewTransition>` is that already done for a theme switch (`viewTransition="reveal"`
111
+ grows the new theme out of the pressed control instead), and a theme switch pauses every CSS transition while it is applied. A name _per list item_
111
112
  is a rule per item that is never freed, so that one belongs in `props={{ style: { viewTransitionName: id } }}`.
112
113
 
113
114
  **Gradients**: `bgGradient` — a gradient as a _value_, written as a record, so its stops are palette tokens and it is
@@ -88,7 +88,7 @@ This library is not in your training data: it was renamed at 1.0.0 (September 20
88
88
 
89
89
  46. **A scroll-driven animation is a timeline prop, and it costs no JavaScript at all.** `animationTimeline="scroll()"` runs an animation off the nearest scrollport's progress and `"view()"` off this element's own pass across it — no listener, no rAF, no state, and the browser drives it on the compositor. Where the scroller is not an ancestor, name one: `scrollTimeline="page block"` on the scroller (or `viewTimeline="card block"` on the subject), `animationTimeline="page"` on the animated element, and `timelineScope="page"` on their common ancestor when the two are not nested — the `--` on a name is optional and added for you, the axis is `block`/`inline`/`x`/`y` (logical, so it mirrors), and the longhands `scrollTimelineName`/`Axis` and `viewTimelineName`/`Axis` exist to override half a shorthand. **`animationRange` is which part of the pass the animation occupies**: `cover`, `contain`, `entry`, `exit`, `entry-crossing`, `exit-crossing`, each taking an offset — `animationRange="entry 0% entry 50%"`, or the `animationRangeStart`/`animationRangeEnd` pair — and `viewTimelineInset` shrinks the scrollport the pass is measured against, which is how a reveal fires before the element really reaches the edge. **Three traps.** The CSS `animation` shorthand **resets `animation-timeline`**, so a timeline declared before it is silently undone; the registry's order settles that for the props, but not for an `animation` written inside `css`. A scroll-driven animation has **no duration**, so `--transitionTime` cannot zero it and it is the one motion here that does _not_ stop itself under `prefers-reduced-motion` — `motionReduce={{ animation: 'none' }}` is the opt-in, and it is not optional. And where the browser has none (Safari, and Firefox behind a flag) the declaration is dropped rather than the animation: it runs on the **document timeline** over whatever duration it was given, so make the end state the resting state and a missing timeline degrades to "already arrived" instead of a loop.
90
90
 
91
- 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
91
+ 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has — `viewTransition="reveal"` grows the new theme out of the control that was pressed as a circle instead of cross-fading. **A theme change pauses every CSS transition** until it has painted (and until the view transition has finished), because `color` is inherited and the base class transitions `all`, so each element would otherwise start one of its own; the view transition is the animation, and it costs the same on any size of page. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
92
92
 
93
93
  48. **In development the console is part of the answer: read it.** Three mistakes write nothing, or the wrong thing, and say so in one `[box-kite]` warning that names what rendered and the line to write instead. **A value a prop does not take** — `bgColor="bleu-500"` → `wrote no CSS … Did you mean "blue-500"?`, `opacity={50}` → `It takes one of 0, 0.1, …`. **An HTML attribute at the top level** — `href`, `onClick`, `data-*`, `aria-*` → `write props={{ href: ... }}`. **A number in a unit it almost certainly did not mean**, which is valid CSS and so the one mistake nothing else catches: `fontSize={4}` is 4px text (`For 16px write fontSize={16}`), `lineHeight={1.5}` a 1.5px line, `transitionDuration={0.3}` 0.3ms, `brightness={1.1}` 1.1%. Each prints once per value, in any environment whose `NODE_ENV` is not `production` — a test run included, which is the cheapest place to see them — and never in a production build, where a rejected value is still dropped with nothing said. `Box.configure({ warnings: false })` turns them off; `check_styles` in `@box-kite/mcp` gives the same three verdicts before anything renders.
94
94
 
package/AGENTS.md CHANGED
@@ -120,7 +120,7 @@ Every value there is a prop, and every prop value becomes one class the engine i
120
120
 
121
121
  46. **A scroll-driven animation is a timeline prop, and it costs no JavaScript at all.** `animationTimeline="scroll()"` runs an animation off the nearest scrollport's progress and `"view()"` off this element's own pass across it — no listener, no rAF, no state, and the browser drives it on the compositor. Where the scroller is not an ancestor, name one: `scrollTimeline="page block"` on the scroller (or `viewTimeline="card block"` on the subject), `animationTimeline="page"` on the animated element, and `timelineScope="page"` on their common ancestor when the two are not nested — the `--` on a name is optional and added for you, the axis is `block`/`inline`/`x`/`y` (logical, so it mirrors), and the longhands `scrollTimelineName`/`Axis` and `viewTimelineName`/`Axis` exist to override half a shorthand. **`animationRange` is which part of the pass the animation occupies**: `cover`, `contain`, `entry`, `exit`, `entry-crossing`, `exit-crossing`, each taking an offset — `animationRange="entry 0% entry 50%"`, or the `animationRangeStart`/`animationRangeEnd` pair — and `viewTimelineInset` shrinks the scrollport the pass is measured against, which is how a reveal fires before the element really reaches the edge. **Three traps.** The CSS `animation` shorthand **resets `animation-timeline`**, so a timeline declared before it is silently undone; the registry's order settles that for the props, but not for an `animation` written inside `css`. A scroll-driven animation has **no duration**, so `--transitionTime` cannot zero it and it is the one motion here that does _not_ stop itself under `prefers-reduced-motion` — `motionReduce={{ animation: 'none' }}` is the opt-in, and it is not optional. And where the browser has none (Safari, and Firefox behind a flag) the declaration is dropped rather than the animation: it runs on the **document timeline** over whatever duration it was given, so make the end state the resting state and a missing timeline degrades to "already arrived" instead of a loop.
122
122
 
123
- 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
123
+ 47. **A view transition is two props and one call.** `Box.viewTransition(() => applyChange())` runs a DOM change inside `document.startViewTransition` where the browser has one and plainly where it has not — the same `ready`/`finished`/`updateCallbackDone` promises and `skip()` either way, so a caller has one code path rather than two — and `viewTransitionName="header"` is what makes an element animate _from where it was to where it is_ instead of being cross-faded along with the page. `viewTransitionClass="card"` groups several names, so `::view-transition-group(.card)` styles them at once. **In React the update has to be flushed**: the browser screenshots the page the moment the callback returns and a `setState` has not rendered by then, so a hand-rolled version captures the old state twice and nothing appears to move — write `Box.viewTransition(() => flushSync(() => setTheme('dark')))`, or use **`<Box.Theme viewTransition>`**, which is that call for the one change every app has — `viewTransition="reveal"` grows the new theme out of the control that was pressed as a circle instead of cross-fading. **A theme change pauses every CSS transition** until it has painted (and until the view transition has finished), because `color` is inherited and the base class transitions `all`, so each element would otherwise start one of its own; the view transition is the animation, and it costs the same on any size of page. **A name has to be unique in the document while the transition runs**, so a list wants a name per item — and a class per item is a rule this library would never free, which is why that one goes in `props={{ style: { viewTransitionName: id } }}`, the exception `useAnchorPosition`'s anchor name and a `Slider` thumb already take. Reduced motion **skips** the transition by default and keeps the update, a whole-page cross-fade being exactly the motion the preference is about; `{ reducedMotion: 'play' }` is the opt-out, and `{ types: ['forward'] }` is what `:active-view-transition-type()` selects on.
124
124
 
125
125
  48. **In development the console is part of the answer: read it.** Three mistakes write nothing, or the wrong thing, and say so in one `[box-kite]` warning that names what rendered and the line to write instead. **A value a prop does not take** — `bgColor="bleu-500"` → `wrote no CSS … Did you mean "blue-500"?`, `opacity={50}` → `It takes one of 0, 0.1, …`. **An HTML attribute at the top level** — `href`, `onClick`, `data-*`, `aria-*` → `write props={{ href: ... }}`. **A number in a unit it almost certainly did not mean**, which is valid CSS and so the one mistake nothing else catches: `fontSize={4}` is 4px text (`For 16px write fontSize={16}`), `lineHeight={1.5}` a 1.5px line, `transitionDuration={0.3}` 0.3ms, `brightness={1.1}` 1.1%. Each prints once per value, in any environment whose `NODE_ENV` is not `production` — a test run included, which is the cheapest place to see them — and never in a production build, where a rejected value is still dropped with nothing said. `Box.configure({ warnings: false })` turns them off; `check_styles` in `@box-kite/mcp` gives the same three verdicts before anything renders.
126
126
 
package/BOX_AI_CONTEXT.md CHANGED
@@ -726,7 +726,7 @@ so a change that was a jump becomes a transition with nothing animated by hand.
726
726
  | `viewTransitionName` | Names this element, so it animates _from where it was to where it is_ instead of being cross-faded with the page. A `<custom-ident>`, **not** dashed |
727
727
  | `viewTransitionClass` | A name shared by several elements, so `::view-transition-group(.card)` styles them together. Space-separated for more than one |
728
728
  | `Box.viewTransition(update, options)` | Runs `update` inside `document.startViewTransition` where the browser has one, and plainly where it has not |
729
- | `<Box.Theme viewTransition>` | That call around a theme change, `flushSync` included |
729
+ | `<Box.Theme viewTransition>` | That call around a theme change, `flushSync` included. `viewTransition="reveal"` grows the new theme out of the pressed control as a circle |
730
730
 
731
731
  ```tsx
732
732
  const handle = Box.viewTransition(() => flushSync(() => setTab(next)), { types: ['forward'] });
@@ -756,7 +756,9 @@ interface ViewTransitionHandle {
756
756
  - **In React the update has to be flushed.** The browser takes its second screenshot the moment the
757
757
  callback returns, and a `setState` has not rendered by then — so a hand-rolled version captures the old
758
758
  state twice and nothing appears to move. `flushSync` inside the callback is the fix, and
759
- `<Box.Theme viewTransition>` is it already done for the one change every app has.
759
+ `<Box.Theme viewTransition>` is it already done for the one change every app has. A theme change also
760
+ pauses every CSS transition until the new theme has painted (and the view transition has finished) — `color` is inherited and every Box transitions
761
+ `all`, so each element would start one of its own — which leaves the view transition as the only animation.
760
762
  - **A name has to be unique in the document while the transition runs.** Two elements sharing one is how a
761
763
  transition silently does nothing. The prop is for the handful of names a layout has (`header`, `main`);
762
764
  a name **per list item** is a rule per item that is never freed, so that one goes in
@@ -2365,34 +2367,36 @@ named ones carry the reason.
2365
2367
 
2366
2368
  ### GridDefinition
2367
2369
 
2368
- | Prop | Type | Default | Description |
2369
- | ------------------------- | ------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- |
2370
- | `columns` | `ColumnType[]` | required | Column definitions |
2371
- | `rowKey` | `keyof TRow \| (row) => Key` | auto | Unique row identifier |
2372
- | `rowHeight` | `number` | `48` | Row height in px |
2373
- | `visibleRowsCount` | `number \| 'all'` | `10` | Visible rows. `'all'` disables virtualization |
2374
- | `showRowNumber` | `boolean \| { pinned?, width? }` | `false` | Row number column |
2375
- | `rowSelection` | `boolean \| { pinned? }` | `false` | Checkbox selection column |
2376
- | `rowDetail` | `RowDetailConfig` | — | Expandable detail panel (see below) |
2377
- | `treeData` | `TreeDataConfig` | — | Rows that hold rows: a `treegrid` with a chevron column (see below) |
2378
- | `groupBy` | `Key[]` | — | The columns the grid starts grouped by, outermost first. The column menu takes it from there |
2379
- | `groupDefaultExpanded` | `boolean \| number` | `false` | Which group rows start open: `true` for all, a number for how many levels down |
2380
- | `editable` | `boolean` | `false` | Whether every column can be edited. A column’s own `editable` takes priority |
2381
- | `onCellEdit` | `(edit) => void \| boolean \| string` | — | Judges an edit and is told about it: nothing accepts, a string refuses. May be async |
2382
- | `dataSource` | `DataSource` | — | The grid fetches its own rows a block at a time; `data` is not read at all |
2383
- | `pagination` | `{ totalCount, pageSize? }` | — | Server-side pagination. Bypasses client-side filtering |
2384
- | `topBar` / `bottomBar` | `boolean` | `false` | Show top/bottom bars |
2385
- | `title` / `topBarContent` | `ReactNode` | — | Top bar content |
2386
- | `globalFilter` | `boolean` | `false` | Enable global fuzzy search |
2387
- | `globalFilterKeys` | `(keyof TRow)[]` | all | Limit global filter columns |
2388
- | `sortable` / `resizable` | `boolean` | `true` | Enable sorting/resizing for all columns |
2389
- | `contextMenu` | `boolean \| ContextMenuConfig` | `true` | Control column header context menu. `false` hides it. Object: `{ sort?, pin?, group? }` |
2390
- | `resizerStyle` | `'visible' \| 'hover' \| 'hidden'` | `'visible'` | Resizer handle visibility. `'hover'`: shows on header cell hover. `'hidden'`: invisible but resize still works |
2391
- | `noDataComponent` | `ReactNode` | `'empty'` | Custom empty state |
2392
- | `footer` | `boolean \| { label? }` | `false` | A pinned row of grand totals over every column with an `aggregate`. `label` replaces the default `Total` |
2393
- | `export` | `boolean \| ExportConfig` | `false` | Export buttons in the top bar: `{ csv?, xlsx?, fileName? }`. The writers load on the first press |
2394
- | `tabNavigation` | `'none' \| 'cells'` | `'none'` | What Tab does. `'none'` is APG's single tab stop; `'cells'` is the spreadsheet reading |
2395
- | `rangeSelection` | `boolean` | `false` | A drag or Shift+arrow marks a block of cells, and `Ctrl+C`/`Ctrl+V` copy and paste it as TSV (see below) |
2370
+ | Prop | Type | Default | Description |
2371
+ | ------------------------- | ------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2372
+ | `columns` | `ColumnType[]` | required | Column definitions |
2373
+ | `rowKey` | `keyof TRow \| (row) => Key` | auto | Unique row identifier |
2374
+ | `rowHeight` | `number` | `48` | Row height in px |
2375
+ | `visibleRowsCount` | `number \| 'all'` | `10` | Visible rows. `'all'` disables virtualization |
2376
+ | `showRowNumber` | `boolean \| { pinned?, width? }` | `false` | Row number column |
2377
+ | `rowSelection` | `boolean \| { pinned? }` | `false` | Checkbox selection column. The header checkbox acts on the rows the filters leave on screen; a filter never drops a selection, and the bottom bar counts what it hides, with **Show selected** and **Clear selection** |
2378
+ | `rowDetail` | `RowDetailConfig` | — | Expandable detail panel (see below) |
2379
+ | `treeData` | `TreeDataConfig` | — | Rows that hold rows: a `treegrid` with a chevron column (see below) |
2380
+ | `groupBy` | `Key[]` | — | The columns the grid starts grouped by, outermost first. The column menu takes it from there |
2381
+ | `groupDefaultExpanded` | `boolean \| number` | `false` | Which group rows start open: `true` for all, a number for how many levels down |
2382
+ | `editable` | `boolean` | `false` | Whether every column can be edited. A column’s own `editable` takes priority |
2383
+ | `onCellEdit` | `(edit) => void \| boolean \| string` | — | Judges an edit and is told about it: nothing accepts, a string refuses. May be async |
2384
+ | `dataSource` | `DataSource` | — | The grid fetches its own rows a block at a time; `data` is not read at all |
2385
+ | `pagination` | `{ totalCount, pageSize? }` | — | Server-side pagination. Bypasses client-side filtering |
2386
+ | `topBar` / `bottomBar` | `boolean` | `false` | Show top/bottom bars |
2387
+ | `title` / `topBarContent` | `ReactNode` | — | Top bar content |
2388
+ | `globalFilter` | `boolean` | `false` | Enable global fuzzy search |
2389
+ | `globalFilterKeys` | `(keyof TRow)[]` | all | Limit global filter columns |
2390
+ | `sortable` / `resizable` | `boolean` | `true` | Enable sorting/resizing for all columns |
2391
+ | `contextMenu` | `boolean \| ContextMenuConfig` | `true` | Control column header context menu. `false` hides it. Object: `{ sort?, pin?, group? }` |
2392
+ | `resizerStyle` | `'visible' \| 'hover' \| 'hidden'` | `'visible'` | Resizer handle visibility. `'hover'`: shows on header cell hover. `'hidden'`: invisible but resize still works |
2393
+ | `headerHover` | `'static' \| 'sliding'` | `'static'` | Header hover: an inset pill per sortable cell, or one pill that flows from cell to cell. Whole cell sorts on press |
2394
+ | `localeText` | `Partial<DataGridLocaleText>` | English | Every word the grid writes — labels, accessible names, announcements, messages. Values with a number or a name in them are functions; spread `DATA_GRID_LOCALE_TEXT` to start a translation |
2395
+ | `noDataComponent` | `ReactNode` | `'empty'` | Custom empty state |
2396
+ | `footer` | `boolean \| { label? }` | `false` | A pinned row of grand totals over every column with an `aggregate`. `label` replaces the default `Total` |
2397
+ | `export` | `boolean \| ExportConfig` | `false` | Export buttons in the top bar: `{ csv?, xlsx?, fileName? }`. The writers load on the first press |
2398
+ | `tabNavigation` | `'none' \| 'cells'` | `'none'` | What Tab does. `'none'` is APG's single tab stop; `'cells'` is the spreadsheet reading |
2399
+ | `rangeSelection` | `boolean` | `false` | A drag or Shift+arrow marks a block of cells, and `Ctrl+C`/`Ctrl+V` copy and paste it as TSV (see below) |
2396
2400
 
2397
2401
  ### ColumnType
2398
2402
 
@@ -2404,7 +2408,7 @@ named ones carry the reason.
2404
2408
  | `align` | `'start' \| 'end' \| 'center' \| 'left' \| 'right'` | `'left'` | Cell alignment. `start`/`end` follow the reading order; `left`/`right` stay on the screen side they name |
2405
2409
  | `pin` | `'START' \| 'END'` (also `'LEFT'`/`'RIGHT'`) | — | Pin to an edge of the **inline axis** (sticky on scroll), so it holds under either `dir`. `LEFT`/`RIGHT` are the older spelling of the same two |
2406
2410
  | `columns` | `ColumnType[]` | — | Nested columns (grouped header) |
2407
- | `Cell` | `({ cell }) => ReactNode` | — | Custom renderer. `cell`: `{ value, row, column, grid }` |
2411
+ | `Cell` | `({ cell }) => ReactNode` | — | Custom renderer. `cell`: `{ value, row, column, grid }`. It redraws when its row changes or the grid does — not when another row is selected |
2408
2412
  | `sortable` / `resizable` | `boolean` | inherits | Override grid-level setting |
2409
2413
  | `flexible` | `boolean` | `true` | Participate in flex width distribution |
2410
2414
  | `filterable` | `boolean \| FilterConfig` | — | `true` (text), `{ type: 'number', min?, max? }`, `{ type: 'multiselect', options? }` |
@@ -2787,52 +2791,66 @@ stops, as they were before A7.
2787
2791
 
2788
2792
  ### Component Style Tree
2789
2793
 
2790
- | Component Name | Description | Variants |
2791
- | --------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2792
- | `datagrid` | Root container | — |
2793
- | `datagrid.content` | Scroll container for header + body | — |
2794
- | `datagrid.topBar` | Top bar (title, filters, column groups) | — |
2795
- | `datagrid.topBar.globalFilter` | Global search wrapper | — |
2796
- | `datagrid.topBar.globalFilter.stats` | Filtered rows count badge | — |
2797
- | `datagrid.topBar.columnGroups` | Column group chips container | — |
2798
- | `datagrid.topBar.columnGroups.icon` | Column group icon | — |
2799
- | `datagrid.topBar.columnGroups.separator` | Separator between groups | — |
2800
- | `datagrid.topBar.columnGroups.item` | Column group chip | — |
2801
- | `datagrid.topBar.columnGroups.item.icon` | Remove icon on chip | — |
2802
- | `datagrid.topBar.columnVisibility` | Column visibility dropdown | — |
2803
- | `datagrid.topBar.columnVisibility.badge` | Hidden columns count badge | — |
2804
- | `datagrid.filter.cell` | Filter row cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned` |
2805
- | `datagrid.filter.cell.input` | Filter input container (text/number/multiselect) | — |
2806
- | `datagrid.header` | Header grid container (sticky) | — |
2807
- | `datagrid.header.cell` | Header cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned`, `isSortable`, `isRowSelection`, `isRowNumber`, `isFirstLeaf`, `isLastLeaf`, `isEmptyCell` |
2808
- | `datagrid.header.cell.contextMenu` | Column context menu button | — |
2809
- | `datagrid.header.cell.contextMenu.icon` | Context menu icon | — |
2810
- | `datagrid.header.cell.contextMenu.tooltip` | Context menu popup | — |
2811
- | `datagrid.header.cell.contextMenu.tooltip.item` | Context menu action | — |
2812
- | `datagrid.header.cell.contextMenu.tooltip.item.icon` | Action icon | — |
2813
- | `datagrid.header.cell.contextMenu.tooltip.item.separator` | Menu separator line | — |
2814
- | `datagrid.header.cell.resizer` | Column resize handle | — |
2815
- | `datagrid.body` | Body grid container (virtualized rows) | — |
2816
- | `datagrid.body.cell` | Body cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned`, `isRowNumber`, `isRowSelection`, `isRowSelected`, `isFirstLeaf`, `isLastLeaf`, `isEmptyCell`, `isEditing`, `isInvalid` |
2817
- | `datagrid.body.cell.text` | Default cell text renderer | — |
2818
- | `datagrid.body.cell.editor` | The open cell editor | `isPending` |
2819
- | `datagrid.body.cell.error` | What a refused value says, in the top layer | — |
2820
- | `datagrid.body.cell.rowDetail` | Row detail expand/collapse button | — |
2821
- | `datagrid.body.cell.tree` | Tree cell: the indent, the chevron and the value | — |
2822
- | `datagrid.body.cell.tree.toggle` | Tree expand/collapse chevron | `isExpanded` |
2823
- | `datagrid.body.cell.tree.spacer` | What a leaf puts where its chevron would be | — |
2824
- | `datagrid.body.row` | Data row (display: contents) | — |
2825
- | `datagrid.body.groupRow` | Group row (display: contents) | — |
2826
- | `datagrid.body.groupRow.expandButton` | Group expand/collapse button | — |
2827
- | `datagrid.body.detailRow` | Expanded detail row | — |
2828
- | `datagrid.body.empty` | Empty state container | — |
2829
- | `datagrid.emptyColumns` | No columns selected placeholder | — |
2830
- | `datagrid.bottomBar` | Bottom bar (row count, pagination) | — |
2831
- | `datagrid.bottomBar.info` | Status text ("Rows: ...", "Selected: ...") | — |
2832
- | `datagrid.bottomBar.clearFilters` | "Clear filters" link | — |
2833
- | `datagrid.bottomBar.pagination` | Pagination controls wrapper | — |
2834
- | `datagrid.bottomBar.pagination.button` | Pagination nav button | — |
2835
- | `datagrid.bottomBar.pagination.info` | Page info text ("1 of 5") | — |
2794
+ | Component Name | Description | Variants |
2795
+ | --------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2796
+ | `datagrid` | Root container | — |
2797
+ | `datagrid.content` | Scroll container for header + body | — |
2798
+ | `datagrid.topBar` | Top bar (title, filters, column groups) | — |
2799
+ | `datagrid.topBar.globalFilter` | Global search wrapper | — |
2800
+ | `datagrid.topBar.globalFilter.stats` | Filtered rows count badge (only without a bottom bar) | — |
2801
+ | `datagrid.topBar.columnGroups` | Column group chips container | — |
2802
+ | `datagrid.topBar.columnGroups.icon` | Column group icon | — |
2803
+ | `datagrid.topBar.columnGroups.separator` | Separator between groups | — |
2804
+ | `datagrid.topBar.columnGroups.item` | Column group chip | — |
2805
+ | `datagrid.topBar.columnGroups.item.icon` | Remove icon on chip | — |
2806
+ | `datagrid.topBar.columnVisibility` | Columns button (opens the columns panel) | — |
2807
+ | `datagrid.topBar.columnVisibility.badge` | Shown/total count, only while columns are hidden | `empty` |
2808
+ | `datagrid.topBar.columnVisibility.panel` | Columns panel body | — |
2809
+ | `datagrid.topBar.columnVisibility.search` | Search row (past six columns) | — |
2810
+ | `datagrid.topBar.columnVisibility.searchIcon` | Magnifier inside the search field | — |
2811
+ | `datagrid.topBar.columnVisibility.searchInput` | Search field | — |
2812
+ | `datagrid.topBar.columnVisibility.toolbar` | Count + Show all / Hide all row | — |
2813
+ | `datagrid.topBar.columnVisibility.count` | "6 of 8 shown" | — |
2814
+ | `datagrid.topBar.columnVisibility.action` | Show all / Hide all buttons | — |
2815
+ | `datagrid.topBar.columnVisibility.list` | Scrolling list of switches | — |
2816
+ | `datagrid.topBar.columnVisibility.section` | A run of columns under one header group | `divided` |
2817
+ | `datagrid.topBar.columnVisibility.group` | Header group name above its columns | — |
2818
+ | `datagrid.topBar.columnVisibility.item` | One column: label + switch (the `<label>`) | — |
2819
+ | `datagrid.topBar.columnVisibility.empty` | No-match message | — |
2820
+ | `datagrid.filter.cell` | Filter row cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned` |
2821
+ | `datagrid.filter.cell.input` | Filter input container (text/number/multiselect) | — |
2822
+ | `datagrid.header` | Header grid container (sticky) | — |
2823
+ | `datagrid.header.cell` | Header cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned`, `isSortable`, `isRowSelection`, `isRowNumber`, `isFirstLeaf`, `isLastLeaf`, `isEmptyCell` |
2824
+ | `datagrid.header.cell.contextMenu` | Column context menu button | — |
2825
+ | `datagrid.header.cell.contextMenu.icon` | Context menu icon | — |
2826
+ | `datagrid.header.cell.contextMenu.tooltip` | Context menu popup | — |
2827
+ | `datagrid.header.cell.contextMenu.tooltip.item` | Context menu action | — |
2828
+ | `datagrid.header.cell.contextMenu.tooltip.item.icon` | Action icon | — |
2829
+ | `datagrid.header.cell.contextMenu.tooltip.item.separator` | Menu separator line | — |
2830
+ | `datagrid.header.cell.resizer` | Column resize handle | — |
2831
+ | `datagrid.body` | Body grid container (virtualized rows) | — |
2832
+ | `datagrid.body.cell` | Body cell | `isPinned`, `isFirstLeftPinned`, `isLastLeftPinned`, `isFirstRightPinned`, `isLastRightPinned`, `isRowNumber`, `isRowSelection`, `isRowSelected`, `isFirstLeaf`, `isLastLeaf`, `isEmptyCell`, `isEditing`, `isInvalid` |
2833
+ | `datagrid.body.cell.text` | Default cell text renderer | — |
2834
+ | `datagrid.body.cell.editor` | The open cell editor | `isPending` |
2835
+ | `datagrid.body.cell.error` | What a refused value says, in the top layer | — |
2836
+ | `datagrid.body.cell.rowDetail` | Row detail expand/collapse button | — |
2837
+ | `datagrid.body.cell.tree` | Tree cell: the indent, the chevron and the value | — |
2838
+ | `datagrid.body.cell.tree.toggle` | Tree expand/collapse chevron | `isExpanded` |
2839
+ | `datagrid.body.cell.tree.spacer` | What a leaf puts where its chevron would be | — |
2840
+ | `datagrid.body.row` | Data row (display: contents) | — |
2841
+ | `datagrid.body.groupRow` | Group row (display: contents) | — |
2842
+ | `datagrid.body.groupRow.expandButton` | Group expand/collapse button | — |
2843
+ | `datagrid.body.detailRow` | Expanded detail row | — |
2844
+ | `datagrid.body.empty` | Empty state container | — |
2845
+ | `datagrid.emptyColumns` | No columns selected placeholder | — |
2846
+ | `datagrid.bottomBar` | Bottom bar (row count, pagination) | — |
2847
+ | `datagrid.bottomBar.info` | Status text ("Rows: ...", "Selected: ...") | — |
2848
+ | `datagrid.bottomBar.clearFilters` | "Clear filters" button | — |
2849
+ | `datagrid.bottomBar.selection` | The selection count and its buttons, at the bar's far end | — |
2850
+ | `datagrid.bottomBar.action` | "Show selected" and "Clear selection" | `pressed` (Show selected while it is on) |
2851
+ | `datagrid.bottomBar.pagination` | Pagination controls wrapper | — |
2852
+ | `datagrid.bottomBar.pagination.button` | Pagination nav button | — |
2853
+ | `datagrid.bottomBar.pagination.info` | Page info text ("1 of 5") | — |
2836
2854
 
2837
2855
  ---
2838
2856