⚡ Adds routing
Squashed commit of the following: commitd26bffc33aAuthor: Jan Andrle <andrle.jan@centrum.cz> Date: Tue Jun 9 10:35:14 2026 +0200 ⚡ ups versions commit8bb12aaac1Author: Jan Andrle <andrle.jan@centrum.cz> Date: Fri May 22 15:56:44 2026 +0200 ⚡ 📺 finlizes routing and improves bs commitc3782509e8Author: Jan Andrle <andrle.jan@centrum.cz> Date: Wed May 20 16:10:51 2026 +0200 ⚡ first steps (footer) commit7c925d1fdbAuthor: Jan Andrle <andrle.jan@centrum.cz> Date: Wed May 20 15:24:20 2026 +0200 ⚡ 📺 tsc-alias commite4e41197b9Author: Jan Andrle <andrle.jan@centrum.cz> Date: Wed May 20 14:40:47 2026 +0200 🐛 fixes routing commit7d6240e28aAuthor: Jan Andrle <andrle.jan@centrum.cz> Date: Wed May 20 14:40:00 2026 +0200 🐛 📺 fixes dev server commit139b1590ceAuthor: Jan Andrle <andrle.jan@centrum.cz> Date: Wed Apr 29 13:40:40 2026 +0200 ⚡ Uses original router - it has benefits to use hash properly commit3cc11b68deAuthor: Jan Andrle <andrle.jan@centrum.cz> Date: Tue Apr 28 17:16:00 2026 +0200 🎉
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { css } from "lit";
|
||||
export const styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { property, customElement } from "lit/decorators.js";
|
||||
import { styles } from "./index.css.js";
|
||||
|
||||
@customElement("app-episode")
|
||||
export class AppEpisode extends LitElement {
|
||||
static override styles = styles;
|
||||
@property({ type: String }) override id = "";
|
||||
override render() {
|
||||
return html`
|
||||
Episode ${this.id}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { route } from "@/core/route.js";
|
||||
import { html } from "lit";
|
||||
|
||||
export const path = "/episodes/" as const;
|
||||
export const routes = route(
|
||||
{
|
||||
path: "/episodes/:id/",
|
||||
async enter() {
|
||||
await import("./index.js");
|
||||
return true;
|
||||
},
|
||||
render({ id }){
|
||||
return html`<app-episode id=${id}></app-episode>`;
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { css } from "lit";
|
||||
export const styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { styles } from "./index.css.js";
|
||||
|
||||
@customElement("app-episodes")
|
||||
export class AppEpisodes extends LitElement {
|
||||
static override styles = styles;
|
||||
override render() {
|
||||
return html`
|
||||
<div class="logo"></div>
|
||||
<a href="/episodes/1">Episode 1</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { route } from "@/core/route.js";
|
||||
import { routes as routeEpisode } from "./:id/routes.js";
|
||||
import { html } from "lit";
|
||||
|
||||
export const path = "/episodes/" as const;
|
||||
export const routes = route(
|
||||
{
|
||||
path,
|
||||
async enter() {
|
||||
await import("./index.js");
|
||||
return true;
|
||||
},
|
||||
render(){
|
||||
return html`<app-episodes></app-episodes>`;
|
||||
},
|
||||
},
|
||||
routeEpisode,
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { css } from "lit";
|
||||
export const styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { html } from "lit";
|
||||
import { fixture, expect } from "@open-wc/testing";
|
||||
|
||||
import type { AppHome } from "./index.js";
|
||||
import "./index.js";
|
||||
|
||||
describe("AppHome", () => {
|
||||
let element: AppHome;
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<app-home></app-home>`);
|
||||
});
|
||||
|
||||
it("renders a h1", () => {
|
||||
const h1 = element.shadowRoot!.querySelector('h1')!;
|
||||
expect(h1).to.exist;
|
||||
expect(h1.textContent).to.equal('My app');
|
||||
});
|
||||
|
||||
it("passes the a11y audit", async () => {
|
||||
await expect(element).shadowDom.to.be.accessible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { styles } from "./index.css.js";
|
||||
|
||||
@customElement("app-home")
|
||||
export class AppHome extends LitElement {
|
||||
static override styles = styles;
|
||||
override render() {
|
||||
return html`
|
||||
<h1>My app</h1>
|
||||
<p>Hello world</p>
|
||||
<a href="/episodes">Episode</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { route } from "@/core/route.js";
|
||||
import { html } from "lit";
|
||||
|
||||
export const path = "/" as const;
|
||||
export const routes = route(
|
||||
{
|
||||
path,
|
||||
async enter() {
|
||||
await import("./index.js");
|
||||
return true;
|
||||
},
|
||||
render(){
|
||||
return html`<app-home></app-home>`;
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
import { css } from "lit";
|
||||
export const styles = css`
|
||||
:host {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: #1a2b42;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
background-color: var(--app-cfpodcasts-background-color);
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
font-size: calc(12px + 0.5vmin);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-footer a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
||||
@@ -1,39 +0,0 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { property, customElement } from "lit/decorators.js";
|
||||
import { styles } from "./app-index.css.js";
|
||||
|
||||
const logo = import.meta.resolve("../../assets/logo.svg");
|
||||
|
||||
@customElement("app-cfpodcasts")
|
||||
export class AppCfpodcasts extends LitElement {
|
||||
@property({ type: String }) header = "My app";
|
||||
static styles = styles;
|
||||
render() {
|
||||
return html`
|
||||
<main>
|
||||
<div class="logo"><img alt="open-wc logo" src=${logo} /></div>
|
||||
<h1>${this.header}</h1>
|
||||
|
||||
<p>Edit <code>src/AppCfpodcasts.ts</code> and save to reload.</p>
|
||||
<a
|
||||
class="app-link"
|
||||
href="https://open-wc.org/guides/developing-components/code-examples"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Code examples
|
||||
</a>
|
||||
</main>
|
||||
|
||||
<p class="app-footer">
|
||||
🚽 Made with love by
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/open-wc"
|
||||
>open-wc</a
|
||||
>.
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="393.84613"
|
||||
width="393.84613"
|
||||
viewBox="0 0 5.0480766 5.0480766"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<rect
|
||||
fill="#f88a14"
|
||||
rx="0.53846151"
|
||||
ry="0.53846151"
|
||||
height="5.0480766"
|
||||
width="5.0480766"
|
||||
id="rect1"
|
||||
x="0"
|
||||
y="0"
|
||||
style="stroke-width:0.769231" />
|
||||
<path
|
||||
d="m 1.3450904,0.64548657 c 2.9002,0 2.9002,2.91010003 2.9002,2.91010003"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 1.3377904,1.9915866 c 1.5705,-0.00908 1.5705,1.5639 1.5705,1.5639"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 2.0192904,3.5227866 c 0,0.23366 -0.10712,0.47418 -0.24663,0.6537 -0.1814,0.2333 -0.5705,0.5618 -0.6913,0.5653 0.0402,-0.0662 0.263,-0.5654 0.2563,-0.5654 -0.36423004,0 -0.65950004,-0.29265 -0.65950004,-0.65365 0,-0.361 0.29527,-0.65365 0.65950004,-0.65365 0.36423,0 0.68159,0.29265 0.68159,0.65365 z"
|
||||
fill="#ffffff"
|
||||
id="path3"
|
||||
style="fill:#c83737" />
|
||||
<path
|
||||
style="fill:#ffffff;stroke:#666666;stroke-width:0"
|
||||
d="m 1.0262395,3.0457825 v 0.944372 L 1.8837176,3.5592476 Z"
|
||||
id="path4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,24 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
const styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
@customElement("c-sronly")
|
||||
export class CSronly extends LitElement {
|
||||
static override styles = styles;
|
||||
override render() {
|
||||
return html`
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { PathRouteConfig } from "@lit-labs/router";
|
||||
|
||||
export function route(...routes: (PathRouteConfig|PathRouteConfig[])[]): PathRouteConfig[] {
|
||||
return routes.flatMap(function process(route){
|
||||
if (Array.isArray(route)) // already processed
|
||||
return route;
|
||||
const { path, ...rest }= route;
|
||||
if (!path.endsWith("/"))
|
||||
throw new Error("path must end with „/”!");
|
||||
return [
|
||||
{ path, ...rest },
|
||||
{ path: path.slice(0, -1), ...rest },
|
||||
];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { css } from "lit";
|
||||
export const styles = css`
|
||||
:host {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"main"
|
||||
"footer";
|
||||
grid-template-rows: auto 4ch;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
nav {
|
||||
grid-area: footer;
|
||||
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
padding: .5ch;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { html } from "lit";
|
||||
import { fixture, expect } from "@open-wc/testing";
|
||||
|
||||
import type { AppCfpodcasts } from "./index.js";
|
||||
import "./index.js";
|
||||
|
||||
describe("App", () => {
|
||||
let element: AppCfpodcasts;
|
||||
let nav: HTMLElement;
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<app-cfpodcasts></app-cfpodcasts>`);
|
||||
nav = element.shadowRoot!.querySelector("nav")!;
|
||||
});
|
||||
|
||||
it("renders <main>", () => {
|
||||
const content = element.shadowRoot!.querySelector('main')!;
|
||||
expect(content).to.exist;
|
||||
});
|
||||
|
||||
it("has all pages", async () => {
|
||||
const links = nav.querySelectorAll("a");
|
||||
await expect(links.length).to.equal(2);
|
||||
});
|
||||
it("passes the a11y audit", async () => {
|
||||
await expect(nav).shadowDom.to.be.accessible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { styles } from "./index.css.js";
|
||||
import { Router } from "@lit-labs/router";
|
||||
|
||||
import * as routeHome from "./app-home/routes.js";
|
||||
import * as routeEpisodes from "./app-episodes/routes.js";
|
||||
|
||||
import "./components/c-sronly.js";
|
||||
const logo = new URL("./assets/logo.svg", import.meta.url);
|
||||
|
||||
@customElement("app-cfpodcasts")
|
||||
export class AppCfpodcasts extends LitElement {
|
||||
static override styles = styles;
|
||||
private _routes = new Router(this, [
|
||||
...routeHome.routes,
|
||||
...routeEpisodes.routes,
|
||||
]);
|
||||
override render() {
|
||||
console.log(this._routes); // TODO
|
||||
return html`
|
||||
<main>${this._routes.outlet()}</main>
|
||||
<nav>
|
||||
<a
|
||||
href="${this._routes.link("/")}"
|
||||
title="Home"
|
||||
>
|
||||
<img alt="" src=${logo} />
|
||||
<c-sronly>Home</c-sronly>
|
||||
</a>
|
||||
<a
|
||||
href="${this._routes.link(routeEpisodes.path)}"
|
||||
>
|
||||
Episodes
|
||||
</a>
|
||||
</nav>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user