All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.dist.index.mjs Maven / Gradle / Ivy

The newest version!
import { createAnatomy } from '@zag-js/anatomy';
import { createScope, queryAll, isDom, dataAttr } from '@zag-js/dom-query';
import { createSplitProps, compact, nextIndex, prevIndex, isEqual, isNumber } from '@zag-js/utils';
import { createMachine, ref } from '@zag-js/core';
import { createProps } from '@zag-js/types';

// src/carousel.anatomy.ts
var anatomy = createAnatomy("carousel").parts(
  "root",
  "viewport",
  "itemGroup",
  "item",
  "nextTrigger",
  "prevTrigger",
  "indicatorGroup",
  "indicator"
);
var parts = anatomy.build();
var dom = createScope({
  getRootId: (ctx) => ctx.ids?.root ?? `carousel:${ctx.id}`,
  getViewportId: (ctx) => ctx.ids?.viewport ?? `carousel:${ctx.id}:viewport`,
  getItemId: (ctx, index) => ctx.ids?.item?.(index) ?? `carousel:${ctx.id}:item:${index}`,
  getItemGroupId: (ctx) => ctx.ids?.itemGroup ?? `carousel:${ctx.id}:item-group`,
  getNextTriggerId: (ctx) => ctx.ids?.nextTrigger ?? `carousel:${ctx.id}:next-trigger`,
  getPrevTriggerId: (ctx) => ctx.ids?.prevTrigger ?? `carousel:${ctx.id}:prev-trigger`,
  getIndicatorGroupId: (ctx) => ctx.ids?.indicatorGroup ?? `carousel:${ctx.id}:indicator-group`,
  getIndicatorId: (ctx, index) => ctx.ids?.indicator?.(index) ?? `carousel:${ctx.id}:indicator:${index}`,
  getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
  getViewportEl: (ctx) => dom.getById(ctx, dom.getViewportId(ctx)),
  getSlideGroupEl: (ctx) => dom.getById(ctx, dom.getItemGroupId(ctx)),
  getSlideEls: (ctx) => queryAll(dom.getSlideGroupEl(ctx), `[data-part=item]`)
});

// src/utils/get-limit.ts
function getLimit(min, max) {
  const length = Math.abs(min - max);
  function reachedMin(n) {
    return n < min;
  }
  function reachedMax(n) {
    return n > max;
  }
  function reachedAny(n) {
    return reachedMin(n) || reachedMax(n);
  }
  function constrain(n) {
    if (!reachedAny(n)) return n;
    return reachedMin(n) ? min : max;
  }
  function removeOffset(n) {
    if (!length) return n;
    return n - length * Math.ceil((n - max) / length);
  }
  return {
    length,
    max,
    min,
    constrain,
    reachedAny,
    reachedMax,
    reachedMin,
    removeOffset
  };
}
var getAlignment = (align, containerSize) => {
  const predefined = { start, center, end };
  function start() {
    return 0;
  }
  function center(n) {
    return end(n) / 2;
  }
  function end(n) {
    return containerSize - n;
  }
  function percent() {
    return containerSize * Number(align);
  }
  return (n) => {
    if (isNumber(align)) return percent();
    return predefined[align](n);
  };
};
function getSlidesToScroll(containerSize, slideSizesWithGaps, slidesPerView) {
  function byNumber(array, groupSize) {
    return Array.from(array.keys()).filter((i) => i % groupSize === 0).map((i) => array.slice(i, i + groupSize));
  }
  function bySize(array) {
    return Array.from(array.keys()).reduce((groups, i) => {
      const chunk = slideSizesWithGaps.slice(groups.at(-1), i + 1);
      const chunkSize = chunk.reduce((a, s) => a + s, 0);
      return !i || chunkSize > containerSize ? groups.concat(i) : groups;
    }, []).map((start, i, groups) => array.slice(start, groups[i + 1]));
  }
  return function groupSlides(array) {
    return isNumber(slidesPerView) ? byNumber(array, slidesPerView) : bySize(array);
  };
}

// src/utils/get-slide-sizes.ts
function getSlideSizes(ctx) {
  const startGap = measureStartGap();
  function measureStartGap() {
    if (!ctx.containerRect) return 0;
    const slideRect = ctx.slideRects[0];
    return Math.abs(ctx.containerRect[ctx.startEdge] - slideRect[ctx.startEdge]);
  }
  function measureWithGaps() {
    return ctx.slideRects.map((rect, index, rects) => {
      const isFirst = !index;
      if (isFirst) return Math.abs(slideSizes[index] + startGap);
      const isLast = index === rects.length - 1;
      if (isLast) return Math.abs(slideSizes[index]);
      return Math.abs(rects[index + 1][ctx.startEdge] - rect[ctx.startEdge]);
    });
  }
  const slideSizes = ctx.slideRects.map((slideRect) => {
    return ctx.isVertical ? slideRect.height : slideRect.width;
  });
  const slideSizesWithGaps = measureWithGaps();
  return {
    slideSizes,
    slideSizesWithGaps
  };
}

