From 75706f24f816d73c8ce184d10a99798e5c931539 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 09:37:26 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05=20=EB=85=B8=EC=84=A0=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=ED=8F=89=EB=A9=B4=20=EC=95=84=EB=9E=98=20=EA=B8=80?= =?UTF-8?q?=ED=8C=90):=20=ED=9A=A1=EB=8B=A8=20=ED=8C=90=20=EB=84=93?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=83=81=ED=83=9C=20=EA=B8=80=ED=8C=90?= =?UTF-8?q?=20=C2=B7=20=EC=9E=B0=20=EA=B0=92=20=EC=B0=BD=20=EC=95=88=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=A8=20(408-4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 횡단 판 왼쪽 빈 폭을 두 글판이 반씩(--b05-bottom-half) · 좁으면 줄바꿈 - 반 폭 200 px 아래면 잰 값 창을 상태 글판 위로 쌓음(is-narrow-bottom · --b05-info-h · logic bottomRowNarrow) - Chrome 관찰에 상태 글판 · 횡단 판 크기 더함 - 시험 test_408_4_bottom_row 새로 · 345_5c 폭 규칙 고쳐 씀 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD --- .../B05_Profile_UI_RouteEdit_Chrome.ts | 13 +++- .../B05_Profile_UI_Style_RouteEdit.css | 18 ++++- .../logic/B05_Profile_RouteEdit_InfoLift.ts | 11 ++++ resources/tester/helper_408_2_status_lift.cjs | 4 +- .../tester/test_345_5c_info_hint_collapse.py | 3 +- resources/tester/test_408_4_bottom_row.py | 65 +++++++++++++++++++ 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 resources/tester/test_408_4_bottom_row.py diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts index 46bf45d9e..ad721cc0f 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts @@ -2,7 +2,7 @@ import { createButton } from "@ui/ui_template_elements"; import { createSidePanels, type SidePanels } from "@ui/ui_template_side_panels"; import { bindFootprintToggle } from "./B05_Profile_UI_RouteEdit_Footprint"; import { bindMarkToggle } from "./B05_Profile_UI_RouteEdit_Marks"; -import { infoLiftPx } from "./logic/B05_Profile_RouteEdit_InfoLift"; +import { bottomRowNarrow, infoLiftPx } from "./logic/B05_Profile_RouteEdit_InfoLift"; /* ============================================================================= * B05_Profile_UI_RouteEdit_Chrome.ts @@ -207,7 +207,8 @@ export function createRouteEditChrome( infoToggle.addEventListener("click", () => setInfo(!infoCollapsed)); // 뜬 종단 판(이웃 칸)이 지도 아래를 덮으면 상태 · 범례를 그 위로(408-2) — 판 높이 끌기 · 접기 · - // 창 분할(판이 다른 창으로 감 = 못 찾음)을 판 크기 관찰로 따라감. + // 창 분할(판이 다른 창으로 감 = 못 찾음)을 판 크기 관찰로 따라감. 횡단 판이 넓어 아래 줄이 좁으면 잰 값 창을 + // 상태 글판 위로 쌓음(408-4 · 횡단 판 · 글판 크기도 관찰). let watchedProfile: Element | null = null; const liftInfo = (): void => { if (!overlay.isConnected) return; // 붙기 전 첫 알림 · 걷힌 뒤 — 판 관찰은 판과 함께 사라짐 @@ -220,9 +221,17 @@ export function createRouteEditChrome( "--b05-profile-cover", `${infoLiftPx(map.bottom, map.height, top)}px`, ); + const sideOpen = parseFloat(overlay.style.getPropertyValue("--b05-side-open")) || 0; + overlay.classList.toggle("is-narrow-bottom", bottomRowNarrow(map.width, sideOpen)); + overlay.style.setProperty( + "--b05-info-h", + `${Math.round(info.getBoundingClientRect().height)}px`, + ); }; const lift = new ResizeObserver(liftInfo); lift.observe(overlay); + lift.observe(info); + lift.observe(overlay.querySelector(".b05-routeedit__side-wrap")!); const measureCloseButton = createButton({ label: "✕", variant: "ghost" }); measureCloseButton.classList.add("b05-routeedit__measure-close"); diff --git a/B05_Profile/B05_Profile_UI_Style_RouteEdit.css b/B05_Profile/B05_Profile_UI_Style_RouteEdit.css index e998a8b6d..4d4584636 100644 --- a/B05_Profile/B05_Profile_UI_Style_RouteEdit.css +++ b/B05_Profile/B05_Profile_UI_Style_RouteEdit.css @@ -5,6 +5,9 @@ (`.b05-route__view-controls` · ☰ 비킴 `--wf-burger-pad`) · 횡단 판은 오른쪽을 덮는 판. */ .b05-routeedit { --b05-side-open: 0px; /* 횡단 판이 지도를 덮는 폭(손잡이 포함 · `_Chrome` 이 적음) */ + /* 아래 줄 반 폭(408-4) — 횡단 판 왼쪽 빈 폭을 상태 글판 · 잰 값 창이 반씩(안 겹침 · 좁으면 줄바꿈) · + % 는 쓰는 자리(지도 칸) 기준 · 반이 모자라면(`is-narrow-bottom` · `_Chrome`) 빈 폭 전부 · 위아래로 쌓음. */ + --b05-bottom-half: max(0px, calc((100% - 3 * var(--spacing-12) - var(--b05-side-open)) / 2)); position: relative; display: flex; flex: 1 1 auto; @@ -97,7 +100,7 @@ grid-template-columns: minmax(0, 1fr) auto auto; align-items: start; gap: 0 var(--spacing-12); - max-width: min(460px, 46%); + max-width: min(460px, 46%, var(--b05-bottom-half)); padding: var(--spacing-4, 4px) var(--spacing-8); line-height: 1.45; } @@ -374,7 +377,7 @@ display: flex; align-items: flex-start; gap: var(--spacing-8); - max-width: 52%; + max-width: var(--b05-bottom-half); padding: var(--spacing-8) var(--spacing-12); border: 1px solid color-mix(in srgb, #22c55e 60%, transparent); border-radius: var(--radius-8, 6px); @@ -385,6 +388,17 @@ font-size: var(--text-caption); } +/* 아래 줄 좁음(408-4) — 잰 값 창은 상태 글판 위에 · 둘 다 횡단 판 왼쪽 빈 폭 전부. */ +.b05-routeedit.is-narrow-bottom { + --b05-bottom-half: max(0px, calc(100% - 2 * var(--spacing-12) - var(--b05-side-open))); +} + +.b05-routeedit.is-narrow-bottom .b05-routeedit__measure { + bottom: calc( + var(--spacing-12) + var(--b05-profile-cover, 0px) + var(--b05-info-h, 0px) + var(--spacing-8) + ); +} + /* 글이 길면 접힌다 — flex 자식은 기본으로 안 줄어들어 왼쪽으로 넘쳐 잘렸다(2026-09-12). */ .b05-routeedit__measure-text { min-width: 0; diff --git a/B05_Profile/logic/B05_Profile_RouteEdit_InfoLift.ts b/B05_Profile/logic/B05_Profile_RouteEdit_InfoLift.ts index 85671a2c8..5b57438a7 100644 --- a/B05_Profile/logic/B05_Profile_RouteEdit_InfoLift.ts +++ b/B05_Profile/logic/B05_Profile_RouteEdit_InfoLift.ts @@ -1,6 +1,7 @@ /* ============================================================================= * B05_Profile/logic/B05_Profile_RouteEdit_InfoLift.ts * 평면 상태 · 범례 올림(408-2) — 뜬 종단 판이 지도 아래를 덮은 높이만큼 위로. + * 아래 줄 좁음(408-4) — 횡단 판 왼쪽 빈 폭의 반이 모자라면 잰 값 창을 상태 글판 위로 쌓음. * 파이썬 짝 아님. 순수 계산 — 화면 · fetch · sessionStorage 없음. 부르는 쪽은 `_UI_RouteEdit_Chrome`. * ========================================================================== */ @@ -17,3 +18,13 @@ export function infoLiftPx( const cover = Math.max(0, mapBottom - profileTop); return Math.round(Math.min(cover, Math.max(0, mapHeight - KEEP_PX))); } + +/** 아래 줄 반 폭이 이보다 좁으면 나란히 두지 않고 쌓음(px). */ +const HALF_MIN_PX = 200; +/** 지도 칸 가장자리 · 두 글판 사이 · 횡단 판 앞 간격(px) — CSS `--spacing-12`. */ +const GAP_PX = 12; + +/** 상태 글판 · 잰 값 창을 나란히 못 두나 — `mapWidth` = 지도 칸 폭 · `sideOpen` = 횡단 판이 덮는 폭. */ +export function bottomRowNarrow(mapWidth: number, sideOpen: number): boolean { + return (mapWidth - 3 * GAP_PX - sideOpen) / 2 < HALF_MIN_PX; +} diff --git a/resources/tester/helper_408_2_status_lift.cjs b/resources/tester/helper_408_2_status_lift.cjs index 887add89c..5b5ba8d0e 100644 --- a/resources/tester/helper_408_2_status_lift.cjs +++ b/resources/tester/helper_408_2_status_lift.cjs @@ -19,7 +19,7 @@ const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { }).outputText; const box = { exports: {} }; new Function("exports", "module", "require", js)(box.exports, box, require); -const { infoLiftPx } = box.exports; +const { infoLiftPx, bottomRowNarrow } = box.exports; // 화면 1078 px · 지도 칸 0~1078 process.stdout.write( @@ -29,5 +29,7 @@ process.stdout.write( popout: infoLiftPx(1078, 1078, null), // 창 분할 = 판 없음 below: infoLiftPx(600, 600, 700), // 횡단 보기 = 판이 지도 아래 흐름 tall: infoLiftPx(1078, 1078, 20), // 판을 끝까지 올림 = 지도 안에 남김 + // 408-4 지도 칸 1172 px · 횡단 판 접힘(손잡이 32) · 460 열림 · 891 넓힘 — 반 폭 200 아래면 쌓음 + narrow: [32, 492, 891, 736, 737].map((side) => bottomRowNarrow(1172, side)), }), ); diff --git a/resources/tester/test_345_5c_info_hint_collapse.py b/resources/tester/test_345_5c_info_hint_collapse.py index 70d0c8b9d..7497d6665 100644 --- a/resources/tester/test_345_5c_info_hint_collapse.py +++ b/resources/tester/test_345_5c_info_hint_collapse.py @@ -58,7 +58,8 @@ def test_상태_글판_작게_반투명_자리() -> None: info = _rule(".b05-routeedit__info") # 왼쪽 아래 제자리 — 뜬 판이 덮으면 그 위로(408-2) · 폭 줄임 assert "bottom: calc(var(--spacing-12) + var(--b05-profile-cover, 0px));" in info - assert "max-width: min(460px, 46%);" in info and "grid-template-columns:" in info + assert "max-width: min(460px, 46%, var(--b05-bottom-half));" in info # 408-4 반 폭 + assert "grid-template-columns:" in info # 상태 글 두 줄에서 자름 · 범례는 옆 칸 두 줄 assert "-webkit-line-clamp: 2;" in _rule(".b05-routeedit__status") assert "flex-direction: column;" in _rule(".b05-routeedit__legend") diff --git a/resources/tester/test_408_4_bottom_row.py b/resources/tester/test_408_4_bottom_row.py new file mode 100644 index 000000000..da84dfab9 --- /dev/null +++ b/resources/tester/test_408_4_bottom_row.py @@ -0,0 +1,65 @@ +"""408-4 횡단 판이 넓을 때 상태 글판 · 잰 값 창 겹침 — 횡단 판 왼쪽 빈 폭을 반씩 나눠 쓰고, 반이 200 px +아래면(`bottomRowNarrow`) 잰 값 창을 상태 글판 위로 쌓음(둘 다 빈 폭 전부 · 좁으면 줄바꿈). + +TS 셈은 `helper_408_2_status_lift.cjs`(Node · `logic/B05_Profile_RouteEdit_InfoLift.ts`). + + ./venv/Scripts/python.exe -m pytest resources/tester/test_408_4_bottom_row.py -q +""" + +import json +import subprocess +from pathlib import Path + +import pytest + +B05 = Path(__file__).resolve().parents[2] / "B05_Profile" +CSS = B05 / "B05_Profile_UI_Style_RouteEdit.css" +HELPER = Path(__file__).with_name("helper_408_2_status_lift.cjs") + + +@pytest.fixture(scope="module") +def got() -> dict: + run = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", str(HELPER)], capture_output=True, text=True, encoding="utf-8", timeout=60 + ) + assert run.returncode == 0, run.stderr + return json.loads(run.stdout) + + +def _rules(selector: str) -> list[str]: + css = CSS.read_text(encoding="utf-8") + return [part[: part.index("}")] for part in css.split(f"{selector} {{")[1:]] + + +def test_반_폭이_모자라면_쌓음(got) -> None: + # (1172 - 36 - 736) / 2 = 200 → 나란히 · 737 → 쌓음 + assert got["narrow"] == [False, False, True, False, True] + + +def test_두_글판이_반_폭까지만() -> None: + assert any( + "max-width: min(460px, 46%, var(--b05-bottom-half));" in r + for r in _rules(".b05-routeedit__info") + ) + assert any("max-width: var(--b05-bottom-half);" in r for r in _rules(".b05-routeedit__measure")) + half = _rules(".b05-routeedit")[0] + assert ( + "--b05-bottom-half: max(0px, calc((100% - 3 * var(--spacing-12) - var(--b05-side-open)) / 2));" + in half + ) + + +def test_좁으면_잰_값_창이_상태_글판_위로() -> None: + narrow = _rules(".b05-routeedit.is-narrow-bottom")[0] + assert "calc(100% - 2 * var(--spacing-12) - var(--b05-side-open))" in narrow + measure = _rules(".b05-routeedit.is-narrow-bottom .b05-routeedit__measure")[0] + assert "var(--b05-info-h, 0px)" in measure and "var(--b05-profile-cover, 0px)" in measure + + +def test_편집기가_횡단_판_글판_크기를_관찰() -> None: + chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8") + assert ( + "lift.observe(info)" in chrome + and 'lift.observe(overlay.querySelector(".b05-routeedit__side-wrap")!)' in chrome + ) + assert '"is-narrow-bottom"' in chrome and '"--b05-info-h"' in chrome