/* ============================================================================= * spreadsheet_find_panel.ts (주인 D) * 찾기(Ctrl+F) · 바꾸기(Ctrl+H) 작은 패널 — 부품 오른위에 뜸. 찾기 규칙은 `spreadsheet_find.ts`(sub1). * Enter = 다음 · Esc = 닫고 격자로. 바꾸기 = 활성 칸이 맞으면 바꾸고 다음으로(엑셀). * ========================================================================== */ import { el } from "@ui/ui_template_elements"; import { findAll, findNext, replaceAll, replaceOne, type FindOptions } from "./spreadsheet_find"; import { selectCell } from "./spreadsheet_selection"; import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types"; const TEXT = { find: "찾을 내용", replace: "바꿀 내용", next: "다음", one: "바꾸기", all: "모두 바꾸기", close: "닫기", case: "대소문자", whole: "전체 칸", formula: "수식 안", none: "찾을 수 없음", replaced: "{n} 개 바꿈", }; export interface FindPanelHandle extends PartHandle { open(replace: boolean): void; close(): void; } export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindPanelHandle { const query = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.find } }); const repl = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.replace } }); const status = el("span", { className: "ss-find__status" }); const button = (text: string, onClick: () => void): HTMLButtonElement => { const b = el("button", { className: "ss-find__btn", text, attrs: { type: "button" } }); b.addEventListener("click", onClick); return b; }; const check = (text: string): HTMLInputElement => { const box = el("input", { attrs: { type: "checkbox" } }); options.append(el("label", { className: "ss-find__opt", children: [box, text] })); return box; }; const closeBtn = button("×", close); closeBtn.title = TEXT.close; const options = el("div", { className: "ss-find__row" }); const caseBox = check(TEXT.case); const wholeBox = check(TEXT.whole); const formulaBox = check(TEXT.formula); const replaceRow = el("div", { className: "ss-find__row", children: [repl, button(TEXT.one, one), button(TEXT.all, all)], }); const root = el("div", { className: "ss-find", attrs: { hidden: "" }, children: [ el("div", { className: "ss-find__row", children: [query, button(TEXT.next, next), closeBtn], }), replaceRow, options, status, ], }); ctx.root.append(root); const opts = (): FindOptions => ({ 대소문자: caseBox.checked, 전체칸: wholeBox.checked, 수식안: formulaBox.checked, }); const here = () => ({ 시트: ctx.selection.시트, ...ctx.selection.활성 }); function next(): void { status.textContent = ""; if (!query.value) return; const hit = findNext(ctx.book, here(), query.value, opts()); if (!hit) return void (status.textContent = TEXT.none); if (hit.시트 !== ctx.selection.시트) ctx.showSheet(hit.시트); ctx.select(selectCell(ctx.sheet(), hit.r, hit.c), true); } function one(): void { if (ctx.readOnly || !query.value) return; const at = here(); const onIt = findAll(ctx.book, query.value, opts(), [at.시트]).some( (h) => h.r === at.r && h.c === at.c, ); if (onIt) replaceOne(ctx, at, query.value, repl.value, opts()); next(); } function all(): void { if (ctx.readOnly || !query.value) return; const n = replaceAll(ctx, query.value, repl.value, opts()); status.textContent = n ? TEXT.replaced.replace("{n}", String(n)) : TEXT.none; } function open(replace: boolean): void { replaceRow.hidden = !replace || ctx.readOnly; root.removeAttribute("hidden"); status.textContent = ""; query.focus(); query.select(); } function close(): void { root.setAttribute("hidden", ""); back(); } const onKey = (e: KeyboardEvent): void => { if (e.key === "Escape") close(); else if (e.key === "Enter") e.target === repl ? one() : next(); else return; e.preventDefault(); e.stopPropagation(); }; root.addEventListener("keydown", onKey); return { root, open, close, refresh() {}, destroy: () => root.remove(), }; }