@allxsmith/bestax-bulma 5.41.0 → 5.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -9456,6 +9456,69 @@ const DefaultPrevIcon = () => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org
9456
9456
  * @returns {JSX.Element} The rendered SVG chevron-right icon.
9457
9457
  */
9458
9458
  const DefaultNextIcon = () => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }));
9459
+ /**
9460
+ * The media queries Bulma's `fullhd`, `widescreen`, `desktop` and `tablet`
9461
+ * mixins open with, widest first, at Bulma's default breakpoints. The
9462
+ * stylesheet picks the count in view with those mixins; these tell the
9463
+ * script which count that is. Narrower than the last one is `mobile`.
9464
+ */
9465
+ const VIEWPORT_QUERIES = [
9466
+ 'screen and (min-width: 1408px)',
9467
+ 'screen and (min-width: 1216px)',
9468
+ 'screen and (min-width: 1024px)',
9469
+ 'screen and (min-width: 769px), print',
9470
+ ];
9471
+ /** `useBulmaViewport`'s answer below the tablet breakpoint. */
9472
+ const MOBILE = VIEWPORT_QUERIES.length;
9473
+ /**
9474
+ * The `--bulma-carousel-items-to-show*` variable each count prop writes, in
9475
+ * the order the component lists them: the viewports as above, mobile, and
9476
+ * last `itemsToShow` itself.
9477
+ */
9478
+ const COUNT_SUFFIXES = [
9479
+ '-fullhd',
9480
+ '-widescreen',
9481
+ '-desktop',
9482
+ '-tablet',
9483
+ '-mobile',
9484
+ '',
9485
+ ];
9486
+ /**
9487
+ * Which of Bulma's viewports the window is in: the index of the widest
9488
+ * `VIEWPORT_QUERIES` entry that matches, `MOBILE` below them all, or `-1`
9489
+ * when that isn't known (on the server, before the first layout effect, and
9490
+ * whenever `enabled` is off, so a carousel with one count everywhere
9491
+ * subscribes to nothing).
9492
+ *
9493
+ * @function
9494
+ * @param {boolean} enabled - Whether any viewport-specific count is set.
9495
+ * @returns {number} The viewport, as described above.
9496
+ */
9497
+ function useBulmaViewport(enabled) {
9498
+ const [viewport, setViewport] = React.useState(-1);
9499
+ useClientLayoutEffect(() => {
9500
+ if (!enabled || typeof window.matchMedia !== 'function')
9501
+ return undefined;
9502
+ const lists = VIEWPORT_QUERIES.map(query => window.matchMedia(query));
9503
+ const update = () => {
9504
+ const index = lists.findIndex(list => list.matches);
9505
+ setViewport(index < 0 ? MOBILE : index);
9506
+ };
9507
+ update();
9508
+ lists.forEach(list => list.addEventListener('change', update));
9509
+ return () => lists.forEach(list => list.removeEventListener('change', update));
9510
+ }, [enabled]);
9511
+ return enabled ? viewport : -1;
9512
+ }
9513
+ /**
9514
+ * A count prop as the whole number the carousel works with: rounded down,
9515
+ * and never below 1, so a zero or a stray `NaN` can't divide by nothing.
9516
+ *
9517
+ * @function
9518
+ * @param {number | undefined} count - The prop as given.
9519
+ * @returns {number | undefined} The count, or `undefined` when unset.
9520
+ */
9521
+ const wholeCount = (count) => count === undefined ? undefined : Math.max(1, Math.floor(count) || 1);
9459
9522
  /**
9460
9523
  * The `Carousel` component provides an image/content slider with navigation arrows and indicators.
9461
9524
  *
@@ -9482,15 +9545,28 @@ const DefaultNextIcon = () => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org
9482
9545
  * <img src="image2.jpg" alt="Slide 2" />
9483
9546
  * </CarouselItem>
9484
9547
  * </Carousel>
9548
+ *
9549
+ * @example
9550
+ * // Three cards in view on desktop, two on tablet, one on mobile
9551
+ * <Carousel itemsToShow={1} itemsToShowTablet={2} itemsToShowDesktop={3}>
9552
+ * {cards.map(card => (
9553
+ * <CarouselItem key={card.id}>{card.body}</CarouselItem>
9554
+ * ))}
9555
+ * </Carousel>
9485
9556
  */
