fix(종단): 도구 고름 화면 흠 셋 — 고른 즉시 붉은 강조 · 스크롤 따라감 · 측점선 누름 전파 막음 (PLAN 456-3 · 요구 421 · 423~425)

- design_profile: 도구 고름 뒤 종단도 한 번 다시 그림
- RunHighlight · Render: 붉은 선을 계획선 세로 창 겹 안에 그림
- Render: 도구가 먹은 누름은 전파 막음 — 측점 고름 안 돎
- Panel_Tools: 도구 바꿀 때 겹 안 붉은 선도 같이 뗌
- test_271: 지킴 시험 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 22:02:25 +09:00
co-authored by Claude Sonnet 5.5
parent 4e2dce461d
commit 1241ad6d17
5 changed files with 37 additions and 6 deletions
+2 -1
View File
@@ -267,7 +267,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
restore: () => void (store.resync() ? rebuild() : draw()),
restoreSaved: () => store.restoreSaved(),
canRestoreSaved: () => store.canRestoreSaved(),
refresh: () => renderBalance(),
// 고름 직후 종단도도 한 번 — 붉은 강조가 다음 그림을 기다리지 않게(423).
refresh: () => (renderBalance(), drawSoon()),
});
/** 편집을 적용한다. 법정 위반 정책이 block이면 새 위반이 생기는 편집을 막는다. false = 막힘. */
@@ -131,7 +131,10 @@ export function createPanelTools(ctx: PanelToolsContext): PanelTools {
});
const mountViews = (mode: string): void => {
// [쉬프트] 붉은 강조는 그래프를 다시 그릴 때만 지워짐 — 다른 도구로 넘어가면 바로 뗌(271-1)
if (mode !== "shift") lastGeom?.chart.querySelector(".b05-profile-runmark")?.remove();
if (mode !== "shift")
lastGeom?.chart
.querySelectorAll(".b05-profile-runmark, .b05-profile-runmark__line")
.forEach((node) => node.remove());
freeView.mount(lastGeom, mode === "free");
bandView.mount(lastGeom, mode === "straighten");
};
+7 -2
View File
@@ -322,8 +322,9 @@ export function renderProfile(ctx: ProfileRenderContext): void {
if (ctx.toolActive()) {
const marker = (event.target as HTMLElement).closest(".b06-chart__station");
const raw = marker?.getAttribute("data-chainage");
let picked: boolean;
if (raw !== null && raw !== undefined) {
if (ctx.handleToolPick(Number(raw))) return;
picked = ctx.handleToolPick(Number(raw));
} else {
const rect = chartWrap.getBoundingClientRect();
const chainage = chainageInverter(
@@ -331,8 +332,10 @@ export function renderProfile(ctx: ProfileRenderContext): void {
width,
layout.originOffset,
)(event.clientX - rect.left + chartWrap.scrollLeft);
if (ctx.handleToolPick(Number.isFinite(chainage) ? chainage : null)) return;
picked = ctx.handleToolPick(Number.isFinite(chainage) ? chainage : null);
}
// 도구가 먹은 누름은 측점선 자기 클릭(측점 고름)으로 새지 않게 막음(425).
if (picked) event.stopPropagation();
return;
}
// 원지반선 근처 · 추가 측점 선 누름 = 그 자리 임시 횡단 작은 창(368-3)
@@ -478,6 +481,8 @@ export function renderProfile(ctx: ProfileRenderContext): void {
axis: yAxis,
widthPx: width,
heightPx: chartHeight,
// 붉은 선은 계획선 겹 안에 — 스크롤 때 세로 맞춤 변환을 계획선과 같이 받음(424).
windowLayer: chartWrap.querySelector(".b06-chart__profile")?.parentElement,
});
if (highlight) chartWrap.append(highlight);
}
@@ -29,6 +29,11 @@ export interface RunHighlightOptions {
axis: { ticks: Array<{ y: number; label: string }> } | null;
widthPx: number;
heightPx: number;
/**
* 계획선이 든 세로 창 겹 — 붉은 선을 여기 넣으면 스크롤 때 계획선과 같은 변환을 받는다.
* 없으면 덮개 안에 그대로 둔다(세로 맞춤을 못 따라감).
*/
windowLayer?: Element | null;
}
/** 눈금 라벨(`880m`)에서 표고를 읽는다. 숫자가 아니면 null. */
@@ -84,6 +89,7 @@ export function createRunHighlight(options: RunHighlightOptions): SVGElement | n
svg.setAttribute("viewBox", `0 0 ${widthPx} ${heightPx}`);
const samples = alignment.samples;
let drawn = 0;
for (const run of runs) {
const [fromM, toM] = runSpanWithCurves(alignment, run);
// 계획선 샘플에는 변화점·BVC·EVC가 모두 들어 있어 라운드 곡률까지 그대로 따라온다.
@@ -95,7 +101,8 @@ export function createRunHighlight(options: RunHighlightOptions): SVGElement | n
const line = document.createElementNS(SVG_NS, "polyline");
line.setAttribute("class", "b05-profile-runmark__line");
line.setAttribute("points", points.join(" "));
svg.append(line);
(options.windowLayer ?? svg).append(line);
drawn += 1;
// 양 끝 표시 — 어디까지가 이 구간인지(라운드 포함) 한눈에 보이게 세로 표식을 둔다.
for (const edge of [fromM, toM]) {
const tick = document.createElementNS(SVG_NS, "line");
@@ -107,5 +114,5 @@ export function createRunHighlight(options: RunHighlightOptions): SVGElement | n
svg.append(tick);
}
}
return svg.childElementCount ? svg : null;
return drawn ? svg : null;
}
@@ -199,3 +199,18 @@ def test_줄_수():
"A00_Common/design/design_profile_range_band.ts",
):
assert len(_read(relative).splitlines()) <= 700
def test_456_3_고름_즉시_강조_계획선_겹_전파_막음():
# 도구 고름 뒤 종단도 한 번 다시 그림(423)
profile = _read("A00_Common/design/design_profile.ts")
assert "refresh: () => (renderBalance(), drawSoon())" in profile
# 붉은 선 = 계획선 겹 안(424) · 도구가 먹은 누름은 측점선 자기 클릭으로 안 샘(425)
render = _read("B05_Profile/B05_Profile_UI_Profile_Render.ts")
assert 'windowLayer: chartWrap.querySelector(".b06-chart__profile")?.parentElement' in render
assert "if (picked) event.stopPropagation();" in render
mark = _read("B05_Profile/B05_Profile_UI_Profile_RunHighlight.ts")
assert "(options.windowLayer ?? svg).append(line)" in mark
# 도구를 바꾸면 겹 안 붉은 선도 같이 뗌
panel = _read("B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts")
assert ".b05-profile-runmark, .b05-profile-runmark__line" in panel