semantic-ui-react
Version:
The official Semantic-UI-React integration.
394 lines (328 loc) • 11.9 kB
JavaScript
import _slicedToArray from 'babel-runtime/helpers/slicedToArray';
import _extends from 'babel-runtime/helpers/extends';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _createClass from 'babel-runtime/helpers/createClass';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
import _forEach from 'lodash/forEach';
import _invoke from 'lodash/invoke';
import _includes from 'lodash/includes';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { customPropTypes, getElementType, getUnhandledProps, META, normalizeOffset, isBrowser } from '../../lib';
/**
* Visibility provides a set of callbacks for when a content appears in the viewport.
*/
var Visibility = function (_Component) {
_inherits(Visibility, _Component);
function Visibility() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, Visibility);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Visibility.__proto__ || Object.getPrototypeOf(Visibility)).call.apply(_ref, [this].concat(args))), _this), _this.calculations = {
topPassed: false,
bottomPassed: false,
topVisible: false,
bottomVisible: false,
fits: false,
passing: false,
onScreen: false,
offScreen: false
}, _this.firedCallbacks = [], _this.execute = function (callback, name) {
var _this$props = _this.props,
continuous = _this$props.continuous,
once = _this$props.once;
if (!callback) return;
// Reverse callbacks aren't fired continuously
if (_this.calculations[name] === false) return;
// Always fire callback if continuous = true
if (continuous) {
callback(null, _extends({}, _this.props, { calculations: _this.calculations }));
return;
}
// If once = true, fire callback only if it wasn't fired before
if (once) {
if (!_includes(_this.firedCallbacks, name)) {
_this.firedCallbacks.push(name);
callback(null, _extends({}, _this.props, { calculations: _this.calculations }));
}
return;
}
// Fire callback only if the value changed
if (_this.calculations[name] !== _this.oldCalculations[name]) {
callback(null, _extends({}, _this.props, { calculations: _this.calculations }));
}
}, _this.fireOnPassed = function () {
var _this$calculations = _this.calculations,
percentagePassed = _this$calculations.percentagePassed,
pixelsPassed = _this$calculations.pixelsPassed;
var onPassed = _this.props.onPassed;
_forEach(onPassed, function (callback, passed) {
var pixelsValue = Number(passed);
if (pixelsValue && pixelsPassed >= pixelsValue) {
_this.execute(callback, passed);
return;
}
var matchPercentage = ('' + passed).match(/^(\d+)%$/);
if (!matchPercentage) return;
var percentageValue = Number(matchPercentage[1]) / 100;
if (percentagePassed >= percentageValue) _this.execute(callback, passed);
});
}, _this.handleScroll = function () {
if (_this.ticking) return;
_this.ticking = true;
requestAnimationFrame(_this.handleUpdate);
}, _this.handleRef = function (c) {
return _this.ref = c;
}, _this.handleUpdate = function () {
_this.ticking = false;
var offset = _this.props.offset;
var _this$ref$getBounding = _this.ref.getBoundingClientRect(),
bottom = _this$ref$getBounding.bottom,
height = _this$ref$getBounding.height,
top = _this$ref$getBounding.top,
width = _this$ref$getBounding.width;
var _normalizeOffset = normalizeOffset(offset),
_normalizeOffset2 = _slicedToArray(_normalizeOffset, 2),
topOffset = _normalizeOffset2[0],
bottomOffset = _normalizeOffset2[1];
var topPassed = top < topOffset;
var bottomPassed = bottom < bottomOffset;
var pixelsPassed = bottomPassed ? 0 : Math.max(top * -1, 0);
var percentagePassed = pixelsPassed / height;
var bottomVisible = bottom >= bottomOffset && bottom <= window.innerHeight;
var topVisible = top >= topOffset && top <= window.innerHeight;
var fits = topVisible && bottomVisible;
var passing = topPassed && !bottomPassed;
var onScreen = (topVisible || topPassed) && !bottomPassed;
var offScreen = !onScreen;
_this.oldCalculations = _this.calculations;
_this.calculations = {
bottomPassed: bottomPassed,
bottomVisible: bottomVisible,
fits: fits,
height: height,
passing: passing,
percentagePassed: percentagePassed,
pixelsPassed: pixelsPassed,
offScreen: offScreen,
onScreen: onScreen,
topPassed: topPassed,
topVisible: topVisible,
width: width
};
_this.fireCallbacks();
}, _temp), _possibleConstructorReturn(_this, _ret);
}
_createClass(Visibility, [{
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(_ref2) {
var continuous = _ref2.continuous,
once = _ref2.once;
var cleanOut = continuous !== this.props.continuous || once !== this.props.once;
if (cleanOut) this.firedCallbacks = [];
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
if (!isBrowser) return;
var _props = this.props,
context = _props.context,
fireOnMount = _props.fireOnMount;
context.addEventListener('scroll', this.handleScroll);
if (fireOnMount) this.handleUpdate();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (!isBrowser) return;
var context = this.props.context;
context.removeEventListener('scroll', this.handleScroll);
}
}, {
key: 'fireCallbacks',
value: function fireCallbacks() {
var _this2 = this;
var _props2 = this.props,
onBottomPassed = _props2.onBottomPassed,
onBottomPassedReverse = _props2.onBottomPassedReverse,
onBottomVisible = _props2.onBottomVisible,
onBottomVisibleReverse = _props2.onBottomVisibleReverse,
onPassing = _props2.onPassing,
onPassingReverse = _props2.onPassingReverse,
onTopPassed = _props2.onTopPassed,
onTopPassedReverse = _props2.onTopPassedReverse,
onTopVisible = _props2.onTopVisible,
onTopVisibleReverse = _props2.onTopVisibleReverse,
onOffScreen = _props2.onOffScreen,
onOnScreen = _props2.onOnScreen;
var callbacks = {
bottomPassed: onBottomPassed,
bottomVisible: onBottomVisible,
passing: onPassing,
offScreen: onOffScreen,
onScreen: onOnScreen,
topPassed: onTopPassed,
topVisible: onTopVisible
};
var reverse = {
bottomPassed: onBottomPassedReverse,
bottomVisible: onBottomVisibleReverse,
passing: onPassingReverse,
topPassed: onTopPassedReverse,
topVisible: onTopVisibleReverse
};
_invoke(this.props, 'onUpdate', null, _extends({}, this.props, { calculations: this.calculations }));
this.fireOnPassed();
_forEach(callbacks, function (callback, name) {
return _this2.execute(callback, name);
});
_forEach(reverse, function (callback, name) {
return _this2.execute(callback, name);
});
}
}, {
key: 'render',
value: function render() {
var children = this.props.children;
var ElementType = getElementType(Visibility, this.props);
var rest = getUnhandledProps(Visibility, this.props);
return React.createElement(
ElementType,
_extends({}, rest, { ref: this.handleRef }),
children
);
}
}]);
return Visibility;
}(Component);
Visibility.defaultProps = {
context: isBrowser ? window : null,
continuous: false,
offset: [0, 0],
once: true
};
Visibility._meta = {
name: 'Visibility',
type: META.TYPES.BEHAVIOR
};
Visibility.handledProps = ['as', 'children', 'context', 'continuous', 'fireOnMount', 'offset', 'onBottomPassed', 'onBottomPassedReverse', 'onBottomVisible', 'onBottomVisibleReverse', 'onOffScreen', 'onOnScreen', 'onPassed', 'onPassing', 'onPassingReverse', 'onTopPassed', 'onTopPassedReverse', 'onTopVisible', 'onTopVisibleReverse', 'onUpdate', 'once'];
export default Visibility;
Visibility.propTypes = process.env.NODE_ENV !== "production" ? {
/** An element type to render as (string or function). */
as: customPropTypes.as,
/** Primary content. */
children: PropTypes.node,
/** Context which visibility should attach onscroll events. */
context: PropTypes.object,
/**
* When set to true a callback will occur anytime an element passes a condition not just immediately after the
* threshold is met.
*/
continuous: PropTypes.bool,
/** Fires callbacks immediately after mount. */
fireOnMount: PropTypes.bool,
/**
* Element's bottom edge has passed top of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onBottomPassed: PropTypes.func,
/**
* Element's bottom edge has not passed top of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onBottomPassedReverse: PropTypes.func,
/**
* Element's bottom edge has passed bottom of screen
*
* @param {null}
* @param {object} data - All props.
*/
onBottomVisible: PropTypes.func,
/**
* Element's bottom edge has not passed bottom of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onBottomVisibleReverse: PropTypes.func,
/**
* Value that context should be adjusted in pixels. Useful for making content appear below content fixed to the
* page.
*/
offset: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf([PropTypes.number, PropTypes.string])]),
/** When set to false a callback will occur each time an element passes the threshold for a condition. */
once: PropTypes.bool,
/** Element is not visible on the screen. */
onPassed: PropTypes.object,
/**
* Any part of an element is visible on screen.
*
* @param {null}
* @param {object} data - All props.
*/
onPassing: PropTypes.func,
/**
* Element's top has not passed top of screen but bottom has.
*
* @param {null}
* @param {object} data - All props.
*/
onPassingReverse: PropTypes.func,
/**
* Element is not visible on the screen.
*
* @param {null}
* @param {object} data - All props.
*/
onOffScreen: PropTypes.func,
/**
* Element is visible on the screen.
*
* @param {null}
* @param {object} data - All props.
*/
onOnScreen: PropTypes.func,
/**
* Element's top edge has passed top of the screen.
*
* @param {null}
* @param {object} data - All props.
*/
onTopPassed: PropTypes.func,
/**
* Element's top edge has not passed top of the screen.
*
* @param {null}
* @param {object} data - All props.
*/
onTopPassedReverse: PropTypes.func,
/**
* Element's top edge has passed bottom of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onTopVisible: PropTypes.func,
/**
* Element's top edge has not passed bottom of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onTopVisibleReverse: PropTypes.func,
/**
* Element's top edge has passed bottom of screen.
*
* @param {null}
* @param {object} data - All props.
*/
onUpdate: PropTypes.func
} : {};