# presentation/widgets/reference.widget.ts

> 189 lines of code and 31 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-widgets-reference-widget-ts
Source text: https://banes-lab.com/assets/sources/source.268b06e4f754997be51cd8b58dae89fefcb4ac91c44df489c5d43258614773de.generated.txt

## Definitions

- `cancel` (lexical_declaration, line 71, exported)
- `hide` (lexical_declaration, line 85, exported)
- `isInside` (lexical_declaration, line 47)
- `show` (lexical_declaration, line 113, exported)
- `settle` (lexical_declaration, line 81, exported)
- `reveal` (lexical_declaration, line 93, exported)
- `panelsOf` (lexical_declaration, line 55)
- `linkOf` (lexical_declaration, line 36)
- `disarm` (lexical_declaration, line 70, exported)
- `onLeave` (lexical_declaration, line 140, exported)
- `onEnter` (lexical_declaration, line 130, exported)
- `onPointer` (lexical_declaration, line 157, exported)
- `mountReference` (lexical_declaration, line 59, exported)
- `onKey` (lexical_declaration, line 152, exported)
- `onClick` (lexical_declaration, line 172, exported)
- `NONE` (lexical_declaration, line 28)
- `Anchored` (interface_declaration, line 30)
- `anchoredOf` (lexical_declaration, line 40)
- `anchor` (lexical_declaration, line 41)
- `href` (lexical_declaration, line 42)
- `target` (lexical_declaration, line 43)
- `relatedOf` (lexical_declaration, line 51)
- `panel` (lexical_declaration, line 60, exported)
- `current` (lexical_declaration, line 66, exported)
- `shown` (lexical_declaration, line 67, exported)
- `timer` (lexical_declaration, line 68, exported)
- `touched` (lexical_declaration, line 69, exported)
- `content` (lexical_declaration, line 94, exported)
- `chained` (lexical_declaration, line 118, exported)
- `related` (lexical_declaration, line 141, exported)
- `anchored` (lexical_declaration, line 161, exported)

## Uses

- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [core/registries/style.registry.ts](https://banes-lab.com/source/tree/core/registries/style.registry.ts.md)
- [presentation/components/reference.component.ts](https://banes-lab.com/source/tree/presentation/components/reference.component.ts.md)

## Source

```typescript
import { DESCRIBED_BY_ATTRIBUTE, HREF_ATTRIBUTE, OPEN_CLASS } from "#configuration/constants/element.constants";
import {
    MILLISECONDS,
    REFERENCE_CHAIN_MS,
    REFERENCE_CHAIN_PROPERTY,
    REFERENCE_CLASS,
    REFERENCE_HIDE_MS,
    REFERENCE_LINK_SELECTOR,
    REFERENCE_SHOW_MS,
    TOUCH_POINTER,
} from "#configuration/constants/reference.constants";
import { REFERENCE_PANEL_LABEL, REFERENCE_ROLE } from "#configuration/strings/reference.strings";
import type { ReferenceContent, ReferenceTarget } from "#types/reference.types";
import {
    armLink,
    createChapterPanel,
    createRecordPanel,
    placePanel,
} from "#presentation/components/reference.component";
import { clearChildren, createElement } from "#core/factories/element.factory";
import type { Disposer } from "#types/base.types";
import { ESCAPE_KEY } from "#configuration/constants/overlay.constants";
import { REFERENCE_PANEL_ID } from "#core/ids/reference.ids";
import { declareStyle } from "#core/registries/style.registry";
import { loadReference } from "#domain/loaders/reference.loader";
import { targetOfHref } from "#domain/converters/reference.converter";

const NONE = "";

interface Anchored {
    readonly anchor: HTMLAnchorElement;
    readonly href: string;
    readonly target: ReferenceTarget;
}

const linkOf = function linkOf(node: EventTarget | null): HTMLAnchorElement | null {
    return node instanceof Element ? node.closest<HTMLAnchorElement>(REFERENCE_LINK_SELECTOR) : null;
};

const anchoredOf = function anchoredOf(node: EventTarget | null): Anchored | null {
    const anchor = linkOf(node);
    const href = anchor?.getAttribute(HREF_ATTRIBUTE) ?? NONE;
    const target = href.length === 0 ? null : targetOfHref(href);
    return anchor === null || target === null ? null : { anchor, href, target };
};

const isInside = function isInside(container: Node, node: EventTarget | null): boolean {
    return node instanceof Node && container.contains(node);
};

const relatedOf = function relatedOf(event: Event): EventTarget | null {
    return event instanceof MouseEvent || event instanceof FocusEvent ? event.relatedTarget : null;
};

const panelsOf = function panelsOf(content: ReferenceContent): readonly HTMLElement[] {
    return content.kind === "record" ? createRecordPanel(content.record) : createChapterPanel(content.chapter);
};

export const mountReference = function mountReference(): Disposer {
    const panel = createElement("div", {
        attributes: { "aria-label": REFERENCE_PANEL_LABEL, id: REFERENCE_PANEL_ID, role: REFERENCE_ROLE },
        className: REFERENCE_CLASS,
    });
    declareStyle(panel, REFERENCE_CHAIN_PROPERTY, String(REFERENCE_CHAIN_MS) + MILLISECONDS);
    document.body.append(panel);
    let current: Anchored | null = null;
    let shown: Anchored | null = null;
    let timer: ReturnType<typeof setTimeout> | null = null;
    let touched: HTMLAnchorElement | null = null;
    let disarm: Disposer | null = null;
    const cancel = function cancel(): void {
        if (timer !== null) {
            clearTimeout(timer);
            timer = null;
        }
        if (disarm !== null) {
            disarm();
            disarm = null;
        }
    };
    const settle = function settle(): void {
        cancel();
        current = shown;
    };
    const hide = function hide(): void {
        cancel();
        shown?.anchor.removeAttribute(DESCRIBED_BY_ATTRIBUTE);
        current = null;
        shown = null;
        panel.classList.remove(OPEN_CLASS);
        clearChildren(panel);
    };
    const reveal = async function reveal(anchored: Anchored, chained: boolean): Promise<void> {
        const content = await loadReference(anchored.target, anchored.href);
        if (current !== anchored) {
            return;
        }
        if (content === null) {
            hide();
            return;
        }
        cancel();
        shown?.anchor.removeAttribute(DESCRIBED_BY_ATTRIBUTE);
        shown = anchored;
        anchored.anchor.setAttribute(DESCRIBED_BY_ATTRIBUTE, REFERENCE_PANEL_ID);
        clearChildren(panel);
        panel.append(...panelsOf(content));
        panel.classList.add(OPEN_CLASS);
        if (!chained) {
            placePanel(panel, anchored.anchor);
        }
    };
    const show = function show(anchored: Anchored): void {
        cancel();
        if (current?.anchor === anchored.anchor) {
            return;
        }
        const chained = isInside(panel, anchored.anchor);
        current = anchored;
        if (chained) {
            disarm = armLink(anchored.anchor);
        }
        timer = setTimeout(
            () => {
                void reveal(anchored, chained);
            },
            chained ? REFERENCE_CHAIN_MS : REFERENCE_SHOW_MS,
        );
    };
    const onEnter = function onEnter(event: Event): void {
        const anchored = anchoredOf(event.target);
        if (anchored !== null) {
            show(anchored);
            return;
        }
        if (isInside(panel, event.target)) {
            settle();
        }
    };
    const onLeave = function onLeave(event: Event): void {
        const related = relatedOf(event);
        if (current === null || isInside(current.anchor, related)) {
            return;
        }
        if (isInside(panel, related)) {
            settle();
            return;
        }
        cancel();
        timer = setTimeout(hide, REFERENCE_HIDE_MS);
    };
    const onKey = function onKey(event: KeyboardEvent): void {
        if (event.key === ESCAPE_KEY) {
            hide();
        }
    };
    const onPointer = function onPointer(event: PointerEvent): void {
        if (event.pointerType !== TOUCH_POINTER) {
            return;
        }
        const anchored = anchoredOf(event.target);
        if (anchored === null) {
            touched = null;
            if (!isInside(panel, event.target)) {
                hide();
            }
            return;
        }
        touched = current?.anchor === anchored.anchor ? null : anchored.anchor;
        show(anchored);
    };
    const onClick = function onClick(event: MouseEvent): void {
        if (touched !== null && touched === linkOf(event.target)) {
            event.preventDefault();
            event.stopImmediatePropagation();
            touched = null;
        }
    };
    document.addEventListener("mouseover", onEnter);
    document.addEventListener("mouseout", onLeave);
    document.addEventListener("focusin", onEnter);
    document.addEventListener("focusout", onLeave);
    document.addEventListener("keydown", onKey);
    document.addEventListener("pointerdown", onPointer);
    document.addEventListener("click", onClick, true);
    return () => {
        hide();
        document.removeEventListener("mouseover", onEnter);
        document.removeEventListener("mouseout", onLeave);
        document.removeEventListener("focusin", onEnter);
        document.removeEventListener("focusout", onLeave);
        document.removeEventListener("keydown", onKey);
        document.removeEventListener("pointerdown", onPointer);
        document.removeEventListener("click", onClick, true);
        panel.remove();
    };
};
```
