feat(spreadsheet): 3단계 — 시트 숨김·보이기·탭 색(계약 더함)
spreadsheet_sheet_extra.ts 새로 만듦 — 시트 관리 창(숨김 토글·탭 색 고르기), 마지막 보이는 시트는 숨김 거절, 활성 시트를 숨기면 다른 시트로 옮김. 계약(spreadsheet_types.ts)에 Sheet.숨김·탭색 최소로 더함(git-sync 뒤 더함) · 파일 목록 표에 이번 셋(상태 표시줄·자동 합계·시트 여분) 적음 · css(spreadsheet_stage3.css)에 세 파일 스타일 얹음. 엔진 A·격자 C 안 건드림 — 탭 줄(spreadsheet_tabs.ts) 이음은 sub7 몫(머리 주석에 적음). 시험 resources/tester/spreadsheet/test_stage3_sheet_extra.ts 9개 통과. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_sheet_extra.ts (3단계 · 주인 sub_laptop_4)
|
||||
* 시트 숨기기 · 보이기 · 시트 탭 색. 저장 자리 `Sheet.숨김` · `Sheet.탭색`(0 계약에 방금 더함).
|
||||
* 이 파일은 관리 창(전체 시트 목록 + 숨김 · 색 조작)만 그림 — 실제 탭 줄(`spreadsheet_tabs.ts`,
|
||||
* E 파일)을 안 건드림.
|
||||
*
|
||||
* 잇는 법(E `spreadsheet_tabs.ts` · sub7 — 지금은 하나도 안 이어짐):
|
||||
* 1) `mountTabs` 의 `render()` 가 `ctx.book.시트` 를 돌 때 `숨김` 인 시트는 탭을 안 그림.
|
||||
* 2) 탭 엘리먼트에 `sheet.탭색` 이 있으면 밑줄 · 배경을 그 색으로(엑셀처럼).
|
||||
* 3) 숨은 시트를 다시 보이려면 탭 우클릭 메뉴에 [숨긴 시트 보이기] 항목을 두고 이 파일의
|
||||
* `createSheetExtraPanel(ctx)` 를 열거나, `showSheet(ctx, id)` 를 바로 부름.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import type { Sheet } from "./spreadsheet_types";
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
// css 는 stage3.css 에 얹음(잇는 법 머리 참고) — 실제로 붙일 때 `import "./spreadsheet_stage3.css";` 더함.
|
||||
|
||||
function applySheet(ctx: SpreadsheetContext, sheetId: string, next: Sheet): void {
|
||||
ctx.dispatch({ 종류: "시트통째", 시트: { ...next, id: sheetId } });
|
||||
}
|
||||
|
||||
/** 시트를 숨김 — 마지막 보이는 시트는 못 숨김(까닭 글 돌려줌 · 되면 null). 활성 시트를 숨기면
|
||||
* 다른 보이는 시트로 활성을 옮김. */
|
||||
export function hideSheet(ctx: SpreadsheetContext, id: string): string | null {
|
||||
const others = ctx.book.시트.filter((s) => s.id !== id && !s.숨김);
|
||||
if (!others.length) return "마지막 보이는 시트는 못 숨김";
|
||||
const sheet = ctx.book.시트.find((s) => s.id === id);
|
||||
if (!sheet || sheet.숨김) return null;
|
||||
applySheet(ctx, id, { ...sheet, 숨김: true });
|
||||
if (ctx.book.활성 === id) ctx.showSheet(others[0].id);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 숨긴 시트를 다시 보임. */
|
||||
export function unhideSheet(ctx: SpreadsheetContext, id: string): void {
|
||||
const sheet = ctx.book.시트.find((s) => s.id === id);
|
||||
if (!sheet || !sheet.숨김) return;
|
||||
applySheet(ctx, id, { ...sheet, 숨김: false });
|
||||
}
|
||||
|
||||
/** 탭 색 — `color` 가 null 이면 색을 지움(기본 탭 색으로). */
|
||||
export function setSheetTabColor(ctx: SpreadsheetContext, id: string, color: string | null): void {
|
||||
const sheet = ctx.book.시트.find((s) => s.id === id);
|
||||
if (!sheet) return;
|
||||
const next: Sheet = { ...sheet };
|
||||
if (color) next.탭색 = color;
|
||||
else delete next.탭색;
|
||||
applySheet(ctx, id, next);
|
||||
}
|
||||
|
||||
/** 시트 관리 창 — 전체 시트 목록 + 숨김 토글 · 탭 색 고르기. */
|
||||
export function createSheetExtraPanel(ctx: SpreadsheetContext): PartHandle {
|
||||
const root = el("div", { className: "ss-sheetextra" });
|
||||
const list = el("ul", { className: "ss-sheetextra__list" });
|
||||
let error: HTMLElement | null = null;
|
||||
|
||||
const showError = (msg: string | null): void => {
|
||||
error?.remove();
|
||||
error = null;
|
||||
if (!msg) return;
|
||||
error = el("p", { text: msg });
|
||||
error.style.color = "var(--color-danger, red)";
|
||||
root.append(error);
|
||||
};
|
||||
|
||||
function render(): void {
|
||||
list.replaceChildren(
|
||||
...ctx.book.시트.map((sheet) => {
|
||||
const li = el("li", { className: "ss-sheetextra__row" });
|
||||
const label = el("span", { text: sheet.이름 + (sheet.숨김 ? "(숨김)" : "") });
|
||||
const colorInput = el("input", {
|
||||
attrs: { type: "color", value: sheet.탭색 ?? "#ffffff" },
|
||||
}) as HTMLInputElement;
|
||||
colorInput.addEventListener("change", () =>
|
||||
setSheetTabColor(ctx, sheet.id, colorInput.value),
|
||||
);
|
||||
const colorClear = createButton({ label: "색 지움", variant: "ghost" });
|
||||
colorClear.addEventListener("click", () => {
|
||||
setSheetTabColor(ctx, sheet.id, null);
|
||||
render();
|
||||
});
|
||||
const toggle = createButton({
|
||||
label: sheet.숨김 ? "보이기" : "숨기기",
|
||||
variant: "ghost",
|
||||
});
|
||||
toggle.addEventListener("click", () => {
|
||||
if (sheet.숨김) {
|
||||
unhideSheet(ctx, sheet.id);
|
||||
showError(null);
|
||||
} else {
|
||||
showError(hideSheet(ctx, sheet.id));
|
||||
}
|
||||
render();
|
||||
});
|
||||
li.append(label, colorInput, colorClear, toggle);
|
||||
return li;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
root.append(el("h4", { text: "시트 관리" }), list);
|
||||
render();
|
||||
|
||||
return {
|
||||
root,
|
||||
refresh: render,
|
||||
destroy(): void {
|
||||
root.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_stage3.css (주인 sub_laptop_4)
|
||||
* 조건부 서식 · 데이터 유효성 · 이름 정의 관리 창 — 화면 스타일 한 벌. 색 · 간격은 테마 토큰만.
|
||||
* 조건부 서식 · 데이터 유효성 · 이름 정의 · 상태 표시줄 · 시트 관리 창 — 화면 스타일 한 벌.
|
||||
* 색 · 간격은 테마 토큰만.
|
||||
* ========================================================================== */
|
||||
|
||||
.ss-condfmt,
|
||||
.ss-validation,
|
||||
.ss-names {
|
||||
.ss-names,
|
||||
.ss-sheetextra {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
@@ -18,7 +20,8 @@
|
||||
|
||||
.ss-condfmt__list,
|
||||
.ss-validation__list,
|
||||
.ss-names__list {
|
||||
.ss-names__list,
|
||||
.ss-sheetextra__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -29,7 +32,8 @@
|
||||
|
||||
.ss-condfmt__row,
|
||||
.ss-validation__row,
|
||||
.ss-names__row {
|
||||
.ss-names__row,
|
||||
.ss-sheetextra__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
@@ -85,3 +89,23 @@
|
||||
.ss-validation__dropdown-item:hover {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.ss-statusbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
padding: 2px var(--spacing-8);
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.ss-statusbar__stats {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ss-statusbar__zoom {
|
||||
color: var(--color-border);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,10 @@
|
||||
* 3 spreadsheet_condfmt.ts 조건부 서식 규칙 · 관리 창(주인 sub_laptop_4 · 안 이어짐) ~260
|
||||
* 3 spreadsheet_validation.ts 데이터 유효성 규칙 · 관리 창(주인 sub_laptop_4 · 안 이어짐) ~230
|
||||
* 3 spreadsheet_names.ts 이름 정의 · 이름 관리 창(주인 sub_laptop_4 · 안 이어짐) ~220
|
||||
* 3 spreadsheet_stage3.css 위 셋 화면 스타일 한 벌 ~90
|
||||
* 3 spreadsheet_statusbar.ts 아래 상태 표시줄 — 합계 · 평균 · 개수 등(주인 sub_laptop_4 · 안 이어짐) ~150
|
||||
* 3 spreadsheet_autosum.ts 자동 합계(Alt+= · Σ, 주인 sub_laptop_4 · 안 이어짐) ~90
|
||||
* 3 spreadsheet_sheet_extra.ts 시트 숨김 · 보이기 · 탭 색(주인 sub_laptop_4 · 계약 더함 · 안 이어짐) ~150
|
||||
* 3 spreadsheet_stage3.css 위 여섯 화면 스타일 한 벌 ~140
|
||||
* F common_util/common_util_spreadsheet_node.ts Node 진입(재계산 · 폴더 밖) ~40
|
||||
* F common_util/common_util_spreadsheet.py 파이썬 껍데기(common_util_node_bundle) ~60
|
||||
* F package.json `build:spreadsheet` · M02 `basis` 종류(Store · Layers) · 옛 산출근거 옮김 ·
|
||||
@@ -172,6 +175,10 @@ export interface Sheet {
|
||||
/** 이 시트에서 정의한 이름 — 범위는 이 시트 안(`spreadsheet_names.ts`). 식 나무의 `{type:"name"}` 는
|
||||
* A 의 eval 이 `resolveName` 으로 풀어야 함(지금 안 이어짐 — 8-3 절 참고). */
|
||||
이름정의?: NamedRange[];
|
||||
/** 시트 숨김(`spreadsheet_sheet_extra.ts`) — 탭은 `spreadsheet_tabs.ts`(E)가 이 값으로 걸러야 함(안 이어짐). */
|
||||
숨김?: boolean;
|
||||
/** 시트 탭 색 `#RRGGBB`(`spreadsheet_sheet_extra.ts`) — 탭 칠은 E 몫(안 이어짐). */
|
||||
탭색?: string;
|
||||
}
|
||||
|
||||
/** 새 시트 기본값 — 값은 사용자 협의로 확정(9_엑셀검토 8장 · 임의 확정 금지). 없으면 A 의 임시값. */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* =============================================================================
|
||||
* test_stage3_sheet_extra.ts — spreadsheet_sheet_extra 시험. 돌리기: npx tsx <이 파일>.
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
hideSheet,
|
||||
setSheetTabColor,
|
||||
unhideSheet,
|
||||
} from "../../../A00_Common/spreadsheet/spreadsheet_sheet_extra.ts";
|
||||
import { assertEqual, makeBook, makeCtx, makeSheet } from "./test_stage2_helpers.ts";
|
||||
|
||||
const s1 = makeSheet("s1", {});
|
||||
const s2 = makeSheet("s2", {});
|
||||
const book = makeBook([s1, s2]);
|
||||
book.활성 = "s1";
|
||||
const shown: string[] = [];
|
||||
const ctx = { ...makeCtx(book, "s1"), showSheet: (id: string) => shown.push(id) };
|
||||
|
||||
assertEqual(hideSheet(ctx, "s1"), null, "hideSheet 성공 — 까닭 없음");
|
||||
assertEqual(book.시트[0].숨김, true, "s1 이 숨겨짐");
|
||||
assertEqual(ctx.applied.at(-1)?.종류, "시트통째", "hideSheet 은 시트통째로 나감");
|
||||
assertEqual(shown, ["s2"], "활성 시트를 숨기면 다른 보이는 시트로 옮김");
|
||||
|
||||
assertEqual(hideSheet(ctx, "s2"), "마지막 보이는 시트는 못 숨김", "마지막 보이는 시트는 거절");
|
||||
assertEqual(book.시트[1].숨김, undefined, "거절되면 안 바뀜");
|
||||
|
||||
unhideSheet(ctx, "s1");
|
||||
assertEqual(book.시트[0].숨김, false, "unhideSheet 로 다시 보임");
|
||||
|
||||
setSheetTabColor(ctx, "s2", "#ff0000");
|
||||
assertEqual(book.시트[1].탭색, "#ff0000", "탭 색 지정");
|
||||
setSheetTabColor(ctx, "s2", null);
|
||||
assertEqual(book.시트[1].탭색, undefined, "탭 색 지움");
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_sheet_extra 시험 끝");
|
||||
Reference in New Issue
Block a user