Files
Aislo/ui_template/ui_template_pick_search.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

89 lines
3.3 KiB
TypeScript

/* =============================================================================
* ui_template_pick_search.ts
* 고르기 칸에 찾기 붙이기(요구 801-2) — 항목이 많은 `createSelectField` 고르기를 열면 바로 아래에
* 찾기 판(팝오버 `createFloatPanel` + 찾기 목록 `createFindList` · 높이 제한 · 스크롤)이 뜸.
* 글자를 치면 이름에 든 것만 · 찾기를 비우면 다시 전부 · 고른 항목이 있으면 그 줄 둘레로 열림
* 고름 = select 값 바꿈 + change 알림(부르는 쪽 코드 그대로) · Esc · 바깥 누름 = 닫음
* 항목이 적으면(`min` 이하) 아무것도 안 함 — 보통 고르기 그대로
* 쓰는 법 = attachPickSearch(field.select, { placeholder: "글자로 찾기" })
* ========================================================================== */
import { createFindList } from "./ui_template_find_list";
import { createFloatPanel } from "./ui_template_float_panel";
import "./ui_template_pick_search.css";
const squash = (s: string): string => s.replace(/\s+/g, "").toLowerCase();
export function attachPickSearch(
select: HTMLSelectElement,
opts: { placeholder: string; min?: number },
): void {
if (select.options.length <= (opts.min ?? 12)) return;
const host = select.parentElement;
if (!host) return;
host.classList.add("ui-pick-search-host");
let panel: HTMLElement | null = null;
const close = (): void => {
panel?.remove();
panel = null;
document.removeEventListener("pointerdown", outside, true);
};
const outside = (ev: PointerEvent): void => {
if (panel && !panel.contains(ev.target as Node) && ev.target !== select) close();
};
const open = (): void => {
if (panel || select.disabled) return;
const fp = createFloatPanel({ popover: true, width: Math.max(select.offsetWidth, 260) });
fp.root.classList.add("ui-pick-search");
fp.root.style.left = `${select.offsetLeft}px`;
fp.root.style.top = `${select.offsetTop + select.offsetHeight + 2}px`;
const find = createFindList({ placeholder: opts.placeholder });
fp.body.append(find.root);
const all = [...select.options].map((o) => ({ value: o.value, text: o.text }));
const show = (): void => {
const q = squash(find.search.value);
const rows = q ? all.filter((o) => squash(o.text).includes(q)) : all;
find.setRows(
rows.map((o) => ({
key: o.value,
name: o.text,
onPick: () => {
select.value = o.value;
select.dispatchEvent(new Event("change", { bubbles: true }));
close();
select.focus();
},
})),
q ? undefined : select.value,
);
};
find.search.addEventListener("input", show);
find.search.addEventListener("keydown", (ev) => {
if (ev.key === "Escape") {
ev.preventDefault();
close();
select.focus();
}
});
host.append(fp.root);
panel = fp.root;
show();
find.search.focus();
document.addEventListener("pointerdown", outside, true);
};
select.addEventListener("mousedown", (ev) => {
ev.preventDefault();
if (panel) close();
else open();
});
select.addEventListener("keydown", (ev) => {
if (["Enter", " ", "ArrowDown"].includes(ev.key)) {
ev.preventDefault();
open();
}
});
}