UNPKG

@zag-js/scroll-snap

Version:

Scroll snap utilities

235 lines (233 loc) 9.41 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { findSnapPoint: () => findSnapPoint, getScrollSnapPositions: () => getScrollSnapPositions, getSnapPointTarget: () => getSnapPointTarget, getSnapPositions: () => getSnapPositions }); module.exports = __toCommonJS(index_exports); var import_dom_query = require("@zag-js/dom-query"); var getDirection = (element) => (0, import_dom_query.getComputedStyle)(element).direction; var convert = (raw, size) => { let n = parseFloat(raw); if (/%/.test(raw)) { n /= 100; n *= size; } return Number.isNaN(n) ? 0 : n; }; function getScrollPadding(element) { const style = (0, import_dom_query.getComputedStyle)(element); const layoutWidth = element.offsetWidth; const layoutHeight = element.offsetHeight; let xBeforeRaw = style.getPropertyValue("scroll-padding-left").replace("auto", "0px"); let yBeforeRaw = style.getPropertyValue("scroll-padding-top").replace("auto", "0px"); let xAfterRaw = style.getPropertyValue("scroll-padding-right").replace("auto", "0px"); let yAfterRaw = style.getPropertyValue("scroll-padding-bottom").replace("auto", "0px"); let xBefore = convert(xBeforeRaw, layoutWidth); let yBefore = convert(yBeforeRaw, layoutHeight); let xAfter = convert(xAfterRaw, layoutWidth); let yAfter = convert(yAfterRaw, layoutHeight); return { x: { before: xBefore, after: xAfter }, y: { before: yBefore, after: yAfter } }; } function isRectIntersecting(a, b, axis = "both") { return axis === "x" && a.right >= b.left && a.left <= b.right || axis === "y" && a.bottom >= b.top && a.top <= b.bottom || axis === "both" && a.right >= b.left && a.left <= b.right && a.bottom >= b.top && a.top <= b.bottom; } function getDescendants(parent) { let children = []; for (const child of parent.children) { children = children.concat(child, getDescendants(child)); } return children; } function getSnapPositions(parent, subtree = false) { const parentRect = parent.getBoundingClientRect(); const dir = getDirection(parent); const isRtl = dir === "rtl"; const scale = (0, import_dom_query.getScale)(parent); const positions = { x: { start: [], center: [], end: [] }, y: { start: [], center: [], end: [] } }; const children = subtree ? getDescendants(parent) : parent.children; for (const axis of ["x", "y"]) { const orthogonalAxis = axis === "x" ? "y" : "x"; const axisStart = axis === "x" ? "left" : "top"; const axisEnd = axis === "x" ? "right" : "bottom"; const axisSize = axis === "x" ? "width" : "height"; const axisScroll = axis === "x" ? "scrollLeft" : "scrollTop"; const axisScale = axis === "x" ? scale.x : scale.y; const useRtlCalc = isRtl && axis === "x"; for (const child of children) { const childRect = child.getBoundingClientRect(); if (!isRectIntersecting(parentRect, childRect, orthogonalAxis)) { continue; } const childStyle = (0, import_dom_query.getComputedStyle)(child); let [childAlignY, childAlignX] = childStyle.getPropertyValue("scroll-snap-align").split(" "); if (typeof childAlignX === "undefined") { childAlignX = childAlignY; } const childAlign = axis === "x" ? childAlignX : childAlignY; let childOffsetStart; let childOffsetEnd; let childOffsetCenter; if (useRtlCalc) { const scrollOffset = Math.abs(parent[axisScroll]); const rightOffset = (parentRect[axisEnd] - childRect[axisEnd]) / axisScale + scrollOffset; childOffsetStart = rightOffset; childOffsetEnd = rightOffset + childRect[axisSize] / axisScale; childOffsetCenter = rightOffset + childRect[axisSize] / (2 * axisScale); } else { childOffsetStart = (childRect[axisStart] - parentRect[axisStart]) / axisScale + parent[axisScroll]; childOffsetEnd = childOffsetStart + childRect[axisSize] / axisScale; childOffsetCenter = childOffsetStart + childRect[axisSize] / (2 * axisScale); } switch (childAlign) { case "none": break; case "start": positions[axis].start.push({ node: child, position: childOffsetStart }); break; case "center": positions[axis].center.push({ node: child, position: childOffsetCenter }); break; case "end": positions[axis].end.push({ node: child, position: childOffsetEnd }); break; } } } return positions; } function getScrollSnapPositions(element) { const dir = getDirection(element); const scrollPadding = getScrollPadding(element); const snapPositions = getSnapPositions(element); const layoutWidth = element.offsetWidth; const layoutHeight = element.offsetHeight; const maxScroll = { x: element.scrollWidth - element.offsetWidth, y: element.scrollHeight - element.offsetHeight }; const isRtl = dir === "rtl"; const usesNegativeScrollLeft = isRtl && element.scrollLeft <= 0; let xPositions; if (isRtl) { xPositions = uniq( [ ...snapPositions.x.start.map((v) => v.position - scrollPadding.x.after), ...snapPositions.x.center.map((v) => v.position - layoutWidth / 2), ...snapPositions.x.end.map((v) => v.position - layoutWidth + scrollPadding.x.before) ].map(clamp(0, maxScroll.x)) ); if (usesNegativeScrollLeft) { xPositions = xPositions.map((pos) => -pos); } } else { xPositions = uniq( [ ...snapPositions.x.start.map((v) => v.position - scrollPadding.x.before), ...snapPositions.x.center.map((v) => v.position - layoutWidth / 2), ...snapPositions.x.end.map((v) => v.position - layoutWidth + scrollPadding.x.after) ].map(clamp(0, maxScroll.x)) ); } return { x: xPositions, y: uniq( [ ...snapPositions.y.start.map((v) => v.position - scrollPadding.y.before), ...snapPositions.y.center.map((v) => v.position - layoutHeight / 2), ...snapPositions.y.end.map((v) => v.position - layoutHeight + scrollPadding.y.after) ].map(clamp(0, maxScroll.y)) ) }; } function findSnapPoint(parent, axis, predicate) { const dir = getDirection(parent); const scrollPadding = getScrollPadding(parent); const snapPositions = getSnapPositions(parent); const items = [...snapPositions[axis].start, ...snapPositions[axis].center, ...snapPositions[axis].end]; const isRtl = dir === "rtl"; const usesNegativeScrollLeft = isRtl && axis === "x" && parent.scrollLeft <= 0; for (const item of items) { if (predicate(item.node)) { let position; if (axis === "x" && isRtl) { position = item.position - scrollPadding.x.after; if (usesNegativeScrollLeft) { position = -position; } } else { position = item.position - (axis === "x" ? scrollPadding.x.before : scrollPadding.y.before); } return position; } } } function getSnapPointTarget(parent, snapPoint) { const rect = parent.getBoundingClientRect(); const scale = (0, import_dom_query.getScale)(parent); const scrollPadding = getScrollPadding(parent); const children = Array.from(parent.children); const layoutWidth = parent.offsetWidth; const layoutHeight = parent.offsetHeight; for (const child of children) { const childRect = child.getBoundingClientRect(); const childOffsetStart = { x: (childRect.left - rect.left) / scale.x + parent.scrollLeft, y: (childRect.top - rect.top) / scale.y + parent.scrollTop }; const childLayoutWidth = childRect.width / scale.x; const childLayoutHeight = childRect.height / scale.y; const matchesX = [ childOffsetStart.x - scrollPadding.x.before, // start childOffsetStart.x + childLayoutWidth / 2 - layoutWidth / 2, // center childOffsetStart.x + childLayoutWidth - layoutWidth + scrollPadding.x.after // end ].some((pos) => Math.abs(pos - snapPoint) < 1); const matchesY = [ childOffsetStart.y - scrollPadding.y.before, childOffsetStart.y + childLayoutHeight / 2 - layoutHeight / 2, childOffsetStart.y + childLayoutHeight - layoutHeight + scrollPadding.y.after ].some((pos) => Math.abs(pos - snapPoint) < 1); if (matchesX || matchesY) { return child; } } return children[0]; } var uniq = (arr) => [...new Set(arr)]; var clamp = (min, max) => (value) => Math.max(min, Math.min(max, value)); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { findSnapPoint, getScrollSnapPositions, getSnapPointTarget, getSnapPositions });