@xeito/router
Version:
Router for Xeito | Framework for building web applications
289 lines (283 loc) • 9.73 kB
JavaScript
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}" ="${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