import { IgniteElement } from "../ignite-html/ignite-element.js"; import { IgniteTemplate, slot, div, html } from "../ignite-html/ignite-template.js"; class RouterLink extends IgniteElement { constructor() { super(); this.pushStateListener = () => this.update(); this.popStateListener = () => this.update(); window.addEventListener("popstate", this.popStateListener); window.addEventListener("pushstate", this.pushStateListener); } get properties() { return { active: false, routes: [], target: null }; } get styles() { return ` router-link { display: flex; flex-direction: row; align-items: center; justify-content: center; } `; } render() { return this.template .onClick((event) => this.onClick(event)) .child( new slot(this) .class(this.active, (value) => { return value ? "active" : null }) ); } ready() { this.update(); } update() { var routeMatches = true; //Check the target first. routeMatches = RouteMatcher.matches(this.target); //Check optional routes next. for (var i = 0; i < this.routes.length && routeMatches; i++) { routeMatches = RouteMatcher.matches(this.routes[i]); } if (routeMatches && !this.active) { this.active = true; } else if (!routeMatches && this.active) { this.active = false; } } onClick(event) { event.preventDefault(); window.history.pushState(this.target, this.target, this.target); window.dispatchEvent(new Event("pushstate")); } cleanup() { window.removeEventListener("popstate", this.popStateListener); window.removeEventListener("pushstate", this.pushStateListener); } } class RouterView extends IgniteElement { constructor() { super(); this.pushStateListener = () => this.update(); this.popStateListener = () => this.update(); window.addEventListener("popstate", this.popStateListener); window.addEventListener("pushstate", this.pushStateListener); } get properties() { return { show: false, routes: [] }; } render() { return this.template.child( new slot(this) ).style("display", this.show, null, (value) => { return value ? null : "none"; }); } ready() { this.update(); } update() { var routeMatches = true; for (var i = 0; i < this.routes.length && routeMatches; i++) { routeMatches = RouteMatcher.matches(this.routes[i]); } if (routeMatches && !this.show) { this.show = true; } else if (!routeMatches && this.show) { this.show = false; } } cleanup() { window.removeEventListener("popstate", this.popStateListener); window.removeEventListener("pushstate", this.pushStateListener); } } class RouteMatcher { static matches(route) { //Get the path parts from the window var pathParts = window.location.pathname.split("/").splice(1); //Get the route parts var fromRoot = (route.trim().startsWith("/")); var routeParts = (fromRoot ? route.trim().split("/").splice(1) : route.trim().split("/")); //Check to see if we have a trailing route part, if so, remove it. if (pathParts.length > 0 && pathParts[pathParts.length - 1] == "") { pathParts.pop(); } if (routeParts.length > 0 && routeParts[routeParts.length - 1] == "") { routeParts.pop(); } //If path parts is 0 and route parts is 0 we have a match. if (pathParts.length == 0 && routeParts.length == 0) { return true; } //If path parts is 0, and the route part is ** then this is a match. else if (pathParts.length == 0 && routeParts.length == 1 && (routeParts[0] == "**" || routeParts[0] == "*")) { return true; } //If path parts is 0 and the route starts with ! and is a length of 1 then this is a match. else if (pathParts.length == 0 && routeParts.length == 1 && routeParts[0].startsWith("!")) { return true; } //If the path parts is 0 and the route is !*/** then this is a match else if (pathParts.length == 0 && routeParts.length == 2 && routeParts[0].startsWith("!") && routeParts[1] == "**") { return true; } //Check the route parts against the path parts. var max = Math.min(pathParts.length, routeParts.length); if (fromRoot) { for (var i = 0; i < max; i++) { if (routeParts[i].startsWith("!") && pathParts[i] == routeParts[i].substring(1)) { return false; } else if (routeParts[i] == "**") { return true; } else if (routeParts[i] != pathParts[i] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) { return false; } else if (i + 2 == routeParts.length && i + 1 == pathParts.length && routeParts[i + 1] == "**") { return true; } } if (routeParts.length > pathParts.length) { return false; } else if (pathParts.length > routeParts.length && routeParts[routeParts.length - 1] != "**") { return false; } else { return true; } } else { for (var offset = 0; offset < pathParts.length; offset++) { for (var i = 0; i < max; i++) { if (i + offset >= pathParts.length) { return false; } if (routeParts[i].startsWith("!") && pathParts[i + offset] == routeParts[i].substring(1)) { break; } else if (routeParts[i] == "**") { return true; } else if (routeParts[i] != pathParts[i + offset] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) { break; } else if (i + 1 == routeParts.length && offset + routeParts.length == pathParts.length) { return true; } else if (i + 2 == routeParts.length && offset + i + 1 == pathParts.length && routeParts[i + 1] == "**") { return true; } } } return false; } } } window.RouteMatcher = RouteMatcher; class RouterLinkTemplate extends IgniteTemplate { /** * Initializes a new router link template. * @param {String} target The target route when the link is clicked. * @param {String|String[]} routes Optional routes that can be used to control the active state of the link. * @param {...any} elements Elements to render within the link. */ constructor(target, routes, ...elements) { super("router-link", elements); if (!routes) { routes = []; } if (!Array.isArray(routes)) { routes = [routes]; } this.property("target", target); this.property("routes", routes); } } class RouterViewTemplate extends IgniteTemplate { /** * Initializes a new router view. * @param {String|String[]} routes Single or multiple routes to trigger this view to render. * @param {...any} elements Elements to render within the view. */ constructor(routes, ...elements) { super("router-view", elements); if (!Array.isArray(routes)) { routes = [routes]; } this.property("routes", routes); } } customElements.define("router-link", RouterLink); customElements.define("router-view", RouterView); export { RouterLinkTemplate as RouterLink, RouterViewTemplate as RouterView, RouteMatcher }