From fe53e0c71dce50871b9fc8cf4154a892742d4c16 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 2 Nov 2026 13:07:33 +0900 Subject: [PATCH] =?UTF-8?q?fix(730-1):=20=EC=97=91=EC=85=80=20=EA=B2=A9?= =?UTF-8?q?=EC=9E=90=20=EA=B3=A0=EB=A5=B8=20=EB=B2=94=EC=9C=84=20=C2=B7=20?= =?UTF-8?q?=EC=B0=B8=EC=A1=B0=20=ED=85=8C=EB=91=90=EB=A6=AC=20=C2=B7=20?= =?UTF-8?q?=EC=B1=84=EC=9A=B0=EA=B8=B0=20=EC=86=90=EC=9E=A1=EC=9D=B4?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=B4=EB=A6=AC=EB=8A=94=20=EC=98=81=EC=97=AD=20?= =?UTF-8?q?=EB=B0=96=EC=97=90=EC=84=9C=20=EC=9E=90=EB=A6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 굴림 때 테두리가 머리 칸 · 모서리 · 엑셀 화면 밖으로 벗어나던 흠(요구 566) - 테두리 층에 clip-path(고정 칸 · 굴리는 영역 크기 기준) · 손잡이는 밖이면 감춤 - 지킴 시험 test_730_grid_sel_clip 추가 Co-Authored-By: Claude Sonnet 5.5 --- A00_Common/spreadsheet/spreadsheet_grid.ts | 32 ++++++++++++++++++++++ resources/tester/test_730_grid_sel_clip.py | 30 ++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 resources/tester/test_730_grid_sel_clip.py diff --git a/A00_Common/spreadsheet/spreadsheet_grid.ts b/A00_Common/spreadsheet/spreadsheet_grid.ts index 9e0b97db7..416f5dd62 100644 --- a/A00_Common/spreadsheet/spreadsheet_grid.ts +++ b/A00_Common/spreadsheet/spreadsheet_grid.ts @@ -437,6 +437,16 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { }; } + /** 굴리는 영역 밖 테두리 자르기(`overlay` 좌표) — 고정 머리 칸 · 모서리 · 스크롤바 위로 안 나가게(요구 566). */ + function clipOf(b: CellBox, inFrozenCol: boolean, inFrozenRow: boolean): string { + const pad = (n: number): number => Math.max(0, Math.ceil(n)); + const top = inFrozenRow ? 0 : pad(frozenH - b.y); + const left = inFrozenCol ? 0 : pad(frozenW - b.x); + const bottom = pad(b.y + b.h - (frozenH + paneBR.clientHeight)); + const right = pad(b.x + b.w - (frozenW + paneBR.clientWidth)); + return `inset(${top}px ${right}px ${bottom}px ${left}px)`; + } + function renderSelection(): void { let selLayer = overlay.querySelector(".aislo-grid__sel-layer"); if (!selLayer) { @@ -470,6 +480,7 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { node.style.height = `${screen.h}px`; node.style.borderColor = "var(--aislo-grid-selected)"; node.style.borderWidth = i === 0 ? "2px" : "1px"; + node.style.clipPath = clipOf(screen, inFrozenCol, inFrozenRow); selLayer!.append(node); } // 활성 칸 테두리 — 칸 다시 그리기(`renderRowOfCells`) 없이 고름만 바뀌어도 옛 칸에 남지 않게. @@ -487,6 +498,17 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { handle.className = "aislo-grid__fill-handle"; handle.style.left = `${corner.x + corner.w - 4}px`; handle.style.top = `${corner.y + corner.h - 4}px`; + const hx = corner.x + corner.w; + const hy = corner.y + corner.h; + const hFC = handleAt.c < frozenCols; + const hFR = handleAt.r < frozenRows; + if ( + (!hFC && hx < frozenW) || + (!hFR && hy < frozenH) || + hx > frozenW + paneBR.clientWidth || + hy > frozenH + paneBR.clientHeight + ) + handle.style.display = "none"; selLayer!.append(handle); } @@ -525,6 +547,16 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle { node.style.width = `${merged.w}px`; node.style.height = `${merged.h}px`; node.style.borderColor = REF_COLORS[ref.색번호 % REF_COLORS.length]; + node.style.clipPath = clipOf( + { + x: parseFloat(node.style.left), + y: parseFloat(node.style.top), + w: merged.w, + h: merged.h, + }, + inFrozenCol, + inFrozenRow, + ); refLayer!.append(node); } } diff --git a/resources/tester/test_730_grid_sel_clip.py b/resources/tester/test_730_grid_sel_clip.py new file mode 100644 index 000000000..3f774ec45 --- /dev/null +++ b/resources/tester/test_730_grid_sel_clip.py @@ -0,0 +1,30 @@ +"""730-1 지킴 — 엑셀 격자 고른 범위 · 참조 테두리 · 채우기 손잡이는 굴리는 영역 밖으로 안 나감(요구 566). +화면 수치 판정은 tmp 의 playwright 일회 검증 · 여기서는 자르기 길이 빠지지 않았는지만 지킴.""" + +import re +from pathlib import Path + +SRC = (Path(__file__).parents[2] / "A00_Common/spreadsheet/spreadsheet_grid.ts").read_text( + encoding="utf-8" +) + + +def body(name: str) -> str: + m = re.search(rf"function {name}\(.*?\n }}\n", SRC, re.S) + assert m, name + return m.group(0) + + +def test_clip_helper_uses_frozen_and_pane_size(): + h = body("clipOf") + assert "frozenH" in h and "frozenW" in h + assert "paneBR.clientHeight" in h and "paneBR.clientWidth" in h + + +def test_selection_and_ref_borders_are_clipped(): + assert "clipPath = clipOf(" in body("renderSelection") + assert "clipPath = clipOf(" in body("setRefHighlights") + + +def test_fill_handle_hidden_outside_view(): + assert 'handle.style.display = "none"' in body("renderSelection")