react-navi
Version:
A batteries-included router for react.
136 lines • 6.28 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);
};
import * as React from 'react';
import { HashScrollContext, scrollToHash, } from './HashScroll';
import { NaviContext } from './NaviContext';
import { ViewHeadRendererContext } from './ViewHeadRendererContext';
function defaultUseViewChunkPredicate(chunk) {
return chunk.type === 'view';
}
export function useViewElement(options) {
if (options === void 0) { options = {}; }
var result = useView(options);
return result && result.element;
}
export function useView(_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.disableScrolling, disableScrolling = _c === void 0 ? false : _c, hashScrollBehavior = _b.hashScrollBehavior, renderHead = _b.renderHead, _d = _b.where, where = _d === void 0 ? defaultUseViewChunkPredicate : _d;
var hashScrollBehaviorFromContext = React.useContext(HashScrollContext);
var renderHeadFromContext = React.useContext(ViewHeadRendererContext);
var context = React.useContext(NaviContext);
if (hashScrollBehavior === undefined) {
hashScrollBehavior = hashScrollBehaviorFromContext;
}
if (renderHead === undefined && renderHeadFromContext) {
renderHead = renderHeadFromContext;
}
var route = context.steadyRoute || context.busyRoute;
if (!route) {
throw new Error('react-navi: A <View> component cannot be rendered outside of a <Router> or <NaviProvider> component.');
}
var unconsumedChunks = context.unconsumedSteadyRouteChunks || route.chunks;
var index = unconsumedChunks.findIndex(where);
var view = index !== -1 && unconsumedChunks[index].view;
// Find any other chunks that come before this chunk, or after this one if
// this is the final view chunk.
//
// Don't treat this as the final chunk is there is an error, as that means
// we don't know whether this is really meant to be the final chunk, and we
// don't want to throw an error before rendering whatever views we can.
var final = index === -1 ||
(!unconsumedChunks.slice(index + 1).find(where) && route.type !== 'error');
var chunks = React.useMemo(function () { return (final ? unconsumedChunks : unconsumedChunks.slice(0, index + 1)); }, [final, unconsumedChunks, index]);
// Look for an error amongst any route chunks that haven't already been used
// by a `useView()` and throw it.
var errorChunk = chunks.find(function (chunk) { return chunk.type === 'error'; });
if (errorChunk) {
throw errorChunk.error || new Error('Unknown routing error');
}
// If there's no steady route, then we'll need to wait until a steady
// route becomes available using Supsense.
if (!view && !context.steadyRoute) {
throw context.navigation.getRoute();
}
var childContext = React.useMemo(function () { return (__assign(__assign({}, context), { unconsumedSteadyRouteChunks: final
? []
: unconsumedChunks.slice(index + 1) })); }, [context, unconsumedChunks, index]);
var connect = React.useCallback(function (children) {
return (React.createElement(NaviContext.Provider, { value: childContext }, // Clone the content to force a re-render even if content hasn't
// changed, as Provider is a PureComponent.
React.isValidElement(children)
? React.cloneElement(children)
: children));
}, [childContext]);
var content = React.useMemo(function () {
return typeof view === 'function'
? React.createElement(view, {
route: context.steadyRoute,
})
: view || null;
}, [view, context.steadyRoute]);
var head = React.useMemo(function () { return (!renderHead ? null : renderHead(chunks)); }, [
renderHead,
chunks,
]);
// Scroll to hash or top of page if appropriate.
var lastRouteRef = React.useRef();
React.useEffect(function () {
var nextRoute = route;
var prevRoute = lastRouteRef.current;
lastRouteRef.current = route;
if (final && route && unconsumedChunks.length !== 0) {
if (nextRoute && nextRoute.type !== 'busy') {
if (prevRoute &&
nextRoute.url.pathname === prevRoute.url.pathname &&
nextRoute.url.search === prevRoute.url.search &&
nextRoute.url.hash === prevRoute.url.hash) {
return;
}
if (!disableScrolling &&
(!prevRoute ||
!prevRoute.url ||
prevRoute.url.hash !== nextRoute.url.hash ||
prevRoute.url.pathname !== nextRoute.url.pathname)) {
scrollToHash(nextRoute.url.hash, prevRoute &&
prevRoute.url &&
prevRoute.url.pathname === nextRoute.url.pathname
? hashScrollBehavior
: 'auto');
}
}
}
}, [route]);
var result = React.useMemo(function () { return ({
chunks: chunks,
connect: connect,
content: content,
element: connect(React.createElement(React.Fragment, null,
head,
content)),
final: final,
head: head,
}); }, [chunks, connect, content, final, head]);
return unconsumedChunks.length === 0 ? null : result;
}
export var View = function View(_a) {
var disableScrolling = _a.disableScrolling, hashScrollBehavior = _a.hashScrollBehavior, renderHead = _a.renderHead, where = _a.where;
var result = useView({
disableScrolling: disableScrolling,
hashScrollBehavior: hashScrollBehavior,
renderHead: renderHead,
where: where,
});
if (!result) {
throw new Error('A Navi <View> was not able to find a view to render.');
}
return result.element;
};
//# sourceMappingURL=View.js.map