@salt-ds/lab 1.0.0-alpha.65 → 1.0.0-alpha.66

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 (53) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/css/salt-lab.css +111 -16
  3. package/dist-cjs/carousel/Carousel.css.js +1 -1
  4. package/dist-cjs/carousel/Carousel.js +30 -87
  5. package/dist-cjs/carousel/Carousel.js.map +1 -1
  6. package/dist-cjs/carousel/CarouselContext.js +62 -0
  7. package/dist-cjs/carousel/CarouselContext.js.map +1 -0
  8. package/dist-cjs/carousel/CarouselControls.css.js +6 -0
  9. package/dist-cjs/carousel/CarouselControls.css.js.map +1 -0
  10. package/dist-cjs/carousel/CarouselControls.js +122 -0
  11. package/dist-cjs/carousel/CarouselControls.js.map +1 -0
  12. package/dist-cjs/carousel/CarouselReducer.js +77 -0
  13. package/dist-cjs/carousel/CarouselReducer.js.map +1 -0
  14. package/dist-cjs/carousel/CarouselSlide.css.js +6 -0
  15. package/dist-cjs/carousel/CarouselSlide.css.js.map +1 -0
  16. package/dist-cjs/carousel/CarouselSlide.js +95 -45
  17. package/dist-cjs/carousel/CarouselSlide.js.map +1 -1
  18. package/dist-cjs/carousel/CarouselSlider.css.js +6 -0
  19. package/dist-cjs/carousel/CarouselSlider.css.js.map +1 -0
  20. package/dist-cjs/carousel/CarouselSlider.js +93 -0
  21. package/dist-cjs/carousel/CarouselSlider.js.map +1 -0
  22. package/dist-cjs/index.js +6 -2
  23. package/dist-cjs/index.js.map +1 -1
  24. package/dist-es/carousel/Carousel.css.js +1 -1
  25. package/dist-es/carousel/Carousel.js +32 -89
  26. package/dist-es/carousel/Carousel.js.map +1 -1
  27. package/dist-es/carousel/CarouselContext.js +58 -0
  28. package/dist-es/carousel/CarouselContext.js.map +1 -0
  29. package/dist-es/carousel/CarouselControls.css.js +4 -0
  30. package/dist-es/carousel/CarouselControls.css.js.map +1 -0
  31. package/dist-es/carousel/CarouselControls.js +120 -0
  32. package/dist-es/carousel/CarouselControls.js.map +1 -0
  33. package/dist-es/carousel/CarouselReducer.js +75 -0
  34. package/dist-es/carousel/CarouselReducer.js.map +1 -0
  35. package/dist-es/carousel/CarouselSlide.css.js +4 -0
  36. package/dist-es/carousel/CarouselSlide.css.js.map +1 -0
  37. package/dist-es/carousel/CarouselSlide.js +96 -46
  38. package/dist-es/carousel/CarouselSlide.js.map +1 -1
  39. package/dist-es/carousel/CarouselSlider.css.js +4 -0
  40. package/dist-es/carousel/CarouselSlider.css.js.map +1 -0
  41. package/dist-es/carousel/CarouselSlider.js +91 -0
  42. package/dist-es/carousel/CarouselSlider.js.map +1 -0
  43. package/dist-es/index.js +3 -1
  44. package/dist-es/index.js.map +1 -1
  45. package/dist-types/carousel/Carousel.d.ts +10 -23
  46. package/dist-types/carousel/CarouselContext.d.ts +11 -0
  47. package/dist-types/carousel/CarouselControls.d.ts +26 -0
  48. package/dist-types/carousel/CarouselReducer.d.ts +30 -0
  49. package/dist-types/carousel/CarouselSlide.d.ts +31 -8
  50. package/dist-types/carousel/CarouselSlider.d.ts +13 -0
  51. package/dist-types/carousel/index.d.ts +2 -0
  52. package/dist-types/utils/index.d.ts +1 -1
  53. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @salt-ds/lab
2
2
 
3
+ ## 1.0.0-alpha.66
4
+
5
+ ### Minor Changes
6
+
7
+ - 799cf41: Updated `Carousel` component
8
+
9
+ - Renamed `initialIndex` to `defaultActiveSlideIndex`
10
+ - Added controlled `activeSlideIndex`
11
+ - Added `visibleSlides` to control how many slides can be visible at a time.
12
+ - Added `CarouselSlider` and extracted the controls to its own component, `CarouselControls` to improve composition.
13
+ - Added appearance in `CarouselSlide` to allow for border items.
14
+ - Added keyboard navigation.
15
+ - Removed usage of `DeckLayout`.
16
+
17
+ before:
18
+
19
+ ```tsx
20
+ <Carousel>
21
+ {items.map((item, index) => (
22
+ <CarouselSlide
23
+ key={index}
24
+ ButtonBar={<Button variant="cta">Learn more</Button>}
25
+ description="Lorem ipsum"
26
+ title="Carousel slide title"
27
+ />
28
+ ))}
29
+ </Carousel>
30
+ ```
31
+
32
+ after:
33
+
34
+ ```tsx
35
+ <Carousel>
36
+ <CarouselControls />
37
+ <CarouselSlider>
38
+ {items.map((slide) => (
39
+ <CarouselSlide
40
+ key={slide.title}
41
+ header={<H3>{slide.title}</H3>}
42
+ actions={<Link href="#">{slide.link}</Link>}
43
+ >
44
+ <Text>{slide.content}</Text>
45
+ </CarouselSlide>
46
+ ))}
47
+ </CarouselSlider>
48
+ </Carousel>
49
+ ```
50
+
51
+ ### Patch Changes
52
+
53
+ - Updated dependencies [851b2eb]
54
+ - @salt-ds/core@1.44.1
55
+
3
56
  ## 1.0.0-alpha.65
4
57
 
5
58
  ### Minor Changes
package/css/salt-lab.css CHANGED
@@ -252,27 +252,122 @@
252
252
  }
253
253
 
254
254
  /* src/carousel/Carousel.css */
