@salt-ds/embla-carousel 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/css/salt-embla-carousel.css +42 -22
  3. package/dist-cjs/Carousel.js +30 -9
  4. package/dist-cjs/Carousel.js.map +1 -1
  5. package/dist-cjs/CarouselAutoplayIndicator.js.map +1 -1
  6. package/dist-cjs/CarouselAutoplayIndicatorSVG.js.map +1 -1
  7. package/dist-cjs/CarouselCard.css.js +1 -1
  8. package/dist-cjs/CarouselCard.js +18 -8
  9. package/dist-cjs/CarouselCard.js.map +1 -1
  10. package/dist-cjs/CarouselContext.js.map +1 -1
  11. package/dist-cjs/CarouselNextButton.js +4 -1
  12. package/dist-cjs/CarouselNextButton.js.map +1 -1
  13. package/dist-cjs/CarouselPreviousButton.js +4 -1
  14. package/dist-cjs/CarouselPreviousButton.js.map +1 -1
  15. package/dist-cjs/CarouselProgressLabel.js +11 -33
  16. package/dist-cjs/CarouselProgressLabel.js.map +1 -1
  17. package/dist-cjs/CarouselSlides.css.js +1 -1
  18. package/dist-cjs/CarouselSlides.js +119 -23
  19. package/dist-cjs/CarouselSlides.js.map +1 -1
  20. package/dist-cjs/CarouselTab.css.js +1 -1
  21. package/dist-cjs/CarouselTab.js +1 -5
  22. package/dist-cjs/CarouselTab.js.map +1 -1
  23. package/dist-cjs/CarouselTabList.css.js +1 -1
  24. package/dist-cjs/CarouselTabList.js +41 -27
  25. package/dist-cjs/CarouselTabList.js.map +1 -1
  26. package/dist-cjs/createCustomSettle.js.map +1 -1
  27. package/dist-cjs/getSlideDescription.js +32 -0
  28. package/dist-cjs/getSlideDescription.js.map +1 -0
  29. package/dist-cjs/getVisibleSlideDescription.js +26 -0
  30. package/dist-cjs/getVisibleSlideDescription.js.map +1 -0
  31. package/dist-cjs/getVisibleSlideIndexes.js +33 -0
  32. package/dist-cjs/getVisibleSlideIndexes.js.map +1 -0
  33. package/dist-cjs/index.js +0 -3
  34. package/dist-cjs/index.js.map +1 -1
  35. package/dist-cjs/usePrevNextButtons.js +3 -1
  36. package/dist-cjs/usePrevNextButtons.js.map +1 -1
  37. package/dist-es/Carousel.js +32 -11
  38. package/dist-es/Carousel.js.map +1 -1
  39. package/dist-es/CarouselAutoplayIndicator.js.map +1 -1
  40. package/dist-es/CarouselAutoplayIndicatorSVG.js.map +1 -1
  41. package/dist-es/CarouselCard.css.js +1 -1
  42. package/dist-es/CarouselCard.js +18 -8
  43. package/dist-es/CarouselCard.js.map +1 -1
  44. package/dist-es/CarouselContext.js.map +1 -1
  45. package/dist-es/CarouselNextButton.js +4 -1
  46. package/dist-es/CarouselNextButton.js.map +1 -1
  47. package/dist-es/CarouselPreviousButton.js +4 -1
  48. package/dist-es/CarouselPreviousButton.js.map +1 -1
  49. package/dist-es/CarouselProgressLabel.js +12 -34
  50. package/dist-es/CarouselProgressLabel.js.map +1 -1
  51. package/dist-es/CarouselSlides.css.js +1 -1
  52. package/dist-es/CarouselSlides.js +121 -25
  53. package/dist-es/CarouselSlides.js.map +1 -1
  54. package/dist-es/CarouselTab.css.js +1 -1
  55. package/dist-es/CarouselTab.js +1 -5
  56. package/dist-es/CarouselTab.js.map +1 -1
  57. package/dist-es/CarouselTabList.css.js +1 -1
  58. package/dist-es/CarouselTabList.js +42 -28
  59. package/dist-es/CarouselTabList.js.map +1 -1
  60. package/dist-es/createCustomSettle.js.map +1 -1
  61. package/dist-es/getSlideDescription.js +30 -0
  62. package/dist-es/getSlideDescription.js.map +1 -0
  63. package/dist-es/getVisibleSlideDescription.js +24 -0
  64. package/dist-es/getVisibleSlideDescription.js.map +1 -0
  65. package/dist-es/getVisibleSlideIndexes.js +30 -0
  66. package/dist-es/getVisibleSlideIndexes.js.map +1 -0
  67. package/dist-es/index.js +0 -1
  68. package/dist-es/index.js.map +1 -1
  69. package/dist-es/usePrevNextButtons.js +3 -1
  70. package/dist-es/usePrevNextButtons.js.map +1 -1
  71. package/dist-types/Carousel.d.ts +7 -1
  72. package/dist-types/CarouselContext.d.ts +31 -0
  73. package/dist-types/CarouselTab.d.ts +0 -6
  74. package/dist-types/CarouselTabList.d.ts +4 -0
  75. package/dist-types/getSlideDescription.d.ts +7 -0
  76. package/dist-types/getVisibleSlideDescription.d.ts +7 -0
  77. package/dist-types/getVisibleSlideIndexes.d.ts +8 -0
  78. package/dist-types/index.d.ts +0 -1
  79. package/package.json +2 -2
  80. package/dist-cjs/CarouselAnnouncementPlugin.js +0 -52
  81. package/dist-cjs/CarouselAnnouncementPlugin.js.map +0 -1
  82. package/dist-cjs/CarouselProgressLabel.css.js +0 -6
  83. package/dist-cjs/CarouselProgressLabel.css.js.map +0 -1
  84. package/dist-es/CarouselAnnouncementPlugin.js +0 -49
  85. package/dist-es/CarouselAnnouncementPlugin.js.map +0 -1
  86. package/dist-es/CarouselProgressLabel.css.js +0 -4
  87. package/dist-es/CarouselProgressLabel.css.js.map +0 -1
  88. package/dist-types/CarouselAnnouncementPlugin.d.ts +0 -24
@@ -1,51 +1,104 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { makePrefixer, useForkRef } from '@salt-ds/core';
2
+ import { makePrefixer, useForkRef, useAriaAnnouncer } from '@salt-ds/core';
3
3
  import { useComponentCssInjection } from '@salt-ds/styles';
4
4
  import { useWindow } from '@salt-ds/window';
5
5
  import { clsx } from 'clsx';
6
- import { forwardRef, useRef, useEffect } from 'react';
6
+ import { forwardRef, useRef, useState, useEffect, useLayoutEffect, Children, cloneElement } from 'react';
7
7
  import { useCarouselContext } from './CarouselContext.js';
