# presentation/components/reference.component.ts

> 130 lines of code and 31 definitions.

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

## Definitions

- `placePanel` (lexical_declaration, line 43, exported)
- `edgeNode` (lexical_declaration, line 79)
- `clampPanel` (lexical_declaration, line 55, exported)
- `createRecordPanel` (lexical_declaration, line 127, exported)
- `titleRow` (lexical_declaration, line 107)
- `metaRow` (lexical_declaration, line 115)
- `summaryRow` (lexical_declaration, line 123)
- `armLink` (lexical_declaration, line 64, exported)
- `labelledRow` (lexical_declaration, line 88)
- `createChapterPanel` (lexical_declaration, line 136, exported)
- `joined` (lexical_declaration, line 84)
- `clampedTop` (lexical_declaration, line 39)
- `humanised` (lexical_declaration, line 98)
- `relationRow` (lexical_declaration, line 103)
- `NO_CLASS` (lexical_declaration, line 33)
- `HYPHEN` (lexical_declaration, line 34)
- `SPACE` (lexical_declaration, line 35)
- `LEFT_PROPERTY` (lexical_declaration, line 36)
- `TOP_PROPERTY` (lexical_declaration, line 37)
- `rect` (lexical_declaration, line 44, exported)
- `width` (lexical_declaration, line 45, exported)
- `height` (lexical_declaration, line 46, exported)
- `below` (lexical_declaration, line 47, exported)
- `top` (lexical_declaration, line 49, exported)
- `left` (lexical_declaration, line 56, exported)
- `dial` (lexical_declaration, line 65, exported)
- `arrow` (lexical_declaration, line 69, exported)
- `href` (lexical_declaration, line 80)
- `words` (lexical_declaration, line 99)
- `children` (lexical_declaration, line 116)
- `principle` (lexical_declaration, line 137, 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/link.component.ts](https://banes-lab.com/source/tree/presentation/components/link.component.ts.md)

## Used by

- [presentation/components/source.component.ts](https://banes-lab.com/source/tree/presentation/components/source.component.ts.md)
- [presentation/components/step.component.ts](https://banes-lab.com/source/tree/presentation/components/step.component.ts.md)
- [presentation/widgets/reference.widget.ts](https://banes-lab.com/source/tree/presentation/widgets/reference.widget.ts.md)

## Source

```typescript
import { CHAPTER_KIND_LABEL, REFERENCE_ARROW, RELATION_LABELS } from "#configuration/strings/reference.strings";
import type { ChapterReference, ReferenceRecord, ReferenceRelation } from "#types/reference.types";
import {
    FIELD_SEPARATOR,
    LABEL_SEPARATOR,
    LIST_SEPARATOR,
    PRINCIPLE_LABEL,
} from "#configuration/strings/ontology.strings";
import {
    REFERENCE_ABOVE_CLASS,
    REFERENCE_ARROW_CLASS,
    REFERENCE_CODE_CLASS,
    REFERENCE_DIAL_CLASS,
    REFERENCE_DIAL_MARKUP,
    REFERENCE_EDGES_CLASS,
    REFERENCE_GAP_PX,
    REFERENCE_LABEL_CLASS,
    REFERENCE_META_CLASS,
    REFERENCE_PENDING_CLASS,
    REFERENCE_RELATION_CLASS,
    REFERENCE_SUMMARY_CLASS,
    REFERENCE_TITLE_CLASS,
} from "#configuration/constants/reference.constants";
import { createElement, createVectorElement } from "#core/factories/element.factory";
import type { Disposer } from "#types/base.types";
import type { EdgeRef } from "#types/link.types";
import type { ElementChild } from "#types/element.types";
import { PIXEL } from "#configuration/constants/element.constants";
import { createLink } from "#presentation/components/link.component";
import { declareStyle } from "#core/registries/style.registry";
import { hrefOf } from "#domain/converters/ontology.converter";

const NO_CLASS = "";
const HYPHEN = "-";
const SPACE = " ";
const LEFT_PROPERTY = "left";
const TOP_PROPERTY = "top";

const clampedTop = function clampedTop(top: number, height: number): number {
    return Math.max(REFERENCE_GAP_PX, Math.min(top, window.innerHeight - height - REFERENCE_GAP_PX));
};

export const placePanel = function placePanel(panel: HTMLElement, anchor: Element): void {
    const rect = anchor.getBoundingClientRect();
    const width = panel.offsetWidth;
    const height = panel.offsetHeight;
    const below = rect.bottom + REFERENCE_GAP_PX + height <= window.innerHeight;
    const left = Math.max(REFERENCE_GAP_PX, Math.min(rect.left, window.innerWidth - width - REFERENCE_GAP_PX));
    const top = below ? rect.bottom + REFERENCE_GAP_PX : clampedTop(rect.top - REFERENCE_GAP_PX - height, height);
    panel.classList.toggle(REFERENCE_ABOVE_CLASS, !below);
    declareStyle(panel, LEFT_PROPERTY, String(left) + PIXEL);
    declareStyle(panel, TOP_PROPERTY, String(top) + PIXEL);
};

export const clampPanel = function clampPanel(panel: HTMLElement): void {
    const left = Math.max(
        REFERENCE_GAP_PX,
        Math.min(panel.offsetLeft, window.innerWidth - panel.offsetWidth - REFERENCE_GAP_PX),
    );
    declareStyle(panel, LEFT_PROPERTY, String(left) + PIXEL);
    declareStyle(panel, TOP_PROPERTY, String(clampedTop(panel.offsetTop, panel.offsetHeight)) + PIXEL);
};

export const armLink = function armLink(anchor: HTMLAnchorElement): Disposer {
    const dial = createElement("span", {
        children: [createVectorElement(REFERENCE_DIAL_MARKUP)],
        className: REFERENCE_DIAL_CLASS,
    });
    const arrow = createElement("span", { className: REFERENCE_ARROW_CLASS, text: REFERENCE_ARROW });
    anchor.classList.add(REFERENCE_PENDING_CLASS);
    anchor.append(dial, arrow);
    return () => {
        anchor.classList.remove(REFERENCE_PENDING_CLASS);
        dial.remove();
        arrow.remove();
    };
};

const edgeNode = function edgeNode(edge: EdgeRef): ElementChild {
    const href = edge.ref === null ? null : hrefOf(edge.ref);
    return href === null ? edge.label : createLink(href, NO_CLASS, [edge.label]);
};

const joined = function joined(edges: readonly EdgeRef[]): readonly ElementChild[] {
    return edges.flatMap((edge, at) => (at === 0 ? [edgeNode(edge)] : [LIST_SEPARATOR, edgeNode(edge)]));
};

const labelledRow = function labelledRow(label: string, value: readonly ElementChild[]): HTMLElement {
    return createElement("div", {
        children: [
            createElement("span", { className: REFERENCE_LABEL_CLASS, text: label + LABEL_SEPARATOR }),
            createElement("span", { children: value, className: REFERENCE_EDGES_CLASS }),
        ],
        className: REFERENCE_RELATION_CLASS,
    });
};

const humanised = function humanised(id: string): string {
    const words = id.split(HYPHEN).join(SPACE);
    return words.charAt(0).toUpperCase() + words.slice(1);
};

const relationRow = function relationRow(relation: ReferenceRelation): HTMLElement {
    return labelledRow(RELATION_LABELS.get(relation.relation) ?? humanised(relation.relation), joined(relation.edges));
};

const titleRow = function titleRow(name: string, code: string | null): HTMLElement {
    const children: ElementChild[] = [name];
    if (code !== null) {
        children.push(FIELD_SEPARATOR, createElement("code", { className: REFERENCE_CODE_CLASS, text: code }));
    }
    return createElement("div", { children, className: REFERENCE_TITLE_CLASS });
};

const metaRow = function metaRow(kind: string, layer: EdgeRef | null): HTMLElement {
    const children: ElementChild[] = [kind];
    if (layer !== null) {
        children.push(FIELD_SEPARATOR, edgeNode(layer));
    }
    return createElement("div", { children, className: REFERENCE_META_CLASS });
};

const summaryRow = function summaryRow(text: string | null): readonly HTMLElement[] {
    return text === null ? [] : [createElement("p", { className: REFERENCE_SUMMARY_CLASS, text })];
};

export const createRecordPanel = function createRecordPanel(record: ReferenceRecord): readonly HTMLElement[] {
    return [
        titleRow(record.name, record.code),
        metaRow(record.kind, record.layer),
        ...summaryRow(record.summary),
        ...record.relations.map(relationRow),
    ];
};

export const createChapterPanel = function createChapterPanel(chapter: ChapterReference): readonly HTMLElement[] {
    const principle = chapter.principle === null ? [] : [labelledRow(PRINCIPLE_LABEL, [chapter.principle])];
    return [
        titleRow(chapter.title, null),
        metaRow(CHAPTER_KIND_LABEL, null),
        ...summaryRow(chapter.intro),
        ...principle,
        ...chapter.relations.map(relationRow),
    ];
};
```
