@blakesteve/roster 5.12.0 → 5.14.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.
- package/README.md +188 -13
- package/dist/components/molecules/Carousel/Carousel.cjs +1 -1
- package/dist/components/molecules/Carousel/Carousel.d.ts +65 -3
- package/dist/components/molecules/Carousel/Carousel.js +376 -225
- package/dist/components/molecules/Carousel/carousel-math.cjs +1 -1
- package/dist/components/molecules/Carousel/carousel-math.d.ts +16 -0
- package/dist/components/molecules/Carousel/carousel-math.js +63 -44
- package/dist/components/molecules/Carousel/carousel-selection.cjs +1 -0
- package/dist/components/molecules/Carousel/carousel-selection.d.ts +28 -0
- package/dist/components/molecules/Carousel/carousel-selection.js +23 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.d.ts +29 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.js +21 -0
- package/dist/components/molecules/LiquidTabs/LiquidNav.cjs +1 -1
- package/dist/components/molecules/LiquidTabs/LiquidNav.d.ts +12 -1
- package/dist/components/molecules/LiquidTabs/LiquidNav.js +50 -44
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.d.ts +52 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.js +32 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabs.cjs +1 -1
- package/dist/components/molecules/LiquidTabs/LiquidTabs.d.ts +20 -1
- package/dist/components/molecules/LiquidTabs/LiquidTabs.js +60 -55
- package/dist/components/molecules/LiquidTabs/liquid-count.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/liquid-count.d.ts +37 -0
- package/dist/components/molecules/LiquidTabs/liquid-count.js +9 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -1
- package/dist/components/organisms/ModerationQueue/ModerationQueue.js +214 -193
- package/dist/index.d.ts +2 -0
- package/dist/internal/dev.cjs +1 -0
- package/dist/internal/dev.d.ts +10 -0
- package/dist/internal/dev.js +10 -0
- package/dist/internal/key-focus.d.ts +27 -2
- package/dist/meta.json +3 -3
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +150 -145
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -561,7 +561,7 @@ element marked `data-key-focus`. WebKit doesn't match `:focus-visible` on an
|
|
|
561
561
|
element a script focuses in answer to a key once the page has been clicked.
|
|
562
562
|
Escape handing focus back to a menu's or a select's trigger, or to a dialog's
|
|
563
563
|
opener, and a radio group's or tabs' arrows all drew no ring in Safari after
|
|
564
|
-
a click. A small
|
|
564
|
+
a click. A small tracker marks those elements: keyboard mode starts on a navigation key (Tab, Option+Tab, the
|
|
565
565
|
arrows, Escape, Enter, Space, Home, End, PageUp, PageDown) and ends on pointer
|
|
566
566
|
input, and it marks only what Chromium's own `:focus-visible` already matches,
|
|
567
567
|
so Chromium's rings are unchanged. It runs while a component that moves focus
|
|
@@ -576,6 +576,23 @@ variant in your stylesheet as Roster does:
|
|
|
576
576
|
@custom-variant focus-visible (&:is(:focus-visible, [data-key-focus]));
|
|
577
577
|
```
|
|
578
578
|
|
|
579
|
+
The variant is enough for your rings on a page where one of Roster's
|
|
580
|
+
components has the tracker running. A control of your own that moves focus by
|
|
581
|
+
script in answer to a key, arrows around a hand-drawn wheel or a grid that
|
|
582
|
+
roves its own focus, shouldn't count on one: call `useKeyFocus()` in it, and
|
|
583
|
+
the tracker runs while it's mounted. The listeners are shared, so it costs
|
|
584
|
+
nothing beside Roster's own components. `KEY_FOCUS` is the attribute's name,
|
|
585
|
+
for a selector or a test.
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
import { useKeyFocus } from "@blakesteve/roster";
|
|
589
|
+
|
|
590
|
+
function Wheel() {
|
|
591
|
+
useKeyFocus();
|
|
592
|
+
// arrows move focus between the stops with stop.focus(); rings on focus-visible:
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
579
596
|
### Motion
|
|
580
597
|
|
|
581
598
|
Two hand-rolled utilities, both of which stop for `prefers-reduced-motion`.
|
|
@@ -1352,6 +1369,10 @@ What it promises:
|
|
|
1352
1369
|
item's decision.
|
|
1353
1370
|
- **Steps take focus and give it back.** Escape or Cancel returns focus to the
|
|
1354
1371
|
button that opened the step.
|
|
1372
|
+
- **A blocked step says why, plainly.** A step that can't be submitted yet (a
|
|
1373
|
+
reason not picked, an edit with nothing changed) says why in plain text,
|
|
1374
|
+
and its submit is described by it. A submit tried anyway decides nothing
|
|
1375
|
+
and turns the reason into an error.
|
|
1355
1376
|
- **An item that disappears** (another reviewer, a reload) with its step open
|
|
1356
1377
|
closes the step without deciding, and is announced once as decided
|
|
1357
1378
|
elsewhere. Without a step, it moves focus only if focus was inside it.
|
|
@@ -1360,10 +1381,11 @@ What it promises:
|
|
|
1360
1381
|
- **Phones.** Every button is 44px tall at every width, and nothing scrolls
|
|
1361
1382
|
sideways at 320px. Nothing animates as an item leaves.
|
|
1362
1383
|
|
|
1363
|
-
Counts for a tab
|
|
1364
|
-
`items.length
|
|
1365
|
-
image can't render (an expired link, a
|
|
1366
|
-
falls back to a link to the original in the
|
|
1384
|
+
Counts for a tab need no API: the app holds the items, so it's
|
|
1385
|
+
`items.length`, passed as a `LiquidTabs` tab's `count`. `MediaPreview` is the
|
|
1386
|
+
thumbnail to use in an item: when the image can't render (an expired link, a
|
|
1387
|
+
format the browser can't draw), it falls back to a link to the original in the
|
|
1388
|
+
same footprint.
|
|
1367
1389
|
|
|
1368
1390
|
### FileUpload
|
|
1369
1391
|
|
|
@@ -1839,8 +1861,8 @@ Things to know:
|
|
|
1839
1861
|
|
|
1840
1862
|
### Carousel
|
|
1841
1863
|
|
|
1842
|
-
A sideways row: a row of cards, a selection strip, or a
|
|
1843
|
-
view. It is a real scroll container with CSS scroll snap, so touch, trackpad,
|
|
1864
|
+
A sideways row: a row of cards, a selection strip (`selectable`, below), or a
|
|
1865
|
+
gallery of one per view. It is a real scroll container with CSS scroll snap, so touch, trackpad,
|
|
1844
1866
|
Shift with the wheel and keyboard focus all scroll it without script, and every
|
|
1845
1867
|
item is in the server render. No clones, no virtualization, no loop, and
|
|
1846
1868
|
nothing moves on its own.
|
|
@@ -1873,11 +1895,159 @@ Script adds what a mouse is missing:
|
|
|
1873
1895
|
|
|
1874
1896
|
A vertical wheel over the row scrolls the page, and sideways overscroll is
|
|
1875
1897
|
contained. Under reduced motion every scroll the component makes is instant.
|
|
1876
|
-
`ref` gives `scrollToIndex(i)`, `scrollPrev()` and `
|
|
1898
|
+
`ref` gives `scrollToIndex(i)`, `scrollPrev()`, `scrollNext()` and `focus()`;
|
|
1877
1899
|
`onIndexChange` reports the first item wholly in view. For a gallery,
|
|
1878
1900
|
`showPosition` shows "2 of 5", which is also each item's name and is announced
|
|
1879
1901
|
politely when it changes.
|
|
1880
1902
|
|
|
1903
|
+
#### Selection mode
|
|
1904
|
+
|
|
1905
|
+
For a strip of choices, one of which is picked, pass `selectable`. The row
|
|
1906
|
+
becomes a horizontal listbox: one tab stop, the keys moving focus between
|
|
1907
|
+
options, and one option selected. A row of buttons with `aria-pressed` is a tab
|
|
1908
|
+
stop per button (300 options, 300 Tab presses), and a screen reader hears a
|
|
1909
|
+
list of unrelated buttons. Without `selectable` nothing changes.
|
|
1910
|
+
|
|
1911
|
+
```tsx
|
|
1912
|
+
<Carousel
|
|
1913
|
+
aria-label="Choose a record"
|
|
1914
|
+
aria-describedby="how"
|
|
1915
|
+
selectable
|
|
1916
|
+
selectedKey={chosen}
|
|
1917
|
+
onSelectionChange={setChosen}
|
|
1918
|
+
onActivate={play}
|
|
1919
|
+
clearable
|
|
1920
|
+
itemWidth={150}
|
|
1921
|
+
>
|
|
1922
|
+
{records.map((r) => <RecordCell key={r.id} record={r} selected={r.id === chosen} />)}
|
|
1923
|
+
</Carousel>
|
|
1924
|
+
```
|
|
1925
|
+
|
|
1926
|
+
- **Tab** lands on the selected option, or the first when nothing is. Leaving
|
|
1927
|
+
the row hands the tab stop back to the selected option.
|
|
1928
|
+
- **Left and Right** move by one, without wrapping; **Home and End** go to the
|
|
1929
|
+
ends; **Page Up and Page Down** move by as many options as are wholly in
|
|
1930
|
+
view.
|
|
1931
|
+
- **Type-ahead:** typing jumps to the next option whose name starts with what
|
|
1932
|
+
was typed. A pause of 700ms starts over, and the same letter again steps on
|
|
1933
|
+
to the next option starting with it.
|
|
1934
|
+
- **Enter or Space selects. Selection doesn't follow focus:** arrowing only
|
|
1935
|
+
moves focus, so whatever a selection sets off (a pan, a fetch, an
|
|
1936
|
+
announcement) happens once, on purpose.
|
|
1937
|
+
- **Enter, a click or a tap on the selected option** calls `onActivate`, the
|
|
1938
|
+
step after choosing: open it, read it, play it. A click or tap on any other
|
|
1939
|
+
option selects it.
|
|
1940
|
+
- **Escape** clears the selection when `clearable` is set, and calls
|
|
1941
|
+
`onSelectionChange(null)`.
|
|
1942
|
+
|
|
1943
|
+
Moving focus with the keys scrolls the option into view, smoothly, or at once
|
|
1944
|
+
under reduced motion; a click doesn't move the row. A selection doesn't scroll
|
|
1945
|
+
the row at all, so a `scrollToIndex` of your own (centering the choice, say)
|
|
1946
|
+
isn't undone. The keyboard-mode tracker is mounted, so the ring shows in Safari
|
|
1947
|
+
after a click too (see [Focus](#focus)).
|
|
1948
|
+
|
|
1949
|
+
**Identity is the child's `key`.** `selectedKey`, `defaultSelectedKey` and both
|
|
1950
|
+
callbacks deal in it, as React stores it: a string. Keys, not positions,
|
|
1951
|
+
because a consumer that filters the row changes every position after the first
|
|
1952
|
+
option it hides. A development build warns about a child without a key.
|
|
1953
|
+
Controlled (`selectedKey` with `onSelectionChange`) or uncontrolled
|
|
1954
|
+
(`defaultSelectedKey`); where focus is stays internal, and `focus()` on the
|
|
1955
|
+
`ref` moves focus onto the tab stop, from a find field's Down Arrow, say.
|
|
1956
|
+
|
|
1957
|
+
**Names.** An option is named by its content, or by `getOptionLabel(key,
|
|
1958
|
+
index)`, which is also the text type-ahead matches. Name the listbox with
|
|
1959
|
+
`aria-label` or `aria-labelledby`, and put a sentence saying how it works in
|
|
1960
|
+
`aria-describedby`.
|
|
1961
|
+
|
|
1962
|
+
**Nothing interactive inside an option.** An option is one choice, reached with
|
|
1963
|
+
the arrow keys; a link or a button inside it is a tab stop the keys skip, and
|
|
1964
|
+
a screen reader can't reach it as anything but text. Put actions outside the
|
|
1965
|
+
row, or behind `onActivate`. A development build warns when an option holds
|
|
1966
|
+
something that takes focus.
|
|
1967
|
+
|
|
1968
|
+
**Filtering.** Render fewer children and `aria-setsize` and `aria-posinset`
|
|
1969
|
+
describe what's shown. The tab stop stays on its option if that's still shown;
|
|
1970
|
+
otherwise it moves to the nearest option still shown, in the order before the
|
|
1971
|
+
change, after it if there is one, else before it. A selection made elsewhere
|
|
1972
|
+
moves the tab stop to the selected option while focus is outside the row, and
|
|
1973
|
+
leaves it where the keys put it while focus is inside.
|
|
1974
|
+
|
|
1975
|
+
**The selected look is yours.** Roster draws the focus ring, an outline 2px out,
|
|
1976
|
+
so a selected look drawn with a ring or a border sits inside it. Draw the
|
|
1977
|
+
selection in the child, from `selectedKey`, or on the option with
|
|
1978
|
+
`itemClassName` and the `aria-selected:` variant.
|
|
1979
|
+
|
|
1980
|
+
**Scale.** Every option is in the DOM, for the keys and for screen readers;
|
|
1981
|
+
draw expensive content lazily inside it. Options are memoized and the keys,
|
|
1982
|
+
clicks and focus are one listener each on the row. Measured at 1,000 options
|
|
1983
|
+
in Chromium: mounting takes about 30ms longer than the default mode, a key
|
|
1984
|
+
moves focus within a millisecond, and the frame after it lands in about 7ms.
|
|
1985
|
+
|
|
1986
|
+
### LiquidTabs
|
|
1987
|
+
|
|
1988
|
+
A tab strip with a sliding pill, on the WAI tabs pattern: one tab stop, arrows
|
|
1989
|
+
that wrap, Home and End, and `activation="manual"` for tabs that are costly to
|
|
1990
|
+
switch. `LiquidNav` is the same strip as a `nav` of links.
|
|
1991
|
+
|
|
1992
|
+
**Counts.** A tab or a nav link takes `count`, shown in a small mark after its
|
|
1993
|
+
label:
|
|
1994
|
+
|
|
1995
|
+
```tsx
|
|
1996
|
+
<LiquidTabs tabs={[{ id: "queue", label: "Queue", count: queue.length }, …]} … />
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
The count is part of the name, "Queue, 12 items" rather than "Queue12" or
|
|
2000
|
+
"Queue (12)", and its words come from `labels`, English by default with a
|
|
2001
|
+
plural. Name your own noun, and use `Intl.PluralRules` for a language with
|
|
2002
|
+
more than two forms:
|
|
2003
|
+
|
|
2004
|
+
```tsx
|
|
2005
|
+
labels={{ count: (n) => (n === 1 ? "1 game" : `${n.toLocaleString()} games`) }}
|
|
2006
|
+
```
|
|
2007
|
+
|
|
2008
|
+
- **Zero shows,** as "0" and "Queue, 0 items": an empty queue is worth
|
|
2009
|
+
saying. To show no count, pass `undefined`.
|
|
2010
|
+
- **A count that isn't a whole number from zero** (negative, a fraction,
|
|
2011
|
+
`NaN`) shows nothing, as if there were none, rather than being rounded or
|
|
2012
|
+
clamped into a number nobody passed.
|
|
2013
|
+
- **Large counts are written whole,** "12,345", in the mark and the name. To
|
|
2014
|
+
shorten the mark, pass `labels.countMark` (`(n) => "12K"`); the name stays
|
|
2015
|
+
whole, since a screen reader has no width to save.
|
|
2016
|
+
- **The mark is drawn from the tab's own text color:** its number in that
|
|
2017
|
+
color on a 12% tint of it. So it follows the tab through every state, both
|
|
2018
|
+
variants and both themes, and through any palette set on the
|
|
2019
|
+
`--roster-lt-*` tokens, and its number clears 4.5:1 in every state on
|
|
2020
|
+
Roster's own (4.88:1 at the least). `Badge` and `Pill` each draw from a fixed
|
|
2021
|
+
palette, which on the selected tab would be a second chip sitting on the
|
|
2022
|
+
pill.
|
|
2023
|
+
- **The pill follows,** re-measuring when a count widens its tab or one
|
|
2024
|
+
before it.
|
|
2025
|
+
|
|
2026
|
+
**The panel.** `LiquidTabPanel` is the `role="tabpanel"` a tab controls, paired
|
|
2027
|
+
with it by construction: its `id` is the tab's `panelId`, and its
|
|
2028
|
+
`aria-labelledby` points at the tab through `liquidTabId`, so the panel takes
|
|
2029
|
+
the tab's name, count included. Give the `LiquidTabs` an `id` to pair it with.
|
|
2030
|
+
One panel that swaps its content, or a panel per tab:
|
|
2031
|
+
|
|
2032
|
+
```tsx
|
|
2033
|
+
// One panel: every tab carries the same panelId, and `tab` is the selected one.
|
|
2034
|
+
<LiquidTabs id="lists" tabs={tabs.map((t) => ({ ...t, panelId: "lists-panel" }))} activeTab={list} onChange={setList} />
|
|
2035
|
+
<LiquidTabPanel tabsId="lists" id="lists-panel" tab={list}>…</LiquidTabPanel>
|
|
2036
|
+
|
|
2037
|
+
// A panel per tab: each its own panelId, and every panel gets activeTab.
|
|
2038
|
+
<LiquidTabPanel tabsId="views" id="views-board" tab="board" activeTab={view}>…</LiquidTabPanel>
|
|
2039
|
+
<LiquidTabPanel tabsId="views" id="views-list" tab="list" activeTab={view} keepMounted>…</LiquidTabPanel>
|
|
2040
|
+
```
|
|
2041
|
+
|
|
2042
|
+
Per tab, a panel that isn't selected stays in the page, hidden, so every tab's
|
|
2043
|
+
`aria-controls` points at something, and renders no content. `keepMounted`
|
|
2044
|
+
keeps its content mounted, so a half-filled form or a scroll position survives
|
|
2045
|
+
a switch away and back. A panel with nothing inside it to Tab to is itself in
|
|
2046
|
+
the tab order (`tabIndex={0}`), as the WAI pattern asks, so a keyboard user
|
|
2047
|
+
can reach what they just selected; it leaves the tab order once its content
|
|
2048
|
+
holds a link, a button or a field, rechecked as the content changes. Pass
|
|
2049
|
+
`tabIndex` to decide it yourself.
|
|
2050
|
+
|
|
1881
2051
|
### Components that render links
|
|
1882
2052
|
|
|
1883
2053
|
`Breadcrumbs` renders a plain `<a>` by default, which is right for a static
|
|
@@ -1899,8 +2069,10 @@ once, and the pages that use the wrapper stay server-rendered.
|
|
|
1899
2069
|
`LiquidNav` takes a `linkComponent` too, with a wider contract: each link
|
|
1900
2070
|
receives `href` and `to` with the same value, so React Router's `Link` works as
|
|
1901
2071
|
well as `next/link`, plus `className`, `children`, `aria-current`, `onClick` and
|
|
1902
|
-
`data-tab-id`, which it must forward to the DOM
|
|
1903
|
-
|
|
2072
|
+
`data-tab-id`, which it must forward to the DOM, and `aria-label` when the
|
|
2073
|
+
link has a count (forward it too; a component that doesn't still gets a name
|
|
2074
|
+
with the count in it). The props type is exported as `LiquidNavLinkProps`. The
|
|
2075
|
+
same client-component rule applies.
|
|
1904
2076
|
|
|
1905
2077
|
`Eyebrow` takes the other approach: `as` accepts any element and the props
|
|
1906
2078
|
follow it, so `<Eyebrow as="a" href="/work">` works, as does `as={NextLink}`.
|
|
@@ -1926,7 +2098,8 @@ Dialog or Sheet), alongside `Pagination`, the loading system and DataTable's
|
|
|
1926
2098
|
instead, and 5.8.0 adds the ones that were missing: MultiSelect's
|
|
1927
2099
|
`countLabel`, DataTable's `emptyMessage` and MediaPreview's `newTabLabel`, next
|
|
1928
2100
|
to the existing `dismissLabel`, `placeholder`, `removeLabel`, `prevLabel` and
|
|
1929
|
-
the like.
|
|
2101
|
+
the like. `LiquidTabs` and `LiquidNav` take `labels` for the words a count
|
|
2102
|
+
says. Words that take a number or a name are functions, so a language
|
|
1930
2103
|
whose word changes with the count can say so. An entry passed as `undefined`
|
|
1931
2104
|
(a catalog missing a key) keeps its English rather than leaving a control
|
|
1932
2105
|
unnamed.
|
|
@@ -2033,12 +2206,13 @@ function App() {
|
|
|
2033
2206
|
| `Breadcrumbs` | Navigation trail; pass `linkComponent` to keep navigation client-side |
|
|
2034
2207
|
| `CheckboxGroup` | Checkbox set reporting one `string[]`; groups, columns, scroll, error |
|
|
2035
2208
|
| `CallToAction` | Prominent hero-style CTA block |
|
|
2036
|
-
| `Carousel` | Sideways row of items on native scroll snap; mouse drag, paging arrows, no autoplay |
|
|
2209
|
+
| `Carousel` | Sideways row of items on native scroll snap; mouse drag, paging arrows, no autoplay; a selection mode for a strip of choices (see [Selection mode](#selection-mode)) |
|
|
2037
2210
|
| `DescriptionList` | Label and value pairs as a real `<dl>`: inline, stacked, or split |
|
|
2038
2211
|
| `EmptyState` | Zero-data placeholder with icon and action slot; `headingLevel` sets its title's `h1` to `h6` |
|
|
2039
2212
|
| `ErrorState` | Error display with retry action |
|
|
2040
2213
|
| `LiquidNav` | `LiquidTabs`' strip as a `nav` of links; takes a router's link component |
|
|
2041
|
-
| `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size
|
|
2214
|
+
| `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, counts, 44px `lg` size (see [LiquidTabs](#liquidtabs)) |
|
|
2215
|
+
| `LiquidTabPanel` | The panel a `LiquidTabs` tab controls, named for its tab by construction; one swapping panel or one per tab |
|
|
2042
2216
|
| `MatchupCard` | Head-to-head comparison card |
|
|
2043
2217
|
| `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
|
|
2044
2218
|
| `Menu` | A button's menu of actions: icons, shortcuts, checkbox and radio items, links, groups, submenus; the full keyboard pattern, 44px targets (see [Menu](#menu)) |
|
|
@@ -2125,6 +2299,7 @@ the one that can not be affected by a future change to the barrel.
|
|
|
2125
2299
|
| Hook | Description |
|
|
2126
2300
|
| ---------------- | ------------------------------------------------------------------------------- |
|
|
2127
2301
|
| `useCountdown` | Countdown timer logic without the UI; `days` counts the whole remaining total |
|
|
2302
|
+
| `useKeyFocus` | Keeps keyboard focus ringed in Safari for your own control that moves focus by script (see [Focus](#focus)). Ships `KEY_FOCUS` |
|
|
2128
2303
|
| `useKeySequence` | Fires a callback when a sequence of keys is typed in order. Ships `KONAMI_CODE` |
|
|
2129
2304
|
|
|
2130
2305
|
## Authoring components: the `rst:` prefix
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),Be=require("react"),$e=require("../../atoms/Button/Button.cjs"),T=require("../../../lib/utils.cjs"),ze=require("../../../lib/labels.cjs"),He=require("../../../internal/motion.cjs"),d=require("./carousel-math.cjs"),U=require("./carousel-layout.cjs"),Ue=require("../../../internal/key-focus.cjs");function Ve(o){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(o){for(const f in o)if(f!=="default"){const x=Object.getOwnPropertyDescriptor(o,f);Object.defineProperty(a,f,x.get?x:{enumerable:!0,get:()=>o[f]})}}return a.default=o,Object.freeze(a)}const n=Ve(Be),We={position:(o,a)=>`${o} of ${a}`},Xe=o=>Math.min(3e3,500+o*.35),Ge='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable]:not([contenteditable="false"])',V=o=>typeof o=="number"?`${o}px`:o;function se(o){const a=parseFloat(o);return Number.isFinite(a)?a:0}const Je="rst:hidden rst:[@media(any-hover:hover)]:inline-flex",Ke="rst:hidden rst:[@media(any-hover:hover)]:flex";function oe({direction:o}){return c.jsx("svg",{"aria-hidden":"true",focusable:"false",viewBox:"0 0 20 20",className:"rst:h-5 rst:w-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:c.jsx("path",{d:o==="prev"?"M12.5 4.5 7 10l5.5 5.5":"M7.5 4.5 13 10l-5.5 5.5"})})}const le={overflowing:null,canScrollPrev:!1,canScrollNext:!1,index:0},Qe=n.forwardRef(function({"aria-label":a,"aria-labelledby":f,children:x,itemWidth:I,perView:C,gap:W=12,gutter:X=16,bleed:ce=!1,snap:R="start",snapStrictness:ie="mandatory",oneAtATime:ae=!1,arrows:j="hover",arrowPlacement:M="controls",renderArrows:L,prevLabel:ue,nextLabel:de,prevIcon:fe,nextIcon:ge,arrowButtonProps:ve,fade:pe=!1,scrollbar:me=!1,showPosition:G=!1,labels:be,onIndexChange:J,onScrollStateChange:K,itemClassName:xe,listClassName:he,className:ye,style:Se,...we},Ne){Ue.useKeyFocus();const Q=ze.withLabels(We,be),p=n.useRef(null),Y=`${n.useId()}-list`,Z=n.Children.toArray(x),E=Z.length,[l,Te]=n.useState(le),ee=n.useRef(le),[Ie,Le]=n.useState(""),[Ee,Pe]=n.useState(!1),O=n.useRef({onIndexChange:J,onScrollStateChange:K});n.useEffect(()=>{O.current={onIndexChange:J,onScrollStateChange:K}});const g=n.useCallback(()=>{const e=p.current;if(!e)return null;const t=e.getBoundingClientRect(),r=getComputedStyle(e),s=t.left+e.clientLeft-e.scrollLeft;return{items:Array.from(e.children).map(u=>{const b=u.getBoundingClientRect();return{start:b.left-s,end:b.right-s}}),scrollLeft:e.scrollLeft,clientWidth:e.clientWidth,scrollWidth:e.scrollWidth,paddingStart:se(r.scrollPaddingInlineStart||r.scrollPaddingLeft),paddingEnd:se(r.scrollPaddingInlineEnd||r.scrollPaddingRight),align:R}},[R]),h=n.useCallback(()=>{const e=g();if(!e)return;const t={overflowing:d.overflows(e),canScrollPrev:d.canScrollPrev(e),canScrollNext:d.canScrollNext(e),index:e.items.length?d.currentIndex(e):0},r=ee.current;r.overflowing===t.overflowing&&r.canScrollPrev===t.canScrollPrev&&r.canScrollNext===t.canScrollNext&&r.index===t.index||(ee.current=t,Te(t),r.index!==t.index&&O.current.onIndexChange?.(t.index),(r.canScrollPrev!==t.canScrollPrev||r.canScrollNext!==t.canScrollNext)&&O.current.onScrollStateChange?.({canScrollPrev:t.canScrollPrev,canScrollNext:t.canScrollNext}))},[g]),ke=JSON.stringify([W,X,I,C]);n.useLayoutEffect(()=>{const e=p.current;if(!e)return;h();let t=0;const r=()=>{t||(t=requestAnimationFrame(()=>{t=0,h()}))};e.addEventListener("scroll",r,{passive:!0});const s=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>h()),i=()=>{s?.observe(e),Array.from(e.children).forEach(b=>s?.observe(b))};i();const u=typeof MutationObserver>"u"?null:new MutationObserver(()=>{i(),h()});return u?.observe(e,{childList:!0}),()=>{e.removeEventListener("scroll",r),s?.disconnect(),u?.disconnect(),t&&cancelAnimationFrame(t)}},[h,E,ke]),n.useLayoutEffect(()=>{const e=p.current;if(e&&(Pe(!e.querySelector(Ge)),f)){const t=f.split(/\s+/).map(r=>document.getElementById(r)?.textContent?.trim()??"").filter(Boolean).join(" ");Le(t)}},[x,f]);const y=n.useRef(null),v=n.useCallback(()=>{const e=p.current,t=y.current;t&&(y.current=null,window.clearTimeout(t.timer),e?.removeEventListener("scrollend",t.done),e&&(e.style.scrollSnapType="",delete e.dataset.settling))},[]),m=n.useCallback((e,t)=>{const r=p.current;if(!r)return;if(v(),t!=="smooth"||He.prefersReducedMotion()||Math.abs(r.scrollLeft-e)<1){r.style.scrollSnapType="none",r.scrollTo({left:e,behavior:"instant"}),r.style.scrollSnapType="";return}r.style.scrollSnapType="none",r.dataset.settling="";const i=r.scrollLeft,u=()=>{Math.abs(r.scrollLeft-i)<.5||v()};y.current={timer:window.setTimeout(v,Xe(Math.abs(r.scrollLeft-e))),done:u,target:e},r.addEventListener("scrollend",u),r.scrollTo({left:e,behavior:"smooth"})},[v]),P=n.useCallback(e=>{const t=g();if(!t)return;const r=y.current?{...t,scrollLeft:y.current.target}:t;m(d.pageTarget(r,e),"smooth")},[g,m]);n.useImperativeHandle(Ne,()=>({scrollToIndex:(e,t)=>{const r=g();if(!r||!r.items.length)return;const s=Math.min(Math.max(0,Math.round(e)),r.items.length-1);m(d.snapPosition(r,s),t?.behavior??"smooth")},scrollPrev:()=>P(-1),scrollNext:()=>P(1)}),[g,P,m]),n.useEffect(()=>v,[v]);const D=n.useRef(null),F=n.useRef(null),_=n.useRef(null);n.useLayoutEffect(()=>{const e=_.current;if(!e)return;const t=e==="prev"?D.current:F.current,r=e==="prev"?F.current:D.current,s=e==="prev"?!l.canScrollPrev:!l.canScrollNext,i=document.activeElement;s&&(i!==t&&i!==document.body&&i!==null||r&&!r.disabled&&r.focus())},[l.canScrollPrev,l.canScrollNext]);const S=n.useRef(null),w=n.useRef(!1),Ce=e=>{e.pointerType!=="mouse"||e.button!==0||l.overflowing&&(v(),w.current=!1,S.current={pointerId:e.pointerId,x0:e.clientX,scroll0:e.currentTarget.scrollLeft,dragging:!1})},Re=e=>{const t=S.current;if(!t||e.pointerId!==t.pointerId)return;const r=e.currentTarget,s=e.clientX-t.x0;if(!t.dragging){if(Math.abs(s)<=d.CAROUSEL_DRAG_THRESHOLD)return;t.dragging=!0,r.dataset.dragging="",r.style.scrollSnapType="none",window.getSelection?.()?.removeAllRanges();try{r.setPointerCapture(e.pointerId)}catch{}}r.scrollLeft=t.scroll0-s},A=e=>{const t=S.current;if(!t||e.pointerId!==t.pointerId||(S.current=null,!t.dragging))return;const r=e.currentTarget;delete r.dataset.dragging,w.current=e.type==="pointerup",window.setTimeout(()=>{w.current=!1},0);const s=g();s?m(d.snapPosition(s,d.nearestIndex(s)),"smooth"):r.style.scrollSnapType=""},je=e=>{!w.current||e.detail===0||(w.current=!1,e.preventDefault(),e.stopPropagation())},Me=e=>{l.overflowing&&e.preventDefault()},Oe=e=>{const t=e.currentTarget,r=e.target;if(r===t||S.current?.dragging)return;let s=!1;try{s=r.matches(":focus-visible, [data-key-focus]")}catch{s=!1}if(!s)return;const i=r.closest("[data-carousel-item]");if(!i||i.parentElement!==t)return;const u=g();if(!u)return;const b=Array.prototype.indexOf.call(t.children,i);m(d.snapPosition(u,b),"instant")},[De,Fe]=n.useState(0);n.useEffect(()=>{const e=window.setTimeout(()=>Fe(l.index),150);return()=>window.clearTimeout(e)},[l.index]);const te=a??Ie,re=e=>te?`${e} ${te}`:e,q=j!=="none"&&l.overflowing!==!1,_e=j==="hover"?Je:"rst:inline-flex",ne=e=>{const t=e==="prev",{className:r,...s}=ve??{};return c.jsx($e.Button,{type:"button",variant:"outline",colorScheme:"neutral",size:"icon",...s,"aria-label":t?ue??re("Previous"):de??re("Next"),"aria-controls":Y,disabled:t?!l.canScrollPrev:!l.canScrollNext,onClick:()=>P(t?-1:1),ref:t?D:F,onFocus:()=>{_.current=e},onBlur:i=>{i.relatedTarget&&(_.current=null)},"data-carousel-arrow":e,className:T.cn("rst:size-11 rst:shrink-0 rst:rounded-full",M==="overlay"&&!L&&"rst:bg-[var(--roster-popover-bg)] rst:elevation-raised rst:hover:bg-[var(--roster-popover-bg)] rst:disabled:invisible",_e,r),children:t?fe??c.jsx(oe,{direction:"prev"}):ge??c.jsx(oe,{direction:"next"})})},k=q?ne("prev"):null,B=q?ne("next"):null,N=G&&E?c.jsx("p",{"aria-live":"polite",className:"rst:font-ui rst:m-0 rst:text-sm rst:tabular-nums rst:opacity-75","data-carousel-position":"",children:Q.position(De+1,E)}):null;let $=null;L?$=L({prev:k,next:B,position:N}):M==="controls"&&(k||N)&&($=c.jsxs("div",{className:T.cn("rst:mb-2 rst:items-center rst:justify-end rst:gap-2",!N&&j==="hover"?Ke:"rst:flex"),"data-carousel-controls":"",children:[N&&c.jsx("div",{className:"rst:mr-auto",children:N}),k,B]}));const z=pe&&l.overflowing&&(l.canScrollPrev||l.canScrollNext)?`linear-gradient(to right, ${l.canScrollPrev?"transparent, black 48px":"black"}, ${l.canScrollNext?"black calc(100% - 48px), transparent":"black"})`:void 0,H=I===void 0&&C!==void 0,Ae={"--rst-cv-gap":V(W),"--rst-cv-gutter":V(X),...H?U.perViewVars(C):{},...z?{maskImage:z,WebkitMaskImage:z}:{}},qe=I!==void 0?{width:V(I)}:H?{width:U.FLUID_ITEM_WIDTH}:void 0;return c.jsxs("div",{className:T.cn("rst:min-w-0",ye),style:Se,...we,children:[$,c.jsxs("div",{className:"rst:relative",children:[c.jsx("ul",{ref:p,id:Y,role:"list","aria-label":a,"aria-labelledby":a?void 0:f,tabIndex:Ee&&l.overflowing?0:void 0,"data-overflowing":l.overflowing?"":void 0,onPointerDown:Ce,onPointerMove:Re,onPointerUp:A,onPointerCancel:A,onLostPointerCapture:A,onClickCapture:je,onDragStart:Me,onFocus:Oe,style:Ae,className:T.cn("rst:m-0 rst:flex rst:list-none rst:overflow-x-auto rst:overflow-y-hidden","rst:gap-(--rst-cv-gap) rst:px-(--rst-cv-gutter) rst:py-1 rst:scroll-px-(--rst-cv-gutter)","rst:overscroll-x-contain",ie==="mandatory"?"rst:snap-x rst:snap-mandatory":"rst:snap-x rst:snap-proximity",ce&&"rst:-mx-(--rst-cv-gutter)",!me&&"rst:[scrollbar-width:none] rst:[&::-webkit-scrollbar]:hidden",H&&U.FLUID_CLASSES,"rst:data-overflowing:cursor-grab rst:data-dragging:cursor-grabbing rst:data-dragging:select-none","rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-inset",he),children:Z.map((e,t)=>c.jsx("li",{"data-carousel-item":"","aria-label":G?Q.position(t+1,E):void 0,style:qe,className:T.cn("rst:shrink-0",R==="start"?"rst:snap-start":"rst:snap-center",ae&&"rst:snap-always",xe),children:e},n.isValidElement(e)&&e.key!=null?e.key:t))}),!L&&M==="overlay"&&q&&c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:start-1 rst:flex rst:items-center",children:c.jsx("div",{className:"rst:pointer-events-auto",children:k})}),c.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:end-1 rst:flex rst:items-center",children:c.jsx("div",{className:"rst:pointer-events-auto",children:B})})]})]})]})});exports.Carousel=Qe;
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react/jsx-runtime"),Ee=require("react"),Te=require("../../atoms/Button/Button.cjs"),_=require("../../../lib/utils.cjs"),Ie=require("../../../lib/labels.cjs"),Ne=require("../../../internal/motion.cjs"),v=require("./carousel-math.cjs"),ct=require("./carousel-layout.cjs"),at=require("./carousel-selection.cjs"),Ce=require("../../../internal/key-focus.cjs"),Re=require("../../../internal/dev.cjs");function Pe(l){const c=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const u in l)if(u!=="default"){const x=Object.getOwnPropertyDescriptor(l,u);Object.defineProperty(c,u,x.get?x:{enumerable:!0,get:()=>l[u]})}}return c.default=l,Object.freeze(c)}const s=Pe(Ee),Le={position:(l,c)=>`${l} of ${c}`},je=l=>Math.min(3e3,500+l*.35),ut='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable]:not([contenteditable="false"])',dt=l=>typeof l=="number"?`${l}px`:l;function Lt(l){const c=parseFloat(l);return Number.isFinite(c)?c:0}const Oe="rst:hidden rst:[@media(any-hover:hover)]:inline-flex",De="rst:hidden rst:[@media(any-hover:hover)]:flex";function jt({direction:l}){return a.jsx("svg",{"aria-hidden":"true",focusable:"false",viewBox:"0 0 20 20",className:"rst:h-5 rst:w-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:a.jsx("path",{d:l==="prev"?"M12.5 4.5 7 10l5.5 5.5":"M7.5 4.5 13 10l-5.5 5.5"})})}const Me="rst:cursor-pointer rst:in-data-dragging:cursor-grabbing rst:outline-hidden rst:focus-visible:outline-solid rst:focus-visible:outline-2 rst:focus-visible:outline-offset-2 rst:focus-visible:outline-ring",Ae=s.memo(function({child:c,index:u,count:x,selected:C,active:k,label:P,style:q,className:F}){return a.jsx("li",{role:"option","aria-selected":C,"aria-setsize":x,"aria-posinset":u+1,"aria-label":P,tabIndex:k?0:-1,"data-carousel-item":"","data-selected":C?"":void 0,style:q,className:F,children:c})});function _e(l){const c=[];return s.Children.forEach(l,u=>{if(u==null||typeof u=="boolean")return;const x=s.isValidElement(u)&&u.key!==null;c.push({child:u,key:x?String(u.key):`#${c.length}`,keyed:x})}),c}const qe={active:null,keys:[],sig:"",selected:null,init:!1},W=(l,c)=>Array.prototype.indexOf.call(l.children,c),Ot={overflowing:null,canScrollPrev:!1,canScrollNext:!1,index:0},Fe=s.forwardRef(function({"aria-label":c,"aria-labelledby":u,"aria-describedby":x,children:C,itemWidth:k,perView:P,gap:q=12,gutter:F=16,bleed:Dt=!1,snap:G="start",snapStrictness:Mt="mandatory",oneAtATime:At=!1,arrows:X="hover",arrowPlacement:J="controls",renderArrows:$,prevLabel:_t,nextLabel:qt,prevIcon:Ft,nextIcon:$t,arrowButtonProps:Bt,fade:zt=!1,scrollbar:Ht=!1,showPosition:ft=!1,labels:Vt,onIndexChange:gt,onScrollStateChange:pt,selectable:p=!1,selectedKey:vt,defaultSelectedKey:Kt=null,onSelectionChange:Ut,onActivate:mt,clearable:Wt=!1,getOptionLabel:Y,itemClassName:Gt,listClassName:Xt,className:Jt,style:Yt,...Qt},Zt){Ce.useKeyFocus();const yt=Ie.withLabels(Le,Vt),h=s.useRef(null),xt=`${s.useId()}-list`,ht=s.Children.toArray(C),w=p?_e(C):null,L=w?w.length:ht.length,bt=vt!==void 0,[te,ee]=s.useState(Kt),d=bt?vt:te,[re,St]=s.useState(!1),m=w?w.map(t=>t.key):[],kt=m.join("\0"),[E,B]=s.useState(qe);let T=E.active;if(p&&(!E.init||E.sig!==kt||E.selected!==d)){const t=new Set(m);E.init&&E.selected!==d&&!re&&d!==null&&t.has(d)&&(T=d),(T===null||!t.has(T))&&(T=at.survivingNeighbor(E.keys,t,T)??(d!==null&&t.has(d)?d:m[0]??null)),B({active:T,keys:m,sig:kt,selected:d,init:!0})}const[f,ne]=s.useState(Ot),wt=s.useRef(Ot),[se,oe]=s.useState(""),[ie,le]=s.useState(!1),Q=s.useRef({onIndexChange:gt,onScrollStateChange:pt});s.useEffect(()=>{Q.current={onIndexChange:gt,onScrollStateChange:pt}});const y=s.useCallback(()=>{const t=h.current;if(!t)return null;const e=t.getBoundingClientRect(),r=getComputedStyle(t),n=e.left+t.clientLeft-t.scrollLeft;return{items:Array.from(t.children).map(i=>{const g=i.getBoundingClientRect();return{start:g.left-n,end:g.right-n}}),scrollLeft:t.scrollLeft,clientWidth:t.clientWidth,scrollWidth:t.scrollWidth,paddingStart:Lt(r.scrollPaddingInlineStart||r.scrollPaddingLeft),paddingEnd:Lt(r.scrollPaddingInlineEnd||r.scrollPaddingRight),align:G}},[G]),j=s.useCallback(()=>{const t=y();if(!t)return;const e={overflowing:v.overflows(t),canScrollPrev:v.canScrollPrev(t),canScrollNext:v.canScrollNext(t),index:t.items.length?v.currentIndex(t):0},r=wt.current;r.overflowing===e.overflowing&&r.canScrollPrev===e.canScrollPrev&&r.canScrollNext===e.canScrollNext&&r.index===e.index||(wt.current=e,ne(e),r.index!==e.index&&Q.current.onIndexChange?.(e.index),(r.canScrollPrev!==e.canScrollPrev||r.canScrollNext!==e.canScrollNext)&&Q.current.onScrollStateChange?.({canScrollPrev:e.canScrollPrev,canScrollNext:e.canScrollNext}))},[y]),ce=JSON.stringify([q,F,k,P]);s.useLayoutEffect(()=>{const t=h.current;if(!t)return;j();let e=0;const r=()=>{e||(e=requestAnimationFrame(()=>{e=0,j()}))};t.addEventListener("scroll",r,{passive:!0});const n=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>j()),o=()=>{n?.observe(t),Array.from(t.children).forEach(g=>n?.observe(g))};o();const i=typeof MutationObserver>"u"?null:new MutationObserver(()=>{o(),j()});return i?.observe(t,{childList:!0}),()=>{t.removeEventListener("scroll",r),n?.disconnect(),i?.disconnect(),e&&cancelAnimationFrame(e)}},[j,L,ce]),s.useLayoutEffect(()=>{const t=h.current;if(t&&(le(!t.querySelector(ut)),u)){const e=u.split(/\s+/).map(r=>document.getElementById(r)?.textContent?.trim()??"").filter(Boolean).join(" ");oe(e)}},[C,u]);const I=s.useRef(null),N=s.useCallback(()=>{const t=h.current,e=I.current;e&&(I.current=null,window.clearTimeout(e.timer),t?.removeEventListener("scrollend",e.done),t&&(t.style.scrollSnapType="",delete t.dataset.settling))},[]),b=s.useCallback((t,e)=>{const r=h.current;if(!r)return;if(N(),e!=="smooth"||Ne.prefersReducedMotion()||Math.abs(r.scrollLeft-t)<1){r.style.scrollSnapType="none",r.scrollTo({left:t,behavior:"instant"}),r.style.scrollSnapType="";return}r.style.scrollSnapType="none",r.dataset.settling="";const o=r.scrollLeft,i=()=>{Math.abs(r.scrollLeft-o)<.5||N()};I.current={timer:window.setTimeout(N,je(Math.abs(r.scrollLeft-t))),done:i,target:t},r.addEventListener("scrollend",i),r.scrollTo({left:t,behavior:"smooth"})},[N]),z=s.useCallback(t=>{const e=y();if(!e)return;const r=I.current?{...e,scrollLeft:I.current.target}:e;b(v.pageTarget(r,t),"smooth")},[y,b]);s.useImperativeHandle(Zt,()=>({scrollToIndex:(t,e)=>{const r=y();if(!r||!r.items.length)return;const n=Math.min(Math.max(0,Math.round(t)),r.items.length-1);b(v.snapPosition(r,n),e?.behavior??"smooth")},scrollPrev:()=>z(-1),scrollNext:()=>z(1),focus:()=>{const t=h.current;if(!t)return;const e=p?t.querySelector(':scope > [tabindex="0"]'):t.querySelector(ut)??(t.tabIndex===0?t:null);if(!e)return;e.focus({preventScroll:!0});const r=e.closest("[data-carousel-item]"),n=y(),o=r&&r.parentElement===t?W(t,r):-1,i=n&&o>=0?v.revealTarget(n,o,I.current?.target??n.scrollLeft):null;i!==null&&b(i,"smooth")}}),[y,z,b,p]),s.useEffect(()=>N,[N]);const Z=s.useRef(null),tt=s.useRef(null),et=s.useRef(null);s.useLayoutEffect(()=>{const t=et.current;if(!t)return;const e=t==="prev"?Z.current:tt.current,r=t==="prev"?tt.current:Z.current,n=t==="prev"?!f.canScrollPrev:!f.canScrollNext,o=document.activeElement;n&&(o!==e&&o!==document.body&&o!==null||r&&!r.disabled&&r.focus())},[f.canScrollPrev,f.canScrollNext]);const R=s.useRef(null),O=s.useRef(!1),ae=t=>{t.pointerType!=="mouse"||t.button!==0||f.overflowing&&(N(),O.current=!1,R.current={pointerId:t.pointerId,x0:t.clientX,scroll0:t.currentTarget.scrollLeft,dragging:!1})},ue=t=>{const e=R.current;if(!e||t.pointerId!==e.pointerId)return;const r=t.currentTarget,n=t.clientX-e.x0;if(!e.dragging){if(Math.abs(n)<=v.CAROUSEL_DRAG_THRESHOLD)return;e.dragging=!0,r.dataset.dragging="",r.style.scrollSnapType="none",window.getSelection?.()?.removeAllRanges();try{r.setPointerCapture(t.pointerId)}catch{}}r.scrollLeft=e.scroll0-n},rt=t=>{const e=R.current;if(!e||t.pointerId!==e.pointerId||(R.current=null,!e.dragging))return;const r=t.currentTarget;delete r.dataset.dragging,O.current=t.type==="pointerup",window.setTimeout(()=>{O.current=!1},0);const n=y();n?b(v.snapPosition(n,v.nearestIndex(n)),"smooth"):r.style.scrollSnapType=""},de=t=>{!O.current||t.detail===0||(O.current=!1,t.preventDefault(),t.stopPropagation())},fe=t=>{f.overflowing&&t.preventDefault()},ge=t=>{if(p){ve(t);return}const e=t.currentTarget,r=t.target;if(r===e||R.current?.dragging)return;let n=!1;try{n=r.matches(":focus-visible, [data-key-focus]")}catch{n=!1}if(!n)return;const o=r.closest("[data-carousel-item]");if(!o||o.parentElement!==e)return;const i=y();if(!i)return;const g=Array.prototype.indexOf.call(e.children,o);b(v.snapPosition(i,g),"instant")},H=t=>{bt||ee(t),Ut?.(t)},pe=t=>{const e=y();if(!e)return;const r=v.revealTarget(e,t,I.current?.target??e.scrollLeft);r!==null&&b(r,"smooth")},ve=t=>{const e=t.currentTarget,r=t.target;if(r.parentElement!==e)return;const n=W(e,r),o=m[n];if(o===void 0||(B(g=>g.active===o?g:{...g,active:o}),St(!0),R.current?.dragging))return;let i=!1;try{i=r.matches(":focus-visible, [data-key-focus]")}catch{i=!1}i&&pe(n)},me=t=>{const e=t.relatedTarget;e&&t.currentTarget.contains(e)||(St(!1),d!==null&&m.includes(d)&&B(r=>r.active===d?r:{...r,active:d}))},ye=t=>{const e=t.currentTarget,r=t.target.closest("[data-carousel-item]");if(!r||r.parentElement!==e)return;const n=m[W(e,r)];n!==void 0&&(B(o=>o.active===n?o:{...o,active:n}),n===d?mt?.(n):H(n))},D=s.useRef({text:"",at:-1/0}),xe=t=>{const e=t.currentTarget,r=t.target;if(r.parentElement!==e||t.nativeEvent.isComposing||t.ctrlKey||t.metaKey||t.altKey)return;const n=m.length-1,o=W(e,r),i=m[o];if(i===void 0)return;const g=S=>{t.preventDefault();const A=Math.min(n,Math.max(0,S));A!==o&&e.children[A].focus({preventScroll:!0})},Rt=t.timeStamp-D.current.at<at.TYPEAHEAD_RESET_MS&&D.current.text!=="";switch(t.key.length!==1&&(D.current={text:"",at:-1/0}),t.key){case"ArrowRight":return g(o+1);case"ArrowLeft":return g(o-1);case"Home":return g(0);case"End":return g(n);case"PageDown":case"PageUp":{const S=y(),A=S?v.pageStep(S):1;return g(t.key==="PageDown"?o+A:o-A)}case"Enter":t.preventDefault(),i===d?mt?.(i):H(i);return;case" ":if(Rt)break;t.preventDefault(),i!==d&&H(i);return;case"Escape":if(!Wt||d===null)return;t.preventDefault(),t.stopPropagation(),H(null);return}if(t.key.length!==1)return;const Pt=(Rt?D.current.text:"")+t.key.toLowerCase();D.current={text:Pt,at:t.timeStamp},t.preventDefault();const we=S=>Y?Y(m[S],S):e.children[S]?.textContent??"",lt=at.typeaheadIndex(m.length,o,Pt,we);lt>=0&<!==o&&e.children[lt].focus({preventScroll:!0})},V=s.useRef({keys:!1,interactive:!1});s.useEffect(()=>{if(!p||!Re.isDevelopment())return;const t=h.current;!V.current.keys&&w?.some(e=>!e.keyed)&&(V.current.keys=!0,console.warn("Roster Carousel: selection mode needs a key on every child; it's the option's identity.")),!V.current.interactive&&t?.querySelector(`[data-carousel-item] :is(${ut})`)&&(V.current.interactive=!0,console.warn("Roster Carousel: an option holds something that takes focus. In selection mode the keys can't reach it; put it outside the row, or use onActivate."))});const[he,be]=s.useState(0);s.useEffect(()=>{const t=window.setTimeout(()=>be(f.index),150);return()=>window.clearTimeout(t)},[f.index]);const Et=c??se,Tt=t=>Et?`${t} ${Et}`:t,nt=X!=="none"&&f.overflowing!==!1,Se=X==="hover"?Oe:"rst:inline-flex",It=t=>{const e=t==="prev",{className:r,...n}=Bt??{};return a.jsx(Te.Button,{type:"button",variant:"outline",colorScheme:"neutral",size:"icon",...n,"aria-label":e?_t??Tt("Previous"):qt??Tt("Next"),"aria-controls":xt,disabled:e?!f.canScrollPrev:!f.canScrollNext,onClick:()=>z(e?-1:1),ref:e?Z:tt,onFocus:()=>{et.current=t},onBlur:o=>{o.relatedTarget&&(et.current=null)},"data-carousel-arrow":t,className:_.cn("rst:size-11 rst:shrink-0 rst:rounded-full",J==="overlay"&&!$&&"rst:bg-[var(--roster-popover-bg)] rst:elevation-raised rst:hover:bg-[var(--roster-popover-bg)] rst:disabled:invisible",Se,r),children:e?Ft??a.jsx(jt,{direction:"prev"}):$t??a.jsx(jt,{direction:"next"})})},K=nt?It("prev"):null,st=nt?It("next"):null,M=ft&&L?a.jsx("p",{"aria-live":"polite",className:"rst:font-ui rst:m-0 rst:text-sm rst:tabular-nums rst:opacity-75","data-carousel-position":"",children:yt.position(he+1,L)}):null;let ot=null;$?ot=$({prev:K,next:st,position:M}):J==="controls"&&(K||M)&&(ot=a.jsxs("div",{className:_.cn("rst:mb-2 rst:items-center rst:justify-end rst:gap-2",!M&&X==="hover"?De:"rst:flex"),"data-carousel-controls":"",children:[M&&a.jsx("div",{className:"rst:mr-auto",children:M}),K,st]}));const it=zt&&f.overflowing&&(f.canScrollPrev||f.canScrollNext)?`linear-gradient(to right, ${f.canScrollPrev?"transparent, black 48px":"black"}, ${f.canScrollNext?"black calc(100% - 48px), transparent":"black"})`:void 0,U=k===void 0&&P!==void 0,ke={"--rst-cv-gap":dt(q),"--rst-cv-gutter":dt(F),...U?ct.perViewVars(P):{},...it?{maskImage:it,WebkitMaskImage:it}:{}},Nt=s.useMemo(()=>k!==void 0?{width:dt(k)}:U?{width:ct.FLUID_ITEM_WIDTH}:void 0,[k,U]),Ct=_.cn("rst:shrink-0",G==="start"?"rst:snap-start":"rst:snap-center",At&&"rst:snap-always",p&&Me,Gt);return a.jsxs("div",{className:_.cn("rst:min-w-0",Jt),style:Yt,...Qt,"aria-describedby":p?void 0:x,children:[ot,a.jsxs("div",{className:"rst:relative",children:[a.jsx("ul",{ref:h,id:xt,role:p?"listbox":"list","aria-label":c,"aria-labelledby":c?void 0:u,"aria-orientation":p?"horizontal":void 0,"aria-describedby":p?x:void 0,tabIndex:!p&&ie&&f.overflowing?0:void 0,"data-overflowing":f.overflowing?"":void 0,onPointerDown:ae,onPointerMove:ue,onPointerUp:rt,onPointerCancel:rt,onLostPointerCapture:rt,onClickCapture:de,onClick:p?ye:void 0,onKeyDown:p?xe:void 0,onBlur:p?me:void 0,onDragStart:fe,onFocus:ge,style:ke,className:_.cn("rst:m-0 rst:flex rst:list-none rst:overflow-x-auto rst:overflow-y-hidden","rst:gap-(--rst-cv-gap) rst:px-(--rst-cv-gutter) rst:py-1 rst:scroll-px-(--rst-cv-gutter)","rst:overscroll-x-contain",Mt==="mandatory"?"rst:snap-x rst:snap-mandatory":"rst:snap-x rst:snap-proximity",Dt&&"rst:-mx-(--rst-cv-gutter)",!Ht&&"rst:[scrollbar-width:none] rst:[&::-webkit-scrollbar]:hidden",U&&ct.FLUID_CLASSES,"rst:data-overflowing:cursor-grab rst:data-dragging:cursor-grabbing rst:data-dragging:select-none","rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-inset",Xt),children:w?w.map((t,e)=>a.jsx(Ae,{child:t.child,index:e,count:L,selected:t.key===d,active:t.key===T,label:Y?.(t.key,e),style:Nt,className:Ct},t.key)):ht.map((t,e)=>a.jsx("li",{"data-carousel-item":"","aria-label":ft?yt.position(e+1,L):void 0,style:Nt,className:Ct,children:t},s.isValidElement(t)&&t.key!=null?t.key:e))}),!$&&J==="overlay"&&nt&&a.jsxs(a.Fragment,{children:[a.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:start-1 rst:flex rst:items-center",children:a.jsx("div",{className:"rst:pointer-events-auto",children:K})}),a.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:end-1 rst:flex rst:items-center",children:a.jsx("div",{className:"rst:pointer-events-auto",children:st})})]})]})]})});exports.Carousel=Fe;
|
|
@@ -17,6 +17,13 @@ export interface CarouselHandle {
|
|
|
17
17
|
scrollPrev: () => void;
|
|
18
18
|
/** What the next arrow does: on one page. */
|
|
19
19
|
scrollNext: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Moves focus into the row. In selection mode, onto the option a Tab would
|
|
22
|
+
* land on: the selected one, or where the keys last left off. Otherwise onto
|
|
23
|
+
* the first item that can take focus, or the list when it's the tab stop.
|
|
24
|
+
* Scrolls it into view.
|
|
25
|
+
*/
|
|
26
|
+
focus: () => void;
|
|
20
27
|
}
|
|
21
28
|
export interface CarouselArrowsRender {
|
|
22
29
|
/** The previous arrow, or `null` while the row fits and there is nothing to page. */
|
|
@@ -35,7 +42,17 @@ export interface CarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
35
42
|
"aria-label"?: string;
|
|
36
43
|
/** The id of the heading that names the row. */
|
|
37
44
|
"aria-labelledby"?: string;
|
|
38
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* In selection mode, describes the listbox: say how it works, as "Enter
|
|
47
|
+
* selects. Enter again opens it." Otherwise it describes the outer wrapper,
|
|
48
|
+
* as it always has.
|
|
49
|
+
*/
|
|
50
|
+
"aria-describedby"?: string;
|
|
51
|
+
/**
|
|
52
|
+
* One child per item. Each is wrapped in the list item the row snaps to. In
|
|
53
|
+
* selection mode each child is an option, and its React `key` is its
|
|
54
|
+
* identity: give every child one.
|
|
55
|
+
*/
|
|
39
56
|
children: React.ReactNode;
|
|
40
57
|
/**
|
|
41
58
|
* Fixed mode: every item is this wide (a number is px). The row overflows
|
|
@@ -117,14 +134,53 @@ export interface CarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
117
134
|
canScrollPrev: boolean;
|
|
118
135
|
canScrollNext: boolean;
|
|
119
136
|
}) => void;
|
|
137
|
+
/**
|
|
138
|
+
* Selection mode: the row becomes a listbox of options, one of which is
|
|
139
|
+
* selected. One tab stop, the arrow keys, Home, End and the Page keys move
|
|
140
|
+
* between options, type-ahead jumps to one, and Enter or Space selects.
|
|
141
|
+
* Selection doesn't follow focus: arrowing only moves focus. See the README
|
|
142
|
+
* for when to use it. Off by default, and off changes nothing.
|
|
143
|
+
*
|
|
144
|
+
* **Nothing interactive inside an option.** An option is one choice; a link
|
|
145
|
+
* or a button inside it would be a second tab stop the keys can't reach.
|
|
146
|
+
* Put actions outside the row (or use `onActivate`). A development build
|
|
147
|
+
* warns when an option holds something that takes focus.
|
|
148
|
+
*/
|
|
149
|
+
selectable?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Selection mode, controlled: the selected option's `key`, as React stores
|
|
152
|
+
* it (a string), or `null` for none.
|
|
153
|
+
*/
|
|
154
|
+
selectedKey?: string | null;
|
|
155
|
+
/** Selection mode, uncontrolled: the option selected at first. */
|
|
156
|
+
defaultSelectedKey?: string | null;
|
|
157
|
+
/**
|
|
158
|
+
* Selection mode: an option was selected (Enter, Space, a click or a tap on
|
|
159
|
+
* one that isn't), with its key; or the selection was cleared (Escape, with
|
|
160
|
+
* `clearable`), with `null`.
|
|
161
|
+
*/
|
|
162
|
+
onSelectionChange?: (key: string | null) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Selection mode: Enter, or a click or a tap, on the option that's already
|
|
165
|
+
* selected. The second step after choosing, such as opening it.
|
|
166
|
+
*/
|
|
167
|
+
onActivate?: (key: string) => void;
|
|
168
|
+
/** Selection mode: Escape clears the selection. */
|
|
169
|
+
clearable?: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Selection mode: each option's accessible name, which is also the text
|
|
172
|
+
* type-ahead matches. Without it an option is named by its content, and
|
|
173
|
+
* type-ahead reads its text.
|
|
174
|
+
*/
|
|
175
|
+
getOptionLabel?: (key: string, index: number) => string;
|
|
120
176
|
/** Classes for each list item. */
|
|
121
177
|
itemClassName?: string;
|
|
122
178
|
/** Classes for the scrolling list. `className` is the outer wrapper. */
|
|
123
179
|
listClassName?: string;
|
|
124
180
|
}
|
|
125
181
|
/**
|
|
126
|
-
* A sideways row of items: a row of cards, a selection strip, a
|
|
127
|
-
* per view.
|
|
182
|
+
* A sideways row of items: a row of cards, a selection strip (`selectable`), a
|
|
183
|
+
* gallery of one per view.
|
|
128
184
|
*
|
|
129
185
|
* Native first. The row is a real scroll container with CSS scroll snap, so
|
|
130
186
|
* touch, trackpad, Shift with the wheel and focus all scroll it without
|
|
@@ -148,6 +204,12 @@ export interface CarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
148
204
|
*
|
|
149
205
|
* Renders a `ul` named by `aria-label` or `aria-labelledby`, one `li` per
|
|
150
206
|
* child. There is no carousel role and no slide role: nothing rotates.
|
|
207
|
+
*
|
|
208
|
+
* For a strip of choices, one of which is picked, use `selectable`: the row
|
|
209
|
+
* becomes a horizontal listbox with one tab stop, roving focus over its
|
|
210
|
+
* options, and a selection that changes only on Enter, Space or a tap. A row
|
|
211
|
+
* of buttons with `aria-pressed` is as many tab stops as there are buttons,
|
|
212
|
+
* and a screen reader hears a list of unrelated buttons.
|
|
151
213
|
*/
|
|
152
214
|
declare const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<CarouselHandle>>;
|
|
153
215
|
export { Carousel };
|