From e48870765e14a5b7e78f8dac6b92d9eda6fc93af Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 27 Sep 2026 21:34:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(spreadsheet):=20C=20=EA=B2=A9=EC=9E=90=20?= =?UTF-8?q?=E2=80=94=20=EC=A1=B0=EA=B1=B4=EB=B6=80=20=EC=84=9C=EC=8B=9D(su?= =?UTF-8?q?b4)=20=EC=96=B9=EC=9D=8C=20=C2=B7=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=B9=B8=20=EC=B4=88=EB=A1=9D=20=EC=84=B8=EB=AA=A8(=EC=99=BC?= =?UTF-8?q?=20=EC=9C=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveCondFmtStyle 결과를 칸 서식 위에 덮어씀(채움·글자색·굵게) · 글자색은 조건부 서식이 숫자 형식([Red] 등)보다 이김(엑셀과 같음). 오류 칸은 기존 title 풍선에 더해 왼 위 초록 세모(엑셀 오류 검사 표시)도 그림. 필터로 걸러진 행 숨김은 이전 커밋(ebdee04f)에서 이미 buildRowGeometry 에 같이 넣어 둠(걸러짐 · 숨김 · 묶음 접힘 모두 처리). ORCA 시험 틀 — 조건 맞는 칸 배경·글자색·굵기 수치로 확인 · 오류 칸 title·세모 확인. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VVM3xZ1aUPcUTuW2WZSnwg --- A00_Common/spreadsheet/spreadsheet_grid.css | 13 +++++++ A00_Common/spreadsheet/spreadsheet_grid.ts | 37 ++++++++++++++----- .../spreadsheet/spreadsheet_grid_test.ts | 12 +++++- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/A00_Common/spreadsheet/spreadsheet_grid.css b/A00_Common/spreadsheet/spreadsheet_grid.css index be6845e2..9998e5b2 100644 --- a/A00_Common/spreadsheet/spreadsheet_grid.css +++ b/A00_Common/spreadsheet/spreadsheet_grid.css @@ -183,6 +183,19 @@ color: #c00; } +/* 오류 칸 — 엑셀 오류 검사 초록 세모(왼 위 · sub3 오류 까닭은 title 풍선). */ +.aislo-cell.is-error::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + border-style: solid; + border-width: 6px 6px 0 0; + border-color: #2e7d32 transparent transparent transparent; +} + .aislo-cell.is-selected { outline: 2px solid var(--aislo-grid-selected); outline-offset: -1px; diff --git a/A00_Common/spreadsheet/spreadsheet_grid.ts b/A00_Common/spreadsheet/spreadsheet_grid.ts index 6f455663..3dc806f1 100644 --- a/A00_Common/spreadsheet/spreadsheet_grid.ts +++ b/A00_Common/spreadsheet/spreadsheet_grid.ts @@ -34,6 +34,7 @@ import { type LineGeometry, type OutlineRun, } from "./spreadsheet_headers"; +import { resolveCondFmtStyle, type CondFmtValue } from "./spreadsheet_condfmt"; import { formatValue } from "./spreadsheet_numfmt"; import { MAX_COLS, MAX_ROWS } from "./spreadsheet_types"; import type { @@ -150,6 +151,23 @@ interface CellDisplay { color?: string; } +/** 칸 하나 값 — 식이면 엔진 값 · 아니면 입력 값(수는 Frac로) · 둘 다 없으면 빈 칸. */ +function scalarAt(ctx: SpreadsheetContext, sheet: Sheet, r: number, c: number): Scalar { + const cell = sheet.칸[toA1(r, c)]; + return cell?.식 !== undefined + ? ctx.engine.value(sheet.id, r, c) + : cell?.값 !== undefined + ? toScalar(cell.값) + : null; +} + +/** 조건부 서식(sub4) 이 값을 보는 형 — Frac 는 근사 수로(조건 비교용 · 오류는 조건 대상 아님). */ +function toCondFmtValue(scalar: Scalar): CondFmtValue { + if (scalar === null || typeof scalar === "boolean" || typeof scalar === "string") return scalar; + if ("error" in scalar) return null; + return Number(scalar.n) / Number(scalar.d); +} + function cellDisplay( ctx: SpreadsheetContext, sheet: Sheet, @@ -158,13 +176,7 @@ function cellDisplay( hAlign0: HAlign, code?: string, ): CellDisplay { - const cell = sheet.칸[toA1(r, c)]; - const scalar: Scalar = - cell?.식 !== undefined - ? ctx.engine.value(sheet.id, r, c) - : cell?.값 !== undefined - ? toScalar(cell.값) - : null; + const scalar = scalarAt(ctx, sheet, r, c); const formatted = formatValue(scalar, code); const isError = typeof scalar === "object" && scalar !== null && "error" in scalar; const hAlign: HAlign = hAlign0 !== "general" ? hAlign0 : formatted.정렬; @@ -365,7 +377,13 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { cellEl.dataset.c = String(c0); const addr = toA1(r0, c0); cellEl.dataset.addr = addr; - const style = cellStyleAt(book, sheet, r0, c0); + const baseStyle = cellStyleAt(book, sheet, r0, c0); + const condStyle = sheet.조건부서식?.length + ? resolveCondFmtStyle(sheet, r0, c0, { + value: (rr, cc) => toCondFmtValue(scalarAt(ctx, sheet, rr, cc)), + }) + : null; + const style = condStyle ? { ...baseStyle, ...condStyle } : baseStyle; const borders: ResolvedBorders = resolveBoxBorders( book, sheet, @@ -378,7 +396,8 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { ); const display = cellDisplay(ctx, sheet, r0, c0, style.가로 ?? "general", style.형식); applyCellStyle(cellEl, style, borders, display.hAlign, zoom); - if (display.color) cellEl.style.color = display.color; + // 조건부 서식 글자색이 이기고(엑셀과 같음) · 없으면 숫자 형식([Red] 등)이 정한 색. + if (display.color && !condStyle?.글자색) cellEl.style.color = display.color; const comment = sheet.comments?.[addr]; setCellText(cellEl, display.text, display.title || comment || undefined); cellEl.classList.toggle("is-error", display.isError); diff --git a/resources/tester/spreadsheet/spreadsheet_grid_test.ts b/resources/tester/spreadsheet/spreadsheet_grid_test.ts index 44c80247..53d9d3be 100644 --- a/resources/tester/spreadsheet/spreadsheet_grid_test.ts +++ b/resources/tester/spreadsheet/spreadsheet_grid_test.ts @@ -35,9 +35,18 @@ const sheet: Sheet = { A6: { 값: "선택 영역 가운데(centerContinuous) 보기", 서식: 5 }, A8: { 값: "hair 테두리 칸", 서식: 3 }, B8: { 값: 3.14159, 서식: 7 }, + G9: { 식: "1/0" }, J10000: { 값: "맨 끝 칸(1만 행 스크롤 확인용)" }, }, comments: { H4: "메모 — ROUNDDOWN 끝수 확인" }, + 조건부서식: [ + { + id: "cf1", + 범위: [{ r0: 3, c0: 7, r1: 3, c1: 7 }], + 조건: { 종류: "비교", 연산: "보다작음", 값: 0 }, + 스타일: { 채움: "#ffe0e0", 글자색: "#c00000", 굵게: true }, + }, + ], 병합: ["A1:H1", "B8:C9"], 열: { A: { 폭: 14 }, @@ -96,7 +105,8 @@ function noopEngine(): CalcEngine { return { update: () => [], rebuild: () => {}, - value: () => null, + value: (_sheet, r, c) => + r === 8 && c === 6 ? { error: "#DIV/0!", why: "0 으로 나눔(시험 칸 G9)" } : null, precedents: () => [], snapshot: () => ({}), };