# presentation/components/search.component.ts

> 92 lines of code and 15 definitions.

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

## Definitions

- `showButtons` (lexical_declaration, line 70, exported)
- `cancel` (lexical_declaration, line 50, exported)
- `onSearchPage` (lexical_declaration, line 33)
- `showField` (lexical_declaration, line 65, exported)
- `unsubscribe` (lexical_declaration, line 83, exported)
- `attachSearchToggle` (lexical_declaration, line 48, exported)
- `iconButton` (lexical_declaration, line 25)
- `requestResults` (lexical_declaration, line 37)
- `KEY_EVENT` (lexical_declaration, line 22)
- `CLOSE_KEY` (lexical_declaration, line 23)
- `path` (lexical_declaration, line 41)
- `timer` (lexical_declaration, line 49, exported)
- `field` (lexical_declaration, line 56, exported)
- `openButton` (lexical_declaration, line 63, exported)
- `closeButton` (lexical_declaration, line 64, 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)

## Source

```typescript
import { HOME_PAGE, SEARCH_PAGE } from "#core/ids/page.ids";
import { ROUTE_CHANGED, ROUTE_REQUESTED } from "#core/ids/route.ids";
import { SEARCH_CLOSE_ICON, SEARCH_ICON } from "#configuration/icons/page.icons";
import {
    SEARCH_CLOSE_TITLE,
    SEARCH_FIELD_LABEL,
    SEARCH_FIELD_PLACEHOLDER,
    SEARCH_OPEN_TITLE,
} from "#configuration/strings/search.strings";
import {
    SEARCH_DEBOUNCE_MS,
    SEARCH_MIN_QUERY_LENGTH,
    SEARCH_QUERY_PARAM,
} from "#configuration/constants/search.constants";
import { emitEvent, subscribeEvent } from "#core/buses/base.bus";
import { pageFromPath, queryPath, queryValue } from "#assets/link.assets";
import { ICON_BASE_CLASS } from "#configuration/icons/element.icons";
import type { SearchToggle } from "#types/search.types";
import { createElement } from "#core/factories/element.factory";
import { createFilterBox } from "#presentation/components/filter.component";

const KEY_EVENT = "keydown";
const CLOSE_KEY = "Escape";

const iconButton = function iconButton(icon: string, title: string): HTMLButtonElement {
    return createElement("button", {
        attributes: { "aria-label": title, title, type: "button" },
        children: [createElement("i", { className: `${ICON_BASE_CLASS} ${icon}` })],
        className: "tab-button",
    });
};

const onSearchPage = function onSearchPage(): boolean {
    return pageFromPath(window.location.pathname, HOME_PAGE) === SEARCH_PAGE;
};

const requestResults = function requestResults(query: string): void {
    if (query.length < SEARCH_MIN_QUERY_LENGTH) {
        return;
    }
    const path = queryPath(SEARCH_PAGE, SEARCH_QUERY_PARAM, query);
    if (onSearchPage()) {
        window.history.replaceState({ page: SEARCH_PAGE }, "", path);
    }
    emitEvent({ name: ROUTE_REQUESTED, page: SEARCH_PAGE, path });
};

export const attachSearchToggle = function attachSearchToggle(bar: Element, buttons: readonly Element[]): SearchToggle {
    let timer: ReturnType<typeof setTimeout> | null = null;
    const cancel = function cancel(): void {
        if (timer !== null) {
            clearTimeout(timer);
            timer = null;
        }
    };
    const field = createFilterBox(SEARCH_FIELD_PLACEHOLDER, SEARCH_FIELD_LABEL, (query) => {
        cancel();
        timer = setTimeout(() => {
            timer = null;
            requestResults(query);
        }, SEARCH_DEBOUNCE_MS);
    });
    const openButton = iconButton(SEARCH_ICON, SEARCH_OPEN_TITLE);
    const closeButton = iconButton(SEARCH_CLOSE_ICON, SEARCH_CLOSE_TITLE);
    const showField = function showField(query: string): void {
        field.value = query;
        bar.replaceChildren(field, closeButton);
        field.focus();
    };
    const showButtons = function showButtons(): void {
        cancel();
        bar.replaceChildren(...buttons, openButton);
    };
    openButton.addEventListener("click", () => {
        showField("");
    });
    closeButton.addEventListener("click", showButtons);
    field.addEventListener(KEY_EVENT, (event) => {
        if (event.key === CLOSE_KEY) {
            showButtons();
        }
    });
    const unsubscribe = subscribeEvent(ROUTE_CHANGED, (event) => {
        if (event.page === SEARCH_PAGE && !field.isConnected) {
            showField(queryValue(window.location.search, SEARCH_QUERY_PARAM));
        }
    });
    showButtons();
    return {
        dispose: () => {
            cancel();
            unsubscribe();
        },
        showButtons,
        showField,
    };
};
```
