pathpunk
Version:
A react component based router with most of the functionalities of an SPA router
160 lines (159 loc) • 6.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterContext = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const RouterContext = (0, react_1.createContext)(null);
exports.RouterContext = RouterContext;
const findRoute = (routes, path) => {
return (routes.find(item => {
const regex = item.path.replaceAll(/((:\w+)|(\[\w+\]))/gi, '\\w+');
if (item.path !== '*')
return new RegExp(`^${regex}$`).test(path);
}) || routes.find(item => item.path === '*'));
};
const getCrumbs = (path) => {
const crumbArray = path.replace(/^\//gi, '').split(/\//gi);
return crumbArray[0] ? crumbArray : [];
};
function Router({ allowHistory, children, name, routes }) {
const searchParams = (0, react_1.useRef)(new URLSearchParams(window.location.search));
const [activeRoute, setActiveRoute] = (0, react_1.useState)();
const [routerState, setRouterState] = (0, react_1.useState)(null);
const [breadCrumbs, setBreadCrumbs] = (0, react_1.useState)([]);
const history = (0, react_1.useRef)([
searchParams.current.get(`router_${name}`) || '/',
]);
const historyIndex = (0, react_1.useRef)(0);
const redirectId = (0, react_1.useRef)(0);
const query = (0, react_1.useMemo)(() => {
const obj = {};
if (activeRoute) {
const realPath = searchParams.current.get(`router_${name}`);
if (realPath) {
const arrOfKeys = activeRoute.path.replace(/^\//gi, '').split(/\//gi);
const arrOfValues = realPath.replace(/^\//gi, '').split(/\//gi);
arrOfKeys.forEach((key, index) => {
var _a, _b;
const _key = ((_a = key.match(/\[(\w+)\]/i)) === null || _a === void 0 ? void 0 : _a[1]) || ((_b = key.match(/:(\w+)/i)) === null || _b === void 0 ? void 0 : _b[1]);
if (_key) {
obj[_key] = arrOfValues[index];
}
});
}
}
return obj;
}, [activeRoute, name]);
const _push = (0, react_1.useCallback)((path, historyOperation, options) => {
clearTimeout(redirectId.current);
// Render UI
const route = findRoute(routes, path);
if (route === null || route === void 0 ? void 0 : route.redirect) {
redirectId.current = setTimeout(_push, 100, route.redirect);
}
setActiveRoute(route);
setRouterState((options === null || options === void 0 ? void 0 : options.state) || null);
setBreadCrumbs(getCrumbs(path));
// Concatenate new full path
searchParams.current = new URLSearchParams(window.location.search);
searchParams.current.set(`router_${name}`, path);
const relativePath = window.location.pathname +
'?' +
decodeURIComponent(searchParams.current.toString());
// Update URL Bar with new path
if (allowHistory) {
window.history.pushState(null, '', relativePath);
}
else {
if (historyOperation === 'back') {
historyIndex.current--;
}
else if (historyOperation === 'forward') {
historyIndex.current++;
}
else {
history.current.splice(historyIndex.current + 1);
history.current.push(path);
historyIndex.current++;
}
window.history.replaceState(null, '', relativePath);
}
}, [allowHistory, name, routes]);
const push = (0, react_1.useCallback)((path, options) => _push(path, undefined, options), [_push]);
const back = (0, react_1.useCallback)(() => {
if (allowHistory) {
window.history.back();
}
else {
const prevPath = history.current[historyIndex.current - 1];
if (prevPath)
_push(prevPath, 'back');
}
}, [_push, allowHistory]);
const forward = (0, react_1.useCallback)(() => {
if (allowHistory) {
window.history.forward();
}
else {
const nextPath = history.current[historyIndex.current + 1];
if (nextPath)
_push(nextPath, 'forward');
}
}, [_push, allowHistory]);
const reload = (0, react_1.useCallback)(() => {
setActiveRoute(undefined);
const id = setTimeout(() => {
setActiveRoute(activeRoute);
clearTimeout(id);
}, 0);
}, [activeRoute]);
const renderUI = (0, react_1.useCallback)(() => {
const _path = searchParams.current.get(`router_${name}`) || '/';
// Update UI
const route = findRoute(routes, _path);
if (route === null || route === void 0 ? void 0 : route.redirect) {
redirectId.current = setTimeout(_push, 0, route.redirect);
}
setActiveRoute(route);
setBreadCrumbs(getCrumbs(_path));
}, [_push, name, routes]);
const handlePopState = (0, react_1.useCallback)((event) => {
searchParams.current = new URLSearchParams(event.target.location.search);
renderUI();
}, [renderUI]);
(0, react_1.useEffect)(() => {
renderUI();
}, [renderUI]);
(0, react_1.useEffect)(() => {
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, [handlePopState]);
const providerValue = (0, react_1.useMemo)(() => ({
breadCrumbs,
back,
forward,
name,
pathname: activeRoute === null || activeRoute === void 0 ? void 0 : activeRoute.path,
push,
query,
reload,
state: routerState,
}), [
activeRoute === null || activeRoute === void 0 ? void 0 : activeRoute.path,
back,
forward,
breadCrumbs,
name,
push,
query,
reload,
routerState,
]);
return ((0, jsx_runtime_1.jsx)(RouterContext.Provider, { value: providerValue, children: (() => {
const content = activeRoute ? activeRoute.component : 'NotFound';
return children ? children(content) : content;
})() }));
}
exports.default = Router;