fix(tgu-ui): двуручейный слайдер — выбор ползунка по направлению при наложении

При наложении в крайней точке хват попадал на верхний ползунок to, который на
правом краю заперт fromIndex и не уходит влево, а нижний from был недоступен.
Теперь при fromIndex === toIndex управляемый ползунок выбирается по направлению
движения (влево → from, вправо → to). Чинит и «Сутки» (SlotDaySelector), и
«Витки» (MapPassSelector).
This commit is contained in:
Дмитрий Соловьев
2026-06-15 14:38:34 +03:00
parent 33b56ba951
commit bffb8e47b7
2 changed files with 34 additions and 8 deletions
@@ -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<HTMLDivElement>) {
e.stopPropagation();
dragRef.current = which;
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
},
onPointerMove(e: React.PointerEvent<HTMLDivElement>) {
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<HTMLDivElement>) {
dragRef.current = null;
(e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);
},
});
@@ -111,13 +124,13 @@ export function MapPassSelector({ passes, fromIndex, toIndex, onFromChange, onTo
<div
className="map-pass-selector__thumb"
style={{ left: `${fromPct}%` }}
{...makeHandlers(true)}
{...makeHandlers("from")}
/>
{/* To handle */}
<div
className="map-pass-selector__thumb"
style={{ left: `${toPct}%` }}
{...makeHandlers(false)}
{...makeHandlers("to")}
/>
</div>
@@ -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<HTMLDivElement>) {
e.stopPropagation();
dragRef.current = which;
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
},
onPointerMove(e: React.PointerEvent<HTMLDivElement>) {
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<HTMLDivElement>) {
dragRef.current = null;
(e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);
},
});
@@ -101,12 +114,12 @@ export function SlotDaySelector({ days, fromIndex, toIndex, onFromChange, onToCh
<div
className="map-pass-selector__thumb"
style={{ left: `${fromPct}%` }}
{...makeHandlers(true)}
{...makeHandlers("from")}
/>
<div
className="map-pass-selector__thumb"
style={{ left: `${toPct}%` }}
{...makeHandlers(false)}
{...makeHandlers("to")}
/>
</div>
</div>