UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

128 lines 5.01 kB
import { clamp } from "../../utils/clamp.js"; import { getPushedThumbValues } from "./getPushedThumbValues.js"; export function resolveThumbCollision({ behavior, values, currentValues, initialValues, pressedIndex, nextValue, min, max, step, minStepsBetweenValues }) { const activeValues = currentValues ?? values; const baselineValues = initialValues ?? values; const range = activeValues.length > 1; if (!range) { return { value: nextValue, thumbIndex: 0, didSwap: false }; } const minValueDifference = step * minStepsBetweenValues; switch (behavior) { case 'swap': { const pressedInitialValue = activeValues[pressedIndex]; const epsilon = 1e-7; const candidateValues = activeValues.slice(); const previousNeighbor = candidateValues[pressedIndex - 1]; const nextNeighbor = candidateValues[pressedIndex + 1]; const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min; const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max; const constrainedValue = clamp(nextValue, lowerBound, upperBound); const pressedValueAfterClamp = Number(constrainedValue.toFixed(12)); candidateValues[pressedIndex] = pressedValueAfterClamp; const movingForward = nextValue > pressedInitialValue; const movingBackward = nextValue < pressedInitialValue; const shouldSwapForward = movingForward && nextNeighbor != null && nextValue >= nextNeighbor - epsilon; const shouldSwapBackward = movingBackward && previousNeighbor != null && nextValue <= previousNeighbor + epsilon; if (!shouldSwapForward && !shouldSwapBackward) { return { value: candidateValues, thumbIndex: pressedIndex, didSwap: false }; } const targetIndex = shouldSwapForward ? pressedIndex + 1 : pressedIndex - 1; const initialValuesForPush = candidateValues.map((_, index) => { if (index === pressedIndex) { return pressedValueAfterClamp; } const baseline = baselineValues[index]; if (baseline != null) { return baseline; } return activeValues[index]; }); let nextValueForTarget = nextValue; if (shouldSwapForward) { nextValueForTarget = Math.max(nextValue, candidateValues[targetIndex]); } else { nextValueForTarget = Math.min(nextValue, candidateValues[targetIndex]); } const adjustedValues = getPushedThumbValues({ values: candidateValues, index: targetIndex, nextValue: nextValueForTarget, min, max, step, minStepsBetweenValues, initialValues: initialValuesForPush }); const neighborIndex = shouldSwapForward ? targetIndex - 1 : targetIndex + 1; if (neighborIndex >= 0 && neighborIndex < adjustedValues.length) { const previousValue = adjustedValues[neighborIndex - 1]; const nextValueAfter = adjustedValues[neighborIndex + 1]; let neighborLowerBound = previousValue != null ? previousValue + minValueDifference : min; neighborLowerBound = Math.max(neighborLowerBound, min + neighborIndex * minValueDifference); let neighborUpperBound = nextValueAfter != null ? nextValueAfter - minValueDifference : max; neighborUpperBound = Math.min(neighborUpperBound, max - (adjustedValues.length - 1 - neighborIndex) * minValueDifference); const restoredValue = clamp(pressedValueAfterClamp, neighborLowerBound, neighborUpperBound); adjustedValues[neighborIndex] = Number(restoredValue.toFixed(12)); } return { value: adjustedValues, thumbIndex: targetIndex, didSwap: true }; } case 'push': { const nextValues = getPushedThumbValues({ values: activeValues, index: pressedIndex, nextValue, min, max, step, minStepsBetweenValues }); return { value: nextValues, thumbIndex: pressedIndex, didSwap: false }; } case 'none': default: { const candidateValues = activeValues.slice(); const previousNeighbor = candidateValues[pressedIndex - 1]; const nextNeighbor = candidateValues[pressedIndex + 1]; const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min; const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max; const constrainedValue = clamp(nextValue, lowerBound, upperBound); candidateValues[pressedIndex] = Number(constrainedValue.toFixed(12)); return { value: candidateValues, thumbIndex: pressedIndex, didSwap: false }; } } }