// packages/snap/src/debounce.ts
function debounce(callback, delay) {
  let timer;
  return function(...args) {
    let context = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = void 0;
      callback.apply(context, args);
    }, delay);
  };
}

// packages/snap/src/element.ts
function removeParentSticky(element) {
  const position = getComputedStyle(element).position;
  const isSticky = position === "sticky";
  if (isSticky) {
    element.style.setProperty("position", "static");
    element.dataset.sticky = "true";
  }
  if (element.offsetParent) {
    removeParentSticky(element.offsetParent);
  }
}
function addParentSticky(element) {
  if (element?.dataset?.sticky === "true") {
    element.style.removeProperty("position");
    delete element.dataset.sticky;
  }
  if (element.offsetParent) {
    addParentSticky(element.offsetParent);
  }
}
function offsetTop(element, accumulator = 0) {
  const top = accumulator + element.offsetTop;
  if (element.offsetParent) {
    return offsetTop(element.offsetParent, top);
  }
  return top;
}
function offsetLeft(element, accumulator = 0) {
  const left = accumulator + element.offsetLeft;
  if (element.offsetParent) {
    return offsetLeft(element.offsetParent, left);
  }
  return left;
}
function scrollTop(element, accumulator = 0) {
  const top = accumulator + element.scrollTop;
  if (element.offsetParent) {
    return scrollTop(element.offsetParent, top);
  }
  return top + window.scrollY;
}
function scrollLeft(element, accumulator = 0) {
  const left = accumulator + element.scrollLeft;
  if (element.offsetParent) {
    return scrollLeft(element.offsetParent, left);
  }
  return left + window.scrollX;
}
var SnapElement = class {
  element;
  options;
  align;
  // @ts-ignore
  rect = {};
  wrapperResizeObserver;
  resizeObserver;
  debouncedWrapperResize;
  constructor(element, {
    align = ["start"],
    ignoreSticky = true,
    ignoreTransform = false
  } = {}) {
    this.element = element;
    this.options = { align, ignoreSticky, ignoreTransform };
    this.align = [align].flat();
    this.debouncedWrapperResize = debounce(this.onWrapperResize, 500);
    this.wrapperResizeObserver = new ResizeObserver(this.debouncedWrapperResize);
    this.wrapperResizeObserver.observe(document.body);
    this.onWrapperResize();
    this.resizeObserver = new ResizeObserver(this.onResize);
    this.resizeObserver.observe(this.element);
    this.setRect({
      width: this.element.offsetWidth,
      height: this.element.offsetHeight
    });
  }
  destroy() {
    this.wrapperResizeObserver.disconnect();
    this.resizeObserver.disconnect();
  }
  setRect({
    top,
    left,
    width,
    height,
    element
  } = {}) {
    top = top ?? this.rect.top;
    left = left ?? this.rect.left;
    width = width ?? this.rect.width;
    height = height ?? this.rect.height;
    element = element ?? this.rect.element;
    if (top === this.rect.top && left === this.rect.left && width === this.rect.width && height === this.rect.height && element === this.rect.element)
      return;
    this.rect.top = top;
    this.rect.y = top;
    this.rect.width = width;
    this.rect.height = height;
    this.rect.left = left;
    this.rect.x = left;
    this.rect.bottom = top + height;
    this.rect.right = left + width;
  }
  onWrapperResize = () => {
    let top, left;
    if (this.options.ignoreSticky) removeParentSticky(this.element);
    if (this.options.ignoreTransform) {
      top = offsetTop(this.element);
      left = offsetLeft(this.element);
    } else {
      const rect = this.element.getBoundingClientRect();
      top = rect.top + scrollTop(this.element);
      left = rect.left + scrollLeft(this.element);
    }
    if (this.options.ignoreSticky) addParentSticky(this.element);
    this.setRect({ top, left });
  };
  onResize = ([entry]) => {
    if (!entry?.borderBoxSize[0]) return;
    const width = entry.borderBoxSize[0].inlineSize;
    const height = entry.borderBoxSize[0].blockSize;
    this.setRect({ width, height });
  };
};

// packages/snap/src/uid.ts
var index = 0;
function uid() {
  return index++;
}

