UNPKG

@xeito/router

Version:

Router for Xeito | Framework for building web applications

289 lines (283 loc) 9.73 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; // packages/router/classes/xeito-router-plugin.ts import { XeitoPlugin } from "@xeito/core"; import { DerivedStore, WriteStore } from "@xeito/store"; import { createBrowserHistory, createHashHistory, createMemoryHistory } from "history"; // packages/router/components/router-slot.ts import { XeitoComponent, Component, html, Global, State } from "@xeito/core"; import { match } from "path-to-regexp"; // packages/router/functions/process-guards.ts async function processGuards(route, router) { return new Promise(async (resolve) => { let shouldContinue = true; let redirectRoute = null; if (route.guards) { for await (const guard of route.guards) { const guardResult = guard(router.location.value.pathname); if (guardResult instanceof Promise) { const promiseGuardResult = await guardResult; if (typeof promiseGuardResult === "string") { redirectRoute = promiseGuardResult; } else { shouldContinue = promiseGuardResult; } } if (typeof guardResult === "string") redirectRoute = guardResult; if (typeof guardResult === "boolean") shouldContinue = guardResult; } if (redirectRoute) { router.replace(redirectRoute); resolve(false); } resolve(shouldContinue); } else { resolve(true); } }); } // packages/router/components/router-slot.ts var RouterSlot = class extends XeitoComponent { onWillMount() { this.historySubscription = this.routerInternal.history.listen((update) => { this.routerInternal.pathAccumulator.set(""); this.routerInternal.previousRoute.set({ children: this.routerInternal.routes }); this.handleRouteUpdate(); }); this.handleRouteUpdate(); } async handleRouteUpdate() { const currentURL = this.router.location.value.pathname; const previousRoute = this.routerInternal.previousRoute.value; let matchedRoute; if (previousRoute.children) { for (let route of previousRoute.children) { let routePath = `${this.routerInternal.pathAccumulator.value ?? ""}${route.path}`; const fn = match(routePath, { decode: decodeURIComponent, end: false }); const result = fn(currentURL); if (result) { matchedRoute = route; const fullFn = match(routePath, { decode: decodeURIComponent, end: true }); const matched = fullFn(currentURL); if (matched && matched.path === currentURL) { if (JSON.stringify(this.routerInternal.params.value) !== JSON.stringify(result.params)) { this.routerInternal.params.set(result.params); } } break; } } } if (matchedRoute) { let shouldContinue = true; if (matchedRoute.redirectTo) { this.router.replace(matchedRoute.redirectTo); return; } if (matchedRoute.guards && matchedRoute.guards.length > 0) { shouldContinue = await processGuards(matchedRoute, this.router); } if (shouldContinue) { this.routerInternal.previousRoute.set(matchedRoute); this.routerInternal.pathAccumulator.set(this.routerInternal.pathAccumulator.value + matchedRoute.path); this.handleRoute(matchedRoute); } } } handleRoute(route) { if (route) { this.component = new route.component(); } else { this.component = null; } } onUnmount() { this.historySubscription && this.historySubscription(); } render() { return html`${this.component}`; } }; __decorateClass([ Global() ], RouterSlot.prototype, "router", 2); __decorateClass([ Global() ], RouterSlot.prototype, "routerInternal", 2); __decorateClass([ State() ], RouterSlot.prototype, "component", 2); RouterSlot = __decorateClass([ Component({ selector: "router-slot" }) ], RouterSlot); // packages/router/components/router-link.ts import { XeitoComponent as XeitoComponent2, Component as Component2, html as html2, Prop, Global as Global2 } from "@xeito/core"; var RouterLink = class extends XeitoComponent2 { handleClick(event) { event.preventDefault(); let url = this.to; if (!url) url = this.getAttribute("to"); if (!url) url = this.getAttribute("href"); if (!url) console.warn("No URL provided to router-link"); this.router.push(this.router.createHref(this.to), this.state); } render() { return html2` <a href="${this.to}" @click="${this.handleClick}"> ${this.slotContent.default} </a> `; } }; __decorateClass([ Global2() ], RouterLink.prototype, "router", 2); __decorateClass([ Prop() ], RouterLink.prototype, "to", 2); __decorateClass([ Prop() ], RouterLink.prototype, "state", 2); RouterLink = __decorateClass([ Component2({ selector: "router-link" }) ], RouterLink); // packages/router/classes/xeito-router-plugin.ts var XeitoRouterPlugin = class extends XeitoPlugin { constructor() { super(...arguments); // WriteStore that emits the route update this.$routeUpdate = new WriteStore(null); // WriteStore that emits the current location this.$location = new WriteStore(null); // WriteStore that emits the current route params this.$params = new WriteStore(null); } /** * Install the router plugin * This method is called by the Xeito framework when the plugin is registered * @param options The router options */ install(options) { if (!options) throw new Error("Router options are required"); this.routes = this.formatRoutes(options.routes); this.initializeHistory(options.strategy || "browser"); this.registerGlobalProperty("router", this.getRouterInstance()); this.registerGlobalProperty("routerInternal", this.getRouterInternalInstance()); this.registerGlobalComponent(RouterSlot); this.registerGlobalComponent(RouterLink); this.registerRouteComponentsAsGlobal(this.routes); } /** * Create the history instance based on the strategy supplied by the user * @param strategy The strategy to use */ initializeHistory(strategy) { if (strategy === "hash") this.history = createHashHistory(); else if (strategy === "memory") this.history = createMemoryHistory(); else this.history = createBrowserHistory(); this.history.listen((update) => { this.$routeUpdate.set(update); this.$location.set(update.location); }); this.$location.set(this.history.location); } /** * Return the router instance * This is exposed to the user and can be used to navigate between routes * It can be used in the components using the '@Global' decorator * eg: @Global() router: XeitoRouter; * @returns The router instance */ getRouterInstance() { return { routeUpdate: new DerivedStore(this.$routeUpdate, (value) => value), routeParams: this.$params, location: new DerivedStore(this.$location, (value) => value), push: (path, state) => this.history.push(path, state), replace: (path, state) => this.history.replace(path, state), go: (delta) => this.history.go(delta), back: () => this.history.back(), forward: () => this.history.forward(), createHref: (path) => this.history.createHref(path) }; } /** * Router internal instance used in the router-slot component to render the active route * This is not exposed to the user and is only used internally * @returns The router internal instance */ getRouterInternalInstance() { return { routeUpdate: this.$routeUpdate, routes: this.routes, params: this.$params, previousRoute: new WriteStore({ children: this.routes }), pathAccumulator: new WriteStore(""), history: this.history }; } /** * Register the components for the routes as global components * This is done to allow components declared in the routes to use the '@Global' decorator * @param routes List of routes */ registerRouteComponentsAsGlobal(routes) { routes.forEach((route) => { if (route.component) this.registerGlobalComponent(route.component); if (route.children) this.registerRouteComponentsAsGlobal(route.children); }); } /** * Moves the root route to the end of the array to ensure that it is always the last route to be matched * Prevents infinite loops when the root route is matched * @param routes List of routes * @returns routes with the root route at the end of the array */ formatRoutes(routes) { routes.forEach((route) => { if (route.children) { route.children = this.formatRoutes(route.children); } else { if (this.isRootRoute(route)) { routes = routes.filter((r) => r !== route); routes.push(route); } } }); return routes; } isRootRoute(route) { if (route.path === "/" || route.path === "" || route.path === " " || route.path === "/:" || route.path === "(.*)" || route.path === "/(.*)" || route.path === void 0 || route.path === null) return true; } }; export { RouterLink, RouterSlot, XeitoRouterPlugin }; //# sourceMappingURL=index.js.map