UNPKG

react-elegant-ui

Version:

Elegant UI components, made by BEM best practices for react

119 lines 4.46 kB
import { useEffect } from 'react'; import { findIndexLoop } from '../../lib/findIndexLoop'; import { isKeyCode, Keys } from '../../lib/keyboard'; import { useImmutableCallback } from '../useImmutableCallback'; export var defaultPredicate = function (item) { return !item.disabled; }; /** * Return index of new active item * * If new item can't be found - return `-1` * It's return `-1` in case when all items except current is not match */ export var navigate = function (items, cursor, direction, predicate, loop) { var actualPredicate = predicate !== null && predicate !== void 0 ? predicate : defaultPredicate; var lastIndex = items.length - 1; // Init cursor if (cursor === -1) { var findDownToUp = direction === 'prev' || direction === 'end'; var startIndex = findDownToUp ? lastIndex : 0; var searchDirection = findDownToUp ? -1 : 1; return findIndexLoop(items, actualPredicate, startIndex, searchDirection); } var newCursor = -1; // Navigation if (direction === 'start') { newCursor = items.findIndex(actualPredicate); } else if (direction === 'end') { newCursor = findIndexLoop(items, actualPredicate, lastIndex, -1); } else if (direction === 'prev') { if (loop) { newCursor = findIndexLoop(items, // Skip first iteration, cuz it self object function (item, iteration) { return iteration > 0 && actualPredicate(item); }, cursor, -1); } else { var prevItemIndex = cursor - 1; // Skip while out of bounds if (prevItemIndex < 0) { return -1; } // Slice array from start to previous item and search from end to start var offsetFromEnd = items.slice(0, cursor).reverse().findIndex(actualPredicate); if (offsetFromEnd !== -1) { newCursor = prevItemIndex - offsetFromEnd; } } } else if (direction === 'next') { if (loop) { newCursor = findIndexLoop(items, // Skip first iteration, cuz it self object function (item, iteration) { return iteration > 0 && actualPredicate(item); }, cursor, 1); } else { var nextItemIndex = cursor + 1; var foundIndex = items.slice(nextItemIndex).findIndex(actualPredicate); newCursor = foundIndex === -1 ? -1 : foundIndex + nextItemIndex; } } return newCursor; }; /** * Global hook which implement keyboard navigation * * It useful when u want navigate in items by keyboard arrows */ export var useKeyboardNavigation = function (_a) { var enabled = _a.enabled, items = _a.items, cursor = _a.cursor, setCursor = _a.setCursor, predicate = _a.predicate, direction = _a.direction, loop = _a.loop, enableJump = _a.enableJump, _b = _a.eventCapture, eventCapture = _b === void 0 ? true : _b; // Update global handler var onKeyDownGlobal = useImmutableCallback(function (evt) { if (direction === undefined) return; var cursorIndex = cursor !== null && cursor !== void 0 ? cursor : -1; var navDirection = null; // Set navigation direction if (enableJump && isKeyCode(evt.code, [Keys.HOME, Keys.END])) { navDirection = evt.code === Keys.HOME ? 'start' : 'end'; } else if (direction.indexOf('vertical') !== -1 && isKeyCode(evt.code, [Keys.UP, Keys.DOWN])) { navDirection = evt.code === Keys.UP ? 'prev' : 'next'; } else if (direction.indexOf('horizontal') !== -1 && isKeyCode(evt.code, [Keys.LEFT, Keys.RIGHT])) { navDirection = evt.code === Keys.LEFT ? 'prev' : 'next'; } // Skip handle a non navigation keys if (navDirection === null) return; evt.preventDefault(); var newCursor = navigate(items, cursorIndex, navDirection, predicate, loop); // Fix cursor when current cursor exist but next item is not found // Should not reset cursor due to this, but setter should be called if (cursorIndex !== -1 && newCursor === -1) { newCursor = cursorIndex; } // Set cursor if (setCursor !== undefined) { setCursor(newCursor); } }, [items, cursor, setCursor, predicate, direction, loop, enableJump]); // Global handler useEffect(function () { if (!enabled) return; document.addEventListener('keydown', onKeyDownGlobal, { capture: eventCapture }); return function () { return document.removeEventListener('keydown', onKeyDownGlobal, { capture: eventCapture }); }; }, [enabled, onKeyDownGlobal, eventCapture]); };