9486
- const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, interval = 5000, pauseOnHover = true, repeat = true, hasDrag = true, arrow = true, arrowHover = false, indicator = true, indicatorInside = false, indicatorPosition = 'bottom', indicatorStyle = 'dots', iconPrev, iconNext, iconLibrary, iconVariant, iconSize, iconFeatures, arrowBackground = true, arrowColor, ariaLabel = 'Image carousel', onChange, className, children, ...props }, ref) => {
9557
+ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, interval = 5000, pauseOnHover = true, repeat = true, hasDrag = true, itemsToShow, itemsToShowMobile, itemsToShowTablet, itemsToShowDesktop, itemsToShowWidescreen, itemsToShowFullhd, itemsToScroll, arrow = true, arrowHover = false, indicator = true, indicatorInside = false, indicatorPosition = 'bottom', indicatorStyle = 'dots', iconPrev, iconNext, iconLibrary, iconVariant, iconSize, iconFeatures, arrowBackground = true, arrowColor, ariaLabel = 'Image carousel', onChange, className, style, children, ...props }, ref) => {
9487
9558
  const { bulmaHelperClasses, rest } = useBulmaClasses(props);
9488
9559
  const carouselRef = React.useRef(null);
9489
9560
  // Cleanup returned by a consumer's callback ref on attach, held until detach.
9490
9561
  const consumerCleanupRef = React.useRef(null);
9491
9562
  const containerRef = React.useRef(null);
9563
+ const slidesRef = React.useRef(null);
9564
+ // The item (a child of the track) that holds focus, until focus leaves
9565
+ // the items for somewhere else
9566
+ const focusedItemRef = React.useRef(undefined);
9492
9567
  const [internalValue, setInternalValue] = React.useState(0);
9493
9568
  const [isPaused, setIsPaused] = React.useState(false);
9569
+ const [isFocusInside, setIsFocusInside] = React.useState(false);
9494
9570
  const [isDragging, setIsDragging] = React.useState(false);
9495
9571
  const [dragStart, setDragStart] = React.useState(0);
9496
9572
  const [dragOffset, setDragOffset] = React.useState(0);
@@ -9500,64 +9576,105 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9500
9576
  // Get valid children
9501
9577
  const items = React.Children.toArray(children).filter(React.isValidElement);
9502
9578
  const itemCount = items.length;
9503
- // Use controlled or internal value
9504
- const activeIndex = controlledValue !== undefined ? controlledValue : internalValue;
9505
- // Sync displayIndex when controlled value changes externally. Intentional
9506
- // external-prop sync that must not run mid-wrap animation; covered by tests
9507
- // and certified in-browser.
9579
+ // The count in view. The viewport props come widest first, in the order
9580
+ // of VIEWPORT_QUERIES, then mobile; each min-width viewport falls back to
9581
+ // the narrower ones, and every viewport to `itemsToShow`, the cascade the
9582
+ // stylesheet's `--bulma-carousel-items-to-show-*` defaults spell out.
9583
+ const counts = [
9584
+ itemsToShowFullhd,
9585
+ itemsToShowWidescreen,
9586
+ itemsToShowDesktop,
9587
+ itemsToShowTablet,
9588
+ itemsToShowMobile,
9589
+ ].map(wholeCount);
9590
+ const baseCount = wholeCount(itemsToShow);
9591
+ const viewport = useBulmaViewport(counts.some(c => c !== undefined));
9592
+ const inView = (viewport === MOBILE
9593
+ ? counts[MOBILE]
9594
+ : viewport >= 0
9595
+ ? counts.slice(viewport, MOBILE).find(c => c !== undefined)
9596
+ : undefined) ??
9597
+ baseCount ??
9598
+ 1;
9599
+ // The props as the stylesheet reads them, so the widths are right from
9600
+ // the first paint, on the server too.
9601
+ const countStyle = {};
9602
+ [...counts, baseCount].forEach((count, i) => {
9603
+ if (count !== undefined) {
9604
+ countStyle[`--bulma-carousel-items-to-show${COUNT_SUFFIXES[i]}`] =
9605
+ count;
9606
+ }
9607
+ });
9608
+ const scroll = Math.min(wholeCount(itemsToScroll) ?? 1, inView);
9609
+ const looping = repeat && itemCount > inView;
9610
+ const lastStart = Math.max(0, itemCount - inView);
9611
+ // Where the view can start: every `scroll` items from the first, and
9612
+ // without a loop the last full view as well, which is what keeps the
9613
+ // end of the track from showing empty space.
9614
+ const positions = [];
9615
+ for (let p = 0; p < (looping ? itemCount : lastStart); p += scroll) {
9616
+ positions.push(p);
9617
+ }
9618
+ if (!looping)
9619
+ positions.push(lastStart);
9620
+ const lastPosition = positions[positions.length - 1];
9621
+ const canMove = positions.length > 1;
9622
+ const wrap = (index) => ((index % itemCount) + itemCount) % itemCount;
9623
+ const fit = (index) => looping ? wrap(index) : Math.min(Math.max(index, 0), lastStart);
9624
+ // Use controlled or internal value, as a start the view can show
9625
+ const activeIndex = fit(controlledValue ?? internalValue);
9626
+ // The indicator for the last position at or before the first item in view
9627
+ const activePosition = positions.reduce((found, position, index) => (position <= activeIndex ? index : found), 0);
9628
+ const nextIndex = positions[activePosition + 1];
9629
+ // From between two positions, the previous step is the one just behind
9630
+ const prevIndex = activeIndex > positions[activePosition]
9631
+ ? positions[activePosition]
9632
+ : positions[activePosition - 1];
9633
+ // Sync displayIndex when the value changes externally, or when the count
9634
+ // in view moves the start it fits to. Intentional external-prop sync that
9635
+ // must not run mid-wrap animation; covered by tests and certified
9636
+ // in-browser. Uncontrolled, the fitted start becomes the value too, so a
9637
+ // later change of count keeps what was first in view in view.
9508
9638
  React.useEffect(() => {
9509
- if (controlledValue !== undefined && !isWrapping) {
9639
+ if (!isWrapping) {
9510
9640
  // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop
9511
- setDisplayIndex(controlledValue);
9641
+ setDisplayIndex(activeIndex);
9642
+ if (controlledValue === undefined)
9643
+ setInternalValue(activeIndex);
9512
9644
  }
9513
- }, [controlledValue, isWrapping]);
9645
+ }, [activeIndex, isWrapping, controlledValue]);
9514
9646
  // Update active index
