(ev: PointerEvent)
| 172 | } |
| 173 | |
| 174 | const onMove = (ev: PointerEvent): void => { |
| 175 | if (disposed) return; |
| 176 | if (ev.pointerId !== e.pointerId) return; |
| 177 | |
| 178 | ev.preventDefault(); |
| 179 | |
| 180 | const dxPercent = pxToPercent(ev.clientX - dragStartX); |
| 181 | if (dxPercent === null) return; |
| 182 | |
| 183 | switch (mode) { |
| 184 | case 'left-handle': { |
| 185 | // UX: don't allow handle crossing; clamp left <= current end. |
| 186 | const nextStart = Math.min(startRange.end, startRange.start + dxPercent); |
| 187 | const anchored = zoomState as unknown as Partial<{ |
| 188 | setRangeAnchored: (start: number, end: number, anchor: 'start' | 'end' | 'center') => void; |
| 189 | }>; |
| 190 | if (anchored.setRangeAnchored) { |
| 191 | // When clamped by minSpan/maxSpan, keep the right edge anchored (prevents jumpiness). |
| 192 | anchored.setRangeAnchored(nextStart, startRange.end, 'end'); |
| 193 | } else { |
| 194 | zoomState.setRange(nextStart, startRange.end); |
| 195 | } |
| 196 | return; |
| 197 | } |
| 198 | case 'right-handle': { |
| 199 | // UX: don't allow handle crossing; clamp right >= current start. |
| 200 | const nextEnd = Math.max(startRange.start, startRange.end + dxPercent); |
| 201 | const anchored = zoomState as unknown as Partial<{ |
| 202 | setRangeAnchored: (start: number, end: number, anchor: 'start' | 'end' | 'center') => void; |
| 203 | }>; |
| 204 | if (anchored.setRangeAnchored) { |
| 205 | // When clamped by minSpan/maxSpan, keep the left edge anchored (prevents jumpiness). |
| 206 | anchored.setRangeAnchored(startRange.start, nextEnd, 'start'); |
| 207 | } else { |
| 208 | zoomState.setRange(startRange.start, nextEnd); |
| 209 | } |
| 210 | return; |
| 211 | } |
| 212 | case 'pan-window': { |
| 213 | zoomState.setRange(startRange.start + dxPercent, startRange.end + dxPercent); |
| 214 | return; |
| 215 | } |
| 216 | } |
| 217 | }; |
| 218 | |
| 219 | let cleanedUp = false; |
| 220 |
nothing calls this directly
no test coverage detected