@mortenbrudvik/waveui 0.15.0 → 0.16.0

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 (155) hide show
  1. package/README.md +274 -24
  2. package/dist/components/data-display/List.cjs +1 -1
  3. package/dist/components/data-display/List.cjs.map +1 -1
  4. package/dist/components/data-display/List.mjs +405 -219
  5. package/dist/components/data-display/List.mjs.map +1 -1
  6. package/dist/components/data-display/List.registry.cjs +1 -1
  7. package/dist/components/data-display/List.registry.cjs.map +1 -1
  8. package/dist/components/data-display/List.registry.mjs +12 -6
  9. package/dist/components/data-display/List.registry.mjs.map +1 -1
  10. package/dist/components/input/Combobox.cjs +1 -1
  11. package/dist/components/input/Combobox.cjs.map +1 -1
  12. package/dist/components/input/Combobox.mjs +163 -148
  13. package/dist/components/input/Combobox.mjs.map +1 -1
  14. package/dist/components/input/Dropdown.cjs +1 -1
  15. package/dist/components/input/Dropdown.cjs.map +1 -1
  16. package/dist/components/input/Dropdown.mjs +132 -117
  17. package/dist/components/input/Dropdown.mjs.map +1 -1
  18. package/dist/components/input/Listbox.cjs +1 -1
  19. package/dist/components/input/Listbox.cjs.map +1 -1
  20. package/dist/components/input/Listbox.mjs +90 -78
  21. package/dist/components/input/Listbox.mjs.map +1 -1
  22. package/dist/components/input/Option.cjs +1 -1
  23. package/dist/components/input/Option.cjs.map +1 -1
  24. package/dist/components/input/Option.data.cjs +2 -0
  25. package/dist/components/input/Option.data.cjs.map +1 -0
  26. package/dist/components/input/Option.data.mjs +114 -0
  27. package/dist/components/input/Option.data.mjs.map +1 -0
  28. package/dist/components/input/Option.mjs +21 -17
  29. package/dist/components/input/Option.mjs.map +1 -1
  30. package/dist/components/layout/FlatTree.cjs +2 -0
  31. package/dist/components/layout/FlatTree.cjs.map +1 -0
  32. package/dist/components/layout/FlatTree.context.cjs +2 -0
  33. package/dist/components/layout/FlatTree.context.cjs.map +1 -0
  34. package/dist/components/layout/FlatTree.context.mjs +20 -0
  35. package/dist/components/layout/FlatTree.context.mjs.map +1 -0
  36. package/dist/components/layout/FlatTree.item.cjs +2 -0
  37. package/dist/components/layout/FlatTree.item.cjs.map +1 -0
  38. package/dist/components/layout/FlatTree.item.mjs +90 -0
  39. package/dist/components/layout/FlatTree.item.mjs.map +1 -0
  40. package/dist/components/layout/FlatTree.mjs +276 -0
  41. package/dist/components/layout/FlatTree.mjs.map +1 -0
  42. package/dist/components/layout/Tree.core.cjs +1 -1
  43. package/dist/components/layout/Tree.core.cjs.map +1 -1
  44. package/dist/components/layout/Tree.core.mjs +166 -165
  45. package/dist/components/layout/Tree.core.mjs.map +1 -1
  46. package/dist/components/layout/Tree.selection.cjs +1 -1
  47. package/dist/components/layout/Tree.selection.cjs.map +1 -1
  48. package/dist/components/layout/Tree.selection.mjs +11 -57
  49. package/dist/components/layout/Tree.selection.mjs.map +1 -1
  50. package/dist/components/layout/useFlatTree.cjs +2 -0
  51. package/dist/components/layout/useFlatTree.cjs.map +1 -0
  52. package/dist/components/layout/useFlatTree.mjs +83 -0
  53. package/dist/components/layout/useFlatTree.mjs.map +1 -0
  54. package/dist/components/table/DataGrid.cjs +1 -1
  55. package/dist/components/table/DataGrid.cjs.map +1 -1
  56. package/dist/components/table/DataGrid.context.cjs +2 -0
  57. package/dist/components/table/DataGrid.context.cjs.map +1 -0
  58. package/dist/components/table/DataGrid.context.mjs +74 -0
  59. package/dist/components/table/DataGrid.context.mjs.map +1 -0
  60. package/dist/components/table/DataGrid.header.cjs +2 -0
  61. package/dist/components/table/DataGrid.header.cjs.map +1 -0
  62. package/dist/components/table/DataGrid.header.mjs +168 -0
  63. package/dist/components/table/DataGrid.header.mjs.map +1 -0
  64. package/dist/components/table/DataGrid.mjs +488 -591
  65. package/dist/components/table/DataGrid.mjs.map +1 -1
  66. package/dist/components/table/DataGrid.sizing.cjs +2 -0
  67. package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
  68. package/dist/components/table/DataGrid.sizing.mjs +187 -0
  69. package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
  70. package/dist/components/table/DataGrid.store.cjs +2 -0
  71. package/dist/components/table/DataGrid.store.cjs.map +1 -0
  72. package/dist/components/table/DataGrid.store.mjs +108 -0
  73. package/dist/components/table/DataGrid.store.mjs.map +1 -0
  74. package/dist/components/table/DataGrid.window.cjs +2 -0
  75. package/dist/components/table/DataGrid.window.cjs.map +1 -0
  76. package/dist/components/table/DataGrid.window.mjs +165 -0
  77. package/dist/components/table/DataGrid.window.mjs.map +1 -0
  78. package/dist/components/table/Table.cjs +1 -1
  79. package/dist/components/table/Table.cjs.map +1 -1
  80. package/dist/components/table/Table.mjs +74 -72
  81. package/dist/components/table/Table.mjs.map +1 -1
  82. package/dist/components/table/Table.resize.cjs +2 -0
  83. package/dist/components/table/Table.resize.cjs.map +1 -0
  84. package/dist/components/table/Table.resize.mjs +98 -0
  85. package/dist/components/table/Table.resize.mjs.map +1 -0
  86. package/dist/components/table/useGridNavigation.cjs +1 -1
  87. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  88. package/dist/components/table/useGridNavigation.mjs +343 -140
  89. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  90. package/dist/components/table/useTableColumnSizing.cjs +2 -0
  91. package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
  92. package/dist/components/table/useTableColumnSizing.mjs +76 -0
  93. package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
  94. package/dist/components/table/useTableFeatures.cjs +2 -0
  95. package/dist/components/table/useTableFeatures.cjs.map +1 -0
  96. package/dist/components/table/useTableFeatures.mjs +110 -0
  97. package/dist/components/table/useTableFeatures.mjs.map +1 -0
  98. package/dist/components/table/useTableSelection.cjs +2 -0
  99. package/dist/components/table/useTableSelection.cjs.map +1 -0
  100. package/dist/components/table/useTableSelection.mjs +39 -0
  101. package/dist/components/table/useTableSelection.mjs.map +1 -0
  102. package/dist/components/table/useTableSort.cjs +2 -0
  103. package/dist/components/table/useTableSort.cjs.map +1 -0
  104. package/dist/components/table/useTableSort.mjs +58 -0
  105. package/dist/components/table/useTableSort.mjs.map +1 -0
  106. package/dist/hooks/useActiveDescendant.cjs +1 -1
  107. package/dist/hooks/useActiveDescendant.cjs.map +1 -1
  108. package/dist/hooks/useActiveDescendant.mjs +33 -25
  109. package/dist/hooks/useActiveDescendant.mjs.map +1 -1
  110. package/dist/hooks/useListbox.cjs +1 -1
  111. package/dist/hooks/useListbox.cjs.map +1 -1
  112. package/dist/hooks/useListbox.mjs +140 -130
  113. package/dist/hooks/useListbox.mjs.map +1 -1
  114. package/dist/hooks/useRovingTabIndex.cjs +1 -1
  115. package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
  116. package/dist/hooks/useRovingTabIndex.mjs +1 -1
  117. package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
  118. package/dist/hooks/useSelection.cjs +2 -0
  119. package/dist/hooks/useSelection.cjs.map +1 -0
  120. package/dist/hooks/useSelection.mjs +57 -0
  121. package/dist/hooks/useSelection.mjs.map +1 -0
  122. package/dist/hooks/useVirtualWindow.cjs +2 -0
  123. package/dist/hooks/useVirtualWindow.cjs.map +1 -0
  124. package/dist/hooks/useVirtualWindow.mjs +102 -0
  125. package/dist/hooks/useVirtualWindow.mjs.map +1 -0
  126. package/dist/index.cjs +1 -1
  127. package/dist/index.d.cts +1542 -53
  128. package/dist/index.d.ts +1542 -53
  129. package/dist/index.mjs +147 -136
  130. package/dist/lib/classNames.cjs +1 -1
  131. package/dist/lib/classNames.cjs.map +1 -1
  132. package/dist/lib/classNames.mjs +68 -49
  133. package/dist/lib/classNames.mjs.map +1 -1
  134. package/dist/lib/columnSizing.cjs +2 -0
  135. package/dist/lib/columnSizing.cjs.map +1 -0
  136. package/dist/lib/columnSizing.mjs +27 -0
  137. package/dist/lib/columnSizing.mjs.map +1 -0
  138. package/dist/lib/flatTree.cjs +2 -0
  139. package/dist/lib/flatTree.cjs.map +1 -0
  140. package/dist/lib/flatTree.mjs +118 -0
  141. package/dist/lib/flatTree.mjs.map +1 -0
  142. package/dist/lib/selection.cjs +2 -0
  143. package/dist/lib/selection.cjs.map +1 -0
  144. package/dist/lib/selection.mjs +28 -0
  145. package/dist/lib/selection.mjs.map +1 -0
  146. package/dist/lib/treeCascade.cjs +2 -0
  147. package/dist/lib/treeCascade.cjs.map +1 -0
  148. package/dist/lib/treeCascade.mjs +52 -0
  149. package/dist/lib/treeCascade.mjs.map +1 -0
  150. package/dist/lib/virtual.cjs +2 -0
  151. package/dist/lib/virtual.cjs.map +1 -0
  152. package/dist/lib/virtual.mjs +54 -0
  153. package/dist/lib/virtual.mjs.map +1 -0
  154. package/dist/styles.css +1 -1
  155. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @mortenbrudvik/waveui
2
2
 
3
- A React component library inspired by the Fluent UI 2 design language: 79 accessible, composable components built with TypeScript, Tailwind CSS 4 and CSS custom properties, with light, dark and high-contrast themes and right-to-left support.
3
+ A React component library inspired by the Fluent UI 2 design language: 80 accessible, composable components built with TypeScript, Tailwind CSS 4 and CSS custom properties, with light, dark and high-contrast themes and right-to-left support.
4
4
 
5
5
  > This is **not** Microsoft's official `@fluentui/react-components` package.
6
6
 
