feat(B05): 계획노선 편집 창 고정 표시 · 시점 · 종점 고정(PLAN 22-4 · 22-5)

- 고정 한 벌 — 시점 · 종점(기본 고정) · 교각점을 한 칸 줄로 봄 · 저장 · 읽기는 _RouteEdit_Lock.ts(readRouteLocks = 초기노선 계산에 넘길 한 곳)
- 곧은 선은 양 끝 · 호는 교각점과 앞뒤 점이 모두 고정일 때 고정 색(--map-route-locked · 밝음 #db2777 · 어두움 #f472b6) · 고정 점도 같은 색
- 시점 · 종점을 누르면 고정 단추만 뜸 · 풀면 끌어 옮김 · 고정이면 끌기 · 지우기 막힘
- 되돌리기 · [취소] · 다시 열기 · [확인] 뒤에도 고정 유지(pref 통 · 좌표로 되짚음) · [확인] 몸통은 그대로
- 시험 test_b05_routeedit_lock 5건

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
This commit is contained in:
2026-09-29 19:46:46 +09:00
co-authored by Claude Opus 5.5
parent 5b41cfaf01
commit ea4cb043cd
10 changed files with 387 additions and 20 deletions
+15 -4
View File
@@ -57,6 +57,8 @@ const COMPASS_STEPS = 16;
export interface CurveLabelState {
/** 몇 번째 꺾임점인지 — 0부터 센 자리. 표시는 +1 해서 낸다. */
seat: number;
/** 시점 · 종점을 골랐나 — 곡선 칸을 숨기고 **고정 단추만** 낸다(계획서 22-5). */
endpoint?: "start" | "end" | null;
/** 그 꺾임점의 **화면(viewport) 좌표** px — 패널이 `position: fixed` 라서. */
at: [number, number];
/** **곡선 중심이 있는 쪽**(화면 기준 방향벡터). 패널은 이 반대쪽에 붙는다. */
@@ -135,9 +137,8 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel {
<button type="button" class="b05-routeedit__lock" data-act="lock-arc"
title="곡선 길이 고정 — 노드를 옮겨도 안 바뀝니다">고정</button>
</label>
<div class="b05-routeedit__curve-field">교각점
<button type="button" class="b05-routeedit__lock" data-act="lock-apex"
title="교각점 고정 — 이 꺾임점의 자리를 못 박습니다. 노드도 손잡이도 안 끌리고 반지름·곡선 길이 칸으로만 바뀝니다">고정</button>
<div class="b05-routeedit__curve-field"><span class="b05-routeedit__lock-name">교각점</span>
<button type="button" class="b05-routeedit__lock" data-act="lock-apex">고정</button>
</div>
<span class="b05-routeedit__curve-info"></span>`;
document.body.append(root);
@@ -151,6 +152,9 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel {
const lockArc = root.querySelector<HTMLButtonElement>('[data-act="lock-arc"]')!;
const lockApex = root.querySelector<HTMLButtonElement>('[data-act="lock-apex"]')!;
const info = root.querySelector<HTMLElement>(".b05-routeedit__curve-info")!;
const lockName = root.querySelector<HTMLElement>(".b05-routeedit__lock-name")!;
/** 끝점에서는 숨길 곡선 칸 — 곡선 지우기 단추 · 반지름 줄 · 곡선 길이 줄 · 내각 글. */
const curveOnly = [toggle, radius.closest("label")!, arc.closest("label")!, info];
// 패널 위에서 누른 것이 캔버스로 새어 나가면 노드가 딸려 움직인다.
for (const type of ["pointerdown", "dblclick", "contextmenu", "wheel"] as const) {
@@ -280,7 +284,14 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel {
radius.min = limitRadius > 0 ? String(limitRadius) : "1";
arc.min = limitArc > 0 ? String(limitArc) : "1";
root.hidden = false;
seatText.textContent = `${state.seat + 1}번째 꺾임점`;
const end = state.endpoint ?? null;
const name = end === "start" ? "시점" : end === "end" ? "종점" : "교각점";
curveOnly.forEach((element) => (element.hidden = end !== null));
seatText.textContent = end ? name : `${state.seat + 1}번째 꺾임점`;
lockName.textContent = name;
lockApex.title = end
? `${name} 고정 — 켜면 끌어 옮길 수 없습니다. 풀면 끌어 옮깁니다`
: "교각점 고정 — 이 꺾임점의 자리를 못 박습니다. 노드도 손잡이도 안 끌리고 반지름·곡선 길이 칸으로만 바뀝니다";
toggle.textContent = state.curveOn ? "곡선 지우기" : "곡선 넣기";
radius.disabled = !state.curveOn;
arc.disabled = !state.curveOn;