Files
Aislo/ui_template/ui_template_find_list.ts
T
eomsangdonandClaude Opus 5.5 f22b506d0e feat(801-2): 기계 양식 직종 고르기에 찾기 판 · 쓰는 계산식 글 · 미리보기 잡품 글
- 부품 ui_template_pick_search(새) — 항목 많은 고르기(열둘 넘음)를 열면 찾기 판(팝오버 + 찾기 목록 · 높이 240 · 스크롤) · 글자로 거름 · 비우면 전부 · 고른 항목 둘레로 열림 · Esc · 바깥 누름 닫음 · 연료처럼 적은 고르기는 그대로
- 찾기 목록 setRows 둘째 인자 = 처음 짚을 줄(가운데로)
- 기계 양식(M01 상세 · 내 기계 · 줄 쌓기 새 기계 셋 다): 직종 고르기에 붙임 · 전력 줄을 잡품 위로 · 잡품 = (주연료 + 전력) × %
- 글: 쓰는 계산식 · 목록에 있음 · 꼴로 고름 · 글자로 찾기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 18:12:43 +09:00

107 lines
3.8 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 }]); // 둘째 인자 = 처음 짚을 줄 key(가운데로 보임)
* ========================================================================== */
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;
/** active = 처음 짚을 줄 key(없으면 첫 줄) — 그 줄이 가운데로 보임 */
setRows: (rows: FindRow[], active?: string) => 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, block: ScrollLogicalPosition = "nearest"): void => {
list.children[at]?.classList.remove("is-active");
at = i;
const row = list.children[at];
row?.classList.add("is-active");
row?.scrollIntoView({ block });
};
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[], active?: string): 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;
}),
);
const pin = active === undefined ? -1 : rows.findIndex((r) => r.key === active);
if (pin >= 0) mark(pin, "center");
else 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 };
}