@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
587 lines (583 loc) • 25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = exports.TransitionBase = exports.Transition = void 0;
var _handle = require("@enact/core/handle");
var _propTypes = _interopRequireDefault(require("@enact/core/internal/prop-types"));
var _kind = _interopRequireDefault(require("@enact/core/kind"));
var _util = require("@enact/core/util");
var _react = require("react");
var _propTypes2 = _interopRequireDefault(require("prop-types"));
var _Resizable = require("../Resizable");
var _TransitionModule = _interopRequireDefault(require("./Transition.module.css"));
var _jsxRuntime = require("react/jsx-runtime");
var _excluded = ["css", "childRef", "children", "innerStyle"],
_excluded2 = ["visible"];
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": 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 _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 _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
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 _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); } /**
* A component that can transition its children components onto the screen.
*
* Transitions whether that's from off the edge of the screen or hidden inside or behind an
* already-on-screen component. You can switch types of transitions using the `type` property,
* change the direction they come in from using the `direction` property, or even adjust the
* transition timing function using `timingFunction`.
*
* @example
* <Transition visible={true} type="slide">
* <div>Set `visible` above to `false` to hide this element.</div>
* </Transition>
*
* @module ui/Transition
* @exports Transition
* @exports TransitionBase
*/
var formatter = function formatter(duration) {
return typeof duration === 'number' ? duration + 'ms' : duration;
};
/**
* The stateless structure of the component.
*
* In case you want to provide all of the state yourself.
* In general, you'll probably want to use the `Transition` instead of `TransitionBase`.
*
* @class TransitionBase
* @ui
* @memberof ui/Transition
* @public
*/
var TransitionBase = exports.TransitionBase = (0, _kind["default"])({
name: 'TransitionBase',
propTypes: /** @lends ui/Transition.TransitionBase.prototype */{
/**
* Provide a function to get the reference to the child node (the one with the content) at
* render time.
*
*Useful if you need to measure or interact with the node directly.
*
* @type {Object|Function}
* @default null
* @public
*/
childRef: _propTypes["default"].ref,
/**
* The node to be transitioned.
*
* @type {Node}
* @public
*/
children: _propTypes2["default"].node,
/**
* The height of the transition when `type` is set to `'clip'`, used when direction is
* 'left' or 'right'.
*
* @type {Number}
* @default null
* @public
*/
clipHeight: _propTypes2["default"].number,
/**
* The width of the transition when `type` is set to `'clip'`, used when direction is 'left'
* or 'right'.
*
* @type {Number}
* @default null
* @public
*/
clipWidth: _propTypes2["default"].number,
/**
* Customizes the component by mapping the supplied collection of CSS class names to the
* corresponding internal elements and states of this component.
*
* The following classes are supported:
*
* * `transition` - The root component class
* * `inner` - The element inside the transition. This is the container for the transitioning content.
* * `shown` - Applied when content is present (visible), related to the `visible` prop/state
* * `hidden` - Applied when content is not present (hiding), related to the `visible` prop/state
* * `slide` - Applied when the `slide` `type` is set
* * `fade` - Applied when the `fade` `type` is set
* * `clip` - Applied when the `clip` `type` is set
* * `up` - Applied when the `direction` `up` is set
* * `right` - Applied when the `direction` `right` is set
* * `down` - Applied when the `direction` `down` is set
* * `left` - Applied when the `direction` `left` is set
* * `short` - Applied when the `duration` `short` is set
* * `medium` - Applied when the `duration` `medium` is set
* * `long` - Applied when the `duration` `long` is set
* * `ease` - Applied when the `timingFunction` `ease` is set
* * `ease-in` - Applied when the `timingFunction` `ease-in` is set
* * `ease-out` - Applied when the `timingFunction` `ease-out` is set
* * `ease-in-out` - Applied when the `timingFunction` `ease-in-out` is set
* * `ease-in-quart` - Applied when the `timingFunction` `ease-in-quart` is set
* * `ease-out-quart` - Applied when the `timingFunction` `ease-out-quart` is set
* * `linear` - Applied when the `timingFunction` `linear` is set
*
* @type {Object}
* @public
*/
css: _propTypes2["default"].object,
/**
* Sets the direction of transition. Where the component will move *to*; the destination.
* Supported directions are: `'up'`, `'right'`, `'down'`, `'left'`.
*
* @type {String}
* @default 'up'
* @public
*/
direction: _propTypes2["default"].oneOf(['up', 'right', 'down', 'left']),
/**
* Controls how long the transition should take.
* Supported preset durations are: `'short'` (250ms), `'medium'` (500ms), and `'long'` (1s).
* `'medium'` (500ms) is default when no others are specified.
* Any valid CSS duration value is also accepted, e.g. "200ms" or "3s". Pure numeric values
* are also supported and treated as milliseconds.
*
* @type {String|Number}
* @default 'medium'
* @public
*/
duration: _propTypes2["default"].oneOfType([_propTypes2["default"].string, _propTypes2["default"].number]),
/**
* Disables transition animation.
*
* When `false`, visibility changes animate.
*
* @type {Boolean}
* @default false
* @public
*/
noAnimation: _propTypes2["default"].bool,
/**
* The transition timing function.
*
* * Supported function names are: `ease`, `ease-in`, `ease-out`, `ease-in-out`, `ease-in-quart`,
* `ease-out-quart`, and `linear`.
*
* @type {String}
* @default 'ease-in-out'
* @public
*/
timingFunction: _propTypes2["default"].oneOf(['ease', 'ease-in', 'ease-out', 'ease-in-out', 'ease-in-quart', 'ease-out-quart', 'linear']),
/**
* The type of transition to affect the content.
*
* * Supported types are: `'slide'`, `'clip'`, and `'fade'`.
*
* Details on types:
* * `'slide'` - Typically used for bringing something which is off the edge of the screen,
* and not visible, onto the screen. Think of a popup, toast, notification, dialog, or
* an overlaying menu. This requires no re-rendering or repainting of the screen during
* the transition, making it very performant. However, this does not affect layout at
* all, which makes it less useful for transitioning from a place already on the
* screen.
* * `'clip'` - This is useful for showing a component that transitions-in from a location
* that is already on the screen. Examples would be an expanding header or an
* accordion. This type does affect layout, its current size will push other sibling
* elements to make room for itself. Because of this, repainting the layout does happen
* during transition.
* * `'fade'` - Fade the components onto the screen, from 0 opacity (completely invisible)
* to 1 (full visibility). Pretty basic, but useful for fading on/off a tooltip, a
* menu, a panel, or even view contents. This does not affect layout at all.
*
* @type {String}
* @default 'slide'
* @public
*/
type: _propTypes2["default"].oneOf(['slide', 'clip', 'fade']),
/**
* Sets the visibility of the component, which determines whether it's on screen or off.
*
* @type {Boolean}
* @default true
* @public
*/
visible: _propTypes2["default"].bool
},
defaultProps: {
noAnimation: false,
direction: 'up',
duration: 'medium',
timingFunction: 'ease-in-out',
type: 'slide',
visible: true
},
styles: {
css: _TransitionModule["default"],
className: 'transition',
publicClassNames: true
},
computed: {
className: function className(_ref) {
var css = _ref.css,
direction = _ref.direction,
duration = _ref.duration,
noAnimation = _ref.noAnimation,
timingFunction = _ref.timingFunction,
type = _ref.type,
visible = _ref.visible,
styler = _ref.styler;
return styler.append(visible ? 'shown' : 'hidden', direction && css[direction], !noAnimation && duration && css[duration], !noAnimation && timingFunction && css[timingFunction], css[type]);
},
innerStyle: function innerStyle(_ref2) {
var clipWidth = _ref2.clipWidth,
css = _ref2.css,
direction = _ref2.direction,
duration = _ref2.duration,
type = _ref2.type;
var style = {};
if (type === 'clip' && (direction === 'left' || direction === 'right')) {
style.width = clipWidth;
} else if ((type === 'fade' || type === 'slide') && duration && !css[duration]) {
// If it's a number, assume it's milliseconds, if not, assume it's already a CSS duration string (like "200ms" or "2s")
style.transitionDuration = formatter(duration);
}
return style;
},
style: function style(_ref3) {
var clipHeight = _ref3.clipHeight,
css = _ref3.css,
direction = _ref3.direction,
duration = _ref3.duration,
type = _ref3.type,
visible = _ref3.visible,
_style = _ref3.style;
if (type === 'clip') {
_style = _objectSpread(_objectSpread({}, _style), {}, {
overflow: 'hidden'
});
if (visible && (direction === 'up' || direction === 'down')) {
_style.height = clipHeight;
}
// If duration isn't a known named string, assume it is a CSS duration value
if (duration && !css[duration]) {
// If it's a number, assume it's milliseconds, if not, assume it's already a CSS duration string (like "200ms" or "2s")
_style.transitionDuration = formatter(duration);
}
}
return _style;
}
},
render: function render(_ref4) {
var css = _ref4.css,
childRef = _ref4.childRef,
children = _ref4.children,
innerStyle = _ref4.innerStyle,
rest = _objectWithoutProperties(_ref4, _excluded);
delete rest.clipHeight;
delete rest.clipWidth;
delete rest.direction;
delete rest.duration;
delete rest.noAnimation;
delete rest.timingFunction;
delete rest.type;
delete rest.visible;
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, rest), {}, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: css.inner,
style: innerStyle,
ref: childRef,
children: children
})
}));
}
});
var TRANSITION_STATE = {
INIT: 0,
// closed and unmeasured
MEASURE: 1,
// open but need to measure
READY: 2 // measured and ready
};
/**
* A stateful component that allows for applying transitions to its child items via configurable
* properties and events.
*
* @class Transition
* @ui
* @memberof ui/Transition
* @public
*/
var Transition = exports.Transition = /*#__PURE__*/function (_Component) {
function Transition(props) {
var _this;
_classCallCheck(this, Transition);
_this = _callSuper(this, Transition, [props]);
_this.measuringJob = new _util.Job(function () {
_this.setState({
renderState: TRANSITION_STATE.MEASURE
});
});
_this.handleResize = function () {
// @TODO oddly, using the setState callback is required here to ensure that the bounds
// are remeasured in a separate tick
_this.setState({
initialHeight: null
}, _this.measureInner);
};
_this.handleTransitionEnd = function (ev) {
(0, _handle.forward)('onTransitionEnd', ev, _this.props);
if (ev.target === _this.childNode) {
if (!_this.props.visible) {
(0, _handle.forward)('onHide', {
type: 'onHide',
currentTarget: ev.currentTarget
}, _this.props);
} else if (_this.props.visible) {
(0, _handle.forward)('onShow', {
type: 'onShow',
currentTarget: ev.currentTarget
}, _this.props);
}
}
};
_this.measureInner = function () {
if (_this.childNode) {
var initialHeight = _this.childNode.scrollHeight;
var initialWidth = _this.childNode.scrollWidth;
if (initialHeight !== _this.state.initialHeight || initialWidth !== _this.state.initialWidth) {
_this.setState({
initialHeight: initialHeight,
initialWidth: initialWidth,
renderState: TRANSITION_STATE.READY
});
}
}
};
_this.childRef = function (node) {
_this.childNode = node;
};
_this.state = {
initialHeight: null,
initialWidth: null,
prevVisible: props.visible,
renderState: props.visible ? TRANSITION_STATE.READY : TRANSITION_STATE.INIT
};
_this.resizeRegistry = null;
return _this;
}
_inherits(Transition, _Component);
return _createClass(Transition, [{
key: "componentDidMount",
value: function componentDidMount() {
if (!this.props.visible) {
this.measuringJob.idle();
} else {
this.measureInner();
}
if (this.context && typeof this.context === 'function') {
this.resizeRegistry = this.context(this.handleResize);
}
}
}, {
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps, nextState) {
// Don't update if only updating the height and we're not visible
return this.state.initialHeight === nextState.initialHeight || this.props.visible || nextProps.visible;
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
var _this$props = this.props,
noAnimation = _this$props.noAnimation,
visible = _this$props.visible;
var _this$state = this.state,
initialHeight = _this$state.initialHeight,
renderState = _this$state.renderState;
if (this.state.renderState === TRANSITION_STATE.MEASURE) {
this.measuringJob.stop();
}
// Checking that something changed that wasn't the visibility
// or the initialHeight state or checking if component should be visible but doesn't have a height
if (visible === prevProps.visible && initialHeight === prevState.initialHeight && renderState !== TRANSITION_STATE.INIT || initialHeight == null && visible) {
this.measureInner();
}
if (noAnimation) {
if (!prevProps.visible && visible) {
(0, _handle.forwardCustom)('onShow')(null, this.props);
} else if (prevProps.visible && !visible) {
(0, _handle.forwardCustom)('onHide')(null, this.props);
}
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.measuringJob.stop();
if (this.resizeRegistry) {
this.resizeRegistry.unregister();
}
}
}, {
key: "render",
value: function render() {
var _this$props2 = this.props,
visible = _this$props2.visible,
props = _objectWithoutProperties(_this$props2, _excluded2);
delete props.onHide;
delete props.onShow;
delete props.textSize;
switch (this.state.renderState) {
// If we are deferring children, don't render any
case TRANSITION_STATE.INIT:
return null;
// If we're transitioning to visible but don't have a measurement yet, create the
// transition container with its children so we can measure. Measuring will cause a
// state change to trigger the animation.
case TRANSITION_STATE.MEASURE:
return /*#__PURE__*/(0, _jsxRuntime.jsx)(TransitionBase, _objectSpread(_objectSpread({}, props), {}, {
childRef: this.childRef,
visible: false
}));
case TRANSITION_STATE.READY:
return /*#__PURE__*/(0, _jsxRuntime.jsx)(TransitionBase, _objectSpread(_objectSpread({}, props), {}, {
childRef: this.childRef,
visible: visible,
clipHeight: this.state.initialHeight,
clipWidth: this.state.initialWidth,
onTransitionEnd: this.handleTransitionEnd
}));
}
}
}], [{
key: "getDerivedStateFromProps",
value: function getDerivedStateFromProps(props, state) {
if (!state.prevVisible && props.visible) {
return {
initialHeight: null,
initialWidth: null,
prevVisible: props.visible,
renderState: TRANSITION_STATE.MEASURE
};
}
return null;
}
}]);
}(_react.Component);
Transition.contextType = _Resizable.ResizeContext;
Transition.propTypes = /** @lends ui/Transition.Transition.prototype */{
/**
* The node to be transitioned.
*
* @type {Node}
* @public
*/
children: _propTypes2["default"].node,
/**
* The direction of transition (i.e. where the component will move *to*; the destination).
*
* * Supported directions are: `'up'`, `'right'`, `'down'`, `'left'`.
*
* @type {String}
* @default 'up'
* @public
*/
direction: _propTypes2["default"].oneOf(['up', 'right', 'down', 'left']),
/**
* Controls how long the transition should take.
*
* * Supported preset durations are: `'short'` (250ms), `'medium'` (500ms), and `'long'` (1s).
* `'medium'` (500ms) is default when no others are specified.
*
* Any valid CSS duration value is also accepted, e.g. "200ms" or "3s". Pure numeric values
* are also supported and treated as milliseconds.
*
* @type {String|Number}
* @default 'medium'
* @public
*/
duration: _propTypes2["default"].oneOfType([_propTypes2["default"].string, _propTypes2["default"].number]),
/**
* Disables transition animation.
*
* When `false`, visibility changes animate.
*
* @type {Boolean}
* @default false
* @public
*/
noAnimation: _propTypes2["default"].bool,
/**
* Called after transition for hiding is finished.
*
* @type {Function}
* @public
*/
onHide: _propTypes2["default"].func,
/**
* Called after transition for showing is finished.
*
* @type {Function}
* @public
*/
onShow: _propTypes2["default"].func,
/**
* The transition timing function.
* Supported function names are: `ease`, `ease-in`, `ease-out`, `ease-in-out`, `ease-in-quart`,
* `ease-out-quart`, and `linear`.
*
* @type {String}
* @default 'ease-in-out'
* @public
*/
timingFunction: _propTypes2["default"].oneOf(['ease', 'ease-in', 'ease-out', 'ease-in-out', 'ease-in-quart', 'ease-out-quart', 'linear']),
/**
* The type of transition to affect the content.
*
* * Supported types are: `'slide'`, `'clip'`, and `'fade'`.
*
* Details on types:
* * `'slide'` - Typically used for bringing something which is off the edge of the screen,
* and not visible, onto the screen. Think of a popup, toast, notification, dialog, or
* an overlaying menu. This requires no re-rendering or repainting of the screen during
* the transition, making it very performant. However, this does not affect layout at
* all, which makes it less useful for transitioning from a place already on the
* screen.
* * `'clip'` - This is useful for showing a component that transitions-in from a location
* that is already on the screen. Examples would be an expanding header or an
* accordion. This type does affect layout, its current size will push other sibling
* elements to make room for itself. Because of this, repainting the layout does happen
* during transition.
* * `'fade'` - Fade the components onto the screen, from 0 opacity (completely invisible)
* to 1 (full visibility). Pretty basic, but useful for fading on/off a tooltip, a
* menu, a panel, or even view contents. This does not affect layout at all.
*
* @type {String}
* @default 'slide'
* @public
*/
type: _propTypes2["default"].oneOf(['slide', 'clip', 'fade']),
/**
* The visibility of the component, which determines whether it's on the screen or off.
*
* @type {Boolean}
* @default true
* @public
*/
visible: _propTypes2["default"].bool
};
Transition.defaultProps = {
direction: 'up',
duration: 'medium',
noAnimation: false,
timingFunction: 'ease-in-out',
type: 'slide',
visible: true
};
var _default = exports["default"] = Transition;