UNPKG

@enact/ui

Version:

A collection of simplified unstyled cross-platform UI components for Enact

587 lines (583 loc) 25 kB
"use strict"; 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;