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: () => ({}), };