UNPKG

monday-ui-react-core

Version:

Official monday.com UI resources for application development in React.js

119 lines (108 loc) 4.45 kB
import { NavDirections } from "../../hooks/useFullKeyboardListeners"; import { DirectionMap, DirectionMaps, GridElementRef, Position } from "./GridKeyboardNavigationContextConstants"; function throwIfCausingCircularDependency(directionMaps: DirectionMaps, newPosition: Position) { const { topElement, bottomElement, leftElement, rightElement } = newPosition; if (topElement && bottomElement) { if (directionMaps[NavDirections.UP].get(topElement) === bottomElement) { throwMessage("BOTTOM", "TOP"); } if (directionMaps[NavDirections.DOWN].get(bottomElement) === topElement) { throwMessage("TOP", "BOTTOM"); } } if (leftElement && rightElement) { if (directionMaps[NavDirections.LEFT].get(leftElement) === rightElement) { throwMessage("RIGHT", "LEFT"); } if (directionMaps[NavDirections.RIGHT].get(rightElement) === leftElement) { throwMessage("LEFT", "RIGHT"); } } function throwMessage(directionFrom: string, directionTo: string) { throw new Error( `Circular positioning detected: the ${directionFrom} element is already positioned to the ${directionTo} of the ${directionTo} element. This probably means the layout isn't ordered correctly.` ); } } export const getDirectionMaps = (positions: Position[]) => { const directionMaps: DirectionMaps = { [NavDirections.RIGHT]: new Map(), [NavDirections.LEFT]: new Map(), [NavDirections.UP]: new Map(), [NavDirections.DOWN]: new Map() }; positions.forEach(position => { throwIfCausingCircularDependency(directionMaps, position); const { topElement, bottomElement, leftElement, rightElement } = position; if (topElement && bottomElement) { directionMaps[NavDirections.UP].set(bottomElement, topElement); directionMaps[NavDirections.DOWN].set(topElement, bottomElement); } if (leftElement && rightElement) { directionMaps[NavDirections.LEFT].set(rightElement, leftElement); directionMaps[NavDirections.RIGHT].set(leftElement, rightElement); } }); return directionMaps; }; export const getOppositeDirection = (direction: NavDirections) => { switch (direction) { case NavDirections.LEFT: return NavDirections.RIGHT; case NavDirections.RIGHT: return NavDirections.LEFT; case NavDirections.UP: return NavDirections.DOWN; case NavDirections.DOWN: return NavDirections.UP; default: throw new Error(`Unexpected direction: ${direction}`); } }; export const getOutmostElementInDirection = ( directionMaps: DirectionMaps, direction: NavDirections ): GridElementRef => { const directionMap = directionMaps[direction]; const firstEntry = [...directionMap][0]; // start with any element if (!firstEntry) { // no relations were registered for this direction - fallback to a different direction if ([NavDirections.LEFT, NavDirections.RIGHT].includes(direction)) { // there are no registered horizontal relations registered, try vertical relations. Get the top-most element. return getOutmostElementInDirection(directionMaps, NavDirections.UP); } // there are no registered vertical relations registered, try horizontal relations. Get the left-most element. return getOutmostElementInDirection(directionMaps, NavDirections.LEFT); } const firstRef = firstEntry[0]; return getLastFocusableElementFromElementInDirection(directionMap, firstRef); }; export const getNextElementToFocusInDirection = ( directionMap: DirectionMap, elementRef: GridElementRef ): null | GridElementRef => { const next = directionMap.get(elementRef); if (!next) { // this is the last element on the direction map - there' nothing next return null; } if (!next.current || next.current.disabled || next.current.dataset?.disabled === "true") { // the next element is not mounted or disabled - try the next one return getNextElementToFocusInDirection(directionMap, next); } return next; }; function getLastFocusableElementFromElementInDirection(directionMap: DirectionMap, initialRef: GridElementRef) { let done = false; let currentRef = initialRef; while (!done) { // as long as there's a mounted element which in that direction, take it. const nextEligible = getNextElementToFocusInDirection(directionMap, currentRef); if (!nextEligible) { done = true; } else { currentRef = nextEligible; } } return currentRef; }