255
- .saltGridLayout.saltCarousel {
256
- grid-template-columns: min-content auto min-content;
257
- grid-template-areas: "prev-button slider next-button" "dots dots dots";
255
+ .saltCarousel {
256
+ display: grid;
257
+ grid-row-gap: var(--salt-spacing-100);
258
+ grid-template-areas: "controls" "slider";
259
+ grid-template-rows: auto 1fr;
258
260
  }
259
- .saltCarousel.saltCarousel-compact {
260
- grid-template-areas: "slider slider slider" "prev-button dots next-button";
261
+ .saltCarousel.saltCarousel-bottom {
262
+ grid-template-areas: "slider" "controls";
263
+ grid-template-rows: 1fr auto;
261
264
  }
262
- .saltCarousel-prev-button {
263
- grid-area: prev-button;
264
- height: 100%;
265
+
266
+ /* src/carousel/CarouselControls.css */
267
+ .saltCarouselControls {
268
+ display: flex;
269
+ gap: var(--salt-spacing-100);
270
+ align-items: center;
271
+ grid-area: controls;
265
272
  }
266
- .saltCarousel-next-button {
267
- grid-area: next-button;
268
- height: 100%;
273
+ .saltCarouselControls-container {
274
+ display: flex;
275
+ flex-flow: wrap;
276
+ align-items: center;
277
+ gap: var(--salt-spacing-100);
278
+ justify-content: space-between;
279
+ }
280
+ .saltCarouselControls-container:last-child {
281
+ margin-left: auto;
282
+ }
283
+ .saltCarouselControls-container h2 {
284
+ margin: 0;
285
+ }
286
+ .saltCarouselControls-sr-only {
287
+ display: none;
269
288
  }
270
- .saltCarousel-slider {
289
+ .saltCarouselControls:focus-within .saltCarouselControls-sr-only {
290
+ display: block;
291
+ position: fixed;
292
+ top: 0;
293
+ left: 0;
294
+ transform: translate(-100%, -100%);
295
+ }
296
+
297
+ /* src/carousel/CarouselSlide.css */
298
+ .saltCarouselSlide {
299
+ scroll-snap-align: start;
300
+ display: flex;
301
+ flex-direction: column;
302
+ flex: 0 0 auto;
303
+ box-sizing: border-box;
304
+ width: var(--carousel-slide-width, 100%);
305
+ gap: var(--salt-spacing-200);
306
+ }
307
+ .saltCarouselSlide:focus-visible {
308
+ outline: var(--salt-focused-outline);
309
+ outline-offset: calc(0px - var(--salt-focused-outlineWidth));
310
+ }
311
+ .saltCarouselSlide-actions {
312
+ display: none;
313
+ }
314
+ .saltCarouselSlide-sr-only {
315
+ position: fixed;
316
+ top: 0;
317
+ left: 0;
318
+ transform: translate(-100%, -100%);
319
+ }
320
+ .saltCarouselSlide-actions.saltCarouselSlide-visible {
321
+ display: flex;
322
+ }
323
+ .saltCarouselSlide-container {
324
+ display: flex;
325
+ flex-direction: column;
326
+ box-sizing: border-box;
327
+ gap: var(--salt-spacing-200);
328
+ padding-left: var(--salt-spacing-25);
329
+ padding-bottom: var(--salt-spacing-25);
330
+ flex-grow: 1;
331
+ }
332
+ .saltCarouselSlide-content {
333
+ display: flex;
334
+ flex-direction: column;
335
+ flex-grow: 1;
336
+ gap: var(--salt-spacing-100);
337
+ }
338
+ .saltCarouselSlide-card {
339
+ padding: var(--salt-spacing-200);
340
+ padding-top: 0;
341
+ }
342
+ .saltCarouselSlide-content h2,
343
+ .saltCarouselSlide-content h3 {
344
+ margin: 0;
345
+ }
346
+ .saltCarouselSlide-bordered {
347
+ border: var(--salt-size-border) var(--salt-container-borderStyle) var(--salt-container-primary-borderColor);
348
+ border-radius: var(--salt-palette-corner, 0);
349
+ overflow: hidden;
350
+ }
351
+
352
+ /* src/carousel/CarouselSlider.css */
353
+ .saltCarouselSlider {
354
+ display: flex;
355
+ scroll-snap-type: x mandatory;
356
+ overflow-x: auto;
357
+ scroll-behavior: smooth;
358
+ width: 100%;
359
+ gap: var(--salt-spacing-100);
271
360
  grid-area: slider;
272
361
  }
273
- .saltCarousel-dots {
274
- grid-area: dots;
275
- justify-self: center;
362
+ @supports (scrollbar-width: none) {
363
+ .saltCarouselSlider {
364
+ scrollbar-width: none;
365
+ }
366
+ }
367
+ @supports not (scrollbar-width: none) {
368
+ .saltCarouselSlider::-webkit-scrollbar {
369
+ display: none;
370
+ }
276
371
  }
277
372
 
278
373
  /* src/cascading-menu/CascadingMenuItem.css */
@@ -4597,4 +4692,4 @@
4597
4692
  margin: calc(var(--salt-size-unit) / 2) 0;
4598
4693
  }
4599
4694
 
4600
- /* src/5896d85c-5f80-4171-bcd0-176a25d4d17f.css */
4695
+ /* src/89b851ef-eba4-4203-af10-5eb7e5bfc25b.css */
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var css_248z = ".saltGridLayout.saltCarousel {\n grid-template-columns: min-content auto min-content;\n grid-template-areas: \"prev-button slider next-button\" \"dots dots dots\";\n}\n\n.saltCarousel.saltCarousel-compact {\n grid-template-areas: \"slider slider slider\" \"prev-button dots next-button\";\n}\n\n.saltCarousel-prev-button {\n grid-area: prev-button;\n height: 100%;\n}\n\n.saltCarousel-next-button {\n grid-area: next-button;\n height: 100%;\n}\n\n.saltCarousel-slider {\n grid-area: slider;\n}\n\n.saltCarousel-dots {\n grid-area: dots;\n justify-self: center;\n}\n";
3
+ var css_248z = ".saltCarousel {\n display: grid;\n grid-row-gap: var(--salt-spacing-100);\n grid-template-areas: \"controls\" \"slider\";\n grid-template-rows: auto 1fr;\n}\n\n.saltCarousel.saltCarousel-bottom {\n grid-template-areas: \"slider\" \"controls\";\n grid-template-rows: 1fr auto;\n}\n";
4
4
 
5
5
  module.exports = css_248z;
6
6
  //# sourceMappingURL=Carousel.css.js.map
@@ -1,24 +1,22 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var react = require('react');
5
4
  var core = require('@salt-ds/core');
6
5
  var styles = require('@salt-ds/styles');
7
6
  var window = require('@salt-ds/window');
8
7
  var clsx = require('clsx');
9
- var DeckLayout = require('../deck-layout/DeckLayout.js');
10
- var useSlideSelection = require('../utils/useSlideSelection.js');
8
+ var react = require('react');
11
9
  var Carousel$1 = require('./Carousel.css.js');
10
+ var CarouselContext = require('./CarouselContext.js');
12
11
 
13
12
  const withBaseName = core.makePrefixer("saltCarousel");
14
13
  const Carousel = react.forwardRef(
15
14
  function Carousel2({
16
- initialIndex,
17
- animation = "slide",
18
- carouselDescription,
15
+ defaultActiveSlideIndex = 0,
16
+ activeSlideIndex,
17
+ visibleSlides: visibleSlidesProp = 1,
19
18
  children,
20
- className,
21
- compact,
19
+ controlsPlacement = "top",
22
20
  id: idProp,
23
21
  ...rest
24
22
  }, ref) {
@@ -28,88 +26,33 @@ const Carousel = react.forwardRef(
28
26
  css: Carousel$1,
29
27
  window: targetWindow
30
28
  });
31
- const { NextIcon, PreviousIcon } = core.useIcon();
29
+ const { matchedBreakpoints } = core.useBreakpoint();
30
+ const visibleSlides = core.resolveResponsiveValue(
31
+ visibleSlidesProp,
32
+ matchedBreakpoints
33
+ );
32
34
  const id = core.useId(idProp);
33
- const slidesCount = react.Children.count(children);
34
- const [_, selectedSlide, handleSlideSelection] = useSlideSelection.useSlideSelection(initialIndex);
35
- const moveSlide = (direction) => {
36
- const moveLeft = selectedSlide === 0 ? slidesCount - 1 : selectedSlide - 1;
37
- const moveRight = selectedSlide === slidesCount - 1 ? 0 : selectedSlide + 1;
38
- const newSelection = direction === "left" ? moveLeft : moveRight;
39
- const newTransition = direction === "left" ? "decrease" : "increase";
40
- handleSlideSelection(newSelection, newTransition);
41
- };
42
- const handleRadioChange = ({
43
- target: { value }
44
- }) => {
45
- handleSlideSelection(Number(value));
46
- };
47
- react.useEffect(() => {
48
- if (process.env.NODE_ENV !== "production") {
49
- if (slidesCount < 1) {
50
- console.warn(
51
- "Carousel component requires more than one children to render. At least two elements should be provided."
52
- );
53
- }
54
- }
55
- }, [slidesCount]);
56
- return /* @__PURE__ */ jsxRuntime.jsxs(
57
- core.GridLayout,
35
+ return /* @__PURE__ */ jsxRuntime.jsx(
36
+ CarouselContext.CarouselProvider,
58
37
  {
59
- "aria-label": carouselDescription,
60
- "aria-roledescription": "carousel",
38
+ defaultActiveSlideIndex,
39
+ activeSlideIndex,
40
+ visibleSlides,
61
41
  id,
62
- role: "region",
63
- ref,
64
- gap: 0,
65
- columns: 3,
66
- className: clsx.clsx(
67
- withBaseName(),
68
- compact && withBaseName("compact"),
69
- className
70
- ),
71
- ...rest,
72
- children: [
73
- /* @__PURE__ */ jsxRuntime.jsx(
74
- core.Button,
75
- {
76
- variant: "secondary",
77
- className: withBaseName("prev-button"),
78
- onClick: () => moveSlide("left"),
79
- children: /* @__PURE__ */ jsxRuntime.jsx(PreviousIcon, { size: 2 })
80
- }
81
- ),
82
- /* @__PURE__ */ jsxRuntime.jsx(
83
- DeckLayout.DeckLayout,
84
- {
85
- activeIndex: selectedSlide,
86
- animation,
87
- className: withBaseName("slider"),
88
- children
89
- }
90
- ),
91
- /* @__PURE__ */ jsxRuntime.jsx(
92
- core.Button,
93
- {
94
- variant: "secondary",
95
- className: withBaseName("next-button"),
96
- onClick: () => moveSlide("right"),
97
- children: /* @__PURE__ */ jsxRuntime.jsx(NextIcon, { size: 2 })
98
- }
99
- ),
100
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: withBaseName("dots"), children: /* @__PURE__ */ jsxRuntime.jsx(
101
- core.RadioButtonGroup,
102
- {
103
- "aria-label": "Carousel buttons",
104
- onChange: handleRadioChange,
105
- value: `${selectedSlide}`,
106
- direction: "horizontal",
107
- children: Array.from({ length: slidesCount }, (_2, index) => ({
108
- value: `${index}`
109
- })).map((radio) => /* @__PURE__ */ react.createElement(core.RadioButton, { ...radio, key: radio.value }))
110
- }
111
- ) })
112
- ]
42
+ children: /* @__PURE__ */ jsxRuntime.jsx(
43
+ "section",
44
+ {
45
+ role: "region",
46
+ className: clsx.clsx(withBaseName(), {
47
+ [withBaseName(controlsPlacement)]: controlsPlacement === "bottom"
48
+ }),
49
+ "aria-roledescription": "carousel",
50
+ id,
51
+ ref,
52
+ ...rest,
53
+ children
54
+ }
55
+ )
113
56
  }
114
57
  );
115
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Carousel.js","sources":["../src/carousel/Carousel.tsx"],"sourcesContent":["import {\n Button,\n GridLayout,\n RadioButton,\n RadioButtonGroup,\n makePrefixer,\n useIcon,\n useId,\n} from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n type ChangeEventHandler,\n Children,\n type HTMLAttributes,\n type ReactElement,\n forwardRef,\n useEffect,\n} from \"react\";\nimport { DeckLayout } from \"../deck-layout\";\nimport { useSlideSelection } from \"../utils\";\nimport type { CarouselSlideProps } from \"./CarouselSlide\";\n\nimport carouselCss from \"./Carousel.css\";\n\nconst withBaseName = makePrefixer(\"saltCarousel\");\n\nexport interface CarouselProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * The initial Index enables you to select the active slide in the carousel.\n * Optional, default 0.\n **/\n initialIndex?: number;\n /**\n * The animation when the slides are shown.\n * Optional. Defaults to `slide`\n **/\n animation?: \"slide\" | \"fade\";\n /**\n * If this props is passed it will set the aria-label with value to the carousel container.\n * Optional. Defaults to undefined\n */\n carouselDescription?: string;\n /**\n * Collection of slides to render\n * Component must implement CarouselSlideProps. Mandatory.\n */\n children: Array<ReactElement<CarouselSlideProps>>;\n /**\n * This prop will enable compact / reduced width mode.\n * The navigation buttons would be part of indicators\n * Optional. Defaults to false\n **/\n compact?: boolean;\n /**\n * It sets the id for the Carousel Container.\n * String. Optional\n */\n id?: string;\n}\n\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(\n function Carousel(\n {\n initialIndex,\n animation = \"slide\",\n carouselDescription,\n children,\n className,\n compact,\n id: idProp,\n ...rest\n },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel\",\n css: carouselCss,\n window: targetWindow,\n });\n const { NextIcon, PreviousIcon } = useIcon();\n const id = useId(idProp);\n const slidesCount = Children.count(children);\n\n const [_, selectedSlide, handleSlideSelection] =\n useSlideSelection(initialIndex);\n\n const moveSlide = (direction: \"left\" | \"right\") => {\n const moveLeft =\n selectedSlide === 0 ? slidesCount - 1 : selectedSlide - 1;\n const moveRight =\n selectedSlide === slidesCount - 1 ? 0 : selectedSlide + 1;\n const newSelection = direction === \"left\" ? moveLeft : moveRight;\n const newTransition = direction === \"left\" ? \"decrease\" : \"increase\";\n handleSlideSelection(newSelection, newTransition);\n };\n\n const handleRadioChange: ChangeEventHandler<HTMLInputElement> = ({\n target: { value },\n }) => {\n handleSlideSelection(Number(value));\n };\n\n useEffect(() => {\n if (process.env.NODE_ENV !== \"production\") {\n if (slidesCount < 1) {\n console.warn(\n \"Carousel component requires more than one children to render. At least two elements should be provided.\",\n );\n }\n }\n }, [slidesCount]);\n\n return (\n <GridLayout\n aria-label={carouselDescription}\n aria-roledescription=\"carousel\"\n id={id}\n role=\"region\"\n ref={ref}\n gap={0}\n columns={3}\n className={clsx(\n withBaseName(),\n compact && withBaseName(\"compact\"),\n className,\n )}\n {...rest}\n >\n <Button\n variant=\"secondary\"\n className={withBaseName(\"prev-button\")}\n onClick={() => moveSlide(\"left\")}\n >\n <PreviousIcon size={2} />\n </Button>\n <DeckLayout\n activeIndex={selectedSlide}\n animation={animation}\n className={withBaseName(\"slider\")}\n >\n {children}\n </DeckLayout>\n <Button\n variant=\"secondary\"\n className={withBaseName(\"next-button\")}\n onClick={() => moveSlide(\"right\")}\n >\n <NextIcon size={2} />\n </Button>\n <div className={withBaseName(\"dots\")}>\n <RadioButtonGroup\n aria-label=\"Carousel buttons\"\n onChange={handleRadioChange}\n value={`${selectedSlide}`}\n direction={\"horizontal\"}\n >\n {Array.from({ length: slidesCount }, (_, index) => ({\n value: `${index}`,\n })).map((radio) => (\n <RadioButton {...radio} key={radio.value} />\n ))}\n </RadioButtonGroup>\n </div>\n </GridLayout>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","Carousel","useWindow","useComponentCssInjection","carouselCss","useIcon","useId","Children","useSlideSelection","useEffect","jsxs","GridLayout","clsx","jsx","Button","DeckLayout","RadioButtonGroup","_","createElement","RadioButton"],"mappings":";;;;;;;;;;;;AA0BA,MAAM,YAAA,GAAeA,kBAAa,cAAc,CAAA;AAoCzC,MAAM,QAAW,GAAAC,gBAAA;AAAA,EACtB,SAASC,SACP,CAAA;AAAA,IACE,YAAA;AAAA,IACA,SAAY,GAAA,OAAA;AAAA,IACZ,mBAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,EAAI,EAAA,MAAA;AAAA,IACJ,GAAG;AAAA,KAEL,GACA,EAAA;AACA,IAAA,MAAM,eAAeC,gBAAU,EAAA;AAC/B,IAAyBC,+BAAA,CAAA;AAAA,MACvB,MAAQ,EAAA,eAAA;AAAA,MACR,GAAK,EAAAC,UAAA;AAAA,MACL,MAAQ,EAAA;AAAA,KACT,CAAA;AACD,IAAA,MAAM,EAAE,QAAA,EAAU,YAAa,EAAA,GAAIC,YAAQ,EAAA;AAC3C,IAAM,MAAA,EAAA,GAAKC,WAAM,MAAM,CAAA;AACvB,IAAM,MAAA,WAAA,GAAcC,cAAS,CAAA,KAAA,CAAM,QAAQ,CAAA;AAE3C,IAAA,MAAM,CAAC,CAAG,EAAA,aAAA,EAAe,oBAAoB,CAAA,GAC3CC,oCAAkB,YAAY,CAAA;AAEhC,IAAM,MAAA,SAAA,GAAY,CAAC,SAAgC,KAAA;AACjD,MAAA,MAAM,QACJ,GAAA,aAAA,KAAkB,CAAI,GAAA,WAAA,GAAc,IAAI,aAAgB,GAAA,CAAA;AAC1D,MAAA,MAAM,SACJ,GAAA,aAAA,KAAkB,WAAc,GAAA,CAAA,GAAI,IAAI,aAAgB,GAAA,CAAA;AAC1D,MAAM,MAAA,YAAA,GAAe,SAAc,KAAA,MAAA,GAAS,QAAW,GAAA,SAAA;AACvD,MAAM,MAAA,aAAA,GAAgB,SAAc,KAAA,MAAA,GAAS,UAAa,GAAA,UAAA;AAC1D,MAAA,oBAAA,CAAqB,cAAc,aAAa,CAAA;AAAA,KAClD;AAEA,IAAA,MAAM,oBAA0D,CAAC;AAAA,MAC/D,MAAA,EAAQ,EAAE,KAAM;AAAA,KACZ,KAAA;AACJ,MAAqB,oBAAA,CAAA,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,KACpC;AAEA,IAAAC,eAAA,CAAU,MAAM;AACd,MAAI,IAAA,OAAA,CAAQ,GAAI,CAAA,QAAA,KAAa,YAAc,EAAA;AACzC,QAAA,IAAI,cAAc,CAAG,EAAA;AACnB,UAAQ,OAAA,CAAA,IAAA;AAAA,YACN;AAAA,WACF;AAAA;AACF;AACF,KACF,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,IACE,uBAAAC,eAAA;AAAA,MAACC,eAAA;AAAA,MAAA;AAAA,QACC,YAAY,EAAA,mBAAA;AAAA,QACZ,sBAAqB,EAAA,UAAA;AAAA,QACrB,EAAA;AAAA,QACA,IAAK,EAAA,QAAA;AAAA,QACL,GAAA;AAAA,QACA,GAAK,EAAA,CAAA;AAAA,QACL,OAAS,EAAA,CAAA;AAAA,QACT,SAAW,EAAAC,SAAA;AAAA,UACT,YAAa,EAAA;AAAA,UACb,OAAA,IAAW,aAAa,SAAS,CAAA;AAAA,UACjC;AAAA,SACF;AAAA,QACC,GAAG,IAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAAC,cAAA;AAAA,YAACC,WAAA;AAAA,YAAA;AAAA,cACC,OAAQ,EAAA,WAAA;AAAA,cACR,SAAA,EAAW,aAAa,aAAa,CAAA;AAAA,cACrC,OAAA,EAAS,MAAM,SAAA,CAAU,MAAM,CAAA;AAAA,cAE/B,QAAA,kBAAAD,cAAA,CAAC,YAAa,EAAA,EAAA,IAAA,EAAM,CAAG,EAAA;AAAA;AAAA,WACzB;AAAA,0BACAA,cAAA;AAAA,YAACE,qBAAA;AAAA,YAAA;AAAA,cACC,WAAa,EAAA,aAAA;AAAA,cACb,SAAA;AAAA,cACA,SAAA,EAAW,aAAa,QAAQ,CAAA;AAAA,cAE/B;AAAA;AAAA,WACH;AAAA,0BACAF,cAAA;AAAA,YAACC,WAAA;AAAA,YAAA;AAAA,cACC,OAAQ,EAAA,WAAA;AAAA,cACR,SAAA,EAAW,aAAa,aAAa,CAAA;AAAA,cACrC,OAAA,EAAS,MAAM,SAAA,CAAU,OAAO,CAAA;AAAA,cAEhC,QAAA,kBAAAD,cAAA,CAAC,QAAS,EAAA,EAAA,IAAA,EAAM,CAAG,EAAA;AAAA;AAAA,WACrB;AAAA,0BACCA,cAAA,CAAA,KAAA,EAAA,EAAI,SAAW,EAAA,YAAA,CAAa,MAAM,CACjC,EAAA,QAAA,kBAAAA,cAAA;AAAA,YAACG,qBAAA;AAAA,YAAA;AAAA,cACC,YAAW,EAAA,kBAAA;AAAA,cACX,QAAU,EAAA,iBAAA;AAAA,cACV,KAAA,EAAO,GAAG,aAAa,CAAA,CAAA;AAAA,cACvB,SAAW,EAAA,YAAA;AAAA,cAEV,QAAA,EAAA,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,aAAe,EAAA,CAACC,IAAG,KAAW,MAAA;AAAA,gBAClD,KAAA,EAAO,GAAG,KAAK,CAAA;AAAA,eACf,CAAA,CAAA,CAAE,GAAI,CAAA,CAAC,KACP,qBAAAC,mBAAA,CAACC,gBAAa,EAAA,EAAA,GAAG,KAAO,EAAA,GAAA,EAAK,KAAM,CAAA,KAAA,EAAO,CAC3C;AAAA;AAAA,WAEL,EAAA;AAAA;AAAA;AAAA,KACF;AAAA;AAGN;;;;"}
1
+ {"version":3,"file":"Carousel.js","sources":["../src/carousel/Carousel.tsx"],"sourcesContent":["import {\n type ResponsiveProp,\n makePrefixer,\n resolveResponsiveValue,\n useBreakpoint,\n useId,\n} from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport { type HTMLAttributes, forwardRef } from \"react\";\nimport carouselCss from \"./Carousel.css\";\nimport { CarouselProvider } from \"./CarouselContext\";\n\nconst withBaseName = makePrefixer(\"saltCarousel\");\n\nexport interface CarouselProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * The initial Index enables you to select the active slide in the carousel.\n * Optional, default 0.\n **/\n defaultActiveSlideIndex?: number;\n /**\n * Controlled index of active slide in the carousel.\n **/\n activeSlideIndex?: number;\n /**\n * Set the placement of the CarouselControls relative to the CarouselSlider element. Defaults to `top`.\n */\n controlsPlacement?: \"top\" | \"bottom\";\n /**\n * Number of slides visible at a time.\n * Optional, default 1.\n **/\n visibleSlides?: ResponsiveProp<number>;\n}\n\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(\n function Carousel(\n {\n defaultActiveSlideIndex = 0,\n activeSlideIndex,\n visibleSlides: visibleSlidesProp = 1,\n children,\n controlsPlacement = \"top\",\n id: idProp,\n ...rest\n },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel\",\n css: carouselCss,\n window: targetWindow,\n });\n const { matchedBreakpoints } = useBreakpoint();\n\n const visibleSlides = resolveResponsiveValue(\n visibleSlidesProp,\n matchedBreakpoints,\n );\n const id = useId(idProp);\n return (\n <CarouselProvider\n defaultActiveSlideIndex={defaultActiveSlideIndex}\n activeSlideIndex={activeSlideIndex}\n visibleSlides={visibleSlides}\n id={id}\n >\n <section\n role=\"region\"\n className={clsx(withBaseName(), {\n [withBaseName(controlsPlacement)]: controlsPlacement === \"bottom\",\n })}\n aria-roledescription=\"carousel\"\n id={id}\n ref={ref}\n {...rest}\n >\n {children}\n </section>\n </CarouselProvider>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","Carousel","useWindow","useComponentCssInjection","carouselCss","useBreakpoint","resolveResponsiveValue","useId","jsx","CarouselProvider","clsx"],"mappings":";;;;;;;;;;;AAcA,MAAM,YAAA,GAAeA,kBAAa,cAAc,CAAA;AAuBzC,MAAM,QAAW,GAAAC,gBAAA;AAAA,EACtB,SAASC,SACP,CAAA;AAAA,IACE,uBAA0B,GAAA,CAAA;AAAA,IAC1B,gBAAA;AAAA,IACA,eAAe,iBAAoB,GAAA,CAAA;AAAA,IACnC,QAAA;AAAA,IACA,iBAAoB,GAAA,KAAA;AAAA,IACpB,EAAI,EAAA,MAAA;AAAA,IACJ,GAAG;AAAA,KAEL,GACA,EAAA;AACA,IAAA,MAAM,eAAeC,gBAAU,EAAA;AAC/B,IAAyBC,+BAAA,CAAA;AAAA,MACvB,MAAQ,EAAA,eAAA;AAAA,MACR,GAAK,EAAAC,UAAA;AAAA,MACL,MAAQ,EAAA;AAAA,KACT,CAAA;AACD,IAAM,MAAA,EAAE,kBAAmB,EAAA,GAAIC,kBAAc,EAAA;AAE7C,IAAA,MAAM,aAAgB,GAAAC,2BAAA;AAAA,MACpB,iBAAA;AAAA,MACA;AAAA,KACF;AACA,IAAM,MAAA,EAAA,GAAKC,WAAM,MAAM,CAAA;AACvB,IACE,uBAAAC,cAAA;AAAA,MAACC,gCAAA;AAAA,MAAA;AAAA,QACC,uBAAA;AAAA,QACA,gBAAA;AAAA,QACA,aAAA;AAAA,QACA,EAAA;AAAA,QAEA,QAAA,kBAAAD,cAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,IAAK,EAAA,QAAA;AAAA,YACL,SAAA,EAAWE,SAAK,CAAA,YAAA,EAAgB,EAAA;AAAA,cAC9B,CAAC,YAAA,CAAa,iBAAiB,CAAC,GAAG,iBAAsB,KAAA;AAAA,aAC1D,CAAA;AAAA,YACD,sBAAqB,EAAA,UAAA;AAAA,YACrB,EAAA;AAAA,YACA,GAAA;AAAA,YACC,GAAG,IAAA;AAAA,YAEH;AAAA;AAAA;AACH;AAAA,KACF;AAAA;AAGN;;;;"}
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var core = require('@salt-ds/core');
5
+ var react = require('react');
6
+ var CarouselReducer = require('./CarouselReducer.js');
7
+
8
+ const CarouselStateContext = core.createContext(
9
+ "CarouselStateContext",
10
+ {
11
+ slides: /* @__PURE__ */ new Map(),
12
+ activeSlideIndex: 0,
13
+ visibleSlides: 1,
14
+ focusedSlideIndex: 0,
15
+ carouselId: void 0
16
+ }
17
+ );
18
+ const CarouselDispatchContext = core.createContext(
19
+ "CarouselDispatchContext",
20
+ () => {
21
+ return;
22
+ }
23
+ );
24
+ function CarouselProvider({
25
+ children,
26
+ activeSlideIndex: activeSlideIndexProp,
27
+ defaultActiveSlideIndex = 0,
28
+ visibleSlides = 1,
29
+ id
30
+ }) {
31
+ const [activeSlideIndex, setActiveSlideIndex] = core.useControlled({
32
+ controlled: activeSlideIndexProp,
33
+ default: defaultActiveSlideIndex,
34
+ name: "Carousel",
35
+ state: "activeSlideIndex"
36
+ });
37
+ const [state, dispatch] = react.useReducer(CarouselReducer.carouselReducer, {
38
+ slides: /* @__PURE__ */ new Map(),
39
+ focusedSlideIndex: activeSlideIndex,
40
+ activeSlideIndex,
41
+ visibleSlides,
42
+ carouselId: id
43
+ });
44
+ react.useEffect(() => {
45
+ dispatch({
46
+ type: "updateSlideCount",
47
+ payload: visibleSlides
48
+ });
49
+ }, [visibleSlides]);
50
+ react.useEffect(() => {
51
+ dispatch({
52
+ type: "moveToIndex",
53
+ payload: activeSlideIndex
54
+ });
55
+ }, [activeSlideIndex]);
56
+ return /* @__PURE__ */ jsxRuntime.jsx(CarouselStateContext.Provider, { value: state, children: /* @__PURE__ */ jsxRuntime.jsx(CarouselDispatchContext.Provider, { value: dispatch, children }) });
57
+ }
58
+
59
+ exports.CarouselDispatchContext = CarouselDispatchContext;
60
+ exports.CarouselProvider = CarouselProvider;
61
+ exports.CarouselStateContext = CarouselStateContext;
62
+ //# sourceMappingURL=CarouselContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CarouselContext.js","sources":["../src/carousel/CarouselContext.tsx"],"sourcesContent":["import { createContext, useControlled } from \"@salt-ds/core\";\nimport { type ReactNode, useEffect, useReducer } from \"react\";\nimport {\n type CarouselReducerDispatch,\n type CarouselReducerState,\n carouselReducer,\n} from \"./CarouselReducer\";\n\nexport const CarouselStateContext = createContext<CarouselReducerState>(\n \"CarouselStateContext\",\n {\n slides: new Map(),\n activeSlideIndex: 0,\n visibleSlides: 1,\n focusedSlideIndex: 0,\n carouselId: undefined,\n },\n);\nexport const CarouselDispatchContext = createContext<CarouselReducerDispatch>(\n \"CarouselDispatchContext\",\n () => {\n return;\n },\n);\n\nexport function CarouselProvider({\n children,\n activeSlideIndex: activeSlideIndexProp,\n defaultActiveSlideIndex = 0,\n visibleSlides = 1,\n id,\n}: {\n children: ReactNode;\n activeSlideIndex?: number;\n defaultActiveSlideIndex?: number;\n visibleSlides?: number;\n id?: string;\n}) {\n const [activeSlideIndex, setActiveSlideIndex] = useControlled({\n controlled: activeSlideIndexProp,\n default: defaultActiveSlideIndex,\n name: \"Carousel\",\n state: \"activeSlideIndex\",\n });\n const [state, dispatch] = useReducer(carouselReducer, {\n slides: new Map(),\n focusedSlideIndex: activeSlideIndex,\n activeSlideIndex,\n visibleSlides,\n carouselId: id,\n });\n\n useEffect(() => {\n dispatch({\n type: \"updateSlideCount\",\n payload: visibleSlides,\n });\n }, [visibleSlides]);\n\n useEffect(() => {\n dispatch({\n type: \"moveToIndex\",\n payload: activeSlideIndex,\n });\n }, [activeSlideIndex]);\n\n return (\n <CarouselStateContext.Provider value={state}>\n <CarouselDispatchContext.Provider value={dispatch}>\n {children}\n </CarouselDispatchContext.Provider>\n </CarouselStateContext.Provider>\n );\n}\n"],"names":["createContext","useControlled","useReducer","carouselReducer","useEffect","jsx"],"mappings":";;;;;;;AAQO,MAAM,oBAAuB,GAAAA,kBAAA;AAAA,EAClC,sBAAA;AAAA,EACA;AAAA,IACE,MAAA,sBAAY,GAAI,EAAA;AAAA,IAChB,gBAAkB,EAAA,CAAA;AAAA,IAClB,aAAe,EAAA,CAAA;AAAA,IACf,iBAAmB,EAAA,CAAA;AAAA,IACnB,UAAY,EAAA,KAAA;AAAA;AAEhB;AACO,MAAM,uBAA0B,GAAAA,kBAAA;AAAA,EACrC,yBAAA;AAAA,EACA,MAAM;AACJ,IAAA;AAAA;AAEJ;AAEO,SAAS,gBAAiB,CAAA;AAAA,EAC/B,QAAA;AAAA,EACA,gBAAkB,EAAA,oBAAA;AAAA,EAClB,uBAA0B,GAAA,CAAA;AAAA,EAC1B,aAAgB,GAAA,CAAA;AAAA,EAChB;AACF,CAMG,EAAA;AACD,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAIC,kBAAc,CAAA;AAAA,IAC5D,UAAY,EAAA,oBAAA;AAAA,IACZ,OAAS,EAAA,uBAAA;AAAA,IACT,IAAM,EAAA,UAAA;AAAA,IACN,KAAO,EAAA;AAAA,GACR,CAAA;AACD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,iBAAWC,+BAAiB,EAAA;AAAA,IACpD,MAAA,sBAAY,GAAI,EAAA;AAAA,IAChB,iBAAmB,EAAA,gBAAA;AAAA,IACnB,gBAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAY,EAAA;AAAA,GACb,CAAA;AAED,EAAAC,eAAA,CAAU,MAAM;AACd,IAAS,QAAA,CAAA;AAAA,MACP,IAAM,EAAA,kBAAA;AAAA,MACN,OAAS,EAAA;AAAA,KACV,CAAA;AAAA,GACH,EAAG,CAAC,aAAa,CAAC,CAAA;AAElB,EAAAA,eAAA,CAAU,MAAM;AACd,IAAS,QAAA,CAAA;AAAA,MACP,IAAM,EAAA,aAAA;AAAA,MACN,OAAS,EAAA;AAAA,KACV,CAAA;AAAA,GACH,EAAG,CAAC,gBAAgB,CAAC,CAAA;AAErB,EAAA,uBACGC,cAAA,CAAA,oBAAA,CAAqB,QAArB,EAAA,EAA8B,KAAO,EAAA,KAAA,EACpC,QAAC,kBAAAA,cAAA,CAAA,uBAAA,CAAwB,QAAxB,EAAA,EAAiC,KAAO,EAAA,QAAA,EACtC,UACH,CACF,EAAA,CAAA;AAEJ;;;;;;"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ var css_248z = ".saltCarouselControls {\n display: flex;\n gap: var(--salt-spacing-100);\n align-items: center;\n grid-area: controls;\n}\n.saltCarouselControls-container {\n display: flex;\n flex-flow: wrap;\n align-items: center;\n gap: var(--salt-spacing-100);\n justify-content: space-between;\n}\n.saltCarouselControls-container:last-child {\n margin-left: auto;\n}\n.saltCarouselControls-container h2 {\n margin: 0;\n}\n\n.saltCarouselControls-sr-only {\n display: none;\n}\n.saltCarouselControls:focus-within .saltCarouselControls-sr-only {\n display: block;\n position: fixed;\n top: 0;\n left: 0;\n transform: translate(-100%, -100%);\n}\n";
4
+
5
+ module.exports = css_248z;
6
+ //# sourceMappingURL=CarouselControls.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CarouselControls.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var core = require('@salt-ds/core');
5
+ var styles = require('@salt-ds/styles');
6
+ var window = require('@salt-ds/window');
7
+ var react = require('react');
8
+ var CarouselContext = require('./CarouselContext.js');
9
+ var CarouselControls$1 = require('./CarouselControls.css.js');
10
+
11
+ const withBaseName = core.makePrefixer("saltCarouselControls");
12
+ const CarouselControls = react.forwardRef(function CarouselControls2({
13
+ onPrevious,
14
+ onNext,
15
+ disabled,
16
+ className,
17
+ title,
18
+ labelPlacement = "right",
19
+ ...rest
20
+ }, ref) {
21
+ var _a;
22
+ const targetWindow = window.useWindow();
23
+ styles.useComponentCssInjection({
24
+ testId: "salt-carousel-controls",
25
+ css: CarouselControls$1,
26
+ window: targetWindow
27
+ });
28
+ const { slides, carouselId, activeSlideIndex, visibleSlides } = react.useContext(CarouselContext.CarouselStateContext);
29
+ const dispatch = react.useContext(CarouselContext.CarouselDispatchContext);
30
+ const slideCount = slides.size;
31
+ const { NextIcon, PreviousIcon } = core.useIcon();
32
+ const [isFocused, setIsFocused] = react.useState(false);
33
+ function handleFocusCapture() {
34
+ !isFocused && setIsFocused(true);
35
+ }
36
+ function handleBlurCapture() {
37
+ isFocused && setIsFocused(false);
38
+ }
39
+ const prevButtonRef = react.useRef(null);
40
+ const nextButtonRef = react.useRef(null);
41
+ const slideIds = [...slides.keys()];
42
+ const currentId = slideIds[activeSlideIndex] || null;
43
+ const prevId = slideIds[activeSlideIndex - 1] || null;
44
+ const nextId = slideIds[activeSlideIndex + 1] || null;
45
+ const isOnFirstSlide = activeSlideIndex === 0;
46
+ const isOnLastSlide = activeSlideIndex === slideCount - visibleSlides;
47
+ const isAnnouncerOn = nextButtonRef.current === document.activeElement || prevButtonRef.current === document.activeElement;
48
+ const currentSlideDescription = visibleSlides === 1 && currentId && ((_a = slides.get(currentId)) == null ? void 0 : _a.slideDescription) || void 0;
49
+ const controlsLabel = slideCount >= 1 && /* @__PURE__ */ jsxRuntime.jsx(
50
+ core.Text,
51
+ {
52
+ as: "span",
53
+ "aria-live": isAnnouncerOn ? "polite" : void 0,
54
+ "aria-atomic": "false",
55
+ children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
56
+ `${activeSlideIndex + 1} ${visibleSlides > 1 && slideCount > 1 ? ` - ${activeSlideIndex + visibleSlides}` : ""} of
57
+ ${slideCount}`,
58
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "saltCarouselControls-sr-only", children: currentSlideDescription })
59
+ ] })
60
+ }
61
+ );
62
+ function handlePrevClick(event) {
63
+ if (!prevId) return;
64
+ dispatch({ type: "scroll", payload: prevId });
65
+ onPrevious == null ? void 0 : onPrevious(event, slideIds.indexOf(prevId));
66
+ }
67
+ function handleNextClick(event) {
68
+ if (!nextId) return;
69
+ dispatch({ type: "scroll", payload: nextId });
70
+ onNext == null ? void 0 : onNext(event, slideIds.indexOf(nextId));
71
+ }
72
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: withBaseName("container"), ref, ...rest, children: [
73
+ title,
74
+ /* @__PURE__ */ jsxRuntime.jsxs(
75
+ "div",
76
+ {
77
+ className: withBaseName(),
78
+ ref,
79
+ ...rest,
80
+ onFocusCapture: handleFocusCapture,
81
+ onBlurCapture: handleBlurCapture,
82
+ children: [
83
+ (labelPlacement === "left" || title) && controlsLabel,
84
+ /* @__PURE__ */ jsxRuntime.jsx(
85
+ core.Button,
86
+ {
87
+ ref: prevButtonRef,
88
+ focusableWhenDisabled: true,
89
+ appearance: "bordered",
90
+ sentiment: "neutral",
91
+ className: withBaseName("prev-button"),
92
+ onClick: handlePrevClick,
93
+ disabled: isOnFirstSlide || disabled,
94
+ "aria-controls": carouselId,
95
+ "aria-label": `Previous slide${visibleSlides > 1 ? "s" : ""}`,
96
+ children: /* @__PURE__ */ jsxRuntime.jsx(PreviousIcon, { "aria-hidden": true })
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsxRuntime.jsx(
100
+ core.Button,
101
+ {
102
+ ref: nextButtonRef,
103
+ focusableWhenDisabled: true,
104
+ appearance: "bordered",
105
+ sentiment: "neutral",
106
+ className: withBaseName("next-button"),
107
+ onClick: handleNextClick,
108
+ disabled: isOnLastSlide || disabled,
109
+ "aria-controls": carouselId,
110
+ "aria-label": `Next slide${visibleSlides > 1 ? "s" : ""}`,
111
+ children: /* @__PURE__ */ jsxRuntime.jsx(NextIcon, { "aria-hidden": true })
112
+ }
113
+ ),
114
+ labelPlacement === "right" && !title && controlsLabel
115
+ ]
116
+ }
117
+ )
118
+ ] });
119
+ });
120
+
121
+ exports.CarouselControls = CarouselControls;
122
+ //# sourceMappingURL=CarouselControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CarouselControls.js","sources":["../src/carousel/CarouselControls.tsx"],"sourcesContent":["import { Button, Text, makePrefixer, useIcon } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport {\n type HTMLAttributes,\n type MouseEvent,\n type ReactNode,\n type SyntheticEvent,\n forwardRef,\n useContext,\n useRef,\n useState,\n} from \"react\";\nimport {\n CarouselDispatchContext,\n CarouselStateContext,\n} from \"./CarouselContext\";\n\nimport carouselControlsCss from \"./CarouselControls.css\";\n\nconst withBaseName = makePrefixer(\"saltCarouselControls\");\n\nexport interface CarouselControlsProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /**\n * Callback when Back button is clicked.\n */\n onPrevious?: (\n event: SyntheticEvent<HTMLButtonElement>,\n index: number,\n ) => void;\n /**\n * Callback when Next button is clicked.\n */\n onNext?: (event: SyntheticEvent<HTMLButtonElement>, index: number) => void;\n /**\n * Location of the label relative to the controls.\n *\n * Either 'left', or 'right'`.\n */\n labelPlacement?: \"left\" | \"right\";\n /**\n * If `true`, the carousel controls will be disabled.\n * **/\n disabled?: boolean;\n /**\n * The title of the carousel that accompanies the controls.\n */\n title?: ReactNode;\n}\n\nexport const CarouselControls = forwardRef<\n HTMLDivElement,\n CarouselControlsProps\n>(function CarouselControls(\n {\n onPrevious,\n onNext,\n disabled,\n className,\n title,\n labelPlacement = \"right\",\n ...rest\n },\n ref,\n) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-controls\",\n css: carouselControlsCss,\n window: targetWindow,\n });\n const { slides, carouselId, activeSlideIndex, visibleSlides } =\n useContext(CarouselStateContext);\n const dispatch = useContext(CarouselDispatchContext);\n\n const slideCount = slides.size;\n const { NextIcon, PreviousIcon } = useIcon();\n\n const [isFocused, setIsFocused] = useState(false);\n function handleFocusCapture() {\n !isFocused && setIsFocused(true);\n }\n function handleBlurCapture() {\n isFocused && setIsFocused(false);\n }\n\n const prevButtonRef = useRef<HTMLButtonElement>(null);\n const nextButtonRef = useRef<HTMLButtonElement>(null);\n const slideIds = [...slides.keys()];\n\n const currentId = slideIds[activeSlideIndex] || null;\n const prevId = slideIds[activeSlideIndex - 1] || null;\n const nextId = slideIds[activeSlideIndex + 1] || null;\n\n const isOnFirstSlide = activeSlideIndex === 0;\n const isOnLastSlide = activeSlideIndex === slideCount - visibleSlides;\n\n const isAnnouncerOn =\n nextButtonRef.current === document.activeElement ||\n prevButtonRef.current === document.activeElement;\n\n const currentSlideDescription =\n (visibleSlides === 1 &&\n currentId &&\n slides.get(currentId)?.slideDescription) ||\n undefined;\n\n const controlsLabel = slideCount >= 1 && (\n <Text\n as=\"span\"\n aria-live={isAnnouncerOn ? \"polite\" : undefined}\n aria-atomic=\"false\"\n >\n <strong>\n {`${activeSlideIndex + 1} ${visibleSlides > 1 && slideCount > 1 ? ` - ${activeSlideIndex + visibleSlides}` : \"\"} of\n ${slideCount}`}\n\n {\n <span className=\"saltCarouselControls-sr-only\">\n {currentSlideDescription}\n </span>\n }\n </strong>\n </Text>\n );\n\n function handlePrevClick(event: MouseEvent<HTMLButtonElement>) {\n if (!prevId) return;\n dispatch({ type: \"scroll\", payload: prevId });\n onPrevious?.(event, slideIds.indexOf(prevId));\n }\n\n function handleNextClick(event: MouseEvent<HTMLButtonElement>) {\n if (!nextId) return;\n dispatch({ type: \"scroll\", payload: nextId });\n onNext?.(event, slideIds.indexOf(nextId));\n }\n\n return (\n <div className={withBaseName(\"container\")} ref={ref} {...rest}>\n {title}\n <div\n className={withBaseName()}\n ref={ref}\n {...rest}\n onFocusCapture={handleFocusCapture}\n onBlurCapture={handleBlurCapture}\n >\n {(labelPlacement === \"left\" || title) && controlsLabel}\n <Button\n ref={prevButtonRef}\n focusableWhenDisabled\n appearance=\"bordered\"\n sentiment=\"neutral\"\n className={withBaseName(\"prev-button\")}\n onClick={handlePrevClick}\n disabled={isOnFirstSlide || disabled}\n aria-controls={carouselId}\n aria-label={`Previous slide${visibleSlides > 1 ? \"s\" : \"\"}`}\n >\n <PreviousIcon aria-hidden />\n </Button>\n <Button\n ref={nextButtonRef}\n focusableWhenDisabled\n appearance=\"bordered\"\n sentiment=\"neutral\"\n className={withBaseName(\"next-button\")}\n onClick={handleNextClick}\n disabled={isOnLastSlide || disabled}\n aria-controls={carouselId}\n aria-label={`Next slide${visibleSlides > 1 ? \"s\" : \"\"}`}\n >\n <NextIcon aria-hidden />\n </Button>\n {labelPlacement === \"right\" && !title && controlsLabel}\n </div>\n </div>\n );\n});\n"],"names":["makePrefixer","forwardRef","CarouselControls","useWindow","useComponentCssInjection","carouselControlsCss","useContext","CarouselStateContext","CarouselDispatchContext","useIcon","useState","useRef","jsx","Text","jsxs","Button"],"mappings":"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"}