8
8
  import css_248z from './CarouselSlides.css.js';
9
9
  import { createCustomSettle } from './createCustomSettle.js';
10
+ import { getVisibleSlideDescription } from './getVisibleSlideDescription.js';
11
+ import { getVisibleSlideIndexes } from './getVisibleSlideIndexes.js';
10
12
 
13
+ const ANNOUNCEMENT_DURATION = 1200;
11
14
  const withBaseName = makePrefixer("saltCarouselSlides");
15
+ const announceSlideChangesFrom = [
16
+ "drag",
17
+ "navigation"
18
+ ];
12
19
  const CarouselSlides = forwardRef(
13
- function CarouselSlides2({ children, className, onKeyDown, ...rest }, ref) {
20
+ function CarouselSlides2({ children, className, id, onKeyDown, ...rest }, ref) {
14
21
  const targetWindow = useWindow();
15
22
  useComponentCssInjection({
16
23
  testId: "salt-carousel-slides",
17
24
  css: css_248z,
18
25
  window: targetWindow
19
26
  });
20
- const { emblaApi, emblaRef } = useCarouselContext();
27
+ const {
28
+ disableSlideAnnouncements,
29
+ announcementState,
30
+ setAnnouncementState,
31
+ emblaApi,
32
+ emblaRef,
33
+ carouselId
34
+ } = useCarouselContext();
21
35
  const carouselRef = useForkRef(ref, emblaRef);
22
- const usingArrowNavigation = useRef();
36
+ const slideRefs = useRef([]);
37
+ const [focusSlideIndex, setFocusedSlideIndex] = useState(-1);
38
+ const [dragging, setDragging] = useState(false);
39
+ const [stableScrollSnap, setStableScrollSnap] = useState(void 0);
40
+ const visibleSlideIndexes = getVisibleSlideIndexes(
41
+ emblaApi,
42
+ stableScrollSnap ?? 0
43
+ );
44
+ const { announce } = useAriaAnnouncer();
23
45
  useEffect(() => {
24
46
  const handleSettle = (emblaApi2) => {
25
- if (!usingArrowNavigation.current) {
26
- return;
27
- }
28
- const slideIndexInView = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
29
- const snappedSlide = emblaApi2.slideNodes()[slideIndexInView];
30
- if (snappedSlide) {
31
- const focusableElements = snappedSlide.querySelectorAll(
32
- 'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
33
- );
34
- if (focusableElements.length > 0) {
35
- focusableElements[0].focus();
36
- }
37
- }
38
- usingArrowNavigation.current = false;
47
+ const selectedScrollSnap = (emblaApi2 == null ? void 0 : emblaApi2.selectedScrollSnap()) ?? 0;
48
+ setStableScrollSnap(selectedScrollSnap);
39
49
  };
40
50
  if (!emblaApi) {
41
51
  return;
42
52
  }
43
53
  const scrollCallback = createCustomSettle(handleSettle);
54
+ const pointerDownCallback = () => {
55
+ setAnnouncementState("drag");
56
+ };
44
57
  emblaApi.on("scroll", scrollCallback);
58
+ emblaApi.on("pointerDown", pointerDownCallback);
45
59
  return () => {
46
60
  emblaApi.off("scroll", scrollCallback);
61
+ emblaApi.off("pointerDown", pointerDownCallback);
47
62
  };
48
63
  }, [emblaApi]);
64
+ useLayoutEffect(() => {
65
+ if (focusSlideIndex >= 0) {
66
+ const numberOfSnaps = (emblaApi == null ? void 0 : emblaApi.scrollSnapList().length) ?? 1;
67
+ const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;
68
+ const nearestScrollSnap = Math.floor(
69
+ focusSlideIndex / numberOfSlidesPerSnap
70
+ );
71
+ if ((emblaApi == null ? void 0 : emblaApi.selectedScrollSnap()) !== nearestScrollSnap) {
72
+ setAnnouncementState("focus");
73
+ emblaApi == null ? void 0 : emblaApi.scrollTo(nearestScrollSnap);
74
+ }
75
+ setTimeout(() => {
76
+ var _a;
77
+ (_a = slideRefs.current[focusSlideIndex]) == null ? void 0 : _a.focus();
78
+ }, 0);
79
+ }
80
+ }, [focusSlideIndex, emblaApi, setAnnouncementState]);
81
+ useEffect(() => {
82
+ if (disableSlideAnnouncements === false) {
83
+ setAnnouncementState(void 0);
84
+ }
85
+ }, [disableSlideAnnouncements]);
86
+ useEffect(() => {
87
+ if (stableScrollSnap === void 0 || disableSlideAnnouncements || !announcementState || announceSlideChangesFrom.indexOf(announcementState) === -1) {
88
+ return;
89
+ }
90
+ const announcement = getVisibleSlideDescription(
91
+ emblaApi,
92
+ stableScrollSnap
93
+ );
94
+ announce(announcement, ANNOUNCEMENT_DURATION);
95
+ }, [
96
+ announce,
97
+ announcementState,
98
+ disableSlideAnnouncements,
99
+ stableScrollSnap,
100
+ emblaApi
101
+ ]);
49
102
  const handleKeyDown = (event) => {
50
103
  if (event.repeat) {
51
104
  return;
@@ -53,27 +106,70 @@ const CarouselSlides = forwardRef(
53
106
  switch (event.key) {
54
107
  case "ArrowLeft": {
55
108
  event.preventDefault();
56
- emblaApi == null ? void 0 : emblaApi.scrollPrev();
57
- usingArrowNavigation.current = true;
109
+ setFocusedSlideIndex((prevState) => Math.max(prevState - 1, 0));
58
110
  break;
59
111
  }
60
112
  case "ArrowRight": {
61
113
  event.preventDefault();
62
- emblaApi == null ? void 0 : emblaApi.scrollNext();
63
- usingArrowNavigation.current = true;
114
+ setFocusedSlideIndex(
115
+ (prevState) => Math.min(prevState + 1, slideRefs.current.length - 1)
116
+ );
64
117
  break;
65
118
  }
66
119
  }
67
120
  onKeyDown == null ? void 0 : onKeyDown(event);
68
121
  };
122
+ const handleMouseDown = (event) => {
123
+ var _a;
124
+ setDragging(true);
125
+ (_a = rest.onMouseDown) == null ? void 0 : _a.call(rest, event);
126
+ };
127
+ const handleMouseUp = (event) => {
128
+ var _a;
129
+ setDragging(false);
130
+ (_a = rest.onMouseUp) == null ? void 0 : _a.call(rest, event);
131
+ };
69
132
  return /* @__PURE__ */ jsx(
70
133
  "div",
71
134
  {
72
135
  onKeyDown: handleKeyDown,
136
+ onMouseDown: handleMouseDown,
137
+ onMouseUp: handleMouseUp,
73
138
  ref: carouselRef,
74
- className: clsx(withBaseName(), className),
139
+ className: clsx(
140
+ withBaseName(),
141
+ { [withBaseName("dragging")]: dragging },
142
+ className
143
+ ),
75
144
  ...rest,
76
- children: /* @__PURE__ */ jsx("div", { className: withBaseName("container"), children })
145
+ children: /* @__PURE__ */ jsx(
146
+ "div",
147
+ {
148
+ className: withBaseName("container"),
149
+ id: id ?? `${carouselId}-slides`,
150
+ children: Children.map(children, (child, index) => {
151
+ const childElement = child;
152
+ const existingId = childElement.props.id;
153
+ const isHidden = !visibleSlideIndexes.includes(index + 1);
154
+ const element = child;
155
+ return cloneElement(element, {
156
+ "aria-hidden": isHidden,
157
+ id: existingId ?? `${carouselId}-slide${index + 1}`,
158
+ onMouseDown: (event) => event.preventDefault(),
159
+ onFocus: (event) => {
160
+ var _a, _b;
161
+ event.preventDefault();
162
+ setFocusedSlideIndex(index);
163
+ (_b = (_a = element.props) == null ? void 0 : _a.onFocus) == null ? void 0 : _b.call(_a, event);
164
+ },
165
+ tabIndex: !isHidden ? 0 : -1,
166
+ ref: (el) => {
167
+ slideRefs.current[index] = el;
168
+ }
169
+ });
170
+ })
171
+ }
172
+ )
77
173
  }
78
174
  );
79
175
  }
@@ -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":["CarouselSlides","carouselSlidesCss","emblaApi"],"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 forwardRef,\n type KeyboardEvent,\n type MouseEventHandler,\n type SyntheticEvent,\n useEffect,\n useLayoutEffect,\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 \"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 carouselRef = useForkRef<HTMLDivElement>(ref, emblaRef);\n\n const slideRefs = useRef<(HTMLDivElement | null)[]>([]);\n const [focusSlideIndex, setFocusedSlideIndex] = useState<number>(-1);\n const [dragging, setDragging] = useState(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 };\n\n if (!emblaApi) {\n return;\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]);\n\n useLayoutEffect(() => {\n if (focusSlideIndex >= 0) {\n const numberOfSnaps = emblaApi?.scrollSnapList().length ?? 1;\n const numberOfSlidesPerSnap = slideRefs.current.length / numberOfSnaps;\n const nearestScrollSnap = Math.floor(\n focusSlideIndex / numberOfSlidesPerSnap,\n );\n if (emblaApi?.selectedScrollSnap() !== nearestScrollSnap) {\n setAnnouncementState(\"focus\");\n emblaApi?.scrollTo(nearestScrollSnap);\n }\n setTimeout(() => {\n slideRefs.current[focusSlideIndex]?.focus();\n }, 0);\n }\n }, [focusSlideIndex, emblaApi, setAnnouncementState]);\n\n useEffect(() => {\n if (disableSlideAnnouncements === false) {\n setAnnouncementState(undefined);\n }\n }, [disableSlideAnnouncements]);\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 switch (event.key) {\n case \"ArrowLeft\": {\n event.preventDefault();\n setFocusedSlideIndex((prevState) => Math.max(prevState - 1, 0));\n break;\n }\n case \"ArrowRight\": {\n event.preventDefault();\n setFocusedSlideIndex((prevState) =>\n Math.min(prevState + 1, slideRefs.current.length - 1),\n );\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 React.ReactElement;\n const existingId = childElement.props.id;\n const isHidden = !visibleSlideIndexes.includes(index + 1);\n const element = child as React.ReactElement;\n return cloneElement(element, {\n \"aria-hidden\": isHidden,\n id: existingId ?? `${carouselId}-slide${index + 1}`,\n onMouseDown: (event: SyntheticEvent) => event.preventDefault(),\n onFocus: (event: FocusEvent) => {\n event.preventDefault();\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":["CarouselSlides","carouselSlidesCss","emblaApi"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- 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";
1
+ 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";
2
2
 
3
3
  export { css_248z as default };
4
4
  //# sourceMappingURL=CarouselTab.css.js.map
@@ -10,9 +10,6 @@ const withBaseName = makePrefixer("saltCarouselTab");
10
10
  const useCarouselTab = (emblaApi) => {
11
11
  const [selectedIndex, setSelectedIndex] = useState(0);
12
12
  const [scrollSnaps, setScrollSnaps] = useState([]);
13
- const handleClick = (index) => {
14
- emblaApi == null ? void 0 : emblaApi.scrollTo(index);
15
- };
16
13
  useEffect(() => {
17
14
  const handleInit = (emblaApi2) => {
18
15
  setScrollSnaps(emblaApi2.scrollSnapList());
@@ -32,8 +29,7 @@ const useCarouselTab = (emblaApi) => {
32
29
  }, [emblaApi]);
33
30
  return {
34
31
  selectedIndex,
35
- scrollSnaps,
36
- onClick: handleClick
32
+ scrollSnaps
37
33
  };
38
34
  };
39
35
  const CarouselTab = 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":["emblaApi","CarouselTab","carouselTabCss"],"mappings":";;;;;;;;AAoCA,MAAM,YAAA,GAAe,aAAa,iBAAiB,CAAA;AAEtC,MAAA,cAAA,GAAiB,CAC5B,QACwB,KAAA;AACxB,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,CAAC,CAAA;AACpD,EAAA,MAAM,CAAC,WAAa,EAAA,cAAc,CAAI,GAAA,QAAA,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,EAAA,SAAA,CAAU,MAAM;AACd,IAAM,MAAA,UAAA,GAAa,CAACA,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,GAAA,UAAA;AAAA,EACzB,SAASC,YACP,CAAA,EAAE,QAAU,EAAA,SAAA,EAAW,WAAW,KAAO,EAAA,GAAG,IAAK,EAAA,EACjD,GACA,EAAA;AACA,IAAA,MAAM,eAAe,SAAU,EAAA;AAC/B,IAAyB,wBAAA,CAAA;AAAA,MACvB,MAAQ,EAAA,mBAAA;AAAA,MACR,GAAK,EAAAC,QAAA;AAAA,MACL,MAAQ,EAAA;AAAA,KACT,CAAA;AAED,IACE,uBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAW,EAAA,IAAA;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":["emblaApi","CarouselTab","carouselTabCss"],"mappings":";;;;;;;;AA6BA,MAAM,YAAA,GAAe,aAAa,iBAAiB,CAAA;AAE5C,MAAM,cAAA,GAAiB,CAC5B,QAAA,KACwB;AACxB,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,CAAC,CAAA;AACpD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA,CAAmB,EAAE,CAAA;AAE3D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAa,CAACA,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,GAAc,UAAA;AAAA,EACzB,SAASC,YAAAA,CACP,EAAE,QAAA,EAAU,SAAA,EAAW,WAAW,KAAA,EAAO,GAAG,IAAA,EAAK,EACjD,GAAA,EACA;AACA,IAAA,MAAM,eAAe,SAAA,EAAU;AAC/B,IAAA,wBAAA,CAAyB;AAAA,MACvB,MAAA,EAAQ,mBAAA;AAAA,MACR,GAAA,EAAKC,QAAA;AAAA,MACL,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,uBACE,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,IAAA;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,4 +1,4 @@
1
- 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";
1
+ 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";
2
2
 
3
3
  export { css_248z as default };
4
4
  //# sourceMappingURL=CarouselTabList.css.js.map
@@ -3,10 +3,12 @@ import { makePrefixer, renderProps } from '@salt-ds/core';
3
3
  import { useComponentCssInjection } from '@salt-ds/styles';
4
4
  import { useWindow } from '@salt-ds/window';
5
5
  import { clsx } from 'clsx';
6
- import { forwardRef, useRef } from 'react';
6
+ import { forwardRef, useRef, useState, useEffect } from 'react';
7
7
  import { useCarouselContext } from './CarouselContext.js';
8
8
  import { CarouselTab, useCarouselTab } from './CarouselTab.js';
9
9
  import css_248z from './CarouselTabList.css.js';
10
+ import { getSlideDescription } from './getSlideDescription.js';
11
+ import { getVisibleSlideIndexes } from './getVisibleSlideIndexes.js';
10
12
 
11
13
  const withBaseName = makePrefixer("saltCarouselTabList");
12
14
  const CarouselTabRenderer = forwardRef((props, ref) => {
@@ -20,67 +22,79 @@ const CarouselTabList = forwardRef(
20
22
  css: css_248z,
21
23
  window: targetWindow
22
24
  });
23
- const { emblaApi } = useCarouselContext();
24
- const { selectedIndex, scrollSnaps, onClick } = useCarouselTab(emblaApi);
25
- const slideNodes = emblaApi == null ? void 0 : emblaApi.slideNodes();
26
- const numberOfSlides = (slideNodes == null ? void 0 : slideNodes.length) ?? 0;
27
- const slidesPerTransition = numberOfSlides ? Math.ceil(numberOfSlides / scrollSnaps.length) : 0;
25
+ const { emblaApi, setAriaVariant, setAnnouncementState } = useCarouselContext();
26
+ const { selectedIndex, scrollSnaps } = useCarouselTab(emblaApi);
28
27
  const buttonRefs = useRef([]);
28
+ const [focusedTabIndex, setFocusedTabIndex] = useState(null);
29
29
  const handleKeyDown = (event) => {
30
30
  var _a;
31
- let newIndex = selectedIndex;
31
+ let newIndex = focusedTabIndex ?? selectedIndex;
32
32
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
33
33
  const direction = event.key === "ArrowLeft" ? -1 : 1;
34
- newIndex = (selectedIndex + direction + scrollSnaps.length) % scrollSnaps.length;
34
+ newIndex = (newIndex + direction + scrollSnaps.length) % scrollSnaps.length;
35
35
  } else if (event.key === "Home") {
36
36
  newIndex = 0;
37
37
  } else if (event.key === "End") {
38
38
  newIndex = scrollSnaps.length - 1;
39
39
  }
40
- if (newIndex !== selectedIndex) {
41
- onClick(newIndex);
40
+ if (newIndex !== focusedTabIndex) {
42
41
  (_a = buttonRefs.current[newIndex]) == null ? void 0 : _a.focus();
43
- event.preventDefault();
44
- event.stopPropagation();
42
+ setFocusedTabIndex(newIndex);
45
43
  }
46
44
  onKeyDown == null ? void 0 : onKeyDown(event);
47
45
  };
46
+ useEffect(() => {
47
+ setAriaVariant("tabpanel");
48
+ }, [setAriaVariant]);
48
49
  return /* @__PURE__ */ jsx(
49
50
  "div",
50
51
  {
51
52
  role: "tablist",
52
- "aria-label": "Choose slide",
53
- tabIndex: 0,
54
53
  className: clsx(withBaseName(), className),
55
54
  onKeyDown: handleKeyDown,
56
55
  ref,
57
56
  ...rest,
58
- children: scrollSnaps.map((_, tabIndex) => {
59
- const startSlideNumber = tabIndex * slidesPerTransition + 1;
60
- const endSlideNumber = Math.min(
61
- startSlideNumber + slidesPerTransition - 1,
62
- numberOfSlides
57
+ children: scrollSnaps.map((_, scrollSnapIndex) => {
58
+ const visibleSlides = getVisibleSlideIndexes(
59
+ emblaApi,
60
+ scrollSnapIndex
63
61
  );
64
- const label = startSlideNumber === endSlideNumber ? `Slide ${startSlideNumber}` : `Slides ${startSlideNumber}-${endSlideNumber} of ${numberOfSlides}`;
65
- const selected = selectedIndex === tabIndex;
62
+ const startSlideNumber = visibleSlides.length >= 1 ? visibleSlides[0] : 0;
63
+ const endSlideNumber = visibleSlides.length > 1 ? visibleSlides[visibleSlides.length - 1] : 0;
64
+ const slideNodes = emblaApi == null ? void 0 : emblaApi.slideNodes();
65
+ const numberOfSlides = slideNodes == null ? void 0 : slideNodes.length;
66
+ let ariaLabel;
67
+ if (endSlideNumber >= 1) {
68
+ ariaLabel = `Slide ${startSlideNumber} to ${endSlideNumber} of ${numberOfSlides}`;
69
+ } else {
70
+ const description = getSlideDescription(emblaApi, startSlideNumber);
71
+ ariaLabel = `${description}`;
72
+ }
73
+ const selected = selectedIndex === scrollSnapIndex;
66
74
  const ariaControls = (slideNodes == null ? void 0 : slideNodes.length) ? slideNodes[startSlideNumber - 1].id : void 0;
67
75
  return /* @__PURE__ */ jsx(
68
76
  CarouselTabRenderer,
69
77
  {
70
78
  ref: (element) => {
71
- buttonRefs.current[tabIndex] = element;
79
+ buttonRefs.current[scrollSnapIndex] = element;
72
80
  },
73
81
  render,
74
82
  role: "tab",
75
- onClick: () => onClick(tabIndex),
76
- "aria-selected": selected,
77
83
  selected,
78
- tabIndex: selected ? 0 : -1,
79
- "aria-label": label,
80
- "aria-labelledby": ariaControls,
84
+ onBlur: () => {
85
+ setFocusedTabIndex(null);
86
+ },
87
+ onFocus: () => {
88
+ setFocusedTabIndex(scrollSnapIndex);
89
+ setAnnouncementState("tab");
90
+ emblaApi == null ? void 0 : emblaApi.scrollTo(scrollSnapIndex);
91
+ },
92
+ "aria-label": ariaLabel,
93
+ "aria-selected": selected,
94
+ tabIndex: selected && focusedTabIndex === null ? 0 : -1,
81
95
  "aria-controls": ariaControls
82
96
  },
83
- `carouselTab-${tabIndex}}`
97
+ `carouselTab-${scrollSnapIndex}}`
84
98
  );
85
99
  })
86
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CarouselTabList.js","sources":["../src/CarouselTabList.tsx"],"sourcesContent":["import { makePrefixer, type RenderPropsType, renderProps } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n forwardRef,\n type HTMLAttributes,\n type KeyboardEventHandler,\n useRef,\n} from \"react\";\nimport { useCarouselContext } from \"./CarouselContext\";\nimport {\n CarouselTab,\n type CarouselTabProps,\n useCarouselTab,\n} from \"./CarouselTab\";\nimport carouselControlsCss from \"./CarouselTabList.css\";\n\nconst withBaseName = makePrefixer(\"saltCarouselTabList\");\n\n/**\n * Props for the CarouselTabList component.\n */\nexport interface CarouselTabListProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Render prop to enable customisation of tab button.\n */\n render?: RenderPropsType[\"render\"];\n}\n\nconst CarouselTabRenderer = forwardRef<\n HTMLButtonElement,\n CarouselTabProps & { render?: CarouselTabListProps[\"render\"] }\n>((props, ref) => {\n return renderProps(CarouselTab, { ...props, ref });\n});\n\nexport const CarouselTabList = forwardRef<HTMLDivElement, CarouselTabListProps>(\n function CarouselTabList({ className, render, onKeyDown, ...rest }, ref) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-controls\",\n css: carouselControlsCss,\n window: targetWindow,\n });\n\n const { emblaApi } = useCarouselContext();\n const { selectedIndex, scrollSnaps, onClick } = useCarouselTab(emblaApi);\n\n const slideNodes = emblaApi?.slideNodes();\n const numberOfSlides = slideNodes?.length ?? 0;\n const slidesPerTransition = numberOfSlides\n ? Math.ceil(numberOfSlides / scrollSnaps.length)\n : 0;\n\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {\n let newIndex = selectedIndex;\n\n if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n const direction = event.key === \"ArrowLeft\" ? -1 : 1;\n newIndex =\n (selectedIndex + direction + scrollSnaps.length) % scrollSnaps.length;\n } else if (event.key === \"Home\") {\n newIndex = 0;\n } else if (event.key === \"End\") {\n newIndex = scrollSnaps.length - 1;\n }\n\n if (newIndex !== selectedIndex) {\n onClick(newIndex);\n buttonRefs.current[newIndex]?.focus();\n event.preventDefault();\n event.stopPropagation();\n }\n onKeyDown?.(event);\n };\n\n return (\n <div\n role=\"tablist\"\n aria-label=\"Choose slide\"\n tabIndex={0}\n className={clsx(withBaseName(), className)}\n onKeyDown={handleKeyDown}\n ref={ref}\n {...rest}\n >\n {scrollSnaps.map((_, tabIndex) => {\n const startSlideNumber = tabIndex * slidesPerTransition + 1;\n const endSlideNumber = Math.min(\n startSlideNumber + slidesPerTransition - 1,\n numberOfSlides,\n );\n const label =\n startSlideNumber === endSlideNumber\n ? `Slide ${startSlideNumber}`\n : `Slides ${startSlideNumber}-${endSlideNumber} of ${numberOfSlides}`;\n\n const selected = selectedIndex === tabIndex;\n\n const ariaControls = slideNodes?.length\n ? slideNodes[startSlideNumber - 1].id\n : undefined;\n return (\n <CarouselTabRenderer\n key={`carouselTab-${tabIndex}}`}\n ref={(element: HTMLButtonElement) => {\n buttonRefs.current[tabIndex] = element;\n }}\n render={render}\n role={\"tab\"}\n onClick={() => onClick(tabIndex)}\n aria-selected={selected}\n selected={selected}\n tabIndex={selected ? 0 : -1}\n aria-label={label}\n aria-labelledby={ariaControls}\n aria-controls={ariaControls}\n />\n );\n })}\n </div>\n );\n },\n);\n"],"names":["CarouselTabList","carouselControlsCss"],"mappings":"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"}
1
+ {"version":3,"file":"CarouselTabList.js","sources":["../src/CarouselTabList.tsx"],"sourcesContent":["import { makePrefixer, type RenderPropsType, renderProps } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n forwardRef,\n type HTMLAttributes,\n type KeyboardEventHandler,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useCarouselContext } from \"./CarouselContext\";\nimport {\n CarouselTab,\n type CarouselTabProps,\n useCarouselTab,\n} from \"./CarouselTab\";\nimport carouselControlsCss from \"./CarouselTabList.css\";\nimport { getSlideDescription } from \"./getSlideDescription\";\nimport { getVisibleSlideIndexes } from \"./getVisibleSlideIndexes\";\n\nconst withBaseName = makePrefixer(\"saltCarouselTabList\");\n\n/**\n * Props for the CarouselTabList component.\n */\nexport interface CarouselTabListProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Render prop to enable customisation of tab button.\n */\n render?: RenderPropsType[\"render\"];\n}\n\nexport interface CarouselTabRendererProps extends CarouselTabProps {\n render?: CarouselTabListProps[\"render\"];\n}\n\nconst CarouselTabRenderer = forwardRef<\n HTMLButtonElement,\n CarouselTabRendererProps\n>((props, ref) => {\n return renderProps(CarouselTab, { ...props, ref });\n});\n\nexport const CarouselTabList = forwardRef<HTMLDivElement, CarouselTabListProps>(\n function CarouselTabList({ className, render, onKeyDown, ...rest }, ref) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-carousel-controls\",\n css: carouselControlsCss,\n window: targetWindow,\n });\n\n const { emblaApi, setAriaVariant, setAnnouncementState } =\n useCarouselContext();\n const { selectedIndex, scrollSnaps } = useCarouselTab(emblaApi);\n\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const [focusedTabIndex, setFocusedTabIndex] = useState<number | null>(null);\n\n const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {\n let newIndex = focusedTabIndex ?? selectedIndex;\n\n if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n const direction = event.key === \"ArrowLeft\" ? -1 : 1;\n newIndex =\n (newIndex + direction + scrollSnaps.length) % scrollSnaps.length;\n } else if (event.key === \"Home\") {\n newIndex = 0;\n } else if (event.key === \"End\") {\n newIndex = scrollSnaps.length - 1;\n }\n\n if (newIndex !== focusedTabIndex) {\n buttonRefs.current[newIndex]?.focus();\n setFocusedTabIndex(newIndex);\n }\n onKeyDown?.(event);\n };\n\n useEffect(() => {\n setAriaVariant(\"tabpanel\");\n }, [setAriaVariant]);\n\n return (\n <div\n role=\"tablist\"\n className={clsx(withBaseName(), className)}\n onKeyDown={handleKeyDown}\n ref={ref}\n {...rest}\n >\n {scrollSnaps.map((_, scrollSnapIndex) => {\n const visibleSlides = getVisibleSlideIndexes(\n emblaApi,\n scrollSnapIndex,\n );\n const startSlideNumber =\n visibleSlides.length >= 1 ? visibleSlides[0] : 0;\n const endSlideNumber =\n visibleSlides.length > 1\n ? visibleSlides[visibleSlides.length - 1]\n : 0;\n const slideNodes = emblaApi?.slideNodes();\n const numberOfSlides = slideNodes?.length;\n\n let ariaLabel: string;\n if (endSlideNumber >= 1) {\n ariaLabel = `Slide ${startSlideNumber} to ${endSlideNumber} of ${numberOfSlides}`;\n } else {\n const description = getSlideDescription(emblaApi, startSlideNumber);\n ariaLabel = `${description}`;\n }\n\n const selected = selectedIndex === scrollSnapIndex;\n const ariaControls = slideNodes?.length\n ? slideNodes[startSlideNumber - 1].id\n : undefined;\n\n return (\n <CarouselTabRenderer\n key={`carouselTab-${scrollSnapIndex}}`}\n ref={(element: HTMLButtonElement) => {\n buttonRefs.current[scrollSnapIndex] = element;\n }}\n render={render}\n role={\"tab\"}\n selected={selected}\n onBlur={() => {\n setFocusedTabIndex(null);\n }}\n onFocus={() => {\n setFocusedTabIndex(scrollSnapIndex);\n setAnnouncementState(\"tab\");\n emblaApi?.scrollTo(scrollSnapIndex);\n }}\n aria-label={ariaLabel}\n aria-selected={selected}\n tabIndex={selected && focusedTabIndex === null ? 0 : -1}\n aria-controls={ariaControls}\n />\n );\n })}\n </div>\n );\n },\n);\n"],"names":["CarouselTabList","carouselControlsCss"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createCustomSettle.js","sources":["../src/createCustomSettle.ts"],"sourcesContent":["import type { EmblaCarouselType } from \"embla-carousel\";\n\nconst settlePixelThreshold = 10;\n\nexport type SettleCallback = (emblaApi: EmblaCarouselType) => void;\nexport type CreateCustomSettle = (callback: SettleCallback) => SettleCallback;\n\nexport const createCustomSettle: CreateCustomSettle = (callback) =>\n function fireCustomSettle(emblaApi: EmblaCarouselType) {\n const { dragHandler, location, target } = emblaApi.internalEngine();\n if (dragHandler.pointerDown()) return;\n const displacement = target.get() - location.get();\n if (Math.abs(displacement) < settlePixelThreshold) {\n callback(emblaApi);\n }\n };\n"],"names":[],"mappings":"AAEA,MAAM,oBAAuB,GAAA,EAAA;AAKtB,MAAM,kBAAyC,GAAA,CAAC,QACrD,KAAA,SAAS,iBAAiB,QAA6B,EAAA;AACrD,EAAA,MAAM,EAAE,WAAa,EAAA,QAAA,EAAU,MAAO,EAAA,GAAI,SAAS,cAAe,EAAA;AAClE,EAAI,IAAA,WAAA,CAAY,aAAe,EAAA;AAC/B,EAAA,MAAM,YAAe,GAAA,MAAA,CAAO,GAAI,EAAA,GAAI,SAAS,GAAI,EAAA;AACjD,EAAA,IAAI,IAAK,CAAA,GAAA,CAAI,YAAY,CAAA,GAAI,oBAAsB,EAAA;AACjD,IAAA,QAAA,CAAS,QAAQ,CAAA;AAAA;AAErB;;;;"}
1
+ {"version":3,"file":"createCustomSettle.js","sources":["../src/createCustomSettle.ts"],"sourcesContent":["import type { EmblaCarouselType } from \"embla-carousel\";\n\nconst settlePixelThreshold = 10;\n\nexport type SettleCallback = (emblaApi: EmblaCarouselType) => void;\nexport type CreateCustomSettle = (callback: SettleCallback) => SettleCallback;\n\nexport const createCustomSettle: CreateCustomSettle = (callback) =>\n function fireCustomSettle(emblaApi: EmblaCarouselType) {\n const { dragHandler, location, target } = emblaApi.internalEngine();\n if (dragHandler.pointerDown()) return;\n const displacement = target.get() - location.get();\n if (Math.abs(displacement) < settlePixelThreshold) {\n callback(emblaApi);\n }\n };\n"],"names":[],"mappings":"AAEA,MAAM,oBAAA,GAAuB,EAAA;AAKtB,MAAM,kBAAA,GAAyC,CAAC,QAAA,KACrD,SAAS,iBAAiB,QAAA,EAA6B;AACrD,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,EAAU,MAAA,EAAO,GAAI,SAAS,cAAA,EAAe;AAClE,EAAA,IAAI,WAAA,CAAY,aAAY,EAAG;AAC/B,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,GAAA,EAAI,GAAI,SAAS,GAAA,EAAI;AACjD,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,YAAY,CAAA,GAAI,oBAAA,EAAsB;AACjD,IAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,EACnB;AACF;;;;"}
@@ -0,0 +1,30 @@
1
+ const getSlideDescription = (emblaApi, slideNumber) => {
2
+ if (!emblaApi || slideNumber < 1) {
3
+ return "";
4
+ }
5
+ const slideElement = emblaApi.slideNodes()[slideNumber - 1];
6
+ const { ownerDocument } = emblaApi.internalEngine();
7
+ const resolveAriaLabelledBy = (element) => {
8
+ if (!element) return null;
9
+ const labelledById = element.getAttribute("aria-labelledby");
10
+ let description2 = null;
11
+ if (labelledById) {
12
+ const labelledByElement = ownerDocument.getElementById(labelledById);
13
+ if (labelledByElement) {
14
+ description2 = resolveAriaLabelledBy(labelledByElement);
15
+ }
16
+ }
17
+ if (!description2) {
18
+ description2 = element.getAttribute("aria-label") || element.textContent || null;
19
+ }
20
+ return description2;
21
+ };
22
+ let description = resolveAriaLabelledBy(slideElement);
23
+ if (!description) {
24
+ description = (slideElement == null ? void 0 : slideElement.getAttribute("aria-label")) ?? null;
25
+ }
26
+ return description || "";
27
+ };
28
+
29
+ export { getSlideDescription };
30
+ //# sourceMappingURL=getSlideDescription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getSlideDescription.js","sources":["../src/getSlideDescription.ts"],"sourcesContent":["import type { EmblaCarouselType } from \"embla-carousel\";\n\n/** Get a description of the slide.\n * @param emblaApi\n * @param slideIndex\n * @returns A slide description if defined or an empty string\n */\nexport const getSlideDescription = (\n emblaApi: EmblaCarouselType | undefined,\n slideNumber: number,\n): string => {\n if (!emblaApi || slideNumber < 1) {\n return \"\";\n }\n\n const slideElement = emblaApi.slideNodes()[slideNumber - 1];\n const { ownerDocument } = emblaApi.internalEngine();\n\n const resolveAriaLabelledBy = (element: Element | null): string | null => {\n if (!element) return null;\n\n const labelledById = element.getAttribute(\"aria-labelledby\");\n let description = null;\n\n if (labelledById) {\n const labelledByElement = ownerDocument.getElementById(labelledById);\n if (labelledByElement) {\n // Recursively resolve aria-labelledby\n description = resolveAriaLabelledBy(labelledByElement);\n }\n }\n\n // If no description found, use aria-label or text content\n if (!description) {\n description =\n element.getAttribute(\"aria-label\") || element.textContent || null;\n }\n\n return description;\n };\n\n let description = resolveAriaLabelledBy(slideElement);\n\n if (!description) {\n description = slideElement?.getAttribute(\"aria-label\") ?? null;\n }\n\n return description || \"\";\n};\n"],"names":["description"],"mappings":"AAOO,MAAM,mBAAA,GAAsB,CACjC,QAAA,EACA,WAAA,KACW;AACX,EAAA,IAAI,CAAC,QAAA,IAAY,WAAA,GAAc,CAAA,EAAG;AAChC,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAe,QAAA,CAAS,UAAA,EAAW,CAAE,cAAc,CAAC,CAAA;AAC1D,EAAA,MAAM,EAAE,aAAA,EAAc,GAAI,QAAA,CAAS,cAAA,EAAe;AAElD,EAAA,MAAM,qBAAA,GAAwB,CAAC,OAAA,KAA2C;AACxE,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,YAAA,CAAa,iBAAiB,CAAA;AAC3D,IAAA,IAAIA,YAAAA,GAAc,IAAA;AAElB,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,MAAM,iBAAA,GAAoB,aAAA,CAAc,cAAA,CAAe,YAAY,CAAA;AACnE,MAAA,IAAI,iBAAA,EAAmB;AAErB,QAAAA,YAAAA,GAAc,sBAAsB,iBAAiB,CAAA;AAAA,MACvD;AAAA,IACF;AAGA,IAAA,IAAI,CAACA,YAAAA,EAAa;AAChB,MAAAA,eACE,OAAA,CAAQ,YAAA,CAAa,YAAY,CAAA,IAAK,QAAQ,WAAA,IAAe,IAAA;AAAA,IACjE;AAEA,IAAA,OAAOA,YAAAA;AAAA,EACT,CAAA;AAEA,EAAA,IAAI,WAAA,GAAc,sBAAsB,YAAY,CAAA;AAEpD,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,WAAA,GAAA,CAAc,YAAA,IAAA,IAAA,GAAA,MAAA,GAAA,YAAA,CAAc,aAAa,YAAA,CAAA,KAAiB,IAAA;AAAA,EAC5D;AAEA,EAAA,OAAO,WAAA,IAAe,EAAA;AACxB;;;;"}
@@ -0,0 +1,24 @@
1
+ import { getSlideDescription } from './getSlideDescription.js';
2
+ import { getVisibleSlideIndexes } from './getVisibleSlideIndexes.js';
3
+
4
+ const getVisibleSlideDescription = (emblaApi, scrollSnapIndex) => {
5
+ if (!emblaApi) {
6
+ return "";
7
+ }
8
+ const visibleSlideIndexes = getVisibleSlideIndexes(emblaApi, scrollSnapIndex);
9
+ const numberOfSlides = (emblaApi == null ? void 0 : emblaApi.slideNodes().length) ?? 0;
10
+ if (numberOfSlides < 1) {
11
+ return "";
12
+ }
13
+ if (visibleSlideIndexes.length > 1) {
14
+ return `Slide ${visibleSlideIndexes[0]} to ${visibleSlideIndexes[visibleSlideIndexes.length - 1]} of ${numberOfSlides}`;
15
+ }
16
+ const visibleSlideIndex = visibleSlideIndexes[0];
17
+ const description = getSlideDescription(emblaApi, visibleSlideIndex);
18
+ const slide = emblaApi == null ? void 0 : emblaApi.slideNodes()[visibleSlideIndex - 1];
19
+ const slideRoleDescription = (slide == null ? void 0 : slide.ariaRoleDescription) ?? "slide";
20
+ return `${description}, ${slideRoleDescription}, ${visibleSlideIndex} of ${numberOfSlides}`;
21
+ };
22
+
23
+ export { getVisibleSlideDescription };
24
+ //# sourceMappingURL=getVisibleSlideDescription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getVisibleSlideDescription.js","sources":["../src/getVisibleSlideDescription.ts"],"sourcesContent":["import type { EmblaCarouselType } from \"embla-carousel\";\nimport { getSlideDescription } from \"./getSlideDescription\";\nimport { getVisibleSlideIndexes } from \"./getVisibleSlideIndexes\";\n\n/** Get a description of the visible slide(s).\n * @param emblaApi\n * @param slideIndex\n * @returns An array of descriptions for the visible slides\n */\nexport const getVisibleSlideDescription = (\n emblaApi: EmblaCarouselType | undefined,\n scrollSnapIndex: number,\n): string => {\n if (!emblaApi) {\n return \"\";\n }\n\n const visibleSlideIndexes = getVisibleSlideIndexes(emblaApi, scrollSnapIndex);\n const numberOfSlides = emblaApi?.slideNodes().length ?? 0;\n\n if (numberOfSlides < 1) {\n return \"\";\n }\n\n if (visibleSlideIndexes.length > 1) {\n return `Slide ${visibleSlideIndexes[0]} to ${visibleSlideIndexes[visibleSlideIndexes.length - 1]} of ${numberOfSlides}`;\n }\n const visibleSlideIndex = visibleSlideIndexes[0];\n const description = getSlideDescription(emblaApi, visibleSlideIndex);\n const slide = emblaApi?.slideNodes()[visibleSlideIndex - 1];\n const slideRoleDescription = slide?.ariaRoleDescription ?? \"slide\";\n return `${description}, ${slideRoleDescription}, ${visibleSlideIndex} of ${numberOfSlides}`;\n};\n"],"names":[],"mappings":";;;AASO,MAAM,0BAAA,GAA6B,CACxC,QAAA,EACA,eAAA,KACW;AACX,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,mBAAA,GAAsB,sBAAA,CAAuB,QAAA,EAAU,eAAe,CAAA;AAC5E,EAAA,MAAM,cAAA,GAAA,CAAiB,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,UAAA,EAAA,CAAa,MAAA,KAAU,CAAA;AAExD,EAAA,IAAI,iBAAiB,CAAA,EAAG;AACtB,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,IAAI,mBAAA,CAAoB,SAAS,CAAA,EAAG;AAClC,IAAA,OAAO,CAAA,MAAA,EAAS,mBAAA,CAAoB,CAAC,CAAC,CAAA,IAAA,EAAO,mBAAA,CAAoB,mBAAA,CAAoB,MAAA,GAAS,CAAC,CAAC,CAAA,IAAA,EAAO,cAAc,CAAA,CAAA;AAAA,EACvH;AACA,EAAA,MAAM,iBAAA,GAAoB,oBAAoB,CAAC,CAAA;AAC/C,EAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,QAAA,EAAU,iBAAiB,CAAA;AACnE,EAAA,MAAM,KAAA,GAAQ,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,UAAA,EAAA,CAAa,iBAAA,GAAoB,CAAA,CAAA;AACzD,EAAA,MAAM,oBAAA,GAAA,CAAuB,+BAAO,mBAAA,KAAuB,OAAA;AAC3D,EAAA,OAAO,GAAG,WAAW,CAAA,EAAA,EAAK,oBAAoB,CAAA,EAAA,EAAK,iBAAiB,OAAO,cAAc,CAAA,CAAA;AAC3F;;;;"}
@@ -0,0 +1,30 @@
1
+ const getSlideNumberRange = (startSlideNumber, endSlideNumber) => {
2
+ const slideNumbers = [];
3
+ for (let i = startSlideNumber; i <= endSlideNumber; i++) {
4
+ slideNumbers.push(i);
5
+ }
6
+ return slideNumbers;
7
+ };
8
+ const getVisibleSlideIndexes = (emblaApi, scrollSnapIndex) => {
9
+ if (!emblaApi) {
10
+ return [];
11
+ }
12
+ const numberOfSlides = (emblaApi == null ? void 0 : emblaApi.slideNodes().length) ?? 0;
13
+ const scrollSnaps = (emblaApi == null ? void 0 : emblaApi.scrollSnapList()) ?? [];
14
+ const slidesPerTransition = numberOfSlides ? Math.ceil(numberOfSlides / scrollSnaps.length) : 0;
15
+ const startSlideNumber = Math.min(
16
+ scrollSnapIndex * slidesPerTransition + 1,
17
+ numberOfSlides - (slidesPerTransition - 1)
18
+ );
19
+ const endSlideNumber = Math.min(
20
+ startSlideNumber + slidesPerTransition - 1,
21
+ numberOfSlides
22
+ );
23
+ if (startSlideNumber === endSlideNumber) {
24
+ return [startSlideNumber];
25
+ }
26
+ return getSlideNumberRange(startSlideNumber, endSlideNumber);
27
+ };
28
+
29
+ export { getSlideNumberRange, getVisibleSlideIndexes };
30
+ //# sourceMappingURL=getVisibleSlideIndexes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getVisibleSlideIndexes.js","sources":["../src/getVisibleSlideIndexes.ts"],"sourcesContent":["import type { EmblaCarouselType } from \"embla-carousel\";\n\nexport const getSlideNumberRange = (\n startSlideNumber: number,\n endSlideNumber: number,\n): number[] => {\n const slideNumbers: number[] = [];\n for (let i = startSlideNumber; i <= endSlideNumber; i++) {\n slideNumbers.push(i);\n }\n return slideNumbers;\n};\n\n/** Get the visible slide indexes\n * @param emblaApi\n * @param scrollSnap\n * @returns An array of visible slide indexes\n */\nexport const getVisibleSlideIndexes = (\n emblaApi: EmblaCarouselType | undefined,\n scrollSnapIndex: number,\n): number[] => {\n if (!emblaApi) {\n return [];\n }\n\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 scrollSnapIndex * 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 return [startSlideNumber];\n }\n return getSlideNumberRange(startSlideNumber, endSlideNumber);\n};\n"],"names":[],"mappings":"AAEO,MAAM,mBAAA,GAAsB,CACjC,gBAAA,EACA,cAAA,KACa;AACb,EAAA,MAAM,eAAyB,EAAC;AAChC,EAAA,KAAA,IAAS,CAAA,GAAI,gBAAA,EAAkB,CAAA,IAAK,cAAA,EAAgB,CAAA,EAAA,EAAK;AACvD,IAAA,YAAA,CAAa,KAAK,CAAC,CAAA;AAAA,EACrB;AACA,EAAA,OAAO,YAAA;AACT;AAOO,MAAM,sBAAA,GAAyB,CACpC,QAAA,EACA,eAAA,KACa;AACb,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,cAAA,GAAA,CAAiB,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,UAAA,EAAA,CAAa,MAAA,KAAU,CAAA;AACxD,EAAA,MAAM,WAAA,GAAA,CAAc,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAU,cAAA,EAAA,KAAoB,EAAC;AACnD,EAAA,MAAM,sBAAsB,cAAA,GACxB,IAAA,CAAK,KAAK,cAAA,GAAiB,WAAA,CAAY,MAAM,CAAA,GAC7C,CAAA;AACJ,EAAA,MAAM,mBAAmB,IAAA,CAAK,GAAA;AAAA,IAC5B,kBAAkB,mBAAA,GAAsB,CAAA;AAAA,IACxC,kBAAkB,mBAAA,GAAsB,CAAA;AAAA,GAC1C;AACA,EAAA,MAAM,iBAAiB,IAAA,CAAK,GAAA;AAAA,IAC1B,mBAAmB,mBAAA,GAAsB,CAAA;AAAA,IACzC;AAAA,GACF;AAEA,EAAA,IAAI,qBAAqB,cAAA,EAAgB;AACvC,IAAA,OAAO,CAAC,gBAAgB,CAAA;AAAA,EAC1B;AACA,EAAA,OAAO,mBAAA,CAAoB,kBAAkB,cAAc,CAAA;AAC7D;;;;"}
package/dist-es/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export { Carousel } from './Carousel.js';
2
- export { CarouselAnnouncement, getSlideLabel } from './CarouselAnnouncementPlugin.js';
3
2
  export { CarouselAutoplayIndicator } from './CarouselAutoplayIndicator.js';
4
3
  export { CarouselCard } from './CarouselCard.js';
5
4
  export { CarouselContext, useCarouselContext } from './CarouselContext.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}