fix(sheet): 글 열(꼴=글)의 일위대가 · 도면 · 산출근거 칸은 빈칸 · 잠김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Kyw9qnkJ94pC1A4PfpDtA
This commit is contained in:
2026-09-28 10:01:09 +09:00
co-authored by Claude Opus 5.5
parent 31bcad6454
commit 9b9787f60a
4 changed files with 21 additions and 5 deletions
@@ -19,12 +19,13 @@ OPS = ROOT / "ui_template" / "sheet" / "ui_template_sheet_ops.ts"
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
SCRIPT = """
const { navRows, selectionOf } = await import(process.argv[1]);
const { navRows, selectionOf, isBlankPick } = await import(process.argv[1]);
const doc = JSON.parse(process.argv[2]);
console.log(JSON.stringify({
master: navRows(doc, "master"),
project: navRows(doc, "project"),
pick: [selectionOf(doc, "s:drawing", "c2"), selectionOf(doc, "d:r1", "c1"), selectionOf(doc, "s:unit", "zz")],
blank: ["s:price", "s:drawing", "s:basis", "s:desc", "d:r1"].map((k) => [isBlankPick(k, { id: "t", 머리: [], 꼴: "글" }), isBlankPick(k, { id: "n", 머리: [], 꼴: "수" })]),
}));
"""
@@ -62,3 +63,9 @@ def test_selection_shape():
{"row": "d:r1", "col": 0, "colId": "c1"},
{"row": "s:unit", "col": -1, "colId": None},
]
def test_text_column_pick_rows_blank():
assert _run()["blank"] == [
[True, False], [True, False], [True, False], [False, False], [False, False],
] # fmt: skip
+2
View File
@@ -22,6 +22,7 @@ import {
canDeleteRow,
deleteColumn,
deleteRow,
isBlankPick,
KEY_BASIS,
KEY_DRAWING,
KEY_PRICE,
@@ -235,6 +236,7 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio
const sel = state.sel;
const col = sel && colOf(sel.c);
if (!sel || !col) return;
if (isBlankPick(sel.r, col)) return; // 글 열은 빈칸 · 잠김
if (opts.onPick && state.mode === "master" && PICK_ROWS.includes(sel.r)) {
if (initial === undefined) void pick(sel.r, col.id); // 글자를 쳐서는 안 엶
return;
@@ -28,6 +28,10 @@ export type MasterRowKey = (typeof MASTER_ROWS)[number];
/** 조회 후 선택 줄 — `onPick` 을 주면 이 줄 칸은 치지 않고 고름. */
export const PICK_ROWS: readonly string[] = [KEY_PRICE, KEY_DRAWING, KEY_BASIS];
/** 글 열(NO · 측점 · 비고 같은 꼴=글)의 일위대가 · 도면 · 산출근거 칸 — 빈칸 · 잠김. */
export const isBlankPick = (key: string, col: SheetColumn): boolean =>
PICK_ROWS.includes(key) && col.꼴 === "글";
/** 키보드로 옮겨 다니는 줄 차례(쪽을 가로지름). */
export function navRows(doc: SheetDoc, mode: SheetMode): string[] {
const special = mode === "master" ? [KEY_UNIT, ...MASTER_ROWS] : [KEY_UNIT];
@@ -14,6 +14,7 @@ import { columnNames, formulaLabel, headMinWidths, textWidth } from "./ui_templa
import {
cellEditable,
isBoundColumn,
isBlankPick,
isCalcColumn,
KEY_BASIS,
KEY_DRAWING,
@@ -64,7 +65,7 @@ function headFont(): string {
/** 칸을 고칠 수 있나 — 줄 열쇠 기준(머리 칸은 따로) · 도면 · 산출근거 줄은 손으로 못 침. */
export function keyEditable(state: RenderState, key: string, col: SheetColumn): boolean {
if (key === KEY_DRAWING || key === KEY_BASIS) return false;
if (key === KEY_DRAWING || key === KEY_BASIS || isBlankPick(key, col)) return false;
if (key.startsWith("s:")) return state.mode === "master";
if (key.startsWith("t:")) return false;
const row = state.doc.줄.find((r) => `d:${r.id}` === key);
@@ -178,8 +179,10 @@ export function renderSheet(state: RenderState): HTMLElement {
)[key];
const tr = el("tr", { className: "ui-sheet__meta", children: [gutter("th", st(label))] });
cols.forEach((col, j) => {
const text =
key === "s:formula"
const blank = isBlankPick(key, col);
const text = blank
? ""
: key === "s:formula"
? formulaLabel(col.식 ?? "", names, col.id)
: key === "s:desc"
? ""
@@ -188,7 +191,7 @@ export function renderSheet(state: RenderState): HTMLElement {
: (col.일위대가 ?? "");
const td = bodyCell(key, col, j, text);
td.classList.remove("is-num");
if (state.pick && PICK_ROWS.includes(key)) td.classList.add("is-pick");
if (state.pick && PICK_ROWS.includes(key) && !blank) td.classList.add("is-pick");
if (key === "s:formula" && col.식) {
// 같은 종류 열은 짧은 이름 · 풍선은 긴 이름 + 고칠 때 쓰는 id 식
td.textContent = "";