react-stickynode
Advanced tools
Comparing version 4.2.0 to 5.0.0
@@ -8,16 +8,2 @@ /** | ||
require("core-js/modules/es.symbol.js"); | ||
require("core-js/modules/es.symbol.description.js"); | ||
require("core-js/modules/es.symbol.iterator.js"); | ||
require("core-js/modules/es.symbol.to-primitive.js"); | ||
require("core-js/modules/es.array.iterator.js"); | ||
require("core-js/modules/es.date.to-primitive.js"); | ||
require("core-js/modules/es.number.constructor.js"); | ||
require("core-js/modules/es.object.get-own-property-descriptor.js"); | ||
require("core-js/modules/es.object.get-prototype-of.js"); | ||
require("core-js/modules/es.object.to-string.js"); | ||
require("core-js/modules/es.reflect.construct.js"); | ||
require("core-js/modules/es.string.iterator.js"); | ||
require("core-js/modules/es.weak-map.js"); | ||
require("core-js/modules/web.dom-collections.iterator.js"); | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -27,3 +13,2 @@ value: true | ||
exports.default = void 0; | ||
require("core-js/modules/es.object.set-prototype-of.js"); | ||
var _react = _interopRequireWildcard(require("react")); | ||
@@ -35,52 +20,36 @@ var _propTypes = _interopRequireDefault(require("prop-types")); | ||
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } | ||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } | ||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } | ||
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } | ||
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } | ||
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } | ||
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } | ||
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } | ||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } | ||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } | ||
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } | ||
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } | ||
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } | ||
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } | ||
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } | ||
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } | ||
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } | ||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } | ||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } | ||
// constants | ||
var STATUS_ORIGINAL = 0; // The default status, locating at the original position. | ||
var STATUS_RELEASED = 1; // The released status, locating at somewhere on document but not default one. | ||
var STATUS_FIXED = 2; // The sticky status, locating fixed to the top or the bottom of screen. | ||
const STATUS_ORIGINAL = 0; // The default status, locating at the original position. | ||
const STATUS_RELEASED = 1; // The released status, locating at somewhere on document but not default one. | ||
const STATUS_FIXED = 2; // The sticky status, locating fixed to the top or the bottom of screen. | ||
var TRANSFORM_PROP = 'transform'; | ||
let TRANSFORM_PROP = 'transform'; | ||
// global variable for all instances | ||
var doc; | ||
var docBody; | ||
var docEl; | ||
var canEnableTransforms = true; // Use transform by default, so no Sticky on lower-end browser when no Modernizr | ||
var M; | ||
var scrollDelta = 0; | ||
var win; | ||
var winHeight = -1; | ||
var Sticky = /*#__PURE__*/function (_Component) { | ||
function Sticky(props, context) { | ||
var _this; | ||
_classCallCheck(this, Sticky); | ||
_this = _callSuper(this, Sticky, [props, context]); | ||
_this.handleResize = _this.handleResize.bind(_this); | ||
_this.handleScroll = _this.handleScroll.bind(_this); | ||
_this.handleScrollStart = _this.handleScrollStart.bind(_this); | ||
_this.delta = 0; | ||
_this.stickyTop = 0; | ||
_this.stickyBottom = 0; | ||
_this.frozen = false; | ||
_this.skipNextScrollEvent = false; | ||
_this.scrollTop = -1; | ||
_this.bottomBoundaryTarget; | ||
_this.topTarget; | ||
_this.subscribers; | ||
_this.state = { | ||
let doc; | ||
let docBody; | ||
let docEl; | ||
let canEnableTransforms = true; // Use transform by default, so no Sticky on lower-end browser when no Modernizr | ||
let M; | ||
let scrollDelta = 0; | ||
let win; | ||
let winHeight = -1; | ||
class Sticky extends _react.Component { | ||
constructor(props, context) { | ||
super(props, context); | ||
this.handleResize = this.handleResize.bind(this); | ||
this.handleScroll = this.handleScroll.bind(this); | ||
this.handleScrollStart = this.handleScrollStart.bind(this); | ||
this.delta = 0; | ||
this.stickyTop = 0; | ||
this.stickyBottom = 0; | ||
this.frozen = false; | ||
this.skipNextScrollEvent = false; | ||
this.scrollTop = -1; | ||
this.bottomBoundaryTarget; | ||
this.topTarget; | ||
this.subscribers; | ||
this.state = { | ||
top: 0, | ||
@@ -108,378 +77,342 @@ // A top offset from viewport top where Sticky sticks to when scrolling up | ||
}; | ||
return _this; | ||
} | ||
_inherits(Sticky, _Component); | ||
return _createClass(Sticky, [{ | ||
key: "getTargetHeight", | ||
value: function getTargetHeight(target) { | ||
return target && target.offsetHeight || 0; | ||
} | ||
}, { | ||
key: "getTopPosition", | ||
value: function getTopPosition(top) { | ||
// a top argument can be provided to override reading from the props | ||
top = top || this.props.top || 0; | ||
if (typeof top === 'string') { | ||
if (!this.topTarget) { | ||
this.topTarget = doc.querySelector(top); | ||
} | ||
top = this.getTargetHeight(this.topTarget); | ||
getTargetHeight(target) { | ||
return target && target.offsetHeight || 0; | ||
} | ||
getTopPosition(top) { | ||
// a top argument can be provided to override reading from the props | ||
top = top || this.props.top || 0; | ||
if (typeof top === 'string') { | ||
if (!this.topTarget) { | ||
this.topTarget = doc.querySelector(top); | ||
} | ||
return top; | ||
top = this.getTargetHeight(this.topTarget); | ||
} | ||
}, { | ||
key: "getTargetBottom", | ||
value: function getTargetBottom(target) { | ||
if (!target) { | ||
return -1; | ||
} | ||
var rect = target.getBoundingClientRect(); | ||
return this.scrollTop + rect.bottom; | ||
return top; | ||
} | ||
getTargetBottom(target) { | ||
if (!target) { | ||
return -1; | ||
} | ||
}, { | ||
key: "getBottomBoundary", | ||
value: function getBottomBoundary(bottomBoundary) { | ||
// a bottomBoundary can be provided to avoid reading from the props | ||
var boundary = bottomBoundary || this.props.bottomBoundary; | ||
const rect = target.getBoundingClientRect(); | ||
return this.scrollTop + rect.bottom; | ||
} | ||
getBottomBoundary(bottomBoundary) { | ||
// a bottomBoundary can be provided to avoid reading from the props | ||
let boundary = bottomBoundary || this.props.bottomBoundary; | ||
// TODO, bottomBoundary was an object, depricate it later. | ||
if (_typeof(boundary) === 'object') { | ||
boundary = boundary.value || boundary.target || 0; | ||
// TODO, bottomBoundary was an object, depricate it later. | ||
if (typeof boundary === 'object') { | ||
boundary = boundary.value || boundary.target || 0; | ||
} | ||
if (typeof boundary === 'string') { | ||
if (!this.bottomBoundaryTarget) { | ||
this.bottomBoundaryTarget = doc.querySelector(boundary); | ||
} | ||
if (typeof boundary === 'string') { | ||
if (!this.bottomBoundaryTarget) { | ||
this.bottomBoundaryTarget = doc.querySelector(boundary); | ||
} | ||
boundary = this.getTargetBottom(this.bottomBoundaryTarget); | ||
} | ||
return boundary && boundary > 0 ? boundary : Infinity; | ||
boundary = this.getTargetBottom(this.bottomBoundaryTarget); | ||
} | ||
}, { | ||
key: "reset", | ||
value: function reset() { | ||
this.setState({ | ||
status: STATUS_ORIGINAL, | ||
pos: 0 | ||
}); | ||
return boundary && boundary > 0 ? boundary : Infinity; | ||
} | ||
reset() { | ||
this.setState({ | ||
status: STATUS_ORIGINAL, | ||
pos: 0 | ||
}); | ||
} | ||
release(pos) { | ||
this.setState({ | ||
status: STATUS_RELEASED, | ||
pos: pos - this.state.y | ||
}); | ||
} | ||
fix(pos) { | ||
this.setState({ | ||
status: STATUS_FIXED, | ||
pos: pos | ||
}); | ||
} | ||
/** | ||
* Update the initial position, width, and height. It should update whenever children change. | ||
* @param {Object} options optional top and bottomBoundary new values | ||
*/ | ||
updateInitialDimension(options) { | ||
options = options || {}; | ||
if (!this.outerElement || !this.innerElement) { | ||
return; | ||
} | ||
}, { | ||
key: "release", | ||
value: function release(pos) { | ||
this.setState({ | ||
status: STATUS_RELEASED, | ||
pos: pos - this.state.y | ||
}); | ||
const outerRect = this.outerElement.getBoundingClientRect(); | ||
const innerRect = this.innerElement.getBoundingClientRect(); | ||
const width = outerRect.width || outerRect.right - outerRect.left; | ||
const height = innerRect.height || innerRect.bottom - innerRect.top; | ||
const outerY = outerRect.top + this.scrollTop; | ||
this.setState({ | ||
top: this.getTopPosition(options.top), | ||
bottom: Math.min(this.state.top + height, winHeight), | ||
width, | ||
height, | ||
x: outerRect.left, | ||
y: outerY, | ||
bottomBoundary: this.getBottomBoundary(options.bottomBoundary), | ||
topBoundary: outerY | ||
}); | ||
} | ||
handleResize(e, ae) { | ||
if (this.props.shouldFreeze()) { | ||
return; | ||
} | ||
}, { | ||
key: "fix", | ||
value: function fix(pos) { | ||
this.setState({ | ||
status: STATUS_FIXED, | ||
pos: pos | ||
}); | ||
winHeight = ae.resize.height; | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
handleScrollStart(e, ae) { | ||
this.frozen = this.props.shouldFreeze(); | ||
if (this.frozen) { | ||
return; | ||
} | ||
/** | ||
* Update the initial position, width, and height. It should update whenever children change. | ||
* @param {Object} options optional top and bottomBoundary new values | ||
*/ | ||
}, { | ||
key: "updateInitialDimension", | ||
value: function updateInitialDimension(options) { | ||
options = options || {}; | ||
if (!this.outerElement || !this.innerElement) { | ||
return; | ||
} | ||
var outerRect = this.outerElement.getBoundingClientRect(); | ||
var innerRect = this.innerElement.getBoundingClientRect(); | ||
var width = outerRect.width || outerRect.right - outerRect.left; | ||
var height = innerRect.height || innerRect.bottom - innerRect.top; | ||
var outerY = outerRect.top + this.scrollTop; | ||
this.setState({ | ||
top: this.getTopPosition(options.top), | ||
bottom: Math.min(this.state.top + height, winHeight), | ||
width: width, | ||
height: height, | ||
x: outerRect.left, | ||
y: outerY, | ||
bottomBoundary: this.getBottomBoundary(options.bottomBoundary), | ||
topBoundary: outerY | ||
}); | ||
} | ||
}, { | ||
key: "handleResize", | ||
value: function handleResize(e, ae) { | ||
if (this.props.shouldFreeze()) { | ||
return; | ||
} | ||
winHeight = ae.resize.height; | ||
if (this.scrollTop === ae.scroll.top) { | ||
// Scroll position hasn't changed, | ||
// do nothing | ||
this.skipNextScrollEvent = true; | ||
} else { | ||
this.scrollTop = ae.scroll.top; | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
}, { | ||
key: "handleScrollStart", | ||
value: function handleScrollStart(e, ae) { | ||
this.frozen = this.props.shouldFreeze(); | ||
if (this.frozen) { | ||
return; | ||
} | ||
if (this.scrollTop === ae.scroll.top) { | ||
// Scroll position hasn't changed, | ||
// do nothing | ||
this.skipNextScrollEvent = true; | ||
} else { | ||
this.scrollTop = ae.scroll.top; | ||
this.updateInitialDimension(); | ||
} | ||
} | ||
handleScroll(e, ae) { | ||
// Scroll doesn't need to be handled | ||
if (this.skipNextScrollEvent) { | ||
this.skipNextScrollEvent = false; | ||
return; | ||
} | ||
}, { | ||
key: "handleScroll", | ||
value: function handleScroll(e, ae) { | ||
// Scroll doesn't need to be handled | ||
if (this.skipNextScrollEvent) { | ||
this.skipNextScrollEvent = false; | ||
return; | ||
scrollDelta = ae.scroll.delta; | ||
this.scrollTop = ae.scroll.top; | ||
this.update(); | ||
} | ||
/** | ||
* Update Sticky position. | ||
*/ | ||
update() { | ||
var disabled = !this.props.enabled || this.state.bottomBoundary - this.state.topBoundary <= this.state.height || this.state.width === 0 && this.state.height === 0; | ||
if (disabled) { | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
this.reset(); | ||
} | ||
scrollDelta = ae.scroll.delta; | ||
this.scrollTop = ae.scroll.top; | ||
this.update(); | ||
return; | ||
} | ||
var delta = scrollDelta; | ||
// "top" and "bottom" are the positions that this.state.top and this.state.bottom project | ||
// on document from viewport. | ||
var top = this.scrollTop + this.state.top; | ||
var bottom = this.scrollTop + this.state.bottom; | ||
/** | ||
* Update Sticky position. | ||
*/ | ||
}, { | ||
key: "update", | ||
value: function update() { | ||
var disabled = !this.props.enabled || this.state.bottomBoundary - this.state.topBoundary <= this.state.height || this.state.width === 0 && this.state.height === 0; | ||
if (disabled) { | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
this.reset(); | ||
// There are 2 principles to make sure Sticky won't get wrong so much: | ||
// 1. Reset Sticky to the original postion when "top" <= topBoundary | ||
// 2. Release Sticky to the bottom boundary when "bottom" >= bottomBoundary | ||
if (top <= this.state.topBoundary) { | ||
// #1 | ||
this.reset(); | ||
} else if (bottom >= this.state.bottomBoundary) { | ||
// #2 | ||
this.stickyBottom = this.state.bottomBoundary; | ||
this.stickyTop = this.stickyBottom - this.state.height; | ||
this.release(this.stickyTop); | ||
} else { | ||
if (this.state.height > winHeight - this.state.top) { | ||
// In this case, Sticky is higher then viewport minus top offset | ||
switch (this.state.status) { | ||
case STATUS_ORIGINAL: | ||
this.release(this.state.y); | ||
this.stickyTop = this.state.y; | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
// Commentting out "break" is on purpose, because there is a chance to transit to FIXED | ||
// from ORIGINAL when calling window.scrollTo(). | ||
// break; | ||
/* eslint-disable-next-line no-fallthrough */ | ||
case STATUS_RELEASED: | ||
// If "top" and "bottom" are inbetween stickyTop and stickyBottom, then Sticky is in | ||
// RELEASE status. Otherwise, it changes to FIXED status, and its bottom sticks to | ||
// viewport bottom when scrolling down, or its top sticks to viewport top when scrolling up. | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
if (delta > 0 && bottom > this.stickyBottom) { | ||
this.fix(this.state.bottom - this.state.height); | ||
} else if (delta < 0 && top < this.stickyTop) { | ||
this.fix(this.state.top); | ||
} | ||
break; | ||
case STATUS_FIXED: | ||
var toRelease = true; | ||
var pos = this.state.pos; | ||
var height = this.state.height; | ||
// In regular cases, when Sticky is in FIXED status, | ||
// 1. it's top will stick to the screen top, | ||
// 2. it's bottom will stick to the screen bottom, | ||
// 3. if not the cases above, then it's height gets changed | ||
if (delta > 0 && pos === this.state.top) { | ||
// case 1, and scrolling down | ||
this.stickyTop = top - delta; | ||
this.stickyBottom = this.stickyTop + height; | ||
} else if (delta < 0 && pos === this.state.bottom - height) { | ||
// case 2, and scrolling up | ||
this.stickyBottom = bottom - delta; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else if (pos !== this.state.bottom - height && pos !== this.state.top) { | ||
// case 3 | ||
// This case only happens when Sticky's bottom sticks to the screen bottom and | ||
// its height gets changed. Sticky should be in RELEASE status and update its | ||
// sticky bottom by calculating how much height it changed. | ||
const deltaHeight = pos + height - this.state.bottom; | ||
this.stickyBottom = bottom - delta + deltaHeight; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else { | ||
toRelease = false; | ||
} | ||
if (toRelease) { | ||
this.release(this.stickyTop); | ||
} | ||
break; | ||
} | ||
return; | ||
} else { | ||
// In this case, Sticky is shorter then viewport minus top offset | ||
// and will always fix to the top offset of viewport | ||
this.fix(this.state.top); | ||
} | ||
var delta = scrollDelta; | ||
// "top" and "bottom" are the positions that this.state.top and this.state.bottom project | ||
// on document from viewport. | ||
var top = this.scrollTop + this.state.top; | ||
var bottom = this.scrollTop + this.state.bottom; | ||
} | ||
this.delta = delta; | ||
} | ||
componentDidUpdate(prevProps, prevState) { | ||
if (prevState.status !== this.state.status && this.props.onStateChange) { | ||
this.props.onStateChange({ | ||
status: this.state.status | ||
}); | ||
} | ||
// There are 2 principles to make sure Sticky won't get wrong so much: | ||
// 1. Reset Sticky to the original postion when "top" <= topBoundary | ||
// 2. Release Sticky to the bottom boundary when "bottom" >= bottomBoundary | ||
if (top <= this.state.topBoundary) { | ||
// #1 | ||
this.reset(); | ||
} else if (bottom >= this.state.bottomBoundary) { | ||
// #2 | ||
this.stickyBottom = this.state.bottomBoundary; | ||
this.stickyTop = this.stickyBottom - this.state.height; | ||
this.release(this.stickyTop); | ||
} else { | ||
if (this.state.height > winHeight - this.state.top) { | ||
// In this case, Sticky is higher then viewport minus top offset | ||
switch (this.state.status) { | ||
case STATUS_ORIGINAL: | ||
this.release(this.state.y); | ||
this.stickyTop = this.state.y; | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
// Commentting out "break" is on purpose, because there is a chance to transit to FIXED | ||
// from ORIGINAL when calling window.scrollTo(). | ||
// break; | ||
/* eslint-disable-next-line no-fallthrough */ | ||
case STATUS_RELEASED: | ||
// If "top" and "bottom" are inbetween stickyTop and stickyBottom, then Sticky is in | ||
// RELEASE status. Otherwise, it changes to FIXED status, and its bottom sticks to | ||
// viewport bottom when scrolling down, or its top sticks to viewport top when scrolling up. | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
if (delta > 0 && bottom > this.stickyBottom) { | ||
this.fix(this.state.bottom - this.state.height); | ||
} else if (delta < 0 && top < this.stickyTop) { | ||
this.fix(this.state.top); | ||
} | ||
break; | ||
case STATUS_FIXED: | ||
var toRelease = true; | ||
var pos = this.state.pos; | ||
var height = this.state.height; | ||
// In regular cases, when Sticky is in FIXED status, | ||
// 1. it's top will stick to the screen top, | ||
// 2. it's bottom will stick to the screen bottom, | ||
// 3. if not the cases above, then it's height gets changed | ||
if (delta > 0 && pos === this.state.top) { | ||
// case 1, and scrolling down | ||
this.stickyTop = top - delta; | ||
this.stickyBottom = this.stickyTop + height; | ||
} else if (delta < 0 && pos === this.state.bottom - height) { | ||
// case 2, and scrolling up | ||
this.stickyBottom = bottom - delta; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else if (pos !== this.state.bottom - height && pos !== this.state.top) { | ||
// case 3 | ||
// This case only happens when Sticky's bottom sticks to the screen bottom and | ||
// its height gets changed. Sticky should be in RELEASE status and update its | ||
// sticky bottom by calculating how much height it changed. | ||
var deltaHeight = pos + height - this.state.bottom; | ||
this.stickyBottom = bottom - delta + deltaHeight; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else { | ||
toRelease = false; | ||
} | ||
if (toRelease) { | ||
this.release(this.stickyTop); | ||
} | ||
break; | ||
} | ||
// check if we are up-to-date, is triggered in case of scroll restoration | ||
if (this.state.top !== prevState.top) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
const arePropsChanged = !(0, _shallowequal.default)(this.props, prevProps); | ||
if (arePropsChanged) { | ||
// if the props for enabling are toggled, then trigger the update or reset depending on the current props | ||
if (prevProps.enabled !== this.props.enabled) { | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}, () => { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
}); | ||
} else { | ||
// In this case, Sticky is shorter then viewport minus top offset | ||
// and will always fix to the top offset of viewport | ||
this.fix(this.state.top); | ||
this.setState({ | ||
activated: false | ||
}, () => { | ||
this.reset(); | ||
}); | ||
} | ||
} | ||
this.delta = delta; | ||
} | ||
}, { | ||
key: "componentDidUpdate", | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
var _this2 = this; | ||
if (prevState.status !== this.state.status && this.props.onStateChange) { | ||
this.props.onStateChange({ | ||
status: this.state.status | ||
}); | ||
} | ||
// check if we are up-to-date, is triggered in case of scroll restoration | ||
if (this.state.top !== prevState.top) { | ||
// if the top or bottomBoundary props were changed, then trigger the update | ||
else if (prevProps.top !== this.props.top || prevProps.bottomBoundary !== this.props.bottomBoundary) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
var arePropsChanged = !(0, _shallowequal.default)(this.props, prevProps); | ||
if (arePropsChanged) { | ||
// if the props for enabling are toggled, then trigger the update or reset depending on the current props | ||
if (prevProps.enabled !== this.props.enabled) { | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}, function () { | ||
_this2.updateInitialDimension(); | ||
_this2.update(); | ||
}); | ||
} else { | ||
this.setState({ | ||
activated: false | ||
}, function () { | ||
_this2.reset(); | ||
}); | ||
} | ||
} | ||
// if the top or bottomBoundary props were changed, then trigger the update | ||
else if (prevProps.top !== this.props.top || prevProps.bottomBoundary !== this.props.bottomBoundary) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
} | ||
} | ||
}, { | ||
key: "componentWillUnmount", | ||
value: function componentWillUnmount() { | ||
var subscribers = this.subscribers || []; | ||
for (var i = subscribers.length - 1; i >= 0; i--) { | ||
this.subscribers[i].unsubscribe(); | ||
} | ||
componentWillUnmount() { | ||
const subscribers = this.subscribers || []; | ||
for (var i = subscribers.length - 1; i >= 0; i--) { | ||
this.subscribers[i].unsubscribe(); | ||
} | ||
} | ||
componentDidMount() { | ||
// Only initialize the globals if this is the first | ||
// time this component type has been mounted | ||
if (!win) { | ||
win = window; | ||
doc = document; | ||
docEl = doc.documentElement; | ||
docBody = doc.body; | ||
winHeight = win.innerHeight || docEl.clientHeight; | ||
M = window.Modernizr; | ||
// No Sticky on lower-end browser when no Modernizr | ||
if (M && M.prefixed) { | ||
canEnableTransforms = M.csstransforms3d; | ||
TRANSFORM_PROP = M.prefixed('transform'); | ||
} | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
// Only initialize the globals if this is the first | ||
// time this component type has been mounted | ||
if (!win) { | ||
win = window; | ||
doc = document; | ||
docEl = doc.documentElement; | ||
docBody = doc.body; | ||
winHeight = win.innerHeight || docEl.clientHeight; | ||
M = window.Modernizr; | ||
// No Sticky on lower-end browser when no Modernizr | ||
if (M && M.prefixed) { | ||
canEnableTransforms = M.csstransforms3d; | ||
TRANSFORM_PROP = M.prefixed('transform'); | ||
} | ||
} | ||
// when mount, the scrollTop is not necessary on the top | ||
this.scrollTop = docBody.scrollTop + docEl.scrollTop; | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}); | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
// bind the listeners regardless if initially enabled - allows the component to toggle sticky functionality | ||
this.subscribers = [(0, _subscribeUiEvent.subscribe)('scrollStart', this.handleScrollStart.bind(this), { | ||
useRAF: true | ||
}), (0, _subscribeUiEvent.subscribe)('scroll', this.handleScroll.bind(this), { | ||
useRAF: true, | ||
enableScrollInfo: true | ||
}), (0, _subscribeUiEvent.subscribe)('resize', this.handleResize.bind(this), { | ||
enableResizeInfo: true | ||
})]; | ||
// when mount, the scrollTop is not necessary on the top | ||
this.scrollTop = docBody.scrollTop + docEl.scrollTop; | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}); | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
}, { | ||
key: "translate", | ||
value: function translate(style, pos) { | ||
var enableTransforms = canEnableTransforms && this.props.enableTransforms; | ||
if (enableTransforms && this.state.activated) { | ||
style[TRANSFORM_PROP] = 'translate3d(0,' + Math.round(pos) + 'px,0)'; | ||
} else { | ||
style.top = pos + 'px'; | ||
} | ||
// bind the listeners regardless if initially enabled - allows the component to toggle sticky functionality | ||
this.subscribers = [(0, _subscribeUiEvent.subscribe)('scrollStart', this.handleScrollStart.bind(this), { | ||
useRAF: true | ||
}), (0, _subscribeUiEvent.subscribe)('scroll', this.handleScroll.bind(this), { | ||
useRAF: true, | ||
enableScrollInfo: true | ||
}), (0, _subscribeUiEvent.subscribe)('resize', this.handleResize.bind(this), { | ||
enableResizeInfo: true | ||
})]; | ||
} | ||
translate(style, pos) { | ||
const enableTransforms = canEnableTransforms && this.props.enableTransforms; | ||
if (enableTransforms && this.state.activated) { | ||
style[TRANSFORM_PROP] = 'translate3d(0,' + Math.round(pos) + 'px,0)'; | ||
} else { | ||
style.top = pos + 'px'; | ||
} | ||
}, { | ||
key: "shouldComponentUpdate", | ||
value: function shouldComponentUpdate(nextProps, nextState) { | ||
return !this.props.shouldFreeze() && !((0, _shallowequal.default)(this.props, nextProps) && (0, _shallowequal.default)(this.state, nextState)); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this3 = this; | ||
// TODO, "overflow: auto" prevents collapse, need a good way to get children height | ||
var innerStyle = { | ||
position: this.state.status === STATUS_FIXED ? 'fixed' : 'relative', | ||
top: this.state.status === STATUS_FIXED ? '0px' : '', | ||
zIndex: this.props.innerZ | ||
}; | ||
var outerStyle = {}; | ||
} | ||
shouldComponentUpdate(nextProps, nextState) { | ||
return !this.props.shouldFreeze() && !((0, _shallowequal.default)(this.props, nextProps) && (0, _shallowequal.default)(this.state, nextState)); | ||
} | ||
render() { | ||
// TODO, "overflow: auto" prevents collapse, need a good way to get children height | ||
const innerStyle = { | ||
position: this.state.status === STATUS_FIXED ? 'fixed' : 'relative', | ||
top: this.state.status === STATUS_FIXED ? '0px' : '', | ||
zIndex: this.props.innerZ | ||
}; | ||
const outerStyle = {}; | ||
// always use translate3d to enhance the performance | ||
this.translate(innerStyle, this.state.pos); | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
innerStyle.width = this.state.width + 'px'; | ||
outerStyle.height = this.state.height + 'px'; | ||
} | ||
var outerClasses = (0, _classnames.default)('sticky-outer-wrapper', this.props.className, _defineProperty(_defineProperty({}, this.props.activeClass, this.state.status === STATUS_FIXED), this.props.releasedClass, this.state.status === STATUS_RELEASED)); | ||
var innerClasses = (0, _classnames.default)('sticky-inner-wrapper', this.props.innerClass, _defineProperty({}, this.props.innerActiveClass, this.state.status === STATUS_FIXED)); | ||
var children = this.props.children; | ||
return /*#__PURE__*/_react.default.createElement("div", { | ||
ref: function ref(outer) { | ||
_this3.outerElement = outer; | ||
}, | ||
className: outerClasses, | ||
style: outerStyle | ||
}, /*#__PURE__*/_react.default.createElement("div", { | ||
ref: function ref(inner) { | ||
_this3.innerElement = inner; | ||
}, | ||
className: innerClasses, | ||
style: innerStyle | ||
}, typeof children === 'function' ? children({ | ||
status: this.state.status | ||
}) : children)); | ||
// always use translate3d to enhance the performance | ||
this.translate(innerStyle, this.state.pos); | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
innerStyle.width = this.state.width + 'px'; | ||
outerStyle.height = this.state.height + 'px'; | ||
} | ||
}]); | ||
}(_react.Component); | ||
const outerClasses = (0, _classnames.default)('sticky-outer-wrapper', this.props.className, { | ||
[this.props.activeClass]: this.state.status === STATUS_FIXED, | ||
[this.props.releasedClass]: this.state.status === STATUS_RELEASED | ||
}); | ||
const innerClasses = (0, _classnames.default)('sticky-inner-wrapper', this.props.innerClass, { | ||
[this.props.innerActiveClass]: this.state.status === STATUS_FIXED | ||
}); | ||
const children = this.props.children; | ||
return /*#__PURE__*/_react.default.createElement("div", { | ||
ref: outer => { | ||
this.outerElement = outer; | ||
}, | ||
className: outerClasses, | ||
style: outerStyle | ||
}, /*#__PURE__*/_react.default.createElement("div", { | ||
ref: inner => { | ||
this.innerElement = inner; | ||
}, | ||
className: innerClasses, | ||
style: innerStyle | ||
}, typeof children === 'function' ? children({ | ||
status: this.state.status | ||
}) : children)); | ||
} | ||
} | ||
Sticky.displayName = 'Sticky'; | ||
Sticky.defaultProps = { | ||
shouldFreeze: function shouldFreeze() { | ||
shouldFreeze: function () { | ||
return false; | ||
@@ -486,0 +419,0 @@ }, |
@@ -8,29 +8,2 @@ /** | ||
import "core-js/modules/es.symbol.js"; | ||
import "core-js/modules/es.symbol.description.js"; | ||
import "core-js/modules/es.symbol.iterator.js"; | ||
import "core-js/modules/es.symbol.to-primitive.js"; | ||
import "core-js/modules/es.array.iterator.js"; | ||
import "core-js/modules/es.date.to-primitive.js"; | ||
import "core-js/modules/es.number.constructor.js"; | ||
import "core-js/modules/es.object.get-prototype-of.js"; | ||
import "core-js/modules/es.object.set-prototype-of.js"; | ||
import "core-js/modules/es.object.to-string.js"; | ||
import "core-js/modules/es.reflect.construct.js"; | ||
import "core-js/modules/es.string.iterator.js"; | ||
import "core-js/modules/web.dom-collections.iterator.js"; | ||
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } | ||
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } | ||
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } | ||
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } | ||
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } | ||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } | ||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } | ||
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } | ||
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } | ||
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } | ||
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } | ||
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } | ||
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } | ||
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } | ||
import React, { Component } from 'react'; | ||
@@ -43,35 +16,33 @@ import PropTypes from 'prop-types'; | ||
// constants | ||
var STATUS_ORIGINAL = 0; // The default status, locating at the original position. | ||
var STATUS_RELEASED = 1; // The released status, locating at somewhere on document but not default one. | ||
var STATUS_FIXED = 2; // The sticky status, locating fixed to the top or the bottom of screen. | ||
const STATUS_ORIGINAL = 0; // The default status, locating at the original position. | ||
const STATUS_RELEASED = 1; // The released status, locating at somewhere on document but not default one. | ||
const STATUS_FIXED = 2; // The sticky status, locating fixed to the top or the bottom of screen. | ||
var TRANSFORM_PROP = 'transform'; | ||
let TRANSFORM_PROP = 'transform'; | ||
// global variable for all instances | ||
var doc; | ||
var docBody; | ||
var docEl; | ||
var canEnableTransforms = true; // Use transform by default, so no Sticky on lower-end browser when no Modernizr | ||
var M; | ||
var scrollDelta = 0; | ||
var win; | ||
var winHeight = -1; | ||
var Sticky = /*#__PURE__*/function (_Component) { | ||
function Sticky(props, context) { | ||
var _this; | ||
_classCallCheck(this, Sticky); | ||
_this = _callSuper(this, Sticky, [props, context]); | ||
_this.handleResize = _this.handleResize.bind(_this); | ||
_this.handleScroll = _this.handleScroll.bind(_this); | ||
_this.handleScrollStart = _this.handleScrollStart.bind(_this); | ||
_this.delta = 0; | ||
_this.stickyTop = 0; | ||
_this.stickyBottom = 0; | ||
_this.frozen = false; | ||
_this.skipNextScrollEvent = false; | ||
_this.scrollTop = -1; | ||
_this.bottomBoundaryTarget; | ||
_this.topTarget; | ||
_this.subscribers; | ||
_this.state = { | ||
let doc; | ||
let docBody; | ||
let docEl; | ||
let canEnableTransforms = true; // Use transform by default, so no Sticky on lower-end browser when no Modernizr | ||
let M; | ||
let scrollDelta = 0; | ||
let win; | ||
let winHeight = -1; | ||
class Sticky extends Component { | ||
constructor(props, context) { | ||
super(props, context); | ||
this.handleResize = this.handleResize.bind(this); | ||
this.handleScroll = this.handleScroll.bind(this); | ||
this.handleScrollStart = this.handleScrollStart.bind(this); | ||
this.delta = 0; | ||
this.stickyTop = 0; | ||
this.stickyBottom = 0; | ||
this.frozen = false; | ||
this.skipNextScrollEvent = false; | ||
this.scrollTop = -1; | ||
this.bottomBoundaryTarget; | ||
this.topTarget; | ||
this.subscribers; | ||
this.state = { | ||
top: 0, | ||
@@ -99,378 +70,342 @@ // A top offset from viewport top where Sticky sticks to when scrolling up | ||
}; | ||
return _this; | ||
} | ||
_inherits(Sticky, _Component); | ||
return _createClass(Sticky, [{ | ||
key: "getTargetHeight", | ||
value: function getTargetHeight(target) { | ||
return target && target.offsetHeight || 0; | ||
} | ||
}, { | ||
key: "getTopPosition", | ||
value: function getTopPosition(top) { | ||
// a top argument can be provided to override reading from the props | ||
top = top || this.props.top || 0; | ||
if (typeof top === 'string') { | ||
if (!this.topTarget) { | ||
this.topTarget = doc.querySelector(top); | ||
} | ||
top = this.getTargetHeight(this.topTarget); | ||
getTargetHeight(target) { | ||
return target && target.offsetHeight || 0; | ||
} | ||
getTopPosition(top) { | ||
// a top argument can be provided to override reading from the props | ||
top = top || this.props.top || 0; | ||
if (typeof top === 'string') { | ||
if (!this.topTarget) { | ||
this.topTarget = doc.querySelector(top); | ||
} | ||
return top; | ||
top = this.getTargetHeight(this.topTarget); | ||
} | ||
}, { | ||
key: "getTargetBottom", | ||
value: function getTargetBottom(target) { | ||
if (!target) { | ||
return -1; | ||
} | ||
var rect = target.getBoundingClientRect(); | ||
return this.scrollTop + rect.bottom; | ||
return top; | ||
} | ||
getTargetBottom(target) { | ||
if (!target) { | ||
return -1; | ||
} | ||
}, { | ||
key: "getBottomBoundary", | ||
value: function getBottomBoundary(bottomBoundary) { | ||
// a bottomBoundary can be provided to avoid reading from the props | ||
var boundary = bottomBoundary || this.props.bottomBoundary; | ||
const rect = target.getBoundingClientRect(); | ||
return this.scrollTop + rect.bottom; | ||
} | ||
getBottomBoundary(bottomBoundary) { | ||
// a bottomBoundary can be provided to avoid reading from the props | ||
let boundary = bottomBoundary || this.props.bottomBoundary; | ||
// TODO, bottomBoundary was an object, depricate it later. | ||
if (_typeof(boundary) === 'object') { | ||
boundary = boundary.value || boundary.target || 0; | ||
// TODO, bottomBoundary was an object, depricate it later. | ||
if (typeof boundary === 'object') { | ||
boundary = boundary.value || boundary.target || 0; | ||
} | ||
if (typeof boundary === 'string') { | ||
if (!this.bottomBoundaryTarget) { | ||
this.bottomBoundaryTarget = doc.querySelector(boundary); | ||
} | ||
if (typeof boundary === 'string') { | ||
if (!this.bottomBoundaryTarget) { | ||
this.bottomBoundaryTarget = doc.querySelector(boundary); | ||
} | ||
boundary = this.getTargetBottom(this.bottomBoundaryTarget); | ||
} | ||
return boundary && boundary > 0 ? boundary : Infinity; | ||
boundary = this.getTargetBottom(this.bottomBoundaryTarget); | ||
} | ||
}, { | ||
key: "reset", | ||
value: function reset() { | ||
this.setState({ | ||
status: STATUS_ORIGINAL, | ||
pos: 0 | ||
}); | ||
return boundary && boundary > 0 ? boundary : Infinity; | ||
} | ||
reset() { | ||
this.setState({ | ||
status: STATUS_ORIGINAL, | ||
pos: 0 | ||
}); | ||
} | ||
release(pos) { | ||
this.setState({ | ||
status: STATUS_RELEASED, | ||
pos: pos - this.state.y | ||
}); | ||
} | ||
fix(pos) { | ||
this.setState({ | ||
status: STATUS_FIXED, | ||
pos: pos | ||
}); | ||
} | ||
/** | ||
* Update the initial position, width, and height. It should update whenever children change. | ||
* @param {Object} options optional top and bottomBoundary new values | ||
*/ | ||
updateInitialDimension(options) { | ||
options = options || {}; | ||
if (!this.outerElement || !this.innerElement) { | ||
return; | ||
} | ||
}, { | ||
key: "release", | ||
value: function release(pos) { | ||
this.setState({ | ||
status: STATUS_RELEASED, | ||
pos: pos - this.state.y | ||
}); | ||
const outerRect = this.outerElement.getBoundingClientRect(); | ||
const innerRect = this.innerElement.getBoundingClientRect(); | ||
const width = outerRect.width || outerRect.right - outerRect.left; | ||
const height = innerRect.height || innerRect.bottom - innerRect.top; | ||
const outerY = outerRect.top + this.scrollTop; | ||
this.setState({ | ||
top: this.getTopPosition(options.top), | ||
bottom: Math.min(this.state.top + height, winHeight), | ||
width, | ||
height, | ||
x: outerRect.left, | ||
y: outerY, | ||
bottomBoundary: this.getBottomBoundary(options.bottomBoundary), | ||
topBoundary: outerY | ||
}); | ||
} | ||
handleResize(e, ae) { | ||
if (this.props.shouldFreeze()) { | ||
return; | ||
} | ||
}, { | ||
key: "fix", | ||
value: function fix(pos) { | ||
this.setState({ | ||
status: STATUS_FIXED, | ||
pos: pos | ||
}); | ||
winHeight = ae.resize.height; | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
handleScrollStart(e, ae) { | ||
this.frozen = this.props.shouldFreeze(); | ||
if (this.frozen) { | ||
return; | ||
} | ||
/** | ||
* Update the initial position, width, and height. It should update whenever children change. | ||
* @param {Object} options optional top and bottomBoundary new values | ||
*/ | ||
}, { | ||
key: "updateInitialDimension", | ||
value: function updateInitialDimension(options) { | ||
options = options || {}; | ||
if (!this.outerElement || !this.innerElement) { | ||
return; | ||
} | ||
var outerRect = this.outerElement.getBoundingClientRect(); | ||
var innerRect = this.innerElement.getBoundingClientRect(); | ||
var width = outerRect.width || outerRect.right - outerRect.left; | ||
var height = innerRect.height || innerRect.bottom - innerRect.top; | ||
var outerY = outerRect.top + this.scrollTop; | ||
this.setState({ | ||
top: this.getTopPosition(options.top), | ||
bottom: Math.min(this.state.top + height, winHeight), | ||
width: width, | ||
height: height, | ||
x: outerRect.left, | ||
y: outerY, | ||
bottomBoundary: this.getBottomBoundary(options.bottomBoundary), | ||
topBoundary: outerY | ||
}); | ||
} | ||
}, { | ||
key: "handleResize", | ||
value: function handleResize(e, ae) { | ||
if (this.props.shouldFreeze()) { | ||
return; | ||
} | ||
winHeight = ae.resize.height; | ||
if (this.scrollTop === ae.scroll.top) { | ||
// Scroll position hasn't changed, | ||
// do nothing | ||
this.skipNextScrollEvent = true; | ||
} else { | ||
this.scrollTop = ae.scroll.top; | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
}, { | ||
key: "handleScrollStart", | ||
value: function handleScrollStart(e, ae) { | ||
this.frozen = this.props.shouldFreeze(); | ||
if (this.frozen) { | ||
return; | ||
} | ||
if (this.scrollTop === ae.scroll.top) { | ||
// Scroll position hasn't changed, | ||
// do nothing | ||
this.skipNextScrollEvent = true; | ||
} else { | ||
this.scrollTop = ae.scroll.top; | ||
this.updateInitialDimension(); | ||
} | ||
} | ||
handleScroll(e, ae) { | ||
// Scroll doesn't need to be handled | ||
if (this.skipNextScrollEvent) { | ||
this.skipNextScrollEvent = false; | ||
return; | ||
} | ||
}, { | ||
key: "handleScroll", | ||
value: function handleScroll(e, ae) { | ||
// Scroll doesn't need to be handled | ||
if (this.skipNextScrollEvent) { | ||
this.skipNextScrollEvent = false; | ||
return; | ||
scrollDelta = ae.scroll.delta; | ||
this.scrollTop = ae.scroll.top; | ||
this.update(); | ||
} | ||
/** | ||
* Update Sticky position. | ||
*/ | ||
update() { | ||
var disabled = !this.props.enabled || this.state.bottomBoundary - this.state.topBoundary <= this.state.height || this.state.width === 0 && this.state.height === 0; | ||
if (disabled) { | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
this.reset(); | ||
} | ||
scrollDelta = ae.scroll.delta; | ||
this.scrollTop = ae.scroll.top; | ||
this.update(); | ||
return; | ||
} | ||
var delta = scrollDelta; | ||
// "top" and "bottom" are the positions that this.state.top and this.state.bottom project | ||
// on document from viewport. | ||
var top = this.scrollTop + this.state.top; | ||
var bottom = this.scrollTop + this.state.bottom; | ||
/** | ||
* Update Sticky position. | ||
*/ | ||
}, { | ||
key: "update", | ||
value: function update() { | ||
var disabled = !this.props.enabled || this.state.bottomBoundary - this.state.topBoundary <= this.state.height || this.state.width === 0 && this.state.height === 0; | ||
if (disabled) { | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
this.reset(); | ||
// There are 2 principles to make sure Sticky won't get wrong so much: | ||
// 1. Reset Sticky to the original postion when "top" <= topBoundary | ||
// 2. Release Sticky to the bottom boundary when "bottom" >= bottomBoundary | ||
if (top <= this.state.topBoundary) { | ||
// #1 | ||
this.reset(); | ||
} else if (bottom >= this.state.bottomBoundary) { | ||
// #2 | ||
this.stickyBottom = this.state.bottomBoundary; | ||
this.stickyTop = this.stickyBottom - this.state.height; | ||
this.release(this.stickyTop); | ||
} else { | ||
if (this.state.height > winHeight - this.state.top) { | ||
// In this case, Sticky is higher then viewport minus top offset | ||
switch (this.state.status) { | ||
case STATUS_ORIGINAL: | ||
this.release(this.state.y); | ||
this.stickyTop = this.state.y; | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
// Commentting out "break" is on purpose, because there is a chance to transit to FIXED | ||
// from ORIGINAL when calling window.scrollTo(). | ||
// break; | ||
/* eslint-disable-next-line no-fallthrough */ | ||
case STATUS_RELEASED: | ||
// If "top" and "bottom" are inbetween stickyTop and stickyBottom, then Sticky is in | ||
// RELEASE status. Otherwise, it changes to FIXED status, and its bottom sticks to | ||
// viewport bottom when scrolling down, or its top sticks to viewport top when scrolling up. | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
if (delta > 0 && bottom > this.stickyBottom) { | ||
this.fix(this.state.bottom - this.state.height); | ||
} else if (delta < 0 && top < this.stickyTop) { | ||
this.fix(this.state.top); | ||
} | ||
break; | ||
case STATUS_FIXED: | ||
var toRelease = true; | ||
var pos = this.state.pos; | ||
var height = this.state.height; | ||
// In regular cases, when Sticky is in FIXED status, | ||
// 1. it's top will stick to the screen top, | ||
// 2. it's bottom will stick to the screen bottom, | ||
// 3. if not the cases above, then it's height gets changed | ||
if (delta > 0 && pos === this.state.top) { | ||
// case 1, and scrolling down | ||
this.stickyTop = top - delta; | ||
this.stickyBottom = this.stickyTop + height; | ||
} else if (delta < 0 && pos === this.state.bottom - height) { | ||
// case 2, and scrolling up | ||
this.stickyBottom = bottom - delta; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else if (pos !== this.state.bottom - height && pos !== this.state.top) { | ||
// case 3 | ||
// This case only happens when Sticky's bottom sticks to the screen bottom and | ||
// its height gets changed. Sticky should be in RELEASE status and update its | ||
// sticky bottom by calculating how much height it changed. | ||
const deltaHeight = pos + height - this.state.bottom; | ||
this.stickyBottom = bottom - delta + deltaHeight; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else { | ||
toRelease = false; | ||
} | ||
if (toRelease) { | ||
this.release(this.stickyTop); | ||
} | ||
break; | ||
} | ||
return; | ||
} else { | ||
// In this case, Sticky is shorter then viewport minus top offset | ||
// and will always fix to the top offset of viewport | ||
this.fix(this.state.top); | ||
} | ||
var delta = scrollDelta; | ||
// "top" and "bottom" are the positions that this.state.top and this.state.bottom project | ||
// on document from viewport. | ||
var top = this.scrollTop + this.state.top; | ||
var bottom = this.scrollTop + this.state.bottom; | ||
} | ||
this.delta = delta; | ||
} | ||
componentDidUpdate(prevProps, prevState) { | ||
if (prevState.status !== this.state.status && this.props.onStateChange) { | ||
this.props.onStateChange({ | ||
status: this.state.status | ||
}); | ||
} | ||
// There are 2 principles to make sure Sticky won't get wrong so much: | ||
// 1. Reset Sticky to the original postion when "top" <= topBoundary | ||
// 2. Release Sticky to the bottom boundary when "bottom" >= bottomBoundary | ||
if (top <= this.state.topBoundary) { | ||
// #1 | ||
this.reset(); | ||
} else if (bottom >= this.state.bottomBoundary) { | ||
// #2 | ||
this.stickyBottom = this.state.bottomBoundary; | ||
this.stickyTop = this.stickyBottom - this.state.height; | ||
this.release(this.stickyTop); | ||
} else { | ||
if (this.state.height > winHeight - this.state.top) { | ||
// In this case, Sticky is higher then viewport minus top offset | ||
switch (this.state.status) { | ||
case STATUS_ORIGINAL: | ||
this.release(this.state.y); | ||
this.stickyTop = this.state.y; | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
// Commentting out "break" is on purpose, because there is a chance to transit to FIXED | ||
// from ORIGINAL when calling window.scrollTo(). | ||
// break; | ||
/* eslint-disable-next-line no-fallthrough */ | ||
case STATUS_RELEASED: | ||
// If "top" and "bottom" are inbetween stickyTop and stickyBottom, then Sticky is in | ||
// RELEASE status. Otherwise, it changes to FIXED status, and its bottom sticks to | ||
// viewport bottom when scrolling down, or its top sticks to viewport top when scrolling up. | ||
this.stickyBottom = this.stickyTop + this.state.height; | ||
if (delta > 0 && bottom > this.stickyBottom) { | ||
this.fix(this.state.bottom - this.state.height); | ||
} else if (delta < 0 && top < this.stickyTop) { | ||
this.fix(this.state.top); | ||
} | ||
break; | ||
case STATUS_FIXED: | ||
var toRelease = true; | ||
var pos = this.state.pos; | ||
var height = this.state.height; | ||
// In regular cases, when Sticky is in FIXED status, | ||
// 1. it's top will stick to the screen top, | ||
// 2. it's bottom will stick to the screen bottom, | ||
// 3. if not the cases above, then it's height gets changed | ||
if (delta > 0 && pos === this.state.top) { | ||
// case 1, and scrolling down | ||
this.stickyTop = top - delta; | ||
this.stickyBottom = this.stickyTop + height; | ||
} else if (delta < 0 && pos === this.state.bottom - height) { | ||
// case 2, and scrolling up | ||
this.stickyBottom = bottom - delta; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else if (pos !== this.state.bottom - height && pos !== this.state.top) { | ||
// case 3 | ||
// This case only happens when Sticky's bottom sticks to the screen bottom and | ||
// its height gets changed. Sticky should be in RELEASE status and update its | ||
// sticky bottom by calculating how much height it changed. | ||
var deltaHeight = pos + height - this.state.bottom; | ||
this.stickyBottom = bottom - delta + deltaHeight; | ||
this.stickyTop = this.stickyBottom - height; | ||
} else { | ||
toRelease = false; | ||
} | ||
if (toRelease) { | ||
this.release(this.stickyTop); | ||
} | ||
break; | ||
} | ||
// check if we are up-to-date, is triggered in case of scroll restoration | ||
if (this.state.top !== prevState.top) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
const arePropsChanged = !shallowEqual(this.props, prevProps); | ||
if (arePropsChanged) { | ||
// if the props for enabling are toggled, then trigger the update or reset depending on the current props | ||
if (prevProps.enabled !== this.props.enabled) { | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}, () => { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
}); | ||
} else { | ||
// In this case, Sticky is shorter then viewport minus top offset | ||
// and will always fix to the top offset of viewport | ||
this.fix(this.state.top); | ||
this.setState({ | ||
activated: false | ||
}, () => { | ||
this.reset(); | ||
}); | ||
} | ||
} | ||
this.delta = delta; | ||
} | ||
}, { | ||
key: "componentDidUpdate", | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
var _this2 = this; | ||
if (prevState.status !== this.state.status && this.props.onStateChange) { | ||
this.props.onStateChange({ | ||
status: this.state.status | ||
}); | ||
} | ||
// check if we are up-to-date, is triggered in case of scroll restoration | ||
if (this.state.top !== prevState.top) { | ||
// if the top or bottomBoundary props were changed, then trigger the update | ||
else if (prevProps.top !== this.props.top || prevProps.bottomBoundary !== this.props.bottomBoundary) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
var arePropsChanged = !shallowEqual(this.props, prevProps); | ||
if (arePropsChanged) { | ||
// if the props for enabling are toggled, then trigger the update or reset depending on the current props | ||
if (prevProps.enabled !== this.props.enabled) { | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}, function () { | ||
_this2.updateInitialDimension(); | ||
_this2.update(); | ||
}); | ||
} else { | ||
this.setState({ | ||
activated: false | ||
}, function () { | ||
_this2.reset(); | ||
}); | ||
} | ||
} | ||
// if the top or bottomBoundary props were changed, then trigger the update | ||
else if (prevProps.top !== this.props.top || prevProps.bottomBoundary !== this.props.bottomBoundary) { | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
} | ||
} | ||
}, { | ||
key: "componentWillUnmount", | ||
value: function componentWillUnmount() { | ||
var subscribers = this.subscribers || []; | ||
for (var i = subscribers.length - 1; i >= 0; i--) { | ||
this.subscribers[i].unsubscribe(); | ||
} | ||
componentWillUnmount() { | ||
const subscribers = this.subscribers || []; | ||
for (var i = subscribers.length - 1; i >= 0; i--) { | ||
this.subscribers[i].unsubscribe(); | ||
} | ||
} | ||
componentDidMount() { | ||
// Only initialize the globals if this is the first | ||
// time this component type has been mounted | ||
if (!win) { | ||
win = window; | ||
doc = document; | ||
docEl = doc.documentElement; | ||
docBody = doc.body; | ||
winHeight = win.innerHeight || docEl.clientHeight; | ||
M = window.Modernizr; | ||
// No Sticky on lower-end browser when no Modernizr | ||
if (M && M.prefixed) { | ||
canEnableTransforms = M.csstransforms3d; | ||
TRANSFORM_PROP = M.prefixed('transform'); | ||
} | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
value: function componentDidMount() { | ||
// Only initialize the globals if this is the first | ||
// time this component type has been mounted | ||
if (!win) { | ||
win = window; | ||
doc = document; | ||
docEl = doc.documentElement; | ||
docBody = doc.body; | ||
winHeight = win.innerHeight || docEl.clientHeight; | ||
M = window.Modernizr; | ||
// No Sticky on lower-end browser when no Modernizr | ||
if (M && M.prefixed) { | ||
canEnableTransforms = M.csstransforms3d; | ||
TRANSFORM_PROP = M.prefixed('transform'); | ||
} | ||
} | ||
// when mount, the scrollTop is not necessary on the top | ||
this.scrollTop = docBody.scrollTop + docEl.scrollTop; | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}); | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
// bind the listeners regardless if initially enabled - allows the component to toggle sticky functionality | ||
this.subscribers = [subscribe('scrollStart', this.handleScrollStart.bind(this), { | ||
useRAF: true | ||
}), subscribe('scroll', this.handleScroll.bind(this), { | ||
useRAF: true, | ||
enableScrollInfo: true | ||
}), subscribe('resize', this.handleResize.bind(this), { | ||
enableResizeInfo: true | ||
})]; | ||
// when mount, the scrollTop is not necessary on the top | ||
this.scrollTop = docBody.scrollTop + docEl.scrollTop; | ||
if (this.props.enabled) { | ||
this.setState({ | ||
activated: true | ||
}); | ||
this.updateInitialDimension(); | ||
this.update(); | ||
} | ||
}, { | ||
key: "translate", | ||
value: function translate(style, pos) { | ||
var enableTransforms = canEnableTransforms && this.props.enableTransforms; | ||
if (enableTransforms && this.state.activated) { | ||
style[TRANSFORM_PROP] = 'translate3d(0,' + Math.round(pos) + 'px,0)'; | ||
} else { | ||
style.top = pos + 'px'; | ||
} | ||
// bind the listeners regardless if initially enabled - allows the component to toggle sticky functionality | ||
this.subscribers = [subscribe('scrollStart', this.handleScrollStart.bind(this), { | ||
useRAF: true | ||
}), subscribe('scroll', this.handleScroll.bind(this), { | ||
useRAF: true, | ||
enableScrollInfo: true | ||
}), subscribe('resize', this.handleResize.bind(this), { | ||
enableResizeInfo: true | ||
})]; | ||
} | ||
translate(style, pos) { | ||
const enableTransforms = canEnableTransforms && this.props.enableTransforms; | ||
if (enableTransforms && this.state.activated) { | ||
style[TRANSFORM_PROP] = 'translate3d(0,' + Math.round(pos) + 'px,0)'; | ||
} else { | ||
style.top = pos + 'px'; | ||
} | ||
}, { | ||
key: "shouldComponentUpdate", | ||
value: function shouldComponentUpdate(nextProps, nextState) { | ||
return !this.props.shouldFreeze() && !(shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState)); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var _this3 = this; | ||
// TODO, "overflow: auto" prevents collapse, need a good way to get children height | ||
var innerStyle = { | ||
position: this.state.status === STATUS_FIXED ? 'fixed' : 'relative', | ||
top: this.state.status === STATUS_FIXED ? '0px' : '', | ||
zIndex: this.props.innerZ | ||
}; | ||
var outerStyle = {}; | ||
} | ||
shouldComponentUpdate(nextProps, nextState) { | ||
return !this.props.shouldFreeze() && !(shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState)); | ||
} | ||
render() { | ||
// TODO, "overflow: auto" prevents collapse, need a good way to get children height | ||
const innerStyle = { | ||
position: this.state.status === STATUS_FIXED ? 'fixed' : 'relative', | ||
top: this.state.status === STATUS_FIXED ? '0px' : '', | ||
zIndex: this.props.innerZ | ||
}; | ||
const outerStyle = {}; | ||
// always use translate3d to enhance the performance | ||
this.translate(innerStyle, this.state.pos); | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
innerStyle.width = this.state.width + 'px'; | ||
outerStyle.height = this.state.height + 'px'; | ||
} | ||
var outerClasses = classNames('sticky-outer-wrapper', this.props.className, _defineProperty(_defineProperty({}, this.props.activeClass, this.state.status === STATUS_FIXED), this.props.releasedClass, this.state.status === STATUS_RELEASED)); | ||
var innerClasses = classNames('sticky-inner-wrapper', this.props.innerClass, _defineProperty({}, this.props.innerActiveClass, this.state.status === STATUS_FIXED)); | ||
var children = this.props.children; | ||
return /*#__PURE__*/React.createElement("div", { | ||
ref: function ref(outer) { | ||
_this3.outerElement = outer; | ||
}, | ||
className: outerClasses, | ||
style: outerStyle | ||
}, /*#__PURE__*/React.createElement("div", { | ||
ref: function ref(inner) { | ||
_this3.innerElement = inner; | ||
}, | ||
className: innerClasses, | ||
style: innerStyle | ||
}, typeof children === 'function' ? children({ | ||
status: this.state.status | ||
}) : children)); | ||
// always use translate3d to enhance the performance | ||
this.translate(innerStyle, this.state.pos); | ||
if (this.state.status !== STATUS_ORIGINAL) { | ||
innerStyle.width = this.state.width + 'px'; | ||
outerStyle.height = this.state.height + 'px'; | ||
} | ||
}]); | ||
}(Component); | ||
const outerClasses = classNames('sticky-outer-wrapper', this.props.className, { | ||
[this.props.activeClass]: this.state.status === STATUS_FIXED, | ||
[this.props.releasedClass]: this.state.status === STATUS_RELEASED | ||
}); | ||
const innerClasses = classNames('sticky-inner-wrapper', this.props.innerClass, { | ||
[this.props.innerActiveClass]: this.state.status === STATUS_FIXED | ||
}); | ||
const children = this.props.children; | ||
return /*#__PURE__*/React.createElement("div", { | ||
ref: outer => { | ||
this.outerElement = outer; | ||
}, | ||
className: outerClasses, | ||
style: outerStyle | ||
}, /*#__PURE__*/React.createElement("div", { | ||
ref: inner => { | ||
this.innerElement = inner; | ||
}, | ||
className: innerClasses, | ||
style: innerStyle | ||
}, typeof children === 'function' ? children({ | ||
status: this.state.status | ||
}) : children)); | ||
} | ||
} | ||
Sticky.displayName = 'Sticky'; | ||
Sticky.defaultProps = { | ||
shouldFreeze: function shouldFreeze() { | ||
shouldFreeze: function () { | ||
return false; | ||
@@ -477,0 +412,0 @@ }, |
{ | ||
"name": "react-stickynode", | ||
"version": "4.2.0", | ||
"version": "5.0.0", | ||
"description": "A performant and comprehensive React sticky component", | ||
@@ -50,3 +50,2 @@ "main": "./dist/cjs/Sticky.js", | ||
"classnames": "^2.0.0", | ||
"core-js": "^3.6.5", | ||
"prop-types": "^15.6.0", | ||
@@ -74,2 +73,3 @@ "shallowequal": "^1.0.0", | ||
"chromedriver": "^131.0.2", | ||
"core-js": "^3.39.0", | ||
"eslint": "^9.0.0", | ||
@@ -101,8 +101,3 @@ "eslint-plugin-react": "^7.37.1", | ||
"license": "BSD-3-Clause", | ||
"browserslist": [ | ||
"last 2 versions", | ||
"ie >= 11", | ||
"iOS >= 12", | ||
"Android >= 11" | ||
], | ||
"browserslist": "> 0.25%, not dead", | ||
"prettier": { | ||
@@ -109,0 +104,0 @@ "singleQuote": true, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6
45931
34
877
- Removedcore-js@^3.6.5
- Removedcore-js@3.40.0(transitive)