@stokr/components-library
Version:
STOKR - Components Library
138 lines (130 loc) • 5.47 kB
JavaScript
;
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;