Skip to content

Commit

Permalink
Merge pull request #1214 from tomivm/feature/navigate-with-arrows-keys
Browse files Browse the repository at this point in the history
feature - navigate on fixed boards with arrow keys
  • Loading branch information
martinbedouret authored Jun 21, 2022
2 parents b407a52 + b102efe commit 0a3217d
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 3 deletions.
170 changes: 167 additions & 3 deletions src/components/FixedGrid/Grid.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

Expand All @@ -16,6 +16,10 @@ function chunks(array, size) {
return results;
}

const focusPosition = {
x: 0,
y: 0
};
function Grid(props) {
const { className, items, style, ...other } = props;

Expand All @@ -24,19 +28,179 @@ function Grid(props) {

const gridClassName = classNames(styles.grid, className);

const findPressedArrow = event => {
const code = event.code;
const right = code === 'ArrowRight';
const left = code === 'ArrowLeft';
const up = code === 'ArrowUp';
const down = code === 'ArrowDown';

if (!(right || left || up || down)) return null;
event.preventDefault();
if (event.repeat) return null;
return { right, left, up, down };
};

const setFocusPositionFromFocusedButton = buttonElement => {
const setFocusPositionFromId = id => {
const divider = '-';
const positionArrayXY = id.split(divider);
const xPosition = parseInt(positionArrayXY[0]);
const yPosition = parseInt(positionArrayXY[1]);
focusPosition.x = xPosition;
focusPosition.y = yPosition;
};
if (buttonElement) {
const activeDraggableItem = buttonElement.parentNode;
const activeDroppableCellId = activeDraggableItem?.parentNode?.id;
if (activeDroppableCellId) setFocusPositionFromId(activeDroppableCellId);
}
};

const handleOnKeyDown = event => {
const keycode = event.code;

const manageArrows = event => {
const setFocusPosition = pressedArrow => {
const { columns, rows } = other;
const totalRows = pages.length * rows;
const { right, left, up, down } = pressedArrow;
const rightLimit = focusPosition.x >= columns - 1;
const leftLimit = focusPosition.x <= 0;
const topLimit = focusPosition.y <= 0;
const bottomLimit = focusPosition.y >= totalRows - 1;
if (right) {
if (rightLimit) {
focusPosition.x = 0;
return;
}
focusPosition.x = focusPosition.x + 1;
return;
}
if (left) {
if (leftLimit) {
focusPosition.x = columns - 1;
return;
}
focusPosition.x = focusPosition.x - 1;
return;
}
if (up) {
if (topLimit) {
focusPosition.y = totalRows - 1;
return;
}
focusPosition.y = focusPosition.y - 1;
return;
}
if (down) {
if (bottomLimit) {
focusPosition.y = 0;
return;
}
focusPosition.y = focusPosition.y + 1;
return;
}
};

const pressedArrow = findPressedArrow(event);
if (!pressedArrow) return;
setFocusPosition(pressedArrow);

const currentId = `${focusPosition.x}-${focusPosition.y}`;
const currentTile = document.getElementById(currentId);

if (currentTile) {
const isAvailableTile = () => {
if (currentTile?.firstChild) {
currentTile.firstChild.querySelector('button').focus();
return true;
}
return false;
};

if (isAvailableTile()) return;
}
manageArrows({
code: keycode,
preventDefault: event.preventDefault,
repeat: false
});
};

const manageTabs = () => {
const tabIsPressed = () => {
const tab = keycode === 'Tab';
if (tab) return true;
return false;
};
if (tabIsPressed()) {
const awaitTabSetNewFocus = () => {
const refreshFocusPosition = () => {
const activeButtonElement = document.activeElement;
setFocusPositionFromFocusedButton(activeButtonElement);
};
setTimeout(refreshFocusPosition, 0);
};
awaitTabSetNewFocus();
}
};

manageArrows(event);
manageTabs();
};

useEffect(() => {
const manageKeyDown = event => {
if (findPressedArrow(event)) {
const focusIsNotOnTile = () => {
const activeElement = document.activeElement;
const activeElementChildsArray = Array.from(
activeElement?.childNodes
);
if (
activeElementChildsArray.find(
element => element.className === 'Symbol'
)
)
return false;
return true;
};

if (focusIsNotOnTile()) {
const focusFirstTile = () => {
const firstTile = document.getElementsByClassName('Tile')[0];
if (!firstTile) return;
firstTile.focus();
setFocusPositionFromFocusedButton(firstTile);
};

focusFirstTile();
}
}
};

window.addEventListener('keydown', manageKeyDown);

return () => {
window.removeEventListener('keydown', manageKeyDown);
};
}, []);

return (
<div className={styles.root} style={style}>
<div className={styles.root} style={style} onKeyDown={handleOnKeyDown}>
{pages.length > 0 ? (
pages.map((pageItems, i) => (
<GridBase
{...other}
className={gridClassName}
items={pageItems}
key={i}
page={i}
/>
))
) : (
<GridBase {...other} className={gridClassName} />
<GridBase {...other} className={gridClassName} page={0} />
)}
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/FixedGrid/GridBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function GridBase(props) {
renderEmptyCell,
renderItem,
rows,
page,
...other
} = props;

Expand All @@ -33,9 +34,12 @@ function GridBase(props) {
{grid.map((row, rowIndex) => (
<Row key={rowIndex}>
{row.map((item, columnIndex) => {
const yPosition = page * rows + rowIndex;
const idWithPosition = `${columnIndex}-${yPosition}`;
return (
<DroppableCell
key={columnIndex}
id={idWithPosition}
accept={'grid-item'}
onDrop={item => {
const position = { row: rowIndex, column: columnIndex };
Expand Down

0 comments on commit 0a3217d

Please sign in to comment.