Files
Aislo/A00_Common/spreadsheet/spreadsheet_find_panel.ts
T
eomsangdonandClaude Opus 5.5 b037073b10 style(spreadsheet): 셀 서식 · 메모 · 찾기 · 패널 창 · 오류 ⚠ 를 부품 모달 · 탭 · 떠 있는 판 · 단추로 (PLAN 553-6)
- 셀 서식 창 = 부품 모달(40rem) · 탭(32) · 칸 = 부품 입력 · 고르기 28 · 자체 판 · 탭 · 칸 CSS 지움
- 메모 편집 = 공용 확인창 판(.ui-confirm) · 글 칸 = 부품 입력
- 찾기 · 바꾸기 = 떠 있는 판(머리 없음) · 칸 24 · 단추 22 · 닫기 = 아이콘 단추
- 데이터 패널 창(조건부 서식 · 유효성 · 이름) = 떠 있는 판(머리 · 닫기 ✕)
- 오류 ⚠ = 아이콘 단추(선 18) · 골라 붙여넣기 · 정렬 칸 · 알약 찾기 칸 = 부품 단추 · 고르기 · 입력
- 옛 보라 별칭 · 날 검정 가림막 → 공통 값(hover · accent · scrim) · 창 닫힘 감시가 모달도 기다림
- 날 단추 23 → 15(남음 = 수식 줄 칸 단추 · 격자 안 ▼ · 알약) · 자체 꼴 규칙 95 → 77 · 격자 색 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:11:14 +09:00

135 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* spreadsheet_find_panel.ts (주인 D)
* 찾기(Ctrl+F) · 바꾸기(Ctrl+H) 작은 패널 — 부품 오른위에 뜸. 찾기 규칙은 `spreadsheet_find.ts`(sub1).
* Enter = 다음 · Esc = 닫고 격자로. 바꾸기 = 활성 칸이 맞으면 바꾸고 다음으로(엑셀).
* ========================================================================== */
import { createButton, createIconButton, el, setUiSize } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
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 field = (placeholder: string): HTMLInputElement =>
setUiSize(el("input", { className: "ui-input ss-find__input", attrs: { placeholder } }), 24);
const query = field(TEXT.find);
const repl = field(TEXT.replace);
const status = el("span", { className: "ss-find__status" });
const button = (text: string, onClick: () => void): HTMLButtonElement => {
const b = createButton({ label: text, variant: "ghost", size: 22, onClick });
b.classList.add("ss-find__btn");
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 = createIconButton({ glyph: "×", title: TEXT.close, size: 22, onClick: close });
closeBtn.classList.add("ss-find__btn");
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)],
});
// 떠 있는 판 부품(머리 없음 · 553-6) — 자리는 `.ss-find`
const { root, body } = createFloatPanel({ popover: true });
root.classList.add("ss-find");
root.setAttribute("hidden", "");
body.append(
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(),
};
}