// src/utils/get-scroll-snaps.ts
var arrayLast = (array) => array[arrayLastIndex(array)];
var arrayLastIndex = (array) => Math.max(0, array.length - 1);
function getScrollSnaps(ctx) {
  const { slideSizes, slideSizesWithGaps } = getSlideSizes(ctx);
  const groupSlides = getSlidesToScroll(ctx.containerSize, slideSizesWithGaps, ctx.slidesPerView);
  function measureSizes() {
    return groupSlides(ctx.slideRects).map((rects) => arrayLast(rects)[ctx.endEdge] - rects[0][ctx.startEdge]).map(Math.abs);
  }
  function measureUnaligned() {
    return ctx.slideRects.map((slideRect) => ctx.containerRect[ctx.startEdge] - slideRect[ctx.startEdge]).map((snap) => -Math.abs(snap));
  }
  function measureAligned() {
    const measureFn = getAlignment(ctx.align, ctx.containerSize);
    const alignments = measureSizes().map(measureFn);
    return groupSlides(snaps).map((snap) => snap[0]).map((snap, index) => snap + alignments[index]);
  }
  const snaps = measureUnaligned();
  const snapsAligned = measureAligned();
  const contentSize = -arrayLast(snaps) + arrayLast(slideSizesWithGaps);
  const scrollLimit = getLimit(snaps[snaps.length - 1], snaps[0]);
  const scrollProgress = (snapsAligned[ctx.index] - scrollLimit.max) / -scrollLimit.length;
  return {
    snaps,
    snapsAligned,
    slideSizes,
    slideSizesWithGaps,
    contentSize,
    scrollLimit,
    scrollProgress: Math.abs(scrollProgress)
  };
}

// src/utils/get-slide-in-view.ts
var slideThreshold = 0;
function getSlidesInView(ctx) {
  const roundingSafety = 0.5;
  const slideOffsets = [0];
  const { snaps, slideSizes, scrollLimit } = getScrollSnaps(ctx);
  const slideThresholds = slideSizes.map((slideSize) => {
    const thresholdLimit = getLimit(roundingSafety, slideSize - roundingSafety);
    return thresholdLimit.constrain(slideSize * slideThreshold);
  });
  const slideBounds = slideOffsets.reduce((acc, offset) => {
    const bounds = snaps.map((snap, index) => ({
      start: snap - slideSizes[index] + slideThresholds[index] + offset,
      end: snap + ctx.containerSize - slideThresholds[index] + offset,
      index
    }));
    return acc.concat(bounds);
  }, []);
  return (location) => {
    const loc = scrollLimit.constrain(location);
    return slideBounds.reduce((list, bound) => {
      const { index, start, end } = bound;
      const inList = list.includes(index);
      const inView = start < loc && end > loc;
      return !inList && inView ? list.concat([index]) : list;
    }, []);
  };
}

