Skip to content

Commit

Permalink
Fix ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Feb 4, 2025
1 parent c419c5e commit 6599a23
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 82 deletions.
26 changes: 15 additions & 11 deletions packages/react/src/slider/root/useSliderRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ function findClosest(values: readonly number[], currentValue: number) {
return closestIndex;
}

function valueArrayToPercentages(values: number[], min: number, max: number) {
const output = [];
for (let i = 0; i < values.length; i += 1) {
output.push(valueToPercent(values[i], min, max));
}
return output;
}

export function focusThumb(
thumbIndex: number,
sliderRef: React.RefObject<HTMLElement | null>,
Expand Down Expand Up @@ -193,11 +201,7 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo
}, [max, min, range, valueUnwrapped]);

function initializePercentageValues() {
const vals = [];
for (let i = 0; i < values.length; i += 1) {
vals.push(valueToPercent(values[i], min, max));
}
return vals;
return valueArrayToPercentages(values, min, max);
}

const [percentageValues, setPercentageValues] = React.useState<readonly number[]>(
Expand Down Expand Up @@ -237,9 +241,9 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo
// for pointer drag only
const commitValue = useEventCallback((value: number | readonly number[], event: Event) => {
if (Array.isArray(value)) {
const newPercentageValues = [];
for (let i = 0; i < value.length; i += 1) {
newPercentageValues.push(valueToPercent(value[i], min, max));
const newPercentageValues = valueArrayToPercentages(value, min, max);
if (!areArraysEqual(newPercentageValues, percentageValues)) {
setPercentageValues(newPercentageValues);
}
} else if (typeof value === 'number') {
setPercentageValues([valueToPercent(value, min, max)]);
Expand Down Expand Up @@ -373,9 +377,9 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo
setPercentageValues([newPercentageValue]);
}
} else if (Array.isArray(valueUnwrapped)) {
const newPercentageValues = [];
for (let i = 0; i < valueUnwrapped.length; i += 1) {
newPercentageValues.push(valueToPercent(valueUnwrapped[i], min, max));
const newPercentageValues = valueArrayToPercentages(valueUnwrapped, min, max);
if (!areArraysEqual(newPercentageValues, percentageValues)) {
setPercentageValues(newPercentageValues);
}
}
}, [dragging, min, max, percentageValues, setPercentageValues, valueProp, valueUnwrapped]);
Expand Down
248 changes: 177 additions & 71 deletions packages/react/src/slider/thumb/SliderThumb.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,87 +95,193 @@ describe('<Slider.Thumb />', () => {
}));

describe.skipIf(isJSDOM)('positioning styles', () => {
it('positions the thumb when dragged', async () => {
const { getByTestId } = await render(
<Slider.Root
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '1000px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>,
);
describe('positions the thumb when dragged', () => {
it('single thumb', async () => {
const { getByTestId } = await render(
<Slider.Root
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '1000px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>,
);

const sliderControl = getByTestId('control');
const sliderControl = getByTestId('control');

const thumbStyles = getComputedStyle(getByTestId('thumb'));
const thumbStyles = getComputedStyle(getByTestId('thumb'));

stub(sliderControl, 'getBoundingClientRect').callsFake(
() => GETBOUNDINGCLIENTRECT_HORIZONTAL_SLIDER_RETURN_VAL,
);
stub(sliderControl, 'getBoundingClientRect').callsFake(
() => GETBOUNDINGCLIENTRECT_HORIZONTAL_SLIDER_RETURN_VAL,
);

fireEvent.touchStart(
sliderControl,
createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);
fireEvent.touchStart(
sliderControl,
createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);

fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),
);

expect(thumbStyles.getPropertyValue('left')).to.equal('199px');
fireEvent.touchEnd(document.body, createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]));
expect(thumbStyles.getPropertyValue('left')).to.equal('200px');
});
expect(thumbStyles.getPropertyValue('left')).to.equal('199px');
fireEvent.touchEnd(
document.body,
createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),
);
expect(thumbStyles.getPropertyValue('left')).to.equal('200px');
});