9515
- const setActiveIndex = React.useCallback((index, syncDisplay = true) => {
9516
- let newIndex = index;
9517
- if (repeat) {
9518
- if (index < 0) {
9519
- newIndex = itemCount - 1;
9520
- }
9521
- else if (index >= itemCount) {
9522
- newIndex = 0;
9523
- }
9524
- }
9525
- else {
9526
- newIndex = Math.max(0, Math.min(index, itemCount - 1));
9527
- }
9647
+ const setActiveIndex = React.useCallback((index) => {
9528
9648
  if (controlledValue === undefined) {
9529
- setInternalValue(newIndex);
9530
- }
9531
- if (syncDisplay) {
9532
- setDisplayIndex(newIndex);
9649
+ setInternalValue(index);
9533
9650
  }
9534
- onChange?.(newIndex);
9535
- }, [controlledValue, itemCount, repeat, onChange]);
9651
+ setDisplayIndex(index);
9652
+ setIsWrapping(false);
9653
+ onChange?.(index);
9654
+ }, [controlledValue, onChange]);
9536
9655
  // Navigation handlers
9537
9656
  const goToPrev = React.useCallback(() => {
9538
- if (activeIndex === 0 && repeat && itemCount > 1) {
9539
- // Animate to clone of last slide (position -1)
9540
- setDisplayIndex(-1);
9541
- setIsWrapping(true);
9657
+ if (prevIndex !== undefined) {
9658
+ setActiveIndex(prevIndex);
9542
9659
  }
9543
- else {
9544
- setActiveIndex(activeIndex - 1);
9660
+ else if (looping) {
9661
+ // Animate to the clones before the first item, which show the last
9662
+ // position, and snap to the real one when the transition ends
9663
+ setDisplayIndex(lastPosition - itemCount);
9664
+ setIsWrapping(true);
9545
9665
  }
9546
- }, [activeIndex, repeat, itemCount, setActiveIndex]);
9666
+ }, [prevIndex, looping, lastPosition, itemCount, setActiveIndex]);
9547
9667
  const goToNext = React.useCallback(() => {
9548
- if (activeIndex === itemCount - 1 && repeat && itemCount > 1) {
9549
- // Animate to clone of first slide (position itemCount)
9668
+ if (nextIndex !== undefined) {
9669
+ setActiveIndex(nextIndex);
9670
+ }
9671
+ else if (looping) {
9672
+ // Animate to the clones after the last item, which show the first
9673
+ // position, and snap to the real one when the transition ends
9550
9674
  setDisplayIndex(itemCount);
9551
9675
  setIsWrapping(true);
9552
9676
  }
9553
- else {
9554
- setActiveIndex(activeIndex + 1);
9555
- }
9556
- }, [activeIndex, itemCount, repeat, setActiveIndex]);
9557
- const goToSlide = React.useCallback((index) => {
9558
- setIsWrapping(false);
9559
- setActiveIndex(index);
9560
- }, [setActiveIndex]);
9677
+ }, [nextIndex, looping, itemCount, setActiveIndex]);
9561
9678
  // Read when the timer fires or an arrow key arrives, so neither the
