feat(spreadsheet): 공개 손잡이 addSheet · 되돌리기 한 번 · 탭 없이 띄운 시트에 탭 줄 붙임 · 도면 표 → 면적 새 시트 검증(83-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
2026-10-08 13:00:01 +09:00
co-authored by Claude Opus 5.5
parent 9c3e1f8193
commit 152d97af15
2 changed files with 185 additions and 0 deletions
+41
View File
@@ -36,6 +36,7 @@ import type {
CalcValues,
Command,
CommandEffect,
Sheet,
SheetCellAddress,
Workbook,
} from "./spreadsheet_types";
@@ -81,6 +82,10 @@ export interface SpreadsheetHandle {
/** 알약 키를 지금 시트 `칸`(A1)에 잇기 · 풀기(null) — 알약 누름과 같은 규칙 · 되돌리기 됨 ·
* 그 칸 다른 알약은 함께 둠(`replace` 면 뺌) · 주소가 틀리거나 읽기만이면 false */
setLink(key: string, cell: string | null, info?: LinkInfo, replace?: boolean): boolean;
/** 시트 하나 덧붙임(끝 자리 · 83-3 도면 → 면적) — 되돌리기 한 번 · onChange · 이름 겹치면 「이름 (n)」 ·
* `activate` 면 새 시트로 · 탭 줄 없이 띄운 시트면 탭 줄을 붙임(시트 1장으로 돌아가면 숨김) ·
* 붙인 시트 이름 · 읽기만이면 null */
addSheet(sheet: Sheet, opts?: { activate?: boolean }): string | null;
destroy(): void;
}
@@ -154,6 +159,8 @@ export function createSpreadsheet(
others.push(...extras.parts);
const detachKeys = attachKeys(ctx, ed, extras.keys);
const detachMouse = attachMouse(ctx, ed, extras.mouse);
/** `addSheet` 가 나중에 붙인 탭 줄(탭 없이 띄운 시트) */
let lateTabs: PartHandle | null = null;
const toast = el("div", { className: "ss-toast" });
let toastTimer = 0;
/** 알약 막대 · 연결 따라가기 — 선택이 있거나 문서에 연결이 있을 때만 불러옴(크기 예산) */
@@ -303,6 +310,40 @@ export function createSpreadsheet(
loadLinks(); // 처음 연결이면 알약 막대 · 칸 세모를 불러옴
return true;
},
addSheet(sheet, add) {
if (readOnly) return null;
const taken = (name: string) =>
ctx.book.시트.some((x) => x.이름.toLowerCase() === name.toLowerCase());
let 이름 = sheet.이름;
for (let n = 2; taken(이름); n += 1) 이름 = `${sheet.이름} (${n})`;
let id = sheet.id;
for (let n = ctx.book.시트.length + 1; ctx.book.시트.some((x) => x.id === id); n += 1)
id = `s${n}`;
const before = ctx.book.시트.length;
dispatch({ 종류: "시트더하기", 시트: { ...sheet, id, 이름 }, 자리: ctx.book.시트.length });
if (ctx.book.시트.length === before) return null; // 명령이 막힘(알림은 냄)
if (noTabs && !lateTabs) {
// 탭 줄 없이 띄운 시트 — 지금 붙이고 시트가 1장이면(되돌리기) 숨김
const part = mountTabs(ctx);
const bar = part.root;
const sync = (): void => {
const many = ctx.book.시트.length > 1;
if (bar) bar.hidden = !many;
root.classList.toggle("ss--notabs", !many);
};
lateTabs = {
root: part.root,
refresh: () => (part.refresh(), sync()),
destroy: () => part.destroy(),
};
if (bar) status.root ? status.root.before(bar) : root.append(bar);
others.push(lateTabs);
sync();
}
if (add?.activate) showSheet(id);
else for (const p of others) p.refresh();
return 이름;
},
destroy() {
clearTimeout(toastTimer);
detachKeys();