import { attr, godown, htmlSlot, styles } from "@godown/element"; import { property } from "lit/decorators.js"; import Router from "../router/component.js"; import { css, html, nothing, type TemplateResult } from "lit"; import GlobalStyle from "../../internal/global-style.js"; const protoName = "link"; const linkTypes = { push: "push", replace: "replace", normal: "normal", auto: "auto", } as const; type LinkType = keyof typeof linkTypes; /** * {@linkcode Link} is used for link jumping, works standalone or in {@linkcode Router}. * * Set `type` to `"normal"`, * behave like a normal anchor. * * Set `type` to `"push" `or `"replace"`, * update history state by `history.pushState` or `history.replaceState`, * update all routers whether current pathname is registered or not. * * Set `type` to `"auto"`, * only update the routers if the current pathname is registered, * if not registered, behave like `"normal"`. * * `replace` property will enforce `history.replaceState`. * * @fires navigate - Fires when the link is clicked. * @category navigation */ @godown(protoName) @styles(css` :host { display: inline-block; cursor: default; } :host([href]) { cursor: pointer; } a { display: contents; } `) class Link extends GlobalStyle { /** * If `"normal"`, behave like a normal anchor. * * If `"auto"` or `"push"`, call `history.pushState` if `replace` is false, * * If `"replace"`, call `history.replaceState`. */ @property() type: LinkType = linkTypes.auto; /** * If `true`, the {@linkcode Router} will not be updated. */ @property({ type: Boolean }) suppress = false; /** * Use `replaceState` instead of `pushState`. */ @property({ type: Boolean }) replace = false; /** * A element href. */ @property() href: string; /** * A element target. */ @property() target: "_blank" | "_self" | "_parent" | "_top" = "_self"; /** * Location state object. */ state = {}; get pathname(): string { return new URL(this.href, location.href).pathname; } protected _handleClick(e: MouseEvent): void { const { state, type, href, pathname, suppress } = this; if (!href) { return; } this.dispatchCustomEvent("navigate", { ...this.observedRecord, pathname, state, }); if (href.startsWith("#") || type === linkTypes.normal) { return; } this.handleState(); const routers = [...Router.routerInstances]; if ( // only runs when suppress is false !suppress && (type !== linkTypes.auto || // in auto mode, only update the routers if the current pathname is registered routers.some((i) => i.search(location.pathname))) ) { e.preventDefault(); Router.routerInstances.forEach((i) => { i.handlePopstate(); }); } } handleState: () => void = () => { switch (this.type) { case linkTypes.auto: case linkTypes.push: if (!this.replace) { // type is auto or push and replace is false history.pushState(this.state, "", this.href); break; } // fallthrough to replace case linkTypes.replace: history.replaceState(this.state, "", this.href); break; } }; protected render(): TemplateResult<1> { return html` ${htmlSlot()} `; } } export default Link; export { Link };