9562
9679
  // timer nor the key listener starts over while `onChange` changes
9563
9680
  // identity, as an inline one does on every render of the parent.
@@ -9565,15 +9682,28 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9565
9682
  const goToNextRef = useLatestRef(goToNext);
9566
9683
  // Auto-play. Each slide gets the whole interval, so a slide change,
9567
9684
  // autoplay's own or the user's, starts the timer over. A render that
9568
- // leaves the slide where it is doesn't.
9685
+ // leaves the slide where it is doesn't. It waits while focus is inside
9686
+ // an item, so what someone is reading or using doesn't move away. The
9687
+ // timer reads where focus is when it fires, rather than trusting the
9688
+ // last focus event alone: a browser moving focus off an element that
9689
+ // went inert need not fire one, and the pause would never end. A focus
9690
+ // change still starts the timer over, so an item gets the whole
9691
+ // interval once focus leaves it.
9692
+ const isPlaying = autoplay && (looping || nextIndex !== undefined) && !isPaused;
9693
+ const isRotating = isPlaying && !isFocusInside;
9569
9694
  React.useEffect(() => {
9570
- if (!autoplay || isPaused || itemCount <= 1)
9695
+ if (!isPlaying)
9571
9696
  return undefined;
9572
9697
  const timer = setInterval(() => {
9698
+ const track = slidesRef.current;
9699
+ if (track?.contains(getActiveElementInTree(track)))
9700
+ return;
9701
+ if (isFocusInside)
9702
+ setIsFocusInside(false);
9573
9703
  goToNextRef.current();
9574
9704
  }, interval);
9575
9705
  return () => clearInterval(timer);
9576
- }, [autoplay, isPaused, interval, itemCount, activeIndex, goToNextRef]);
9706
+ }, [isPlaying, isFocusInside, interval, activeIndex, goToNextRef]);
9577
9707
  // Pause on hover
