@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
372 lines (365 loc) • 15.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.wrapWithView = exports["default"] = exports.View = void 0;
var _util = require("@enact/core/util");
var _react = require("react");
var _propTypes = _interopRequireDefault(require("prop-types"));
var _Arranger = require("./Arranger");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
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 _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); } /*
* Exports the {@link ui/ViewManager.View} component.
*/ // If the View was "appearing", then entering will always be false and this will not result in a
// re-render. If the view should enter, state.enter will be true and this will toggle it to false
// causing a re-render.
var clearEntering = function clearEntering(_ref) {
var entering = _ref.entering;
return entering ? {
entering: false
} : null;
};
/**
* A `View` wraps a set of children for {@link ui/ViewManager.ViewManager}.
* It is not intended to be used directly
*
* @class View
* @memberof ui/ViewManager
* @private
*/
var View = exports.View = /*#__PURE__*/function (_Component) {
function View(props) {
var _this;
_classCallCheck(this, View);
_this = _callSuper(this, View, [props]);
_this.enteringJob = new _util.Job(function () {
_this.setState(clearEntering);
});
/**
* Initiates a new transition
*
* @param {Function} arranger Arranger function to call (enter, leave)
* @param {Function} callback Completion callback
* @param {Boolean} [noAnimation] `true` to disable animation for this transition
* @returns {undefined}
* @private
*/
_this.prepareTransition = function (arranger, callback, noAnimation) {
var _getParentRef;
var _this$props = _this.props,
duration = _this$props.duration,
getParentRef = _this$props.getParentRef,
index = _this$props.index,
_this$props$previousI = _this$props.previousIndex,
previousIndex = _this$props$previousI === void 0 ? index : _this$props$previousI,
_this$props$renderedI = _this$props.renderedIndex,
renderedIndex = _this$props$renderedI === void 0 ? 0 : _this$props$renderedI,
reverseTransition = _this$props.reverseTransition,
rtl = _this$props.rtl;
// Need to ensure that we have a valid node reference before we animation. Sometimes, React
// will replace the node after mount causing a reference cached there to be invalid.
_this.node = getParentRef === null || getParentRef === void 0 || (_getParentRef = getParentRef()) === null || _getParentRef === void 0 || (_getParentRef = _getParentRef.children) === null || _getParentRef === void 0 ? void 0 : _getParentRef[renderedIndex];
if (_this.animation && _this.animation.playState !== 'finished' && _this.changeDirection) {
_this.animation.reverse();
} else {
_this.animation = arranger({
from: previousIndex,
node: _this.node,
reverse: reverseTransition,
rtl: rtl,
to: index,
fill: 'forwards',
duration: duration
});
}
// Must set a new handler here to ensure the right callback is invoked
_this.animation.onfinish = function () {
_this.animation = null;
// Possible for the animation callback to still be fired after the node has been
// unmounted if it finished before the unmount can cancel it so we check for that.
if (_this.node) {
callback();
}
};
// disable animation when the instance or props flag is true
if (noAnimation || _this.props.noAnimation) {
_this.animation.finish();
}
};
_this.animation = null;
_this.state = {
entering: !props.appearing
};
return _this;
}
_inherits(View, _Component);
return _createClass(View, [{
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps) {
if (nextProps.leaving) {
// FIXME: This is generally a bad practice to mutate local state in sCU but is necessary
// for the time being to ensure that a view that is reversed before it completes
// entering will transition correctly out of the viewport.
this.changeDirection = this.shouldChangeDirection(this.props, nextProps);
return false;
}
return true;
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
this.changeDirection = this.shouldChangeDirection(prevProps, this.props);
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.enteringJob.stop();
this.node = null;
if (this.animation && typeof this.animation.cancel === 'function') {
this.animation.cancel();
}
}
}, {
key: "shouldChangeDirection",
value: function shouldChangeDirection(prevProps, nextProps) {
return this.animation ? prevProps.reverseTransition !== nextProps.reverseTransition : false;
}
}, {
key: "componentWillAppear",
value: function componentWillAppear(callback) {
var arranger = this.props.arranger;
if (arranger && arranger.stay) {
this.prepareTransition(arranger.stay, callback, true);
} else {
callback();
}
}
}, {
key: "componentDidAppear",
value: function componentDidAppear() {
this.setState(clearEntering);
}
// This is called at the same time as componentDidMount() for components added to an existing
// TransitionGroup. It will block other animations from occurring until callback is called. It
// will not be called on the initial render of a TransitionGroup.
}, {
key: "componentWillEnter",
value: function componentWillEnter(callback) {
var _this$props2 = this.props,
appearing = _this$props2.appearing,
arranger = _this$props2.arranger,
reverseTransition = _this$props2.reverseTransition,
enteringProp = _this$props2.enteringProp;
// This can happen if the panel was going to be removed and the animation was canceled,
// causing this panel to re-enter.
if (!appearing && enteringProp && !this.state.entering) {
this.setState({
entering: true
});
}
if (arranger) {
this.prepareTransition(reverseTransition ? arranger.leave : arranger.enter, callback);
} else {
callback();
}
}
}, {
key: "componentDidEnter",
value: function componentDidEnter() {
var _this$props3 = this.props,
enteringDelay = _this$props3.enteringDelay,
enteringProp = _this$props3.enteringProp;
if (enteringProp) {
// FIXME: `startRafAfter` is a temporary solution using rAF. We need a better way to handle
// transition cycle and component life cycle to be in sync. See ENYO-4835.
this.enteringJob.startRafAfter(enteringDelay);
} else {
this.enteringJob.start();
}
}
}, {
key: "componentWillStay",
value: function componentWillStay(callback) {
var arranger = this.props.arranger;
if (arranger && arranger.stay) {
this.prepareTransition(arranger.stay, callback);
} else {
callback();
}
}
// This is called when the child has been removed from the ReactTransitionGroup. Though the
// child has been removed, ReactTransitionGroup will keep it in the DOM until callback is
// called.
}, {
key: "componentWillLeave",
value: function componentWillLeave(callback) {
var _this$props4 = this.props,
arranger = _this$props4.arranger,
reverseTransition = _this$props4.reverseTransition;
this.enteringJob.stop();
if (arranger) {
this.prepareTransition(reverseTransition ? arranger.enter : arranger.leave, callback);
} else {
callback();
}
}
}, {
key: "render",
value: function render() {
var _this$props5 = this.props,
enteringProp = _this$props5.enteringProp,
children = _this$props5.children,
childProps = _this$props5.childProps;
if (enteringProp || childProps) {
var props = Object.assign({}, childProps);
if (enteringProp) {
props[enteringProp] = this.state.entering;
}
return /*#__PURE__*/(0, _react.cloneElement)(children, props);
} else {
return _react.Children.only(children);
}
}
}]);
}(_react.Component); // Not a true render method but instead a wrapper for TransitionGroup to wrap arbitrary children
// with a TransitionGroup-compatible child that supports animation
//
// eslint-disable-next-line enact/display-name
View.propTypes = /** @lends ui/ViewManager.View.prototype */{
children: _propTypes["default"].node.isRequired,
/**
* Time in milliseconds to complete a transition
*
* @type {Number}
* @required
* @public
*/
duration: _propTypes["default"].number.isRequired,
/**
* Set to `true` when the View should 'appear' without transitioning into the viewport
*
* @type {Boolean}
* @public
*/
appearing: _propTypes["default"].bool,
/**
* Arranger to control the animation
*
* @type {Arranger}
* @public
*/
arranger: _Arranger.shape,
/**
* An object containing properties to be passed to each child.
*
* @type {Object}
* @public
*/
childProps: _propTypes["default"].object,
/**
* Time, in milliseconds, to wait after a view has entered to inform it by passing the
* `enteringProp` as `false`.
*
* @type {Number}
* @default 0
* @public
*/
enteringDelay: _propTypes["default"].number,
/**
* Name of the property to pass to the wrapped view to indicate when it is entering the
* viewport. When `true`, the view has been created but has not transitioned into place.
* When `false`, the view has finished its transition.
*
* The notification can be delayed by setting `enteringDelay`. If not set, the view will not
* be notified of the change in transition.
*
* @type {String}
* @public
*/
enteringProp: _propTypes["default"].string,
/**
* A getter function for a DOM node of the parent element
*
* @type {Function}
* @private
*/
getParentRef: _propTypes["default"].func,
/**
* Index of the currently 'active' view.
*
* @type {Number}
*/
index: _propTypes["default"].number,
/**
* When `true`, indicates if a view is currently leaving.
*
* @type {Boolean}
*/
leaving: _propTypes["default"].bool,
/**
* When `true`, indicates if the transition should be animated
*
* @type {Boolean}
* @default true
* @public
*/
noAnimation: _propTypes["default"].bool,
/**
* Index of the previously 'active' view.
*
* @type {Number}
*/
previousIndex: _propTypes["default"].number,
/**
* Index of the view node among the rendered children of the parent node
*
* @type {Number}
* @private
*/
renderedIndex: _propTypes["default"].number,
/**
* When `true`, indicates if the transition should be reversed. The effect depends on how the provided
* `arranger` handles reversal.
*
* @type {Boolean}
* @default false
*/
reverseTransition: _propTypes["default"].bool,
/**
* When `true`, indicates the current locale uses right-to-left reading order.
*
* The effect depends on how the provided `arranger` handles this option.
*
* @type {Boolean}
*/
rtl: _propTypes["default"].bool
};
View.defaultProps = {
appearing: false,
enteringDelay: 0,
index: 0,
reverseTransition: false
};
var wrapWithView = exports.wrapWithView = function wrapWithView(config) {
return function (child) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(View, _objectSpread(_objectSpread({}, config), {}, {
children: child
}));
};
};
var _default = exports["default"] = View;