7
7
  - [Requirements](#requirements) · [Installation](#installation) · [Quick start](#quick-start)
8
8
  - [Styles](#styles) · [Theming](#theming) · [Global effects](#global-effects) · [Styling hooks](#styling-hooks)
9
9
  - [React Server Components](#react-server-components) · [Components](#components) · [Usage notes](#usage-notes)
10
- - [Keyboard support](#keyboard-support) · [Hooks and utilities](#hooks-and-utilities) · [Upgrading from 0.14](#upgrading-from-014) · [Upgrading from 0.13](#upgrading-from-013) · [Upgrading from 0.12](#upgrading-from-012) · [Upgrading from 0.11](#upgrading-from-011) · [Upgrading from 0.10](#upgrading-from-010) · [Upgrading from 0.9](#upgrading-from-09) · [Upgrading from 0.8](#upgrading-from-08) · [Upgrading from 0.7](#upgrading-from-07) · [Upgrading from 0.6](#upgrading-from-06) · [Upgrading from 0.5](#upgrading-from-05) · [Upgrading from 0.4](#upgrading-from-04) · [Development](#development)
10
+ - [Large data](#large-data) · [Keyboard support](#keyboard-support) · [Hooks and utilities](#hooks-and-utilities) · [Upgrading from 0.15](#upgrading-from-015) · [Upgrading from 0.14](#upgrading-from-014) · [Upgrading from 0.13](#upgrading-from-013) · [Upgrading from 0.12](#upgrading-from-012) · [Upgrading from 0.11](#upgrading-from-011) · [Upgrading from 0.10](#upgrading-from-010) · [Upgrading from 0.9](#upgrading-from-09) · [Upgrading from 0.8](#upgrading-from-08) · [Upgrading from 0.7](#upgrading-from-07) · [Upgrading from 0.6](#upgrading-from-06) · [Upgrading from 0.5](#upgrading-from-05) · [Upgrading from 0.4](#upgrading-from-04) · [Development](#development)
11
11
 
12
12
  ## Requirements
13
13
 
@@ -373,7 +373,7 @@ export function checkedRows(tree: HTMLElement) {
373
373
  - Every component and hook module of the package starts with `"use client"`. The entry point, `cn`, the slot helpers, the class-name constants (`buttonClassNames`, …), `hsvToHex`, `hexToHsv`, `createToaster`, the date helpers (`addDays`, `addMonths`, `isSameDay`, `startOfWeek`, `getWeekNumber`), `getInitials`, `partitionAvatarGroupItems`, the breadcrumb helpers (`partitionBreadcrumbItems`, `truncateBreadcrumbLongName`, `truncateBreadcrumbLongTooltip`, `isTruncatableBreadcrumbContent`), `createTableColumn` and the types are server-safe, so a Server Component can import from `@mortenbrudvik/waveui`, read the constants, create a toaster handle, compute dates and render Wave components with serializable props. A DataGrid column with functions (`compare`, `renderCell`) is not serializable: define such columns in a client component.
374
374
  - A Server Component **cannot dot into a client component**: `Card.Header` throws "Cannot access Header on the server". Every sub-component is therefore also exported under a flat name. Use the flat names in Server Components and either form in client components.
375
375
  - Every compound can be composed in a Server Component. Parts written there reach the client as lazy references, and the compounds recognise them, so the server HTML and the client behaviour are the same as in a client file: Accordion items, Carousel slides, the options of Combobox, Dropdown and Listbox, the parts and options of TagPicker, DataGrid parts (`DataGrid.SelectionCell` included), the Table parts, Overflow items and dividers, Breadcrumb items (collapsed into the overflow menu too), the TeachingPopover parts, Drawer and Menu triggers, the Drawer header, body and footer parts, the tags of a TagGroup and the halves of an InteractionTag, the header of a `Menu.Group`, the halves of a `Menu.SplitGroup`, List, TabList and Tree items, Nav items, categories, section headers, dividers and app items, the header, body and footer of a NavDrawer, the stars of a Rating or RatingDisplay, the parts of Toast and MessageBar, the bars of a MessageBarGroup, the ColorPicker parts, `SwatchPicker.Row` and the swatches (`ColorSwatch`, `ImageSwatch`, `EmptySwatch`, in rows of the grid layout or the implicit ones), `Tree.ItemPersonaLayout`, a `Label` or `InfoLabel` passed as the `label` of a Field, and a `Button` passed as a MessageBar, SearchBox or Tag dismiss slot.
376
- - Event handlers and other functions cannot be passed from a Server Component; put interactive parts in a `'use client'` component.
376
+ - Event handlers and other functions cannot be passed from a Server Component; put interactive parts in a `'use client'` component. That includes every windowed or data-driven component: a `virtualWindow` (from the `useVirtualWindow` hook), `renderOption`, a FlatTree's model and `renderItem`, a windowed List's function child and `isSelectionDisabled` are client values. The server HTML of a windowed component holds its first window (the top of the list in the window's `initialViewportSize`), and that of a closed data-mode Combobox or Dropdown no options.
377
377
  - A `Popover`, `Dialog`, overlay `Drawer` or `NavDrawer`, popup `Menu`, `Combobox`, `Dropdown`, `DatePicker`, `TimePicker` or `TagPicker` rendered on the server with `defaultOpen` (or `open`) is closed in the server HTML and opens once hydrated, so no ARIA reference points at a popup that is not there. `onOpenChange` is not called for it.
378
378
  - An inline `Drawer` or `NavDrawer` (`type="inline"`) renders in the server HTML: open, or `hidden` when it is closed and kept mounted (`unmountOnClose={false}`).
379
379
  - A TeachingPopover with a `TeachingPopover.Trigger` written directly inside it starts closed. One that points at its Trigger or a `target` renders its surface once hydrated (the surface sits in a portal). Write `TeachingPopover.Surface` directly inside TeachingPopover too: a Surface rendered by a component of yours is found only once it has mounted, so the server HTML holds the popover's own surface as well (two dialogs with the same title id) until then. A Surface with children of yours is named by its `TeachingPopover.Title` once mounted (as a Dialog is by `Dialog.Title`), so its server HTML has no `aria-labelledby`.
@@ -457,6 +457,7 @@ export function SaveArea() {
457
457
  | `Dialog` | `DialogTrigger`, `DialogContent`, `DialogTitle`, `DialogFooter`, `DialogClose` |
458
458
  | `Drawer` | `DrawerTrigger`, `DrawerTitle`, `DrawerClose`, `DrawerHeader`, `DrawerHeaderTitle`, `DrawerHeaderNavigation`, `DrawerBody`, `DrawerFooter` |
459
459
  | `Dropdown` | `DropdownOption`, `DropdownOptionGroup` |
460
+ | `FlatTree` | `FlatTreeItem` (the row the tree renders; it is not written by hand) |
460
461
  | `InteractionTag` | `InteractionTagPrimary`, `InteractionTagSecondary` |
461
462
  | `List` | `ListItem` |
462
463
  | `Listbox` | `ListboxOption`, `ListboxOptionGroup` (also `Option`, `OptionGroup`) |
@@ -472,7 +473,7 @@ export function SaveArea() {
472
473
  | `Skeleton` | `SkeletonGroup` |
473
474
  | `Stepper` | `StepperStep` |
474
475
  | `SwatchPicker` | `SwatchPickerRow` |
475
- | `Table` | `TableHeader`, `TableHeaderCell`, `TableBody`, `TableRow`, `TableCell`, `TableSelectionCell`, `TableCellLayout`, `TableCellActions` |
476
+ | `Table` | `TableHeader`, `TableHeaderCell`, `TableBody`, `TableRow`, `TableCell`, `TableSelectionCell`, `TableCellLayout`, `TableCellActions`, `TableResizeHandle` |
476
477
  | `TabList` | `TabListTab`, `TabListPanel`, `TabListPanels` |
477
478
  | `TagPicker` | `TagPickerControl`, `TagPickerGroup`, `TagPickerInput`, `TagPickerButton`, `TagPickerList`, `TagPickerOption`, `TagPickerOptionGroup` (also `OptionGroup`) |
478
479
  | `TeachingPopover` | `TeachingPopoverTrigger`, `TeachingPopoverSurface`, `TeachingPopoverHeader`, `TeachingPopoverTitle`, `TeachingPopoverBody`, `TeachingPopoverFooter`, `TeachingPopoverCarousel`, `TeachingPopoverCarouselNav`, `TeachingPopoverCarouselPageCount`, `TeachingPopoverCarouselFooter` |
@@ -482,7 +483,7 @@ export function SaveArea() {
482
483
 
483
484
  ## Components
484
485
 
485
- 79 components, plus the `Portal` and `Presence` utilities. Every component accepts `ref` as a prop (React 19), a `className`, and the native attributes of its element. Storybook (`npm run dev`) shows every component with its props, states and themes.
486
+ 80 components, plus the `Portal` and `Presence` utilities. Every component accepts `ref` as a prop (React 19), a `className`, and the native attributes of its element. Storybook (`npm run dev`) shows every component with its props, states and themes.
486
487
 
487
488
  Your `className` is merged last with `cn()`, so it replaces a conflicting class **of the same variant**: `bg-error` replaces a resting `bg-primary`. A class behind a variant is replaced only by a class with the same variant. That matters for the gated hover and pressed classes (`not-disabled:not-aria-disabled:hover:…` / `…:active:…`) and the state classes (`data-[selected]:…`, `aria-disabled:…`): they stay next to yours and win while their state applies, because they are more specific. A bare `hover:bg-error` therefore does not override a Button's built-in hover color while the Button is enabled. It still paints on hover while the Button is disabled or `aria-disabled`, and on `appearance="transparent"`, which has no hover background. To override the gated and state classes, use the same prefix (`not-disabled:not-aria-disabled:hover:bg-error`, `data-[selected]:bg-error`) or the important modifier (`hover:bg-error!`); see [Buttons](#buttons).
488
489
 
@@ -513,9 +514,9 @@ Your `className` is merged last with `cn()`, so it replaces a conflicting class
513
514
  | `SearchBox` | Search input with a clear button; `size`, `appearance`, `onValueChange`; input attributes go to the `<input>`. The root draws the field, so `className` and `style` style the field box, and `contentBefore`/`contentAfter` sit beside the text. |
514
515
  | `Slider` | Styled native range input, filled up to the thumb; `size` (`small`, `medium`); `onValueChange(number)`. |
515
516
  | `SpinButton` | Numeric input with step buttons; `size`, `appearance`; typed text is a draft committed on blur or Enter; `min`, `max`, `step`, `largeStep`, `precision`; `displayValue` shows formatted text; `allowEmpty` allows a `null` value; holding a step button repeats it (see [SpinButton](#spinbutton)). |
516
- | `Combobox` | Editable combobox: typing filters the options (`filter` replaces the match, `query` controls the typed text); `multiselect` selects several values; `freeform` makes the typed text the value; a chevron (`expandIcon`) and `clearable`. `Combobox.Option`, `Combobox.OptionGroup` (see [Multi-select pickers](#multi-select-pickers) and [Filtering and async search](#filtering-and-async-search)). |
517
- | `Dropdown` | Select-only combobox (a button) with typeahead; `multiselect` selects several values; `clearable`, `expandIcon` and `renderValue`. `Dropdown.Option`, `Dropdown.OptionGroup`. |
518
- | `Listbox` | A list of options in the page that holds focus itself (`aria-activedescendant`), single-select or `multiselect`, with typeahead, groups and native form support; `Listbox.Option`, `Listbox.OptionGroup`. The listbox primitives under the pickers are public, for pickers of your own (see [Listbox and custom pickers](#listbox-and-custom-pickers)). |
517
+ | `Combobox` | Editable combobox: typing filters the options (`filter` replaces the match, `query` controls the typed text); `multiselect` selects several values; `freeform` makes the typed text the value; a chevron (`expandIcon`) and `clearable`. `Combobox.Option`, `Combobox.OptionGroup`, or the options as data (`items`, `renderOption`) with an optional `virtualWindow` for long lists (see [Multi-select pickers](#multi-select-pickers), [Filtering and async search](#filtering-and-async-search) and [Large data](#large-data)). |
518
+ | `Dropdown` | Select-only combobox (a button) with typeahead; `multiselect` selects several values; `clearable`, `expandIcon` and `renderValue`. `Dropdown.Option`, `Dropdown.OptionGroup`, or the options as data (`items`, `renderOption`) with an optional `virtualWindow` (see [Large data](#large-data)). |
519
+ | `Listbox` | A list of options in the page that holds focus itself (`aria-activedescendant`), single-select or `multiselect`, with typeahead, groups and native form support; `Listbox.Option`, `Listbox.OptionGroup`, or the options as data (`items`, `renderOption`) with an optional `virtualWindow`. The listbox primitives under the pickers are public, for pickers of your own (see [Listbox and custom pickers](#listbox-and-custom-pickers)). |
519
520
  | `TagPicker` | Multi-select combobox that shows the selection as removable tags (Tags in a TagGroup list named "Selected", one Tag size below the picker); `size` (`medium`, `large`, `extra-large`), `appearance`; the input is described by a summary of the selection ("Selected: Apple, Banana"), and additions and removals are announced; `filter` and `query` as in Combobox. A chevron opens the list (`expandIcon`), `clearable` removes every tag, `overflow="count"` keeps the tags on one line with a "+N" count, and `allowCreate` with `onOptionCreate` adds a tag of the user's own text. Composable: `TagPicker.Control`, `.Group`, `.Input` or `.Button` (a trigger without text entry), `.List`, `.Option` (`media`, `secondaryContent`) and `.OptionGroup`; a part without children renders its defaults (see [TagPicker parts and free tagging](#tagpicker-parts-and-free-tagging)). |
520
521
  | `DatePicker` | Date input with a calendar dialog (month and year views, week numbers, marked days, optional "Go to today" and Close buttons); `size`, `appearance`; locale-aware format and parse, `minDate`, `maxDate`, `disabledDates`, `onInvalidInput`. |
521
522
  | `Calendar` | The DatePicker's calendar in the page (`role="group"`): month and year views, week numbers, marked days and custom day content, week, work-week or month ranges, "Go to today", native form support (see [Calendar](#calendar)). |
@@ -547,7 +548,7 @@ Your `className` is merged last with `cn()`, so it replaces a conflicting class
547
548
  | `Persona` | Avatar with up to four lines of text (`primaryText` to `quaternaryText`), `textPosition` (`after`, `before`, `below`), `presenceOnly` and presence; sizes up to `huge`. |
548
549
  | `Divider` | Horizontal or vertical separator, optionally labelled. |
549
550
  | `Image` | `<img>` with `fit`, `shape`, `shadow`, `bordered`; warns in development when `alt` is missing. Never wider than its parent; with the default `fit` its height follows its width. `fit="none"` keeps the top-left corner in view, `"center"` the middle. |
550
- | `List` | Plain list, selectable listbox (single or multiple) or, with item `action`s, a grid; `navigationMode` (`none`, `items`, `composite`) gives a list without selection arrow keys, or pins the grid; `List.Item onAction`, `disabledSelection` and check marks (`checkmark`); in a selectable or navigable List item values are unique (development warning; see [Lists](#lists)). |
551
+ | `List` | Plain list, selectable listbox (single or multiple) or, with item `action`s, a grid; `navigationMode` (`none`, `items`, `composite`) gives a list without selection arrow keys, or pins the grid; `List.Item onAction`, `disabledSelection` and check marks (`checkmark`); `virtualWindow` with `count` and a function child renders only the items in view of a long list; in a selectable or navigable List item values are unique (development warning; see [Lists](#lists)). |
551
552
 
552
553
  ### Typography
553
554
 
@@ -563,6 +564,7 @@ Your `className` is merged last with `cn()`, so it replaces a conflicting class
563
564
  | `Accordion` | Disclosure sections (`Accordion.Item`, `.Trigger`, `.Panel`); single (default) or `type="multiple"`; `headingLevel`; panels collapse as they open and close (`Accordion.Panel motion`); item values are unique (development warning). |
564
565
  | `TabList` | Tabs (`TabList.Tab`, `.Panel`, `.Panels`) with automatic activation, or manual with `selectTabOnFocus={false}`; `orientation`, `appearance` (`transparent`, `subtle`, `subtle-circular`, `filled-circular`), `size`, a `Tab` `icon` (alone: icon-only); the selection line slides to the new tab (`motion`); tab values are unique (development warning). |
565
566
  | `Tree` | Hierarchical tree (`Tree.Item`) with expand/collapse and typeahead; single selection, or checkboxes with a cascading mixed state (`selectionMode="multiple"`); row `actions`, `aside` and `Tree.ItemPersonaLayout`; `navigationMode="treegrid"`; groups collapse as they open and close (`motion`) (see [Trees](#trees)). Nested items are `Tree.Item` elements in their parent's children (directly, in Fragments or from a render function), not rendered by a recursive component; values are unique. |
567
+ | `FlatTree` | A tree from flat data (`useFlatTree(items)`: items with a `parentValue`, in any order) for trees of thousands of items: the visible items as sibling `treeitem`s with their level and position, the nested Tree's keys, single selection and cascading checkboxes, and a `virtualWindow` that renders only the items in view (see [Trees](#trees) and [Large data](#large-data)). |
566
568
  | `Carousel` | Slides (`Carousel.Item` children, directly or in Fragments; a component that renders `Carousel.Item` is not a slide) with previous/next, a slide picker and optional auto-rotation with a pause control. `groupSize` (a number or `auto`) shows several at once, `draggable` drags them, `Carousel.Item` `thumbnail` shows thumbnails, `controlsLayout` places the buttons, `Carousel.Button` replaces one, and `motion` (`slide`, `fade` or `null`) with `motionDuration` sets how the page changes. |
567
569
  | `Overflow` | Hides items that do not fit in one row (or column, `orientation="vertical"`) and renders an overflow button (`useOverflowMenu` lists the hidden items); items hide by `priority` unless `pinned`, from the end or the start (`overflowDirection`), in groups with `Overflow.Divider`; `buttonPosition`, `minimumVisible`, `asChild` items, `onOverflowChange`; item ids are unique (development warning). The row has a 4px padding, so its items' focus rings are not clipped (see [Overflow](#overflow)). |
568
570
  | `Grid` | CSS grid with a column count and token gaps. |
@@ -606,8 +608,8 @@ Your `className` is merged last with `cn()`, so it replaces a conflicting class
606
608
 
607
609
  | Component | Description |
608
610
  | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
609
- | `Table` | Static table: `Table.Header`, `Table.HeaderCell` (`sortable`, with your sort state), `Table.Body`, `Table.Row`, `Table.Cell`, and the cell parts `Table.SelectionCell`, `Table.CellLayout` and `Table.CellActions`; `striped`; scrolls horizontally (see [Static tables](#static-tables)). |
610
- | `DataGrid` | Interactive grid (APG grid keyboard model) with row selection and sortable headers; header from children or `columns`, rows from `items` with typed columns (`createTableColumn`) that sort themselves; `selectOnRowClick`, `selectionAppearance`, `subtleSelection`, `DataGrid.SelectionCell`; row ids are unique (development warning; see [Sorting and selecting in a DataGrid](#sorting-and-selecting-in-a-datagrid)). |
611
+ | `Table` | Static table: `Table.Header`, `Table.HeaderCell` (`sortable`, with your sort state), `Table.Body`, `Table.Row`, `Table.Cell`, and the cell parts `Table.SelectionCell`, `Table.CellLayout`, `Table.CellActions` and `Table.ResizeHandle` (a column's resize separator); `striped`; scrolls horizontally; its state can come from the headless `useTableFeatures` with sort, selection and column-sizing plugins (see [Static tables](#static-tables)). |
612
+ | `DataGrid` | Interactive grid (APG grid keyboard model) with row selection and sortable headers; header from children or `columns`, rows from `items` with typed columns (`createTableColumn`) that sort themselves; with `items`, selection and select-all follow the data (`isSelectionDisabled`); `selectOnRowClick`, `selectionAppearance`, `subtleSelection`, `DataGrid.SelectionCell`; `resizableColumns` and column widths (`columnSizingOptions`, `columnWidths`); `virtualWindow` renders only the rows in view; row ids are unique (development warning; see [Sorting and selecting in a DataGrid](#sorting-and-selecting-in-a-datagrid) and [Large data](#large-data)). |
611
613
 
612
614
  ### Provider
613
615
 
@@ -1087,10 +1089,11 @@ export function FruitList() {
1087
1089
  - **Focus and selection.** One Tab stop. The active option exists only while the list has focus: the selected option (with `multiselect`, the first selected one in list order), else the first; while no option can be active, the list draws its own focus ring. Space and Enter select, so the selection does not follow the active option; a single-select Listbox never deselects. A press on an option focuses the list and makes that option active without scrolling, so the click selects it even while the selected option is scrolled out of view. Tab, Escape and the Alt+arrow keys are left to the page: an enclosing Dialog closes on Escape.
1088
1090
  - **Props.** `multiselect` (an array value, announced toggles; see [Multi-select pickers](#multi-select-pickers)), `onActiveOptionChange` (`null` when focus leaves the list), `disabled` (out of the Tab order, selects and submits nothing), `disabledOptionsFocusable`, `autoFocus`, `name`, `form` and `required`, and Field wiring. It has no built-in height: give it one through `className` and it scrolls.
1089
1091
  - **Option groups.** `OptionGroup`'s `label` is optional and takes any text content (an icon next to the text is fine). Without one no heading renders, and `aria-label` or `aria-labelledby` names the group; these two always land on the `role="group"` list. A group without a name warns in development.
1092
+ - **Data mode** (Listbox, Combobox, Dropdown): `items` (`ListboxItem`: `value`, `label`, and optionally `textValue`, `disabled` and `hidden`; values unique) replace the option children, which are then neither read nor rendered (passing both warns once). The keys, typeahead, the selected labels and the Combobox filter come from `items`, also for a selected item whose option is not rendered, and every option carries `aria-posinset` and `aria-setsize` among the shown options. `renderOption(item, state)` draws an option (`state`: `selected`, `active`, and `index` among the shown options; `ListboxOptionRenderState`): return an `Option`, or an option of yours on `useListboxOption`, with the item's `value`; it is called again only when its item, state or position, or the function, change, so pass a stable function. A closed Combobox or Dropdown in data mode renders no options (its labels come from `items`). `virtualWindow` windows a long data-mode list (see [Large data](#large-data)).
1090
1093
 
1091
1094
  **Pickers of your own.** The pieces under Dropdown, Combobox and TagPicker are public:
1092
1095
 
1093
- - `useListbox(options)` holds the listbox: its keys, typeahead, filtering, the active option and the registration of the options. `mode` is `'select-only'` (a button combobox, as Dropdown), `'editable'` (an `<input>` combobox, as Combobox) or `'standalone'` (a list that holds focus, as Listbox). You own the open state (`open`, `onOpenChange(open, details)`) and the value (`selectedValues`, `onSelect(value, details)`, with `details.item` and `details.event`); spread `getComboboxProps()` on your combobox and attach `onKeyDown` and `onKeyUp`.
1096
+ - `useListbox(options)` holds the listbox: its keys, typeahead, filtering, the active option and the registration of the options. `mode` is `'select-only'` (a button combobox, as Dropdown), `'editable'` (an `<input>` combobox, as Combobox) or `'standalone'` (a list that holds focus, as Listbox). You own the open state (`open`, `onOpenChange(open, details)`) and the value (`selectedValues`, `onSelect(value, details)`, with `details.item` and `details.event`); spread `getComboboxProps()` on your combobox and attach `onKeyDown` and `onKeyUp`. With `items` (data mode) it reads no children, so you may render only the shown options (`listbox.items`), or a window of them: `onScrollToItem(index)` is then called first for every move that scrolls the active option into view, with its index among the shown options, so pass `(index) => virtualWindow.scrollToIndex(index, { align: 'auto' })` and keep the active option rendered.
1094
1097
  - `useListboxPopup(options)` dismisses the popup (Escape, a press outside, focus leaving: `DismissReason`) and positions it, and `ListboxSurface` renders the list: in a portaled surface while it is shown, `hidden` in place otherwise, so the options register from the first render. `listClassName` and `surfaceClassName` are merged last (`max-h-*` replaces the list's height). Two duties remain yours: set your combobox's `aria-expanded` from `expanded` after spreading `getComboboxProps()`, and while the listbox is open with nothing to show, close it on Escape yourself (outside an IME composition) without calling `listbox.onKeyDown`, so the Escape reaches an enclosing layer.
1095
1098
  - `ListboxProvider` provides the listbox's context to its options when you render the list yourself instead of through `ListboxSurface`. `ListboxSurface`'s `listProps` (`ListboxSurfaceListProps`) pass attributes and a `ref` to the list element; the listbox's own `aria-multiselectable` and `aria-activedescendant` always win.
1096
1099
  - Options are `Option` and `OptionGroup` (or `TagPicker.Option`, an Option with `media` and `secondaryContent`), or components of your own on `useListboxOption`, marked with `markListboxElement` so that `collectOptionLabels(children)` knows their labels before they register (in the server HTML and the first render).
@@ -2190,10 +2193,41 @@ const rows = React.useMemo(() => {
2190
2193
  // sort={sort} onSortChange={setSort}> <DataGrid.Body>{rows.map(…)}</DataGrid.Body> </DataGrid>
2191
2194
  ```
2192
2195
 
2193
- - **Row selection.** `selectionMode` (`single`, `multiple`) with `selectedItems`, `defaultSelectedItems` and `onSelectedItemsChange(selectedItems, details)`. With `selectionMode="single"`, pass at most one id (with several, the first one whose row is rendered is selected, with a development warning). Each row's selection control is named after the row's first `DataGrid.Cell`, `<td>` or `<th>` child (Fragments are looked into); a cell rendered by another component is not seen, so pass `selectionLabel` to `DataGrid.Row` then.
2196
+ - **Row selection.** `selectionMode` (`single`, `multiple`) with `selectedItems`, `defaultSelectedItems` and `onSelectedItemsChange(selectedItems, details)`. With `selectionMode="single"`, pass at most one id (with several, the first one is selected, or in a grid without `items` the first one whose row is rendered, with a development warning). Each row's selection control is named after the row's first `DataGrid.Cell`, `<td>` or `<th>` child (Fragments are looked into); a cell rendered by another component is not seen, so pass `selectionLabel` to `DataGrid.Row` then.
2194
2197
  - **Selecting on a row click.** `selectOnRowClick` toggles a row when it is clicked (in single mode it selects the row alone, or clears it). The row's `onClick` runs first, and its `preventDefault()` keeps the selection. A click on a link, a button, an input or another interactive element in the row, a click from a popup opened from the row (a Menu in a cell), and a click that ends a text selection never toggle; Space on a cell toggles either way.
2195
2198
  - **Looks.** `selectionAppearance` fills a selected row with the selected token (`brand`, the default), the subtle one (`neutral`) or not at all (`none`; the control, `aria-selected` and the forced-colors outline still show it). `subtleSelection` draws an unchecked row control only while its row is hovered or holds focus, and the select-all while the grid is (always in forced colors and on touch screens); the controls stay focusable and named.
2196
- - **Per row.** `DataGrid.Row selectionCellProps` takes `disabled` (a natively disabled control: row clicks, Space and select-all skip the row, and select-all counts the enabled rows only), `invisible` (no visible control; the row still selects), `className` and `aria-label`. `DataGrid.SelectionCell` places the selection cell where you write it in a row (the select-all in a header row), instead of the automatic first cell.
2199
+ - **Selection follows the data** in a grid with `items` (0.16): select-all selects every item whose selection is not disabled, rendered or not, and its checkbox shows their state; when all of them are selected it removes only their ids. A row click, Space and a row's control change one id, and the ids of rows that are not rendered (filtered out, on another page, outside a `virtualWindow`, restored by id) stay selected and are reported by `onSelectedItemsChange`, until your code removes them. Select-all reports the ids outside `items` first, then the selected ids in `items` order. Both use the ids from `getRowId`: a row that sets its own, different `rowId` is outside them (a development warning says so). A grid of `DataGrid.Row` children keeps the 0.15 rule: ids of rows that are not rendered drop out at the next change.
2200
+ - **Per row.** `DataGrid.Row selectionCellProps` takes `disabled` (a natively disabled control: row clicks, Space and select-all skip the row, and select-all counts the enabled rows only), `invisible` (no visible control; the row still selects), `className` and `aria-label`. `DataGrid.SelectionCell` places the selection cell where you write it in a row (the select-all in a header row), instead of the automatic first cell. In a grid with `items`, `isSelectionDisabled(item, index)` disables items whether their rows are rendered or not (select-all skips them and keeps their state) and is the default `disabled` of each rendered row's selection cell; a cell's own `disabled` wins for its control, and a value that disagrees warns in development. Define it outside the component or memoise it: it is called for every item when `items`, `getRowId` or the function change.
2201
+ - **Resizable columns.** With `columns` and `items`, `resizableColumns` puts a resize handle (a focusable `role="separator"`) at the end edge of each column's header cell: drag it with a mouse, a finger or a pen (mirrored in right-to-left grids), or press F2 on the header cell (or its sort button) to move focus to it, then the arrow keys (20 pixels, 5 with Shift), Home (the minimum width), and Enter, Space or Escape to return; Enter on a sortable header still sorts. A double click sets a resized column back to its initial width. `columnSizingOptions` gives each column a `minWidth` (80 by default) and its starting width (`defaultWidth`, else `idealWidth`, else 150), and `columnWidths` or `defaultColumnWidths` with `onColumnWidthsChange(widths, details)` hold the widths (every column's, by `columnId`), for instance to persist them. Any of these props lays the columns out from widths (a `<colgroup>`, a fixed table layout, cells truncated with an ellipsis), with or without handles. An unresized grid fills its container (at least as wide as its columns); after the first change, or with `columnWidths` or `defaultColumnWidths`, it is exactly as wide as its columns, so the dragged edge follows the pointer and slack shows after the last column. There is no auto-fit and no maximum width. `labels.resizeColumn` and `labels.columnWidth(width)` name the handles and their value ("Resize column", "150 pixels").
2202
+
2203
+ ```tsx
2204
+ import * as React from 'react';
2205
+ import { DataGrid } from '@mortenbrudvik/waveui';
2206
+
2207
+ // Person, columns and personId as in the first example.
2208
+ const SIZING = { name: { defaultWidth: 240, minWidth: 120 }, role: { idealWidth: 160 } };
2209
+
2210
+ export function ResizablePeople({ people }: { people: Person[] }) {
2211
+ const [widths, setWidths] = React.useState<Record<string, number>>(() =>
2212
+ JSON.parse(localStorage.getItem('people-widths') ?? '{}'),
2213
+ );
2214
+ return (
2215
+ <DataGrid
2216
+ aria-label="People"
2217
+ items={people}
2218
+ columns={columns}
2219
+ getRowId={personId}
2220
+ resizableColumns
2221
+ columnSizingOptions={SIZING}
2222
+ columnWidths={widths}
2223
+ onColumnWidthsChange={(next) => {
2224
+ setWidths(next);
2225
+ localStorage.setItem('people-widths', JSON.stringify(next));
2226
+ }}
2227
+ />
2228
+ );
2229
+ }
2230
+ ```
2197
2231
  - **Typing a wrapper.** A generic wrapper `function AppGrid<T>(props: DataGridProps<T>)` compiles and forwards every form, but an inline `onSortChange` passed to it next to `items` gets no contextual type: annotate its parameters, or type the wrapper's props as `DataGridBaseProps<T> & DataGridItemsSortProps<T>`.
2198
2232
 
2199
2233
  ### Static tables
@@ -2268,6 +2302,88 @@ export function Members({ members }: { members: Member[] }) {
2268
2302
  - **Cell layout.** `Table.CellLayout` puts `media` (an icon, or an Avatar, which gets a 32px box) before the main text and a `description` under it; `truncate` keeps each line on one line where the column's width is bounded, and `appearance="primary"` marks the cell that names the row.
2269
2303
  - **Cell actions.** `Table.CellActions` covers the end of its cell (the cell is its positioning box) and shows its buttons while the row is hovered or holds focus, always in forced colors and on touch screens; `visible` keeps them (while a menu opened from one is open). Its background follows the library's own row fills: none, the row hover, a `striped` table's stripe and a DataGrid's selected rows. A row background of your own, or a table on another surface (a Card), needs the same background class on the actions, and the actions must be direct children of the cell for the stripe and selection fills to match.
2270
2304
 
2305
+ **State from hooks.** `useTableFeatures` holds a table's rows and its plugins hold the sort, the selection and the column widths, by row id and over every item, so a table that renders only a page or a window of its rows keeps select-all and the sort over all of them (Fluent's call shape; the `Components/Table/Features` story is the full version):
2306
+
2307
+ ```tsx
2308
+ import {
2309
+ Table,
2310
+ createTableColumn,
2311
+ useTableColumnSizing,
2312
+ useTableFeatures,
2313
+ useTableSelection,
2314
+ useTableSort,
2315
+ } from '@mortenbrudvik/waveui';
2316
+
2317
+ type Member = { id: string; name: string; role: string };
2318
+
2319
+ // Outside the component, as the grid's columns: the rows keep their array while these stay.
2320
+ const columns = [
2321
+ createTableColumn<Member>({ columnId: 'name', label: 'Name', compare: (a, b) => a.name.localeCompare(b.name) }),
2322
+ createTableColumn<Member>({ columnId: 'role', label: 'Role', compare: (a, b) => a.role.localeCompare(b.role) }),
2323
+ ];
2324
+ const memberId = (member: Member) => member.id;
2325
+ const sizing = { name: { defaultWidth: 240, minWidth: 120 } };
2326
+
2327
+ export function Members({ members }: { members: Member[] }) {
2328
+ const { getRows, sort, selection, columnSizing } = useTableFeatures(
2329
+ { items: members, columns, getRowId: memberId },
2330
+ [
2331
+ useTableSort({ defaultSort: { columnId: 'name', direction: 'ascending' } }),
2332
+ useTableSelection({ selectionMode: 'multiple' }),
2333
+ useTableColumnSizing({ columnSizingOptions: sizing }),
2334
+ ],
2335
+ );
2336
+ return (
2337
+ <Table aria-label="Members" {...columnSizing.getTableProps()}>
2338
+ <colgroup>
2339
+ <col style={{ width: 40 }} />
2340
+ <col {...columnSizing.getColumnProps('name')} />
2341
+ <col {...columnSizing.getColumnProps('role')} />
2342
+ </colgroup>
2343
+ <Table.Header>
2344
+ <Table.Row>
2345
+ <Table.SelectionCell
2346
+ aria-label="Select all members"
2347
+ checked={selection.allRowsSelected ? true : selection.someRowsSelected ? 'mixed' : false}
2348
+ onCheckedChange={(_checked, details) => selection.toggleAllRows(details?.event)}
2349
+ />
2350
+ {(['name', 'role'] as const).map((columnId) => (
2351
+ <Table.HeaderCell
2352
+ key={columnId}
2353
+ className="relative"
2354
+ sortable
2355
+ sortDirection={sort.getSortDirection(columnId)}
2356
+ onSortClick={(event) => sort.toggleColumnSort(columnId, event.nativeEvent)}
2357
+ aside={<Table.ResizeHandle {...columnSizing.getResizeHandleProps(columnId)} />}
2358
+ >
2359
+ {columnId === 'name' ? 'Name' : 'Role'}
2360
+ </Table.HeaderCell>
2361
+ ))}
2362
+ </Table.Row>
2363
+ </Table.Header>
2364
+ <Table.Body>
2365
+ {getRows().map(({ item, rowId }) => (
2366
+ <Table.Row key={rowId}>
2367
+ <Table.SelectionCell
2368
+ aria-label={`Select ${item.name}`}
2369
+ checked={selection.isRowSelected(rowId)}
2370
+ onCheckedChange={(_checked, details) => selection.toggleRow(rowId, details?.event)}
2371
+ />
2372
+ <Table.Cell>{item.name}</Table.Cell>
2373
+ <Table.Cell>{item.role}</Table.Cell>
2374
+ </Table.Row>
2375
+ ))}
2376
+ </Table.Body>
2377
+ </Table>
2378
+ );
2379
+ }
2380
+ ```
2381
+
2382
+ - **Plugins.** Call each plugin hook unconditionally in the array (or before it) and pass the same plugins on every render; a slot without its plugin holds inert values, and the order of the array does not matter (a feature given twice uses its last plugin, with a development warning). Options may be written inline: a plugin whose options change identity only makes the state be computed again, while `getRows()` keeps its array until `items`, `getRowId`, `columns` or the sort change.
2383
+ - **Sort.** `useTableSort` takes DataGrid's sort shape (`sort`, `defaultSort`, `onSortChange(sort, details)`); `getRows()` is a stable sort of a copy of `items` by the sorted column's `compare` (descending negates it), and `sort` gives `value`, `setColumnSort`, `toggleColumnSort` and `getSortDirection`.
2384
+ - **Selection.** `useTableSelection` takes DataGrid's selection props (`selectionMode`, `selectedItems`, `defaultSelectedItems`, `onSelectedItemsChange(selectedItems, details)`, row ids) and `isDisabled(item, index)`. `allRowsSelected` and `someRowsSelected` count every selectable item of `items`, rendered or not; `toggleAllRows` selects them all, or removes only their ids (a selected disabled item and ids outside `items` keep their state), and `clearRows` empties the selection. For a selection without a table, `useSelection` holds the same id set (`toggle`, `select`, `deselect`, `toggleAll(universe)`, `clear`).
2385
+ - **Column widths.** `useTableColumnSizing` takes DataGrid's sizing props (`columnSizingOptions`, `columnWidths`, `defaultColumnWidths`, `onColumnWidthsChange(widths, details)`); spread `getTableProps()` on the table (a fixed layout and its width), `getColumnProps(columnId)` on each `<col>` and `getResizeHandleProps(columnId)` on a `Table.ResizeHandle`, which the `aside` of a positioned header cell (`relative`) holds. The handle is a Tab stop in a static table: Tab to it, then the arrow keys (20 pixels, 5 with Shift), Home and End (the minimum and, with `maxWidth`, the maximum); a double click sets a resized column back to its initial width. It keeps no width of its own: without the hook, pass `width`, `minWidth` and `onResize(width, details)` from your state. `label` ("Resize column") and `valueText(width)` ("N pixels") name it and its value.
2386
+
2271
2387
  ### Lists
2272
2388
 
2273
2389
  A `List` without selection is a plain list. `navigationMode="items"` makes its items one Tab stop with the arrow keys, Home, End and typeahead, and `onAction` gives an item an action of its own:
@@ -2293,6 +2409,7 @@ export function RecentFiles({ open }: { open: (file: string) => void }) {
2293
2409
  - **`onAction`** runs on a click and on Enter, and on Space where the item cannot be selected (a list without selection, an item without `value`); in a selectable item Space only toggles. It runs after the item's `onClick` and before the selection toggles: `event.preventDefault()` keeps the selection. Clicks and keys in the item's actions never run it. In a list that does not navigate its items it runs on clicks only, with a development warning.
2294
2410
  - **`disabledSelection`** keeps an item's selection as it is (click, Enter and Space) while the item stays focusable and in the arrow order; without `onAction` it is also `aria-disabled`.
2295
2411
  - **Check marks.** `checkmark` on a selectable List draws a decorative check at the start of each selected item; `List.Item checkmark` replaces it with your glyph or hides it (`false`).
2412
+ - **Long lists.** `virtualWindow` with `count` and a function child `(index) => <List.Item …/>` renders only the items in view (see [Large data](#large-data)); the keys then move by index over the whole list, and selection keeps the values of items that are not rendered.
2296
2413
 
2297
2414
  ### Trees
2298
2415
 
@@ -2341,6 +2458,37 @@ export function FileTree() {
2341
2458
  - **Persona rows.** `Tree.ItemPersonaLayout` (flat `TreeItemPersonaLayout`, Fluent's name) lays out an item's label as a person: `media` (an Avatar of size 32 fits a row), the name as children and a `description` under it; the item is named by the name and described by the description.
2342
2459
  - **Motion.** A child group opens and closes the space it takes while it fades, over 200ms both ways, in an element of its own around the `role="group"` element (`wave-tree-item__collapse`, `display: contents` while open); a nested group goes with its parent. The items of a closing group leave the arrow order and typeahead at once, and focus inside it moves to the item that collapsed. `Tree motion` replaces the motion of every group, or turns it off with `null`; the chevron's turn is not part of it. A pointer click that falls through an exiting group onto its item (an `inert` element lets the pointer through) does nothing; Enter and a screen reader's click on the item still activate it.
2343
2460
 
2461
+ **Flat trees.** `FlatTree` builds a tree from flat data, for trees of thousands of items: `useFlatTree(items, options)` takes items with a `parentValue` (in any order) and the nested Tree's state props, and the tree renders the visible items (the roots and the children of every expanded branch) as sibling `role="treeitem"` elements with `aria-level`, `aria-setsize`, `aria-posinset` and, on branches, `aria-expanded`:
2462
+
2463
+ ```tsx
2464
+ import * as React from 'react';
2465
+ import { FlatTree, useFlatTree, useVirtualWindow, type FlatTreeItemData } from '@mortenbrudvik/waveui';
2466
+
2467
+ export function Files({ items }: { items: readonly FlatTreeItemData[] }) {
2468
+ const [checked, setChecked] = React.useState<string[]>([]);
2469
+ const model = useFlatTree(items, {
2470
+ defaultExpandedItems: ['docs'],
2471
+ selectionMode: 'multiple',
2472
+ checkedItems: checked,
2473
+ onCheckedItemsChange: setChecked,
2474
+ });
2475
+ const virtualWindow = useVirtualWindow({ itemSize: 28 });
2476
+ return (
2477
+ <FlatTree
2478
+ aria-label="Files"
2479
+ model={model}
2480
+ {...model.getTreeProps()}
2481
+ virtualWindow={virtualWindow}
2482
+ style={{ height: 400 }}
2483
+ />
2484
+ );
2485
+ }
2486
+ // items: [{ value: 'docs', label: 'Documents' }, { value: 'cv', parentValue: 'docs', label: 'CV.pdf' }, …]
2487
+ ```
2488
+
2489
+ - **The model.** `useFlatTree` returns `visibleItems` (each with `item`, `level`, `position`, `setSize`, `parentValue`, `hasChildren`, `isBranch`, `expanded`, `index` and, in multiple mode, `checked`), `getItem`, `expand` (a value or a list, in one change), `collapse`, `toggle`, `isExpanded`, `setChecked` and `getTreeProps()`. Expanded and checked state take the nested Tree's names (`expandedItems`, `defaultExpandedItems`, `onExpandedItemsChange`, `checkedItems`, `defaultCheckedItems`, `onCheckedItemsChange(checkedItems, details)`), and checks cascade the same way. `itemType: 'branch'` makes an item without children expandable (children loaded on demand). Pass `items`, and controlled arrays, as stable references: the model is rebuilt when they change. An unknown `parentValue` (the item becomes a root), a cycle of parents (cut) and a duplicate value (the first wins) warn in development.
2490
+ - **The tree.** `FlatTree` takes `model`, `selectionMode` (`model.getTreeProps()` passes the model's), `selected`, `current`, `onItemSelect(value)` and `renderItem(row)` (the item's content; its `label` by default), with the nested Tree's keys, chevrons, checkboxes and radios. An item whose data is `disabled` is skipped by the keys and typeahead and cannot be activated, expanded or checked by the user. When a branch collapses around the focused item (from code), focus moves to the branch. It has no row actions, `aside` or motion (items expand and collapse at once). Name it with `aria-label` or `aria-labelledby` (a development warning asks for one).
2491
+
2344
2492
  ### Breadcrumbs
2345
2493
 
2346
2494
  ```tsx
@@ -2542,7 +2690,8 @@ The names and hidden texts that components render themselves are English by defa
2542
2690
  | `Rating` | `labels` (`star(value, max)`; it also names the half values of a `step={0.5}` Rating, "2.5 stars") |
2543
2691
  | `RatingDisplay` | `labels` (`rating(value, max, formattedValue)`, `count(count, formattedCount)`); `locale` formats the value and the count (pass it when rendering on the server) |
2544
2692
  | `SpinButton` | `labels` (`increment`, `decrement`) |
2545
- | `DataGrid` | `labels` (`selectAll`, `selectionHeader`); each row's fallback name "Select row": `DataGrid.Row` `selectionLabel` |
2693
+ | `DataGrid` | `labels` (`selectAll`, `selectionHeader`, and with `resizableColumns` `resizeColumn` ("Resize column", the handles' name) and `columnWidth(width)` ("150 pixels", their value text)); each row's fallback name "Select row": `DataGrid.Row` `selectionLabel` |
2694
+ | `Table.ResizeHandle` | `label` ("Resize column"), `valueText(width)` ("150 pixels") |
2546
2695
  | `AvatarGroup` | `overflowLabel`, `unnamedMemberLabel` |
2547
2696
  | `InfoButton` | `aria-label` (the button and its note; default "Information") |
2548
2697
  | `Hamburger` | `aria-label` ("Navigation", only while it has no other name) |
@@ -2552,11 +2701,95 @@ The names and hidden texts that components render themselves are English by defa
2552
2701
 
2553
2702
  Without a text prop, these defaults are replaced through `aria-label`: the PresenceBadge status names ("Available", …: `aria-label` on the badge), the MessageBar "Dismiss" and SearchBox "Clear search" button names (`dismiss={{ 'aria-label': 'Lukk' }}`, which keeps the default icon), and the names of the Breadcrumb, Pagination, Nav, Stepper, Carousel and Toaster regions ("Breadcrumb", "Pagination", "Navigation", "Progress", "Carousel", "Notifications"). The placeholders of Dropdown, DatePicker, TimePicker and SearchBox are `placeholder` props, and ColorPicker's default presets are replaced through `presets`.
2554
2703
 
2555
- 0.7 and 0.8 add no other built-in text: the submenu half of a `Menu.SplitGroup`, a context menu (`Menu.Popover`) and a `Toolbar.RadioGroup` are named by the `aria-label` you pass, a toast's container by its severity and title, and a tree row's actions toolbar by its item's label. Nor do 0.10 and 0.11, beyond the rows above: a `TagGroup` is named by the `aria-label` you pass, an inline Drawer by its title; a `Listbox`, a `Calendar` and a `ColorSwatch` or `ImageSwatch` are named by the `aria-label` you pass (or a `Field`'s label, or a `Tooltip` around the swatch), an `OptionGroup` by its label, and the month and year buttons by their text in `locale`. 0.14 adds none either beyond the rows above: a `Table.SelectionCell` control is named by the `aria-label` or `aria-labelledby` you pass (a development warning asks for one), and a `CarouselFooter`'s `initialStepText` and `finalStepText` are your text.
2704
+ 0.7 and 0.8 add no other built-in text: the submenu half of a `Menu.SplitGroup`, a context menu (`Menu.Popover`) and a `Toolbar.RadioGroup` are named by the `aria-label` you pass, a toast's container by its severity and title, and a tree row's actions toolbar by its item's label. Nor do 0.10 and 0.11, beyond the rows above: a `TagGroup` is named by the `aria-label` you pass, an inline Drawer by its title; a `Listbox`, a `Calendar` and a `ColorSwatch` or `ImageSwatch` are named by the `aria-label` you pass (or a `Field`'s label, or a `Tooltip` around the swatch), an `OptionGroup` by its label, and the month and year buttons by their text in `locale`. 0.14 adds none either beyond the rows above: a `Table.SelectionCell` control is named by the `aria-label` or `aria-labelledby` you pass (a development warning asks for one), and a `CarouselFooter`'s `initialStepText` and `finalStepText` are your text. 0.16 adds none beyond the resize handles' rows above: a `FlatTree` is named by the `aria-label` or `aria-labelledby` you pass (a development warning asks for one).
2705
+
2706
+ ## Large data
2707
+
2708
+ DataGrid, List, Listbox, Combobox, Dropdown and FlatTree can render only the items in view of a long list, plus an overscan, with spacers (`aria-hidden`, `data-virtual-spacer`) as tall as the items they stand for. Create a window with `useVirtualWindow({ itemSize })` and pass it as `virtualWindow`; the component calls its `layout`, attaches its scroller and keeps the focused item rendered. Selection, select-all, sorting, the keyboard and the positions a screen reader announces follow the data, not the rendered items. No virtualization library is needed (an adapter of one can implement the same `VirtualWindow` interface: the guide's "Data at Scale" chapter has one for `@tanstack/react-virtual`).
2709
+
2710
+ ```tsx
2711
+ import {
2712
+ Combobox,
2713
+ DataGrid,
2714
+ Field,
2715
+ List,
2716
+ createTableColumn,
2717
+ useVirtualWindow,
2718
+ type ListboxItem,
2719
+ } from '@mortenbrudvik/waveui';
2720
+
2721
+ interface Member {
2722
+ id: string;
2723
+ name: string;
2724
+ }
2725
+
2726
+ // Outside the component: a new array would be computed again over every item at every row the
2727
+ // window scrolls past.
2728
+ const columns = [
2729
+ createTableColumn<Member>({
2730
+ columnId: 'name',
2731
+ label: 'Name',
2732
+ compare: (a, b) => a.name.localeCompare(b.name),
2733
+ renderCell: (member) => member.name,
2734
+ }),
2735
+ ];
2736
+ const memberId = (member: Member) => member.id;
2737
+
2738
+ export function Members({ members }: { members: Member[] }) {
2739
+ // 44-pixel rows under a header of 45 pixels (with the select-all checkbox), which sticks over them.
2740
+ const virtualWindow = useVirtualWindow({ itemSize: 44, scrollPaddingStart: 45 });
2741
+ return (
2742
+ <DataGrid
2743
+ aria-label="Members"
2744
+ items={members}
2745
+ columns={columns}
2746
+ getRowId={memberId}
2747
+ selectionMode="multiple"
2748
+ virtualWindow={virtualWindow}
2749
+ containerProps={{ style: { height: 480 } }}
2750
+ />
2751
+ );
2752
+ }
2753
+
2754
+ export function Files({ files }: { files: readonly string[] }) {
2755
+ const virtualWindow = useVirtualWindow({ itemSize: 40 });
2756
+ return (
2757
+ <List
2758
+ aria-label="Files"
2759
+ selectable
2760
+ selectionMode="multiple"
2761
+ virtualWindow={virtualWindow}
2762
+ count={files.length}
2763
+ style={{ height: 320 }}
2764
+ >
2765
+ {(index) => <List.Item value={files[index]}>{files[index]}</List.Item>}
2766
+ </List>
2767
+ );
2768
+ }
2769
+
2770
+ export function PersonPicker({ people }: { people: readonly ListboxItem[] }) {
2771
+ const virtualWindow = useVirtualWindow({ itemSize: 32 });
2772
+ return (
2773
+ <Field label="Person">
2774
+ <Combobox items={people} virtualWindow={virtualWindow} />
2775
+ </Field>
2776
+ );
2777
+ }
2778
+ ```
2779
+
2780
+ - **A fixed item height.** Every item is exactly the window's `itemSize` tall: the component sets that height on each row, item or option, after its own `style`, so the content must fit. A windowed DataGrid's body cells drop their block padding and keep their text on one line (truncated, as the columns are laid out from widths); a List item with one line of text fits in 40 pixels and the default `Option` in 32 (the stories use 44-pixel DataGrid rows and 28-pixel FlatTree items). Variable heights are not supported.
2781
+ - **A bounded height.** The scroller needs a height or a maximum height: the DataGrid's scroll wrapper (`containerProps`), the List, Listbox or FlatTree element. Without one the window measures the whole list and renders every item (DataGrid warns in development). The popup list of a Combobox or Dropdown scrolls within its own maximum height.
2782
+ - **Data, not elements.** The DataGrid needs `items` and `columns` (rows rendered from them, or by a `DataGrid.Body` render function), the pickers their data mode (`items`, `renderOption`), and the List `count` with a function child `(index) => <List.Item …/>`, called for the rendered indexes only; FlatTree renders its model. Keep `items`, `columns`, `getRowId`, `isSelectionDisabled` and `renderOption` stable (outside the component, or memoised): the component that calls `useVirtualWindow` renders again at every row boundary, and a new array or function is computed again over every item.
2783
+ - **The keyboard** moves by the data: a target that is not rendered is scrolled into the window and focused (or, in a picker, made active) in the commit that renders it, and the focused item stays rendered when it is scrolled away. The DataGrid carries `aria-rowcount` and `aria-rowindex`, List items and options `aria-setsize` and `aria-posinset` (a windowed List that is a grid, `aria-rowcount` and `aria-rowindex`), and FlatTree items `aria-level`, `aria-setsize` and `aria-posinset`.
2784
+ - **The scroller** turns the browser's scroll anchoring off, and is no Tab stop of its own while one of its items is the Tab stop; a plain windowed List (`navigationMode` `none`) keeps the browser's default, under which WebKit's keyboard cannot reach it: pass `tabIndex={0}` or `navigationMode="items"`.
2785
+ - **Overscan.** By default the window renders enough items for 240 pixels beyond each edge, and at least 6 (`overscan` sets a number of items). A scroll faster than the overscan in one frame (a fling of the scroll bar) shows the spacers until the next frame renders the new items; raise `overscan` if that matters.
2786
+ - **On the server** the window lays out the top of the list in `initialViewportSize` (480 pixels), so the server HTML holds the first window.
2787
+ - **Your own scroller.** `useVirtualWindow` works for markup of your own too: render `layout(count)`, items for the item entries and empty blocks of `size` pixels for the spacers, and attach `scrollRef` by merging it (`useMergedRefs`) or after destructuring it (`const { scrollRef } = virtualWindow`): `ref={virtualWindow.scrollRef}` fails the React Compiler's lint. `scrollToIndex(index, { align })` scrolls an item into view and moves the window in the same update.
2788
+ - **Limits.** Typeahead in a windowed List matches the rendered items only (in the pickers and FlatTree it searches the data); option groups cannot be windowed (`OptionGroup` children beside a `virtualWindow` are a development warning); TagPicker, TimePicker and the static `Table` have no `virtualWindow` (the table hooks keep a static table's state over every item); `<tfoot>` rows are not supported in a windowed DataGrid (they get no position, and the arrow keys skip them). The guide lists the rest.
2556
2789
 
2557
2790
  ## Keyboard support
2558
2791
 
2559
- As implemented through 0.14. Buttons, links, checkboxes, switches and the trigger buttons of Accordion, Carousel, Nav, Breadcrumb and Pagination are native elements: Tab to reach them, Enter or Space to activate. A Link or `Button as="a"` without `href`, and a Button or Link rendered as a `div` or `span`, are buttons: Tab reaches them, Enter (key down) and Space (key up) activate them. A `disabledFocusable` control stays in the Tab order but Enter, Space and clicks do nothing. The sort buttons of a sortable `Table.HeaderCell` and the step buttons of `TeachingPopover.CarouselNav` are native buttons too.
2792
+ As implemented through 0.16. Buttons, links, checkboxes, switches and the trigger buttons of Accordion, Carousel, Nav, Breadcrumb and Pagination are native elements: Tab to reach them, Enter or Space to activate. A Link or `Button as="a"` without `href`, and a Button or Link rendered as a `div` or `span`, are buttons: Tab reaches them, Enter (key down) and Space (key up) activate them. A `disabledFocusable` control stays in the Tab order but Enter, Space and clicks do nothing. The sort buttons of a sortable `Table.HeaderCell` and the step buttons of `TeachingPopover.CarouselNav` are native buttons too.
2560
2793
 
2561
2794
  | Component | Keys |
2562
2795
  | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -2567,12 +2800,14 @@ As implemented through 0.14. Buttons, links, checkboxes, switches and the trigge
2567
2800
  | RadioGroup, SwatchPicker (row layout) | One Tab stop. All four arrows move and select (wrapping), Home/End jump; Left/Right mirrored in RTL. Keys pressed on a link inside a radio's label stay the link's. An `EmptySwatch` in the row layout is a Tab stop of its own, and the arrow keys pressed on it do nothing. |
2568
2801
  | SwatchPicker (`layout="grid"`) | One Tab stop: the last focused swatch, at first the selected one, else the first. The arrows move focus only: Left/Right through the swatches in reading order across the rows (mirrored in RTL), stopping at the grid's first and last swatch; Up/Down to the enabled swatch of the previous/next row nearest the same column, stopping at the first and last row; Home/End to the first/last swatch of the row, Ctrl+Home/Ctrl+End of the grid. Space, Enter and a click select. Disabled swatches are skipped; an `EmptySwatch` is a cell like the others. |
2569
2802
  | SwatchPicker (`focusMode="tab"`) | Every enabled swatch is a Tab stop, and the arrow keys do nothing; Space, Enter and a click select (the selected swatch stays selected). |
2570
- | Listbox | One Tab stop: the list. The active option shows only while the list has focus (the selected option, else the first). Down/Up move without wrapping, Home/End, PageUp/PageDown by 10, typing moves to the matching option (a Space within 500 ms of a letter continues the search); Space and Enter select, or with `multiselect` toggle. Tab, Escape and Alt with an arrow are left to the page (an enclosing Dialog closes on Escape). With `disabledOptionsFocusable` the keys reach disabled options, but Space and Enter do nothing on them. |
2803
+ | Listbox | One Tab stop: the list. The active option shows only while the list has focus (the selected option, else the first). Down/Up move without wrapping, Home/End, PageUp/PageDown by 10, typing moves to the matching option (a Space within 500 ms of a letter continues the search); Space and Enter select, or with `multiselect` toggle. Tab, Escape and Alt with an arrow are left to the page (an enclosing Dialog closes on Escape). With `disabledOptionsFocusable` the keys reach disabled options, but Space and Enter do nothing on them. Windowed (`virtualWindow`): the same keys over every option, rendered or not; the active option is scrolled into view with the window. |
2571
2804
  | Rating | Right/Up one step more (a star, or half a star with `step={0.5}`), Left/Down one fewer (never below `step`), Home/End `step`/`max`; mirrored in RTL. A pointer click on a half-star Rating chooses the half or the whole star by position; Space and Enter on a focused radio choose that radio's own value. |
2572
2805
  | TabList | One Tab stop (the selected tab). Left/Right (Up/Down when vertical) move and select, Home/End; with `selectTabOnFocus={false}` they only move focus, and Enter or Space selects the focused tab; disabled tabs skipped; mirrored in RTL. |
2573
- | Tree | One Tab stop. Up/Down, Home/End; Right expands or moves to the first child, Left collapses or moves to the parent (mirrored in RTL); Enter activates; Space activates, or in a checkbox tree (`selectionMode="multiple"`) toggles the focused item's check; `*` expands siblings; type to jump (a Space within 500 ms of a letter continues the search). Row actions: Tab from the focused item enters them and Shift+Tab returns (`navigationMode="tree"`); with `navigationMode="treegrid"` the "next" arrow moves into them (on an expanded parent too, instead of to its first child), "previous" returns, and Up/Down move between items. |
2574
- | List (selectable, or `navigationMode="items"`) | One Tab stop (selectable: the first selected item, else the first; without selection: the item that last had focus). Up/Down (wrapping), Home/End, typeahead above 7 items (a Space within 500 ms of a letter continues the search). Enter toggles a selectable item and runs `onAction`; Space toggles a selectable item, or runs `onAction` on an item that cannot be selected. With item actions in a selectable list, or `navigationMode="composite"` (grid): Up/Down between rows, Left/Right into the actions (mirrored in RTL), Enter/F2 into a text field, Escape back. A plain List (`navigationMode` `none`) has no arrow keys: its items' controls are Tab stops. |
2575
- | DataGrid | One Tab stop. Arrows between cells (Left/Right to the previous/next cell of the row, mirrored in RTL; Up/Down and PageUp/PageDown keep the visual column across `colSpan`/`rowSpan` cells of grouped headers), Home/End row start/end, Ctrl+Home/Ctrl+End grid start/end, PageUp/PageDown 10 rows; Enter/F2 into a cell's widgets, Escape back; Space on a cell toggles its row's selection. |
2806
+ | Tree | One Tab stop. Up/Down, Home/End; Right expands or moves to the first child, Left collapses or moves to the parent (mirrored in RTL); Enter activates; Space activates, or in a checkbox tree (`selectionMode="multiple"`) toggles the focused item's check; `*` expands siblings; type to jump (a Space within 500 ms of a letter continues the search). Row actions: Tab from the focused item enters them and Shift+Tab returns (`navigationMode="tree"`); with `navigationMode="treegrid"` the "next" arrow moves into them (on an expanded parent too, instead of to its first child), "previous" returns, and Up/Down move between items. |
2807
+ | FlatTree | One Tab stop: the focused item while focus is inside, else the `selected` item while it is visible, else the first enabled item. Up/Down between the visible items, Home/End; Right expands a collapsed branch or moves to its first child, Left collapses an expanded branch or moves to its parent (mirrored in RTL); Enter activates (and toggles a branch); Space activates, or in a checkbox tree toggles the check; `*` expands the sibling branches; typing moves to the next visible item whose label starts with the text. Disabled items are skipped. Windowed, an item that is not rendered is scrolled into view before it takes focus. |
2808
+ | List (selectable, or `navigationMode="items"`) | One Tab stop (selectable: the first selected item, else the first; without selection: the item that last had focus). Up/Down (wrapping), Home/End, typeahead above 7 items (a Space within 500 ms of a letter continues the search). Enter toggles a selectable item and runs `onAction`; Space toggles a selectable item, or runs `onAction` on an item that cannot be selected. With item actions in a selectable list, or `navigationMode="composite"` (grid): Up/Down between rows, Left/Right into the actions (mirrored in RTL), Enter/F2 into a text field, Escape back. A plain List (`navigationMode` `none`) has no arrow keys: its items' controls are Tab stops. Windowed (`virtualWindow` with `count`): the keys move by index over the whole list, Up/Down wrapping, Home/End, PageUp/PageDown by ten (stopping at the ends); typeahead searches the rendered items only; the focused item is the Tab stop while focus is in the list. |
2809
+ | DataGrid | One Tab stop. Arrows between cells (Left/Right to the previous/next cell of the row, mirrored in RTL; Up/Down and PageUp/PageDown keep the visual column across `colSpan`/`rowSpan` cells of grouped headers), Home/End row start/end, Ctrl+Home/Ctrl+End grid start/end, PageUp/PageDown 10 rows; Enter/F2 into a cell's widgets, Escape back; Space on a cell toggles its row's selection. A column header with a resize handle (`resizableColumns`): F2 on the header cell or its sort button moves focus to the handle (Enter on a sortable header still sorts); there Left/Right move the column's edge by 20 pixels (5 with Shift; mirrored in RTL), Home sets the minimum width, and Enter, Space or Escape return to the header; Tab moves between the header's sort button and handle. Windowed (`virtualWindow`): Up/Down, PageUp/PageDown and Ctrl+Home/Ctrl+End move over every row of the data; a row that is not rendered is scrolled in, then focused. |
2810
+ | Table.ResizeHandle | A Tab stop in a static table. Left/Right move the column's edge by 20 pixels (5 with Shift; mirrored in RTL), Home and End set the minimum and the maximum (`maxWidth`); with `onLeave`, Escape and the release of Enter or Space call it (to move focus back to the header; a held Enter or Space stays on the handle until it is released). |
2576
2811
  | Menu | Trigger: Enter/Space/Down open and focus the first item, Up opens on the last; a trigger with `aria-disabled="true"` (a `disabledFocusable` MenuButton or SplitButton) ignores them and its click. Menu: Up/Down (wrapping), Home/End, typeahead, Enter/Space activate (a Space within 500 ms of a letter continues the search); on a checkbox, radio or switch item Space changes it and keeps the menu open, Enter changes it and closes a popup menu; on `Menu.ItemLink` Enter is the browser's link activation. A submenu's item: Right (Left in RTL), Enter and Space open the submenu and focus its first item; in a submenu Left (Right in RTL) and Escape close only it and return focus to its item. `Menu.SplitGroup`: Right (Left in RTL) moves from the action to the submenu button, Left back. Escape closes and returns focus, Tab closes every level. Context menu (`openOnContext`): Shift+F10 or the ContextMenu key on the region opens it at the focused element; Escape and item activation return focus there, and Tab moves on from it. |
2577
2812
  | Combobox, TagPicker, TimePicker | Down/Up open and move, Alt+Down opens, typing makes the first match active (not in a `freeform` Combobox), Enter commits the active option (with no active option, Enter submits the form; TimePicker instead never submits while its text is edited: a complete time within the bounds is committed, erased text clears the value, other text is kept and flagged invalid, and the list closes; leaving the input with edited text settles it the same way and closes the list), Alt+Up and Tab close, Escape closes and then discards typed text. The chevron button (Combobox, TimePicker) is not a Tab stop; the clear button (`clearable`) is a Tab stop after the input. Tab from erased TimePicker text clears the value first, so focus moves past the clear button that goes with it. TagPicker: Backspace in the empty input, and the previous arrow (Left, Right in RTL) with the caret at the start of the text and nothing selected, focus the last tag; on a tag's remove button Left/Right move between the tags (mirrored in RTL) and past the last one return to the trigger, and Delete, Backspace, Enter or Space remove it and focus the next tag's remove button, else the input (a click focuses the input). Typing makes the option whose label equals the text active, else the first match; with `allowCreate` Enter on the create option adds the text. `TagPicker.Button`: Down, Up and a typed letter open and move, Enter and Space add the active option. The chevron is not a Tab stop; the clear button is, after the trigger. Multi-select (`multiselect`, Combobox): Enter toggles the active option and keeps the list open; Tab, Escape and Alt+Up close without committing; focusing the input selects the shown labels, so typing replaces them with a query, and Backspace and Delete only edit the text. With `disabledOptionsFocusable` (Combobox) the arrow keys reach disabled options, but Enter does nothing on them. |
2578
2813
  | Dropdown | Down/Up/Home/End and typing open and move, PageUp/PageDown move by 10, Enter/Space commit (a Space within 500 ms of a letter continues the search), Alt+Up and Tab commit and close, Escape closes. The clear button (`clearable`) is a Tab stop after the combobox button. Multi-select (`multiselect`): Enter and Space toggle the active option and keep the list open; Tab, Escape and Alt+Up close without committing. With `disabledOptionsFocusable` the keys reach disabled options, but Enter and Space do nothing on them, and Tab or Alt+Up on one closes the list without selecting. |
@@ -2612,7 +2847,7 @@ Typeahead (Menu, a selectable or navigable List, Tree, Dropdown, Listbox) accept
2612
2847
  | `useModalOpen()`, `hasOpenModal()`, `modalAllowProps` | Whether a modal is open; props that keep an app-owned region usable over a modal. |
2613
2848
  | `useFocusableGroup(options)` | One tab stop that holds several focusable elements (`tabBehavior`: `unlimited`, `limited`, `limited-trap-focus`); Enter or F2 enters, Escape leaves. |
2614
2849
  | `useFocusVisible()` | `true` while the last input was the keyboard; `false` on the server. |
2615
- | `useGridNavigation(options)` | Keyboard navigation of a static `<table role="grid">` (the Table's own). |
2850
+ | `useGridNavigation(options)` | Keyboard navigation of a static `<table role=\"grid\">` (the Table's own); `model` (`GridNavigationModel`: `rowCount`, `headerRowCount`, `scrollToRow(index)`) moves the vertical keys by the data of a grid whose rows are rendered in part (`data-row-index` on each rendered row), and returns `activeRowIndex` and `pendingRowIndex`, the rows to keep rendered. A widget with `data-grid-auxiliary` (a resize handle) is never a cell's target; F2 enters it. |
2616
2851
  | `getTabbableElements(container)`, `getFirstTabbable`, `getLastTabbable` | The tabbable elements of a container in Tab order (hidden, disabled and `inert` ones left out). |
2617
2852
  | `focusRing`, `focusRingInset`, `inputFocus`, `inputFocusWithin` | The focus class strings of the library, to compose with `cn()` on your own controls. |
2618
2853
  | `useId(prefix?)` | SSR-safe ids (`prefix-<react id>`; never parse the React part). |
@@ -2622,8 +2857,12 @@ Typeahead (Menu, a selectable or navigable List, Tree, Dropdown, Listbox) accept
2622
2857
  | `usePresence(visible, options?)`, `<Presence visible>` | Mount and unmount an element with CSS enter and exit motion (`data-presence` phases; see [Enter and exit motion](#enter-and-exit-motion)). |
2623
2858
  | `useFieldControl(props, options?)` | Merge a custom control's labelling props with the surrounding `Field` (its label, validation message, hint and state). |
2624
2859
  | `useAnnounce()`, `announce(message, politeness?)` | Screen-reader announcements through a shared live region. |
2625
- | `useActiveDescendant(options)` | The `aria-activedescendant` model under the pickers, for composites that keep focus on one element: `items`, `getId`, `getElement` (the item's element for scrolling; by default the element with its id), `enabled`, `fallback`, `loop`, `activateFirstOnChange`, `onActiveValueChange`; returns `activeValue`, `activeDescendantId` and stable `setActiveValue`, `highlight` (pointer moves, not scrolled into view), `first`, `last`, `next`, `prev` and `move`. State-based: it reads no DOM during render, so an active fallback item's id is in the server HTML. |
2626
- | `useListbox(options)`, `useListboxOption(props)` | The listbox under Dropdown, Combobox, TagPicker and Listbox: keys, typeahead, filtering, the active option and option registration, in `'select-only'`, `'editable'` or `'standalone'` mode, with `multiselect` and `disabledOptionsFocusable`; `onOpenChange(open, details)` and `onSelect(value, details)` (see [Listbox and custom pickers](#listbox-and-custom-pickers)). `useListboxOption` builds an option component of your own. |
2860
+ | `useActiveDescendant(options)` | The `aria-activedescendant` model under the pickers, for composites that keep focus on one element: `items`, `getId`, `getElement` (the item's element for scrolling; by default the element with its id), `enabled`, `fallback`, `loop`, `activateFirstOnChange`, `onActiveValueChange`; returns `activeValue`, `activeDescendantId` and stable `setActiveValue`, `highlight` (pointer moves, not scrolled into view), `first`, `last`, `next`, `prev` and `move`. State-based: it reads no DOM during render, so an active fallback item's id is in the server HTML. `onScrollToItem(index)` is called first for every move that scrolls, for a windowed list that renders part of its items. |
2861
+ | `useListbox(options)`, `useListboxOption(props)` | The listbox under Dropdown, Combobox, TagPicker and Listbox: keys, typeahead, filtering, the active option and option registration, in `'select-only'`, `'editable'` or `'standalone'` mode, with `multiselect` and `disabledOptionsFocusable`; `onOpenChange(open, details)` and `onSelect(value, details)` (see [Listbox and custom pickers](#listbox-and-custom-pickers)). `useListboxOption` builds an option component of your own. In data mode (`items`) `onScrollToItem(index)` receives every active option that scrolls into view, by its index among the shown options. |
2862
+ | `useVirtualWindow(options)` | A window over a long list of fixed-height items (`itemSize`, `overscan`, `initialViewportSize`, `scrollPaddingStart`), for the `virtualWindow` prop of DataGrid, List, Listbox, Combobox, Dropdown and FlatTree: `VirtualWindow` (`itemSize`, `scrollRef`, `layout(count, { keep })`, `scrollToIndex(index, { align })`) and its `VirtualEntry` list (see [Large data](#large-data)). |
2863
+ | `useSelection(options)` | Selection by id held outside the DOM (`selectionMode`, `selectedItems`, `defaultSelectedItems`, `onSelectedItemsChange`): `isSelected`, `toggle`, `select`, `deselect`, `toggleAll(universe)` (keeps the ids outside the universe), `clear`. |
2864
+ | `useTableFeatures(options, plugins)`, `useTableSort`, `useTableSelection`, `useTableColumnSizing` | Headless state of a table built from the `Table` parts: the rows of `items` (`getRows()`), and the sort, selection and column widths of its plugins, over every item (see [Static tables](#static-tables)). |
2865
+ | `useFlatTree(items, options?)` | The model of a `FlatTree`: flat items with a `parentValue`, the visible rows with their level and position, the expanded and checked state (see [Trees](#trees)). |
2627
2866
  | `useListboxPopup(options)`, `ListboxSurface` | The popup of a custom picker: dismissal (`onDismiss(reason)`, a `DismissReason`: `'escape'`, `'outside-press'` or `'focus-outside'`) and positioning, and the surface that renders the list (`listClassName`, `surfaceClassName`). |
2628
2867
  | `ListboxProvider` | Provides a listbox's context to its options when you render the list yourself. |
2629
2868
  | `collectOptionLabels(children)`, `markListboxElement(component, kind)` | Read option labels from children before the options register (server HTML, first render); mark your own option and group components so they are read. |
@@ -2646,6 +2885,17 @@ Typeahead (Menu, a selectable or navigable List, Tree, Dropdown, Listbox) accept
2646
2885
  | `resolveSlot`, `renderSlot` | The slot helpers the components use. |
2647
2886
  | `getThemeClassName(theme)` | The theme classes of a theme. |
2648
2887
 
2888
+ ## Upgrading from 0.15
2889
+
2890
+ 0.16 removes nothing public, and everything it adds is opt-in (windowing, `FlatTree`, the table hooks, the pickers' data mode, resizable columns), except one behaviour change for a DataGrid given `items`:
2891
+
2892
+ 1. **Selection follows the data.** Select-all selects every selectable item of `items`, rendered or not, and its checkbox reflects them; a row click, Space or a row's control no longer drops the selected ids whose rows are not rendered, and `onSelectedItemsChange` (same signature) reports them, so an id of a filtered-out item, of another page or restored by id stays selected until your code removes it. In single mode the selected id is the first one given. Select-all reports the ids outside `items` first, then the others in `items` order (0.15: the order the rows mounted in, the sorted order for a grid that mounts sorted). A grid of `DataGrid.Row` children is unchanged.
2893
+ 2. **Disabled rows:** select-all can skip only the rows it knows. Pass `isSelectionDisabled(item, index)` when items can be disabled and not all rows are rendered (a `virtualWindow`): it also becomes each rendered row's selection-cell `disabled`.
2894
+ 3. **New development warnings** for a `DataGrid.Row` whose own `rowId` differs from its item's id, or is no item's id, in a grid with `items` (`DataGrid.Row:row-id-mismatch`, `DataGrid.Row:row-id-outside-items`): pass a `getRowId` that returns the rows' ids. The `DataGrid:single-several-selected` text changes with `items`.
2895
+ 4. **Types:** `UseGridNavigationResult` has two new members (`activeRowIndex`, `pendingRowIndex`), which a hand-built value adds; `ListboxOptionElementProps` gains optional `aria-posinset`, `aria-setsize` and `style` (spread them in an option of yours; merge `style` after your own); `DataGridLabels` gains `resizeColumn` and `columnWidth`.
2896
+
2897
+ A windowed list needs a bounded height and a fixed item height (see [Large data](#large-data)). The CHANGELOG's [0.16.0 section](CHANGELOG.md) lists the additions and the full upgrade notes.
2898
+
2649
2899
  ## Upgrading from 0.14
2650
2900
 
2651
2901
  0.15 removes nothing public and every type only widens, but the overlays and disclosures of item 1 (not yet the listbox popups of Combobox, Dropdown, TagPicker and TimePicker, the DatePicker calendar and the AvatarGroup popover) now animate by default; these changes can affect your end-to-end tests, styles and type mocks: