feat(B06): 조정창 항목 2행 구조 + 이동 버튼 십자(D-pad) 배치

- 항목별 1행 = 이름 라벨, 2행 = 값 조작(구조물 형식·재질·높이·단 수·이동)
- 이동 버튼 십자 배치: ▲ 위 / ◀ ↺ ▶ 가운데 / ▼ 아래 — 중앙이 초기화
- __controls(flex)와의 우선순위 충돌은 복합 선택자로 해결

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:42:06 +09:00
co-authored by Claude Opus 5
parent 97bf00a8ef
commit c4322f620c
3 changed files with 109 additions and 44 deletions
@@ -77,6 +77,19 @@ function makeButton(label: string, title: string, onClick: () => void): HTMLButt
return button;
}
/** 항목 행 — 1행 이름 라벨 + 2행 값 조작(2026-08-22 사용자 확정 2행 구조). */
function makeRow(labelText: string): { row: HTMLElement; controls: HTMLElement } {
const row = document.createElement("div");
row.className = "b06-structure-panel__struct";
const label = document.createElement("span");
label.className = "b06-structure-panel__label";
label.textContent = labelText;
const controls = document.createElement("div");
controls.className = "b06-structure-panel__controls";
row.append(label, controls);
return { row, controls };
}
/** 구조물 조정 오버레이 창을 만든다. 처음에는 숨어 있다. */
export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHandle {
const root = document.createElement("div");
@@ -96,41 +109,57 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
if (current) run(current);
};
const buttons = document.createElement("div");
buttons.className = "b06-structure-panel__buttons";
const moveRow = makeRow(L("B06_Cross_Move_Label"));
// 버튼 묶음 클래스는 컨트롤 행에 — **십자(D-pad) 배치**(2026-08-22 사용자):
// ▲ 위 / ◀ ↺ ▶ 가운데 / ▼ 아래. 초기화가 십자 중앙이다.
moveRow.controls.classList.add("b06-structure-panel__buttons");
const buttons = moveRow.controls;
const dpad = (
label: string,
slot: "up" | "down" | "left" | "right" | "reset",
title: string,
onClick: () => void,
): HTMLButtonElement => {
const button = makeButton(label, title, onClick);
button.classList.add(`b06-structure-panel__btn--${slot}`);
return button;
};
buttons.append(
makeButton(
"◀",
L("B06_Cross_Revet_Left"),
act((key) => deps.nudge(key, STEP_M)),
),
makeButton(
"▶",
L("B06_Cross_Revet_Right"),
act((key) => deps.nudge(key, -STEP_M)),
),
makeButton(
dpad(
"▲",
"up",
L("B06_Cross_Revet_Up"),
act((key) => deps.nudgeSlope(key, -STEP_M)),
),
makeButton(
"",
L("B06_Cross_Revet_Down"),
act((key) => deps.nudgeSlope(key, STEP_M)),
dpad(
"",
"left",
L("B06_Cross_Revet_Left"),
act((key) => deps.nudge(key, STEP_M)),
),
makeButton(
dpad(
"↺",
"reset",
L("B06_Cross_Revet_Reset"),
act((key) => deps.reset(key)),
),
dpad(
"▶",
"right",
L("B06_Cross_Revet_Right"),
act((key) => deps.nudge(key, -STEP_M)),
),
dpad(
"▼",
"down",
L("B06_Cross_Revet_Down"),
act((key) => deps.nudgeSlope(key, STEP_M)),
),
);
// 재질 행(2026-08-22 사용자 ④ — 높이 행 **위**, 폭 축소: 한계는 툴팁으로).
const materialRow = document.createElement("label");
materialRow.className = "b06-structure-panel__struct";
const materialLabelEl = document.createElement("span");
materialLabelEl.textContent = L("B06_Cross_Mat_Label");
const materialParts = makeRow(L("B06_Cross_Mat_Label"));
const materialRow = materialParts.row;
const materialSelect = document.createElement("select");
materialSelect.className = "b06-structure-panel__select";
const MATERIAL_LABEL: Record<RevetMaterial, string> = {
@@ -148,13 +177,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
materialSelect.addEventListener("change", () => {
if (current) deps.setMaterial(current, materialSelect.value as RevetMaterial);
});
materialRow.append(materialLabelEl, materialSelect);
materialParts.controls.append(materialSelect);
// 높이 행 — ±0.1m, 한계는 재질이 정한다.
const heightRow = document.createElement("div");
heightRow.className = "b06-structure-panel__struct";
const heightLabel = document.createElement("span");
heightLabel.textContent = L("B06_Cross_Height_Label");
const heightParts = makeRow(L("B06_Cross_Height_Label"));
const heightRow = heightParts.row;
const heightValue = document.createElement("span");
heightValue.className = "b06-structure-panel__hval";
const heightMinus = makeButton(
@@ -167,13 +194,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
L("B06_Cross_Height_Plus"),
act((key) => deps.nudgeHeight(key, HEIGHT_STEP_M)),
);
heightRow.append(heightLabel, heightMinus, heightValue, heightPlus);
heightParts.controls.append(heightMinus, heightValue, heightPlus);
// 유입측 구조물 형식 드롭다운(2026-08-22 사용자) — 자동/기슭막이/집수정 I·ㄴ·ㄷ.
const structureRow = document.createElement("label");
structureRow.className = "b06-structure-panel__struct";
const structureLabel = document.createElement("span");
structureLabel.textContent = L("B06_Cross_Struct_Label");
const structureParts = makeRow(L("B06_Cross_Struct_Label"));
const structureRow = structureParts.row;
const select = document.createElement("select");
select.className = "b06-structure-panel__select";
const OPTIONS: Array<[InletStructureChoice, string]> = [
@@ -192,13 +217,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
select.addEventListener("change", () => {
deps.setStructure(select.value as InletStructureChoice);
});
structureRow.append(structureLabel, select);
structureParts.controls.append(select);
// 다단 기슭막이 단 수(2026-08-22 사용자) — 숫자 입력 + +/- 병행(스피너는 CSS 제거).
const extraRow = document.createElement("label");
extraRow.className = "b06-structure-panel__struct";
const extraLabel = document.createElement("span");
extraLabel.textContent = L("B06_Cross_Extra_Count");
const extraParts = makeRow(L("B06_Cross_Extra_Count"));
const extraRow = extraParts.row;
const countInput = document.createElement("input");
countInput.type = "number";
countInput.min = "0";
@@ -218,7 +241,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
const countPlus = makeButton("", L("B06_Cross_Extra_Add"), () =>
deps.setExtraCount(clampCount(Number(countInput.value) + 1)),
);
extraRow.append(extraLabel, countMinus, countInput, countPlus);
extraParts.controls.append(countMinus, countInput, countPlus);
const closeButton = makeButton("✕", L("B06_Cross_Revet_Close"), () => deps.close());
closeButton.classList.add("b06-structure-panel__close");
@@ -226,7 +249,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
const head = document.createElement("div");
head.className = "b06-structure-panel__head";
head.append(title, closeButton);
root.append(head, value, structureRow, materialRow, heightRow, extraRow, buttons);
root.append(head, value, structureRow, materialRow, heightRow, extraRow, moveRow.row);
return {
root,
@@ -259,7 +282,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan
}
select.value = structure;
}
buttons.classList.toggle("is-hidden", !movable);
moveRow.row.classList.toggle("is-hidden", !movable);
materialRow.classList.toggle("is-hidden", !movable);
heightRow.classList.toggle("is-hidden", !movable);
if (movable) {
+45 -4
View File
@@ -601,11 +601,38 @@
font-size: var(--text-caption);
}
.b06-structure-panel__buttons {
display: flex;
/* 이동 조작 = 십자(D-pad) 배치(2026-08-22 사용자) — 중앙이 초기화.
__controls(flex)보다 뒤에 선언된 것과 무관하게 이기도록 복합 선택자. */
.b06-structure-panel__controls.b06-structure-panel__buttons {
display: grid;
grid-template-areas:
". up ."
"left reset right"
". down .";
justify-content: start;
gap: 2px;
}
.b06-structure-panel__btn--up {
grid-area: up;
}
.b06-structure-panel__btn--down {
grid-area: down;
}
.b06-structure-panel__btn--left {
grid-area: left;
}
.b06-structure-panel__btn--right {
grid-area: right;
}
.b06-structure-panel__btn--reset {
grid-area: reset;
}
.b06-structure-panel__btn.is-hidden {
display: none;
}
@@ -643,14 +670,28 @@
}
/* 유입 구조물 형식 드롭다운(2026-08-22 사용자) — 라벨 + select 한 줄. */
/* 항목 행 = 2행 구조(2026-08-22 사용자): 1행 이름 라벨, 2행 값 조작. */
.b06-structure-panel__struct {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-direction: column;
align-items: stretch;
gap: 2px;
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
.b06-structure-panel__label {
color: var(--color-text-muted);
font-size: var(--text-caption);
line-height: 1.2;
}
.b06-structure-panel__controls {
display: flex;
align-items: center;
gap: 2px;
}
.b06-structure-panel__struct.is-hidden {
display: none;
}
+1
View File
@@ -265,6 +265,7 @@ export const ui_locales_b2 = {
"Cannot move further down the slope",
],
B06_Cross_Height_Label: ["높이", "Height"],
B06_Cross_Move_Label: ["이동(좌우·사면 상하)", "Move (lateral / along slope)"],
B06_Cross_Height_Minus: ["높이 0.1m", "Height 0.1m"],
B06_Cross_Height_Plus: ["높이 +0.1m", "Height +0.1m"],
B06_Cross_Height_Limit: [