UNPKG

pathpunk

Version:

A react component based router with most of the functionalities of an SPA router

160 lines (159 loc) 6.35 kB
"use strict"; 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;