UNPKG

one

Version:

One is a React framework for web, iOS, and Android apps.

189 lines (186 loc) • 6.03 kB
import { Children, forwardRef, isValidElement, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { validatePage, validatePager } from "./pagerTypes.mjs"; import { domStyle } from "../web/DomView.mjs"; import { jsx } from "react/jsx-runtime"; function event(nativeEvent) { return { nativeEvent }; } const Pager = forwardRef(function Pager2({ children, initialPage = 0, scrollEnabled = true, orientation = "horizontal", layoutDirection = "ltr", pageMargin = 0, onPageScroll, onPageSelected, onPageScrollStateChanged, keyboardDismissMode = "none", style, testID, ...props }, ref) { validatePager({ ...props, initialPage, orientation, layoutDirection, pageMargin, keyboardDismissMode }); const container = useRef(null); const callbacks = useRef({ onPageScroll, onPageSelected, onPageScrollStateChanged }); callbacks.current = { onPageScroll, onPageSelected, onPageScrollStateChanged }; const pointer = useRef(null); const selected = useRef(initialPage); const initialized = useRef(false); const state = useRef("idle"); const [enabled, setEnabled] = useState(scrollEnabled); const vertical = orientation === "vertical"; const count = Children.count(children); const dimensions = useRef({ vertical, rtl: layoutDirection === "rtl", pageMargin, count }); dimensions.current = { vertical, rtl: layoutDirection === "rtl", pageMargin, count }; function setState(next) { if (state.current === next) return; state.current = next; callbacks.current.onPageScrollStateChanged?.(event({ pageScrollState: next })); } function scroll() { const node = container.current; if (!node || !count) return; const stride = (vertical ? node.clientHeight : node.clientWidth) + pageMargin; if (!stride) return; const value = Math.max(0, Math.min(count - 1, (vertical ? node.scrollTop : Math.abs(node.scrollLeft)) / stride)); callbacks.current.onPageScroll?.(event({ position: Math.floor(value), offset: value - Math.floor(value) })); } function settle() { const node = container.current; if (!node || !count) return; const stride = (vertical ? node.clientHeight : node.clientWidth) + pageMargin; if (!stride) return; const page = Math.max(0, Math.min(count - 1, Math.round((vertical ? node.scrollTop : Math.abs(node.scrollLeft)) / stride))); if (page !== selected.current || !initialized.current) { selected.current = page; callbacks.current.onPageSelected?.(event({ position: page })); } initialized.current = true; callbacks.current.onPageScroll?.(event({ position: page, offset: 0 })); setState("idle"); } function goTo(index, animated) { validatePage(index); const node = container.current; const d = dimensions.current; if (!node || index >= d.count) return; const offset = index * ((d.vertical ? node.clientHeight : node.clientWidth) + d.pageMargin); if (animated && index !== selected.current) setState("settling"); node.scrollTo({ left: d.vertical ? 0 : offset * (d.rtl ? -1 : 1), top: d.vertical ? offset : 0, behavior: animated ? "smooth" : "instant" }); if (!animated) settle(); } useImperativeHandle(ref, () => ({ setPage: (index) => goTo(index, true), setPageWithoutAnimation: (index) => goTo(index, false), setScrollEnabled: setEnabled })); useEffect(() => setEnabled(scrollEnabled), [scrollEnabled]); useLayoutEffect(() => { const node = container.current; if (!node) return; const resize = new ResizeObserver(() => goTo(Math.min(selected.current, Math.max(0, count - 1)), false)); resize.observe(node); goTo(Math.min(selected.current, Math.max(0, count - 1)), false); node.addEventListener("scrollend", settle); return () => { resize.disconnect(); node.removeEventListener("scrollend", settle); }; }, [ count, orientation, layoutDirection, pageMargin ]); const css = { ...domStyle(style), display: "flex", position: "relative", flexDirection: vertical ? "column" : "row", direction: layoutDirection, overflowX: enabled && !vertical ? "auto" : "hidden", overflowY: enabled && vertical ? "auto" : "hidden", scrollSnapType: `${vertical ? "y" : "x"} mandatory`, gap: pageMargin, scrollbarWidth: "none", overscrollBehavior: "contain" }; return /* @__PURE__ */ jsx("div", { ref: container, style: css, "data-testid": testID, onScroll: scroll, onPointerDown: (e) => { const node = container.current; if (!enabled || !node) return; pointer.current = { x: e.clientX, y: e.clientY, offset: vertical ? node.scrollTop : node.scrollLeft }; }, onPointerMove: (e) => { const start = pointer.current; if (!start || !enabled || state.current === "dragging") return; const distance = vertical ? Math.abs(e.clientY - start.y) : Math.abs(e.clientX - start.x); if (distance < 8) return; setState("dragging"); if (keyboardDismissMode === "on-drag" && document.activeElement instanceof HTMLElement) document.activeElement.blur(); }, onPointerUp: () => { const start = pointer.current; const node = container.current; pointer.current = null; if (state.current !== "dragging" || !node) return; if ((vertical ? node.scrollTop : node.scrollLeft) === start?.offset) settle(); else setState("settling"); }, onPointerCancel: () => { pointer.current = null; if (state.current === "dragging") setState("settling"); }, onWheel: () => { if (enabled) setState("dragging"); }, children: Children.map(children, (child, index) => { if (!isValidElement(child)) throw new Error("Pager children must be React Native views"); return /* @__PURE__ */ jsx("div", { style: { flex: "0 0 100%", display: "flex", flexDirection: "column", width: vertical ? "100%" : void 0, height: vertical ? void 0 : "100%", scrollSnapAlign: "start", position: "relative", overflow: "hidden" }, children: child }, index); }) }); }); export { Pager }; //# sourceMappingURL=Pager.mjs.map