Skip to content

Commit

Permalink
Merge branch 'agilgur5-master'
Browse files Browse the repository at this point in the history
* agilgur5-master:
  Flesh out test for horizontal waypoints
  Add horizontal scrolling
  • Loading branch information
trotzig committed Nov 17, 2016
2 parents 1943c9a + 8434f95 commit 74dbd00
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 7 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ below) has changed.
*/
onPositionChange: PropTypes.func,

/**
* Whether to activate on horizontal scrolling instead of vertical
*/
horizontal: PropTypes.bool,

/**
* `topOffset` can either be a number, in which case its a distance from the
* top of the container in pixels, or a string value. Valid string values are
Expand Down Expand Up @@ -219,6 +224,15 @@ then the boundaries will be pushed inward, toward the center of the page. If
you specify a negative value for an offset, then the boundary will be pushed
outward from the center of the page.

#### Horizontal Scrolling

By default, waypoints listen to vertical scrolling. If you want to switch to
horizontal scrolling instead, use the `horizontal` prop. For simplicity's sake,
all other props and callbacks do not change. Instead, `topOffset` and
`bottomOffset` (among other directional variables) will mean the offset from
the left and the offset from the right, respectively, and work exactly as they
did before, just calculated in the horizontal direction.

#### Example Usage

Positive values of the offset props are useful when you have an element that
Expand Down
159 changes: 159 additions & 0 deletions spec/waypoint_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,162 @@ describe('<Waypoint>', function() {
});
});
});

// smoke tests for horizontal scrolling
const scrollNodeToHorizontal = function(node, scrollLeft) {
if (node === window) {
window.scroll(scrollLeft, 0);
} else {
node.scrollLeft = scrollLeft;
}
const event = document.createEvent('Event');
event.initEvent('scroll', false, false);
node.dispatchEvent(event);
};

describe('<Waypoint>', function() {
beforeEach(() => {
jasmine.clock().install();
document.body.style.margin = 'auto'; // should be no horizontal margin

this.props = {
onEnter: jasmine.createSpy('onEnter'),
onLeave: jasmine.createSpy('onLeave'),
horizontal: true
};

this.margin = 10;
this.parentWidth = 100;

this.parentStyle = {
height: 100,
overflow: 'auto',
whiteSpace: 'nowrap',
width: this.parentWidth,
margin: this.margin, //Normalize the space left of the viewport.
};

this.leftSpacerWidth = 0;
this.rightSpacerWidth = 0;

this.subject = () => {
const el = renderAttached(
React.createElement('div', { style: this.parentStyle },
React.createElement('div', {
style: {
width: this.leftSpacerWidth,
display: 'inline-block'
}
}),
React.createElement(Waypoint, this.props),
React.createElement('div', {
style: {
width: this.rightSpacerWidth,
display: 'inline-block'
}
})
)
);

jasmine.clock().tick(1);
return el;
};
});

afterEach(() => {
if (div) {
ReactDOM.unmountComponentAtNode(div);
}
scrollNodeToHorizontal(window, 0);
jasmine.clock().uninstall();
});

describe('when a div is the scrollable ancestor', () => {
describe('when the Waypoint is visible on mount', () => {
beforeEach(() => {
this.subject();
});

it('calls the onEnter handler', () => {
expect(this.props.onEnter).toHaveBeenCalledWith({
currentPosition: Waypoint.inside,
previousPosition: undefined,
event: null,
waypointTop: this.margin + this.leftSpacerWidth,
viewportTop: this.margin,
viewportBottom: this.margin + this.parentWidth,
});
});
});

describe('when the Waypoint is not visible on mount', () => {
beforeEach(() => {
this.leftSpacerWidth = 300;
this.subject();
});

it('does not call the onEnter handler', () => {
expect(this.props.onEnter).not.toHaveBeenCalled();
});
});
});

describe('when the window is the scrollable ancestor', () => {
beforeEach(() => {
delete this.parentStyle.overflow;
delete this.parentStyle.width;
});

describe('when the Waypoint is visible on mount', () => {
beforeEach(() => {
this.subject();
});

it('calls the onEnter handler', () => {
expect(this.props.onEnter).toHaveBeenCalled();
});
});

describe('when the Waypoint is not visible on mount', () => {
beforeEach(() => {
this.leftSpacerWidth = window.innerWidth * 2;
this.subject();
});

it('does not call the onEnter handler', () => {
expect(this.props.onEnter).not.toHaveBeenCalled();
});

describe('when scrolled sideways to make the waypoint visible', () => {
beforeEach(() => {
scrollNodeToHorizontal(window, window.innerWidth + 100);
});

it('calls the onEnter handler', () => {
expect(this.props.onEnter).toHaveBeenCalled();
});

it('does not call the onLeave handler', () => {
expect(this.props.onLeave).not.toHaveBeenCalled();
});

describe('when scrolled back to initial position', () => {
/* eslint-disable max-nested-callbacks */
beforeEach(() => {
this.props.onEnter.calls.reset();
scrollNodeToHorizontal(window, 0);
});

it('does not call the onEnter handler', () => {
expect(this.props.onEnter).not.toHaveBeenCalled();
});

it('calls the onLeave handler', () => {
expect(this.props.onLeave).toHaveBeenCalled();
});
/* eslint-enable max-nested-callbacks */
});
});
});
});
});
23 changes: 16 additions & 7 deletions src/waypoint.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const POSITIONS = {
const defaultProps = {
topOffset: '0px',
bottomOffset: '0px',
horizontal: false,
onEnter() {},
onLeave() {},
onPositionChange() {},
Expand Down Expand Up @@ -207,10 +208,12 @@ export default class Waypoint extends React.Component {
}

const style = window.getComputedStyle(node);
const overflowY = style.getPropertyValue('overflow-y') ||
style.getPropertyValue('overflow');
const overflowDirec = this.props.horizontal ?
style.getPropertyValue('overflow-x') :
style.getPropertyValue('overflow-y');
const overflow = overflowDirec || style.getPropertyValue('overflow');

if (overflowY === 'auto' || overflowY === 'scroll') {
if (overflow === 'auto' || overflow === 'scroll') {
return node;
}
}
Expand Down Expand Up @@ -292,16 +295,21 @@ export default class Waypoint extends React.Component {
}

_getBounds() {
const waypointTop = this._ref.getBoundingClientRect().top;
const horizontal = this.props.horizontal;
const waypointTop = horizontal ? this._ref.getBoundingClientRect().left :
this._ref.getBoundingClientRect().top;

let contextHeight;
let contextScrollTop;
if (this.scrollableAncestor === window) {
contextHeight = window.innerHeight;
contextHeight = horizontal ? window.innerWidth : window.innerHeight;
contextScrollTop = 0;
} else {
contextHeight = this.scrollableAncestor.offsetHeight;
contextScrollTop = this.scrollableAncestor.getBoundingClientRect().top;
contextHeight = horizontal ? this.scrollableAncestor.offsetWidth :
this.scrollableAncestor.offsetHeight;
contextScrollTop = horizontal ?
this.scrollableAncestor.getBoundingClientRect().left :
this.scrollableAncestor.getBoundingClientRect().top;
}

if (this.props.debug) {
Expand Down Expand Up @@ -340,6 +348,7 @@ Waypoint.propTypes = {
fireOnRapidScroll: PropTypes.bool,
scrollableAncestor: PropTypes.any,
throttleHandler: PropTypes.func,
horizontal: PropTypes.bool,

// `topOffset` can either be a number, in which case its a distance from the
// top of the container in pixels, or a string value. Valid string values are
Expand Down

0 comments on commit 74dbd00

Please sign in to comment.