Merge remote-tracking branch 'origin/dev' into sub_laptop_7

This commit is contained in:
2026-10-10 13:37:21 +09:00
3 changed files with 411 additions and 5 deletions
@@ -8,13 +8,15 @@
* 구역 판정(`crossZoneAt` · 도로 · 절토 · 성토)은 설계선 쉬프트(139-6)도 쓴다.
* 고름 상태는 페이지에 한 칸(측점 + 조각 번호) — 카드를 다시 그려도 되살아난다.
* 고른 조각 쪽 사면을 노견째 이동 패널(◀ ↺ ▶ · 방향키 · 0.1 m)로 옮김(139-6 쉬프트 · 151-3 ·
* 노폭 안쪽 금지 = 0 에서 멈춤).
* 노폭 안쪽 금지 = 0 에서 멈춤). 축 제약(193-1) — 절 · 성토측 = 그 쪽 바깥 가로만(반대측 고정 ·
* 구조물은 노견 끝에 서서 한 벌로 감) · 도로측(노면 · 노견) = 높이만(▲▼ = 종단 계획고).
* ========================================================================== */
import type { CrossDesign } from "./B06_Section_Api_Fetch";
import { clipSegment, type XY } from "../A00_Common/b_polygon_clip";
import { sideRole } from "@util/common_util_cross_design_geometry";
import {
arrowsBelongToProfile,
bindArrowKeys,
buildPanelShell,
dpadButton,
@@ -100,6 +102,11 @@ export function crossZoneAt(
export const pieceZone = (design: ZoneDesign, piece: DesignPiece) =>
crossZoneAt(design, (piece.from.offset + piece.to.offset) / 2, piece.structure);
/** 고른 선이 움직이는 축(193-1) — 도로측(노면 · 노견) = 높이(y · 종단 계획고 ▲▼) ·
* 절토 · 성토측(구조물 트림 선 포함) = 그 쪽 바깥 가로(x · 노견째 · 구조물 한 벌). */
export const pieceMoveAxis = (design: ZoneDesign, piece: DesignPiece): "x" | "y" =>
pieceZone(design, piece).zone === "road" ? "y" : "x";
const gap = (a: CrossPoint, b: CrossPoint): number =>
Math.hypot(a.offset - b.offset, a.elevation - b.elevation);
@@ -330,6 +337,7 @@ export function attachDesignLinePick(input: {
label.className = "b06-cross-card__line-dims";
label.hidden = true;
let side: CrossSide | null = null;
let axis: "x" | "y" = "x";
let active: number | null = null;
const { shift } = input;
@@ -364,6 +372,20 @@ export function attachDesignLinePick(input: {
);
shell.root.append(readout, move.row);
const arrows = bindArrowKeys(shell.root, () => [move.controls]);
// 못 가는 축의 방향키는 먹어 막음(193-1) — 사면 ▲▼ 가 종단 계획고로 새지 않고 · 노면 ◀▶ 는 안 감.
// 노면 ▲▼ 는 그대로 종단도(계획고 · 종단 함께)로 넘김.
const blockOffAxis = (event: KeyboardEvent): void => {
if (!shell.root.isConnected) {
document.removeEventListener("keydown", blockOffAxis);
return;
}
if (shell.root.classList.contains("is-hidden") || arrowsBelongToProfile()) return;
const focus = event.target as HTMLElement | null;
if (focus && (focus.tagName === "SELECT" || focus.tagName === "INPUT")) return;
const vertical = event.key === "ArrowUp" || event.key === "ArrowDown";
const horizontal = event.key === "ArrowLeft" || event.key === "ArrowRight";
if (axis === "x" ? vertical : horizontal) event.preventDefault();
};
return {
pieces,
@@ -375,16 +397,25 @@ export function attachDesignLinePick(input: {
label.hidden = !piece;
active = piece ? index : null;
side = piece ? pieceZone(design, piece).side : null;
const extend = side && shift ? shift.valueOf(side) : 0;
axis = piece ? pieceMoveAxis(design, piece) : "x";
const extend = side && shift && axis === "x" ? shift.valueOf(side) : 0;
label.textContent = piece
? pieceLabel(design, piece) + (extend > 0 ? ` · 이동 +${extend.toFixed(1)}m` : "")
: "";
const open = !!(piece && shift);
shell.root.classList.toggle("is-hidden", !open);
shell.title.textContent = side === "left" ? "사면 이동 (좌)" : "사면 이동 (우)";
readout.textContent = `이동 ${extend.toFixed(1)}m`;
move.row.classList.toggle("is-hidden", axis === "y");
move.controls.classList.toggle("is-hidden", axis === "y");
shell.title.textContent =
axis === "y" ? "노면 높이" : side === "left" ? "사면 이동 (좌)" : "사면 이동 (우)";
readout.textContent =
axis === "y" ? "방향키 ▲▼ = 종단 계획고(종단도 함께)" : `이동 ${extend.toFixed(1)}m`;
arrows.detach();
if (open) arrows.attach();
document.removeEventListener("keydown", blockOffAxis);
if (open) {
arrows.attach();
document.addEventListener("keydown", blockOffAxis);
}
},
};
}