UNPKG

react-router-hash-link

Version:

Hash link scroll functionality for React Router v4/5

176 lines (141 loc) 6.98 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.genericHashLink = genericHashLink; exports.NavHashLink = exports.HashLink = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactRouterDom = require("react-router-dom"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } var hashFragment = ''; var observer = null; var asyncTimerId = null; var scrollFunction = null; function reset() { hashFragment = ''; if (observer !== null) observer.disconnect(); if (asyncTimerId !== null) { window.clearTimeout(asyncTimerId); asyncTimerId = null; } } function isInteractiveElement(element) { var formTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; var linkTags = ['A', 'AREA']; return formTags.includes(element.tagName) && !element.hasAttribute('disabled') || linkTags.includes(element.tagName) && element.hasAttribute('href'); } function getElAndScroll() { var element = null; if (hashFragment === '#') { // use document.body instead of document.documentElement because of a bug in smoothscroll-polyfill in safari // see https://github.com/iamdustan/smoothscroll/issues/138 // while smoothscroll-polyfill is not included, it is the recommended way to implement smoothscroll // in browsers that don't natively support el.scrollIntoView({ behavior: 'smooth' }) element = document.body; } else { // check for element with matching id before assume '#top' is the top of the document // see https://html.spec.whatwg.org/multipage/browsing-the-web.html#target-element var id = hashFragment.replace('#', ''); element = document.getElementById(id); if (element === null && hashFragment === '#top') { // see above comment for why document.body instead of document.documentElement element = document.body; } } if (element !== null) { scrollFunction(element); // update focus to where the page is scrolled to // unfortunately this doesn't work in safari (desktop and iOS) when blur() is called var originalTabIndex = element.getAttribute('tabindex'); if (originalTabIndex === null) element.setAttribute('tabindex', -1); element.focus({ preventScroll: true }); if (originalTabIndex === null) { if (!isInteractiveElement(element)) { // for some reason calling blur() in safari resets the focus region to where it was previously, // if blur() is not called it works in safari, but then are stuck with default focus styles // on an element that otherwise might never had focus styles applied, so not an option element.blur(); } element.removeAttribute('tabindex'); } reset(); return true; } return false; } function hashLinkScroll(timeout) { // Push onto callback queue so it runs after the DOM is updated window.setTimeout(function () { if (getElAndScroll() === false) { if (observer === null) { observer = new MutationObserver(getElAndScroll); } observer.observe(document, { attributes: true, childList: true, subtree: true }); // if the element doesn't show up in specified timeout or 10 seconds, stop checking asyncTimerId = window.setTimeout(function () { reset(); }, timeout || 10000); } }, 0); } function genericHashLink(As) { return /*#__PURE__*/_react["default"].forwardRef(function (props, ref) { var linkHash = ''; if (typeof props.to === 'string' && props.to.includes('#')) { linkHash = "#".concat(props.to.split('#').slice(1).join('#')); } else if (_typeof(props.to) === 'object' && typeof props.to.hash === 'string') { linkHash = props.to.hash; } var passDownProps = {}; if (As === _reactRouterDom.NavLink) { passDownProps.isActive = function (match, location) { return match && match.isExact && location.hash === linkHash; }; } function handleClick(e) { reset(); hashFragment = props.elementId ? "#".concat(props.elementId) : linkHash; if (props.onClick) props.onClick(e); if (hashFragment !== '') { scrollFunction = props.scroll || function (el) { return props.smooth ? el.scrollIntoView({ behavior: 'smooth' }) : el.scrollIntoView(); }; hashLinkScroll(props.timeout); } } var scroll = props.scroll, smooth = props.smooth, timeout = props.timeout, elementId = props.elementId, filteredProps = _objectWithoutProperties(props, ["scroll", "smooth", "timeout", "elementId"]); return /*#__PURE__*/_react["default"].createElement(As, _extends({}, passDownProps, filteredProps, { onClick: handleClick, ref: ref }), props.children); }); } var HashLink = genericHashLink(_reactRouterDom.Link); exports.HashLink = HashLink; var NavHashLink = genericHashLink(_reactRouterDom.NavLink); exports.NavHashLink = NavHashLink; var propTypes = { onClick: _propTypes["default"].func, children: _propTypes["default"].node, scroll: _propTypes["default"].func, timeout: _propTypes["default"].number, elementId: _propTypes["default"].string, to: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object]) }; HashLink.propTypes = propTypes; NavHashLink.propTypes = propTypes;