react-navi
Version:
A batteries-included router for react.
198 lines • 9.36 kB
JavaScript
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