- 부품 ui_template_pick_search(새) — 항목 많은 고르기(열둘 넘음)를 열면 찾기 판(팝오버 + 찾기 목록 · 높이 240 · 스크롤) · 글자로 거름 · 비우면 전부 · 고른 항목 둘레로 열림 · Esc · 바깥 누름 닫음 · 연료처럼 적은 고르기는 그대로 - 찾기 목록 setRows 둘째 인자 = 처음 짚을 줄(가운데로) - 기계 양식(M01 상세 · 내 기계 · 줄 쌓기 새 기계 셋 다): 직종 고르기에 붙임 · 전력 줄을 잡품 위로 · 잡품 = (주연료 + 전력) × % - 글: 쓰는 계산식 · 목록에 있음 · 꼴로 고름 · 글자로 찾기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.3 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
}
|