diff --git a/B09_Estimation/B09_Estimation_UI_Page.css b/B09_Estimation/B09_Estimation_UI_Page.css index 0b6118118..56f923c40 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.css +++ b/B09_Estimation/B09_Estimation_UI_Page.css @@ -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; } diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts index b5f0af7df..2fac2632e 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts @@ -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 레벨 = 셈 나무 깊이(들여 · 내어쓰기 · 끌기 = 다시 셈 · 다시 그림) · 글꼴 = 레벨 · 묶음(빈 공종도) diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Drag.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Drag.ts index 786ccc285..951c43559 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Drag.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Bill_Drag.ts @@ -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("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; diff --git a/resources/tester/test_182_b09_bill_level.py b/resources/tester/test_182_b09_bill_level.py index cccc9ecf4..1bac0f18f 100644 --- a/resources/tester/test_182_b09_bill_level.py +++ b/resources/tester/test_182_b09_bill_level.py @@ -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 diff --git a/resources/tester/test_228_1_table_level_font.py b/resources/tester/test_228_1_table_level_font.py index f098d0ae4..56bab2b46 100644 --- a/resources/tester/test_228_1_table_level_font.py +++ b/resources/tester/test_228_1_table_level_font.py @@ -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", diff --git a/resources/tester/test_240_1_b09_bill_folder_font_drag.py b/resources/tester/test_240_1_b09_bill_folder_font_drag.py new file mode 100644 index 000000000..eda6e364f --- /dev/null +++ b/resources/tester/test_240_1_b09_bill_folder_font_drag.py @@ -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 + ) diff --git a/ui_template/ui_template_data_table.css b/ui_template/ui_template_data_table.css index 42126928b..f726d0827 100644 --- a/ui_template/ui_template_data_table.css +++ b/ui_template/ui_template_data_table.css @@ -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;