@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
985 lines (965 loc) • 38.7 kB
JavaScript
"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;