@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.
Files changed (37) hide show
  1. package/README.md +188 -13
  2. package/dist/components/molecules/Carousel/Carousel.cjs +1 -1
  3. package/dist/components/molecules/Carousel/Carousel.d.ts +65 -3
  4. package/dist/components/molecules/Carousel/Carousel.js +376 -225
  5. package/dist/components/molecules/Carousel/carousel-math.cjs +1 -1
  6. package/dist/components/molecules/Carousel/carousel-math.d.ts +16 -0
  7. package/dist/components/molecules/Carousel/carousel-math.js +63 -44
  8. package/dist/components/molecules/Carousel/carousel-selection.cjs +1 -0
  9. package/dist/components/molecules/Carousel/carousel-selection.d.ts +28 -0
  10. package/dist/components/molecules/Carousel/carousel-selection.js +23 -0
  11. package/dist/components/molecules/LiquidTabs/LiquidCount.cjs +1 -0
  12. package/dist/components/molecules/LiquidTabs/LiquidCount.d.ts +29 -0
  13. package/dist/components/molecules/LiquidTabs/LiquidCount.js +21 -0
  14. package/dist/components/molecules/LiquidTabs/LiquidNav.cjs +1 -1
  15. package/dist/components/molecules/LiquidTabs/LiquidNav.d.ts +12 -1
  16. package/dist/components/molecules/LiquidTabs/LiquidNav.js +50 -44
  17. package/dist/components/molecules/LiquidTabs/LiquidTabPanel.cjs +1 -0
  18. package/dist/components/molecules/LiquidTabs/LiquidTabPanel.d.ts +52 -0
  19. package/dist/components/molecules/LiquidTabs/LiquidTabPanel.js +32 -0
  20. package/dist/components/molecules/LiquidTabs/LiquidTabs.cjs +1 -1
  21. package/dist/components/molecules/LiquidTabs/LiquidTabs.d.ts +20 -1
  22. package/dist/components/molecules/LiquidTabs/LiquidTabs.js +60 -55
  23. package/dist/components/molecules/LiquidTabs/liquid-count.cjs +1 -0
  24. package/dist/components/molecules/LiquidTabs/liquid-count.d.ts +37 -0
  25. package/dist/components/molecules/LiquidTabs/liquid-count.js +9 -0
  26. package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -1
  27. package/dist/components/organisms/ModerationQueue/ModerationQueue.js +214 -193
  28. package/dist/index.d.ts +2 -0
  29. package/dist/internal/dev.cjs +1 -0
  30. package/dist/internal/dev.d.ts +10 -0
  31. package/dist/internal/dev.js +10 -0
  32. package/dist/internal/key-focus.d.ts +27 -2
  33. package/dist/meta.json +3 -3
  34. package/dist/roster.cjs +1 -1
  35. package/dist/roster.css +1 -1
  36. package/dist/roster.js +150 -145
  37. 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 internal tracker marks those elements: keyboard mode starts on a navigation key (Tab, Option+Tab, the
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 badge need no API: the app holds the items, so it's
1364
- `items.length`. `MediaPreview` is the thumbnail to use in an item: when the
1365
- image can't render (an expired link, a format the browser can't draw), it
1366
- falls back to a link to the original in the same footprint.
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 gallery of one per
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 `scrollNext()`;
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. The props type is exported as
1903
- `LiquidNavLinkProps`. The same client-component rule applies.
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. Words that take a number or a name are functions, so a language
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&&lt!==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
- /** One child per item. Each is wrapped in the list item the row snaps to. */
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 gallery of one
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 };