@base-ui/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.
97 lines (95 loc) • 4.39 kB
JavaScript
import { clamp } from "../../internals/clamp.mjs";
import { getPushedThumbValues } from "./getPushedThumbValues.mjs";
/**
* Positional arguments are deliberate: property names of an options object don't
* minify, so passing them positionally keeps this internal helper smaller in the bundle.
*/
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;
// `push` does its own copy/bounds/rounding pass in `getPushedThumbValues`, so it must not
// pay for the neighbor-clamp setup below (this is the hottest path — `push` is the default).
if (behavior === 'push') {
return {
value: getPushedThumbValues(activeValues, pressedIndex, nextValue, min, max, step, minStepsBetweenValues),
thumbIndex: pressedIndex,
didSwap: false
};
}
// Shared by `swap` and `none`.
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 pressedValueAfterClamp = Number(clamp(nextValue, lowerBound, upperBound).toFixed(12));
candidateValues[pressedIndex] = pressedValueAfterClamp;
switch (behavior) {
case 'swap':
{
const pressedInitialValue = activeValues[pressedIndex];
const epsilon = 1e-7;
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(candidateValues, targetIndex, nextValueForTarget, min, max, step, minStepsBetweenValues, initialValuesForPush);
const neighborIndex = shouldSwapForward ? targetIndex - 1 : targetIndex + 1;
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 'none':
default:
{
return {
value: candidateValues,
thumbIndex: pressedIndex,
didSwap: false
};
}
}
}