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