From e657067bd368b5d660bf3f17f5b655a2dead878e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 13 Oct 2026 18:15:53 +0900 Subject: [PATCH] =?UTF-8?q?feat(b09):=20=ED=83=AD=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20=EA=B8=80=20=EC=98=81=EC=97=AD=20=3D=20?= =?UTF-8?q?=EC=A4=84=20=EC=88=98=20=EB=B3=80=EC=88=98(--b09-hint-lines=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=203)=EB=A7=8C=ED=81=BC=20=C2=B7=20=EB=84=98?= =?UTF-8?q?=EC=B9=98=EB=A9=B4=20=EC=98=81=EC=97=AD=20=EC=95=88=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=C2=B7=20=ED=83=AD=20=ED=91=9C=20=3D=20?= =?UTF-8?q?=EA=B3=B5=EC=9A=A9=20fillViewport(=EC=B0=BD=20=EB=81=9D=20?= =?UTF-8?q?=E2=88=92=20=EC=95=88=EB=82=B4=20=EA=B8=80)=20=C2=B7=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=EB=82=B4=EC=97=AD=EC=84=9C=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EC=95=88=EB=82=B4=20+=20=ED=83=AD=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20=ED=95=9C=20=EC=98=81=EC=97=AD=20=C2=B7=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20spaceBelow=20=ED=98=95=EC=A0=9C=20=EB=B0=94?= =?UTF-8?q?=EA=B9=A5=20=EC=97=AC=EB=B0=B1=EB=8F=84=20=EC=85=88(=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=81=AC=EB=A1=A4=207px=20?= =?UTF-8?q?=EC=97=86=EC=95=B0)=20(310=20=C2=B7=20=EC=9A=94=EA=B5=AC=20203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte --- B09_Estimation/B09_Estimation_UI_Page.css | 8 +- B09_Estimation/B09_Estimation_UI_Shell.ts | 8 +- .../B09_Estimation_UI_Tabs_Groups.ts | 2 +- .../tester/test_200_1_b09_box_tool_line.py | 11 +- .../tester/test_200_2_b09_bill_level_bar.py | 4 +- .../tester/test_202_1_table_hint_bottom.py | 5 +- resources/tester/test_203_b09_hint_lines.py | 185 ++++++++++++++++++ .../test_229_1_b09_level_buttons_subbar.py | 4 +- resources/tester/test_236_1_hint_one_line.py | 9 +- ui_template/ui_template_data_table.ts | 5 +- 10 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 resources/tester/test_203_b09_hint_lines.py diff --git a/B09_Estimation/B09_Estimation_UI_Page.css b/B09_Estimation/B09_Estimation_UI_Page.css index fe87eb951..2a2041307 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.css +++ b/B09_Estimation/B09_Estimation_UI_Page.css @@ -1,6 +1,7 @@ /* B09 원가계산 — 탭 줄 · 안내 글. 색은 테마 변수만(밝음 · 어두움 같이). */ .b09-estimation { + --b09-hint-lines: 3; /* 203 안내 글 영역 줄 수 — 이 값만 바꿈(높이 · 스크롤 · 표 칸 같이 맞음) */ display: flex; flex-direction: column; gap: var(--spacing-8); @@ -57,7 +58,7 @@ html.is-bm-route .b09-estimation > .b09-estimation__tabs:first-child { white-space: nowrap; } -/* 202-1 안내 줄 = 표 · 카드 판 아래(묶음 탭은 한 줄 줄임표 · 전문 = 공용 호버) */ +/* 202-1 안내 줄 = 표 · 카드 판 아래 · 203 줄 수 변수만큼 보임 · 넘치면 영역 안 스크롤 */ .b09-estimation .ui-data-table + .ui-general-block__empty, .b09-estimation .ui-group-cards + .ui-general-block__empty { margin: var(--spacing-8) 0 0; @@ -65,9 +66,8 @@ html.is-bm-route .b09-estimation > .b09-estimation__tabs:first-child { .b09-estimation__line-hint { min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + max-height: calc(var(--b09-hint-lines) * 1lh); + overflow-y: auto; font-size: var(--text-caption); } diff --git a/B09_Estimation/B09_Estimation_UI_Shell.ts b/B09_Estimation/B09_Estimation_UI_Shell.ts index 81dc5157f..ff7aee710 100644 --- a/B09_Estimation/B09_Estimation_UI_Shell.ts +++ b/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -183,6 +183,7 @@ export function renderB09Estimation(root: HTMLElement): void { const makeTable = (extra?: (index: number) => string | null): DataTable => { const made = createDataTable({ ...tableOptionsOf(spec, tab), + fillViewport: true, // 203 표 칸 = 창 끝 − 안내 글 영역(팝업 · 인쇄는 그대로) rowClass: extra ? (i) => [spec.rowClass?.(i), extra(i)].filter(Boolean).join(" ") || null : spec.rowClass, @@ -230,7 +231,7 @@ export function renderB09Estimation(root: HTMLElement): void { ); return made; }; - const hint = spec.hint ? lineHint(note(spec.hint)) : null; // 236-1 안내 글 = 한 줄 + const hint = spec.hint ? lineHint(note(spec.hint)) : null; // 203 안내 글 = 줄 수 변수만큼 · 넘치면 스크롤 // 171-3 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) = 묶음 카드 기본 · 좌측 하단 스위치로 표(304-1) if (isGroupTab(tab)) { return void panel.replaceChildren( @@ -259,7 +260,10 @@ export function renderB09Estimation(root: HTMLElement): void { if (range) unpickOnRelease(table.root); const level = bill ? levelBar(table.root, bill, (i) => table.edit(i)) : null; if (level) bars.setTools(level.tools); - panel.replaceChildren(table.root, ...(level ? [level.guide] : []), ...(hint ? [hint] : [])); + // 203 안내 글 영역 하나 — 설계내역서 레벨 안내 + 탭 안내 이어 붙임 + if (level && hint) hint.textContent = `${level.guide.textContent} · ${hint.textContent}`; + const foot = hint ?? level?.guide; + panel.replaceChildren(table.root, ...(foot ? [foot] : [])); if (at >= 0) requestAnimationFrame(() => table.focusRow(at, false)); // 스크롤만(표시 = 빨강) }; diff --git a/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts b/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts index 5ffe20bb2..1cc4f2af6 100644 --- a/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts +++ b/B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts @@ -228,7 +228,7 @@ function numberFormat(spec: TabTable, key: string): (v: number | null | undefine /* ── 그리기 ───────────────────────────────────────────────────────────── */ -/** 안내 글 = 한 줄(줄임표) · 잘렸을 때만 마우스 올리면 전문(공용 호버 185-3) */ +/** 안내 글 = 줄 수 변수(203 `--b09-hint-lines`)만큼 · 넘치면 영역 안 스크롤 · 가로로 잘렸을 때만 전문(공용 호버 185-3) */ export function lineHint(hint: HTMLElement | null): HTMLElement | null { if (!hint || hint.classList.contains("b09-estimation__line-hint")) return hint; hint.classList.add("b09-estimation__line-hint"); diff --git a/resources/tester/test_200_1_b09_box_tool_line.py b/resources/tester/test_200_1_b09_box_tool_line.py index 203d08e9b..b0c95cb85 100644 --- a/resources/tester/test_200_1_b09_box_tool_line.py +++ b/resources/tester/test_200_1_b09_box_tool_line.py @@ -1,6 +1,6 @@ """200-1 B09 박스보기 안내 글 줄임표 + 호버 · 카드 같은 가로 줄 높이 같게. -- 안내 글 = 줄임표 한 줄 · 잘렸을 때만 공용 호버(`attachHoverInfo`)로 전문 · 자리 = 표 · 카드 판 아래(202-1 로 옮김) +- 안내 글 = 203 줄 수 변수만큼(넘치면 스크롤) · 가로로 잘렸을 때만 공용 호버(`attachHoverInfo`)로 전문 · 자리 = 표 · 카드 판 아래(202-1 로 옮김) - 카드 격자 = 행 늘림(`align-items: stretch`) · 카드 = 세로 flex · 몸통이 늘어 합계 줄이 아래 - 카드 제목 줄 · 합계 줄 바탕색(200-3) """ @@ -78,13 +78,12 @@ def test_안내_글_없으면_호버도_없음(): assert out["n"] == 1 and out["hintLast"] is False and out["hover"] == 0 -def test_CSS_안내_글_줄임표_한_줄(): +def test_CSS_안내_글_줄_수_변수(): page = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8") hint = _block(page, ".b09-estimation__line-hint") - for need in ( - "overflow: hidden", - "text-overflow: ellipsis", - "white-space: nowrap", + for need in ( # 203 한 줄 줄임표 → 줄 수 변수만큼 · 넘치면 스크롤 + "max-height: calc(var(--b09-hint-lines) * 1lh)", + "overflow-y: auto", "min-width: 0", ): assert need in hint, need diff --git a/resources/tester/test_200_2_b09_bill_level_bar.py b/resources/tester/test_200_2_b09_bill_level_bar.py index 8da7177e0..2aab73e04 100644 --- a/resources/tester/test_200_2_b09_bill_level_bar.py +++ b/resources/tester/test_200_2_b09_bill_level_bar.py @@ -200,7 +200,9 @@ def test_셸_배선(): "bill ? (i) => pickedClass(bill.rows[i]) ?? pickClass(jumpRange(), i)" in src ) # 239-1 · 242-1 이동 줄 빨강 곁(고름과 한때 하나) assert "if (level) bars.setTools(level.tools);" in src # 229-1 단추 = 보조 탭 줄 - assert "panel.replaceChildren(table.root, ...(level ? [level.guide] : [])" in src + assert ( + "panel.replaceChildren(table.root, ...(foot ? [foot] : []))" in src + ) # 203 레벨 안내 + 탭 안내 = 한 영역 lvl = (B09 / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8") assert "table.menu(index)" in lvl # 단추 · 키 = 우클릭 메뉴 같은 함수 assert "b09-estimation__level" not in lvl diff --git a/resources/tester/test_202_1_table_hint_bottom.py b/resources/tester/test_202_1_table_hint_bottom.py index 8cbe24bb4..ff5509be6 100644 --- a/resources/tester/test_202_1_table_hint_bottom.py +++ b/resources/tester/test_202_1_table_hint_bottom.py @@ -106,9 +106,8 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에(): def test_잇기_쉘_안내_줄_아래_도구_비움(): shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8") assert ( - "panel.replaceChildren(table.root, ...(level ? [level.guide] : []), ...(hint ? [hint] : []))" - in shell - ) # 229-1 레벨 단추 = 보조 탭 줄 · 안내 글 = 표 아래 · 안내 줄 = 맨 아래 + "panel.replaceChildren(table.root, ...(foot ? [foot] : []))" in shell + ) # 229-1 레벨 단추 = 보조 탭 줄 · 안내 글 = 표 아래 · 203 레벨 안내 + 탭 안내 = 한 영역(맨 아래) assert "...(hint ? [hint] : []), table.root" not in shell # 위 자리 없음 assert re.search(r"bars\.draw\(tab\);\s*bars\.setTools\(\[\]\);", shell) assert "setTools: bars.setTools" in shell diff --git a/resources/tester/test_203_b09_hint_lines.py b/resources/tester/test_203_b09_hint_lines.py new file mode 100644 index 000000000..7d58bdf37 --- /dev/null +++ b/resources/tester/test_203_b09_hint_lines.py @@ -0,0 +1,185 @@ +"""203 B09 탭 상세 안내 글 영역 = 줄 수 변수(`--b09-hint-lines` 기본 3)만큼 · 넘치면 영역 안 스크롤 · 표 칸 = 창 끝까지. + +- CSS: 줄 수 변수 하나 · 안내 글 최대 높이 = 변수 × 줄 높이(1lh) · 세로 스크롤 · 한 줄 줄임표 걷음 +- Shell: 탭 표 = 공용 fillViewport(팝업 · 인쇄 표는 그대로) · 설계내역서 레벨 안내 + 탭 안내 = 한 영역 +- 공용 spaceBelow: 아래 형제 바깥 여백(B09 안내 글 margin-top)도 셈 — 빼면 페이지 스크롤이 생김 +- 실제 브라우저(정적 쪽 · 서버 불필요): 변수 3 · 2 × 줄 많음 · 적음 — 안내 글 높이 · 합계 줄 · 안내 글 자리 · 페이지 스크롤 0 +""" + +import re +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +B09 = ROOT / "B09_Estimation" +UI = ROOT / "ui_template" + + +def _read(path: Path) -> str: + return path.read_text("utf-8") + + +def _block(css: str, selector: str) -> str: + m = re.search(re.escape(selector) + r"\s*\{([^}]*)\}", css) + assert m, selector + return m.group(1) + + +def test_CSS_줄_수_변수_하나(): + css = _read(B09 / "B09_Estimation_UI_Page.css") + assert re.search(r"--b09-hint-lines:\s*3;", _block(css, ".b09-estimation")) + assert css.count("--b09-hint-lines:") == 1 # 고칠 자리 하나 + hint = _block(css, ".b09-estimation__line-hint") + assert "max-height: calc(var(--b09-hint-lines) * 1lh)" in hint + assert "overflow-y: auto" in hint + for gone in ("white-space: nowrap", "text-overflow: ellipsis"): + assert gone not in hint, gone + + +def test_Shell_표_창_끝_안내_한_영역(): + shell = _read(B09 / "B09_Estimation_UI_Shell.ts") + assert "fillViewport: true" in shell + assert "panel.replaceChildren(table.root, ...(foot ? [foot] : []))" in shell + assert "hint.textContent = `${level.guide.textContent} · ${hint.textContent}`" in shell + # 팝업 · 인쇄 표는 창 끝 셈 안 씀(팝업 = 창 안 max-height · 인쇄 = 펼침) + assert "fillViewport" not in _read(B09 / "B09_Estimation_UI_Builders.ts") + assert "fillViewport" not in _read(B09 / "B09_Estimation_UI_Popup.ts") + + +# ── 실제 브라우저 ───────────────────────────────────────────────────────────── + +TOKENS = ( + ":root { --spacing-4: 4px; --spacing-8: 8px; --spacing-16: 16px; --spacing-24: 24px;" + " --text-caption: 12px; --radius-lg: 8px; }" + " html, body { margin: 0; line-height: 1.5; font-family: sans-serif; }" +) +LONG = " · ".join(["긴 안내 글 시험 문장"] * 40) +SHORT = "줄을 누르면 단가를 낸 탭 · 줄로 이동" + + +def _module_js() -> str: + """공용 표 모듈(TS) → 브라우저 ES 모듈(CSS import 뺌).""" + src = UI / "ui_template_data_table.ts" + code = ( + "const ts = require(process.argv[1]);" + "const fs = require('fs');" + "const out = ts.transpileModule(fs.readFileSync(process.argv[2], 'utf8')," + " { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2020 } });" + "process.stdout.write(out.outputText);" + ) + ts_path = ROOT / "config" / "node_modules" / "typescript" + proc = subprocess.run( + ["node", "-e", code, str(ts_path), str(src)], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + return re.sub(r'^import\s+"[^"]+\.css";\s*$', "", proc.stdout, flags=re.M) + + +MEASURE = """async ([rows, lines, text]) => { + const { createDataTable } = window.dt; + const body = document.querySelector('.b09-estimation'); + body.style.setProperty('--b09-hint-lines', String(lines)); + const panel = body.querySelector('.b09-estimation__panel'); + const table = createDataTable({ + columns: [{ key: 'name', label: '이름' }, { key: 'sum', label: '금액', kind: 'number' }], + rows: Array.from({ length: rows }, (_, i) => ({ name: '줄 ' + i, sum: i })), + total: { name: '계', sum: 1 }, + fillViewport: true, + }); + const hint = document.createElement('p'); + hint.className = 'ui-general-block__empty b09-estimation__line-hint'; + hint.textContent = text; + panel.replaceChildren(table.root, hint); + await new Promise((r) => setTimeout(r, 300)); + const t = table.root.getBoundingClientRect(); + const h = hint.getBoundingClientRect(); + const foot = table.root.querySelector('tfoot').getBoundingClientRect(); + return { + vh: innerHeight, + page: document.scrollingElement.scrollHeight, + lh: parseFloat(getComputedStyle(hint).lineHeight), + hintH: h.height, + hintScrolls: hint.scrollHeight > hint.clientHeight, + tableTop: t.top, + tableBottom: t.bottom, + tableScrolls: table.root.scrollHeight > table.root.clientHeight, + maxH: parseFloat(table.root.style.maxHeight), + hintTop: h.top, + hintBottom: h.bottom, + padB: parseFloat(getComputedStyle(body).paddingBottom), + footBottom: foot.bottom, + }; +}""" + + +@pytest.fixture(scope="module") +def page(): + from playwright.sync_api import sync_playwright + + css = _read(UI / "ui_template_data_table.css") + _read(B09 / "B09_Estimation_UI_Page.css") + html = ( + f"" + "
" + "
" + ) + js = _module_js() + with sync_playwright() as p: + browser = p.chromium.launch(channel="msedge", headless=True) + pg = browser.new_page(viewport={"width": 1280, "height": 900}) + pg.set_content(html) + pg.add_script_tag( + type="module", + content=f"{js}\nwindow.dt = {{ createDataTable }};", + ) + pg.wait_for_function("!!window.dt") + yield pg + browser.close() + + +@pytest.mark.parametrize("lines", [3, 2]) +def test_줄_많음_표_창_끝_합계_줄_고정_안내_글_바닥(page, lines): + out = page.evaluate(MEASURE, [300, lines, LONG]) + assert out["hintH"] == pytest.approx(lines * out["lh"], abs=0.5), out # 높이 = 줄 수 × 줄 높이 + assert out["hintScrolls"], out # 넘친 글 = 영역 안 스크롤 + assert out["tableScrolls"], out + assert out["page"] == out["vh"], out # 페이지 스크롤 0 + assert out["hintBottom"] + out["padB"] == pytest.approx(out["vh"], abs=1.5), out # 창 끝까지 씀 + assert out["hintTop"] == pytest.approx(out["tableBottom"] + 8, abs=0.5), out # 표 바로 아래 + assert out["tableBottom"] - 30 < out["footBottom"] <= out["tableBottom"], ( + out + ) # 합계 줄 = 표 칸 바닥 + + +def test_줄_수_2_이면_표가_한_줄만큼_더_김(page): + three = page.evaluate(MEASURE, [300, 3, LONG]) + two = page.evaluate(MEASURE, [300, 2, LONG]) + assert two["maxH"] - three["maxH"] == pytest.approx(three["lh"], abs=1), (two, three) + + +@pytest.mark.parametrize("lines", [3, 2]) +def test_줄_적음_합계_줄_안내_글_끌려_올라옴(page, lines): + out = page.evaluate(MEASURE, [3, lines, LONG]) + assert not out["tableScrolls"], out + assert out["tableBottom"] - out["tableTop"] < out["maxH"] - 100, out # 내용만큼(창 끝 아님) + assert out["hintTop"] == pytest.approx(out["tableBottom"] + 8, abs=0.5), out # 표 끝에 붙음 + assert out["hintBottom"] < out["vh"] - 300, out # 화면 바닥에 떨어져 있지 않음 + assert out["hintH"] == pytest.approx(lines * out["lh"], abs=0.5), out + assert out["footBottom"] <= out["tableBottom"], out + + +def test_짧은_글은_한_줄만(page): + out = page.evaluate(MEASURE, [300, 3, SHORT]) + assert out["hintH"] == pytest.approx(out["lh"], abs=0.5), out + assert not out["hintScrolls"], out + assert out["page"] == out["vh"], out + + +def test_spaceBelow_형제_바깥_여백_셈(): + src = _read(UI / "ui_template_data_table.ts") + assert "px(m.marginTop) + px(m.marginBottom)" in src # 브라우저 배치는 위 시험이 잼 diff --git a/resources/tester/test_229_1_b09_level_buttons_subbar.py b/resources/tester/test_229_1_b09_level_buttons_subbar.py index 50a605e4a..6d3d1a40a 100644 --- a/resources/tester/test_229_1_b09_level_buttons_subbar.py +++ b/resources/tester/test_229_1_b09_level_buttons_subbar.py @@ -85,7 +85,9 @@ console.log(JSON.stringify({ def test_표_위_도구_줄_없음_쉘_배선(): shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8") assert "if (level) bars.setTools(level.tools);" in shell - assert "panel.replaceChildren(table.root, ...(level ? [level.guide] : [])" in shell + assert ( + "panel.replaceChildren(table.root, ...(foot ? [foot] : []))" in shell + ) # 203 레벨 안내 + 탭 안내 = 한 영역 assert "...(level ? [level] : [])" not in shell # 표 위 자리 없음 css = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8") lvl = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8") diff --git a/resources/tester/test_236_1_hint_one_line.py b/resources/tester/test_236_1_hint_one_line.py index 1e7ba7e56..df3f087c5 100644 --- a/resources/tester/test_236_1_hint_one_line.py +++ b/resources/tester/test_236_1_hint_one_line.py @@ -2,7 +2,7 @@ - 안내 글 낱말에 제작 · 셈 설명(ROUND · 「 = 」 · 장 번호 · M01 · 곱셈 나눗셈 기호)이 없음 - 안내 글 한 조각 70 자 이하(긴 글은 줄임표 + 공용 호버) -- B09 안내 글(lineHint) · 레벨 안내 · B08 안내 줄(footNote) = 한 줄(nowrap · 줄임표) +- B08 안내 줄(footNote) = 한 줄(nowrap · 줄임표) · B09 안내 글 = 203 줄 수 변수만큼(test_203) - 창 크기(1080 · 768 · 600)마다 한 줄 안내 + 표 칸 = 창 끝(199-1 fillViewport 셈) """ @@ -108,12 +108,13 @@ def _block(css: str, selector: str) -> str: return m.group(1) -def test_B09_안내_글_한_줄(): +def test_B09_안내_글_줄_수_변수(): css = _read(B09 / "B09_Estimation_UI_Page.css") block = _block(css, ".b09-estimation__line-hint") - assert "white-space: nowrap" in block and "text-overflow: ellipsis" in block + # 203 한 줄 줄임표 → 줄 수 변수만큼 · 넘치면 스크롤(test_203_b09_hint_lines) + assert "max-height: calc(var(--b09-hint-lines) * 1lh)" in block and "overflow-y: auto" in block shell = _read(B09 / "B09_Estimation_UI_Shell.ts") - assert "lineHint(note(spec.hint))" in shell # 표 탭도 묶음 탭과 같은 한 줄 + assert "lineHint(note(spec.hint))" in shell # 표 탭도 묶음 탭과 같은 꼴 assert "export function lineHint" in _read(B09 / "B09_Estimation_UI_Tabs_Groups.ts") level = _read(B09 / "B09_Estimation_UI_Tabs_Bill_Level.ts") assert 'guide.className = "ui-general-block__empty b09-estimation__line-hint"' in level diff --git a/ui_template/ui_template_data_table.ts b/ui_template/ui_template_data_table.ts index 44c2e69d8..40a7c1a5d 100644 --- a/ui_template/ui_template_data_table.ts +++ b/ui_template/ui_template_data_table.ts @@ -383,7 +383,7 @@ function isNode(lv: NonNullable, i: number, n: number const OVER = 40; /* ── 창 아래 끝까지(PLAN 199) — 칸 최대 높이 = 창 높이 − 칸 위쪽 자리 − 아래 자리 ──────────── - * 아래 자리 = 칸 뒤 형제 요소 높이 + 윗대 요소의 아래 안쪽 여백 · 테두리 · 바깥 여백. + * 아래 자리 = 칸 뒤 형제 요소 높이 · 바깥 여백 + 윗대 요소의 아래 안쪽 여백 · 테두리 · 바깥 여백. * 줄이 적으면 내용만큼 · 창 크기 · 위아래 요소가 바뀌면 다시 셈 · 칸이 문서에서 떨어지면 스스로 멈춤. * ------------------------------------------------------------------------------------------ */ @@ -407,7 +407,8 @@ export function spaceBelow(el: HTMLElement): number { const gap = px(around.rowGap); for (let s = node.nextElementSibling; s; s = s.nextElementSibling) { const h = s.getBoundingClientRect().height; - if (h) sum += h + gap; + const m = getComputedStyle(s); // 203 형제 위아래 바깥 여백도(B09 안내 글 margin-top) + if (h) sum += h + gap + px(m.marginTop) + px(m.marginBottom); } sum += px(own.marginBottom) + px(around.paddingBottom) + px(around.borderBottomWidth); }