9578
9708
  const handleMouseEnter = React.useCallback(() => {
9579
9709
  if (pauseOnHover) {
@@ -9632,12 +9762,16 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9632
9762
  const handleTouchEnd = React.useCallback(() => {
9633
9763
  handleDragEnd();
9634
9764
  }, [handleDragEnd]);
9635
- // Keyboard navigation
9765
+ // Keyboard navigation, from the carousel's own controls. Inside an item
9766
+ // the keys are its content's (a field's caret, a slider), and a step
9767
+ // from there could take the focused item out of view.
9636
9768
  React.useEffect(() => {
9637
9769
  const handleKeyDown = (e) => {
9638
9770
  const node = carouselRef.current;
9639
- if (!node?.contains(getActiveElementInTree(node)))
9771
+ const active = getActiveElementInTree(node);
9772
+ if (!node?.contains(active) || slidesRef.current?.contains(active)) {
9640
9773
  return;
9774
+ }
9641
9775
  switch (e.key) {
9642
9776
  case 'ArrowLeft':
9643
9777
  e.preventDefault();
@@ -9652,22 +9786,14 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9652
9786
  document.addEventListener('keydown', handleKeyDown);
9653
9787
  return () => document.removeEventListener('keydown', handleKeyDown);
9654
9788
  }, [goToPrevRef, goToNextRef]);
9655
- // Handle wrap-around transition end
9656
- const handleTransitionEnd = React.useCallback(() => {
9657
- if (isWrapping) {
9658
- // Skip transition for the instant snap
9789
+ // Handle wrap-around transition end: the track's own, since an item's
9790
+ // content can end a transition of its own that bubbles up to here
9791
+ const handleTransitionEnd = React.useCallback((e) => {
9792
+ if (isWrapping && e.target === e.currentTarget) {
9793
+ // Skip transition for the instant snap from the clones to the
9794
+ // real items they copy
9659
9795
  setSkipTransition(true);
9660
- setIsWrapping(false);
9661
- if (displayIndex === itemCount) {
9662
- // Was at clone of first slide, snap to real first slide
9663
- setActiveIndex(0, false);
9664
- setDisplayIndex(0);
9665
- }
9666
- else if (displayIndex === -1) {
9667
- // Was at clone of last slide, snap to real last slide
9668
- setActiveIndex(itemCount - 1, false);
9669
- setDisplayIndex(itemCount - 1);
9670
- }
9796
+ setActiveIndex(((displayIndex % itemCount) + itemCount) % itemCount);
9671
9797
  // Re-enable transition after the snap
9672
9798
  requestAnimationFrame(() => {
9673
9799
  requestAnimationFrame(() => {
@@ -9709,8 +9835,8 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9709
9835
  }
9710
9836
  }, [ref]);
9711
9837
  // Can navigate
9712
- const canGoPrev = repeat || activeIndex > 0;
9713
- const canGoNext = repeat || activeIndex < itemCount - 1;
9838
+ const canGoPrev = looping || prevIndex !== undefined;
9839
+ const canGoNext = looping || nextIndex !== undefined;
9714
9840
  // Generate classes
9715
9841
  const carouselClasses = usePrefixedClassNames('carousel', {
9716
9842
  'is-overlay': indicatorInside,
@@ -9728,50 +9854,101 @@ const Carousel = React.forwardRef(({ value: controlledValue, autoplay = false, i
9728
9854
  'is-top': indicatorPosition === 'top',
9729
9855
  });
9730
9856
  const combinedClasses = classNames(carouselClasses, bulmaHelperClasses, className);
9731
- // Render slides with active state (includes clones for infinite loop)
9732
- const renderSlides = () => {
9733
- const slides = items.map((item, index) => {
9734
- if (React.isValidElement(item)) {
9735
- return React.cloneElement(item, {
9736
- key: index,
9737
- active: index === activeIndex,
9738
- });
9739
- }
9740
- return item;
9857
+ // Where the track is drawn from: mid-wrap the clones, otherwise a start
9858
+ // the view can show, so a change in the count in view never paints
9859
+ // empty space while the sync above catches up.
9860
+ const shownIndex = isWrapping ? displayIndex : fit(displayIndex);
9861
+ // Enough clones on each side to fill the widest view any viewport sets,
9862
+ // since the stylesheet, not this count, sizes what's on screen
9863
+ const cloneCount = looping
9864
+ ? Math.min(itemCount, Math.max(baseCount ?? 1, ...counts.map(c => c ?? 1)))
9865
+ : 0;
9866
+ // The track: a full view's worth of clones of the last items, the items,
9867
+ // and clones of the first ones, for a seamless loop. Each entry is the
9868
+ // item it shows and its key.
9869
+ const slots = items.map((_, index) => [
9870
+ index,
9871
+ index,
9872
+ ]);
9873
+ for (let k = 0; k < cloneCount; k++) {
9874
+ slots.splice(k, 0, [itemCount - cloneCount + k, `clone-before-${k}`]);
9875
+ slots.push([k, `clone-after-${k}`]);
9876
+ }
9877
+ // Render slides with active state (includes clones for infinite loop).
9878
+ // What's in view is a run of slots, so where a looping view runs past
9879
+ // the last item, the clones on screen are the live copies and the items
9880
+ // they copy, off screen, are the hidden ones: no item is exposed twice,
9881
+ // and the copy exposed is the one in view.
9882
+ const renderSlides = () => slots.map(([index, key], slot) => {
9883
+ const item = items[index];
9884
+ const own = item.props;
9885
+ const isShown = slot >= shownIndex + cloneCount &&
9886
+ slot < shownIndex + cloneCount + inView;
9887
+ return React.cloneElement(item, {
9888
+ key,
9889
+ active: isShown,
9890
+ role: own.role ?? 'group',
9891
+ 'aria-roledescription': own['aria-roledescription'] ?? 'slide',
9892
+ ...(own['aria-label'] || own['aria-labelledby']
9893
+ ? {}
9894
+ : { 'aria-label': `${index + 1} of ${itemCount}` }),
9895
+ // Out of view: hidden from assistive tech, and inert, so nothing
9896
+ // in it takes focus or a click
9897
+ ...(isShown ? {} : { 'aria-hidden': true, ...inertProps(true) }),
9741
9898
  });
9742
- // Add clones for seamless infinite loop when repeat is enabled
9743
- if (repeat && itemCount > 1) {
9744
- const lastItem = items[itemCount - 1];
9745
- const firstItem = items[0];
9746
- const lastClone = React.isValidElement(lastItem)
9747
- ? React.cloneElement(lastItem, {
9748
- key: 'clone-last',
9749
- active: false,
9750
- })
9751
- : null;
9752
- const firstClone = React.isValidElement(firstItem)
9753
- ? React.cloneElement(firstItem, {
9754
- key: 'clone-first',
9755
- active: false,
9756
- })
9757
- : null;
9758
- return [lastClone, ...slides, firstClone];
9899
+ });
9900
+ // Hand focus on when the item holding it leaves the view. An item out
9901
+ // of view is inert, and a browser drops focus from an inert element to
9902
+ // the page, so a swipe, a change of count with the window's width, a new
9903
+ // controlled value or the item's removal would leave a keyboard or
9904
+ // screen reader user nowhere. Focus goes to the first item in view,
9905
+ // given `tabindex="-1"` for it unless it has a tabindex of its own. A
9906
+ // layout effect runs before the browser gets to drop the focus; the
9907
+ // focused item's removal drops it at once, so focus on the page itself
9908
+ // still counts as the item's.
9909
+ useClientLayoutEffect(() => {
9910
+ const held = focusedItemRef.current;
9911
+ if (!held)
9912
+ return;
9913
+ const track = slidesRef.current;
9914
+ const start = shownIndex + cloneCount;
9915
+ const slot = Array.prototype.indexOf.call(track.children, held);
9916
+ if (slot >= start && slot < start + inView)
9917
+ return;
9918
+ const active = getActiveElementInTree(track);
9919
+ if (active !== held.ownerDocument.body && !held.contains(active)) {
9920
+ return;
9759
9921
  }
9760
- return slides;
9761
- };
9762
- return (jsxRuntime.jsxs("div", { ref: combinedRef, className: combinedClasses, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "region", "aria-roledescription": "carousel", "aria-label": ariaLabel, ...rest, children: [jsxRuntime.jsxs("div", { ref: containerRef, className: carouselContainerClass, onMouseDown: hasDrag ? handleMouseDown : undefined, onMouseMove: hasDrag && isDragging ? handleMouseMove : undefined, onMouseUp: hasDrag ? handleMouseUp : undefined, onMouseLeave: hasDrag ? handleMouseUp : undefined, onTouchStart: hasDrag ? handleTouchStart : undefined, onTouchMove: hasDrag ? handleTouchMove : undefined, onTouchEnd: hasDrag ? handleTouchEnd : undefined, style: {
9922
+ focusedItemRef.current = undefined;
9923
+ const first = track.children[start];
9924
+ if (!first)
9925
+ return;
9926
+ if (!first.hasAttribute('tabindex'))
9927
+ first.tabIndex = -1;
9928
+ first.focus({ preventScroll: true });
9929
+ }, [shownIndex, cloneCount, inView, itemCount]);
9930
+ return (jsxRuntime.jsxs("div", { ref: combinedRef, className: combinedClasses, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "region", "aria-roledescription": "carousel", "aria-label": ariaLabel, style: { ...countStyle, ...style }, ...rest, children: [jsxRuntime.jsxs("div", { ref: containerRef, className: carouselContainerClass, onMouseDown: hasDrag ? handleMouseDown : undefined, onMouseMove: hasDrag && isDragging ? handleMouseMove : undefined, onMouseUp: hasDrag ? handleMouseUp : undefined, onMouseLeave: hasDrag ? handleMouseUp : undefined, onTouchStart: hasDrag ? handleTouchStart : undefined, onTouchMove: hasDrag ? handleTouchMove : undefined, onTouchEnd: hasDrag ? handleTouchEnd : undefined, style: {
9763
9931
  transform: isDragging ? `translateX(${dragOffset}px)` : undefined,
9764
9932
  cursor: hasDrag ? (isDragging ? 'grabbing' : 'grab') : undefined,
9765
- }, children: [jsxRuntime.jsx("div", { className: carouselSlidesClass, style: {
9766
- // When repeat is enabled, account for the prepended clone (+1 offset)
9767
- transform: `translateX(-${(repeat && itemCount > 1 ? displayIndex + 1 : displayIndex) * 100}%)`,
9933
+ }, children: [jsxRuntime.jsx("div", { ref: slidesRef, className: carouselSlidesClass, style: {
9934
+ // One item is the track's width plus a gap, over the count the
9935
+ // stylesheet has in view; account for the prepended clones
9936
+ transform: `translateX(calc(${-(shownIndex + cloneCount)} * (100% + var(--bulma-carousel-item-gap, 0px)) / var(--bulma-carousel-items-in-view, 1)))`,
9768
9937
  // Disable transition during drag or instant snap after wrap-around
9769
9938
  transition: isDragging || skipTransition
9770
9939
  ? 'none'
9771
9940
  : 'transform 0.3s ease-in-out',
9772
- }, onTransitionEnd: handleTransitionEnd, children: renderSlides() }), arrow && itemCount > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Button, { type: "button", className: carouselArrowPrevClass, onClick: goToPrev, isDisabled: !canGoPrev, "aria-label": "Previous slide", children: iconPrev ? (jsxRuntime.jsx(Icon, { name: iconPrev, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultPrevIcon, {})) }), jsxRuntime.jsx(Button, { type: "button", className: carouselArrowNextClass, onClick: goToNext, isDisabled: !canGoNext, "aria-label": "Next slide", children: iconNext ? (jsxRuntime.jsx(Icon, { name: iconNext, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultNextIcon, {})) })] }))] }), indicator && itemCount > 1 && (jsxRuntime.jsx("div", { className: carouselIndicatorClass, role: "tablist", children: items.map((_, index) => (jsxRuntime.jsx(Button, { type: "button", className: prefixedClassNames(classPrefix, 'indicator-item', {
9773
- 'is-active': index === activeIndex,
9774
- }), onClick: () => goToSlide(index), role: "tab", "aria-selected": index === activeIndex, "aria-label": `Go to slide ${index + 1}` }, index))) }))] }));
9941
+ }, "aria-live": isRotating ? 'off' : 'polite', onFocus: e => {
9942
+ setIsFocusInside(true);
9943
+ focusedItemRef.current = Array.from(e.currentTarget.children).find(item => item.contains(e.target));
9944
+ }, onBlur: e => {
9945
+ const inside = e.currentTarget.contains(e.relatedTarget);
9946
+ setIsFocusInside(inside);
9947
+ if (!inside)
9948
+ focusedItemRef.current = undefined;
9949
+ }, onTransitionEnd: handleTransitionEnd, children: renderSlides() }), arrow && canMove && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Button, { type: "button", className: carouselArrowPrevClass, onClick: goToPrev, isDisabled: !canGoPrev, "aria-label": "Previous slide", children: iconPrev ? (jsxRuntime.jsx(Icon, { name: iconPrev, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultPrevIcon, {})) }), jsxRuntime.jsx(Button, { type: "button", className: carouselArrowNextClass, onClick: goToNext, isDisabled: !canGoNext, "aria-label": "Next slide", children: iconNext ? (jsxRuntime.jsx(Icon, { name: iconNext, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })) : (jsxRuntime.jsx(DefaultNextIcon, {})) })] }))] }), indicator && canMove && (jsxRuntime.jsx("div", { className: carouselIndicatorClass, role: "tablist", children: positions.map((position, index) => (jsxRuntime.jsx(Button, { type: "button", className: prefixedClassNames(classPrefix, 'indicator-item', {
9950
+ 'is-active': index === activePosition,
9951
+ }), onClick: () => setActiveIndex(position), role: "tab", "aria-selected": index === activePosition, "aria-label": `Go to slide ${position + 1}` }, position))) }))] }));
9775
9952
  });
9776
9953
  Carousel.displayName = 'Carousel';
9777
9954