UNPKG

@enact/ui

Version:

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

606 lines (596 loc) 25.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Transition = Transition; exports["default"] = exports.TransitionBase = 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 = ["children", "direction", "duration", "noAnimation", "onHide", "onShow", "timingFunction", "type", "visible"]; function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } 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; }; // Shared empty style object reused when no per-type tweaks are needed. var EMPTY_STYLE = {}; /** * 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; if (type === 'clip' && (direction === 'left' || direction === 'right')) { return { width: clipWidth }; } 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") return { transitionDuration: formatter(duration) }; } return EMPTY_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') return _style; var merged = _style ? _objectSpread(_objectSpread({}, _style), {}, { overflow: 'hidden' }) : { overflow: 'hidden' }; if (visible && (direction === 'up' || direction === 'down')) { merged.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") merged.transitionDuration = formatter(duration); } return merged; } }, 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. * * @function Transition * @ui * @memberof ui/Transition * @public */ function Transition(_ref5) { var children = _ref5.children, _ref5$direction = _ref5.direction, direction = _ref5$direction === void 0 ? 'up' : _ref5$direction, _ref5$duration = _ref5.duration, duration = _ref5$duration === void 0 ? 'medium' : _ref5$duration, _ref5$noAnimation = _ref5.noAnimation, noAnimation = _ref5$noAnimation === void 0 ? false : _ref5$noAnimation, onHide = _ref5.onHide, onShow = _ref5.onShow, _ref5$timingFunction = _ref5.timingFunction, timingFunction = _ref5$timingFunction === void 0 ? 'ease-in-out' : _ref5$timingFunction, _ref5$type = _ref5.type, type = _ref5$type === void 0 ? 'slide' : _ref5$type, _ref5$visible = _ref5.visible, visible = _ref5$visible === void 0 ? true : _ref5$visible, rest = _objectWithoutProperties(_ref5, _excluded2); var _useState = (0, _react.useState)(function () { return { initialHeight: null, initialWidth: null, renderState: visible ? TRANSITION_STATE.READY : TRANSITION_STATE.INIT }; }), _useState2 = _slicedToArray(_useState, 2), state = _useState2[0], setState = _useState2[1]; var prevVisibleRef = (0, _react.useRef)(visible); // Adjust state during render when `visible` flips on. The functional equivalent of the // class's getDerivedStateFromProps. Reading/writing the ref here is intentional. // https://react.dev/reference/react/useState#storing-information-from-previous-renders if (!prevVisibleRef.current && visible) { // eslint-disable-line react-hooks/refs prevVisibleRef.current = true; setState({ initialHeight: null, initialWidth: null, renderState: TRANSITION_STATE.MEASURE }); } // Latest props for handlers that fire asynchronously (transitionend, observer callbacks) var currentProps = _objectSpread({ children: children, direction: direction, duration: duration, noAnimation: noAnimation, onHide: onHide, onShow: onShow, timingFunction: timingFunction, type: type, visible: visible }, rest); (0, _util.checkPropTypes)(Transition, currentProps); var propsRef = (0, _react.useRef)(); propsRef.current = currentProps; // eslint-disable-line react-hooks/refs var childNodeRef = (0, _react.useRef)(null); var measuringJobRef = (0, _react.useRef)(null); if (measuringJobRef.current == null) { measuringJobRef.current = new _util.Job(function () { setState(function (prev) { return prev.renderState === TRANSITION_STATE.MEASURE ? prev : _objectSpread(_objectSpread({}, prev), {}, { renderState: TRANSITION_STATE.MEASURE }); }); }); } var measureInner = (0, _react.useCallback)(function () { var node = childNodeRef.current; if (!node) return; var initialHeight = node.scrollHeight; var initialWidth = node.scrollWidth; setState(function (prev) { if (prev.initialHeight === initialHeight && prev.initialWidth === initialWidth && prev.renderState === TRANSITION_STATE.READY) { return prev; } return { initialHeight: initialHeight, initialWidth: initialWidth, renderState: TRANSITION_STATE.READY }; }); }, []); var childRef = (0, _react.useCallback)(function (node) { childNodeRef.current = node; }, []); // Measurement effect — covers MEASURE → READY commits and the resize-driven // re-measure path (handleResize nulls initialHeight, this re-fires). (0, _react.useLayoutEffect)(function () { if (state.renderState === TRANSITION_STATE.MEASURE) { measuringJobRef.current.stop(); measureInner(); } else if (visible && state.initialHeight == null) { // Component should be visible but doesn't have a height — measure now. measureInner(); } }, [state.renderState, state.initialHeight, visible, measureInner]); (0, _react.useEffect)(function () { var job = measuringJobRef.current; if (!propsRef.current.visible) { job.idle(); } return function () { return job.stop(); }; }, []); // ResizeContext registration — the context value is itself the register fn. var resizeRegister = (0, _react.use)(_Resizable.ResizeContext); var handleResize = (0, _react.useCallback)(function () { // Null initialHeight so the next layout commit re-measures in a separate tick. // the useLayoutEffect above re-runs when state.initialHeight transitions to null. setState(function (prev) { return prev.initialHeight == null ? prev : _objectSpread(_objectSpread({}, prev), {}, { initialHeight: null }); }); }, []); (0, _react.useEffect)(function () { if (typeof resizeRegister !== 'function') return; var registry = resizeRegister(handleResize); return function () { if (registry) registry.unregister(); }; }, [resizeRegister, handleResize]); var prevVisibleForEffectRef = (0, _react.useRef)(visible); (0, _react.useEffect)(function () { var prev = prevVisibleForEffectRef.current; prevVisibleForEffectRef.current = visible; if (!noAnimation || prev === visible) return; if (!prev && visible) { (0, _handle.forwardCustom)('onShow')(null, propsRef.current); } else if (prev && !visible) { (0, _handle.forwardCustom)('onHide')(null, propsRef.current); } }, [visible, noAnimation]); var handleTransitionEnd = (0, _react.useCallback)(function (ev) { (0, _handle.forward)('onTransitionEnd', ev, propsRef.current); if (ev.target === childNodeRef.current) { var eventName = propsRef.current.visible ? 'onShow' : 'onHide'; (0, _handle.forward)(eventName, { type: eventName, currentTarget: ev.currentTarget }, propsRef.current); } }, []); // If we are deferring children, don't render any. if (state.renderState === TRANSITION_STATE.INIT) { return null; } var childProps = _objectSpread(_objectSpread({}, rest), {}, { children: children, direction: direction, duration: duration, noAnimation: noAnimation, timingFunction: timingFunction, type: type }); // 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. if (state.renderState === TRANSITION_STATE.MEASURE) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(TransitionBase, _objectSpread(_objectSpread({}, childProps), {}, { childRef: childRef, visible: false })); } return /*#__PURE__*/(0, _jsxRuntime.jsx)(TransitionBase, _objectSpread(_objectSpread({}, childProps), {}, { childRef: childRef, visible: visible, clipHeight: state.initialHeight, clipWidth: state.initialWidth, onTransitionEnd: handleTransitionEnd })); } Transition.displayName = 'Transition'; 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 }; var _default = exports["default"] = Transition;