UNPKG

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
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;