fix(B09): 설계내역서 폴더 줄 글꼴 한 벌 · 들여쓰기 넓힘 · 끌기 놓을 자리 표시 · 데이터 점검 틈 (240 · 121 · 122)

- 레벨 고쳐 생긴 폴더 줄도 처음 폴더 줄과 같은 꼴(is-title · 크기 = 레벨)
- 들여쓰기 한 단 22px · 끌기 좌우 한 단 = 들여쓰기 폭
- 끌기 미리 보기 = 레벨 시작 점 · 「○○ 안 · 레벨 N」 · 들어갈 폴더 줄 테두리 · 끄는 줄 흐림
- 데이터 점검 [업데이트] 위 틈 = 카드 왼쪽 여유

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
2026-10-10 20:13:30 +09:00
co-authored by Claude Opus 5.5
parent 58b35b3ab9
commit fe34f561a0
7 changed files with 135 additions and 14 deletions
+45 -7
View File
@@ -66,11 +66,6 @@
font-size: var(--text-caption);
}
/* 182 설계내역서 묶음 줄 굵게 · 레벨 들여쓰기 = 공용 표 level 옵션(201-1) */
.b09-estimation .ui-data-table__table tbody tr.is-group td {
font-weight: var(--font-weight-bold);
}
/* 166-2 설계내역서 손 줄 — 사용자가 더한 줄 표시(왼쪽 띠) */
.b09-estimation .ui-data-table__table tbody tr.is-hand td:first-child {
background-image: linear-gradient(to right, var(--color-primary) 3px, transparent 3px);
@@ -163,6 +158,11 @@
background: var(--color-warning);
}
/* 122 데이터 점검 [업데이트] — 제목 아래 틈 = 카드 왼쪽 여유와 같게 */
.b09-options [data-b09-refresh] {
margin-top: calc(var(--spacing-8) + var(--spacing-4));
}
/* 연결 점검 목록 — 이름 · 탭(작게) · 알림 글 */
.b09-check__head,
.b09-check__note {
@@ -179,13 +179,51 @@
/* 줄 끌기(204-1) — 놓을 자리 · 레벨 미리 보기 선 · 못 놓는 자리 = 빨간 점선 */
.b09-estimation__drop {
--drop-c: var(--color-primary);
position: fixed;
z-index: 1000;
height: 0;
border-top: 2px solid var(--color-primary);
border-top: 2px solid var(--drop-c);
color: var(--drop-c);
pointer-events: none;
}
.b09-estimation__drop.is-bad {
border-top: 2px dashed var(--color-danger);
--drop-c: var(--color-danger);
border-top-style: dashed;
}
/* 121 선 머리 = 레벨 시작 점 + 「어디 안 · 몇 레벨」 · 들어갈 폴더 줄 = 테두리 · 끄는 줄 = 흐리게 */
.b09-estimation__drop::before {
content: "";
position: absolute;
top: -6px;
left: -5px;
width: 8px;
height: 8px;
border: 2px solid;
border-radius: 50%;
background: var(--color-canvas);
}
.b09-estimation__drop-tag {
position: absolute;
top: -11px;
padding: 1px var(--spacing-8);
border: 1px solid;
border-radius: var(--radius-pills);
background: var(--color-surface-raised);
font-size: var(--text-caption);
white-space: nowrap;
}
.b09-estimation [data-b09-table="bill"] tbody tr.is-drop-into td {
box-shadow:
inset 0 2px 0 var(--color-primary),
inset 0 -2px 0 var(--color-primary);
background-color: color-mix(in srgb, var(--color-primary) 14%, transparent);
}
.b09-estimation [data-b09-table="bill"] tbody tr.is-drag-src td {
opacity: 0.45;
}
@@ -99,7 +99,8 @@ export const buildBill: TabBuild = (ctx): BillTable => {
rowClass: (i) => {
const r = rows[i];
if (!r) return null;
const title = r.kind === "title" ? "is-title" : r.kind === "group" ? "is-group" : "";
// 240 폴더 줄 = 처음 · 레벨 고쳐 생긴 것 모두 같은 줄 꼴(글자 크기 = 레벨 · 공용 is-lvN)
const title = r.kind === "item" ? "" : "is-title";
return [title, r.auto ? "" : "is-hand"].filter((c) => c).join(" ") || null;
},
// 228-1 레벨 = 셈 나무 깊이(들여 · 내어쓰기 · 끌기 = 다시 셈 · 다시 그림) · 글꼴 = 레벨 · 묶음(빈 공종도)
@@ -5,6 +5,7 @@
* 놓을 자리 = 줄 사이 틈(포인터가 줄 가운데 위 = 그 줄 앞) · 끄는 줄 자손 = 함께 감 · 자기 자손 안 = 못 놓음
* 레벨 = 위 줄 + 1(위 줄 아래로 · 항목이면 저절로 묶음) ~ 아래 줄 레벨 · 맨 위(1) = 맨 위 줄만
* 놓기 = 표 drop(`…_Bill_Edit` dropMove — 메뉴와 같은 지킬 것) · 미리 보기 선(못 놓음 = is-bad · 커서 not-allowed)
* 미리 보기(121) = 선 머리 글 「○○ 안 · 레벨 N」 + 들어갈 폴더 줄 테두리 + 끄는 줄 흐리게 · 좌우 한 단 = 들여쓰기 폭(--dt-indent)
* 잡기 = 글 칸 · 단추 밖 아무 칸 · 6px 넘게 움직여야 끌기(그냥 누름 = 줄 고름)
* ========================================================================== */
@@ -64,9 +65,15 @@ export function attachDrag(root: HTMLElement, table: BillTable, done: (key: stri
const tr = target.closest<HTMLElement>("tr[data-i]");
if (!tr || !root.contains(tr)) return;
const from = Number(tr.dataset.i);
const step = parseFloat(getComputedStyle(tr).fontSize) || 14;
const step = parseFloat(getComputedStyle(tr).getPropertyValue("--dt-indent")) || 14;
const line = document.createElement("div");
line.className = "b09-estimation__drop";
const tag = document.createElement("span");
tag.className = "b09-estimation__drop-tag";
line.append(tag);
const rowEl = (i: number): HTMLElement | null => root.querySelector(`tr[data-i="${i}"]`);
let into: HTMLElement | null = null;
let lifted: HTMLElement[] = [];
let on = false;
let run: (() => void) | null = null;
let seen = "";
@@ -76,6 +83,13 @@ export function attachDrag(root: HTMLElement, table: BillTable, done: (key: stri
if (!on) {
on = true;
document.body.append(line);
const lv = Number(table.rows[from]?.level ?? 1);
for (let i = from; i === from || Number(table.rows[i]?.level ?? 1) > lv; i++) {
const el = rowEl(i);
if (!el) break;
el.classList.add("is-drag-src");
lifted.push(el);
}
document.body.style.userSelect = "none";
window.getSelection()?.removeAllRanges();
}
@@ -95,14 +109,24 @@ export function attachDrag(root: HTMLElement, table: BillTable, done: (key: stri
if (key !== seen) {
seen = key;
run = at ? table.drop!(from, at.parent, at.before) : null;
const p = at?.parent ? table.rows.findIndex((r) => String(r.key) === at.parent) : -1;
into?.classList.remove("is-drop-into");
into = p >= 0 ? rowEl(p) : null;
into?.classList.add("is-drop-into");
tag.textContent = !run
? "못 놓음"
: `${p >= 0 ? `「${String(table.rows[p].name ?? "").trim()}」 안` : "맨 위"} · 레벨 ${at!.level}`;
}
const cell = tr.querySelector("td.ui-data-table__lv") ?? tr;
const box = root.getBoundingClientRect();
const left = box.left + ((at?.level ?? want) - 1) * step + 8;
const left = cell.getBoundingClientRect().left + ((at?.level ?? want) - 1) * step + 8;
Object.assign(line.style, {
top: `${y - 1}px`,
left: `${left}px`,
width: `${Math.max(box.right - left, 40)}px`,
});
// 머리 글 = 명칭 칸 바로 오른쪽 · 선 위에 걸침(줄 이름을 덮지 않게)
tag.style.left = `${Math.max(12, cell.getBoundingClientRect().right - left + 8)}px`;
line.classList.toggle("is-bad", !run);
document.body.style.cursor = run ? "grabbing" : "not-allowed";
};
@@ -111,6 +135,9 @@ export function attachDrag(root: HTMLElement, table: BillTable, done: (key: stri
window.removeEventListener("pointerup", stop);
window.removeEventListener("pointercancel", stop);
line.remove();
into?.classList.remove("is-drop-into");
for (const el of lifted) el.classList.remove("is-drag-src");
lifted = [];
document.body.style.userSelect = "";
document.body.style.cursor = "";
if (!on) return;
+1 -1
View File
@@ -311,7 +311,7 @@ def test_연결_단추_칸_메뉴_고르기_창_TS():
assert out["link"] == [None, {"tab": "dg", "key": "DG:a"}] # 단추 칸 = 이동 안 함
assert out["cls"] == [
"is-title",
"is-group is-hand",
"is-title is-hand",
"is-hand",
None,
] # fmt: skip 201-1 레벨 = 공용 표 level
@@ -88,7 +88,7 @@ def test_CSS_레벨_글꼴_규칙():
css = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
tokens = dict(re.findall(r"(--dt-(?:lv\d-size|indent)): ([^;]+);", css))
assert tokens == {
"--dt-indent": "var(--text-body-sm)",
"--dt-indent": "22px",
"--dt-lv1-size": "var(--text-body)",
"--dt-lv2-size": "15px",
"--dt-lv4-size": "13px",
@@ -0,0 +1,55 @@
"""PLAN 240 · 121 · 122 — B09 설계내역서 폴더 줄 글꼴 한 벌 · 들여쓰기 폭 · 끌기 미리 보기 · 데이터 점검 틈."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
B09 = ROOT / "B09_Estimation"
def _read(p: Path) -> str:
return p.read_text(encoding="utf-8")
def test_폴더_줄은_처음이든_레벨_고쳐_생겼든_같은_줄_꼴():
src = _read(B09 / "B09_Estimation_UI_Tabs_Bill.ts")
assert 'const title = r.kind === "item" ? "" : "is-title";' in src
assert '"is-group"' not in src
css = _read(B09 / "B09_Estimation_UI_Page.css")
assert "tr.is-group" not in css # 옛 묶음 줄 꼴(색 다름) 없음
def test_들여쓰기_한_단_넓힘_끌기_한_단과_같음():
css = _read(ROOT / "ui_template" / "ui_template_data_table.css")
assert re.search(r"--dt-indent: 22px;", css)
drag = _read(B09 / "B09_Estimation_UI_Tabs_Bill_Drag.ts")
assert 'getPropertyValue("--dt-indent")' in drag
def test_끌기_미리_보기_어디_안_몇_레벨_폴더_테두리_끄는_줄_흐림():
drag = _read(B09 / "B09_Estimation_UI_Tabs_Bill_Drag.ts")
for part in (
"b09-estimation__drop-tag",
"is-drop-into",
"is-drag-src",
"못 놓음",
"안",
"맨 위",
):
assert part in drag, part
css = _read(B09 / "B09_Estimation_UI_Page.css")
for sel in (".b09-estimation__drop-tag", "tr.is-drop-into td", "tr.is-drag-src td"):
assert sel in css, sel
# 끝나면 표시를 모두 걷음
assert 'into?.classList.remove("is-drop-into")' in drag
assert 'el.classList.remove("is-drag-src")' in drag
def test_데이터_점검_업데이트_위_틈_카드_왼쪽_여유와_같음():
css = _read(B09 / "B09_Estimation_UI_Page.css")
m = re.search(r"\.b09-options \[data-b09-refresh\] \{\s*margin-top: ([^;]+);", css)
assert m and m.group(1) == "calc(var(--spacing-8) + var(--spacing-4))"
card = _read(ROOT / "ui_template" / "ui_template_overlay.css")
assert re.search(
r"\.ui-sidebar-card \{\s*padding: calc\(var\(--spacing-8\) \+ var\(--spacing-4\)\);", card
)
+2 -2
View File
@@ -10,8 +10,8 @@
--dt-head: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface));
--dt-line: var(--color-border);
--dt-edge: color-mix(in srgb, var(--color-text-secondary) 45%, var(--color-border));
/* 들여쓰기 한 단 = 표 기본 글자 1em(레벨 글자 크기와 무관 · 끌기 한 단과 같음) */
--dt-indent: var(--text-body-sm);
/* 들여쓰기 한 단(레벨 글자 크기와 무관 · 끌기 한 단과 같음 · 121 넓힘) */
--dt-indent: 22px;
/* 레벨 글자 크기(PLAN 228-1) — 줄 높이 28px 안 · Lv3 = 표 기본(14px) */
--dt-lv1-size: var(--text-body);
--dt-lv2-size: 15px;