UNPKG

@enact/sandstone

Version:

Large-screen/TV support library for Enact, containing a variety of UI components.

325 lines (314 loc) 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useEventKey = exports.useEventFocus = exports["default"] = void 0; var _keymap = require("@enact/core/keymap"); var _spotlight = _interopRequireWildcard(require("@enact/spotlight")); var _container = require("@enact/spotlight/src/container"); var _target = require("@enact/spotlight/src/target"); var _utilDOM = _interopRequireDefault(require("@enact/ui/useScroll/utilDOM")); var _utilEvent = _interopRequireDefault(require("@enact/ui/useScroll/utilEvent")); var _clamp = _interopRequireDefault(require("ramda/src/clamp")); var _react = require("react"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } var isDown = (0, _keymap.is)('down'), isEnter = (0, _keymap.is)('enter'), isLeft = (0, _keymap.is)('left'), isPageUp = (0, _keymap.is)('pageUp'), isPageDown = (0, _keymap.is)('pageDown'), isRight = (0, _keymap.is)('right'), isUp = (0, _keymap.is)('up'), isPointerHide = (0, _keymap.is)('pointerHide'), getNumberValue = function getNumberValue(index) { // using '+ operator' for string > number conversion based on performance: https://jsperf.com/convert-string-to-number-techniques/7 var number = +index; // should return -1 if index is not a number or a negative value return number >= 0 ? number : -1; }; var prevKeyDownIndex = -1; var useEventKey = exports.useEventKey = function useEventKey(props, instances, context) { // Mutable value var mutableRef = (0, _react.useRef)({ fn: null }); // Functions var findSpottableItem = (0, _react.useCallback)(function (indexFrom, indexTo) { var dataSize = props.dataSize; if (indexFrom < 0 && indexTo < 0 || indexFrom >= dataSize && indexTo >= dataSize) { return -1; } else { return (0, _clamp["default"])(0, dataSize - 1, indexFrom); } }, [props]); var getNextIndex = (0, _react.useCallback)(function (_ref) { var index = _ref.index, keyCode = _ref.keyCode, repeat = _ref.repeat; var dataSize = props.dataSize, rtl = props.rtl, wrap = props.wrap; var scrollContentHandle = instances.scrollContentHandle; var _scrollContentHandle$ = scrollContentHandle.current, isPrimaryDirectionVertical = _scrollContentHandle$.isPrimaryDirectionVertical, dimensionToExtent = _scrollContentHandle$.dimensionToExtent; var column = index % dimensionToExtent; var row = (index - column) % dataSize / dimensionToExtent; var isDownKey = isDown(keyCode); var isLeftMovement = !rtl && isLeft(keyCode) || rtl && isRight(keyCode); var isRightMovement = !rtl && isRight(keyCode) || rtl && isLeft(keyCode); var isUpKey = isUp(keyCode); var isNextRow = index + dimensionToExtent < dataSize; var isNextAdjacent = column < dimensionToExtent - 1 && index < dataSize - 1; var isBackward = isPrimaryDirectionVertical && isUpKey || !isPrimaryDirectionVertical && isLeftMovement || null; var isForward = isPrimaryDirectionVertical && isDownKey || !isPrimaryDirectionVertical && isRightMovement || null; var isWrapped = false; var nextIndex = -1; var targetIndex = -1; if (index >= 0) { if (isPrimaryDirectionVertical) { if (isUpKey && row) { targetIndex = index - dimensionToExtent; } else if (isDownKey && isNextRow) { targetIndex = index + dimensionToExtent; } else if (isLeftMovement && column) { targetIndex = index - 1; } else if (isRightMovement && isNextAdjacent) { targetIndex = index + 1; } } else if (isLeftMovement && row) { targetIndex = index - dimensionToExtent; } else if (isRightMovement && isNextRow) { targetIndex = index + dimensionToExtent; } else if (isUpKey && column) { targetIndex = index - 1; } else if (isDownKey && isNextAdjacent) { targetIndex = index + 1; } if (targetIndex >= 0) { nextIndex = targetIndex; } } if (!repeat && nextIndex === -1 && wrap) { if (isForward && findSpottableItem((row + 1) * dimensionToExtent, dataSize) < 0) { nextIndex = findSpottableItem(0, index); isWrapped = true; } else if (isBackward && findSpottableItem(-1, row * dimensionToExtent - 1) < 0) { nextIndex = findSpottableItem(dataSize, index); isWrapped = true; } } return { isDownKey: isDownKey, isUpKey: isUpKey, isLeftMovement: isLeftMovement, isRightMovement: isRightMovement, isWrapped: isWrapped, nextIndex: nextIndex }; }, [findSpottableItem, props, instances]); // Hooks (0, _react.useEffect)(function () { var scrollContainerRef = instances.scrollContainerRef, scrollContentHandle = instances.scrollContentHandle; var handle5WayKeyUp = context.handle5WayKeyUp, handleDirectionKeyDown = context.handleDirectionKeyDown, handlePageUpDownKeyDown = context.handlePageUpDownKeyDown, spotlightAcceleratorProcessKey = context.spotlightAcceleratorProcessKey; function handleKeyDown(ev) { var keyCode = ev.keyCode, target = ev.target; var direction = (0, _spotlight.getDirection)(keyCode); if (direction) { _spotlight["default"].setPointerMode(false); if (spotlightAcceleratorProcessKey(ev)) { ev.stopPropagation(); } else { var spotlightId = props.spotlightId; var targetIndex = target.dataset.index; var isNotItem = // if target has an index, it must be an item !targetIndex && // if it lacks an index and is inside the scroller, we need to handle this target.matches("[data-spotlight-id=\"".concat(spotlightId, "\"] *")); var index = !isNotItem ? getNumberValue(targetIndex) : -1; var candidate = (0, _target.getTargetByDirectionFromElement)(direction, target); var candidateInside = _utilDOM["default"].containsDangerously(ev.currentTarget, candidate); var candidateIndex = candidate && candidateInside && candidate.dataset && getNumberValue(candidate.dataset.index); var isLeaving = false; if (isNotItem) { // if the focused node is not an item if (!candidateInside) { // if the candidate is out of a list isLeaving = true; } } else if (index >= 0 && candidateIndex !== index) { // the focused node is an item and focus will move out of the item var repeat = ev.repeat; var _getNextIndex = getNextIndex({ index: index, keyCode: keyCode, repeat: repeat }), isDownKey = _getNextIndex.isDownKey, isUpKey = _getNextIndex.isUpKey, isLeftMovement = _getNextIndex.isLeftMovement, isRightMovement = _getNextIndex.isRightMovement, isWrapped = _getNextIndex.isWrapped, nextIndex = _getNextIndex.nextIndex; if (nextIndex >= 0) { // if the candidate is another item ev.preventDefault(); ev.stopPropagation(); if (repeat && prevKeyDownIndex !== -1 && (isDownKey && prevKeyDownIndex > index || isUpKey && prevKeyDownIndex < index)) { // Ignore keyEvent from item with wrong data-index (Workaround for data-index bug) // Sometimes keyDown event occurs before the data-index updated, it causes reverse focus change return; } if (props.scrollContainerHandle && props.scrollContainerHandle.current) { props.scrollContainerHandle.current.lastInputType = 'arrowKey'; } handleDirectionKeyDown(ev, 'acceleratedKeyDown', { isWrapped: isWrapped, keyCode: keyCode, nextIndex: nextIndex, repeat: repeat, target: target }); } else { // if the candidate is not found var dataSize = props.dataSize, focusableScrollbar = props.focusableScrollbar, isHorizontalScrollbarVisible = props.isHorizontalScrollbarVisible, isVerticalScrollbarVisible = props.isVerticalScrollbarVisible; var _scrollContentHandle$2 = scrollContentHandle.current, dimensionToExtent = _scrollContentHandle$2.dimensionToExtent, isPrimaryDirectionVertical = _scrollContentHandle$2.isPrimaryDirectionVertical; var column = index % dimensionToExtent; var row = (index - column) % dataSize / dimensionToExtent; var directions = {}; var isScrollbarVisible; if (isPrimaryDirectionVertical) { directions.left = isLeftMovement; directions.right = isRightMovement; directions.up = isUpKey; directions.down = isDownKey; isScrollbarVisible = isVerticalScrollbarVisible; } else { directions.left = isUpKey; directions.right = isDownKey; directions.up = isLeftMovement; directions.down = isRightMovement; isScrollbarVisible = isHorizontalScrollbarVisible; } isLeaving = directions.up && row === 0 || directions.down && row === Math.floor((dataSize - 1) % dataSize / dimensionToExtent) || directions.left && column === 0 || directions.right && (!focusableScrollbar || !isScrollbarVisible) && (column === dimensionToExtent - 1 || index === dataSize - 1 && row === 0); /* istanbul ignore next */ if (isLeaving) { if (repeat) { var _getContainerConfig; // if focus is about to leave items by holding down an arrowy key ev.preventDefault(); if (spotlightId && !((_getContainerConfig = (0, _container.getContainerConfig)(spotlightId)) !== null && _getContainerConfig !== void 0 && _getContainerConfig.continue5WayHold)) { ev.stopPropagation(); } } else if (!candidate && ev.timeStamp - mutableRef.current.pointerHideTimeStamp < 30) { // No candidate // Spotlight will focus the same item again, then a list scrolls to show the focused item // 30 is an arbitrary value target.blur(); mutableRef.current.pointerHideTimeStamp = 0; } } else { handleDirectionKeyDown(ev, 'keyDown', { direction: direction, keyCode: keyCode, repeat: repeat, target: target }); } } } prevKeyDownIndex = index; if (isLeaving) { handleDirectionKeyDown(ev, 'keyLeave'); } } } else if (isPageUp(keyCode) || isPageDown(keyCode)) { handlePageUpDownKeyDown(); } else if (isPointerHide(keyCode)) { /* istanbul ignore next */ mutableRef.current.pointerHideTimeStamp = ev.timeStamp; } } function handleKeyUp(_ref2) { var keyCode = _ref2.keyCode; if ((0, _spotlight.getDirection)(keyCode) || isEnter(keyCode)) { handle5WayKeyUp(); } } (0, _utilEvent["default"])('keydown').addEventListener(scrollContainerRef, handleKeyDown, { capture: true }); (0, _utilEvent["default"])('keyup').addEventListener(scrollContainerRef, handleKeyUp, { capture: true }); return function () { (0, _utilEvent["default"])('keydown').removeEventListener(scrollContainerRef, handleKeyDown, { capture: true }); (0, _utilEvent["default"])('keyup').removeEventListener(scrollContainerRef, handleKeyUp, { capture: true }); }; }, [getNextIndex, props, instances, context]); // Functions function addGlobalKeyDownEventListener(fn) { mutableRef.current.fn = fn; (0, _utilEvent["default"])('keydown').addEventListener(document, mutableRef.current.fn, { capture: true }); } function removeGlobalKeyDownEventListener() { (0, _utilEvent["default"])('keydown').removeEventListener(document, mutableRef.current.fn, { capture: true }); mutableRef.current.fn = null; } // Return return { addGlobalKeyDownEventListener: addGlobalKeyDownEventListener, removeGlobalKeyDownEventListener: removeGlobalKeyDownEventListener }; }; var useEventFocus = exports.useEventFocus = function useEventFocus(props, instances, context) { var scrollContainerRef = instances.scrollContainerRef, scrollContentHandle = instances.scrollContentHandle; var removeScaleEffect = context.removeScaleEffect; (0, _react.useLayoutEffect)(function () { function handleFocus(ev) { // only for VirtualGridList // To make the focused item cover other near items // We need to find out the general solution for multiple spottable inside of one item if (ev.target && scrollContentHandle.current && scrollContentHandle.current.isItemSized) { ev.target.parentNode.style.setProperty('z-index', 1); removeScaleEffect(); } } function handleBlur(ev) { // only for VirtualGridList // To make the blurred item normal if (ev.target && scrollContentHandle.current && scrollContentHandle.current.isItemSized) { ev.target.parentNode.style.setProperty('z-index', null); } } (0, _utilEvent["default"])('focusin').addEventListener(scrollContainerRef, handleFocus); (0, _utilEvent["default"])('focusout').addEventListener(scrollContainerRef, handleBlur); return function () { (0, _utilEvent["default"])('focusin').removeEventListener(scrollContainerRef, handleFocus); (0, _utilEvent["default"])('focusout').removeEventListener(scrollContainerRef, handleBlur); }; }); }; var _default = exports["default"] = useEventKey;