fix(B05): 임시 배치를 측점+잔여거리 입력이 끝난 뒤로 지연
측점번호만 넣고 잔여거리 칸으로 넘어가는 순간(blur) 임시 배치가 나가 재계산 동기화가 입력을 끊었다(2026-08-18 사용자 보고 2차). - 임시 배치 시점 변경: 측점 입력군(positionRow)에서 포커스가 완전히 빠져나간 뒤(focusout + rAF 판정) 또는 Enter. 칸 사이 이동 중에는 나가지 않는다. - 관이 계획선에 스냅되며 입력 누가거리와 어긋나면 임시 추적이 풀리던 것 — 허용오차를 0.05m에서 0.51m(관 매칭 공통 기준)로 넓히고, 재계산 후 실제 좌표로 임시·선택 누가거리를 갱신해 계속 따라간다. - 우클릭 추가는 측점이 확정돼 들어오므로 즉시 임시 배치(기존과 동일). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -264,26 +264,30 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
facilityOptions.root.classList.toggle("is-locked", locked);
|
||||
}
|
||||
|
||||
/** 측점 입력 변경 — A군이면 임시 배치를 만들거나 옮기고(세부유역 미리보기,
|
||||
* 2026-08-18), 어느 군이든 옵션 잠금을 다시 판정한다. */
|
||||
function handleStationInput(): void {
|
||||
/** A군이면 기준 측점(측점번호+잔여거리)으로 임시 배치를 만들거나 옮긴다
|
||||
* (세부유역 미리보기, 2026-08-18). 측점 두 칸을 **다 쓰고 빠져나온 뒤**에만
|
||||
* 부른다 — 측점번호만 넣고 거리 칸으로 넘어가는 순간 나가면 재계산 동기화가
|
||||
* 입력을 끊는다(2026-08-18 사용자 보고). */
|
||||
function commitTempPipe(): void {
|
||||
const type = currentType();
|
||||
if (type?.managed_by) {
|
||||
const anchor = anchorFields.read(interval(), false);
|
||||
// 기존 관(임시 아님)을 수정 중일 때 기준점 이동은 [수정]으로만 반영한다.
|
||||
if (anchor !== null && (selectedPipeChainage === null || tempPipeChainage !== null)) {
|
||||
const attributes: FacilityAttributes = { facility: type.type_id as PipeFacility };
|
||||
if (tempPipeChainage === null) {
|
||||
tempPipeChainage = anchor;
|
||||
callbacks.onPipeAdd(anchor, attributes);
|
||||
} else if (Math.abs(tempPipeChainage - anchor) > 0.005) {
|
||||
const from = tempPipeChainage;
|
||||
tempPipeChainage = anchor;
|
||||
callbacks.onPipeUpdate(from, anchor, attributes);
|
||||
}
|
||||
syncButtons();
|
||||
}
|
||||
if (!type?.managed_by) return;
|
||||
const anchor = anchorFields.read(interval(), false);
|
||||
// 기존 관(임시 아님)을 수정 중일 때 기준점 이동은 [수정]으로만 반영한다.
|
||||
if (anchor === null || (selectedPipeChainage !== null && tempPipeChainage === null)) return;
|
||||
const attributes: FacilityAttributes = { facility: type.type_id as PipeFacility };
|
||||
if (tempPipeChainage === null) {
|
||||
tempPipeChainage = anchor;
|
||||
callbacks.onPipeAdd(anchor, attributes);
|
||||
} else if (Math.abs(tempPipeChainage - anchor) > 0.005) {
|
||||
const from = tempPipeChainage;
|
||||
tempPipeChainage = anchor;
|
||||
callbacks.onPipeUpdate(from, anchor, attributes);
|
||||
}
|
||||
syncButtons();
|
||||
}
|
||||
|
||||
/** 측점 입력 변경 — 옵션 잠금만 다시 판정한다(임시 배치는 commitTempPipe 몫). */
|
||||
function handleStationInput(): void {
|
||||
syncOptionLock();
|
||||
}
|
||||
|
||||
@@ -354,7 +358,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
if (tempPipeChainage === null) return;
|
||||
const chainage = tempPipeChainage;
|
||||
tempPipeChainage = null;
|
||||
if (selectedPipeChainage !== null && Math.abs(selectedPipeChainage - chainage) < 0.05) {
|
||||
if (selectedPipeChainage !== null && Math.abs(selectedPipeChainage - chainage) < 0.51) {
|
||||
selectedPipeChainage = null;
|
||||
}
|
||||
callbacks.onPipeRemove(chainage);
|
||||
@@ -449,7 +453,9 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
// 임시 배치 중인 그 관이 재계산을 거쳐 되돌아온 경우 — 사용자가 폼에 입력하는
|
||||
// 중이므로 폼을 다시 그리지 않는다(입력이 지워진다, 2026-08-18 사용자 보고).
|
||||
// 재계산으로 갱신된 설계유량만 살짝 반영하고 선택 표시를 맞춘다.
|
||||
const isTemp = tempPipeChainage !== null && Math.abs(pipe.chainage_m - tempPipeChainage) < 0.05;
|
||||
// 관은 계획선에 스냅되며 입력 누가거리와 살짝 어긋날 수 있다 — 0.51m까지 같은
|
||||
// 관으로 본다(다른 곳의 관 매칭 기준과 동일).
|
||||
const isTemp = tempPipeChainage !== null && Math.abs(pipe.chainage_m - tempPipeChainage) < 0.51;
|
||||
if (isTemp) {
|
||||
tempPipeChainage = pipe.chainage_m;
|
||||
selectedPipeChainage = pipe.chainage_m;
|
||||
@@ -637,12 +643,26 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
// 리셋은 폼만 아니라 구조물군·종류도 빈칸으로 되돌린다(2026-08-17 사용자 지시).
|
||||
// 임시 배치 취소도 겸한다(2026-08-18).
|
||||
resetButton.addEventListener("click", resetForm);
|
||||
// 측점 입력 감시 — 옵션 잠금 해제 판정 + A군 임시 배치 생성·이동(2026-08-18).
|
||||
// 측점 입력 감시 — 옵션 잠금 해제 판정(2026-08-18).
|
||||
[anchorFields, startFields, endFields].forEach((fields) =>
|
||||
[fields.station, fields.remainder].forEach((input) =>
|
||||
input.addEventListener("change", handleStationInput),
|
||||
),
|
||||
);
|
||||
// A군 임시 배치는 측점 입력군에서 포커스가 완전히 빠져나간 뒤에만 — 측점번호를
|
||||
// 넣고 잔여거리 칸으로 옮기는 중간에는 나가지 않는다(2026-08-18 사용자 지시:
|
||||
// 측점+거리를 다 받은 후 임시 배치). Enter로도 바로 나갈 수 있다.
|
||||
positionRow.addEventListener("focusout", () => {
|
||||
// 다음 포커스 대상이 확정된 다음 프레임에 판단한다(relatedTarget은 브라우저에
|
||||
// 따라 비어 온다).
|
||||
window.requestAnimationFrame(() => {
|
||||
if (positionRow.contains(document.activeElement)) return;
|
||||
commitTempPipe();
|
||||
});
|
||||
});
|
||||
positionRow.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") commitTempPipe();
|
||||
});
|
||||
|
||||
body.insertBefore(field("메모", memoField), actions);
|
||||
syncButtons();
|
||||
@@ -672,13 +692,19 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
getStructures: () => [...structures],
|
||||
setPipeFacilities(pipes) {
|
||||
pipeFacilities = pipes.map((pipe) => ({ ...pipe }));
|
||||
if (
|
||||
tempPipeChainage !== null &&
|
||||
!pipeFacilities.some((pipe) => Math.abs(pipe.chainage_m - tempPipeChainage!) < 0.05)
|
||||
) {
|
||||
// 임시 배치 관이 재계산에서 사라졌다 — 임시 상태만 정리(관은 이미 없다).
|
||||
tempPipeChainage = null;
|
||||
syncButtons();
|
||||
if (tempPipeChainage !== null) {
|
||||
// 스냅·재계산으로 좌표가 조금 옮겨진 임시 관을 계속 따라간다(0.51m 기준).
|
||||
const hit = pipeFacilities.find(
|
||||
(pipe) => Math.abs(pipe.chainage_m - tempPipeChainage!) < 0.51,
|
||||
);
|
||||
if (hit) {
|
||||
tempPipeChainage = hit.chainage_m;
|
||||
if (selectedPipeChainage !== null) selectedPipeChainage = hit.chainage_m;
|
||||
} else {
|
||||
// 임시 배치 관이 재계산에서 사라졌다 — 임시 상태만 정리(관은 이미 없다).
|
||||
tempPipeChainage = null;
|
||||
syncButtons();
|
||||
}
|
||||
}
|
||||
if (
|
||||
selectedPipeChainage !== null &&
|
||||
@@ -743,7 +769,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
// BOX암거는 레지스트리 기본값(본체 2.0×2.0·날개벽 있음 1m/2m/45°)을 실어 폼을
|
||||
// 연다 — [추가]만 눌러도 하류가 제원을 받는다(2026-08-17 사용자 확정 유지).
|
||||
syncFacilityForm(typeId === "box_culvert" ? defaultOptions(type) : {});
|
||||
if (type.managed_by) handleStationInput();
|
||||
// 우클릭은 측점이 이미 확정돼 들어온다 — 임시 배치 즉시 진행.
|
||||
if (type.managed_by) commitTempPipe();
|
||||
syncButtons();
|
||||
renderList();
|
||||
root.scrollIntoView({ block: "nearest" });
|
||||
|
||||
Reference in New Issue
Block a user