// packages/snap/src/snap.ts
var Snap = class {
  constructor(lenis, {
    type = "proximity",
    lerp,
    easing,
    duration,
    distanceThreshold = "50%",
    // useless when type is "mandatory"
    debounce: debounceDelay = 500,
    onSnapStart,
    onSnapComplete
  } = {}) {
    this.lenis = lenis;
    this.options = {
      type,
      lerp,
      easing,
      duration,
      distanceThreshold,
      debounce: debounceDelay,
      onSnapStart,
      onSnapComplete
    };
    this.onWindowResize();
    window.addEventListener("resize", this.onWindowResize, false);
    this.onSnapDebounced = debounce(this.onSnap, this.options.debounce);
    this.lenis.on("virtual-scroll", this.onSnapDebounced);
  }
  options;
  elements = /* @__PURE__ */ new Map();
  snaps = /* @__PURE__ */ new Map();
  viewport = {
    width: window.innerWidth,
    height: window.innerHeight
  };
  isStopped = false;
  onSnapDebounced;
  currentSnapIndex;
  /**
   * Destroy the snap instance
   */
  destroy() {
    this.lenis.off("virtual-scroll", this.onSnapDebounced);
    window.removeEventListener("resize", this.onWindowResize, false);
    this.elements.forEach((element) => {
      element.destroy();
    });
  }
  /**
   * Start the snap after it has been stopped
   */
  start() {
    this.isStopped = false;
  }
  /**
   * Stop the snap
   */
  stop() {
    this.isStopped = true;
  }
  /**
   * Add a snap to the snap instance
   *
   * @param value The value to snap to
   * @param userData User data that will be forwarded through the snap event
   * @returns Unsubscribe function
   */
  add(value) {
    const id = uid();
    this.snaps.set(id, { value });
    return () => this.snaps.delete(id);
  }
  /**
   * Add an element to the snap instance
   *
   * @param element The element to add
   * @param options The options for the element
   * @returns Unsubscribe function
   */
  addElement(element, options = {}) {
    const id = uid();
    this.elements.set(id, new SnapElement(element, options));
    return () => this.elements.delete(id);
  }
  addElements(elements, options = {}) {
    const map = [...elements].map(
      (element) => this.addElement(element, options)
    );
    return () => {
      map.forEach((remove) => {
        remove();
      });
    };
  }
  onWindowResize = () => {
    this.viewport.width = window.innerWidth;
    this.viewport.height = window.innerHeight;
  };
  computeSnaps = () => {
    const { isHorizontal } = this.lenis;
    let snaps = [...this.snaps.values()];
    this.elements.forEach(({ rect, align }) => {
      let value;
      align.forEach((align2) => {
        if (align2 === "start") {
          value = rect.top;
        } else if (align2 === "center") {
          value = isHorizontal ? rect.left + rect.width / 2 - this.viewport.width / 2 : rect.top + rect.height / 2 - this.viewport.height / 2;
        } else if (align2 === "end") {
          value = isHorizontal ? rect.left + rect.width - this.viewport.width : rect.top + rect.height - this.viewport.height;
        }
        if (typeof value === "number") {
          snaps.push({ value: Math.ceil(value) });
        }
      });
    });
    snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value));
    return snaps;
  };
  previous() {
    this.goTo((this.currentSnapIndex ?? 0) - 1);
  }
  next() {
    this.goTo((this.currentSnapIndex ?? 0) + 1);
  }
  goTo(index2) {
    const snaps = this.computeSnaps();
    if (snaps.length === 0) return;
    this.currentSnapIndex = Math.max(0, Math.min(index2, snaps.length - 1));
    const currentSnap = snaps[this.currentSnapIndex];
    if (currentSnap === void 0) return;
    this.lenis.scrollTo(currentSnap.value, {
      duration: this.options.duration,
      easing: this.options.easing,
      lerp: this.options.lerp,
      lock: this.options.type === "lock",
      userData: { initiator: "snap" },
      onStart: () => {
        this.options.onSnapStart?.({
          index: this.currentSnapIndex,
          ...currentSnap
        });
      },
      onComplete: () => {
        this.options.onSnapComplete?.({
          index: this.currentSnapIndex,
          ...currentSnap
        });
      }
    });
  }
  get distanceThreshold() {
    let distanceThreshold = Infinity;
    if (this.options.type === "mandatory") return Infinity;
    const { isHorizontal } = this.lenis;
    const axis = isHorizontal ? "width" : "height";
    if (typeof this.options.distanceThreshold === "string" && this.options.distanceThreshold.endsWith("%")) {
      distanceThreshold = Number(this.options.distanceThreshold.replace("%", "")) / 100 * this.viewport[axis];
    } else if (typeof this.options.distanceThreshold === "number") {
      distanceThreshold = this.options.distanceThreshold;
    } else {
      distanceThreshold = this.viewport[axis];
    }
    return distanceThreshold;
  }
  onSnap = (e) => {
    if (this.isStopped) return;
    if (e.event.type === "touchmove") return;
    if (this.options.type === "lock" && this.lenis.userData?.initiator === "snap")
      return;
    let { scroll, isHorizontal } = this.lenis;
    const delta = isHorizontal ? e.deltaX : e.deltaY;
    scroll = Math.ceil(this.lenis.scroll + delta);
    const snaps = this.computeSnaps();
    if (snaps.length === 0) return;
    let snapIndex;
    const prevSnapIndex = snaps.findLastIndex(({ value }) => value < scroll);
    const nextSnapIndex = snaps.findIndex(({ value }) => value > scroll);
    if (this.options.type === "lock") {
      if (delta > 0) {
        snapIndex = nextSnapIndex;
      } else if (delta < 0) {
        snapIndex = prevSnapIndex;
      }
    } else {
      const prevSnap = snaps[prevSnapIndex];
      const distanceToPrevSnap = prevSnap ? Math.abs(scroll - prevSnap.value) : Infinity;
      const nextSnap = snaps[nextSnapIndex];
      const distanceToNextSnap = nextSnap ? Math.abs(scroll - nextSnap.value) : Infinity;
      snapIndex = distanceToPrevSnap < distanceToNextSnap ? prevSnapIndex : nextSnapIndex;
    }
    if (snapIndex === void 0) return;
    if (snapIndex === -1) return;
    snapIndex = Math.max(0, Math.min(snapIndex, snaps.length - 1));
    const snap = snaps[snapIndex];
    const distance = Math.abs(scroll - snap.value);
    if (distance <= this.distanceThreshold) {
      this.goTo(snapIndex);
    }
  };
  resize() {
    this.elements.forEach((element) => element.onWrapperResize());
  }
};
export {
  Snap as default
};
//# sourceMappingURL=lenis-snap.mjs.map