UNPKG

react-navi

Version:

A batteries-included router for react.

198 lines 9.36 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import * as React from 'react'; import { createURLDescriptor, joinPaths, modifyTrailingSlash, } from 'navi'; import { HashScrollContext, scrollToHash, } from './HashScroll'; import { NaviContext } from './NaviContext'; function isExternalHref(href) { // If this is an external link, return undefined so that the native // response will be used. return (!href || (typeof href === 'string' && (href.indexOf('://') !== -1 || href.indexOf('mailto:') === 0))); } function getLinkURL(href, routeURL) { if (!isExternalHref(href)) { // Resolve relative to the current "directory" if (routeURL && typeof href === 'string') { href = href[0] === '/' ? href : joinPaths('/', routeURL.pathname, href); } return createURLDescriptor(href); } } /** * Returns a boolean that indicates whether the user is currently * viewing the specified href. * @param href * @param options.exact If false, will match any URL underneath this href * @param options.loading If true, will match even if the route is currently loading */ export var useActive = function (href, _a) { var _b = _a === void 0 ? {} : _a, _c = _b.exact, exact = _c === void 0 ? true : _c, _d = _b.loading, loading = _d === void 0 ? false : _d; var context = React.useContext(NaviContext); var route = loading ? context.busyRoute || context.steadyRoute : context.steadyRoute || context.busyRoute; var routeURL = route && route.url; var linkURL = getLinkURL(href, routeURL); return !!(linkURL && routeURL && (exact ? linkURL.pathname === routeURL.pathname : modifyTrailingSlash(routeURL.pathname, 'add').indexOf(linkURL.pathname) === 0)); }; export var useLinkProps = function (_a) { var disabled = _a.disabled, hashScrollBehavior = _a.hashScrollBehavior, href = _a.href, prefetch = _a.prefetch, state = _a.state, onClick = _a.onClick, onMouseEnter = _a.onMouseEnter; var _b, _c; if (prefetch && state) { prefetch = false; if (process.env.NODE_ENV !== 'production') { console.warn("Warning: A <Link> component received both \"prefetch\" and \"state\" " + "props, but links with state cannot be prefetched. Skipping prefetch."); } } if (prefetch === true) { prefetch = 'mount'; if (process.env.NODE_ENV !== 'production') { console.warn("Warning: A <Link> component received a \"prefetch\" value of \"true\". " + "This value is no longer supported - please set it to \"mount\" instead."); } } // Prefetch on hover by default. if (prefetch === undefined) { prefetch = 'hover'; } var hashScrollBehaviorFromContext = React.useContext(HashScrollContext); var context = React.useContext(NaviContext); var navigation = context.navigation; if (hashScrollBehavior === undefined) { hashScrollBehavior = hashScrollBehaviorFromContext; } var route = context.steadyRoute || context.busyRoute; var routeURL = React.useMemo(function () { return route && route.url; }, [(_b = route) === null || _b === void 0 ? void 0 : _b.url.href]); var linkURL = getLinkURL(href, routeURL); if (!isExternalHref(href)) { var resolvedHref = href; // Resolve relative to the current "directory" if (routeURL && typeof href === 'string') { resolvedHref = href[0] === '/' ? href : joinPaths('/', routeURL.pathname, href); } linkURL = createURLDescriptor(resolvedHref); } // We need a URL descriptor that stays referentially equal so that we don't // trigger prefetches more than we'd like. var memoizedLinkURL = React.useMemo(function () { return linkURL; }, [(_c = linkURL) === null || _c === void 0 ? void 0 : _c.href]); var doPrefetch = React.useMemo(function () { var hasPrefetched = false; return function () { if (!hasPrefetched && memoizedLinkURL && memoizedLinkURL.pathname && navigation) { hasPrefetched = true; navigation.prefetch(memoizedLinkURL).catch(function (e) { console.warn("A <Link> tried to prefetch \"" + memoizedLinkURL.pathname + "\", but the " + "router was unable to fetch this path."); }); } }; }, [memoizedLinkURL, navigation]); // Prefetch on mount if required, or if `prefetch` becomes `true`. React.useEffect(function () { if (prefetch === 'mount') { doPrefetch(); } }, [prefetch, doPrefetch]); var handleMouseEnter = React.useCallback(function (event) { if (prefetch === 'hover') { if (onMouseEnter) { onMouseEnter(event); } if (disabled) { event.preventDefault(); return; } if (!event.defaultPrevented) { doPrefetch(); } } }, [disabled, doPrefetch, onMouseEnter, prefetch]); var handleClick = React.useCallback(function (event) { // Let the browser handle the event directly if: // - The user used the middle/right mouse button // - The user was holding a modifier key // - A `target` property is set (which may cause the browser to open the // link in another tab) if (event.button === 0 && !(event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)) { if (disabled) { event.preventDefault(); return; } if (onClick) { onClick(event); } // Sanity check if (!routeURL) { return; } if (!event.defaultPrevented && linkURL) { event.preventDefault(); var isSamePathname = modifyTrailingSlash(linkURL.pathname, 'remove') === modifyTrailingSlash(routeURL.pathname, 'remove'); navigation.navigate(linkURL, state ? { state: state } : undefined); if ((isSamePathname || linkURL.pathname === '') && linkURL.hash === routeURL.hash && linkURL.hash) { scrollToHash(routeURL.hash, hashScrollBehavior); } } } }, [disabled, onClick, linkURL && linkURL.href, routeURL && routeURL.href]); return { onClick: handleClick, onMouseEnter: handleMouseEnter, href: linkURL ? linkURL.href : href, }; }; // Need to include this type definition, as the automatically generated one // is incompatible with some versions of the react typings. export var Link = React.forwardRef(function (props, anchorRef) { var active = props.active, _a = props.activeClassName, activeClassName = _a === void 0 ? '' : _a, _b = props.activeStyle, activeStyle = _b === void 0 ? {} : _b, _c = props.className, className = _c === void 0 ? '' : _c, disabled = props.disabled, exact = props.exact, hashScrollBehavior = props.hashScrollBehavior, hrefProp = props.href, onClickProp = props.onClick, onMouseEnterProp = props.onMouseEnter, prefetch = props.prefetch, state = props.state, _d = props.style, style = _d === void 0 ? {} : _d, rest = __rest(props, ["active", "activeClassName", "activeStyle", "className", "disabled", "exact", "hashScrollBehavior", "href", "onClick", "onMouseEnter", "prefetch", "state", "style"]); var _e = useLinkProps({ hashScrollBehavior: hashScrollBehavior, href: hrefProp, onClick: onClickProp, onMouseEnter: onMouseEnterProp, prefetch: prefetch, state: state, }), onClick = _e.onClick, onMouseEnter = _e.onMouseEnter, linkProps = __rest(_e, ["onClick", "onMouseEnter"]); var actualActive = useActive(linkProps.href, { exact: !!exact }); if (active === undefined) { active = actualActive; } return (React.createElement("a", __assign({ ref: anchorRef, className: className + " " + (active ? activeClassName : ''), style: __assign(__assign({}, style), (active ? activeStyle : {})) }, rest, linkProps, { // Don't handle events on links with a `target` prop. onClick: props.target ? onClickProp : onClick, onMouseEnter: props.target ? onMouseEnterProp : onMouseEnter }))); }); //# sourceMappingURL=Link.js.map