@zendeskgarden/container-grid
Version:
Containers relating to Grid in the Garden Design System
235 lines (229 loc) • 7.73 kB
JavaScript
/**
* Copyright Zendesk, Inc.
*
* Use of this source code is governed under the Apache License, Version 2.0
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { KEYS, useId, composeEventHandlers } from '@zendeskgarden/container-utilities';
import PropTypes from 'prop-types';
const getCellDown = (matrix, rowIndex, colIndex, wrap) => {
let retVal = [];
const rowCount = matrix.length;
const colCount = matrix[0].length;
const lastRowLength = matrix[rowCount - 1].length;
const isLastCellFocused = rowIndex === rowCount - (colCount > lastRowLength ? 2 : 1) && colIndex === colCount - 1;
if (!isLastCellFocused) {
if (rowIndex === rowCount - (colIndex >= lastRowLength ? 2 : 1) ) {
if (wrap) {
retVal = [0, colIndex + 1];
}
} else {
retVal = [rowIndex + 1, colIndex];
}
}
return retVal;
};
const getCellLeft = (matrix, rowIndex, colIndex, wrap) => {
let retVal = [];
const colCount = matrix[0].length;
const isFirstCellFocused = rowIndex === 0 && colIndex === 0;
if (!isFirstCellFocused) {
if (colIndex === 0 ) {
if (wrap) {
retVal = [rowIndex - 1, colCount - 1];
}
} else {
retVal = [rowIndex, colIndex - 1];
}
}
return retVal;
};
const getCellRight = (matrix, rowIndex, colIndex, wrap) => {
let retVal = [];
const rowCount = matrix.length;
const colCount = matrix[0].length;
const lastRowIndex = rowCount - 1;
const lastColIndex = matrix[lastRowIndex].length - 1;
const isLastCellFocused = rowIndex === lastRowIndex && colIndex === lastColIndex;
if (!isLastCellFocused) {
if (colIndex === colCount - 1 ) {
if (wrap) {
retVal = [rowIndex + 1, 0];
}
} else {
retVal = [rowIndex, colIndex + 1];
}
}
return retVal;
};
const getCellUp = (matrix, rowIndex, colIndex, wrap) => {
let retVal = [];
const rowCount = matrix.length;
const isFirstCellFocused = rowIndex === 0 && colIndex === 0;
if (!isFirstCellFocused) {
if (rowIndex === 0 ) {
if (wrap) {
const lastRowLength = matrix[rowCount - 1].length;
const col = colIndex - 1;
const row = rowCount - (col >= lastRowLength ? 2 : 1);
retVal = [row, col];
}
} else {
retVal = [rowIndex - 1, colIndex];
}
}
return retVal;
};
const getId = (idPrefix, rowIndex, colIndex) => `${idPrefix}--R${rowIndex + 1}C${colIndex + 1}`;
const GRID_KEYS = [KEYS.LEFT, KEYS.RIGHT, KEYS.UP, KEYS.DOWN, KEYS.HOME, KEYS.END];
function useGrid(_ref) {
let {
rtl,
wrap,
matrix,
idPrefix,
onChange = () => undefined,
environment,
rowIndex: controlledRowIndex,
colIndex: controlledColIndex,
defaultRowIndex,
defaultColIndex
} = _ref;
const doc = environment || document;
const prefix = useId(idPrefix);
const [uncontrolledRowIndex, setUncontrolledRowIndex] = useState(defaultRowIndex !== null && defaultRowIndex !== undefined ? defaultRowIndex : 0);
const [uncontrolledColIndex, setUncontrolledColIndex] = useState(defaultColIndex !== null && defaultColIndex !== undefined ? defaultColIndex : 0);
const isControlled = controlledRowIndex !== null && controlledColIndex !== null && controlledRowIndex !== undefined && controlledColIndex !== undefined;
const rowIndex = isControlled ? controlledRowIndex : uncontrolledRowIndex;
const colIndex = isControlled ? controlledColIndex : uncontrolledColIndex;
useEffect(() => {
const rowCount = matrix.length;
const colCount = matrix[0].length;
const isRowIndexInvalid = rowIndex >= rowCount;
const isColIndexInvalid = colIndex >= colCount;
if (isRowIndexInvalid || isColIndexInvalid) {
let _rowIndex = rowIndex;
let _colIndex = colIndex;
if (isRowIndexInvalid) {
_rowIndex = rowCount > 0 ? rowCount - 1 : 0;
}
if (isColIndexInvalid) {
_colIndex = colCount > 0 ? colCount - 1 : 0;
}
if (!isControlled) {
setUncontrolledRowIndex(_rowIndex);
setUncontrolledColIndex(_colIndex);
}
onChange(_rowIndex, _colIndex);
}
}, [matrix, rowIndex, colIndex, isControlled, setUncontrolledColIndex, onChange]);
const getGridProps = useCallback(_ref2 => {
let {
role = 'grid',
...other
} = _ref2;
return {
'data-garden-container-id': 'containers.grid',
'data-garden-container-version': '3.0.19',
role: role === null ? undefined : role,
...other
};
}, []);
const getGridCellProps = useCallback(function (_temp) {
let {
role = 'gridcell',
rowIndex: _rowIndex,
colIndex: _colIndex,
onFocus,
onKeyDown,
...other
} = _temp === void 0 ? {
rowIndex: 0,
colIndex: 0
} : _temp;
const handleFocus = () => {
if (!isControlled) {
setUncontrolledRowIndex(_rowIndex);
setUncontrolledColIndex(_colIndex);
}
onChange(_rowIndex, _colIndex);
};
const handleKeyDown = event => {
if (GRID_KEYS.includes(event.key)) {
event.preventDefault();
let row = rowIndex;
let col = colIndex;
switch (event.key) {
case KEYS.RIGHT:
[row, col] = rtl ? getCellLeft(matrix, rowIndex, colIndex, wrap) : getCellRight(matrix, rowIndex, colIndex, wrap);
break;
case KEYS.LEFT:
[row, col] = rtl ? getCellRight(matrix, rowIndex, colIndex, wrap) : getCellLeft(matrix, rowIndex, colIndex, wrap);
break;
case KEYS.DOWN:
[row, col] = getCellDown(matrix, rowIndex, colIndex, wrap);
break;
case KEYS.UP:
[row, col] = getCellUp(matrix, rowIndex, colIndex, wrap);
break;
case KEYS.HOME:
row = event.ctrlKey ? 0 : rowIndex;
col = 0;
break;
case KEYS.END:
{
const rowCount = matrix.length;
const lastRowIndex = rowCount - 1;
const lastColIndex = matrix[lastRowIndex].length - 1;
row = event.ctrlKey ? lastRowIndex : rowIndex;
col = event.ctrlKey ? lastColIndex : matrix[rowIndex].length - 1;
break;
}
}
if (row !== rowIndex || col !== colIndex) {
const id = getId(prefix, row, col);
const element = doc.getElementById(id);
element?.focus();
}
}
};
return {
'data-garden-container-id': 'containers.grid.cell',
'data-garden-container-version': '3.0.19',
id: getId(prefix, _rowIndex, _colIndex),
role: role === null ? undefined : role,
tabIndex: rowIndex === _rowIndex && colIndex === _colIndex ? 0 : -1,
onFocus: composeEventHandlers(onFocus, handleFocus),
onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
...other
};
}, [matrix, rowIndex, colIndex, doc, prefix, isControlled, onChange, rtl, wrap]);
return useMemo(() => ({
getGridProps,
getGridCellProps
}), [getGridProps, getGridCellProps]);
}
const GridContainer = _ref => {
let {
children,
render = children,
...options
} = _ref;
return React.createElement(React.Fragment, null, render(useGrid(options)));
};
GridContainer.propTypes = {
children: PropTypes.func,
render: PropTypes.func,
rtl: PropTypes.bool,
wrap: PropTypes.bool,
matrix: PropTypes.any,
idPrefix: PropTypes.string,
rowIndex: PropTypes.number,
colIndex: PropTypes.number,
defaultRowIndex: PropTypes.number,
defaultColIndex: PropTypes.number,
onChange: PropTypes.func,
environment: PropTypes.any
};
export { GridContainer, useGrid };