reheaded
Version:
[![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] [![downloads][downloads-badge]][npmcharts] [![version][version-badge]][package] [![MIT License][license-badge]][license]
548 lines (461 loc) • 16 kB
JavaScript
import { Component } from 'react';
import PropTypes from 'prop-types';
//
var shallowequal = function shallowEqual(objA, objB, compare, compareContext) {
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
if (ret !== void 0) {
return !!ret;
}
if (objA === objB) {
return true;
}
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
// Test for A's keys different from B.
for (var idx = 0; idx < keysA.length; idx++) {
var key = keysA[idx];
if (!bHasOwnProperty(key)) {
return false;
}
var valueA = objA[key];
var valueB = objB[key];
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
if (ret === false || (ret === void 0 && valueA !== valueB)) {
return false;
}
}
return true;
};
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var performanceNow = createCommonjsModule(function (module) {
// Generated by CoffeeScript 1.12.2
(function() {
var getNanoSeconds, hrtime, loadTime, moduleLoadTime, nodeLoadTime, upTime;
if ((typeof performance !== "undefined" && performance !== null) && performance.now) {
module.exports = function() {
return performance.now();
};
} else if ((typeof process !== "undefined" && process !== null) && process.hrtime) {
module.exports = function() {
return (getNanoSeconds() - nodeLoadTime) / 1e6;
};
hrtime = process.hrtime;
getNanoSeconds = function() {
var hr;
hr = hrtime();
return hr[0] * 1e9 + hr[1];
};
moduleLoadTime = getNanoSeconds();
upTime = process.uptime() * 1e9;
nodeLoadTime = moduleLoadTime - upTime;
} else if (Date.now) {
module.exports = function() {
return Date.now() - loadTime;
};
loadTime = Date.now();
} else {
module.exports = function() {
return new Date().getTime() - loadTime;
};
loadTime = new Date().getTime();
}
}).call(commonjsGlobal);
});
var root = typeof window === 'undefined' ? commonjsGlobal : window
, vendors = ['moz', 'webkit']
, suffix = 'AnimationFrame'
, raf = root['request' + suffix]
, caf = root['cancel' + suffix] || root['cancelRequest' + suffix];
for(var i = 0; !raf && i < vendors.length; i++) {
raf = root[vendors[i] + 'Request' + suffix];
caf = root[vendors[i] + 'Cancel' + suffix]
|| root[vendors[i] + 'CancelRequest' + suffix];
}
// Some versions of FF have rAF but not cAF
if(!raf || !caf) {
var last = 0
, id = 0
, queue = []
, frameDuration = 1000 / 60;
raf = function(callback) {
if(queue.length === 0) {
var _now = performanceNow()
, next = Math.max(0, frameDuration - (_now - last));
last = next + _now;
setTimeout(function() {
var cp = queue.slice(0);
// Clear queue here to prevent
// callbacks from appending listeners
// to the current frame's queue
queue.length = 0;
for(var i = 0; i < cp.length; i++) {
if(!cp[i].cancelled) {
try{
cp[i].callback(last);
} catch(e) {
setTimeout(function() { throw e }, 0);
}
}
}
}, Math.round(next));
}
queue.push({
handle: ++id,
callback: callback,
cancelled: false
});
return id
};
caf = function(handle) {
for(var i = 0; i < queue.length; i++) {
if(queue[i].handle === handle) {
queue[i].cancelled = true;
}
}
};
}
var raf_1 = function(fn) {
// Wrap in a new function to prevent
// `cancel` potentially being assigned
// to the native rAF function
return raf.call(root, fn)
};
var cancel = function() {
caf.apply(root, arguments);
};
var polyfill = function(object) {
if (!object) {
object = root;
}
object.requestAnimationFrame = raf;
object.cancelAnimationFrame = caf;
};
raf_1.cancel = cancel;
raf_1.polyfill = polyfill;
raf_1.polyfill();
function shouldUpdate () {
var lastKnownScrollY = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var currentScrollY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var props = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var state = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var height = arguments[4];
var scrollDirection = currentScrollY >= lastKnownScrollY ? 'down' : 'up';
var distanceScrolled = Math.abs(currentScrollY - lastKnownScrollY);
// We're disabled
if (props.disabled) {
return {
action: 'none',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
// We're at the top and not fixed yet.
};
} else if (currentScrollY <= props.pinStart && state.state !== 'unfixed') {
return {
action: 'unfix',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
// We're unfixed and headed down. Carry on.
};
} else if (currentScrollY <= height && scrollDirection === 'down' && state.state === 'unfixed') {
return {
action: 'none',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
// We're past the header and scrolling down.
// We transition to "unpinned" if necessary.
};
} else if (scrollDirection === 'down' && ['pinned', 'unfixed'].indexOf(state.state) >= 0 && currentScrollY > height + props.pinStart && distanceScrolled > props.downTolerance) {
return {
action: 'unpin',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
// We're scrolling up, we transition to "pinned"
};
} else if (scrollDirection === 'up' && distanceScrolled > props.upTolerance && ['pinned', 'unfixed'].indexOf(state.state) < 0) {
return {
action: 'pin',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
// We're scrolling up, and inside the header.
// We transition to pin regardless of upTolerance
};
} else if (scrollDirection === 'up' && currentScrollY <= height && ['pinned', 'unfixed'].indexOf(state.state) < 0) {
return {
action: 'pin',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
};
}
return {
action: 'none',
scrollDirection: scrollDirection,
distanceScrolled: distanceScrolled
};
}
function noop() {}
/**
* Takes an argument and if it's an array, returns the first item in the array
* otherwise returns the argument
* @param {*} arg the maybe-array
* @param {*} defaultValue the value if arg is falsey not defined
* @return {*} the arg or it's first item
*/
function unwrapArray(arg, defaultValue) {
var _arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */arg[0] : arg;
if (!_arg && defaultValue) {
return defaultValue;
} else {
return _arg;
}
}
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var Reheaded = function (_Component) {
inherits(Reheaded, _Component);
function Reheaded(props) {
classCallCheck(this, Reheaded);
// Class variables.
var _this = possibleConstructorReturn(this, _Component.call(this, props));
_this.setRef = function (ref) {
_this.inner = ref;
};
_this.setHeightOffset = function () {
_this.setState(function (state) {
return state.height === _this.inner.offsetHeight ? null : { height: _this.inner.offsetHeight };
});
_this.resizeTicking = false;
};
_this.getScrollY = function () {
var parent = _this.props.parent();
/* istanbul ignore else */
if (parent.pageYOffset !== undefined) {
return parent.pageYOffset;
} else if (parent.scrollTop !== undefined) {
return parent.scrollTop;
}
/* istanbul ignore next line */
return (document.documentElement || document.body.parentNode || document.body).scrollTop;
};
_this.getViewportHeight = function () {
return (
/* istanbul ignore next line */
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
);
};
_this.getDocumentHeight = /* istanbul ignore next */function () {
var _document = document,
body = _document.body,
documentElement = _document.documentElement;
return Math.max(body.scrollHeight, documentElement.scrollHeight, body.offsetHeight, documentElement.offsetHeight, body.clientHeight, documentElement.clientHeight);
};
_this.getElementPhysicalHeight = function (elm) {
return Math.max(elm.offsetHeight, elm.clientHeight);
};
_this.getElementHeight = function (elm) {
return Math.max(elm.scrollHeight, elm.offsetHeight, elm.clientHeight);
};
_this.getScrollerPhysicalHeight = function () {
var parent = _this.props.parent();
return parent === window || parent === document.body ? /* istanbul ignore next */_this.getViewportHeight() : _this.getElementPhysicalHeight(parent);
};
_this.getScrollerHeight = function () {
var parent = _this.props.parent();
return parent === window || parent === document.body ? /* istanbul ignore next */_this.getDocumentHeight() : _this.getElementHeight(parent);
};
_this.isOutOfBound = function (currentScrollY) {
var scrollerPhysicalHeight = _this.getScrollerPhysicalHeight();
var scrollerHeight = _this.getScrollerHeight();
return currentScrollY < 0 || currentScrollY + scrollerPhysicalHeight > scrollerHeight;
};
_this.handleScroll = function () {
/* istanbul ignore else */
if (!_this.scrollTicking) {
_this.scrollTicking = true;
window.requestAnimationFrame(_this.update);
}
};
_this.handleResize = /* istanbul ignore next */function () {
/* istanbul ignore else */
if (!_this.resizeTicking) {
_this.resizeTicking = true;
window.requestAnimationFrame(_this.setHeightOffset);
}
};
_this.setNewState = function (state) {
_this.setState(function (prevState) {
return {
state: state,
shouldAnimate: prevState.state !== 'unfixed'
};
});
};
_this.unpin = function () {
_this.props.onUnpin();
_this.setNewState('unpinned');
};
_this.pin = function () {
_this.props.onPin();
_this.setNewState('pinned');
};
_this.unfix = function () {
_this.props.onUnfix();
_this.setNewState('unfixed');
};
_this.update = function () {
_this.currentScrollY = _this.getScrollY();
/* istanbul ignore else */
if (!_this.isOutOfBound(_this.currentScrollY)) {
var _shouldUpdate = shouldUpdate(_this.lastKnownScrollY, _this.currentScrollY, _this.props, _this.state, _this.state.height),
action = _shouldUpdate.action;
/* istanbul ignore else */
if (action === 'pin') {
_this.pin();
} else if (action === 'unpin') {
_this.unpin();
} else if (action === 'unfix') {
_this.unfix();
}
}
_this.lastKnownScrollY = _this.currentScrollY;
_this.scrollTicking = false;
};
_this.currentScrollY = 0;
_this.lastKnownScrollY = 0;
_this.scrollTicking = false;
_this.resizeTicking = false;
_this.state = {
state: props.forcePin ? 'pinned' : 'unfixed',
shouldAnimate: false,
height: 0
};
return _this;
}
Reheaded.prototype.componentDidMount = function componentDidMount() {
var _props = this.props,
disabled = _props.disabled,
forcePin = _props.forcePin,
parentFn = _props.parent,
calcHeightOnResize = _props.calcHeightOnResize;
this.setHeightOffset();
var parent = parentFn();
if (!disabled) {
if (!forcePin) {
parent.addEventListener('scroll', this.handleScroll);
}
if (calcHeightOnResize) {
parent.addEventListener('resize', this.handleResize);
}
}
};
Reheaded.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
return !shallowequal(this.props, nextProps) || !shallowequal(this.state, nextState);
};
Reheaded.prototype.componentDidUpdate = function componentDidUpdate(prevProps) {
var parent = this.props.parent();
if (this.props.forcePin && !prevProps.forcePin) {
this.pin();
parent.removeEventListener('scroll', this.handleScroll);
} else if (!this.props.forcePin && prevProps.forcePin) {
parent.addEventListener('scroll', this.handleScroll);
}
if (this.props.disabled && !prevProps.disabled) {
this.unfix();
parent.removeEventListener('scroll', this.handleScroll);
parent.removeEventListener('resize', this.handleResize);
} else if (!this.props.disabled && prevProps.disabled) {
parent.addEventListener('scroll', this.handleScroll);
/* istanbul ignore else */
if (this.props.calcHeightOnResize) {
parent.addEventListener('resize', this.handleResize);
}
}
};
Reheaded.prototype.componentWillUnmount = function componentWillUnmount() {
var parent = this.props.parent();
parent.removeEventListener('scroll', this.handleScroll);
/* istanbul ignore else */
if (parent !== window) {
window.removeEventListener('scroll', this.handleScroll);
}
parent.removeEventListener('resize', this.handleResize);
};
Reheaded.prototype.render = function render() {
var children = unwrapArray(this.props.children, noop);
return children(_extends({
setRef: this.setRef
}, this.state));
};
return Reheaded;
}(Component);
process.env.NODE_ENV !== "production" ? Reheaded.propTypes = {
parent: PropTypes.func,
children: PropTypes.func.isRequired,
disabled: PropTypes.bool,
upTolerance: PropTypes.number,
downTolerance: PropTypes.number,
onPin: PropTypes.func,
onUnpin: PropTypes.func,
onUnfix: PropTypes.func,
pinStart: PropTypes.number,
forcePin: PropTypes.bool,
calcHeightOnResize: PropTypes.bool
} : void 0;
Reheaded.defaultProps = {
parent: function parent() {
return window;
},
forcePin: false,
disabled: false,
upTolerance: 5,
downTolerance: 5,
onPin: noop,
onUnpin: noop,
onUnfix: noop,
pinStart: 0,
calcHeightOnResize: true
};
export default Reheaded;