react-router-hash-link
Version:
Hash link scroll functionality for React Router v4/5
176 lines (141 loc) • 6.98 kB
JavaScript
;
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;