UNPKG

@stokr/components-library

Version:

STOKR - Components Library

138 lines (130 loc) 5.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useScrollActions = exports.scrollToElement = exports.default = exports.ScrollToTop = void 0; var _react = _interopRequireWildcard(require("react")); var _reactRouterDom = require("react-router-dom"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /** * Scrolls to an element with cross-platform compatibility * Works reliably on iOS, iPadOS, macOS, and other platforms * * @param {React.RefObject} elementRef - React ref object for the target element * @param {Object} options - Scrolling options * @param {string} options.behavior - Scroll behavior ('auto' or 'smooth') * @param {string} options.block - Vertical alignment ('start', 'center', 'end', 'nearest') * @param {number} options.delay - Optional delay in ms before scrolling */ const scrollToElement = function (elementRef) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const { behavior = 'smooth', block = 'start', delay = 0 } = options; const performScroll = () => { if (!(elementRef !== null && elementRef !== void 0 && elementRef.current)) return; // Detect iOS/iPadOS/macOS with touch const isAppleDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; // Use requestAnimationFrame to ensure the DOM has updated requestAnimationFrame(() => { if (isAppleDevice) { // For Apple devices, use window.scrollTo with calculated position const rect = elementRef.current.getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const targetY = rect.top + scrollTop; window.scrollTo({ top: targetY, behavior: behavior === 'smooth' ? 'auto' : behavior // Use 'auto' for Apple devices }); } else { // For other devices, use scrollIntoView elementRef.current.scrollIntoView({ behavior, block }); } }); }; // Apply delay if specified, otherwise scroll immediately if (delay > 0) { setTimeout(performScroll, delay); } else { performScroll(); } }; /** * A custom hook that provides various scrolling utilities. */ exports.scrollToElement = scrollToElement; const useScrollActions = () => { /** * Scrolls the window to the very top with smooth behavior. */ const scrollToTop = (0, _react.useCallback)(() => { window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); }, []); /** * Scrolls a specific DOM element into the viewport with smooth behavior. * * @param {React.RefObject<HTMLElement>} elementRef - The ref object pointing to the target DOM element. * @param {ScrollIntoViewOptions} [options={ behavior: 'smooth' }] - Optional scroll options. * Defaults to `{ behavior: 'smooth' }`. */ const scrollToElement = (0, _react.useCallback)(function (elementRef) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { behavior: 'smooth' }; if (elementRef && elementRef.current) { elementRef.current.scrollIntoView(options); } }, []); // Empty dependency array ensures this function is stable across renders. /** * Scrolls the window to make a specific element visible, * with an optional desired offset from the bottom of the viewport. * * @param {React.RefObject<HTMLElement>} elementRef - Any ref. * @param {number} [desiredOffsetFromBottom=0] - The desired offset in pixels from the bottom of the viewport. * Defaults to 0 if not provided. */ const scrollToElementWithOffset = (0, _react.useCallback)(function (elementRef) { let desiredOffsetFromBottom = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (elementRef && elementRef.current) { const targetElement = elementRef.current; // formula that determines how much the window actually needs to scroll so that // the element's bottom is at `window.innerHeight - desiredOffsetFromBottom` const scrollNeeded = targetElement.getBoundingClientRect().bottom - (window.innerHeight - desiredOffsetFromBottom); window.scrollBy({ top: scrollNeeded, behavior: 'smooth' }); } }, []); return { scrollToTop, scrollToElement, scrollToElementWithOffset }; }; exports.useScrollActions = useScrollActions; const ScrollToTop = () => { const { pathname } = (0, _reactRouterDom.useLocation)(); (0, _react.useEffect)(() => { // Reset scroll position on route change window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); }, [pathname]); return null; }; exports.ScrollToTop = ScrollToTop; var _default = exports.default = useScrollActions;