diff --git a/services/pcp-tgu-ui-service/src/features/tgu-editor/MapPassSelector.tsx b/services/pcp-tgu-ui-service/src/features/tgu-editor/MapPassSelector.tsx index a7eaaf3..e8ab98e 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-editor/MapPassSelector.tsx +++ b/services/pcp-tgu-ui-service/src/features/tgu-editor/MapPassSelector.tsx @@ -42,21 +42,34 @@ export function MapPassSelector({ passes, fromIndex, toIndex, onFromChange, onTo return Math.round(Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)) * (n - 1)) + 1; }; - const makeHandlers = (isFrom: boolean) => ({ + // Какой ползунок реально тянем. При наложении (fromIndex === toIndex) хват попадает на верхний + // (to), но на правом краю он заперт fromIndex и не может уйти влево — поэтому при наложении + // выбираем ползунок по направлению движения: влево → from (открыть диапазон), вправо → to. + const dragRef = useRef<"from" | "to" | null>(null); + + const makeHandlers = (which: "from" | "to") => ({ onPointerDown(e: React.PointerEvent) { e.stopPropagation(); + dragRef.current = which; (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId); }, onPointerMove(e: React.PointerEvent) { if (!(e.currentTarget as HTMLDivElement).hasPointerCapture(e.pointerId)) return; const idx = indexAt(e.clientX); - if (isFrom) { + let active = dragRef.current; + if (fromIndex === toIndex) { + if (idx < fromIndex) active = "from"; + else if (idx > toIndex) active = "to"; + dragRef.current = active; + } + if (active === "from") { onFromChange(Math.max(1, Math.min(idx, toIndex))); } else { onToChange(Math.min(n, Math.max(idx, fromIndex))); } }, onPointerUp(e: React.PointerEvent) { + dragRef.current = null; (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId); }, }); @@ -111,13 +124,13 @@ export function MapPassSelector({ passes, fromIndex, toIndex, onFromChange, onTo
{/* To handle */}
diff --git a/services/pcp-tgu-ui-service/src/features/tgu-slots/SlotDaySelector.tsx b/services/pcp-tgu-ui-service/src/features/tgu-slots/SlotDaySelector.tsx index 915616e..24c3269 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-slots/SlotDaySelector.tsx +++ b/services/pcp-tgu-ui-service/src/features/tgu-slots/SlotDaySelector.tsx @@ -37,21 +37,34 @@ export function SlotDaySelector({ days, fromIndex, toIndex, onFromChange, onToCh return Math.round(Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)) * (n - 1)) + 1; }; - const makeHandlers = (isFrom: boolean) => ({ + // Какой ползунок реально тянем. При наложении (fromIndex === toIndex) хват попадает на верхний + // (to), но на правом краю он заперт fromIndex и не может уйти влево — поэтому при наложении + // выбираем ползунок по направлению движения: влево → from (открыть диапазон), вправо → to. + const dragRef = useRef<"from" | "to" | null>(null); + + const makeHandlers = (which: "from" | "to") => ({ onPointerDown(e: React.PointerEvent) { e.stopPropagation(); + dragRef.current = which; (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId); }, onPointerMove(e: React.PointerEvent) { if (!(e.currentTarget as HTMLDivElement).hasPointerCapture(e.pointerId)) return; const idx = indexAt(e.clientX); - if (isFrom) { + let active = dragRef.current; + if (fromIndex === toIndex) { + if (idx < fromIndex) active = "from"; + else if (idx > toIndex) active = "to"; + dragRef.current = active; + } + if (active === "from") { onFromChange(Math.max(1, Math.min(idx, toIndex))); } else { onToChange(Math.min(n, Math.max(idx, fromIndex))); } }, onPointerUp(e: React.PointerEvent) { + dragRef.current = null; (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId); }, }); @@ -101,12 +114,12 @@ export function SlotDaySelector({ days, fromIndex, toIndex, onFromChange, onToCh