Skip to content

Adds support for pointer events to useDrag1D #1069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ governing permissions and limitations under the License.
width: var(--spectrum-rail-gripper-width);
height: var(--spectrum-rail-gripper-height);
border-width: var(--spectrum-rail-gripper-border-width-vertical) var(--spectrum-rail-gripper-border-width-horizontal);
touch-action: none;
}

.spectrum-SplitView-splitter {
Expand All @@ -51,6 +52,7 @@ governing permissions and limitations under the License.

/* Prevent text selection while dragging */
user-select: none;
touch-action: none;

width: var(--spectrum-rail-handle-width);
height: 100%;
Expand Down
9 changes: 5 additions & 4 deletions packages/@react-aria/slider/src/useSlider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function useSlider(
// It is set onMouseDown; see trackProps below.
const realTimeTrackDraggingIndex = useRef<number | undefined>(undefined);
const isTrackDragging = useRef(false);
const {onMouseDown, onMouseEnter, onMouseOut} = useDrag1D({
const draggableProps = useDrag1D({
containerRef: trackRef as any,
reverse: false,
orientation: 'horizontal',
Expand All @@ -82,6 +82,7 @@ export function useSlider(
}
});

let downHandlerName = draggableProps.onMouseDown ? 'onMouseDown' : 'onPointerDown';
return {
labelProps,

Expand All @@ -93,7 +94,7 @@ export function useSlider(
...fieldProps
},
trackProps: mergeProps({
onMouseDown: (e: React.MouseEvent<HTMLElement>) => {
[ downHandlerName ]: (e: { clientX: number, clientY: number, preventDefault: () => void }) => {
// We only trigger track-dragging if the user clicks on the track itself.
if (trackRef.current && isSliderEditable) {
// Find the closest thumb
Expand All @@ -118,7 +119,7 @@ export function useSlider(
// is updated while you're still holding the mouse button down. And we
// set dragging on now, so that onChangeEnd() won't fire yet when we set
// the value. Dragging state will be reset to false in onDrag above, even
// if no dragging actually occurs.
// if no dragging actually occurs.
state.setThumbDragging(realTimeTrackDraggingIndex.current, true);
state.setThumbValue(index, value);
} else {
Expand All @@ -127,7 +128,7 @@ export function useSlider(
}
}
}, {
onMouseDown, onMouseEnter, onMouseOut
...draggableProps
})
};
}
6 changes: 1 addition & 5 deletions packages/@react-aria/slider/src/useSliderThumb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,7 @@ export function useSliderThumb(
state.setThumbValue(index, parseFloat(e.target.value));
}
}),
thumbProps: isEditable ? mergeProps({
onMouseDown: draggableProps.onMouseDown,
onMouseEnter: draggableProps.onMouseEnter,
onMouseOut: draggableProps.onMouseOut
}, {
thumbProps: isEditable ? mergeProps(draggableProps, {
onMouseDown: focusInput
}) : {},
labelProps
Expand Down
1 change: 1 addition & 0 deletions packages/@react-aria/slider/stories/story-slider.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
display: flex;
flex-direction: column;
align-items: center;
touch-action: none;
}

.thumbHandle {
Expand Down
17 changes: 17 additions & 0 deletions packages/@react-aria/slider/test/PointerEventFake.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
This exists because JSDOM's PointerEvent doesn't work right.
see: https://github.com/jsdom/jsdom/pull/2666.
and https://github.com/testing-library/dom-testing-library/issues/558.
*/

const pointerEventCtorProps = ['clientX', 'clientY', 'pointerType'];
export default class PointerEventFake extends Event {
constructor(type, props = {}) {
super(type, props);
pointerEventCtorProps.forEach((prop) => {
if (props[prop] != null) {
this[prop] = props[prop];
}
});
}
}
101 changes: 101 additions & 0 deletions packages/@react-aria/slider/test/useSlider.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {fireEvent, render, screen} from '@testing-library/react';
import PointerEventFake from './PointerEventFake.js';
import * as React from 'react';
import {renderHook} from '@testing-library/react-hooks';
import {useRef} from 'react';
Expand Down Expand Up @@ -131,4 +132,104 @@ describe('useSlider', () => {
expect(stateRef.current.values).toEqual([10, 80]);
});
});

describe('interactions on track using pointerEvents', () => {
let widthStub;
let origPointerEvent;
beforeAll(() => {
origPointerEvent = window.PointerEvent;
window.PointerEvent = PointerEventFake;
widthStub = jest.spyOn(window.HTMLElement.prototype, 'offsetWidth', 'get').mockImplementation(() => 100);
});
afterAll(() => {
delete window.PointerEvent;
if (origPointerEvent) {
window.PointerEvent = origPointerEvent;
}
widthStub.mockReset();
});

let stateRef = React.createRef();

function Example(props) {
let trackRef = useRef(null);
let state = useSliderState(props);
stateRef.current = state;
let {trackProps} = useSlider(props, state, trackRef);
return <div data-testid="track" ref={trackRef} {...trackProps} />;
}

it('should allow you to set value of closest thumb by clicking on track', () => {
let onChangeSpy = jest.fn();
let onChangeEndSpy = jest.fn();
render(<Example onChange={onChangeSpy} onChangeEnd={onChangeEndSpy} aria-label="Slider" defaultValue={[10, 80]} />);

let track = screen.getByTestId('track');
fireEvent.pointerDown(track, {clientX: 20});
fireEvent.pointerUp(track, {clientX: 20});

expect(onChangeSpy).toHaveBeenLastCalledWith([20, 80]);
expect(onChangeEndSpy).toHaveBeenLastCalledWith([20, 80]);
expect(stateRef.current.values).toEqual([20, 80]);

track = screen.getByTestId('track');
fireEvent.pointerDown(track, {clientX: 90});
fireEvent.pointerUp(track, {clientX: 90});

expect(onChangeSpy).toHaveBeenLastCalledWith([20, 90]);
expect(onChangeEndSpy).toHaveBeenLastCalledWith([20, 90]);
expect(stateRef.current.values).toEqual([20, 90]);
});

it('should allow you to set value of closest thumb by dragging on track', () => {
let onChangeSpy = jest.fn();
let onChangeEndSpy = jest.fn();

render(<Example onChange={onChangeSpy} onChangeEnd={onChangeEndSpy} aria-label="Slider" defaultValue={[10, 80]} />);

let track = screen.getByTestId('track');
fireEvent.pointerDown(track, {clientX: 20});
expect(onChangeSpy).toHaveBeenLastCalledWith([20, 80]);
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([20, 80]);

fireEvent.pointerMove(track, {clientX: 30});
expect(onChangeSpy).toHaveBeenLastCalledWith([30, 80]);
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([30, 80]);

fireEvent.pointerMove(track, {clientX: 40});
expect(onChangeSpy).toHaveBeenLastCalledWith([40, 80]);
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([40, 80]);

fireEvent.pointerUp(track, {clientX: 40});
expect(onChangeEndSpy).toHaveBeenLastCalledWith([40, 80]);
expect(stateRef.current.values).toEqual([40, 80]);
});

it('should not allow you to set value if disabled', () => {
let onChangeSpy = jest.fn();
let onChangeEndSpy = jest.fn();
render(<Example onChange={onChangeSpy} onChangeEnd={onChangeEndSpy} aria-label="Slider" defaultValue={[10, 80]} isDisabled />);

let track = screen.getByTestId('track');
fireEvent.pointerDown(track, {clientX: 20});
expect(onChangeSpy).not.toHaveBeenCalled();
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([10, 80]);

fireEvent.pointerMove(track, {clientX: 30});
expect(onChangeSpy).not.toHaveBeenCalled();
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([10, 80]);

fireEvent.pointerUp(track, {clientX: 40});
expect(onChangeSpy).not.toHaveBeenCalled();
expect(onChangeEndSpy).not.toHaveBeenCalled();
expect(stateRef.current.values).toEqual([10, 80]);
});
});

});

Loading