@enact/sandstone
Version:
Large-screen/TV support library for Enact, containing a variety of UI components.
325 lines (314 loc) • 15.1 kB
JavaScript
;
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;