- ui_template_find_list(.ts · .css) 새로 둠 — 거름 칸 2열 격자 · 찾기 칸 · 알림 줄 · 한 줄 결과 목록(꼬리표 · 이름 · 흐린 보조 글 · 단위) · 고름 하이라이트 · ↑ ↓ Enter - M02 로직 고르기 창(Labor_Pick)이 이 양식을 씀 — 구분 칸 · 층 단추 · data-pick 표지 그대로 - 확인 시나리오 resources/tester/ui_checks/51-9_고르기_찾기양식.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
/* =============================================================================
|
|
* 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 };
|
|
}
|