/* ============================================================================= * ui_template_find_list.ts * 공용 찾기 목록 — 걸러내기 고르기 칸(2열 격자) · 찾기 칸 · 알림 줄 · 결과 목록(이름 왼쪽 맞춤 · 한 줄). * 키보드 — 찾기 칸에서 ↑ ↓ 로 옮기고 Enter 로 고름. 창 겉틀은 부르는 쪽 몫. * * const find = createFindList({ placeholder, filters: [sub.root, detail.root] }); * find.setRows([{ key, tag, name, sub, unit, onPick }]); * ========================================================================== */ import "./ui_template_find_list.css"; import { createInputField, el } from "./ui_template_elements"; export interface FindRow { /** 줄 표지(`data-pick`) */ key: string; name: string; /** 앞 흐린 꼬리표(종류 등) */ tag?: string; /** 이름 뒤 흐린 보조 글(키 · 구분 등) */ sub?: string; /** 끝 흐린 글(단위 등) */ unit?: string; onPick: () => void; } export interface FindList { root: HTMLElement; search: HTMLInputElement; hint: HTMLElement; setRows: (rows: FindRow[]) => void; } export function createFindList(opts: { placeholder: string; filters?: HTMLElement[]; /** 찾기 칸과 알림 줄 사이(층 단추 등) */ extra?: HTMLElement[]; }): FindList { const field = createInputField({ type: "search", placeholder: opts.placeholder }); const hint = el("p", { className: "ui-find__hint" }); const list = el("div", { className: "ui-find__list", attrs: { role: "listbox" } }); let rows: FindRow[] = []; let at = -1; const mark = (i: number): void => { list.children[at]?.classList.remove("is-active"); at = i; const row = list.children[at]; row?.classList.add("is-active"); row?.scrollIntoView({ block: "nearest" }); }; field.input.addEventListener("keydown", (ev) => { if (ev.key === "ArrowDown" || ev.key === "ArrowUp") { ev.preventDefault(); if (!rows.length) return; const step = ev.key === "ArrowDown" ? 1 : -1; mark((at + step + rows.length) % rows.length); } else if (ev.key === "Enter" && rows[at]) { ev.preventDefault(); rows[at].onPick(); } }); const setRows = (next: FindRow[]): void => { rows = next; at = -1; list.replaceChildren( ...rows.map((r, i) => { const row = el("button", { className: "ui-find__row", attrs: { type: "button", role: "option", "data-pick": r.key, title: r.name }, children: [ ...(r.tag ? [ el("span", { className: "ui-find__tag", attrs: { "data-pick-kind": "" }, text: r.tag, }), ] : []), el("span", { className: "ui-find__name", text: r.name }), el("span", { className: "ui-find__sub", text: r.sub ?? "" }), el("span", { className: "ui-find__unit", text: r.unit ?? "" }), ], }); row.addEventListener("click", r.onPick); row.addEventListener("mousemove", () => at !== i && mark(i)); return row; }), ); if (rows.length) mark(0); }; const filters = opts.filters?.length ? [el("div", { className: "ui-find__filters", children: opts.filters })] : []; const root = el("div", { className: "ui-find", children: [...filters, field.root, ...(opts.extra ?? []), hint, list], }); return { root, search: field.input, hint, setRows }; }