@salt-ds/embla-carousel 0.1.5 → 1.0.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 (94) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/css/salt-embla-carousel.css +54 -27
  3. package/dist-cjs/Carousel.js +30 -9
  4. package/dist-cjs/Carousel.js.map +1 -1
  5. package/dist-cjs/CarouselAutoplayIndicator.css.js +1 -1
  6. package/dist-cjs/CarouselAutoplayIndicator.js.map +1 -1
  7. package/dist-cjs/CarouselAutoplayIndicatorSVG.js +12 -24
  8. package/dist-cjs/CarouselAutoplayIndicatorSVG.js.map +1 -1
  9. package/dist-cjs/CarouselCard.css.js +1 -1
  10. package/dist-cjs/CarouselCard.js +23 -9
  11. package/dist-cjs/CarouselCard.js.map +1 -1
  12. package/dist-cjs/CarouselContext.js.map +1 -1
  13. package/dist-cjs/CarouselNextButton.js +4 -1
  14. package/dist-cjs/CarouselNextButton.js.map +1 -1
  15. package/dist-cjs/CarouselPreviousButton.js +4 -1
  16. package/dist-cjs/CarouselPreviousButton.js.map +1 -1
  17. package/dist-cjs/CarouselProgressLabel.js +11 -33
  18. package/dist-cjs/CarouselProgressLabel.js.map +1 -1
  19. package/dist-cjs/CarouselSlides.css.js +1 -1
  20. package/dist-cjs/CarouselSlides.js +148 -25
  21. package/dist-cjs/CarouselSlides.js.map +1 -1
  22. package/dist-cjs/CarouselTab.css.js +1 -1
  23. package/dist-cjs/CarouselTab.js +1 -5
  24. package/dist-cjs/CarouselTab.js.map +1 -1
  25. package/dist-cjs/CarouselTabList.css.js +1 -1
  26. package/dist-cjs/CarouselTabList.js +47 -27
  27. package/dist-cjs/CarouselTabList.js.map +1 -1
  28. package/dist-cjs/createCustomSettle.js +1 -1
  29. package/dist-cjs/createCustomSettle.js.map +1 -1
  30. package/dist-cjs/getSlideDescription.js +32 -0
  31. package/dist-cjs/getSlideDescription.js.map +1 -0
  32. package/dist-cjs/getVisibleSlideDescription.js +26 -0
  33. package/dist-cjs/getVisibleSlideDescription.js.map +1 -0
  34. package/dist-cjs/getVisibleSlideIndexes.js +33 -0
  35. package/dist-cjs/getVisibleSlideIndexes.js.map +1 -0
  36. package/dist-cjs/index.js +0 -3
  37. package/dist-cjs/index.js.map +1 -1
  38. package/dist-cjs/usePrevNextButtons.js +3 -1
  39. package/dist-cjs/usePrevNextButtons.js.map +1 -1
  40. package/dist-es/Carousel.js +32 -11
  41. package/dist-es/Carousel.js.map +1 -1
  42. package/dist-es/CarouselAutoplayIndicator.css.js +1 -1
  43. package/dist-es/CarouselAutoplayIndicator.js.map +1 -1
  44. package/dist-es/CarouselAutoplayIndicatorSVG.js +13 -25
  45. package/dist-es/CarouselAutoplayIndicatorSVG.js.map +1 -1
  46. package/dist-es/CarouselCard.css.js +1 -1
  47. package/dist-es/CarouselCard.js +23 -9
  48. package/dist-es/CarouselCard.js.map +1 -1
  49. package/dist-es/CarouselContext.js.map +1 -1
  50. package/dist-es/CarouselNextButton.js +4 -1
  51. package/dist-es/CarouselNextButton.js.map +1 -1
  52. package/dist-es/CarouselPreviousButton.js +4 -1
  53. package/dist-es/CarouselPreviousButton.js.map +1 -1
  54. package/dist-es/CarouselProgressLabel.js +12 -34
  55. package/dist-es/CarouselProgressLabel.js.map +1 -1
  56. package/dist-es/CarouselSlides.css.js +1 -1
  57. package/dist-es/CarouselSlides.js +150 -27
  58. package/dist-es/CarouselSlides.js.map +1 -1
  59. package/dist-es/CarouselTab.css.js +1 -1
  60. package/dist-es/CarouselTab.js +1 -5
  61. package/dist-es/CarouselTab.js.map +1 -1
  62. package/dist-es/CarouselTabList.css.js +1 -1
  63. package/dist-es/CarouselTabList.js +48 -28
  64. package/dist-es/CarouselTabList.js.map +1 -1
  65. package/dist-es/createCustomSettle.js +1 -1
  66. package/dist-es/createCustomSettle.js.map +1 -1
  67. package/dist-es/getSlideDescription.js +30 -0
  68. package/dist-es/getSlideDescription.js.map +1 -0
  69. package/dist-es/getVisibleSlideDescription.js +24 -0
  70. package/dist-es/getVisibleSlideDescription.js.map +1 -0
  71. package/dist-es/getVisibleSlideIndexes.js +30 -0
  72. package/dist-es/getVisibleSlideIndexes.js.map +1 -0
  73. package/dist-es/index.js +0 -1
  74. package/dist-es/index.js.map +1 -1
  75. package/dist-es/usePrevNextButtons.js +3 -1
  76. package/dist-es/usePrevNextButtons.js.map +1 -1
  77. package/dist-types/Carousel.d.ts +7 -1
  78. package/dist-types/CarouselContext.d.ts +31 -0
  79. package/dist-types/CarouselTab.d.ts +0 -6
  80. package/dist-types/CarouselTabList.d.ts +4 -0
  81. package/dist-types/getSlideDescription.d.ts +7 -0
  82. package/dist-types/getVisibleSlideDescription.d.ts +7 -0
  83. package/dist-types/getVisibleSlideIndexes.d.ts +8 -0
  84. package/dist-types/index.d.ts +0 -1
  85. package/package.json +2 -2
  86. package/dist-cjs/CarouselAnnouncementPlugin.js +0 -52
  87. package/dist-cjs/CarouselAnnouncementPlugin.js.map +0 -1
  88. package/dist-cjs/CarouselProgressLabel.css.js +0 -6
  89. package/dist-cjs/CarouselProgressLabel.css.js.map +0 -1
  90. package/dist-es/CarouselAnnouncementPlugin.js +0 -49
  91. package/dist-es/CarouselAnnouncementPlugin.js.map +0 -1
  92. package/dist-es/CarouselProgressLabel.css.js +0 -4
  93. package/dist-es/CarouselProgressLabel.css.js.map +0 -1
  94. package/dist-types/CarouselAnnouncementPlugin.d.ts +0 -24
@@ -2,12 +2,10 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var core = require('@salt-ds/core');
5
- var styles = require('@salt-ds/styles');
6
- var window = require('@salt-ds/window');
7
5
  var clsx = require('clsx');
8
6
  var react = require('react');
9
7
  var CarouselContext = require('./CarouselContext.js');
10
- var CarouselProgressLabel$1 = require('./CarouselProgressLabel.css.js');
8
+ var getVisibleSlideIndexes = require('./getVisibleSlideIndexes.js');
11
9
 
12
10
  const withBaseName = core.makePrefixer("saltCarouselTabList");