it('multiple thumbs', async () => {
const { getByTestId, getAllByTestId } = await render(
<Slider.Root
defaultValue={[20, 40]}
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '1000px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>,
);

it('positions the thumb when the controlled value changes externally', async () => {
function App() {
const [val, setVal] = React.useState(20);
return (
<React.Fragment>
<button onClick={() => setVal(55)} />
<Slider.Root
value={val}
onValueChange={(newVal) => setVal(newVal as number)}
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '100px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>
</React.Fragment>
const sliderControl = getByTestId('control');

const computedStyles = {
thumb1: getComputedStyle(getAllByTestId('thumb')[0]),
thumb2: getComputedStyle(getAllByTestId('thumb')[1]),
};

stub(sliderControl, 'getBoundingClientRect').callsFake(
() => GETBOUNDINGCLIENTRECT_HORIZONTAL_SLIDER_RETURN_VAL,
);

fireEvent.touchStart(
sliderControl,
createTouches([{ identifier: 1, clientX: 400, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),
);
fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),
);

fireEvent.touchMove(
document.body,
createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),
);
}
const { getByTestId, getByRole } = await render(<App />);

const thumbStyles = getComputedStyle(getByTestId('thumb'));
expect(thumbStyles.getPropertyValue('left')).to.equal('20px');
expect(computedStyles.thumb2.getPropertyValue('left')).to.equal('699px');
fireEvent.touchEnd(
document.body,
createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),
);
expect(computedStyles.thumb1.getPropertyValue('left')).to.equal('200px');
expect(computedStyles.thumb2.getPropertyValue('left')).to.equal('700px');
});
});

describe('positions the thumb when the controlled value changes externally', () => {
it('single thumb', async () => {
function App() {
const [val, setVal] = React.useState(20);
return (
<React.Fragment>
<button onClick={() => setVal(55)} />
<Slider.Root
value={val}
onValueChange={(newVal) => setVal(newVal as number)}
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '100px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>
</React.Fragment>
);
}
const { getByTestId, getByRole } = await render(<App />);

const thumbStyles = getComputedStyle(getByTestId('thumb'));
expect(thumbStyles.getPropertyValue('left')).to.equal('20px');

fireEvent.click(getByRole('button'));
expect(thumbStyles.getPropertyValue('left')).to.equal('55px');
});

it('multiple thumbs', async () => {
function App() {
const [val, setVal] = React.useState([20, 50]);
return (
<React.Fragment>
<button onClick={() => setVal([33, 72])} />
<Slider.Root
value={val}
onValueChange={(newVal) => setVal(newVal as number[])}
style={{
// browser tests render with 1024px width by default
// this is just to make the values asserted more readable
width: '100px',
}}
>
<Slider.Control data-testid="control">
<Slider.Track>
<Slider.Indicator />
<Slider.Thumb data-testid="thumb" />
<Slider.Thumb data-testid="thumb" />
</Slider.Track>
</Slider.Control>
</Slider.Root>
</React.Fragment>
);
}
const { getAllByTestId, getByRole } = await render(<App />);

const computedStyles = {
thumb1: getComputedStyle(getAllByTestId('thumb')[0]),
thumb2: getComputedStyle(getAllByTestId('thumb')[1]),
};

expect(computedStyles.thumb1.getPropertyValue('left')).to.equal('20px');
expect(computedStyles.thumb2.getPropertyValue('left')).to.equal('50px');

fireEvent.click(getByRole('button'));
expect(thumbStyles.getPropertyValue('left')).to.equal('55px');
fireEvent.click(getByRole('button'));
expect(computedStyles.thumb1.getPropertyValue('left')).to.equal('33px');
expect(computedStyles.thumb2.getPropertyValue('left')).to.equal('72px');
});
});
});
});

0 comments on commit 6599a23

Please sign in to comment.