UNPKG

@enact/ui

Version:

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

985 lines (965 loc) 38.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = exports.MarqueeDecorator = void 0; var _dispatcher = require("@enact/core/dispatcher"); var _handle = require("@enact/core/handle"); var _hoc = _interopRequireDefault(require("@enact/core/hoc")); var _keymap = require("@enact/core/keymap"); var _util = require("@enact/core/util"); var _util2 = require("@enact/i18n/util"); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = require("react"); var _reactDom = require("react-dom"); var _warning = _interopRequireDefault(require("warning")); var _resolution = require("../resolution"); var _Resizable = require("../Resizable"); var _MarqueeBase = _interopRequireDefault(require("./MarqueeBase")); var _MarqueeController = require("./MarqueeController"); var _MarqueeModule = _interopRequireDefault(require("./Marquee.module.css")); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["alignment", "children", "disabled", "marqueeOn", "marqueeSpeed"]; 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 _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 _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); } /* global ResizeObserver */ // The minimum number of milliseconds to wait before resetting the marquee position after it finishes. var MINIMUM_MARQUEE_RESET_DELAY = 40; /** * Default configuration parameters for {@link ui/Marquee.MarqueeDecorator} * * @type {Object} * @memberof ui/Marquee.MarqueeDecorator * @hocconfig */ var defaultConfig = { /** * Property containing the callback to stop the animation when `marqueeOn` is `'focus'` * * @type {String} * @default 'onBlur' * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ blur: 'onBlur', /** * The base marquee component wrapping the content. * * @type {Component} * @default ui/Marquee.Marquee * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ component: _MarqueeBase["default"], /** * 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: * * * `marquee` - The root component class * * `animate` - Applied to the inner content node when the text is animating * * `spacing` - The spacing node used between the repeated content * * `text` - The inner content node * * `willAnimate` - Applied to the inner content node shortly before animation * * @type {Object} * @default null * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ css: null, /** * Property containing the callback to start the animation when `marqueeOn` is `'hover'` * * @type {String} * @default 'onMouseEnter' * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ enter: 'onMouseEnter', /** * Property containing the callback to start the animation when `marqueeOn` is `'focus'` * * @type {String} * @default 'onFocus' * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ focus: 'onFocus', /** * Invalidate the distance if any property (like 'inline') changes. * Expects an array of props which on change trigger invalidateMetrics. * * @type {String[]} * @default ['remeasure'] * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ invalidateProps: ['remeasure'], /** * Property containing the callback to stop the animation when `marqueeOn` is `'hover'` * * @type {String} * @default 'onMouseLeave' * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ leave: 'onMouseLeave', /** * A function that determines the text directionality of a string. * * Returns: * * `'rtl'` if the text should marquee to the right * * `'ltr'` if the text should marquee to the left * * @type {Function} * @kind member * @memberof ui/Marquee.MarqueeDecorator.defaultConfig */ marqueeDirection: function marqueeDirection(str) { return (0, _util2.isRtlText)(str) ? 'rtl' : 'ltr'; } }; /* * Checks whether any of the invalidateProps has changed or not * * @param {String[]} propList An array of invalidateProps * @param {Object} prev Previous props * @param {Object} next Next props * @returns {Boolean} `true` if any of the props changed * @private */ var didPropChange = function didPropChange(propList, prev, next) { var hasPropsChanged = propList.map(function (i) { return prev[i] !== next[i]; }); return hasPropsChanged.indexOf(true) !== -1; }; /* * There's only one timer shared for Marquee so we need to keep track of what we may be using it * for. We may need to clean up certain things as we move among states. */ var TimerState = { CLEAR: 0, // No timers pending START_PENDING: 1, // A start request is pending RESET_PENDING: 2, // Marquee finished, waiting for reset delay SYNCSTART_PENDING: 3 // Waiting to alert Controller that we want to start marqueeing }; /** * A higher-order component that provides marquee functionalities. * * Note: This HoC passes additional properties to the wrapped component and wraps the `children` * prop with an additional component necessary for rendering the marquee. * * @class MarqueeDecorator * @memberof ui/Marquee * @hoc * @public */ var MarqueeDecorator = exports.MarqueeDecorator = (0, _hoc["default"])(defaultConfig, function (config, Wrapped) { var _Class; var blur = config.blur, MarqueeComponent = config.component, css = config.css, enter = config.enter, focus = config.focus, invalidateProps = config.invalidateProps, leave = config.leave, marqueeDirection = config.marqueeDirection; // Generate functions to forward events to containers var forwardBlur = (0, _handle.forward)(blur); var forwardFocus = (0, _handle.forward)(focus); var forwardEnter = (0, _handle.forward)(enter); var forwardLeave = (0, _handle.forward)(leave); var determineTextDirection = function determineTextDirection(node, rtl, forceDirection) { // Text directionality is a function of locale direction (rtl), content (node.textContent), // and props (forceDirection) in increasing order of significance. if (forceDirection) { rtl = forceDirection === 'locale' ? rtl : forceDirection === 'rtl'; } else if (node) { rtl = marqueeDirection(node.textContent) === 'rtl'; } return rtl; }; return _Class = /*#__PURE__*/function (_PureComponent) { function _Class(props) { var _this; _classCallCheck(this, _Class); _this = _callSuper(this, _Class, [props]); _this.promoteJob = new _util.Job(function () { if (_this.contentFits === false) { _this.setState(function (state) { return state.promoted ? null : { promoted: true }; }); } }); _this.demoteJob = new _util.Job(function () { _this.setState(function (state) { return !state.animating && state.promoted ? { promoted: false } : null; }); }); /* * Starts the animation without synchronizing * * @param {Number} [delay] Milliseconds to wait before animating * @returns {undefined} */ _this.start = function () { var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props.marqueeDelay; if (_this.props.marqueeDisabled) { // if marquee isn't necessary, do not set `animating` but return `true` to mark it // complete if it's synchronized so it doesn't block other instances. return true; } else if (!_this.state.animating) { // Don't need to worry about this.timerState because if we're sync, we were just // told to start, so our state is correct already. If we're not sync, this will // restart us anyhow. If we were waiting to tell sync to start us, someone else in // our group already did it. _this.setTimeout(function () { _this.calculateMetrics(); if (_this.contentFits === false) { _this.setState({ promoted: true, animating: true }); } else if (_this.sync) { _this.context.complete(_this); } }, delay, TimerState.START_PENDING); } }; /* * Stops the animation * * @returns {undefined} */ _this.stop = function () { _this.clearTimeout(); if (_this.state.animating) { _this.setState({ animating: false }); } _this.demote(); }; /* * Restarts the animation * * @returns {undefined} */ _this.restart = function () { _this.restartAnimation(); }; /* * Starts marquee animation with synchronization, if not already animating and a timer is * not already active to start. * * @param {Number} [delay] Milliseconds to wait before animating * @returns {undefined} */ _this.tryStartingAnimation = function (delay) { if (_this.state.animating || _this.timerState !== TimerState.CLEAR) return; _this.startAnimation(delay); }; /* * Starts marquee animation with synchronization * * @param {Number} [delay] Milliseconds to wait before animating * @returns {undefined} */ _this.startAnimation = function (delay) { _this.promote(delay); if (_this.sync) { // If we're running a timer for anything, we should let that finish, unless it's // another syncstart request. We should probably check to see if the start request // is further in the future than we are so we can choose the nearer one. But, we're // assuming the condition is we're waiting on render delay and someone just hovered // us, so we can start with the (hopefully) faster hover delay. if (_this.timerState !== TimerState.CLEAR && _this.timerState !== TimerState.SYNCSTART_PENDING) { return; } _this.setTimeout(function () { _this.context.start(); }, delay, TimerState.SYNCSTART_PENDING); } else { _this.start(delay); } }; /* * Resets the marquee and restarts it after `marqueeDelay` milliseconds. * * @returns {undefined} */ _this.restartAnimation = function () { var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : MINIMUM_MARQUEE_RESET_DELAY; (0, _reactDom.flushSync)(function () { _this.setState({ animating: false }); }); // synchronized Marquees defer to the controller to restart them if (_this.sync) { // Even in sync mode, it is necessary to apply a minimum reset delay. // In detail, the timer with 40ms delay needs to be applied only when marqueeDelay is less than 40, // but for consistency, we decided to apply 40ms in all cases. _this.setTimeout(function () { _this.context.complete(_this); }, MINIMUM_MARQUEE_RESET_DELAY, TimerState.RESET_PENDING); } else if (!_this.state.animating) { _this.startAnimation(delay); } }; /* * Resets and restarts the marquee after `marqueeResetDelay` milliseconds * * @returns {undefined} */ _this.resetAnimation = function () { var delay = Math.max(MINIMUM_MARQUEE_RESET_DELAY, _this.props.marqueeResetDelay + _this.props.marqueeDelay); // If we're already timing a start action, don't reset. Start actions will clear us if // sync. if (_this.timerState === TimerState.CLEAR) { // If invoked immediately, the setState call in restartAnimation is batched and will // break any non-marquee-on-render instances because the subsequent startAnimation // isn't invoked. By setting a brief timeout, we decouple from the `onTransitionEnd` // event and setState is synchronous allowing the startAnimation to be called // immediately. // // This would be better moved into componentDidUpdate with more expansive state // values to indicate we need to reset an animation vs starting fresh. _this.setTimeout(function () { _this.restartAnimation(delay); }, 0, TimerState.RESET_PENDING); } }; /* * Cancels the marquee * * @returns {undefined} */ _this.cancelAnimation = function () { var retryStartingAnimation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (_this.sync) { _this.context.cancel(retryStartingAnimation); return; } _this.stop(); }; _this.handleResize = function () { if (_this.node && !_this.props.marqueeDisabled) { _this.invalidateMetrics(); if (_this.state.animating) { _this.cancelAnimation(); _this.resetAnimation(); } } }; _this.handleMarqueeComplete = function () { _this.resetAnimation(); }; _this.handleFocus = function (ev) { _this.isFocused = true; if (!_this.sync) { if (!_this.state.animating) { _this.startAnimation(); } } forwardFocus(ev, _this.props); }; _this.handleBlur = function (ev) { _this.promoteJob.stop(); forwardBlur(ev, _this.props); if (_this.isFocused) { _this.isFocused = false; if (!_this.sync && !(_this.isHovered && _this.props.marqueeOn === 'hover')) { _this.cancelAnimation(); } } }; _this.handleEnter = function (ev) { _this.isHovered = true; if (_this.props.marqueeOn === 'hover') { if (_this.sync) { _this.context.enter(_this); } else if (!_this.state.animating) { _this.startAnimation(); } } forwardEnter(ev, _this.props); }; _this.handleLeave = function (ev) { _this.promoteJob.stop(); _this.handleUnhover(); forwardLeave(ev, _this.props); }; _this.handlePointerHide = function (_ref) { var keyCode = _ref.keyCode; if ((0, _keymap.is)('pointerHide', keyCode)) { _this.handleUnhover(); } }; _this.cacheNode = function (node) { _this.node = node; }; _this.state = { animating: false, overflow: 'ellipsis', promoted: false, rtl: determineTextDirection(null, props.rtl, props.forceDirection) }; _this.sync = false; _this.timerState = TimerState.CLEAR; _this.distance = null; _this.contentFits = null; _this.resizeRegistry = null; _this.resizeObserver = null; return _this; } _inherits(_Class, _PureComponent); return _createClass(_Class, [{ key: "componentDidMount", value: function componentDidMount() { var _this2 = this; if (this.context && this.context.register) { this.sync = true; this.context.register(this, { restart: this.restart, start: this.start, stop: this.stop }); } this.validateTextDirection(); if (typeof ResizeObserver === 'function' && this.node) { this.resizeObserver = new ResizeObserver(function () { _this2.handleResize(); }); this.resizeObserver.observe(this.node); } if (this.props.marqueeOn === 'render') { this.startAnimation(this.props.marqueeOnRenderDelay); } (0, _dispatcher.on)('keydown', this.handlePointerHide, document); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { var _this$props = this.props, children = _this$props.children, disabled = _this$props.disabled, forceDirection = _this$props.forceDirection, locale = _this$props.locale, marqueeOn = _this$props.marqueeOn, marqueeDisabled = _this$props.marqueeDisabled, marqueeSpacing = _this$props.marqueeSpacing, marqueeSpeed = _this$props.marqueeSpeed, rtl = _this$props.rtl; var forceRestartMarquee = false; if (prevProps.locale !== locale || prevProps.rtl !== rtl || prevProps.marqueeSpacing !== marqueeSpacing || !(0, _util.shallowEqual)(prevProps.children, children) || invalidateProps && didPropChange(invalidateProps, prevProps, this.props)) { // restart marqueeOn="render" marquees or synced marquees that were animating forceRestartMarquee = marqueeOn === 'render' || this.sync && (this.state.animating || this.timerState > TimerState.CLEAR); this.invalidateMetrics(); this.cancelAnimation(forceRestartMarquee); if (forceRestartMarquee && marqueeOn === 'focus') { this.resetAnimation(); } } else if (prevProps.marqueeOn !== marqueeOn || prevProps.marqueeDisabled !== marqueeDisabled || prevProps.marqueeSpeed !== marqueeSpeed || prevProps.forceDirection !== forceDirection) { this.cancelAnimation(); } else if (disabled && this.isHovered && marqueeOn === 'focus' && this.sync) { this.context.enter(this); } this.validateTextDirection(); if (forceRestartMarquee || this.shouldStartMarquee()) { this.tryStartingAnimation(this.props.marqueeOn === 'render' ? this.props.marqueeOnRenderDelay : this.props.marqueeDelay); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.clearTimeout(); this.promoteJob.stop(); this.demoteJob.stop(); if (this.sync) { this.context.unregister(this); } if (this.resizeRegistry) { this.resizeRegistry.unregister(this.handleResize); } if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } (0, _dispatcher.off)('keydown', this.handlePointerHide, document); } }, { key: "demote", value: function demote() { this.promoteJob.stop(); if (this.state.promoted) { this.demoteJob.idle(); } } }, { key: "promote", value: function promote() { var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props.marqueeDelay; this.demoteJob.stop(); this.promoteJob.startAfter(Math.max(0, delay - 200)); } /* * Clears the timer * * @returns {undefined} */ }, { key: "clearTimeout", value: function clearTimeout() { if (window && this.timer) { window.clearTimeout(this.timer); this.timer = null; } this.timerState = TimerState.CLEAR; } /* * Starts a new timer * * @param {Function} fn Callback * @param {Number} time Delay in milliseconds * @returns {undefined} */ }, { key: "setTimeout", value: function setTimeout(fn) { var _this3 = this; var time = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var state = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : TimerState.CLEAR; this.clearTimeout(); if (window) { this.timerState = state; this.timer = window.setTimeout(function () { _this3.timerState = TimerState.CLEAR; fn(); }, time); } } /* * Checks to see if the children changed during a condition that should cause us to re-check * the animation state * * @returns {Boolean} - `true` if a possible marquee condition exists */ }, { key: "shouldStartMarquee", value: function shouldStartMarquee() { var _this$props2 = this.props, disabled = _this$props2.disabled, marqueeDisabled = _this$props2.marqueeDisabled, marqueeOn = _this$props2.marqueeOn; return !marqueeDisabled && (marqueeOn === 'render' || !this.sync && (this.isFocused && marqueeOn === 'focus' && !disabled || this.isHovered && (marqueeOn === 'hover' || marqueeOn === 'focus' && disabled))); } /* * Invalidates marquee metrics requiring them to be recalculated * * @returns {undefined} */ }, { key: "invalidateMetrics", value: function invalidateMetrics() { this.spacing = 0; // Null distance is the special value to allow recalculation this.distance = null; // Assume the marquee does not fit until calculations show otherwise this.contentFits = null; this.setState(function (state) { if (state.overflow === 'ellipsis' && state.promoted === false) return null; return { overflow: 'ellipsis', promoted: false }; }); } }, { key: "moveChildren", value: function moveChildren(from, to) { while (from.childNodes.length) { to.appendChild(from.childNodes.item(0)); } } }, { key: "measureWidths", value: function measureWidths() { if (this.node.querySelector(".".concat(_MarqueeModule["default"].marquee))) { process.env.NODE_ENV !== "production" ? (0, _warning["default"])(false, 'Marquee should not be nested inside another Marquee') : void 0; return { scrollWidth: this.node.scrollWidth, width: this.node.getBoundingClientRect().width }; } // move all the children into the wrapper node ... var wrapper = document.createElement('span'); this.moveChildren(this.node, wrapper); this.node.appendChild(wrapper); // measure it to find the precise floating point width of the content ... var _wrapper$getBoundingC = wrapper.getBoundingClientRect(), scrollWidth = _wrapper$getBoundingC.width; var _this$node$getBoundin = this.node.getBoundingClientRect(), width = _this$node$getBoundin.width; // and move all the children back and remove the wrapper this.node.removeChild(wrapper); this.moveChildren(wrapper, this.node); return { scrollWidth: scrollWidth, width: width }; } /* * Determines if the component should marquee and the distance to animate * * @returns {undefined} */ }, { key: "calculateMetrics", value: function calculateMetrics() { var node = this.node; // TODO: absolute showing check (or assume that it won't be rendered if it isn't showing?) if (node && this.distance == null && !this.props.marqueeDisabled) { var _this$measureWidths = this.measureWidths(), width = _this$measureWidths.width, scrollWidth = _this$measureWidths.scrollWidth; this.spacing = this.getSpacing(width); this.distance = this.calculateDistance(width, scrollWidth, this.spacing); this.contentFits = !this.shouldAnimate(this.distance); var overflow = this.calculateTextOverflow(this.distance); this.setState(function (state) { return state.overflow === overflow ? null : { overflow: overflow }; }); } } /* * Calculates the distance the marquee must travel to reveal all of the content * * @param {Number} width Width of the node * @param {Number} scrollWidth Width of the node if it were unbounded * @param {Number} spacing Horizontal spacing * @returns {Number} Distance to travel in pixels */ }, { key: "calculateDistance", value: function calculateDistance(width, scrollWidth, spacing) { var overflow = scrollWidth - width; if (this.shouldAnimate(overflow)) { return scrollWidth + spacing; } return 0; } /* * A custom overflow-determining method to reflect real-world truncation/ellipsis * calculation. This catches an edge case that the browser typically does not, where the * size of the text area is the same size as the container (zero distance difference), but * the browser still inserts an ellipsis due to a non-visible part of the last glyph's * render box overflowing the parent container size. * This scenario should not induce a marquee animation or ellipsis, so we directly set * Marquee to not use an ellipsis, and instead just clip the non-visible part of the glyph. * * @param {Number} distance Amount of overflow in pixels * @returns {String} text-overflow value */ }, { key: "calculateTextOverflow", value: function calculateTextOverflow(distance) { return distance === 0 ? 'clip' : 'ellipsis'; } }, { key: "getSpacing", value: function getSpacing(width) { var marqueeSpacing = this.props.marqueeSpacing; if (typeof marqueeSpacing === 'string') { if (/^\d+(\.\d+)?%$/.test(marqueeSpacing)) { return width * Number.parseFloat(marqueeSpacing) / 100; } // warning for invalid string value; return 0; } return (0, _resolution.scale)(marqueeSpacing); } /* * Calculates if the marquee should animate * * @param {Number} distance Amount of overflow in pixels * @returns {Boolean} `true` if it should animate */ }, { key: "shouldAnimate", value: function shouldAnimate(distance) { return distance > 0; } }, { key: "handleUnhover", value: function handleUnhover() { this.isHovered = false; if (this.props.marqueeOn === 'hover') { if (this.sync) { this.context.leave(this); } else { this.cancelAnimation(); } } } }, { key: "validateTextDirection", value: function validateTextDirection() { var _this4 = this; this.setState(function (state, props) { var rtl = determineTextDirection(_this4.node, props.rtl, props.forceDirection); return state.rtl === rtl ? null : { rtl: rtl }; }); } }, { key: "renderMarquee", value: function renderMarquee() { var _this$props3 = this.props, alignment = _this$props3.alignment, children = _this$props3.children, disabled = _this$props3.disabled, marqueeOn = _this$props3.marqueeOn, marqueeSpeed = _this$props3.marqueeSpeed, rest = _objectWithoutProperties(_this$props3, _excluded); var marqueeOnFocus = marqueeOn === 'focus'; var marqueeOnHover = marqueeOn === 'hover'; var marqueeOnRender = marqueeOn === 'render'; if (marqueeOnFocus) { rest[focus] = this.handleFocus; } // TODO: cancel others on hover if (marqueeOnHover || marqueeOnFocus) { rest[enter] = this.handleEnter; rest[leave] = this.handleLeave; } if (marqueeOnRender) { rest[enter] = this.handleEnter; } delete rest.forceDirection; delete rest.locale; delete rest.marqueeCentered; delete rest.marqueeDelay; delete rest.marqueeDisabled; delete rest.marqueeOnRenderDelay; delete rest.marqueeSpacing; delete rest.marqueeResetDelay; delete rest.marqueeSpeed; delete rest.remeasure; delete rest.rtl; return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread(_objectSpread({}, rest), {}, { onBlur: this.handleBlur, disabled: disabled, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MarqueeComponent, { alignment: alignment, animating: this.state.animating, clientRef: this.cacheNode, css: css, distance: this.distance, onMarqueeComplete: this.handleMarqueeComplete, overflow: this.state.overflow, spacing: this.spacing, rtl: this.state.rtl, speed: marqueeSpeed, willAnimate: this.state.promoted, children: children }) })); } }, { key: "renderWrapped", value: function renderWrapped() { var props = Object.assign({}, this.props); delete props.alignment; delete props.forceDirection; delete props.locale; delete props.marqueeCentered; delete props.marqueeDelay; delete props.marqueeDisabled; delete props.marqueeOn; delete props.marqueeOnRenderDelay; delete props.marqueeSpacing; delete props.marqueeResetDelay; delete props.marqueeSpeed; delete props.remeasure; delete props.rtl; return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread({}, props)); } }, { key: "render", value: function render() { var _this5 = this; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Resizable.ResizeContext.Consumer, { children: function children(register) { if (!_this5.resizeRegistry && register) { _this5.resizeRegistry = register(_this5.handleResize); } if (_this5.props.marqueeDisabled) { return _this5.renderWrapped(); } else { return _this5.renderMarquee(); } } }); } }]); }(_react.PureComponent), _Class.displayName = 'ui:MarqueeDecorator', _Class.propTypes = /** @lends ui/Marquee.MarqueeDecorator.prototype */{ /** * Text alignment value of the marquee. Valid values are `'left'`, `'right'` and `'center'`. * * @type {String} * @public */ alignment: _propTypes["default"].oneOf(['left', 'right', 'center']), /** * Children to be marqueed * * @type {Node} * @public */ children: _propTypes["default"].node, /** * Passed through to the wrapped component. * * Does not affect Marquee behavior except that components that are `marqueeOn="focus"` * will be treated as if they were `marqueeOn="hover"`, to allow disabled (and thus, * unfocusable) components to marquee. * * @type {Boolean} * @public */ disabled: _propTypes["default"].bool, /** * Forces the `direction` of the marquee. * * Valid values are `'rtl'`, `'ltr'`, and `'locale'`. This includes non-text elements as well. * The default behavior, if this prop is unset, is to evaluate the text content for * directionality using {@link i18n/util.isRtlText}. * * If `'locale'`, the `direction` is determined by the locale, same as {@link ui/Marquee.MarqueeDecorator.rtl}. * In other words, it will not consider the text content for determining the direction. * * @type {String} * @public */ forceDirection: _propTypes["default"].oneOf(['rtl', 'ltr', 'locale']), /** * The current locale as a * {@link https://tools.ietf.org/html/rfc5646|BCP 47 language tag}. * * @type {String} * @public */ locale: _propTypes["default"].string, /** * Number of milliseconds to wait before starting marquee when `marqueeOn` is `'hover'` or * `'focus'` or before restarting any marquee. * * @type {Number} * @default 1000 * @public */ marqueeDelay: _propTypes["default"].number, /** * Disables all marquee behavior and removes supporting markup. * * @type {Boolean} */ marqueeDisabled: _propTypes["default"].bool, /** * Determines what triggers the marquee to start its animation. * * @type {('focus'|'hover'|'render')} * @default 'focus' * @public */ marqueeOn: _propTypes["default"].oneOf(['focus', 'hover', 'render']), /** * Number of milliseconds to wait before starting marquee the first time. * * Has no effect if `marqueeOn` is not `'render'` * * @type {Number} * @default 1000 * @public */ marqueeOnRenderDelay: _propTypes["default"].number, /** * Number of milliseconds to wait before resetting the marquee position after it * finishes. * * A minimum of 40 milliseconds is enforced. * * @type {Number} * @default 1000 * @public */ marqueeResetDelay: _propTypes["default"].number, /** * Amount of spacing between the instances of the content when animating. * * May either be a number indicating the number of pixels or a string indicating the * percentage relative to the width of the component. * * *Note:* When using a number, the value should be based on 1920x1080 display and * will be scaled automatically for the current resolution using {@link ui/resolution}. * * @type {String | Number} * @default '50%' * @public */ marqueeSpacing: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]), /** * Rate of marquee measured in pixels/second. * * @type {Number} * @default 60 * @public */ marqueeSpeed: _propTypes["default"].number, /** * Used to signal for a remeasurement inside of marquee. * * The value must change for the remeasurement to take place. The value * type is `any` because it does not matter. It is only used to check for changes. * * @private */ remeasure: _propTypes["default"].any, /** * Indicates the text directionality of the current locale is right-to-left * * @type {String} * @public */ rtl: _propTypes["default"].bool }, _Class.defaultProps = { marqueeDelay: 1000, marqueeOn: 'focus', marqueeOnRenderDelay: 1000, marqueeResetDelay: 1000, marqueeSpacing: '50%', marqueeSpeed: 60 }, _Class.contextType = _MarqueeController.MarqueeControllerContext, _Class; }); var _default = exports["default"] = MarqueeDecorator;