13
11
  function CarouselProgressLabel({
@@ -16,35 +14,20 @@ function CarouselProgressLabel({
16
14
  children,
17
15
  ...props
18
16
  }) {
19
- const targetWindow = window.useWindow();
20
- styles.useComponentCssInjection({
21
- testId: "salt-carousel-progress-label",
22
- css: CarouselProgressLabel$1,
23
- window: targetWindow
24
- });
25
17
  const { emblaApi } = CarouselContext.useCarouselContext();
26
- const [currentSlide, setCurrentSlide] = react.useState("");
27
- const [totalSlides, setTotalSlides] = react.useState(0);
18
+ const [progress, setProgress] = react.useState("");
28
19
  react.useEffect(() => {
29
20
  const handleSelect = (emblaApi2) => {
30
- const slideIndexInView = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
21
+ const selectedScrollSnap = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
31
22
  const numberOfSlides = (emblaApi2 == null ? void 0 : emblaApi2.slideNodes().length) ?? 0;
32
- const scrollSnaps = (emblaApi2 == null ? void 0 : emblaApi2.scrollSnapList()) ?? [];
33
- const slidesPerTransition = numberOfSlides ? Math.ceil(numberOfSlides / scrollSnaps.length) : 0;
34
- const startSlideNumber = Math.min(
35
- slideIndexInView * slidesPerTransition + 1,
36
- numberOfSlides - (slidesPerTransition - 1)
23
+ const visibleSlides = getVisibleSlideIndexes.getVisibleSlideIndexes(
24
+ emblaApi2,
25
+ selectedScrollSnap
37
26
  );
38
- const endSlideNumber = Math.min(
39
- startSlideNumber + slidesPerTransition - 1,
40
- numberOfSlides
41
- );
42
- if (startSlideNumber === endSlideNumber) {
43
- setCurrentSlide(startSlideNumber.toString(10));
44
- } else {
45
- setCurrentSlide(`${startSlideNumber}-${endSlideNumber}`);
46
- }
47
- setTotalSlides(numberOfSlides);
27
+ const startSlideNumber = visibleSlides.length >= 1 ? visibleSlides[0] : 0;
28
+ const endSlideNumber = visibleSlides.length > 1 ? visibleSlides[visibleSlides.length - 1] : 0;
29
+ const slidePosition = endSlideNumber ? `${startSlideNumber}-${endSlideNumber}` : startSlideNumber;
30
+ setProgress(`Slide ${slidePosition} of ${numberOfSlides}`);
48
31
  };
49
32
  if (!emblaApi) return;
50
33
  emblaApi.on("init", handleSelect).on("reInit", handleSelect).on("select", handleSelect);
@@ -53,12 +36,7 @@ function CarouselProgressLabel({
53
36
  emblaApi.off("init", handleSelect).off("reInit", handleSelect).off("select", handleSelect);
54
37
  };
55
38
  }, [emblaApi]);
56
- return /* @__PURE__ */ jsxRuntime.jsxs(core.Text, { className: clsx.clsx(withBaseName(), className), ...props, children: [
57
- "Slide ",
58
- currentSlide,
59
- " of ",
60
- totalSlides
61
- ] });
39
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Text, { className: clsx.clsx(withBaseName(), className), ...props, children: progress });
62
40
  }
63
41
 
64
42
  exports.CarouselProgressLabel = CarouselProgressLabel;
@@ -1 +1 @@
1
- {"version":3,"file":"CarouselProgressLabel.js","sources":["../src/CarouselProgressLabel.tsx"],"sourcesContent":["import { makePrefixer, Text, type TextProps } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport { useEffect, useState } from \"react\";\nimport { useCarouselContext } from \"./CarouselContext\";\nimport carouselProgressLabelCss from \"./CarouselProgressLabel.css\";\n\n/**\n * Props for the CarouselProgressLabel component.\n */\nexport interface CarouselProgressLabelProps extends TextProps<\"div\"> {}\n\nconst withBaseName = makePrefixer(\"saltCarouselTabList\");\n\nexport function CarouselProgressLabel({\n className,\n styleAs = \"label\",\n children,\n ...props\n}: CarouselProgressLabelProps) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-progress-label\",\n css: carouselProgressLabelCss,\n window: targetWindow,\n });\n\n const { emblaApi } = useCarouselContext();\n\n const [currentSlide, setCurrentSlide] = useState(\"\");\n const [totalSlides, setTotalSlides] = useState(0);\n\n useEffect(() => {\n const handleSelect = (emblaApi: EmblaCarouselType) => {\n const slideIndexInView = emblaApi?.selectedScrollSnap() ?? 0;\n const numberOfSlides = emblaApi?.slideNodes().length ?? 0;\n const scrollSnaps = emblaApi?.scrollSnapList() ?? [];\n const slidesPerTransition = numberOfSlides\n ? Math.ceil(numberOfSlides / scrollSnaps.length)\n : 0;\n const startSlideNumber = Math.min(\n slideIndexInView * slidesPerTransition + 1,\n numberOfSlides - (slidesPerTransition - 1),\n );\n const endSlideNumber = Math.min(\n startSlideNumber + slidesPerTransition - 1,\n numberOfSlides,\n );\n\n if (startSlideNumber === endSlideNumber) {\n setCurrentSlide(startSlideNumber.toString(10));\n } else {\n setCurrentSlide(`${startSlideNumber}-${endSlideNumber}`);\n }\n setTotalSlides(numberOfSlides);\n };\n\n if (!emblaApi) return;\n emblaApi\n .on(\"init\", handleSelect)\n .on(\"reInit\", handleSelect)\n .on(\"select\", handleSelect);\n handleSelect(emblaApi);\n // Cleanup listener on component unmount\n return () => {\n emblaApi\n .off(\"init\", handleSelect)\n .off(\"reInit\", handleSelect)\n .off(\"select\", handleSelect);\n };\n }, [emblaApi]);\n\n return (\n <Text className={clsx(withBaseName(), className)} {...props}>\n Slide {currentSlide} of {totalSlides}\n </Text>\n );\n}\n"],"names":["makePrefixer","useWindow","useComponentCssInjection","carouselProgressLabelCss","useCarouselContext","useState","useEffect","emblaApi","jsxs","Text","clsx"],"mappings":";;;;;;;;;;;AAcA,MAAM,YAAA,GAAeA,kBAAa,qBAAqB,CAAA;AAEhD,SAAS,qBAAsB,CAAA;AAAA,EACpC,SAAA;AAAA,EACA,OAAU,GAAA,OAAA;AAAA,EACV,QAAA;AAAA,EACA,GAAG;AACL,CAA+B,EAAA;AAC7B,EAAA,MAAM,eAAeC,gBAAU,EAAA;AAC/B,EAAyBC,+BAAA,CAAA;AAAA,IACvB,MAAQ,EAAA,8BAAA;AAAA,IACR,GAAK,EAAAC,uBAAA;AAAA,IACL,MAAQ,EAAA;AAAA,GACT,CAAA;AAED,EAAM,MAAA,EAAE,QAAS,EAAA,GAAIC,kCAAmB,EAAA;AAExC,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIC,eAAS,EAAE,CAAA;AACnD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,CAAC,CAAA;AAEhD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAM,MAAA,YAAA,GAAe,CAACC,SAAgC,KAAA;AACpD,MAAA,MAAM,gBAAmBA,GAAAA,CAAAA,SAAAA,IAAA,IAAAA,GAAAA,MAAAA,GAAAA,SAAAA,CAAU,kBAAwB,EAAA,KAAA,CAAA;AAC3D,MAAA,MAAM,cAAiBA,GAAAA,CAAAA,SAAAA,IAAA,IAAAA,GAAAA,MAAAA,GAAAA,SAAAA,CAAU,aAAa,MAAU,KAAA,CAAA;AACxD,MAAA,MAAM,WAAcA,GAAAA,CAAAA,SAAAA,IAAA,IAAAA,GAAAA,MAAAA,GAAAA,SAAAA,CAAU,qBAAoB,EAAC;AACnD,MAAA,MAAM,sBAAsB,cACxB,GAAA,IAAA,CAAK,KAAK,cAAiB,GAAA,WAAA,CAAY,MAAM,CAC7C,GAAA,CAAA;AACJ,MAAA,MAAM,mBAAmB,IAAK,CAAA,GAAA;AAAA,QAC5B,mBAAmB,mBAAsB,GAAA,CAAA;AAAA,QACzC,kBAAkB,mBAAsB,GAAA,CAAA;AAAA,OAC1C;AACA,MAAA,MAAM,iBAAiB,IAAK,CAAA,GAAA;AAAA,QAC1B,mBAAmB,mBAAsB,GAAA,CAAA;AAAA,QACzC;AAAA,OACF;AAEA,MAAA,IAAI,qBAAqB,cAAgB,EAAA;AACvC,QAAgB,eAAA,CAAA,gBAAA,CAAiB,QAAS,CAAA,EAAE,CAAC,CAAA;AAAA,OACxC,MAAA;AACL,QAAA,eAAA,CAAgB,CAAG,EAAA,gBAAgB,CAAI,CAAA,EAAA,cAAc,CAAE,CAAA,CAAA;AAAA;AAEzD,MAAA,cAAA,CAAe,cAAc,CAAA;AAAA,KAC/B;AAEA,IAAA,IAAI,CAAC,QAAU,EAAA;AACf,IACG,QAAA,CAAA,EAAA,CAAG,MAAQ,EAAA,YAAY,CACvB,CAAA,EAAA,CAAG,UAAU,YAAY,CAAA,CACzB,EAAG,CAAA,QAAA,EAAU,YAAY,CAAA;AAC5B,IAAA,YAAA,CAAa,QAAQ,CAAA;AAErB,IAAA,OAAO,MAAM;AACX,MACG,QAAA,CAAA,GAAA,CAAI,MAAQ,EAAA,YAAY,CACxB,CAAA,GAAA,CAAI,UAAU,YAAY,CAAA,CAC1B,GAAI,CAAA,QAAA,EAAU,YAAY,CAAA;AAAA,KAC/B;AAAA,GACF,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EACE,uBAAAC,eAAA,CAACC,aAAK,SAAW,EAAAC,SAAA,CAAK,cAAgB,EAAA,SAAS,CAAI,EAAA,GAAG,KAAO,EAAA,QAAA,EAAA;AAAA,IAAA,QAAA;AAAA,IACpD,YAAA;AAAA,IAAa,MAAA;AAAA,IAAK;AAAA,GAC3B,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CarouselProgressLabel.js","sources":["../src/CarouselProgressLabel.tsx"],"sourcesContent":["import { makePrefixer, Text, type TextProps } from \"@salt-ds/core\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport { useEffect, useState } from \"react\";\nimport { useCarouselContext } from \"./CarouselContext\";\nimport { getVisibleSlideIndexes } from \"./getVisibleSlideIndexes\";\n\n/**\n * Props for the CarouselProgressLabel component.\n */\nexport interface CarouselProgressLabelProps extends TextProps<\"div\"> {}\n\nconst withBaseName = makePrefixer(\"saltCarouselTabList\");\n\nexport function CarouselProgressLabel({\n className,\n styleAs = \"label\",\n children,\n ...props\n}: CarouselProgressLabelProps) {\n const { emblaApi } = useCarouselContext();\n\n const [progress, setProgress] = useState(\"\");\n\n useEffect(() => {\n const handleSelect = (emblaApi: EmblaCarouselType) => {\n const selectedScrollSnap = emblaApi?.selectedScrollSnap() ?? 0;\n const numberOfSlides = emblaApi?.slideNodes().length ?? 0;\n const visibleSlides = getVisibleSlideIndexes(\n emblaApi,\n selectedScrollSnap,\n );\n const startSlideNumber = visibleSlides.length >= 1 ? visibleSlides[0] : 0;\n const endSlideNumber =\n visibleSlides.length > 1 ? visibleSlides[visibleSlides.length - 1] : 0;\n const slidePosition = endSlideNumber\n ? `${startSlideNumber}-${endSlideNumber}`\n : startSlideNumber;\n setProgress(`Slide ${slidePosition} of ${numberOfSlides}`);\n };\n\n if (!emblaApi) return;\n emblaApi\n .on(\"init\", handleSelect)\n .on(\"reInit\", handleSelect)\n .on(\"select\", handleSelect);\n handleSelect(emblaApi);\n // Cleanup listener on component unmount\n return () => {\n emblaApi\n .off(\"init\", handleSelect)\n .off(\"reInit\", handleSelect)\n .off(\"select\", handleSelect);\n };\n }, [emblaApi]);\n\n return (\n <Text className={clsx(withBaseName(), className)} {...props}>\n {progress}\n </Text>\n );\n}\n"],"names":["makePrefixer","useCarouselContext","useState","useEffect","emblaApi","getVisibleSlideIndexes","jsx","Text","clsx"],"mappings":";;;;;;;;;AAYA,MAAM,YAAA,GAAeA,kBAAa,qBAAqB,CAAA;AAEhD,SAAS,qBAAA,CAAsB;AAAA,EACpC,SAAA;AAAA,EACA,OAAA,GAAU,OAAA;AAAA,EACV,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA+B;AAC7B,EAAA,MAAM,EAAE,QAAA,EAAS,GAAIC,kCAAA,EAAmB;AAExC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIC,eAAS,EAAE,CAAA;AAE3C,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAA,GAAe,CAACC,SAAAA,KAAgC;AACpD,MAAA,MAAM,kBAAA,GAAA,CAAqBA,SAAAA,IAAA,IAAA,GAAA,MAAA,GAAAA,SAAAA,CAAU,kBAAA,EAAA,KAAwB,CAAA;AAC7D,MAAA,MAAM,cAAA,GAAA,CAAiBA,SAAAA,IAAA,IAAA,GAAA,MAAA,GAAAA,SAAAA,CAAU,aAAa,MAAA,KAAU,CAAA;AACxD,MAAA,MAAM,aAAA,GAAgBC,6CAAA;AAAA,QACpBD,SAAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,MAAM,mBAAmB,aAAA,CAAc,MAAA,IAAU,CAAA,GAAI,aAAA,CAAc,CAAC,CAAA,GAAI,CAAA;AACxE,MAAA,MAAM,cAAA,GACJ,cAAc,MAAA,GAAS,CAAA,GAAI,cAAc,aAAA,CAAc,MAAA,GAAS,CAAC,CAAA,GAAI,CAAA;AACvE,MAAA,MAAM,gBAAgB,cAAA,GAClB,CAAA,EAAG,gBAAgB,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA,GACrC,gBAAA;AACJ,MAAA,WAAA,CAAY,CAAA,MAAA,EAAS,aAAa,CAAA,IAAA,EAAO,cAAc,CAAA,CAAE,CAAA;AAAA,IAC3D,CAAA;AAEA,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,QAAA,CACG,EAAA,CAAG,MAAA,EAAQ,YAAY,CAAA,CACvB,EAAA,CAAG,UAAU,YAAY,CAAA,CACzB,EAAA,CAAG,QAAA,EAAU,YAAY,CAAA;AAC5B,IAAA,YAAA,CAAa,QAAQ,CAAA;AAErB,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CACG,GAAA,CAAI,MAAA,EAAQ,YAAY,CAAA,CACxB,GAAA,CAAI,UAAU,YAAY,CAAA,CAC1B,GAAA,CAAI,QAAA,EAAU,YAAY,CAAA;AAAA,IAC/B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,uBACEE,cAAA,CAACC,SAAA,EAAA,EAAK,SAAA,EAAWC,SAAA,CAAK,YAAA,IAAgB,SAAS,CAAA,EAAI,GAAG,KAAA,EACnD,QAAA,EAAA,QAAA,EACH,CAAA;AAEJ;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var css_248z = ".saltCarouselSlides {\n overflow: hidden;\n}\n.saltCarouselSlides-container {\n display: flex;\n touch-action: pan-y pinch-zoom;\n margin-left: calc(var(--carousel-slide-spacing) * -1);\n}\n";
3
+ var css_248z = ".saltCarouselSlides {\n overflow: hidden;\n cursor: var(--salt-cursor-grab);\n}\n\n.saltCarouselSlides-container {\n display: flex;\n touch-action: pan-y pinch-zoom;\n margin-left: calc(var(--carousel-slide-spacing) * -1);\n}\n\n.saltCarouselSlides-sr-only {\n position: absolute;\n height: 1px;\n width: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n.saltCarouselSlides-dragging {\n cursor: var(--salt-cursor-grab-active);\n}\n";
4
4
 
5
5
  module.exports = css_248z;
6
6
  //# sourceMappingURL=CarouselSlides.css.js.map
@@ -9,73 +9,196 @@ var react = require('react');
9
9
  var CarouselContext = require('./CarouselContext.js');
10
10
  var CarouselSlides$1 = require('./CarouselSlides.css.js');
11
11
  var createCustomSettle = require('./createCustomSettle.js');
12
+ var getVisibleSlideDescription = require('./getVisibleSlideDescription.js');
13
+ var getVisibleSlideIndexes = require('./getVisibleSlideIndexes.js');
12
14
 
15
+ const ANNOUNCEMENT_DURATION = 1200;
13
16
  const withBaseName = core.makePrefixer("saltCarouselSlides");
17
+ const announceSlideChangesFrom = [
18
+ "drag",
19
+ "focus",
20
+ "navigation"
21
+ ];
14
22
  const CarouselSlides = react.forwardRef(
15
- function CarouselSlides2({ children, className, onKeyDown, ...rest }, ref) {
23
+ function CarouselSlides2({ children, className, id, onKeyDown, ...rest }, ref) {
16
24
  const targetWindow = window.useWindow();
17
25
  styles.useComponentCssInjection({
18
26
  testId: "salt-carousel-slides",
19
27
  css: CarouselSlides$1,
20
28
  window: targetWindow
21
29
  });
22
- const { emblaApi, emblaRef } = CarouselContext.useCarouselContext();
23
- const carouselRef = core.useForkRef(ref, emblaRef);
24
- const usingArrowNavigation = react.useRef();
30
+ const {
31
+ disableSlideAnnouncements,
32
+ announcementState,
33
+ setAnnouncementState,
34
+ emblaApi,
35
+ emblaRef,
36
+ carouselId
37
+ } = CarouselContext.useCarouselContext();
38
+ const containerRef = react.useRef(null);
39
+ const forkedEmblaRef = core.useForkRef(ref, emblaRef);
40
+ const carouselRef = core.useForkRef(
41
+ forkedEmblaRef,
42
+ containerRef
43
+ );
44
+ const slideRefs = react.useRef([]);
45
+ const [focusedSlideIndex, setFocusedSlideIndex] = react.useState(-1);
46
+ const [dragging, setDragging] = react.useState(false);
47
+ const focusOnSettle = react.useRef(false);
48
+ const [stableScrollSnap, setStableScrollSnap] = react.useState(void 0);
49
+ const visibleSlideIndexes = getVisibleSlideIndexes.getVisibleSlideIndexes(
50
+ emblaApi,
51
+ stableScrollSnap ?? 0
52
+ );
53
+ const { announce } = core.useAriaAnnouncer();
25
54
  react.useEffect(() => {
26
55
  const handleSettle = (emblaApi2) => {
27
- if (!usingArrowNavigation.current) {
28
- return;
29
- }
30
- const slideIndexInView = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
31
- const snappedSlide = emblaApi2.slideNodes()[slideIndexInView];
32
- if (snappedSlide) {
33
- const focusableElements = snappedSlide.querySelectorAll(
34
- 'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
35
- );
36
- if (focusableElements.length > 0) {
37
- focusableElements[0].focus();
38
- }
39
- }
40
- usingArrowNavigation.current = false;
56
+ const selectedScrollSnap = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
57
+ setStableScrollSnap(selectedScrollSnap);
58
+ const numberOfSnaps = (emblaApi2 == null ? void 0 : emblaApi2.scrollSnapList().length) ?? 1;
59
+ const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;
60
+ const settledSlideIndex = Math.floor(
61
+ selectedScrollSnap * numberOfSlidesPerSnap
62
+ );
63
+ setFocusedSlideIndex(settledSlideIndex);
41
64
  };
42
65
  if (!emblaApi) {
43
66
  return;
44
67
  }
45
68
  const scrollCallback = createCustomSettle.createCustomSettle(handleSettle);
69
+ const pointerDownCallback = () => {
70
+ setAnnouncementState("drag");
71
+ };
46
72
  emblaApi.on("scroll", scrollCallback);
73
+ emblaApi.on("pointerDown", pointerDownCallback);
47
74
  return () => {
48
75
  emblaApi.off("scroll", scrollCallback);
76
+ emblaApi.off("pointerDown", pointerDownCallback);
49
77
  };
50
- }, [emblaApi]);
78
+ }, [emblaApi, setAnnouncementState]);
79
+ react.useEffect(() => {
80
+ var _a;
81
+ if (!focusOnSettle.current || stableScrollSnap === void 0) {
82
+ return;
83
+ }
84
+ (_a = slideRefs.current[focusedSlideIndex]) == null ? void 0 : _a.focus();
85
+ setAnnouncementState("focus");
86
+ focusOnSettle.current = false;
87
+ }, [stableScrollSnap, focusedSlideIndex, setAnnouncementState]);
88
+ react.useEffect(() => {
89
+ const numberOfSnaps = (emblaApi == null ? void 0 : emblaApi.scrollSnapList().length) ?? 1;
90
+ const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;
91
+ if (focusedSlideIndex >= 0) {
92
+ const nearestScrollSnap = Math.floor(
93
+ focusedSlideIndex / numberOfSlidesPerSnap
94
+ );
95
+ if ((emblaApi == null ? void 0 : emblaApi.selectedScrollSnap()) !== nearestScrollSnap) {
96
+ emblaApi == null ? void 0 : emblaApi.scrollTo(nearestScrollSnap);
97
+ focusOnSettle.current = true;
98
+ }
99
+ } else if (focusedSlideIndex === -1) {
100
+ const initialSnap = emblaApi == null ? void 0 : emblaApi.selectedScrollSnap();
101
+ const initialSlideIndex = initialSnap !== void 0 ? Math.floor(initialSnap * numberOfSlidesPerSnap) : 0;
102
+ setFocusedSlideIndex(initialSlideIndex);
103
+ setStableScrollSnap(initialSnap);
104
+ }
105
+ }, [focusedSlideIndex, emblaApi]);
106
+ react.useEffect(() => {
107
+ if (disableSlideAnnouncements === false) {
108
+ setAnnouncementState(void 0);
109
+ }
110
+ }, [disableSlideAnnouncements, setAnnouncementState]);
111
+ react.useEffect(() => {
112
+ if (stableScrollSnap === void 0 || disableSlideAnnouncements || !announcementState || announceSlideChangesFrom.indexOf(announcementState) === -1) {
113
+ return;
114
+ }
115
+ const announcement = getVisibleSlideDescription.getVisibleSlideDescription(
116
+ emblaApi,
117
+ stableScrollSnap
118
+ );
119
+ announce(announcement, ANNOUNCEMENT_DURATION);
120
+ }, [
121
+ announce,
122
+ announcementState,
123
+ disableSlideAnnouncements,
124
+ stableScrollSnap,
125
+ emblaApi
126
+ ]);
51
127
  const handleKeyDown = (event) => {
52
128
  if (event.repeat) {
53
129
  return;
54
130
  }
131
+ const numberOfSnaps = (emblaApi == null ? void 0 : emblaApi.scrollSnapList().length) ?? 1;
132
+ const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;
133
+ const currentSnap = Math.floor(focusedSlideIndex / numberOfSlidesPerSnap);
55
134
  switch (event.key) {
56
135
  case "ArrowLeft": {
57
136
  event.preventDefault();
58
- emblaApi == null ? void 0 : emblaApi.scrollPrev();
59
- usingArrowNavigation.current = true;
137
+ const prevSnap = Math.max(currentSnap - 1, 0);
138
+ const newIndex = prevSnap * numberOfSlidesPerSnap;
139
+ setFocusedSlideIndex(newIndex);
60
140
  break;
61
141
  }
62
142
  case "ArrowRight": {
63
143
  event.preventDefault();
64
- emblaApi == null ? void 0 : emblaApi.scrollNext();
65
- usingArrowNavigation.current = true;
144
+ const nextSnap = Math.min(currentSnap + 1, numberOfSnaps - 1);
145
+ const newIndex = nextSnap * numberOfSlidesPerSnap;
146
+ setFocusedSlideIndex(newIndex);
66
147
  break;
67
148
  }
68
149
  }
69
150
  onKeyDown == null ? void 0 : onKeyDown(event);
70
151
  };
152
+ const handleMouseDown = (event) => {
153
+ var _a;
154
+ setDragging(true);
155
+ (_a = rest.onMouseDown) == null ? void 0 : _a.call(rest, event);
156
+ };
157
+ const handleMouseUp = (event) => {
158
+ var _a;
159
+ setDragging(false);
160
+ (_a = rest.onMouseUp) == null ? void 0 : _a.call(rest, event);
161
+ };
71
162
  return /* @__PURE__ */ jsxRuntime.jsx(
72
163
  "div",
73
164
  {
74
165
  onKeyDown: handleKeyDown,
166
+ onMouseDown: handleMouseDown,
167
+ onMouseUp: handleMouseUp,
75
168
  ref: carouselRef,
76
- className: clsx.clsx(withBaseName(), className),
169
+ className: clsx.clsx(
170
+ withBaseName(),
171
+ { [withBaseName("dragging")]: dragging },
172
+ className
173
+ ),
77
174
  ...rest,
78
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: withBaseName("container"), children })
175
+ children: /* @__PURE__ */ jsxRuntime.jsx(
176
+ "div",
177
+ {
178
+ className: withBaseName("container"),
179
+ id: id ?? `${carouselId}-slides`,
180
+ children: react.Children.map(children, (child, index) => {
181
+ const childElement = child;
182
+ const existingId = childElement.props.id;
183
+ const isFocused = focusedSlideIndex === index;
184
+ const isHidden = !visibleSlideIndexes.includes(index + 1) && !isFocused;
185
+ const element = child;
186
+ return react.cloneElement(element, {
187
+ "aria-hidden": isHidden,
188
+ id: existingId ?? `${carouselId}-slide${index + 1}`,
189
+ onFocus: (event) => {
190
+ var _a, _b;
191
+ setFocusedSlideIndex(index);
192
+ (_b = (_a = element.props) == null ? void 0 : _a.onFocus) == null ? void 0 : _b.call(_a, event);
193
+ },
194
+ tabIndex: !isHidden ? 0 : -1,
195
+ ref: (el) => {
196
+ slideRefs.current[index] = el;
197
+ }
198
+ });
199
+ })
200
+ }
201
+ )
79
202
  }
80
203
  );
81
204
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CarouselSlides.js","sources":["../src/CarouselSlides.tsx"],"sourcesContent":["import { makePrefixer, useForkRef } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport {\n type ComponentPropsWithoutRef,\n forwardRef,\n type KeyboardEvent,\n useEffect,\n useRef,\n} from \"react\";\nimport { useCarouselContext } from \"./CarouselContext\";\nimport carouselSlidesCss from \"./CarouselSlides.css\";\nimport { createCustomSettle } from \"./createCustomSettle\";\n\n/**\n * Props for the CarouselSlides component.\n */\nexport interface CarouselSlidesProps extends ComponentPropsWithoutRef<\"div\"> {}\n\nconst withBaseName = makePrefixer(\"saltCarouselSlides\");\n\nexport const CarouselSlides = forwardRef<HTMLDivElement, CarouselSlidesProps>(\n function CarouselSlides({ children, className, onKeyDown, ...rest }, ref) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-slides\",\n css: carouselSlidesCss,\n window: targetWindow,\n });\n const { emblaApi, emblaRef } = useCarouselContext();\n\n const carouselRef = useForkRef<HTMLDivElement>(ref, emblaRef);\n\n const usingArrowNavigation = useRef<boolean>();\n\n useEffect(() => {\n const handleSettle = (emblaApi: EmblaCarouselType) => {\n if (!usingArrowNavigation.current) {\n return;\n }\n const slideIndexInView = emblaApi?.selectedScrollSnap() ?? 0;\n const snappedSlide = emblaApi.slideNodes()[slideIndexInView];\n if (snappedSlide) {\n const focusableElements = snappedSlide.querySelectorAll<HTMLElement>(\n 'a, button, input, textarea, select, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusableElements.length > 0) {\n focusableElements[0].focus();\n }\n }\n usingArrowNavigation.current = false;\n };\n\n if (!emblaApi) {\n return;\n }\n const scrollCallback = createCustomSettle(handleSettle);\n emblaApi.on(\"scroll\", scrollCallback);\n // Cleanup listener on component unmount\n return () => {\n emblaApi.off(\"scroll\", scrollCallback);\n };\n }, [emblaApi]);\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.repeat) {\n return;\n }\n switch (event.key) {\n case \"ArrowLeft\": {\n event.preventDefault();\n emblaApi?.scrollPrev();\n usingArrowNavigation.current = true;\n break;\n }\n case \"ArrowRight\": {\n event.preventDefault();\n emblaApi?.scrollNext();\n usingArrowNavigation.current = true;\n break;\n }\n }\n onKeyDown?.(event);\n };\n\n return (\n <div\n onKeyDown={handleKeyDown}\n ref={carouselRef}\n className={clsx(withBaseName(), className)}\n {...rest}\n >\n <div className={withBaseName(\"container\")}>{children}</div>\n </div>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","CarouselSlides","useWindow","useComponentCssInjection","carouselSlidesCss","useCarouselContext","useForkRef","useRef","useEffect","emblaApi","createCustomSettle","jsx","clsx"],"mappings":"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"}
1
+ {"version":3,"file":"CarouselSlides.js","sources":["../src/CarouselSlides.tsx"],"sourcesContent":["import { makePrefixer, useAriaAnnouncer, useForkRef } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport {\n Children,\n type ComponentPropsWithoutRef,\n cloneElement,\n type FocusEvent,\n forwardRef,\n type KeyboardEvent,\n type MouseEventHandler,\n type ReactElement,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\n type CarouselAnnouncementTrigger,\n useCarouselContext,\n} from \"./CarouselContext\";\nimport carouselSlidesCss from \"./CarouselSlides.css\";\nimport { createCustomSettle } from \"./createCustomSettle\";\nimport { getVisibleSlideDescription } from \"./getVisibleSlideDescription\";\nimport { getVisibleSlideIndexes } from \"./getVisibleSlideIndexes\";\n\nconst ANNOUNCEMENT_DURATION = 1200;\n\n/**\n * Props for the CarouselSlides component.\n */\nexport interface CarouselSlidesProps extends ComponentPropsWithoutRef<\"div\"> {}\n\nconst withBaseName = makePrefixer(\"saltCarouselSlides\");\n\nconst announceSlideChangesFrom: CarouselAnnouncementTrigger[] = [\n \"drag\",\n \"focus\",\n \"navigation\",\n];\n\nexport const CarouselSlides = forwardRef<HTMLDivElement, CarouselSlidesProps>(\n function CarouselSlides(\n { children, className, id, onKeyDown, ...rest },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-slides\",\n css: carouselSlidesCss,\n window: targetWindow,\n });\n const {\n disableSlideAnnouncements,\n announcementState,\n setAnnouncementState,\n emblaApi,\n emblaRef,\n carouselId,\n } = useCarouselContext();\n\n const containerRef = useRef<HTMLDivElement>(null);\n const forkedEmblaRef = useForkRef<HTMLDivElement>(ref, emblaRef);\n const carouselRef = useForkRef<HTMLDivElement>(\n forkedEmblaRef,\n containerRef,\n );\n\n const slideRefs = useRef<(HTMLDivElement | null)[]>([]);\n const [focusedSlideIndex, setFocusedSlideIndex] = useState<number>(-1);\n const [dragging, setDragging] = useState(false);\n const focusOnSettle = useRef<boolean>(false);\n\n const [stableScrollSnap, setStableScrollSnap] = useState<\n number | undefined\n >(undefined);\n\n const visibleSlideIndexes = getVisibleSlideIndexes(\n emblaApi,\n stableScrollSnap ?? 0,\n );\n const { announce } = useAriaAnnouncer();\n\n useEffect(() => {\n const handleSettle = (emblaApi: EmblaCarouselType) => {\n const selectedScrollSnap = emblaApi?.selectedScrollSnap() ?? 0;\n setStableScrollSnap(selectedScrollSnap);\n const numberOfSnaps = emblaApi?.scrollSnapList().length ?? 1;\n const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;\n const settledSlideIndex = Math.floor(\n selectedScrollSnap * numberOfSlidesPerSnap,\n );\n setFocusedSlideIndex(settledSlideIndex);\n };\n\n if (!emblaApi) {\n return;\n }\n\n const scrollCallback = createCustomSettle(handleSettle);\n const pointerDownCallback = () => {\n setAnnouncementState(\"drag\");\n };\n emblaApi.on(\"scroll\", scrollCallback);\n emblaApi.on(\"pointerDown\", pointerDownCallback);\n // Cleanup listener on component unmount\n return () => {\n emblaApi.off(\"scroll\", scrollCallback);\n emblaApi.off(\"pointerDown\", pointerDownCallback);\n };\n }, [emblaApi, setAnnouncementState]);\n\n useEffect(() => {\n if (!focusOnSettle.current || stableScrollSnap === undefined) {\n return;\n }\n slideRefs.current[focusedSlideIndex]?.focus();\n setAnnouncementState(\"focus\");\n focusOnSettle.current = false;\n }, [stableScrollSnap, focusedSlideIndex, setAnnouncementState]);\n\n useEffect(() => {\n const numberOfSnaps = emblaApi?.scrollSnapList().length ?? 1;\n const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;\n if (focusedSlideIndex >= 0) {\n const nearestScrollSnap = Math.floor(\n focusedSlideIndex / numberOfSlidesPerSnap,\n );\n if (emblaApi?.selectedScrollSnap() !== nearestScrollSnap) {\n emblaApi?.scrollTo(nearestScrollSnap);\n focusOnSettle.current = true;\n }\n } else if (focusedSlideIndex === -1) {\n const initialSnap = emblaApi?.selectedScrollSnap();\n const initialSlideIndex =\n initialSnap !== undefined\n ? Math.floor(initialSnap * numberOfSlidesPerSnap)\n : 0;\n setFocusedSlideIndex(initialSlideIndex);\n setStableScrollSnap(initialSnap);\n }\n }, [focusedSlideIndex, emblaApi]);\n\n useEffect(() => {\n if (disableSlideAnnouncements === false) {\n setAnnouncementState(undefined);\n }\n }, [disableSlideAnnouncements, setAnnouncementState]);\n\n useEffect(() => {\n if (\n stableScrollSnap === undefined ||\n disableSlideAnnouncements ||\n !announcementState ||\n announceSlideChangesFrom.indexOf(announcementState) === -1\n ) {\n return;\n }\n const announcement = getVisibleSlideDescription(\n emblaApi,\n stableScrollSnap,\n );\n announce(announcement, ANNOUNCEMENT_DURATION);\n }, [\n announce,\n announcementState,\n disableSlideAnnouncements,\n stableScrollSnap,\n emblaApi,\n ]);\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.repeat) {\n return;\n }\n const numberOfSnaps = emblaApi?.scrollSnapList().length ?? 1;\n const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;\n\n // Find the current snap\n const currentSnap = Math.floor(focusedSlideIndex / numberOfSlidesPerSnap);\n\n switch (event.key) {\n case \"ArrowLeft\": {\n event.preventDefault();\n const prevSnap = Math.max(currentSnap - 1, 0);\n const newIndex = prevSnap * numberOfSlidesPerSnap;\n setFocusedSlideIndex(newIndex);\n break;\n }\n case \"ArrowRight\": {\n event.preventDefault();\n const nextSnap = Math.min(currentSnap + 1, numberOfSnaps - 1);\n const newIndex = nextSnap * numberOfSlidesPerSnap;\n setFocusedSlideIndex(newIndex);\n break;\n }\n }\n onKeyDown?.(event);\n };\n\n const handleMouseDown: MouseEventHandler<HTMLDivElement> = (event) => {\n setDragging(true);\n rest.onMouseDown?.(event);\n };\n const handleMouseUp: MouseEventHandler<HTMLDivElement> = (event) => {\n setDragging(false);\n rest.onMouseUp?.(event);\n };\n\n return (\n <div\n onKeyDown={handleKeyDown}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n ref={carouselRef}\n className={clsx(\n withBaseName(),\n { [withBaseName(\"dragging\")]: dragging },\n className,\n )}\n {...rest}\n >\n <div\n className={withBaseName(\"container\")}\n id={id ?? `${carouselId}-slides`}\n >\n {Children.map(children, (child, index) => {\n const childElement = child as ReactElement;\n const existingId = childElement.props.id;\n const isFocused = focusedSlideIndex === index;\n const isHidden =\n !visibleSlideIndexes.includes(index + 1) && !isFocused;\n const element = child as ReactElement;\n return cloneElement(element, {\n \"aria-hidden\": isHidden,\n id: existingId ?? `${carouselId}-slide${index + 1}`,\n onFocus: (event: FocusEvent) => {\n setFocusedSlideIndex(index);\n element.props?.onFocus?.(event);\n },\n tabIndex: !isHidden ? 0 : -1,\n ref: (el: HTMLDivElement) => {\n slideRefs.current[index] = el;\n },\n });\n })}\n </div>\n </div>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","CarouselSlides","useWindow","useComponentCssInjection","carouselSlidesCss","useCarouselContext","useRef","useForkRef","useState","getVisibleSlideIndexes","useAriaAnnouncer","useEffect","emblaApi","createCustomSettle","getVisibleSlideDescription","jsx","clsx","Children","cloneElement"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var css_248z = ".saltCarouselTab {\n position: relative;\n width: var(--salt-size-selectable);\n height: var(--salt-size-selectable);\n border: none;\n box-shadow: inset 0 0 0 var(--salt-size-fixed-100) var(--salt-selectable-borderColor);\n -webkit-tap-highlight-color: var(--salt-selectable-borderColor-hover);\n -webkit-appearance: none;\n appearance: none;\n background-color: transparent;\n touch-action: manipulation;\n display: inline-flex;\n text-decoration: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n align-items: center;\n justify-content: center;\n border-radius: var(--salt-palette-corner-strongest);\n transition: box-shadow var(--salt-duration-perceptible) ease-in-out;\n}\n\n.saltCarouselTab:after {\n content: \"\";\n position: absolute;\n width: var(--salt-size-selectable);\n height: var(--salt-size-selectable);\n border-radius: 50%;\n background-color: transparent;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n\n.saltCarouselTab:focus-visible {\n outline-style: var(--salt-focused-outlineStyle);\n outline-width: var(--salt-focused-outlineWidth);\n outline-color: var(--salt-focused-outlineColor);\n outline-offset: var(--salt-focused-outlineOffset);\n background: var(--salt-selectable-background-hover);\n border-color: var(--salt-selectable-borderColor-hover);\n}\n\n.saltCarouselTab-selected {\n box-shadow: inset 0 0 0 var(--salt-size-selectable) var(--salt-selectable-borderColor-selected);\n}\n";
3
+ var css_248z = ".saltCarouselTab {\n position: relative;\n width: var(--salt-size-selectable);\n height: var(--salt-size-selectable);\n border: none;\n box-shadow: inset 0 0 0 var(--salt-size-fixed-100) var(--salt-selectable-borderColor);\n -webkit-tap-highlight-color: var(--salt-selectable-borderColor-hover);\n -webkit-appearance: none;\n appearance: none;\n background-color: transparent;\n touch-action: manipulation;\n display: inline-flex;\n text-decoration: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n align-items: center;\n justify-content: center;\n border-radius: var(--salt-palette-corner-strongest);\n transition: box-shadow var(--salt-duration-perceptible) ease-in-out;\n}\n\n.saltCarouselTab:after {\n content: \"\";\n position: absolute;\n width: var(--salt-size-selectable);\n height: var(--salt-size-selectable);\n border-radius: 50%;\n background-color: transparent;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n\n.saltCarouselTab:focus-visible {\n outline: var(--saltRadioButton-outline, var(--salt-focused-outline));\n outline-offset: var(--salt-spacing-fixed-100);\n}\n\n.saltCarouselTab-selected {\n box-shadow: inset 0 0 0 var(--salt-size-selectable) var(--salt-selectable-borderColor-selected);\n}\n";
4
4
 
5
5
  module.exports = css_248z;
6
6
  //# sourceMappingURL=CarouselTab.css.js.map
@@ -12,9 +12,6 @@ const withBaseName = core.makePrefixer("saltCarouselTab");
12
12
  const useCarouselTab = (emblaApi) => {
13
13
  const [selectedIndex, setSelectedIndex] = react.useState(0);
14
14
  const [scrollSnaps, setScrollSnaps] = react.useState([]);
15
- const handleClick = (index) => {
16
- emblaApi == null ? void 0 : emblaApi.scrollTo(index);
17
- };
18
15
  react.useEffect(() => {
19
16
  const handleInit = (emblaApi2) => {
20
17
  setScrollSnaps(emblaApi2.scrollSnapList());
@@ -34,8 +31,7 @@ const useCarouselTab = (emblaApi) => {
34
31
  }, [emblaApi]);
35
32
  return {
36
33
  selectedIndex,
37
- scrollSnaps,
38
- onClick: handleClick
34
+ scrollSnaps
39
35
  };
40
36
  };
41
37
  const CarouselTab = react.forwardRef(
@@ -1 +1 @@
1
- {"version":3,"file":"CarouselTab.js","sources":["../src/CarouselTab.tsx"],"sourcesContent":["import { makePrefixer } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport {\n type ComponentPropsWithRef,\n forwardRef,\n useEffect,\n useState,\n} from \"react\";\nimport carouselTabCss from \"./CarouselTab.css\";\n\n/**\n * Type definition for the UseCarouselTab hook.\n * Provides state and handlers for tablist navigation in a carousel.\n */\ntype UseCarouselTabProps = {\n /**\n * The index of the currently selected slide.\n */\n selectedIndex: number;\n\n /**\n * An array of scroll snap positions for the carousel slides.\n */\n scrollSnaps: number[];\n\n /**\n * Handler function for clicking a tab button to navigate to a specific slide.\n *\n * @param index - The index of the slide to navigate to.\n */\n onClick: (index: number) => void;\n};\n\nconst withBaseName = makePrefixer(\"saltCarouselTab\");\n\nexport const useCarouselTab = (\n emblaApi: EmblaCarouselType | undefined,\n): UseCarouselTabProps => {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);\n\n const handleClick = (index: number) => {\n emblaApi?.scrollTo(index);\n };\n\n useEffect(() => {\n const handleInit = (emblaApi: EmblaCarouselType) => {\n setScrollSnaps(emblaApi.scrollSnapList());\n };\n\n const handleSelect = (emblaApi: EmblaCarouselType) => {\n setSelectedIndex(emblaApi.selectedScrollSnap());\n };\n\n if (!emblaApi) return;\n\n handleInit(emblaApi);\n handleSelect(emblaApi);\n emblaApi\n .on(\"init\", handleInit)\n .on(\"reInit\", handleInit)\n .on(\"select\", handleSelect);\n // Cleanup listener on component unmount\n return () => {\n emblaApi.off(\"init\", handleInit);\n emblaApi.off(\"reInit\", handleInit);\n emblaApi.off(\"select\", handleSelect);\n };\n }, [emblaApi]);\n\n return {\n selectedIndex,\n scrollSnaps,\n onClick: handleClick,\n };\n};\n\n/**\n * Props for the CarouselTab component.\n */\nexport interface CarouselTabProps extends ComponentPropsWithRef<\"button\"> {\n /**\n * Is the selected slide\n */\n selected?: boolean;\n}\n\nexport const CarouselTab = forwardRef<HTMLButtonElement, CarouselTabProps>(\n function CarouselTab(\n { children, className, selected = false, ...rest },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-tab\",\n css: carouselTabCss,\n window: targetWindow,\n });\n\n return (\n <button\n className={clsx(\n withBaseName(),\n { [withBaseName(\"selected\")]: selected },\n className,\n )}\n ref={ref}\n {...rest}\n >\n {children}\n </button>\n );\n },\n);\n"],"names":["makePrefixer","useState","useEffect","emblaApi","forwardRef","CarouselTab","useWindow","useComponentCssInjection","carouselTabCss","jsx","clsx"],"mappings":";;;;;;;;;;AAoCA,MAAM,YAAA,GAAeA,kBAAa,iBAAiB,CAAA;AAEtC,MAAA,cAAA,GAAiB,CAC5B,QACwB,KAAA;AACxB,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIC,eAAS,CAAC,CAAA;AACpD,EAAA,MAAM,CAAC,WAAa,EAAA,cAAc,CAAI,GAAAA,cAAA,CAAmB,EAAE,CAAA;AAE3D,EAAM,MAAA,WAAA,GAAc,CAAC,KAAkB,KAAA;AACrC,IAAA,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,QAAS,CAAA,KAAA,CAAA;AAAA,GACrB;AAEA,EAAAC,eAAA,CAAU,MAAM;AACd,IAAM,MAAA,UAAA,GAAa,CAACC,SAAgC,KAAA;AAClD,MAAeA,cAAAA,CAAAA,SAAAA,CAAS,gBAAgB,CAAA;AAAA,KAC1C;AAEA,IAAM,MAAA,YAAA,GAAe,CAACA,SAAgC,KAAA;AACpD,MAAiBA,gBAAAA,CAAAA,SAAAA,CAAS,oBAAoB,CAAA;AAAA,KAChD;AAEA,IAAA,IAAI,CAAC,QAAU,EAAA;AAEf,IAAA,UAAA,CAAW,QAAQ,CAAA;AACnB,IAAA,YAAA,CAAa,QAAQ,CAAA;AACrB,IACG,QAAA,CAAA,EAAA,CAAG,MAAQ,EAAA,UAAU,CACrB,CAAA,EAAA,CAAG,UAAU,UAAU,CAAA,CACvB,EAAG,CAAA,QAAA,EAAU,YAAY,CAAA;AAE5B,IAAA,OAAO,MAAM;AACX,MAAS,QAAA,CAAA,GAAA,CAAI,QAAQ,UAAU,CAAA;AAC/B,MAAS,QAAA,CAAA,GAAA,CAAI,UAAU,UAAU,CAAA;AACjC,MAAS,QAAA,CAAA,GAAA,CAAI,UAAU,YAAY,CAAA;AAAA,KACrC;AAAA,GACF,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAO,OAAA;AAAA,IACL,aAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAS,EAAA;AAAA,GACX;AACF;AAYO,MAAM,WAAc,GAAAC,gBAAA;AAAA,EACzB,SAASC,YACP,CAAA,EAAE,QAAU,EAAA,SAAA,EAAW,WAAW,KAAO,EAAA,GAAG,IAAK,EAAA,EACjD,GACA,EAAA;AACA,IAAA,MAAM,eAAeC,gBAAU,EAAA;AAC/B,IAAyBC,+BAAA,CAAA;AAAA,MACvB,MAAQ,EAAA,mBAAA;AAAA,MACR,GAAK,EAAAC,aAAA;AAAA,MACL,MAAQ,EAAA;AAAA,KACT,CAAA;AAED,IACE,uBAAAC,cAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAW,EAAAC,SAAA;AAAA,UACT,YAAa,EAAA;AAAA,UACb,EAAE,CAAC,YAAA,CAAa,UAAU,CAAC,GAAG,QAAS,EAAA;AAAA,UACvC;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG,IAAA;AAAA,QAEH;AAAA;AAAA,KACH;AAAA;AAGN;;;;;"}
1
+ {"version":3,"file":"CarouselTab.js","sources":["../src/CarouselTab.tsx"],"sourcesContent":["import { makePrefixer } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport type { EmblaCarouselType } from \"embla-carousel\";\nimport {\n type ComponentPropsWithRef,\n forwardRef,\n useEffect,\n useState,\n} from \"react\";\nimport carouselTabCss from \"./CarouselTab.css\";\n\n/**\n * Type definition for the UseCarouselTab hook.\n * Provides state and handlers for tablist navigation in a carousel.\n */\ntype UseCarouselTabProps = {\n /**\n * The index of the currently selected slide.\n */\n selectedIndex: number;\n\n /**\n * An array of scroll snap positions for the carousel slides.\n */\n scrollSnaps: number[];\n};\n\nconst withBaseName = makePrefixer(\"saltCarouselTab\");\n\nexport const useCarouselTab = (\n emblaApi: EmblaCarouselType | undefined,\n): UseCarouselTabProps => {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);\n\n useEffect(() => {\n const handleInit = (emblaApi: EmblaCarouselType) => {\n setScrollSnaps(emblaApi.scrollSnapList());\n };\n\n const handleSelect = (emblaApi: EmblaCarouselType) => {\n setSelectedIndex(emblaApi.selectedScrollSnap());\n };\n\n if (!emblaApi) return;\n\n handleInit(emblaApi);\n handleSelect(emblaApi);\n emblaApi\n .on(\"init\", handleInit)\n .on(\"reInit\", handleInit)\n .on(\"select\", handleSelect);\n // Cleanup listener on component unmount\n return () => {\n emblaApi.off(\"init\", handleInit);\n emblaApi.off(\"reInit\", handleInit);\n emblaApi.off(\"select\", handleSelect);\n };\n }, [emblaApi]);\n\n return {\n selectedIndex,\n scrollSnaps,\n };\n};\n\n/**\n * Props for the CarouselTab component.\n */\nexport interface CarouselTabProps extends ComponentPropsWithRef<\"button\"> {\n /**\n * Is the selected slide\n */\n selected?: boolean;\n}\n\nexport const CarouselTab = forwardRef<HTMLButtonElement, CarouselTabProps>(\n function CarouselTab(\n { children, className, selected = false, ...rest },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-tab\",\n css: carouselTabCss,\n window: targetWindow,\n });\n\n return (\n <button\n className={clsx(\n withBaseName(),\n { [withBaseName(\"selected\")]: selected },\n className,\n )}\n ref={ref}\n {...rest}\n >\n {children}\n </button>\n );\n },\n);\n"],"names":["makePrefixer","useState","useEffect","emblaApi","forwardRef","CarouselTab","useWindow","useComponentCssInjection","carouselTabCss","jsx","clsx"],"mappings":";;;;;;;;;;AA6BA,MAAM,YAAA,GAAeA,kBAAa,iBAAiB,CAAA;AAE5C,MAAM,cAAA,GAAiB,CAC5B,QAAA,KACwB;AACxB,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIC,eAAS,CAAC,CAAA;AACpD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,cAAA,CAAmB,EAAE,CAAA;AAE3D,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAa,CAACC,SAAAA,KAAgC;AAClD,MAAA,cAAA,CAAeA,SAAAA,CAAS,gBAAgB,CAAA;AAAA,IAC1C,CAAA;AAEA,IAAA,MAAM,YAAA,GAAe,CAACA,SAAAA,KAAgC;AACpD,MAAA,gBAAA,CAAiBA,SAAAA,CAAS,oBAAoB,CAAA;AAAA,IAChD,CAAA;AAEA,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,UAAA,CAAW,QAAQ,CAAA;AACnB,IAAA,YAAA,CAAa,QAAQ,CAAA;AACrB,IAAA,QAAA,CACG,EAAA,CAAG,MAAA,EAAQ,UAAU,CAAA,CACrB,EAAA,CAAG,UAAU,UAAU,CAAA,CACvB,EAAA,CAAG,QAAA,EAAU,YAAY,CAAA;AAE5B,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,GAAA,CAAI,QAAQ,UAAU,CAAA;AAC/B,MAAA,QAAA,CAAS,GAAA,CAAI,UAAU,UAAU,CAAA;AACjC,MAAA,QAAA,CAAS,GAAA,CAAI,UAAU,YAAY,CAAA;AAAA,IACrC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,OAAO;AAAA,IACL,aAAA;AAAA,IACA;AAAA,GACF;AACF;AAYO,MAAM,WAAA,GAAcC,gBAAA;AAAA,EACzB,SAASC,YAAAA,CACP,EAAE,QAAA,EAAU,SAAA,EAAW,WAAW,KAAA,EAAO,GAAG,IAAA,EAAK,EACjD,GAAA,EACA;AACA,IAAA,MAAM,eAAeC,gBAAA,EAAU;AAC/B,IAAAC,+BAAA,CAAyB;AAAA,MACvB,MAAA,EAAQ,mBAAA;AAAA,MACR,GAAA,EAAKC,aAAA;AAAA,MACL,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,uBACEC,cAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAWC,SAAA;AAAA,UACT,YAAA,EAAa;AAAA,UACb,EAAE,CAAC,YAAA,CAAa,UAAU,CAAC,GAAG,QAAA,EAAS;AAAA,UACvC;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG,IAAA;AAAA,QAEH;AAAA;AAAA,KACH;AAAA,EAEJ;AACF;;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var css_248z = ".saltCarouselTabList {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n gap: var(--salt-spacing-200);\n}\n\n.saltCarouselTabList:focus-visible {\n outline-style: var(--salt-focused-outlineStyle);\n outline-width: var(--salt-focused-outlineWidth);\n outline-offset: var(--salt-focused-outlineOffset);\n outline-color: var(--salt-focused-outlineColor);\n}\n";
3
+ var css_248z = ".saltCarouselTabList {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n gap: var(--salt-spacing-200);\n}\n";
4
4
 
5
5
  module.exports = css_248z;
6
6
  //# sourceMappingURL=CarouselTabList.css.js.map
@@ -9,6 +9,8 @@ var react = require('react');
9
9
  var CarouselContext = require('./CarouselContext.js');
10
10
  var CarouselTab = require('./CarouselTab.js');
11
11
  var CarouselTabList$1 = require('./CarouselTabList.css.js');
12
+ var getSlideDescription = require('./getSlideDescription.js');
13
+ var getVisibleSlideIndexes = require('./getVisibleSlideIndexes.js');
12
14
 
13
15
  const withBaseName = core.makePrefixer("saltCarouselTabList");
14
16
  const CarouselTabRenderer = react.forwardRef((props, ref) => {
@@ -22,67 +24,85 @@ const CarouselTabList = react.forwardRef(
22
24
  css: CarouselTabList$1,
23
25
  window: targetWindow
24
26
  });
25
- const { emblaApi } = CarouselContext.useCarouselContext();
26
- const { selectedIndex, scrollSnaps, onClick } = CarouselTab.useCarouselTab(emblaApi);
27
- const slideNodes = emblaApi == null ? void 0 : emblaApi.slideNodes();
28
- const numberOfSlides = (slideNodes == null ? void 0 : slideNodes.length) ?? 0;
29
- const slidesPerTransition = numberOfSlides ? Math.ceil(numberOfSlides / scrollSnaps.length) : 0;
27
+ const { emblaApi, setAriaVariant, setAnnouncementState } = CarouselContext.useCarouselContext();
28
+ const { selectedIndex, scrollSnaps } = CarouselTab.useCarouselTab(emblaApi);
30
29
  const buttonRefs = react.useRef([]);
30
+ const [focusedTabIndex, setFocusedTabIndex] = react.useState(null);
31
31
  const handleKeyDown = (event) => {
32
32
  var _a;
33
- let newIndex = selectedIndex;
33
+ let newIndex = focusedTabIndex ?? selectedIndex;
34
34
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
35
+ event.preventDefault();
35
36
  const direction = event.key === "ArrowLeft" ? -1 : 1;
36
- newIndex = (selectedIndex + direction + scrollSnaps.length) % scrollSnaps.length;
37
+ newIndex = Math.max(
38
+ 0,
39
+ Math.min(newIndex + direction, scrollSnaps.length - 1)
40
+ );
37
41
  } else if (event.key === "Home") {
42
+ event.preventDefault();
38
43
  newIndex = 0;
39
44
  } else if (event.key === "End") {
45
+ event.preventDefault();
40
46
  newIndex = scrollSnaps.length - 1;
41
47
  }
42
- if (newIndex !== selectedIndex) {
43
- onClick(newIndex);
48
+ if (newIndex !== focusedTabIndex) {
44
49
  (_a = buttonRefs.current[newIndex]) == null ? void 0 : _a.focus();
45
- event.preventDefault();
46
- event.stopPropagation();
50
+ setFocusedTabIndex(newIndex);
47
51
  }
48
52
  onKeyDown == null ? void 0 : onKeyDown(event);
49
53
  };
54
+ react.useEffect(() => {
55
+ setAriaVariant("tabpanel");
56
+ }, [setAriaVariant]);
50
57
  return /* @__PURE__ */ jsxRuntime.jsx(
51
58
  "div",
52
59
  {
53
60
  role: "tablist",
54
- "aria-label": "Choose slide",
55
- tabIndex: 0,
56
61
  className: clsx.clsx(withBaseName(), className),
57
62
  onKeyDown: handleKeyDown,
58
63
  ref,
59
64
  ...rest,
60
- children: scrollSnaps.map((_, tabIndex) => {
61
- const startSlideNumber = tabIndex * slidesPerTransition + 1;
62
- const endSlideNumber = Math.min(
63
- startSlideNumber + slidesPerTransition - 1,
64
- numberOfSlides
65
+ children: scrollSnaps.map((_, scrollSnapIndex) => {
66
+ const visibleSlides = getVisibleSlideIndexes.getVisibleSlideIndexes(
67
+ emblaApi,
68
+ scrollSnapIndex
65
69
  );
66
- const label = startSlideNumber === endSlideNumber ? `Slide ${startSlideNumber}` : `Slides ${startSlideNumber}-${endSlideNumber} of ${numberOfSlides}`;
67
- const selected = selectedIndex === tabIndex;
70
+ const startSlideNumber = visibleSlides.length >= 1 ? visibleSlides[0] : 0;
71
+ const endSlideNumber = visibleSlides.length > 1 ? visibleSlides[visibleSlides.length - 1] : 0;
72
+ const slideNodes = emblaApi == null ? void 0 : emblaApi.slideNodes();
73
+ const numberOfSlides = slideNodes == null ? void 0 : slideNodes.length;
74
+ let ariaLabel;
75
+ if (endSlideNumber >= 1) {
76
+ ariaLabel = `Slide ${startSlideNumber} to ${endSlideNumber} of ${numberOfSlides}`;
77
+ } else {
78
+ const description = getSlideDescription.getSlideDescription(emblaApi, startSlideNumber);
79
+ ariaLabel = `${description}`;
80
+ }
81
+ const selected = selectedIndex === scrollSnapIndex;
68
82
  const ariaControls = (slideNodes == null ? void 0 : slideNodes.length) ? slideNodes[startSlideNumber - 1].id : void 0;
69
83
  return /* @__PURE__ */ jsxRuntime.jsx(
70
84
  CarouselTabRenderer,
71
85
  {
72
86
  ref: (element) => {
73
- buttonRefs.current[tabIndex] = element;
87
+ buttonRefs.current[scrollSnapIndex] = element;
74
88
  },
75
89
  render,
76
90
  role: "tab",
77
- onClick: () => onClick(tabIndex),
78
- "aria-selected": selected,
79
91
  selected,
80
- tabIndex: selected ? 0 : -1,
81
- "aria-label": label,
82
- "aria-labelledby": ariaControls,
92
+ onBlur: () => {
93
+ setFocusedTabIndex(null);
94
+ },
95
+ onFocus: () => {
96
+ setFocusedTabIndex(scrollSnapIndex);
97
+ setAnnouncementState("tab");
98
+ emblaApi == null ? void 0 : emblaApi.scrollTo(scrollSnapIndex);
99
+ },
100
+ "aria-label": ariaLabel,
101
+ "aria-selected": selected,
102
+ tabIndex: selected && focusedTabIndex === null ? 0 : -1,
83
103
  "aria-controls": ariaControls
84
104
  },
85
- `carouselTab-${tabIndex}}`
105
+ `carouselTab-${scrollSnapIndex}}`
86
106
  );
87
107
  })
88
108
  }