Microsoft/fast-dna

View on GitHub
packages/web-components/fast-router/src/links.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { Route } from "./navigation.js";

interface AnchorEventInfo {
    shouldHandleEvent: boolean;
    href: string | null;
    anchor: Element | null;
}

/**
 * @beta
 */
export interface LinkHandler {
    connect(): void;
    disconnect(): void;
}

/**
 * @beta
 */
export class DefaultLinkHandler implements LinkHandler {
    private handler = (e: MouseEvent) => {
        const { shouldHandleEvent, href } = this.getEventInfo(e);

        if (shouldHandleEvent) {
            e.preventDefault();
            Route.path.push(href!);
        }
    };

    public connect(): void {
        window.addEventListener("click", this.handler, true);
    }

    public disconnect(): void {
        window.removeEventListener("click", this.handler, true);
    }

    private getEventInfo(event: MouseEvent): AnchorEventInfo {
        const info: AnchorEventInfo = {
            shouldHandleEvent: false,
            href: null,
            anchor: null,
        };

        const target = this.findClosestAnchor(event);

        if (!target || !this.targetIsThisWindow(target)) {
            return info;
        }

        if (
            target.hasAttribute("download") ||
            target.hasAttribute("router-ignore") ||
            target.hasAttribute("data-router-ignore")
        ) {
            return info;
        }

        if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
            return info;
        }

        const href = target.getAttribute("href");
        info.anchor = target;
        info.href = href;

        const leftButtonClicked = event.which === 1;
        const isRelative = href && !(href.charAt(0) === "#" || /^[a-z]+:/i.test(href));

        info.shouldHandleEvent = leftButtonClicked && !!isRelative;

        return info;
    }

    private findClosestAnchor(event: MouseEvent): Element {
        const path: EventTarget[] = event.composedPath();

        for (let i = 0, ii = path.length; i < ii; ++i) {
            const current = path[i] as any;

            if (current.tagName === "A") {
                return current;
            }
        }

        return event.target as any;
    }

    private targetIsThisWindow(target: Element): boolean {
        const targetWindow = target.getAttribute("target");

        return !targetWindow || targetWindow === window.name || targetWindow === "_self";
    }
}