@carbon/ibm-products
Version:
Carbon for IBM Products
164 lines (162 loc) • 7.83 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { blockClass, checkForHoldingKey, focusThisField, focusThisItem, manageTabIndexAndFocus, traverseClockwise, traverseReverse } from "./util.js";
//#region src/components/ConditionBuilder/utils/handleKeyboardEvents.js
/**
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const handleKeyDown = (evt, conditionBuilderRef, variant) => {
if (!document.activeElement.closest(`.${blockClass}__popover`)?.querySelector("[role=\"dialog\"]")) handleKeyPressForMainContent(evt, conditionBuilderRef, variant);
};
const handleKeyDownForPopover = (evt, conditionBuilderRef, popoverRef, closePopover) => {
if (excludeKeyPress(evt)) return;
handleKeyPressForPopover(evt, popoverRef?.current, conditionBuilderRef, closePopover);
};
const excludeKeyPress = (evt) => {
return !["Escape"].includes(evt.key) && evt.target.closest(`.${blockClass}__item-date`);
};
const getVisibleOptions = (parentContainer) => {
return Array.from(parentContainer.querySelectorAll(`[role="option"]`)).filter((el) => !el.hasAttribute("aria-disabled") && !el.hasAttribute("aria-hidden"));
};
const handleKeyPressForPopover = (evt, parentContainer, conditionBuilderRef, closePopover) => {
const key = evt.key;
const isHoldingShiftKey = checkForHoldingKey(evt, "shiftKey");
const isMultiSelect = parentContainer.querySelector("ul")?.dataset.multiSelect;
const isOptionInput = parentContainer.querySelectorAll(`[role="option"]`)?.length;
let allItems = [];
if (key === "Escape") {
closePopover?.();
focusThisField(evt, conditionBuilderRef);
evt.preventDefault();
evt.stopPropagation();
}
if (isOptionInput) switch (key) {
case "ArrowUp":
evt.preventDefault();
allItems = getVisibleOptions(parentContainer);
allItems.forEach((eachElem, index, allElements) => {
traverseReverse(eachElem, index, allElements, null, null, conditionBuilderRef);
});
if (Array.from(evt.target.closest("ul")?.querySelectorAll("li") ?? []).indexOf(evt.target) === 1) parentContainer.querySelector(`.${blockClass}__popover-content-wrapper`).scrollTop = 0;
break;
case "ArrowDown":
evt.preventDefault();
allItems = getVisibleOptions(parentContainer);
allItems.forEach((eachElem, index, allElements) => {
traverseClockwise(eachElem, index, allElements, null, null, conditionBuilderRef);
});
break;
case "Tab":
allItems = [...Array.from(parentContainer.querySelectorAll(`.${blockClass}__selectAll-button,[role="searchbox"]`)), getVisibleOptions(parentContainer)?.[0]];
allItems.forEach((eachElem, index, allElements) => {
if (isHoldingShiftKey) traverseReverse(eachElem, index, allElements, true, true, conditionBuilderRef);
else traverseClockwise(eachElem, index, allElements, true, true, conditionBuilderRef);
});
evt.preventDefault();
break;
case " ":
if (isMultiSelect === "true") {
if (document.activeElement.type !== "button") document.activeElement?.click();
evt.preventDefault();
}
break;
case "Enter":
if (document.activeElement.type !== "button") {
focusThisField(evt, conditionBuilderRef);
document.activeElement?.click();
}
break;
default: break;
}
else if (key === "Enter" && !isHoldingShiftKey) {
if (document.activeElement.type !== "button") {
closePopover?.();
focusThisField(evt, conditionBuilderRef);
}
}
};
const handleKeyPressForMainContent = (evt, conditionBuilderRef, variant) => {
switch (evt.key) {
case "ArrowRight":
evt.preventDefault();
if (variant == "Hierarchical") {
let allCellsInRow = Array.from(evt.target.closest("[role=\"row\"]")?.querySelectorAll("[role=\"gridcell\"] button"));
if (allCellsInRow.length === 1) {
evt.target = evt.target.closest("[role=\"row\"]");
handleRowNavigationHierarchical(evt, conditionBuilderRef, variant);
} else if (evt.target.getAttribute("role") == "row") manageTabIndexAndFocus(allCellsInRow[0], conditionBuilderRef);
else {
let currentItemIndex = allCellsInRow.indexOf(evt.target);
if (currentItemIndex < allCellsInRow.length - 1) focusThisItem(allCellsInRow[currentItemIndex + 1], conditionBuilderRef);
}
} else handleCellNavigation(evt, conditionBuilderRef);
break;
case "ArrowLeft":
evt.preventDefault();
if (variant == "Hierarchical") {
if (evt.target.getAttribute("role") !== "row") {
let allCellsInRow = Array.from(evt.target.closest("[role=\"row\"]")?.querySelectorAll("[role=\"gridcell\"] button"));
let currentItemIndex = allCellsInRow.indexOf(evt.target);
if (currentItemIndex > 0) focusThisItem(allCellsInRow[currentItemIndex - 1], conditionBuilderRef);
else manageTabIndexAndFocus(evt.target.closest(`[role="row"]`), conditionBuilderRef);
}
} else handleCellNavigation(evt, conditionBuilderRef);
break;
case "ArrowUp":
case "ArrowDown":
evt.preventDefault();
if (variant == "Hierarchical") handleRowNavigationHierarchical(evt, conditionBuilderRef, variant);
else handleRowNavigation(evt, conditionBuilderRef, variant);
break;
case "Enter":
if (document.activeElement?.querySelectorAll(`button`)?.length === 1) document.activeElement?.querySelectorAll(`button`)[0]?.click();
break;
default: break;
}
};
const getRows = (conditionBuilderRef) => {
return Array.from(conditionBuilderRef.current?.querySelectorAll("[role=\"row\"]"));
};
const getRowIndex = (element, conditionBuilderRef) => {
return getRows(conditionBuilderRef).findIndex((row) => row.contains(element));
};
const handleRowNavigation = (evt, conditionBuilderRef, variant) => {
const rows = getRows(conditionBuilderRef);
navigateToNextRowCell(evt, getRowIndex(evt.target, conditionBuilderRef), rows, variant, conditionBuilderRef);
};
const handleRowNavigationHierarchical = (evt, conditionBuilderRef, variant) => {
const rows = getRows(conditionBuilderRef);
const currentRowIndex = getRowIndex(evt.target, conditionBuilderRef);
let nextRowIndex = currentRowIndex;
if (evt.target.getAttribute("role") == "row") {
if (["ArrowDown", "ArrowRight"].includes(evt.key)) nextRowIndex += 1;
else if (evt.key === "ArrowUp") nextRowIndex -= 1;
if (nextRowIndex < 0) nextRowIndex = 0;
else if (nextRowIndex >= rows.length) nextRowIndex = rows.length - 1;
if (nextRowIndex !== currentRowIndex) manageTabIndexAndFocus(rows[nextRowIndex], conditionBuilderRef);
} else navigateToNextRowCell(evt, currentRowIndex, rows, variant, conditionBuilderRef);
};
const navigateToNextRowCell = (evt, currentRowIndex, rows, variant, conditionBuilderRef) => {
let nextRowIndex = currentRowIndex;
if (evt.key === "ArrowUp") nextRowIndex = currentRowIndex == 0 ? currentRowIndex : currentRowIndex - 1;
if (evt.key === "ArrowDown") nextRowIndex = currentRowIndex === rows.length - 1 ? rows.length - 1 : currentRowIndex + 1;
const nextRow = rows[nextRowIndex];
const itemName = evt.target.dataset.name;
if (nextRow?.querySelector(`[data-name="${itemName}"]`)) manageTabIndexAndFocus(nextRow?.querySelector(`[data-name="${itemName}"]`), conditionBuilderRef);
else if (variant === "Hierarchical") manageTabIndexAndFocus(nextRow, conditionBuilderRef);
};
const handleCellNavigation = (evt, conditionBuilderRef) => {
conditionBuilderRef.current.querySelectorAll(`[role="gridcell"] button`).forEach((eachElem, index, allElements) => {
if (evt.key === "ArrowRight") traverseClockwise(eachElem, index, allElements, null, null, conditionBuilderRef);
else traverseReverse(eachElem, index, allElements, null, null, conditionBuilderRef);
});
};
//#endregion
export { handleKeyDown, handleKeyDownForPopover };