Skip to content

Commit

Permalink
Allow touch scroll to be default prevented by making the handlers pas…
Browse files Browse the repository at this point in the history
…sive (#423)

* Allow touch scroll to be default prevented by making the handlers passive.
* Added flag 'stopScrollDefaultHandling' to control the bubbling of the event to the browser default handler.
* `stopScrollDefaultHandling` and `stopScrollPropagation` cannot be functions. They can be boolean/undefined.
  • Loading branch information
pradeepnschrodinger authored Apr 4, 2019
1 parent 5dec2f9 commit 6da172b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 104 deletions.
93 changes: 42 additions & 51 deletions examples/TouchScrollExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,58 +20,49 @@ class TouchScrollExample extends React.Component {
render() {
const { dataList } = this.state;

// Recent browser versions are making touch events passive by default. Unfortunately, React doesn't allow us
// to specify the event handlers as passive/active (see #6436 on facebook/react). This can lead to unneeded
// scrolling of parent containers of FDT. This style is a work around to fix this. By applying 'none' to
// touch-action, we are disabling touch events from propagating.
const tableParentStyle = {
'touch-action': 'none'
};

return (
<div style={tableParentStyle}>
<Table
rowHeight={50}
rowsCount={dataList.getSize()}
headerHeight={50}
touchScrollEnabled={true}
width={1000}
height={500}
{...this.props}>
<Column
columnKey="firstName"
header={<Cell>First Name</Cell>}
cell={<TextCell data={dataList} />}
fixed={true}
width={100}
/>
<Column
columnKey="lastName"
header={<Cell>Last Name</Cell>}
cell={<TextCell data={dataList} />}
fixed={true}
width={100}
/>
<Column
columnKey="city"
header={<Cell>City</Cell>}
cell={<TextCell data={dataList} />}
width={100}
/>
<Column
columnKey="street"
header={<Cell>Street</Cell>}
cell={<TextCell data={dataList} />}
width={200}
/>
<Column
columnKey="zipCode"
header={<Cell>Zip Code</Cell>}
cell={<TextCell data={dataList} />}
width={200}
/>
</Table>
</div>
<Table
rowHeight={50}
rowsCount={dataList.getSize()}
headerHeight={50}
touchScrollEnabled={true}
width={1000}
height={500}
{...this.props}
>
<Column
columnKey="firstName"
header={<Cell>First Name</Cell>}
cell={<TextCell data={dataList} />}
fixed={true}
width={100}
/>
<Column
columnKey="lastName"
header={<Cell>Last Name</Cell>}
cell={<TextCell data={dataList} />}
fixed={true}
width={100}
/>
<Column
columnKey="city"
header={<Cell>City</Cell>}
cell={<TextCell data={dataList} />}
width={100}
/>
<Column
columnKey="street"
header={<Cell>Street</Cell>}
cell={<TextCell data={dataList} />}
width={200}
/>
<Column
columnKey="zipCode"
header={<Cell>Zip Code</Cell>}
cell={<TextCell data={dataList} />}
width={200}
/>
</Table>
);
}
}
Expand Down
23 changes: 22 additions & 1 deletion src/FixedDataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,14 @@ class FixedDataTable extends React.Component {
*/
stopReactWheelPropagation: PropTypes.bool,

/**
* If enabled scroll events will never be bubbled to the browser default handler.
* If disabled (default when unspecified), scroll events will be bubbled up if the scroll
* doesn't lead to a change in scroll offsets, which is preferable if you like
* the page/container to scroll up when the table is already scrolled up max.
*/
stopScrollDefaultHandling: PropTypes.bool,

/**
* If enabled scroll events will not be propagated outside of the table.
*/
Expand Down Expand Up @@ -444,23 +452,32 @@ class FixedDataTable extends React.Component {
this._onScroll,
this._shouldHandleWheelX,
this._shouldHandleWheelY,
this.props.stopScrollDefaultHandling,
this.props.stopScrollPropagation
);

this._touchHandler = new ReactTouchHandler(
this._onScroll,
this._shouldHandleTouchX,
this._shouldHandleTouchY,
this.props.stopScrollDefaultHandling,
this.props.stopScrollPropagation
);
}

componentWillUnmount() {
// TODO (pradeep): Remove these and pass to our table component directly after
// React provides an API where event handlers can be specified to be non-passive (facebook/react#6436)
this._divRef && this._divRef.removeEventListener(
'wheel',
this._wheelHandler.onWheel,
{ passive: false }
);
this._divRef && this._divRef.removeEventListener(
'touchmove',
this._touchHandler.onTouchMove,
{ passive: false }
);
this._wheelHandler = null;
this._touchHandler = null;

Expand Down Expand Up @@ -576,6 +593,11 @@ class FixedDataTable extends React.Component {
this._wheelHandler.onWheel,
{ passive: false }
);
this._divRef && this._divRef.addEventListener(
'touchmove',
this._touchHandler.onTouchMove,
{ passive: false }
);
this._reportContentHeight();
}

Expand Down Expand Up @@ -798,7 +820,6 @@ class FixedDataTable extends React.Component {
onKeyDown={this._onKeyDown}
onTouchStart={this._touchHandler.onTouchStart}
onTouchEnd={this._touchHandler.onTouchEnd}
onTouchMove={this._touchHandler.onTouchMove}
onTouchCancel={this._touchHandler.onTouchCancel}
ref={this._onRef}
style={{
Expand Down
33 changes: 20 additions & 13 deletions src/ReactTouchHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class ReactTouchHandler {
/*function*/ onTouchScroll,
/*boolean|function*/ handleScrollX,
/*boolean|function*/ handleScrollY,
/*?boolean|?function*/ stopPropagation
/*?boolean*/ preventDefault,
/*?boolean*/ stopPropagation
) {

// The animation frame id for the drag scroll
Expand Down Expand Up @@ -78,15 +79,9 @@ class ReactTouchHandler {
emptyFunction.thatReturnsFalse;
}

// TODO (jordan) Is configuring this necessary
if (typeof stopPropagation !== 'function') {
stopPropagation = stopPropagation ?
emptyFunction.thatReturnsTrue :
emptyFunction.thatReturnsFalse;
}

this._handleScrollX = handleScrollX;
this._handleScrollY = handleScrollY;
this._preventDefault = preventDefault;
this._stopPropagation = stopPropagation;
this._onTouchScrollCallback = onTouchScroll;

Expand All @@ -101,6 +96,9 @@ class ReactTouchHandler {
}

onTouchStart(/*object*/ event) {
if (this._preventDefault) {
event.preventDefault();
}

// Start tracking drag delta for scrolling
this._lastTouchX = event.touches[0].pageX;
Expand All @@ -117,12 +115,15 @@ class ReactTouchHandler {
clearInterval(this._trackerId);
this._trackerId = setInterval(this._track, TRACKER_TIMEOUT);

if (this._stopPropagation()) {
if (this._stopPropagation) {
event.stopPropagation();
}
}

onTouchEnd(/*object*/ event) {
if (this._preventDefault) {
event.preventDefault();
}

// Stop tracking velocity
clearInterval(this._trackerId);
Expand All @@ -131,7 +132,7 @@ class ReactTouchHandler {
// Initialize decelerating autoscroll on drag stop
requestAnimationFramePolyfill(this._startAutoScroll);

if (this._stopPropagation()) {
if (this._stopPropagation) {
event.stopPropagation();
}
}
Expand All @@ -142,12 +143,15 @@ class ReactTouchHandler {
clearInterval(this._trackerId);
this._trackerId = null;

if (this._stopPropagation()) {
if (this._stopPropagation) {
event.stopPropagation();
}
}

onTouchMove(/*object*/ event) {
if (this._preventDefault) {
event.preventDefault();
}

var moveX = event.touches[0].pageX;
var moveY = event.touches[0].pageY;
Expand Down Expand Up @@ -175,12 +179,15 @@ class ReactTouchHandler {
this._deltaY = 0;
}

event.preventDefault();
// The event will result in a scroll to the table, so there's no need to also let the parent containers scroll
if (!event.defaultPrevented) {
event.preventDefault();
}

// Ensure minimum delta magnitude is met to avoid jitter
var changed = false;
if (Math.abs(this._deltaX) > 2 || Math.abs(this._deltaY) > 2) {
if (this._stopPropagation()) {
if (this._stopPropagation) {
event.stopPropagation();
}
changed = true;
Expand Down
22 changes: 13 additions & 9 deletions src/vendor_upstream/dom/ReactWheelHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class ReactWheelHandler {
/*function*/ onWheel,
/*boolean|function*/ handleScrollX,
/*boolean|function*/ handleScrollY,
/*?boolean|?function*/ stopPropagation
/*?boolean*/ preventDefault,
/*?boolean*/ stopPropagation
) {
this._animationFrameID = null;
this._deltaX = 0;
Expand All @@ -50,20 +51,19 @@ class ReactWheelHandler {
emptyFunction.thatReturnsFalse;
}

if (typeof stopPropagation !== 'function') {
stopPropagation = stopPropagation ?
emptyFunction.thatReturnsTrue :
emptyFunction.thatReturnsFalse;
}

this._handleScrollX = handleScrollX;
this._handleScrollY = handleScrollY;
this._preventDefault = preventDefault;
this._stopPropagation = stopPropagation;
this._onWheelCallback = onWheel;
this.onWheel = this.onWheel.bind(this);
}

onWheel(/*object*/ event) {
if (this._preventDefault) {
event.preventDefault();
}

var normalizedEvent = normalizeWheel(event);

// if shift is held, swap the axis of scrolling.
Expand All @@ -85,11 +85,15 @@ class ReactWheelHandler {

this._deltaX += handleScrollX ? normalizedEvent.pixelX : 0;
this._deltaY += handleScrollY ? normalizedEvent.pixelY : 0;
event.preventDefault();

// This will result in a scroll to the table, so there's no need to let the parent containers scroll
if (!event.defaultPrevented) {
event.preventDefault();
}

var changed;
if (this._deltaX !== 0 || this._deltaY !== 0) {
if (this._stopPropagation()) {
if (this._stopPropagation) {
event.stopPropagation();
}
changed = true;
Expand Down
Loading

0 comments on commit 6da172b

Please sign in to comment.