@box-kite/react 2.2.4 → 2.2.6
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.
- package/.claude/rules/box-kite-rules.md +11 -1
- package/.claude/skills/box-kite/SKILL.md +11 -1
- package/.claude/skills/box-kite/references/components.md +83 -3
- package/.cursor/rules/box-kite.mdc +11 -1
- package/AGENTS.md +11 -1
- package/BOX_AI_CONTEXT.md +371 -25
- package/BOX_KITE_AI_CONTEXT.md +371 -25
- package/behavior.cjs +1 -1
- package/behavior.mjs +57 -57
- package/box.cjs +1 -1
- package/box.d.ts +5 -0
- package/box.mjs +15 -15
- package/catalog.cjs +1 -1
- package/catalog.mjs +906 -0
- package/client.cjs +1 -1
- package/client.mjs +94 -80
- package/components/calendar.cjs +1 -0
- package/components/calendar.d.ts +77 -0
- package/components/calendar.mjs +483 -0
- package/components/dashboard.cjs +1 -1
- package/components/dashboard.mjs +1 -1
- package/components/dataGrid/components/dataGridBooleanValue.d.ts +11 -0
- package/components/dataGrid/components/dataGridDateParts.d.ts +40 -0
- package/components/dataGrid/components/dataGridDateValue.d.ts +14 -0
- package/components/dataGrid/components/dataGridList.d.ts +12 -0
- package/components/dataGrid/components/dataGridListAggregate.d.ts +7 -0
- package/components/dataGrid/components/dataGridListFilter.d.ts +7 -0
- package/components/dataGrid/components/dataGridListFilterSheet.d.ts +11 -0
- package/components/dataGrid/components/dataGridListFooter.d.ts +13 -0
- package/components/dataGrid/components/dataGridListGroup.d.ts +13 -0
- package/components/dataGrid/components/dataGridListItem.d.ts +21 -0
- package/components/dataGrid/components/dataGridListLayout.d.ts +20 -0
- package/components/dataGrid/components/dataGridListRecord.d.ts +11 -0
- package/components/dataGrid/components/dataGridListSelectionBar.d.ts +11 -0
- package/components/dataGrid/components/dataGridListSheet.d.ts +30 -0
- package/components/dataGrid/components/dataGridListSortSheet.d.ts +10 -0
- package/components/dataGrid/components/dataGridListToolbar.d.ts +10 -0
- package/components/dataGrid/components/dataGridListValue.d.ts +12 -0
- package/components/dataGrid/contracts/dataGridContract.d.ts +70 -11
- package/components/dataGrid/models/columnModel.d.ts +3 -1
- package/components/dataGrid/models/editModel.d.ts +3 -1
- package/components/dataGrid/models/gridModel.d.ts +3 -0
- package/components/dataGrid/models/listModel.d.ts +129 -0
- package/components/dataGrid/models/localeText.d.ts +36 -0
- package/components/dataGrid/models/viewportModel.d.ts +0 -2
- package/components/dataGrid/useDateParts.d.ts +17 -0
- package/components/dataGrid/useListLayout.d.ts +13 -0
- package/components/dataGrid.cjs +3 -3
- package/components/dataGrid.mjs +1251 -854
- package/components/dateField.cjs +1 -0
- package/components/dateField.d.ts +78 -0
- package/components/dateField.mjs +313 -0
- package/components/datePicker.cjs +1 -0
- package/components/datePicker.d.ts +120 -0
- package/components/datePicker.mjs +529 -0
- package/components/dateTime.cjs +1 -0
- package/components/dateTime.d.ts +23 -0
- package/components/dateTime.mjs +34 -0
- package/components/dialog.cjs +1 -1
- package/components/dialog.mjs +29 -29
- package/components/list/listItemParts.d.ts +9 -0
- package/components/list.cjs +1 -0
- package/components/list.d.ts +118 -0
- package/components/list.mjs +391 -0
- package/components/menu.cjs +1 -1
- package/components/menu.mjs +25 -25
- package/components/overlay.cjs +1 -1
- package/components/overlay.mjs +12 -12
- package/components/semantics.d.ts +29 -29
- package/components/slider.cjs +1 -1
- package/components/slider.mjs +1 -1
- package/components/sortableList.cjs +1 -0
- package/components/sortableList.d.ts +49 -0
- package/components/sortableList.mjs +384 -0
- package/components/swipeItem.cjs +1 -0
- package/components/swipeItem.d.ts +52 -0
- package/components/swipeItem.mjs +321 -0
- package/components/tabs.cjs +1 -1
- package/components/tabs.mjs +1 -1
- package/components/timeZoneSelect.cjs +1 -0
- package/components/timeZoneSelect.d.ts +52 -0
- package/components/timeZoneSelect.mjs +74 -0
- package/components/toaster.cjs +1 -1
- package/components/toaster.mjs +24 -24
- package/components/virtualList.cjs +1 -0
- package/components/virtualList.d.ts +32 -0
- package/components/virtualList.mjs +188 -0
- package/dataGridDateParts.cjs +1 -0
- package/dataGridDateParts.mjs +223 -0
- package/dataGridList.cjs +1 -0
- package/dataGridList.mjs +1314 -0
- package/dataGridListLayout.cjs +1 -0
- package/dataGridListLayout.mjs +49 -0
- package/date-environment.cjs +1 -0
- package/date-environment.mjs +29 -0
- package/date-input.cjs +1 -0
- package/date-input.mjs +346 -0
- package/date-math.cjs +1 -0
- package/date-math.mjs +317 -0
- package/date.cjs +1 -0
- package/date.d.ts +40 -0
- package/date.mjs +4 -0
- package/dates.cjs +1 -0
- package/dates.mjs +689 -0
- package/docs/components.md +10 -1
- package/icons/closeIcon.d.ts +2 -0
- package/icons/filterIcon.d.ts +2 -0
- package/layer.cjs +1 -1
- package/layer.mjs +1 -1
- package/mcp/src/data.d.ts +2 -0
- package/package.json +178 -22
- package/placement.cjs +1 -1
- package/placement.mjs +10 -10
- package/platform.cjs +1 -1
- package/platform.mjs +4 -1
- package/react/a11y/useControllableState.d.ts +2 -1
- package/react/boxComponent.d.ts +1 -1
- package/react/componentsContext.d.ts +4 -0
- package/react/componentsProvider.d.ts +10 -0
- package/react/date/dateChange.d.ts +18 -0
- package/react/date/dateEnvironment.d.ts +34 -0
- package/react/resolveStyles.d.ts +2 -1
- package/react/theme/theme.d.ts +6 -1
- package/react-shared.cjs +1 -1
- package/react-shared.mjs +6 -6
- package/registry/blocks/dashboard-shell/dashboard-shell.d.ts +3 -1
- package/registry/designs/store/address-form.d.ts +11 -0
- package/registry/designs/store/cart-items.d.ts +10 -0
- package/registry/designs/store/cart-page.d.ts +4 -0
- package/registry/designs/store/checkout-page.d.ts +7 -0
- package/registry/designs/store/credit-card-form.d.ts +10 -0
- package/registry/designs/store/form-field.d.ts +19 -0
- package/registry/designs/store/order-summary.d.ts +18 -0
- package/registry/designs/store/product-art.d.ts +26 -0
- package/registry/designs/store/product-card.d.ts +11 -0
- package/registry/designs/store/product-gallery.d.ts +11 -0
- package/registry/designs/store/product-grid.d.ts +10 -0
- package/registry/designs/store/product-page.d.ts +5 -0
- package/registry/designs/store/quantity-stepper.d.ts +13 -0
- package/registry/designs/store/shipping-method.d.ts +14 -0
- package/registry/designs/store/store-data.d.ts +53 -0
- package/registry/designs/store/store-design.d.ts +63 -0
- package/registry/designs/store/store-shell.d.ts +12 -0
- package/registry/designs/store/storefront-page.d.ts +4 -0
- package/registry/designs/store/variant-picker.d.ts +15 -0
- package/rsc.cjs +1 -1
- package/rsc.d.ts +2 -0
- package/rsc.mjs +1 -1
- package/scrolling.cjs +1 -0
- package/scrolling.mjs +22 -0
- package/utils/date/calendar.d.ts +44 -0
- package/utils/date/calendarModel.d.ts +46 -0
- package/utils/date/dateTime.d.ts +144 -0
- package/utils/date/fieldModel.d.ts +74 -0
- package/utils/date/format.d.ts +39 -0
- package/utils/date/iso.d.ts +40 -0
- package/utils/date/locale.d.ts +17 -0
- package/utils/date/parse.d.ts +20 -0
- package/utils/date/settings.d.ts +26 -0
- package/utils/date/value.d.ts +43 -0
- package/utils/date/zoneOptions.d.ts +19 -0
- package/utils/date/zones.d.ts +32 -0
- package/utils/scroll/scrollUtils.d.ts +6 -0
- package/utils/sortable/sortableUtils.d.ts +25 -0
- package/utils/swipe/swipeUtils.d.ts +53 -0
- package/utils/virtual/measuredWindow.d.ts +28 -0
- package/utils/virtual/rowSearch.d.ts +11 -0
package/BOX_KITE_AI_CONTEXT.md
CHANGED
|
@@ -1064,6 +1064,36 @@ Box.components({
|
|
|
1064
1064
|
});
|
|
1065
1065
|
```
|
|
1066
1066
|
|
|
1067
|
+
`extends` is resolved over everything registered so far, chains included, so overriding the base later still reaches the component built on it.
|
|
1068
|
+
|
|
1069
|
+
**A prop on the element wins in every theme.** `<Box component="card" bgColor="red-500">` replaces the card's `bgColor` and also its `theme.dark.bgColor` (at the same level: `hover.bgColor` replaces the themed hover). Everything else the card styles for dark stays. To keep a different dark value, write it: `theme={{ dark: { bgColor: 'red-900' } }}`.
|
|
1070
|
+
|
|
1071
|
+
### Box.createComponents() — A Design for One Subtree
|
|
1072
|
+
|
|
1073
|
+
`Box.components()` styles the whole app: every call merges into one registry, so two designs cannot share a page through it. `Box.createComponents()` takes the same shape and registers nothing. It returns a design, and `<Box.Components value>`, or `components` on `Box.Theme`, applies it to everything inside.
|
|
1074
|
+
|
|
1075
|
+
```tsx
|
|
1076
|
+
const aurora = Box.createComponents({ button: { styles: { borderRadius: 999 } }, card: { styles: { p: 6 } } });
|
|
1077
|
+
const harbor = Box.createComponents({ button: { styles: { borderRadius: 0 } } }, { base: 'builtin' });
|
|
1078
|
+
|
|
1079
|
+
<Box.Theme use="local" components={aurora}> {/* light/dark + design in one wrapper */}
|
|
1080
|
+
<Button>Pill</Button>
|
|
1081
|
+
</Box.Theme>
|
|
1082
|
+
<Box.Components value={harbor}>
|
|
1083
|
+
<Button>Square</Button>
|
|
1084
|
+
</Box.Components>
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
- **`base`** is what the design is merged over:
|
|
1088
|
+
- `'global'` (the default) is the app's own `Box.components()`, followed as it changes.
|
|
1089
|
+
- `'builtin'` ignores the app's registrations.
|
|
1090
|
+
- Another registry chains onto that design.
|
|
1091
|
+
- **Props still beat the design**, the way they beat `Box.components()`.
|
|
1092
|
+
- **A design is identified by its content**, so building one inline does not grow the cache. Module scope still reads best.
|
|
1093
|
+
- **`<Box.Components>` renders no element.** `value={undefined}` hands a subtree back to the app's design.
|
|
1094
|
+
- **A Server Component cannot read the provider.** A client component under it can.
|
|
1095
|
+
- **Types for two designs that both name a component**: `interface ComponentsTypes extends MergeComponentsAndVariants<[typeof aurora, typeof harbor]> {}` (from `@box-kite/core/types`). Extending two `ExtractComponentsAndVariants` that disagree on `button` is an interface conflict.
|
|
1096
|
+
|
|
1067
1097
|
---
|
|
1068
1098
|
|
|
1069
1099
|
## Extension System
|
|
@@ -1891,6 +1921,170 @@ on something at the panel's edge needs, since the clip is permanent.
|
|
|
1891
1921
|
|
|
1892
1922
|
---
|
|
1893
1923
|
|
|
1924
|
+
## List (`components/list`)
|
|
1925
|
+
|
|
1926
|
+
```tsx
|
|
1927
|
+
import List from '@box-kite/react/components/list';
|
|
1928
|
+
|
|
1929
|
+
<List>
|
|
1930
|
+
{people.map((person) => (
|
|
1931
|
+
<List.Item key={person.id} chevron>
|
|
1932
|
+
<List.Media variant="avatar">{person.initials}</List.Media>
|
|
1933
|
+
<List.Content>
|
|
1934
|
+
<List.Row>
|
|
1935
|
+
<List.Title lines={1}>
|
|
1936
|
+
<List.Action props={{ href: `/people/${person.id}` }}>{person.name}</List.Action>
|
|
1937
|
+
</List.Title>
|
|
1938
|
+
<List.Meta>{person.lastSeen}</List.Meta>
|
|
1939
|
+
</List.Row>
|
|
1940
|
+
<List.Description lines={2}>{person.bio}</List.Description>
|
|
1941
|
+
</List.Content>
|
|
1942
|
+
</List.Item>
|
|
1943
|
+
))}
|
|
1944
|
+
</List>;
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
A list of things to read and pick from — messages, orders, people, settings. **An item is a flex row of
|
|
1948
|
+
three slots**, and the content is a column of rows:
|
|
1949
|
+
|
|
1950
|
+
| Part | Element | What it is |
|
|
1951
|
+
| ------------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
1952
|
+
| `List` | `<ul role="list">` | `variant`: dividers by default, `plain`, `card` (one framed block), `separated` (a frame per item); `tag="ol"` |
|
|
1953
|
+
| `List.Item` | `<li>` | A flex row. `size` `sm`/`md`/`lg` (44/56/72px), `wrap`, `selected` (a tint), `chevron` |
|
|
1954
|
+
| `List.Media` | `<div>` | Leading: `variant` `icon` (a tinted square), `avatar` (a circle that clips), `image` (a thumbnail) |
|
|
1955
|
+
| `List.Content` | `<div>` | The middle, taking the room; a column |
|
|
1956
|
+
| `List.Row` | `<div>` | Parts side by side, the last at the end. `secondary` (smaller, muted), `wrap` |
|
|
1957
|
+
| `List.Overline` | `<span>` | A small line above the title. `lines` |
|
|
1958
|
+
| `List.Title` | `<span>` | The name, holding the `List.Action`. `lines` `1` (ellipsis), `2`/`3` (clamp); unset, it wraps |
|
|
1959
|
+
| `List.Description` | `<span>` | Muted supporting text. `lines` |
|
|
1960
|
+
| `List.Meta` | `<span>` | A small value that never wraps: a time, a count |
|
|
1961
|
+
| `List.End` | `<div>` | Trailing: a value over its label, aligned to the end in tabular figures |
|
|
1962
|
+
| `List.Actions` | `<div>` | Controls of the item's own, lifted above the action |
|
|
1963
|
+
| `List.Action` | `<a>` / `<label>` / `<button>` | The primary action, covering the item: `href` → link, `htmlFor` → label, otherwise a button |
|
|
1964
|
+
| `List.Fields` | `<dl>` | Labelled values, as many to a line as fit at 8rem |
|
|
1965
|
+
| `List.Field` | `<div>` + `<dt>` + `<dd>` | `label` and the value |
|
|
1966
|
+
| `List.Group` | `<li>` + `<ul>` | A section: `label`, `level` (makes the label a heading), `sticky` |
|
|
1967
|
+
| `List.Chevron` | `<span aria-hidden>` | The chevron `chevron` draws, for an item something else renders |
|
|
1968
|
+
|
|
1969
|
+
**One action covers the item.** `List.Action`'s `::after` stretches over the whole `<li>`, so a press
|
|
1970
|
+
anywhere on the item is a press on it and the focus ring draws round the item — while its accessible
|
|
1971
|
+
name is the title alone, not every value in the row. A pick-list is a label: put the checkbox in
|
|
1972
|
+
`List.Actions` and give the action its `htmlFor`.
|
|
1973
|
+
|
|
1974
|
+
```tsx
|
|
1975
|
+
<List.Item selected={picked}>
|
|
1976
|
+
<List.Actions>
|
|
1977
|
+
<Checkbox id="pick-ada" checked={picked} onChange={toggle} />
|
|
1978
|
+
</List.Actions>
|
|
1979
|
+
<List.Title>
|
|
1980
|
+
<List.Action props={{ htmlFor: 'pick-ada' }}>Ada Lovelace</List.Action>
|
|
1981
|
+
</List.Title>
|
|
1982
|
+
</List.Item>
|
|
1983
|
+
```
|
|
1984
|
+
|
|
1985
|
+
Hover, pressed, current (`aria-current` on the action) and disabled are read off the action with
|
|
1986
|
+
`:has()`, so an item with no action is plain text and needs no prop to say so.
|
|
1987
|
+
|
|
1988
|
+
**It renders on a server**: no hook, no context, no effect. That is why the list's look reaches its
|
|
1989
|
+
items as custom properties rather than through React — and they are yours to set with `vars`:
|
|
1990
|
+
|
|
1991
|
+
| Variable | Default | What it is |
|
|
1992
|
+
| ---------------------- | --------------------- | ------------------------------------------------------------- |
|
|
1993
|
+
| `--list-divider` | `currentColor` at 12% | The line between items; `plain` and `separated` make it clear |
|
|
1994
|
+
| `--list-divider-inset` | `0px` | Where the line starts — `4.5rem` lines it up with the text |
|
|
1995
|
+
| `--list-item-frame` | `none` | The border round each item, which `separated` sets |
|
|
1996
|
+
| `--list-item-radius` | `0` | Each item's corners, which its tint follows |
|
|
1997
|
+
| `--list-sticky-top` | `0` | How far down a sticky group label stops — an app's header |
|
|
1998
|
+
|
|
1999
|
+
**The style tree is paint only** — `list` (the three variants), `list.item` (the divider colour, the
|
|
2000
|
+
states, `selected`), `list.media`, `list.muted`, `list.action` (the focus ring) and `list.label` —
|
|
2001
|
+
because every engine entry carries it. The spacing is each part's default props, so a prop on the part
|
|
2002
|
+
beats it (`<List.Item py={1}>`) and `Box.components` cannot move it. The hairlines and tints are mixed
|
|
2003
|
+
from `currentColor`, so they follow any theme with no rule for it.
|
|
2004
|
+
|
|
2005
|
+
It is a `<ul role="list">` — the role spelled out because Safari stops reporting a list whose items have
|
|
2006
|
+
lost their markers — and **not a composite widget**: each action is a tab stop, Enter follows or presses
|
|
2007
|
+
it, and the arrow keys stay the page's. Arrow-key selection among options is a listbox: `Combobox` or
|
|
2008
|
+
`Select`.
|
|
2009
|
+
|
|
2010
|
+
### Swipe actions, reorder and a long list
|
|
2011
|
+
|
|
2012
|
+
Three client-only entries on top of `List`, so `List` itself stays server-safe. Each item they draw is a
|
|
2013
|
+
`List.Item` in everything but name — same parts, same sizes, same `list.item` paint.
|
|
2014
|
+
|
|
2015
|
+
```tsx
|
|
2016
|
+
import SwipeItem from '@box-kite/react/components/swipeItem';
|
|
2017
|
+
import SortableList from '@box-kite/react/components/sortableList';
|
|
2018
|
+
import VirtualList from '@box-kite/react/components/virtualList';
|
|
2019
|
+
|
|
2020
|
+
<SwipeItem
|
|
2021
|
+
fullSwipe
|
|
2022
|
+
startActions={
|
|
2023
|
+
<SwipeItem.Action variant="accent" onClick={pin}>
|
|
2024
|
+
Pin
|
|
2025
|
+
</SwipeItem.Action>
|
|
2026
|
+
}
|
|
2027
|
+
endActions={
|
|
2028
|
+
<SwipeItem.Action variant="danger" onClick={remove}>
|
|
2029
|
+
Delete
|
|
2030
|
+
</SwipeItem.Action>
|
|
2031
|
+
}
|
|
2032
|
+
>
|
|
2033
|
+
<List.Content>…</List.Content>
|
|
2034
|
+
</SwipeItem>;
|
|
2035
|
+
|
|
2036
|
+
<SortableList onOrderChange={(order) => setIds(order)} onOrderCommit={save}>
|
|
2037
|
+
{ids.map((id) => (
|
|
2038
|
+
<SortableList.Item key={id} value={id} label={names[id]}>
|
|
2039
|
+
<List.Content>…</List.Content>
|
|
2040
|
+
<SortableList.Handle />
|
|
2041
|
+
</SortableList.Item>
|
|
2042
|
+
))}
|
|
2043
|
+
</SortableList>;
|
|
2044
|
+
|
|
2045
|
+
<VirtualList items={messages} itemKey={(message) => message.id} onRangeChange={loadMore}>
|
|
2046
|
+
{(message) => <List.Item>…</List.Item>}
|
|
2047
|
+
</VirtualList>;
|
|
2048
|
+
```
|
|
2049
|
+
|
|
2050
|
+
| Component | Props |
|
|
2051
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2052
|
+
| `SwipeItem` | `startActions`, `endActions` (the first of each is outermost), `fullSwipe`, `open`/`defaultOpen`/`onOpenChange`, and `List.Item`'s `size`/`wrap`/`selected`/`chevron` |
|
|
2053
|
+
| `SwipeItem.Action` | A `<button>`: `variant` `accent`/`warning`/`danger` (unset is neutral), `icon`, `onClick`, `closeOnSelect` (default `true`) |
|
|
2054
|
+
| `SortableList` | Everything `List` takes, `onOrderChange(order, { reason, value, from, to })`, `onOrderCommit` (the drop — the one to save) |
|
|
2055
|
+
| `SortableList.Item` | Everything `List.Item` takes, `value` (required, unique), `label` (what the handle and announcements call it), `disabled` |
|
|
2056
|
+
| `SortableList.Handle` | The `<button>` a drag starts from; six dots unless given children; named "Move" + the item's `label` |
|
|
2057
|
+
| `VirtualList` | Everything `List` takes, `items`, `itemKey`, `children` (a function returning one `<li>`), `estimateSize` (57), `overscan` (6), `initialCount` (20), `onRangeChange`, `scrollerRef` |
|
|
2058
|
+
|
|
2059
|
+
**`SwipeItem`'s row slides and a tray opens in the space it leaves** — nothing is under the row, so it
|
|
2060
|
+
needs no background. Past half open opens, short of it closes, a flick goes the way it was going, and
|
|
2061
|
+
`fullSwipe` carried most of the way across performs the outermost action (it clicks it, so its `onClick`
|
|
2062
|
+
is the one place the command lives). A press on an open item closes it instead of following the action.
|
|
2063
|
+
The actions are real buttons in the tab order and focus reaching one opens its tray; Escape closes it.
|
|
2064
|
+
A swipe is a path gesture (WCAG 2.5.1), so put the same commands somewhere a press reaches too.
|
|
2065
|
+
|
|
2066
|
+
**`SortableList`: the app owns the order.** During a drag nothing is reordered — the item follows the
|
|
2067
|
+
pointer, its neighbours step aside as inline translates — and `onOrderChange` fires once, on the drop,
|
|
2068
|
+
which then glides into place (the Web Animations API, from where it was drawn). The keyboard is a grab,
|
|
2069
|
+
DashboardGrid's model: Enter/Space picks up, ArrowUp/ArrowDown/Home/End move (reporting each step),
|
|
2070
|
+
Enter drops, Escape puts back. A press on a handle that does not drag also picks the item up, and a
|
|
2071
|
+
press on another item then drops it there — the single-pointer way WCAG 2.5.7 asks for. Render the
|
|
2072
|
+
items in the order you hold; a refused move is just not applied.
|
|
2073
|
+
|
|
2074
|
+
**`VirtualList` measures each item it draws** and keeps the height by `itemKey`, so mixed heights work;
|
|
2075
|
+
an undrawn item counts as the mean of those drawn (`estimateSize` before any is), and the list holds its
|
|
2076
|
+
height while it scrolls, since a scrollbar drag loses a move whenever it changes. The scroller is the nearest ancestor that scrolls, else the
|
|
2077
|
+
page; `scrollerRef` overrides. Each item gets `aria-setsize`/`aria-posinset`, the item focus is in
|
|
2078
|
+
stays rendered, and an item above the screen that turns out taller than estimated scrolls the list by
|
|
2079
|
+
the difference so nothing on screen moves. Groups and sticky labels need the whole list and do not
|
|
2080
|
+
window; find-in-page cannot see an unrendered item.
|
|
2081
|
+
|
|
2082
|
+
Styling: `swipeItem.row`/`swipeItem.tray` (`dragging` variants), `swipeItem.action` (the three colour
|
|
2083
|
+
variants, `armed`), `sortableList.handle` (`grabbed`), and on `list.item` the `lifted` (dragged),
|
|
2084
|
+
`grabbed` (keyboard) and `settling` variants. `List.Chevron` is the item's chevron as a part.
|
|
2085
|
+
|
|
2086
|
+
---
|
|
2087
|
+
|
|
1894
2088
|
## Slider & Progress
|
|
1895
2089
|
|
|
1896
2090
|
```tsx
|
|
@@ -1995,6 +2189,94 @@ Box.components({
|
|
|
1995
2189
|
|
|
1996
2190
|
---
|
|
1997
2191
|
|
|
2192
|
+
## Dates and times (`@box-kite/react/date` + the date components)
|
|
2193
|
+
|
|
2194
|
+
```tsx
|
|
2195
|
+
import { format, toUtc, toLocal, toZone, toPlainDate, parse, add, today } from '@box-kite/react/date';
|
|
2196
|
+
import DateField from '@box-kite/react/components/dateField';
|
|
2197
|
+
import Calendar from '@box-kite/react/components/calendar';
|
|
2198
|
+
import DatePicker, { DateRangePicker } from '@box-kite/react/components/datePicker';
|
|
2199
|
+
import TimeZoneSelect from '@box-kite/react/components/timeZoneSelect';
|
|
2200
|
+
import DateTime, { DateProvider } from '@box-kite/react/components/dateTime';
|
|
2201
|
+
|
|
2202
|
+
toUtc('2026-10-19T14:30', { from: 'Europe/Chisinau' }); // '2026-10-19T11:30:00Z'
|
|
2203
|
+
toLocal('2026-10-19T11:30:00Z'); // the reader's wall clock, with its offset
|
|
2204
|
+
format('2026-10-19', { locale: 'ro-MD', dateStyle: 'long' }); // '19 octombrie 2026'
|
|
2205
|
+
parse('03/04/2026', { locale: 'en-GB' }); // '2026-04-03'
|
|
2206
|
+
|
|
2207
|
+
<DateField label="Due" name="due" />;
|
|
2208
|
+
<DatePicker type="datetime" zone="utc" label="Publish at" onValueChange={(value, { utc, local }) => schedule(utc)} />;
|
|
2209
|
+
<DateRangePicker label="Stay" startName="from" endName="to" />;
|
|
2210
|
+
<DateTime value={order.createdAt} />;
|
|
2211
|
+
```
|
|
2212
|
+
|
|
2213
|
+
**Every value is an ISO string, and its shape is its kind** — never a `Date`, which is always a moment in
|
|
2214
|
+
the runtime's zone:
|
|
2215
|
+
|
|
2216
|
+
| Kind | Written as | Means |
|
|
2217
|
+
| ---------- | ------------------------------------------ | --------------------------------------------------------- |
|
|
2218
|
+
| `date` | `2026-10-19` | A day: no time, no zone, the same everywhere. |
|
|
2219
|
+
| `time` | `14:30:00` (`.sss` when there are any) | A time of day. |
|
|
2220
|
+
| `datetime` | `2026-10-19T14:30:00` | A wall clock with no zone — an alarm, a timetable. |
|
|
2221
|
+
| `instant` | `2026-10-19T14:30:00+03:00`, or `…Z` (UTC) | One moment, written as one zone sees it. Anything stored. |
|
|
2222
|
+
|
|
2223
|
+
A zone is `'local'` (the reader), `'utc'`, `'system'` (the runtime), an IANA name or a fixed offset.
|
|
2224
|
+
**`'local'` on a server** is `configureDates({ timeZone })` or the nearest `<DateProvider timeZone>`;
|
|
2225
|
+
`systemZone()` is always the server's own. The browser writes its zone into a cookie with
|
|
2226
|
+
`document.cookie = zoneCookie()`, and a server reads it back with `readZoneCookie(request.headers.get('cookie'))`.
|
|
2227
|
+
|
|
2228
|
+
| Function (`@box-kite/react/date`) | What it does |
|
|
2229
|
+
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
2230
|
+
| `toUtc(v, { from })` / `toLocal(v)` / `toZone(v, zone)` | The moment in UTC / on the reader's clock / on another zone's, with offset. |
|
|
2231
|
+
| `toPlainDate` / `toPlainTime` / `toPlainDateTime` / `toInstant` | `(v, { zone })` — the day, time or wall clock a value shows in `zone`. |
|
|
2232
|
+
| `toDate` / `fromDate` / `toEpoch` | The bridge to an API that insists on a `Date`. |
|
|
2233
|
+
| `combine(date, time, { zone })` | A date and a time: a date-time, or with `zone` the instant. |
|
|
2234
|
+
| `today(zone)` / `now(zone)` / `currentTime(zone)` | In the reader's zone unless told. |
|
|
2235
|
+
| `format(v, { locale, zone, dateStyle, timeStyle, … })` | `Intl` options, never format tokens. `''` for a non-value. |
|
|
2236
|
+
| `formatRange` / `formatRelative` / `zoneName` | `Oct 19 – 23, 2026`, `3 minutes ago`, `EEST`. |
|
|
2237
|
+
| `parse(text, { locale, kind, zone })` | ISO, `Date`, epoch, HTTP date, or text in the locale's order. |
|
|
2238
|
+
| `add` / `subtract` / `startOf` / `endOf` / `difference` | Calendar units on the wall clock, clock units on the instant. |
|
|
2239
|
+
| `compare` / `isBefore` / `isAfter` / `isEqual` / `isSame` / `clamp` | Across kinds and zones. |
|
|
2240
|
+
| `details(v, { zone })` | Every reading at once: `utc`, `zoned`, `local`, `offset`, `date`, `epoch`. |
|
|
2241
|
+
| `weekInfo` / `hourCycleOf` / `monthNames` / `weekdayNames` | The locale, from `Intl`. |
|
|
2242
|
+
| `timeZones` / `offsetOf` / `isValidZone` / `normalizeZone` | The runtime's zones. |
|
|
2243
|
+
| `configureDates` / `localZone` / `systemZone` / `zoneCookie` / `readZoneCookie` | Who the reader is. |
|
|
2244
|
+
|
|
2245
|
+
**DST** follows Temporal: a wall-clock time a zone skips or repeats takes `disambiguation` —
|
|
2246
|
+
`'compatible'` (default: the later reading of a skipped time, the earlier of a repeated one), `'earlier'`,
|
|
2247
|
+
`'later'` or `'reject'` (`null`).
|
|
2248
|
+
|
|
2249
|
+
**The controls take and give the same string, and `onValueChange(value, details)` hands over the readings:**
|
|
2250
|
+
`details` is `{ reason, event, utc, local, zoned, date, epoch, zone, offset }`, all `null` while the value
|
|
2251
|
+
is. `reason` is `'keyboard'`, `'paste'`, `'select'`, `'preset'`, `'clear'` or `'reset'`.
|
|
2252
|
+
|
|
2253
|
+
| Component (`components/…`) | What it is |
|
|
2254
|
+
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2255
|
+
| `DateField` (`dateField`) | Segments (`role="spinbutton"`) in the locale's order. `type`, `zone`, `granularity`, `hourCycle`, `min`/`max`, `isDateUnavailable`, `name`, `required`, `label`. |
|
|
2256
|
+
| `Calendar` (`calendar`) | APG's grid keys. `mode="range"` → `{ start, end }`, `months`, `weekNumbers`, `min`/`max`, `isDateUnavailable`, `firstDayOfWeek`. |
|
|
2257
|
+
| `DatePicker` (`datePicker`) | A `DateField` with a calendar (`date`), a list of times every `step` minutes (`time`) or both (`datetime`) in the top layer; `presets`, `clearable`, `closeOnSelect`. |
|
|
2258
|
+
| `DateRangePicker` (`datePicker`) | Two fields over a range calendar; `startName`/`endName`, `months` (2), `presets` of `{ start, end }`. |
|
|
2259
|
+
| `TimeZoneSelect` | A `Combobox` over every zone, searchable by place, region or offset; the value is the IANA name. |
|
|
2260
|
+
| `DateTime` (`dateTime`) | `<time datetime>` in the reader's zone and language; `format`, `relative`, `zone`, `locale`. Also exports `DateProvider`. |
|
|
2261
|
+
|
|
2262
|
+
**A date-time's `zone`** is whose wall clock it is typed on: `'local'` (default), `'utc'`, an IANA name, or
|
|
2263
|
+
`'none'` (a wall clock with no zone, value `2026-10-19T14:30:00`). The value is the instant on that clock
|
|
2264
|
+
(`…+03:00`, or `…Z` for UTC), and a zone that is not the reader's is named after the time.
|
|
2265
|
+
|
|
2266
|
+
**Four things to know.** **Nothing typed is ever parsed**: `03/04/2026` is two days depending on who typed
|
|
2267
|
+
it, so each segment is a spin button and only a paste is read as text. **No hydration error, ever**: a
|
|
2268
|
+
server and a hydrating browser both render in the fallback (`UTC`/`en-US`, or a `DateProvider`'s), and the
|
|
2269
|
+
render after hydration switches to the reader — so pass the cookie's zone to a `DateProvider` for a first
|
|
2270
|
+
paint that is already right. **`name` posts the canonical string** (and `required` is a real input the
|
|
2271
|
+
browser validates), so a server reads `2026-10-19` whatever the reader's locale. And in a **DataGrid**, a
|
|
2272
|
+
column takes `editor: 'date' | 'time' | 'datetime'` (inferred from an ISO string or a `Date`),
|
|
2273
|
+
`dateFormat: 'date' | 'time' | 'datetime' | 'relative' | Intl options` and `filterable: { type: 'date' }`
|
|
2274
|
+
(`on`/`before`/`after`/`between`, each day a `DatePicker`); those parts are a chunk the grid fetches only when it shows a date.
|
|
2275
|
+
Styling: `dateField` (extends `textbox`; `segment`, `literal`, `zone`, `label`; `compact`, `invalid`,
|
|
2276
|
+
`disabled`), `calendar` (`nav`, `title`, `weekday`, `weekNumber`, `cell` with the range band, `day` with
|
|
2277
|
+
`today`/`selected`/`outside`/`blocked`/`unavailable`, `zoomItem`) and `datePicker` (`trigger`, `clear`,
|
|
2278
|
+
`presets`, `preset`, `times`, `time`, `part`); the panel is a `popover`.
|
|
2279
|
+
|
|
1998
2280
|
## Toaster (`toast()` + `<Toaster>`)
|
|
1999
2281
|
|
|
2000
2282
|
```tsx
|
|
@@ -2396,30 +2678,33 @@ named ones carry the reason.
|
|
|
2396
2678
|
| `footer` | `boolean \| { label? }` | `false` | A pinned row of grand totals over every column with an `aggregate`. `label` replaces the default `Total` |
|
|
2397
2679
|
| `export` | `boolean \| ExportConfig` | `false` | Export buttons in the top bar: `{ csv?, xlsx?, fileName? }`. The writers load on the first press |
|
|
2398
2680
|
| `tabNavigation` | `'none' \| 'cells'` | `'none'` | What Tab does. `'none'` is APG's single tab stop; `'cells'` is the spreadsheet reading |
|
|
2681
|
+
| `layout` | `'auto' \| 'table' \| 'list'` | `'auto'` | A table, or a list once the grid's own width is under `list.breakpoint` (see "On a phone") |
|
|
2682
|
+
| `list` | `ListConfig` | — | `{ breakpoint?, fields?, batchSize?, recordView? }` — the list layout's settings |
|
|
2399
2683
|
| `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) |
|
|
2400
2684
|
|
|
2401
2685
|
### ColumnType
|
|
2402
2686
|
|
|
2403
|
-
| Prop | Type
|
|
2404
|
-
| ------------------------ |
|
|
2405
|
-
| `key` | `Key`
|
|
2406
|
-
| `header` | `string`
|
|
2407
|
-
| `width` | `number`
|
|
2408
|
-
| `align` | `'start' \| 'end' \| 'center' \| 'left' \| 'right'`
|
|
2409
|
-
| `pin` | `'START' \| 'END'` (also `'LEFT'`/`'RIGHT'`)
|
|
2410
|
-
| `columns` | `ColumnType[]`
|
|
2411
|
-
| `Cell` | `({ cell }) => ReactNode`
|
|
2412
|
-
| `sortable` / `resizable` | `boolean`
|
|
2413
|
-
| `flexible` | `boolean`
|
|
2414
|
-
| `filterable` | `boolean \| FilterConfig`
|
|
2415
|
-
| `contextMenu` | `boolean \| ContextMenuConfig`
|
|
2416
|
-
| `aggregate` | `AggregateName \| (values, rows) => value`
|
|
2417
|
-
| `AggregateCell` | `({ cell }) => ReactNode`
|
|
2418
|
-
| `editable` | `boolean \| (row) => boolean`
|
|
2419
|
-
| `editor` | `CellEditorType \| CellEditorConfig`
|
|
2420
|
-
| `EditCell` | `({ cell }) => ReactNode`
|
|
2421
|
-
| `exportValue` | `(row: TRow) => value`
|
|
2422
|
-
| `
|
|
2687
|
+
| Prop | Type | Default | Description |
|
|
2688
|
+
| ------------------------ | ------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2689
|
+
| `key` | `Key` | required | Column identifier (maps to TRow property) |
|
|
2690
|
+
| `header` | `string` | — | Header text |
|
|
2691
|
+
| `width` | `number` | `200` | Base width in px |
|
|
2692
|
+
| `align` | `'start' \| 'end' \| 'center' \| 'left' \| 'right'` | `'left'` | Cell alignment. `start`/`end` follow the reading order; `left`/`right` stay on the screen side they name |
|
|
2693
|
+
| `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 |
|
|
2694
|
+
| `columns` | `ColumnType[]` | — | Nested columns (grouped header) |
|
|
2695
|
+
| `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 |
|
|
2696
|
+
| `sortable` / `resizable` | `boolean` | inherits | Override grid-level setting |
|
|
2697
|
+
| `flexible` | `boolean` | `true` | Participate in flex width distribution |
|
|
2698
|
+
| `filterable` | `boolean \| FilterConfig` | — | `true` (text), `{ type: 'number', min?, max? }`, `{ type: 'multiselect', options? }` |
|
|
2699
|
+
| `contextMenu` | `boolean \| ContextMenuConfig` | inherits | Override grid-level context menu. `false` hides entirely. `{ sort?, pin?, group? }` controls sections |
|
|
2700
|
+
| `aggregate` | `AggregateName \| (values, rows) => value` | — | Total this column over each group row, and over the footer. See below |
|
|
2701
|
+
| `AggregateCell` | `({ cell }) => ReactNode` | — | Renders the aggregate. `cell`: `{ value, column, rows, scope }`, `scope` being `'group'` or `'footer'` |
|
|
2702
|
+
| `editable` | `boolean \| (row) => boolean` | inherits | Whether this column’s cells can be edited — every row, or the rows a predicate says |
|
|
2703
|
+
| `editor` | `CellEditorType \| CellEditorConfig` | inferred | `text`/`number`/`checkbox`/`select`. Without one it is read off the value in the cell |
|
|
2704
|
+
| `EditCell` | `({ cell }) => ReactNode` | — | An editor of your own, bound to `cell.draft` with `cell.commitEdit()`/`cell.cancelEdit()` |
|
|
2705
|
+
| `exportValue` | `(row: TRow) => value` | the field | What this column writes to an exported file — an export runs no React, so a `Cell` renderer needs it |
|
|
2706
|
+
| `list` | `'title' \| 'kicker' \| 'subtitle' \| 'end' \| 'field' \| 'detail' \| 'hidden'` | inferred | Where the column goes in a list item, on a phone |
|
|
2707
|
+
| `exportFormat` | `string` | — | The number format its cells wear in a workbook, `'#,##0.00'` or `'yyyy-mm-dd'` |
|
|
2423
2708
|
|
|
2424
2709
|
### Aggregation
|
|
2425
2710
|
|
|
@@ -2599,11 +2884,13 @@ the same function — and an answer to an edit the user has since abandoned is d
|
|
|
2599
2884
|
```
|
|
2600
2885
|
|
|
2601
2886
|
- **Which editor a cell opens is read off the value in it** — a number gets `number`, a boolean
|
|
2602
|
-
`checkbox
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2887
|
+
`checkbox` (drawn as a check or a cross, read out as `localeText.booleanTrue`/`booleanFalse`), an ISO
|
|
2888
|
+
date, time or date-time a `DateField` (an edit keeps the shape the cell held: `…Z` stays UTC, a `Date`
|
|
2889
|
+
a `Date`), everything else `text` — unless `column.editor` names one of the seven
|
|
2890
|
+
(`text`/`number`/`checkbox`/`select`/`date`/`time`/`datetime`) or configures it
|
|
2891
|
+
(`{ type, options?, placeholder?, step?, min?, max?, zone? }`; `options` may be a function of the row).
|
|
2892
|
+
`column.EditCell` is a control of your own, bound to `cell.draft` with `cell.setDraft(v)`,
|
|
2893
|
+
`cell.commitEdit()` and `cell.cancelEdit()` — the same three calls the built-in seven make.
|
|
2607
2894
|
- **Pointer:** a press makes a cell the current one — it takes the ring, whether the pointer or the
|
|
2608
2895
|
keyboard put it there — and a **double press opens the editor** on an editable cell. A cell that cannot
|
|
2609
2896
|
be edited takes both as focus and nothing else, and a double press landing on a widget inside a cell (a
|
|
@@ -2715,6 +3002,65 @@ only gives it a bigger target.
|
|
|
2715
3002
|
landed, so `range.values()` reads what the grid now shows. Everything it accepted lands in the same
|
|
2716
3003
|
stream a typed value does: `onCellEditsChange(edits, { reason: 'paste' })`.
|
|
2717
3004
|
|
|
3005
|
+
### On a phone: the list layout
|
|
3006
|
+
|
|
3007
|
+
Below `def.list.breakpoint` (576px, 36rem) of **the grid's own width** — measured with a `ResizeObserver`, not read off the viewport — the grid draws each row as a list item instead of a table row. Same rows, filters, sort, selection, grouping, tree and edits; a different drawing. `layout: 'table'` or `layout: 'list'` chooses one for good.
|
|
3008
|
+
|
|
3009
|
+
```tsx
|
|
3010
|
+
<DataGrid
|
|
3011
|
+
data={orders}
|
|
3012
|
+
def={{
|
|
3013
|
+
title: 'Orders',
|
|
3014
|
+
globalFilter: true,
|
|
3015
|
+
rowSelection: true,
|
|
3016
|
+
columns: [
|
|
3017
|
+
{ key: 'customer', header: 'Customer', list: 'title' },
|
|
3018
|
+
{ key: 'id', header: 'Order', list: 'kicker' },
|
|
3019
|
+
{ key: 'total', header: 'Total', align: 'end', list: 'end', filterable: { type: 'number' } },
|
|
3020
|
+
{ key: 'status', header: 'Status', Cell: StatusBadge, filterable: { type: 'multiselect' } },
|
|
3021
|
+
{ key: 'date', header: 'Date' },
|
|
3022
|
+
{ key: 'notes', header: 'Notes', list: 'detail' },
|
|
3023
|
+
],
|
|
3024
|
+
}}
|
|
3025
|
+
/>
|
|
3026
|
+
```
|
|
3027
|
+
|
|
3028
|
+
| `column.list` | Where the column goes |
|
|
3029
|
+
| ------------- | ------------------------------------------------------------------------------------------------ |
|
|
3030
|
+
| `'title'` | The item's name, 16px semibold, and the button that opens the record view |
|
|
3031
|
+
| `'kicker'` | A small muted line above the title — an id, a date |
|
|
3032
|
+
| `'subtitle'` | A line under the title; several are joined with `·` |
|
|
3033
|
+
| `'end'` | The one value opposite the title, with its column's name under it — a total, a status |
|
|
3034
|
+
| `'field'` | A labelled value in the two-column `<dl>` under the title |
|
|
3035
|
+
| `'detail'` | Only the record view |
|
|
3036
|
+
| `'hidden'` | Nowhere in the list layout |
|
|
3037
|
+
| (unset) | First column → title, first end-aligned → end, next `def.list.fields` (4) → field, rest → detail |
|
|
3038
|
+
|
|
3039
|
+
| `def.list` | Default | Description |
|
|
3040
|
+
| ------------ | ------- | -------------------------------------------------------------------------------------------------- |
|
|
3041
|
+
| `breakpoint` | `576` | The grid's own width, in px, below which `layout: 'auto'` draws the list |
|
|
3042
|
+
| `fields` | `4` | How many unassigned columns become fields before the rest wait for the record view |
|
|
3043
|
+
| `batchSize` | `20` | How many items one **Show more** adds. A paginated grid draws its page and a compact pager instead |
|
|
3044
|
+
| `recordView` | `true` | Whether pressing an item opens the record view |
|
|
3045
|
+
|
|
3046
|
+
What the list does, in the order a reader meets it:
|
|
3047
|
+
|
|
3048
|
+
- **Toolbar**: the title and row count, a full-width search (`globalFilter`), then pills — **Sort** and **Filters** open bottom sheets, **Select** (with `rowSelection`) turns selecting on. The filters in force sit under them as chips, each removable on its own.
|
|
3049
|
+
- **Sort sheet**: one radio group of the sortable columns and one of the direction, applied as they are picked.
|
|
3050
|
+
- **Filter sheet**: a section per filterable column — a full-width field, the phone's own operator picker for a number, chips for eight options or fewer and a searchable checklist past that. A filter applies as it is set, and the main button reads **Show N rows**.
|
|
3051
|
+
- **Item**: a press opens the record view; while selecting it ticks the row instead, a checkbox leads every item and a bar pinned to the bottom of the viewport counts the selection, with **Select all** and **Clear selection**. A tree row carries its chevron before the title, a `rowDetail` row a **Details** toggle, a group is a section header with its count and aggregates.
|
|
3052
|
+
- **Record view**: every column of one row in a bottom sheet, with a pager (`3 of 40`) that follows the list's own sort and filters — the arrow buttons, Left/Right, or a sideways swipe. An editable column has an **Edit** button that opens the column's editor through the same `def.onCellEdit`. Closing hands focus to the item of the row on screen.
|
|
3053
|
+
- **Footer**: the `def.footer` totals as a summary block, then **Show more** (`20 of 340`) — a button, never an endless scroll, so the totals and the page below stay reachable — or the compact pager when the host paginates.
|
|
3054
|
+
|
|
3055
|
+
Four things to know:
|
|
3056
|
+
|
|
3057
|
+
1. **It is a list, not a restyled grid.** A `<ul>` of `<li>`s, each value in a `<dl>`, `aria-posinset`/`aria-setsize` on each item; the grid's APG keyboard is the table's alone. Do not reach for `role="grid"` in a list item.
|
|
3058
|
+
2. **It is a chunk of its own.** The sheets and the record view load the first time a grid is narrow (and on import, on a phone-width screen); until then the grid goes on drawing its table. In a test, `await loadListLayout()` from `components/dataGrid/useListLayout` before rendering.
|
|
3059
|
+
3. **Unmeasured is a table.** A server render cannot know the width, so `layout: 'auto'` prerenders the table and a phone switches before its first paint after hydration. Force `layout: 'list'` where the server knows better.
|
|
3060
|
+
4. **The selection bar sticks to the viewport**, so no ancestor of the grid may be a scroll container (`overflow: hidden`/`auto`): `overflow: clip` clips the same corners without being one. The grid itself switches to `clip` in the list layout.
|
|
3061
|
+
|
|
3062
|
+
Styling: every row and group header is a `List.Item` (see List), so the `list` tree styles them — the dividers, the hover and pressed tints, `selected`, the muted text. The grid's own is `datagrid.list` > `toolbar` > (`count`, `search`, `button` with an `active` variant, `chip`), `toggle` (a tree's chevron, the Details toggle), `detail`, `button`, `selectionBar`, `sheet` > (`line`, `input`, `chip`, `button`, `primary`). Both trees carry paint only; the geometry is in the list's own chunk.
|
|
3063
|
+
|
|
2718
3064
|
### ContextMenuConfig
|
|
2719
3065
|
|
|
2720
3066
|
| Prop | Type | Default | Description |
|
package/behavior.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=require("./platform.cjs"),t=require("./effects.cjs"),n=require("./keyboard.cjs");let r=require("react");function i(e){let n=(0,r.useRef)(e);return t.n(()=>{n.current=e}),n}function a(e){let t=i(e);return(0,r.useCallback)((...e)=>t.current?.(...e),[t])}function o(e){let{value:t,defaultValue:n,onChange:o}=e,s=t!==void 0,[c,l]=(0,r.useState)(n),u=s?t:c,d=i(u),f=a(o);return[u,(0,r.useCallback)((e,t)=>{let n=typeof e==`function`?e(d.current):e;Object.is(n,d.current)||(l(n),f(n,t))},[f,d])]}var s=[];function c(e,t){let n=t.elements();return e.elements().some(e=>n.some(t=>e!==t&&e.contains(t)))}function l(){let e=s.filter(e=>!s.some(t=>t!==e&&c(e,t)));return e[e.length-1]}function u(n){let{enabled:r=!0,onDismiss:o,inside:c,escapeKey:u=!0,outsidePointer:d=!0}=n,f=i(c),p=a(o);t.n(()=>{if(!r)return;let t={elements:()=>(f.current??[]).map(t=>e.
|
|
1
|
+
const e=require("./platform.cjs"),t=require("./effects.cjs"),n=require("./keyboard.cjs");let r=require("react");function i(e){let n=(0,r.useRef)(e);return t.n(()=>{n.current=e}),n}function a(e){let t=i(e);return(0,r.useCallback)((...e)=>t.current?.(...e),[t])}function o(e){let{value:t,defaultValue:n,onChange:o}=e,s=t!==void 0,[c,l]=(0,r.useState)(n),u=s?t:c,d=i(u),f=a(o);return[u,(0,r.useCallback)((e,t)=>{let n=typeof e==`function`?e(d.current):e;Object.is(n,d.current)||(l(n),f(n,t))},[f,d])]}var s=[];function c(e,t){let n=t.elements();return e.elements().some(e=>n.some(t=>e!==t&&e.contains(t)))}function l(){let e=s.filter(e=>!s.some(t=>t!==e&&c(e,t)));return e[e.length-1]}function u(n){let{enabled:r=!0,onDismiss:o,inside:c,escapeKey:u=!0,outsidePointer:d=!0}=n,f=i(c),p=a(o);t.n(()=>{if(!r)return;let t={elements:()=>(f.current??[]).map(t=>e.c(t)).filter(e=>e!==null)};s.push(t);let n=new AbortController,i=(e,t)=>document.addEventListener(e,t,{signal:n.signal});return u&&i(`keydown`,e=>{e.key!==`Escape`||e.defaultPrevented||l()===t&&p(`escape`,e)}),d&&i(`pointerdown`,t=>{e.u(t,f.current??[])||p(`outside-pointer`,t)}),()=>{n.abort();let e=s.indexOf(t);e!==-1&&s.splice(e,1)}},[r,u,d,p,f])}function d(){let t=e.t()?.activeElement;return t&&t!==document.body?t:null}function f(t={}){let{enabled:n=!0,returnTo:i,preventScroll:o}=t,[s,c]=(0,r.useState)(null),[l,u]=(0,r.useState)(!1);n!==l&&(u(n),n&&c(d()));let f=a(()=>{(e.l(i)??s)?.focus({preventScroll:o})});return(0,r.useEffect)(()=>{if(n)return()=>{let e=document.activeElement;(!e||e===document.body||!document.contains(e))&&f()}},[n,f]),{returnFocus:f}}var p=10;function m(e,t){return t<0?-1:Math.max(0,Math.min(e,t))}function h(e,t,n,r,i){if(n===0)return-1;let a=e;for(let e=0;e<n;e++){if(a+=t,a<0||a>=n){if(!r)return-1;a=(a%n+n)%n}if(!i?.(a))return a}return-1}function g(e,t,n){return h(e===1?-1:t,e,t,!1,n)}function _(t){let{count:a,columns:s,columnIndexOf:c,pageSize:l=p,defaultActiveColumn:u=0,onActiveCellChange:d,orientation:f=`vertical`,loop:_=!0,activeIndex:v,defaultActiveIndex:y=0,onActiveIndexChange:b,isDisabled:x,textOf:S,focusItems:C=!0,onSelect:w}=t,[T,E]=o({value:v,defaultValue:y,onChange:b}),D=Math.min(T,a-1),O=s!==void 0,k=(0,r.useCallback)(e=>typeof s==`function`?s(e):s??0,[s]),A=(0,r.useCallback)((e,t)=>{let n=k(e);if(!c)return m(t,n-1);if(n<=0)return-1;let r=0;for(;r+1<n&&c(e,r+1)<=t;)r++;return r},[c,k]),[j,M]=(0,r.useState)(u),N=O&&D>=0?A(D,j):-1,P=(0,r.useRef)([]),F=(0,r.useRef)(new Map),I=(0,r.useRef)(new Map),L=(0,r.useRef)(new Map),R=(0,r.useRef)({query:``,at:0}),z=(0,r.useCallback)(e=>{P.current[e]?.focus()},[]),B=(0,r.useRef)(!1),V=(0,r.useCallback)((e,t)=>{B.current=!0,I.current.get(`${e}:${t}`)?.focus(),B.current=!1},[]),H=(0,r.useCallback)(()=>O?I.current.get(`${D}:${N}`)??null:P.current[D]??null,[N,D,O]),U=i({row:D,column:N}),W=i(j),G=i(d),K=i(k),q=i(A),J=i(c),Y=(0,r.useCallback)((e,t,n)=>{let r=m(e,a-1),i=Math.max(0,t),o=r<0?-1:q.current(r,i),s=U.current;return r===s.row&&o===s.column?s:(E(r,n),M(i),G.current?.({row:r,column:o},n),{row:r,column:o})},[U,q,G,a,E]),X=(0,r.useCallback)((e,t,n)=>{let r=m(e,a-1),i=r<0?-1:m(Math.max(0,t),K.current(r)-1);return Y(r,i<0?0:J.current?.(r,i)??i,n)},[J,a,Y,K]),Z=(0,r.useCallback)((e,t)=>{let n=`${e}:${t}`;return L.current.has(n)||L.current.set(n,e=>{e?I.current.set(n,e):(I.current.delete(n),L.current.delete(n))}),{ref:L.current.get(n),tabIndex:e===D&&t===N?0:-1,onFocus:()=>{B.current||X(e,t,{reason:`focus`})}}},[N,D,X]),Q=(0,r.useCallback)(e=>(F.current.has(e)||F.current.set(e,t=>{P.current[e]=t}),{ref:F.current.get(e),tabIndex:C?e===D?0:-1:void 0,onFocus:C?()=>E(e,{reason:`focus`}):void 0}),[D,C,E]),$=(0,r.useCallback)(t=>{let{row:n,column:r}=U.current,i=(e,n)=>{t.preventDefault();let r=X(e,n,{reason:`keyboard`,event:t});C&&V(r.row,r.column)},o=e=>{t.preventDefault();let n=Y(e,W.current,{reason:`keyboard`,event:t});C&&V(n.row,n.column)},s=2**53-1,c=()=>e.d(t.currentTarget)?-1:1;switch(t.key){case`ArrowRight`:return i(n,r+c());case`ArrowLeft`:return i(n,r-c());case`ArrowDown`:return o(n+1);case`ArrowUp`:return o(n-1);case`Home`:return t.ctrlKey||t.metaKey?i(0,0):i(n,0);case`End`:return t.ctrlKey||t.metaKey?i(a-1,s):i(n,s);case`PageDown`:return o(n+l);case`PageUp`:return o(n-l)}},[U,a,W,V,C,l,Y,X]);return{activeIndex:D,activeColumn:N,setActiveIndex:E,setActiveCell:X,onKeyDown:(0,r.useCallback)(t=>{if(t.defaultPrevented)return;if(O){$(t);return}let r=f!==`horizontal`,i=f!==`vertical`,o=R.current,s=n.t.isOpen(o,t.timeStamp),c=(e,n)=>{e!==-1&&e!==D&&(t.preventDefault(),E(e,{reason:n,event:t}),C&&z(e))};if(i&&(t.key===`ArrowRight`||t.key===`ArrowLeft`)){let n=t.key===`ArrowRight`!==e.d(t.currentTarget);c(h(D,n?1:-1,a,_,x),`keyboard`);return}if(t.key===`ArrowDown`&&r){c(h(D,1,a,_,x),`keyboard`);return}if(t.key===`ArrowUp`&&r){c(h(D,-1,a,_,x),`keyboard`);return}if(t.key===`Home`){c(g(1,a,x),`keyboard`);return}if(t.key===`End`){c(g(-1,a,x),`keyboard`);return}if(t.key===`Enter`||t.key===` `&&!(s&&S)){if(!w||D===-1)return;t.preventDefault(),w(D,t);return}if(S&&n.t.isPrintable(t)){let e=n.t.push(o,t.key,t.timeStamp);c(n.t.target(e,D,a,S,x),`typeahead`)}},[D,a,z,C,x,O,_,$,w,f,E,S]),itemProps:Q,cellProps:Z,activeItem:H}}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return _}});
|