react-elegant-ui
Version:
Elegant UI components, made by BEM best practices for react
119 lines • 4.46 kB
JavaScript
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]);
};