// src/carousel.connect.ts
function connect(state, send, normalize) {
  const canScrollNext = state.context.canScrollNext;
  const canScrollPrev = state.context.canScrollPrev;
  const horizontal = state.context.isHorizontal;
  const autoPlaying = state.matches("autoplay");
  const activeSnap = state.context.scrollSnaps[state.context.index];
  const slidesInView = isDom() ? getSlidesInView(state.context)(activeSnap) : [];
  function getItemState(props2) {
    return {
      valueText: `Slide ${props2.index + 1}`,
      current: props2.index === state.context.index,
      next: props2.index === state.context.index + 1,
      previous: props2.index === state.context.index - 1,
      inView: slidesInView.includes(props2.index)
    };
  }
  return {
    index: state.context.index,
    scrollProgress: state.context.scrollProgress,
    autoPlaying,
    canScrollNext,
    canScrollPrev,
    scrollTo(index, jump) {
      send({ type: "GOTO", index, jump });
    },
    scrollToNext() {
      send("NEXT");
    },
    scrollToPrevious() {
      send("PREV");
    },
    getItemState,
    play() {
      send("PLAY");
    },
    pause() {
      send("PAUSE");
    },
    getRootProps() {
      return normalize.element({
        ...parts.root.attrs,
        id: dom.getRootId(state.context),
        role: "region",
        "aria-roledescription": "carousel",
        "data-orientation": state.context.orientation,
        dir: state.context.dir,
        "aria-label": "Carousel",
        style: {
          "--slide-spacing": state.context.spacing,
          "--slide-size": `calc(100% / ${state.context.slidesPerView} - var(--slide-spacing))`
        }
      });
    },
    getViewportProps() {
      return normalize.element({
        ...parts.viewport.attrs,
        dir: state.context.dir,
        id: dom.getViewportId(state.context),
        "data-orientation": state.context.orientation
      });
    },
    getItemGroupProps() {
      return normalize.element({
        ...parts.itemGroup.attrs,
        id: dom.getItemGroupId(state.context),
        "data-orientation": state.context.orientation,
        dir: state.context.dir,
        style: {
          display: "flex",
          flexDirection: horizontal ? "row" : "column",
          [horizontal ? "height" : "width"]: "auto",
          gap: "var(--slide-spacing)",
          transform: state.context.translateValue,
          transitionProperty: "transform",
          willChange: "transform",
          transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
          transitionDuration: "0.3s"
        }
      });
    },
    getItemProps(props2) {
      const itemState = getItemState(props2);
      return normalize.element({
        ...parts.item.attrs,
        id: dom.getItemId(state.context, props2.index),
        dir: state.context.dir,
        "data-current": dataAttr(itemState.current),
        "data-inview": dataAttr(itemState.inView),
        role: "group",
        "aria-roledescription": "slide",
        "data-orientation": state.context.orientation,
        "aria-label": itemState.valueText,
        style: {
          position: "relative",
          flex: "0 0 var(--slide-size)",
          [horizontal ? "minWidth" : "minHeight"]: "0px"
        }
      });
    },
    getPrevTriggerProps() {
      return normalize.button({
        ...parts.prevTrigger.attrs,
        id: dom.getPrevTriggerId(state.context),
        type: "button",
        tabIndex: -1,
        disabled: !canScrollPrev,
        dir: state.context.dir,
        "aria-label": "Previous Slide",
        "data-orientation": state.context.orientation,
        "aria-controls": dom.getItemGroupId(state.context),
        onClick() {
          send("PREV");
        }
      });
    },
    getNextTriggerProps() {
      return normalize.button({
        ...parts.nextTrigger.attrs,
        dir: state.context.dir,
        id: dom.getNextTriggerId(state.context),
        type: "button",
        tabIndex: -1,
        "aria-label": "Next Slide",
        "data-orientation": state.context.orientation,
        "aria-controls": dom.getItemGroupId(state.context),
        disabled: !canScrollNext,
        onClick() {
          send("NEXT");
        }
      });
    },
    getIndicatorGroupProps() {
      return normalize.element({
        ...parts.indicatorGroup.attrs,
        dir: state.context.dir,
        id: dom.getIndicatorGroupId(state.context),
        "data-orientation": state.context.orientation
      });
    },
    getIndicatorProps(props2) {
      return normalize.button({
        ...parts.indicator.attrs,
        dir: state.context.dir,
        id: dom.getIndicatorId(state.context, props2.index),
        type: "button",
        "data-orientation": state.context.orientation,
        "data-index": props2.index,
        "data-readonly": dataAttr(props2.readOnly),
        "data-current": dataAttr(props2.index === state.context.index),
        onClick() {
          if (props2.readOnly) return;
          send({ type: "GOTO", index: props2.index });
        }
      });
    }
  };
}
function machine(userContext) {
  const ctx = compact(userContext);
  return createMachine(
    {
      id: "carousel",
      initial: "idle",
      context: {
        index: 0,
        orientation: "horizontal",
        align: "start",
        loop: false,
        slidesPerView: 1,
        spacing: "0px",
        ...ctx,
        scrollSnaps: [],
        scrollProgress: 0,
        containerSize: 0,
        slideRects: []
      },
      watch: {
        index: ["setScrollSnaps"]
      },
      on: {
        NEXT: {
          actions: ["scrollToNext"]
        },
        PREV: {
          actions: ["scrollToPrev"]
        },
        GOTO: {
          actions: ["scrollTo"]
        },
        MEASURE_DOM: {
          actions: ["measureElements", "setScrollSnaps"]
        },
        PLAY: "autoplay"
      },
      states: {
        idle: {
          on: {
            POINTER_DOWN: "dragging"
          }
        },
        autoplay: {
          activities: ["trackDocumentVisibility"],
          every: {
            2e3: ["scrollToNext"]
          },
          on: {
            PAUSE: "idle"
          }
        },
        dragging: {
          on: {
            POINTER_UP: "idle",
            POINTER_MOVE: {
              actions: ["setScrollSnaps"]
            }
          }
        }
      },
      activities: ["trackContainerResize", "trackSlideMutation"],
      entry: ["measureElements", "setScrollSnaps"],
      computed: {
        isRtl: (ctx2) => ctx2.dir === "rtl",
        isHorizontal: (ctx2) => ctx2.orientation === "horizontal",
        isVertical: (ctx2) => ctx2.orientation === "vertical",
        canScrollNext: (ctx2) => ctx2.loop || ctx2.index < ctx2.scrollSnaps.length - 1,
        canScrollPrev: (ctx2) => ctx2.loop || ctx2.index > 0,
        startEdge(ctx2) {
          if (ctx2.isVertical) return "top";
          return ctx2.isRtl ? "right" : "left";
        },
        endEdge(ctx2) {
          if (ctx2.isVertical) return "bottom";
          return ctx2.isRtl ? "left" : "right";
        },
        translateValue: (ctx2) => {
          const scrollSnap = ctx2.scrollSnaps[ctx2.index];
          return ctx2.isHorizontal ? `translate3d(${scrollSnap}px, 0, 0)` : `translate3d(0, ${scrollSnap}px, 0)`;
        }
      }
    },
    {
      activities: {
        trackSlideMutation(ctx2, _evt, { send }) {
          const slideGroupEl = dom.getSlideGroupEl(ctx2);
          if (!slideGroupEl) return;
          const win = dom.getWin(ctx2);
          const observer = new win.MutationObserver(() => {
            send({ type: "MEASURE_DOM", src: "mutation" });
          });
          observer.observe(slideGroupEl, { childList: true });
          return () => {
            observer.disconnect();
          };
        },
        trackContainerResize(ctx2, _evt, { send }) {
          const slideGroupEl = dom.getSlideGroupEl(ctx2);
          if (!slideGroupEl) return;
          const win = dom.getWin(ctx2);
          const observer = new win.ResizeObserver((entries) => {
            entries.forEach((entry) => {
              if (entry.target === slideGroupEl) {
                send({ type: "MEASURE_DOM", src: "resize" });
              }
            });
          });
          observer.observe(slideGroupEl);
          return () => {
            observer.disconnect();
          };
        },
        trackDocumentVisibility(ctx2, _evt, { send }) {
          const doc = dom.getDoc(ctx2);
          const onVisibilityChange = () => {
            if (doc.visibilityState !== "visible") {
              send({ type: "PAUSE", src: "document-hidden" });
            }
          };
          doc.addEventListener("visibilitychange", onVisibilityChange);
          return () => {
            doc.removeEventListener("visibilitychange", onVisibilityChange);
          };
        }
      },
      guards: {
        loop: (ctx2) => ctx2.loop,
        isLastSlide: (ctx2) => ctx2.index === ctx2.scrollSnaps.length - 1,
        isFirstSlide: (ctx2) => ctx2.index === 0
      },
      actions: {
        scrollToNext(ctx2) {
          const index = nextIndex(ctx2.scrollSnaps, ctx2.index);
          set.index(ctx2, index);
        },
        scrollToPrev(ctx2) {
          const index = prevIndex(ctx2.scrollSnaps, ctx2.index);
          set.index(ctx2, index);
        },
        setScrollSnaps(ctx2) {
          const { snapsAligned, scrollProgress } = getScrollSnaps(ctx2);
          ctx2.scrollSnaps = snapsAligned;
          ctx2.scrollProgress = scrollProgress;
        },
        scrollTo(ctx2, evt) {
          const index = Math.max(0, Math.min(evt.index, ctx2.scrollSnaps.length - 1));
          set.index(ctx2, index);
        },
        measureElements
      }
    }
  );
}
var measureElements = (ctx) => {
  const slideGroupEl = dom.getSlideGroupEl(ctx);
  if (!slideGroupEl) return;
  ctx.containerRect = ref(slideGroupEl.getBoundingClientRect());
  ctx.containerSize = ctx.isHorizontal ? ctx.containerRect.width : ctx.containerRect.height;
  ctx.slideRects = ref(dom.getSlideEls(ctx).map((slide) => slide.getBoundingClientRect()));
};
var invoke = {
  change: (ctx) => {
    ctx.onIndexChange?.({ index: ctx.index });
  }
};
var set = {
  index: (ctx, index) => {
    if (isEqual(ctx.index, index)) return;
    ctx.index = index;
    invoke.change(ctx);
  }
};
var props = createProps()([
  "align",
  "dir",
  "getRootNode",
  "id",
  "ids",
  "index",
  "loop",
  "onIndexChange",
  "orientation",
  "slidesPerView",
  "spacing"
]);
var splitProps = createSplitProps(props);
var indicatorProps = createProps()(["index", "readOnly"]);
var splitIndicatorProps = createSplitProps(indicatorProps);

export { anatomy, connect, indicatorProps, machine, props, splitIndicatorProps, splitProps };




© 2015 - 2025 Weber Informatics LLC | Privacy Policy