# presentation/components/link.component.ts

> 87 lines of code and 14 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-components-link-component-ts
Source text: https://banes-lab.com/assets/sources/source.6bb1c7b8a47111c28402c6ca0c2660d62a07f3f6398ae6a76ac6069c2af5600d.generated.txt

## Definitions

- `createLink` (lexical_declaration, line 83, exported)
- `createPageLink` (lexical_declaration, line 42, exported)
- `createExternalLink` (lexical_declaration, line 75, exported)
- `jumpTo` (lexical_declaration, line 21)
- `createAnchorLink` (lexical_declaration, line 61)
- `specOf` (lexical_declaration, line 13)
- `withContext` (lexical_declaration, line 33)
- `anchorOf` (lexical_declaration, line 26)
- `cut` (lexical_declaration, line 27)
- `[only]` (lexical_declaration, line 34)
- `title` (lexical_declaration, line 35)
- `anchor` (lexical_declaration, line 49, exported)
- `href` (lexical_declaration, line 66)
- `link` (lexical_declaration, line 67, exported)

## Uses

- [core/assets/link.assets.ts](https://banes-lab.com/source/tree/core/assets/link.assets.ts.md)
- [core/buses/base.bus.ts](https://banes-lab.com/source/tree/core/buses/base.bus.ts.md)
- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [core/predicates/link.predicate.ts](https://banes-lab.com/source/tree/core/predicates/link.predicate.ts.md)
- [presentation/components/tab.component.ts](https://banes-lab.com/source/tree/presentation/components/tab.component.ts.md)
- [presentation/renderers/element.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/element.renderer.ts.md)

## Used by

- [presentation/components/reference.component.ts](https://banes-lab.com/source/tree/presentation/components/reference.component.ts.md)
- [presentation/components/source.component.ts](https://banes-lab.com/source/tree/presentation/components/source.component.ts.md)
- [presentation/renderers/glossary.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/glossary.renderer.ts.md)
- [presentation/renderers/home.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/home.renderer.ts.md)
- [presentation/renderers/link.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/link.renderer.ts.md)
- [presentation/renderers/page.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/page.renderer.ts.md)
- [presentation/renderers/panel.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/panel.renderer.ts.md)
- [presentation/renderers/site.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/site.renderer.ts.md)
- [presentation/renderers/source.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/source.renderer.ts.md)
- [presentation/renderers/text.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/text.renderer.ts.md)
- [presentation/views/home.view.ts](https://banes-lab.com/source/tree/presentation/views/home.view.ts.md)

## Source

```typescript
import type { ElementChild, ElementSpec } from "#types/element.types";
import { HOME_PATH, pageFromPath, pagePath } from "#assets/link.assets";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import { HOME_PAGE } from "#core/ids/page.ids";
import { ROUTE_REQUESTED } from "#core/ids/route.ids";
import { contextOf } from "#presentation/renderers/element.renderer";
import { createElement } from "#core/factories/element.factory";
import { emitEvent } from "#core/buses/base.bus";
import { getPage } from "#domain/registries/page.registry";
import { isGenericLabel } from "#core/predicates/link.predicate";
import { scrollToId } from "#presentation/components/tab.component";

const specOf = function specOf(
    className: string,
    children: readonly ElementChild[],
    attributes: Readonly<Record<string, string>>,
): ElementSpec {
    return className.length === 0 ? { attributes, children } : { attributes, children, className };
};

const jumpTo = function jumpTo(href: string, targetId: string): void {
    window.history.replaceState({}, "", href);
    scrollToId(targetId);
};

const anchorOf = function anchorOf(href: string): { readonly path: string; readonly target: string | null } {
    const cut = href.indexOf(ANCHOR_PREFIX);
    return cut === -1
        ? { path: href, target: null }
        : { path: href.slice(0, cut), target: href.slice(cut + ANCHOR_PREFIX.length) };
};

const withContext = function withContext(page: string, children: readonly ElementChild[]): readonly ElementChild[] {
    const [only] = children;
    const title = getPage(page)?.title;
    if (children.length !== 1 || typeof only !== "string" || title === undefined || !isGenericLabel(only)) {
        return children;
    }
    return [only, contextOf(title)];
};

export const createPageLink = function createPageLink(
    page: string,
    className: string,
    children: readonly ElementChild[],
    path = pagePath(page),
): HTMLAnchorElement {
    const link = createElement("a", specOf(className, withContext(page, children), { href: path }));
    const anchor = anchorOf(path);
    link.addEventListener("click", (event) => {
        event.preventDefault();
        if (anchor.target !== null && anchor.path === window.location.pathname) {
            jumpTo(path, anchor.target);
            return;
        }
        emitEvent({ name: ROUTE_REQUESTED, page, path });
    });
    return link;
};

const createAnchorLink = function createAnchorLink(
    targetId: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    const href = ANCHOR_PREFIX + targetId;
    const link = createElement("a", specOf(className, children, { href }));
    link.addEventListener("click", (event) => {
        event.preventDefault();
        jumpTo(href, targetId);
    });
    return link;
};

export const createExternalLink = function createExternalLink(
    href: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    return createElement("a", specOf(className, children, { href, rel: "noopener noreferrer", target: "_blank" }));
};

export const createLink = function createLink(
    href: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    if (href.startsWith(ANCHOR_PREFIX)) {
        return createAnchorLink(href.slice(ANCHOR_PREFIX.length), className, children);
    }
    if (!href.startsWith(HOME_PATH)) {
        return createExternalLink(href, className, children);
    }
    return createPageLink(pageFromPath(href, HOME_PAGE), className, children, href);
};
```
