@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
289 lines (283 loc) • 9.08 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VERTICAL_KEYS_WITH_EXTRA_KEYS = exports.VERTICAL_KEYS = exports.HORIZONTAL_KEYS_WITH_EXTRA_KEYS = exports.HORIZONTAL_KEYS = exports.HOME = exports.END = exports.ARROW_UP = exports.ARROW_RIGHT = exports.ARROW_LEFT = exports.ARROW_KEYS = exports.ARROW_DOWN = exports.ALL_KEYS = void 0;
exports.buildCellMap = buildCellMap;
exports.findNonDisabledIndex = findNonDisabledIndex;
exports.getCellIndexOfCorner = getCellIndexOfCorner;
exports.getCellIndices = getCellIndices;
exports.getGridNavigatedIndex = getGridNavigatedIndex;
exports.getMaxIndex = getMaxIndex;
exports.getMinIndex = getMinIndex;
exports.getTextDirection = getTextDirection;
exports.isDifferentRow = isDifferentRow;
exports.isDisabled = isDisabled;
exports.isIndexOutOfBounds = isIndexOutOfBounds;
var _hasComputedStyleMapSupport = require("../utils/hasComputedStyleMapSupport");
var _owner = require("../utils/owner");
const ARROW_UP = exports.ARROW_UP = 'ArrowUp';
const ARROW_DOWN = exports.ARROW_DOWN = 'ArrowDown';
const ARROW_LEFT = exports.ARROW_LEFT = 'ArrowLeft';
const ARROW_RIGHT = exports.ARROW_RIGHT = 'ArrowRight';
const HOME = exports.HOME = 'Home';
const END = exports.END = 'End';
const HORIZONTAL_KEYS = exports.HORIZONTAL_KEYS = [ARROW_LEFT, ARROW_RIGHT];
const HORIZONTAL_KEYS_WITH_EXTRA_KEYS = exports.HORIZONTAL_KEYS_WITH_EXTRA_KEYS = [ARROW_LEFT, ARROW_RIGHT, HOME, END];
const VERTICAL_KEYS = exports.VERTICAL_KEYS = [ARROW_UP, ARROW_DOWN];
const VERTICAL_KEYS_WITH_EXTRA_KEYS = exports.VERTICAL_KEYS_WITH_EXTRA_KEYS = [ARROW_UP, ARROW_DOWN, HOME, END];
const ARROW_KEYS = exports.ARROW_KEYS = [...HORIZONTAL_KEYS, ...VERTICAL_KEYS];
const ALL_KEYS = exports.ALL_KEYS = [...ARROW_KEYS, HOME, END];
function stopEvent(event) {
event.preventDefault();
event.stopPropagation();
}
function isDifferentRow(index, cols, prevRow) {
return Math.floor(index / cols) !== prevRow;
}
function isIndexOutOfBounds(listRef, index) {
return index < 0 || index >= listRef.current.length;
}
function getMinIndex(listRef, disabledIndices) {
return findNonDisabledIndex(listRef, {
disabledIndices
});
}
function getMaxIndex(listRef, disabledIndices) {
return findNonDisabledIndex(listRef, {
decrement: true,
startingIndex: listRef.current.length,
disabledIndices
});
}
function findNonDisabledIndex(listRef, {
startingIndex = -1,
decrement = false,
disabledIndices,
amount = 1
} = {}) {
const list = listRef.current;
let index = startingIndex;
do {
index += decrement ? -amount : amount;
} while (index >= 0 && index <= list.length - 1 && isDisabled(list, index, disabledIndices));
return index;
}
function getGridNavigatedIndex(elementsRef, {
event,
orientation,
loop,
cols,
disabledIndices,
minIndex,
maxIndex,
prevIndex,
rtl,
stopEvent: stop = false
}) {
let nextIndex = prevIndex;
if (event.key === ARROW_UP) {
if (stop) {
stopEvent(event);
}
if (prevIndex === -1) {
nextIndex = maxIndex;
} else {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: nextIndex,
amount: cols,
decrement: true,
disabledIndices
});
if (loop && (prevIndex - cols < minIndex || nextIndex < 0)) {
const col = prevIndex % cols;
const maxCol = maxIndex % cols;
const offset = maxIndex - (maxCol - col);
if (maxCol === col) {
nextIndex = maxIndex;
} else {
nextIndex = maxCol > col ? offset : offset - cols;
}
}
}
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
nextIndex = prevIndex;
}
}
if (event.key === ARROW_DOWN) {
if (stop) {
stopEvent(event);
}
if (prevIndex === -1) {
nextIndex = minIndex;
} else {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
amount: cols,
disabledIndices
});
if (loop && prevIndex + cols > maxIndex) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex % cols - cols,
amount: cols,
disabledIndices
});
}
}
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
nextIndex = prevIndex;
}
}
// Remains on the same row/column.
if (orientation === 'both') {
const nextKey = rtl ? ARROW_LEFT : ARROW_RIGHT;
const prevKey = rtl ? ARROW_RIGHT : ARROW_LEFT;
const prevRow = Math.floor(prevIndex / cols);
if (event.key === nextKey) {
if (stop) {
stopEvent(event);
}
if (prevIndex % cols !== cols - 1) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
disabledIndices
});
if (loop && isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
}
} else if (loop) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
}
if (isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = prevIndex;
}
}
if (event.key === prevKey) {
if (stop) {
stopEvent(event);
}
if (prevIndex % cols !== 0) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
decrement: true,
disabledIndices
});
if (loop && isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex + (cols - prevIndex % cols),
decrement: true,
disabledIndices
});
}
} else if (loop) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex + (cols - prevIndex % cols),
decrement: true,
disabledIndices
});
}
if (isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = prevIndex;
}
}
const lastRow = Math.floor(maxIndex / cols) === prevRow;
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
if (loop && lastRow) {
nextIndex = event.key === prevKey ? maxIndex : findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
} else {
nextIndex = prevIndex;
}
}
}
return nextIndex;
}
/** For each cell index, gets the item index that occupies that cell */
function buildCellMap(sizes, cols, dense) {
const cellMap = [];
let startIndex = 0;
sizes.forEach(({
width,
height
}, index) => {
if (width > cols) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(`[Base UI]: Invalid grid - item width at index ${index} is greater than grid columns`);
}
}
let itemPlaced = false;
if (dense) {
startIndex = 0;
}
while (!itemPlaced) {
const targetCells = [];
for (let i = 0; i < width; i += 1) {
for (let j = 0; j < height; j += 1) {
targetCells.push(startIndex + i + j * cols);
}
}
if (startIndex % cols + width <= cols && targetCells.every(cell => cellMap[cell] == null)) {
targetCells.forEach(cell => {
cellMap[cell] = index;
});
itemPlaced = true;
} else {
startIndex += 1;
}
}
});
// convert into a non-sparse array
return [...cellMap];
}
/** Gets cell index of an item's corner or -1 when index is -1. */
function getCellIndexOfCorner(index, sizes, cellMap, cols, corner) {
if (index === -1) {
return -1;
}
const firstCellIndex = cellMap.indexOf(index);
const sizeItem = sizes[index];
switch (corner) {
case 'tl':
return firstCellIndex;
case 'tr':
if (!sizeItem) {
return firstCellIndex;
}
return firstCellIndex + sizeItem.width - 1;
case 'bl':
if (!sizeItem) {
return firstCellIndex;
}
return firstCellIndex + (sizeItem.height - 1) * cols;
case 'br':
return cellMap.lastIndexOf(index);
default:
return -1;
}
}
/** Gets all cell indices that correspond to the specified indices */
function getCellIndices(indices, cellMap) {
return cellMap.flatMap((index, cellIndex) => indices.includes(index) ? [cellIndex] : []);
}
function isDisabled(list, index, disabledIndices) {
if (disabledIndices) {
return disabledIndices.includes(index);
}
const element = list[index];
return element == null || element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true';
}
function getTextDirection(element) {
if ((0, _hasComputedStyleMapSupport.hasComputedStyleMapSupport)()) {
const direction = element.computedStyleMap().get('direction');
return direction?.value;
}
return (0, _owner.ownerWindow)(element).getComputedStyle(element).direction;
}