feat(B06): [상세] 공용 큰 창 · 밖 누르면 닫힘 · 그림 위 크게 → 글 → 표 · 스크롤 없음(150-2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
This commit is contained in:
2026-10-09 19:07:55 +09:00
co-authored by Claude Opus 5.5
parent 940f707ffc
commit 43398878b2
3 changed files with 34 additions and 66 deletions
+6 -53
View File
@@ -2,11 +2,11 @@
* B06_Section_UI_Cross_Detail.ts
* 횡단 카드 측구 줄 맨 오른쪽 [상세] → 모달(138-4).
*
* 맨 위 = 도면 표 위 글(`design.structure_note` · 세션 초안 → [저장]이 patch 로 내보냄 ·
* 위 = 카드 그림 복제(어두운 바탕 · CAD 꼴 · 남은 높이를 채움) ·
* 가운데 = 도면 표 위 글(`design.structure_note` · 세션 초안 → [저장]이 patch 로 내보냄 ·
* B07 횡단도 수량표 바로 위에 줄마다 찍힘 — `_Engine_Cad_Table.cross_note_lines`) ·
* 가운데 = 카드 그림 복제(어두운 바탕 · CAD 꼴) · 아래 = B07 횡단 수량표(값 · 칸 배치 모두
* 서버 `cross-quantity-table` 한 길 · 캐시 설계로 · 열 때 부름).
* 막은 투명(누름 통과) · 자리는 그 카드를 안 가리는 넓은 쪽(좌/우 → 위/아래).
* 아래 = B07 횡단 수량표(값 · 칸 배치 모두 서버 `cross-quantity-table` 한 길 · 캐시 설계로 · 열 때 부름).
* 공용 큰 창(`wide`) · 공용 막(밖 누르면 닫힘) · 스크롤 없음(150-2).
* 모달이 열린 채 카드를 다시 그리면 그림 · 표를 새로 맞춤.
* ========================================================================== */
@@ -64,51 +64,6 @@ function writeNote(section: CrossSection, text: string): void {
writeState("structure-notes", all, host.projectId, routeId);
}
/* ── 자리 ───────────────────────────────────────────────────────────── */
const GAP_PX = 12;
const MIN_WIDTH_PX = 360;
const MAX_WIDTH_PX = 720;
/** 카드를 안 가리는 넓은 쪽에 붙임 — 좌우가 좁으면 위아래. */
function place(dialog: HTMLElement, card: HTMLElement | null): void {
const vw = window.innerWidth;
const vh = window.innerHeight;
const rect = card?.getBoundingClientRect();
const style = dialog.style;
style.position = "fixed";
style.margin = "0";
if (!rect) {
style.width = `${Math.min(MAX_WIDTH_PX, vw - GAP_PX * 2)}px`;
style.left = `${(vw - Math.min(MAX_WIDTH_PX, vw - GAP_PX * 2)) / 2}px`;
style.top = `${GAP_PX}px`;
style.maxHeight = `${vh - GAP_PX * 2}px`;
return;
}
const left = rect.left;
const right = vw - rect.right;
if (Math.max(left, right) >= MIN_WIDTH_PX + GAP_PX * 2) {
const width = Math.min(MAX_WIDTH_PX, Math.max(left, right) - GAP_PX * 2);
const top = Math.min(Math.max(rect.top, GAP_PX), vh - GAP_PX - 240);
style.width = `${width}px`;
style.left = `${right >= left ? rect.right + GAP_PX : rect.left - GAP_PX - width}px`;
style.top = `${Math.max(top, GAP_PX)}px`;
style.maxHeight = `${vh - Math.max(top, GAP_PX) - GAP_PX}px`;
return;
}
const width = Math.min(MAX_WIDTH_PX, vw - GAP_PX * 2);
style.width = `${width}px`;
style.left = `${Math.min(Math.max(rect.left, GAP_PX), vw - GAP_PX - width)}px`;
const below = vh - rect.bottom;
if (below >= rect.top) {
style.top = `${rect.bottom + GAP_PX}px`;
style.maxHeight = `${Math.max(below - GAP_PX * 2, 160)}px`;
} else {
style.top = `${GAP_PX}px`;
style.maxHeight = `${Math.max(rect.top - GAP_PX * 2, 160)}px`;
}
}
/* ── 표(B07 칸 배치 그대로) ─────────────────────────────────────────────── */
function cell(
@@ -182,9 +137,8 @@ export function openCrossDetail(section: CrossSection): void {
title: `${title} 상세`,
closeLabel: "닫기",
dialogClass: "b06-detail",
backdropClass: "b06-detail__backdrop",
wide: true,
mount(body, close) {
const dialog = body.parentElement as HTMLElement;
const note = document.createElement("textarea");
note.className = "ui-input b06-detail__note";
note.rows = 3;
@@ -195,7 +149,7 @@ export function openCrossDetail(section: CrossSection): void {
cad.className = "b06-detail__cad";
const tableBox = document.createElement("div");
tableBox.className = "b06-detail__table";
body.append(note, cad, tableBox);
body.append(cad, note, tableBox);
// 표 칸 누름 = 그림 그 항목 강조 · 다시 누르면 끔(150-4)
let picked: string | null = null;
@@ -220,7 +174,6 @@ export function openCrossDetail(section: CrossSection): void {
const card = cardOf(state.section);
const svg = card?.querySelector("svg.b06-section__chart");
cad.replaceChildren(svg ? svg.cloneNode(true) : document.createTextNode("그림 없음"));
place(dialog, card);
showPicked();
},
loadTable() {
@@ -1,6 +1,6 @@
/* =============================================================================
* B06_Section_UI_Style_Cross_Detail.css
* 횡단 카드 [상세] 단추 · 모달(138-4) — 투명 막(누름 통과) · 글 칸 · CAD 꼴 그림 · B07 수량표.
* 횡단 카드 [상세] 단추 · 모달(138-4 · 150-2) — 공용 큰 창 · 그림(남은 높이) → 글 칸 → B07 수량표.
* ========================================================================== */
/* 측구 줄 맨 오른쪽 — ⋯ 가 보이면 그 옆, 숨으면 혼자 오른쪽 끝 */
@@ -13,25 +13,23 @@
margin-left: 0;
}
/* 막은 투명 · 누름은 아래 화면으로 — 모달 몸만 받음 */
.ui-modal__backdrop.b06-detail__backdrop {
background-color: transparent;
pointer-events: none;
}
.b06-detail__backdrop > .ui-modal.b06-detail {
pointer-events: auto;
overflow: auto;
box-shadow: var(--shadow-lg);
/* 스크롤 없음 — 글 · 표는 제 높이 · 그림이 남은 높이를 채움 */
.ui-modal.b06-detail > .ui-modal__body {
overflow: hidden;
}
.b06-detail__note {
flex: none;
width: 100%;
box-sizing: border-box;
resize: vertical;
resize: none;
font: inherit;
}
.b06-detail__table {
flex: none;
}
/* 가벼운 CAD 꼴 — 어두운 바탕 · 카드 그림 복제에 다크 색 토큰을 덮어씀 */
.b06-detail__cad {
--color-bg: #111111;
@@ -50,12 +48,15 @@
background: #111111;
color: #f2f2f2;
overflow: hidden;
flex: 1;
min-height: 0;
}
/* viewBox 그대로 — 비율 유지하며 칸 가득 */
.b06-detail__cad > svg {
display: block;
width: 100%;
height: auto;
height: 100%;
}
/* B07 횡단 수량 산출표 — 칸 배치 그대로 */
+14
View File
@@ -76,3 +76,17 @@ def test_상세_표_칸_키_누름_배선():
assert "element.dataset.key = key" in src
assert "highlightDetail(svg, state.section.design, picked)" in src
assert 'closest<HTMLElement>("[data-key]")' in src
def test_상세_큰_창_공용_막_그림_글_표_순서():
"""150-2 — 투명 막 지움(밖 누르면 공용 닫기) · 큰 창 · 그림 → 글 → 표 · 스크롤 없음."""
base = os.path.join(ROOT, "B06_Section")
with open(os.path.join(base, "B06_Section_UI_Cross_Detail.ts"), encoding="utf-8") as f:
src = f.read()
with open(os.path.join(base, "B06_Section_UI_Style_Cross_Detail.css"), encoding="utf-8") as f:
css = f.read().replace(chr(13), "")
assert "wide: true" in src and "backdropClass" not in src
assert "body.append(cad, note, tableBox)" in src
assert "pointer-events: none" not in css and "b06-detail__backdrop" not in css
assert ".ui-modal.b06-detail > .ui-modal__body {\n overflow: hidden;" in css
assert "flex: 1;\n min-height: 0;" in css and "height: 100%;" in css