presentation/components/search.component.ts
presentation/components/search.component.ts is a file in Bane's Lab Site. 92 lines of code and 15 definitions.
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 "#core/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,
};
};