UNPKG

@enact/ui

Version:

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

277 lines (263 loc) 9.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useMarqueeController = exports["default"] = exports.MarqueeControllerContext = void 0; var _handle = require("@enact/core/handle"); var _util = require("@enact/core/util"); var _react = require("react"); var _jsxRuntime = require("react/jsx-runtime"); 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); } var STATE = { inactive: 0, // Marquee is not necessary (render or focus not happened) active: 1, // Marquee in progress, awaiting complete ready: 2 // Marquee completed or not needed, but state is active }; var MarqueeControllerContext = exports.MarqueeControllerContext = /*#__PURE__*/(0, _react.createContext)(null); var forwardBlur = (0, _handle.forward)('onBlur'); var forwardFocus = (0, _handle.forward)('onFocus'); var useMarqueeController = exports.useMarqueeController = function useMarqueeController(props) { var mutableRef = (0, _react.useRef)({ controlled: [], isFocused: false, isHovered: false }); /* * Invokes the `action` handler for each synchronized component except the invoking * `component`. * * @param {String} action `'start'`, `'stop'`, or `'restart'` * @param {Object} component A previously registered component * * @returns {undefined} */ var dispatch = (0, _react.useCallback)(function (action, component) { mutableRef.current.controlled.forEach(function (controlled) { var controlledComponent = controlled.component, handler = controlled[action]; if (component !== controlledComponent && typeof handler === 'function') { var complete = handler.call(controlledComponent); // Returning `true` from a start request means that the marqueeing is // unnecessary and is therefore not awaiting a finish if (action === 'start' && complete) { controlled.state = STATE.ready; } else if (action === 'start') { controlled.state = STATE.active; } } else if (action === 'start' && component === controlledComponent) { controlled.state = STATE.active; } }); }, []); /* * Marks all components with the passed-in state * * @param {Enum} state The state to set * * @returns {undefined} */ var markAll = (0, _react.useCallback)(function (state) { mutableRef.current.controlled.forEach(function (c) { c.state = state; }); }, []); /* * Marks `component` as ready for next marquee action * * @param {Object} component A previously registered component * * @returns {Boolean} `true` if no components are STATE.active */ var markReady = (0, _react.useCallback)(function (component) { var complete = true; mutableRef.current.controlled.forEach(function (c) { if (c.component === component) { c.state = STATE.ready; } complete = complete && c.state !== STATE.active; }); return complete; }, []); /* * Checks that all components are inactive * * @returns {Boolean} `true` if any components should be running */ var allInactive = (0, _react.useCallback)(function () { var activeOrReady = mutableRef.current.controlled.reduce(function (res, component) { return res || !(component.state === STATE.inactive); }, false); return !activeOrReady; }, []); /* * Checks for any components currently marqueeing * * @returns {Boolean} `true` if any component is marqueeing */ var anyRunning = (0, _react.useCallback)(function () { return mutableRef.current.controlled.reduce(function (res, component) { return res || component.state === STATE.active; }, false); }, []); var doCancel = (0, _react.useCallback)(function (retryStartingAnimation) { if (mutableRef.current.isHovered || mutableRef.current.isFocused) { return; } markAll(STATE.inactive); dispatch('stop'); if (retryStartingAnimation) { dispatch('restart'); } }, [dispatch, markAll]); var cancelJob = (0, _react.useMemo)(function () { return new _util.Job(function () { var retryStartingAnimation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return doCancel(retryStartingAnimation); }, 30); }, [doCancel]); /* * Registers `component` with a set of handlers for `start`, `stop`, and `restart`. * * @param {Object} component A component, typically a React component instance, on * which handlers will be dispatched. * @param {Object} handlers An object containing `start`, `stop`, and `restart` functions * * @returns {undefined} */ var handleRegister = (0, _react.useCallback)(function (component, handlers) { var needStart = !allInactive() || mutableRef.current.isFocused; mutableRef.current.controlled.push(_objectSpread(_objectSpread({}, handlers), {}, { state: STATE.inactive, component: component })); if (needStart) { dispatch('start'); } }, [allInactive, dispatch]); /* * Unregisters `component` for synchronization * * @param {Object} component A previously registered component * * @returns {undefined} */ var handleUnregister = (0, _react.useCallback)(function (component) { var wasRunning = false; for (var i = 0; i < mutableRef.current.controlled.length; i++) { if (mutableRef.current.controlled[i].component === component) { wasRunning = mutableRef.current.controlled[i].state === STATE.active; mutableRef.current.controlled.splice(i, 1); break; } } if (wasRunning && !anyRunning()) { dispatch('start'); } }, [anyRunning, dispatch]); /* * Handler for the `start` context function * * @param {Object} component A previously registered component * * @returns {undefined} */ var handleStart = (0, _react.useCallback)(function (component) { cancelJob.stop(); if (!anyRunning()) { markAll(STATE.ready); dispatch('start', component); } }, [anyRunning, cancelJob, dispatch, markAll]); /* * Handler for the `cancel` context function * * @param {Boolean} retryStartingAnimation If true, `restart` called after `cancelJob` completes * * @returns {undefined} */ var handleCancel = (0, _react.useCallback)(function (retryStartingAnimation) { if (anyRunning()) { cancelJob.start(retryStartingAnimation); } }, [anyRunning, cancelJob]); /* * Handler for the `complete` context function * * @param {Object} component A previously registered component * * @returns {undefined} */ var handleComplete = (0, _react.useCallback)(function (component) { var complete = markReady(component); if (complete) { markAll(STATE.ready); dispatch('start'); } }, [dispatch, markAll, markReady]); var handleEnter = (0, _react.useCallback)(function () { mutableRef.current.isHovered = true; if (!anyRunning()) { dispatch('start'); } cancelJob.stop(); }, [anyRunning, cancelJob, dispatch]); var handleLeave = (0, _react.useCallback)(function () { mutableRef.current.isHovered = false; cancelJob.start(); }, [cancelJob]); /* * Handler for the focus event */ var handleFocus = (0, _react.useCallback)(function (ev) { mutableRef.current.isFocused = true; if (!anyRunning()) { dispatch('start'); } cancelJob.stop(); forwardFocus(ev, props); }, [anyRunning, cancelJob, dispatch, props]); /* * Handler for the blur event */ var handleBlur = (0, _react.useCallback)(function (ev) { mutableRef.current.isFocused = false; if (anyRunning()) { cancelJob.start(); } forwardBlur(ev, props); }, [anyRunning, cancelJob, props]); (0, _react.useEffect)(function () { return function () { cancelJob.stop(); }; }, [cancelJob]); var value = (0, _react.useMemo)(function () { return { cancel: handleCancel, complete: handleComplete, enter: handleEnter, leave: handleLeave, register: handleRegister, start: handleStart, unregister: handleUnregister }; }, [handleCancel, handleComplete, handleEnter, handleLeave, handleRegister, handleStart, handleUnregister]); var provideMarqueeControllerContext = (0, _react.useCallback)(function (children) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(MarqueeControllerContext, { value: value, children: children }); }, [value]); return { handleBlur: handleBlur, handleFocus: handleFocus, provideMarqueeControllerContext: provideMarqueeControllerContext }; }; var _default = exports["default"] = useMarqueeController;