@carbon/ibm-products
Version:
Carbon for IBM Products
271 lines (264 loc) • 9.71 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var deepCloneObject = require('../../../global/js/utils/deepCloneObject.js');
var uuidv4 = require('../../../global/js/utils/uuidv4.js');
var selectAllCells = require('./selectAllCells.js');
const includesResourceKey = (arr, usingMac) => {
if (usingMac) {
return includesMeta(arr);
}
if (!usingMac) {
return includesControl(arr);
}
};
const includesShift = arr => {
if (arr.includes('ShiftLeft') || arr.includes('ShiftRight')) {
return true;
}
return false;
};
const includesMeta = arr => {
if (arr.includes('MetaLeft') || arr.includes('MetaRight')) {
return true;
}
return false;
};
const includesControl = arr => {
if (arr.includes('ControlLeft') || arr.includes('ControlRight')) {
return true;
}
return false;
};
const handleMultipleKeys = _ref => {
let {
activeCellCoordinates,
event,
keysPressedList,
selectionAreas,
currentMatcher,
rows,
setSelectionAreas,
columns,
updateActiveCellCoordinates,
spreadsheetRef,
removeCellSelections,
blockClass,
setCurrentMatcher,
usingMac
} = _ref;
const selectionAreasClone = deepCloneObject.deepCloneObject(selectionAreas);
const indexOfCurrentArea = selectionAreasClone.findIndex(item => item.matcher === currentMatcher);
const pointToUpdate = selectionAreasClone[indexOfCurrentArea]?.point2 ? selectionAreasClone[indexOfCurrentArea]?.point2 : selectionAreasClone[indexOfCurrentArea]?.point1;
// Down + Shift
if (includesShift(keysPressedList) && keysPressedList.includes('ArrowDown') && keysPressedList.length === 2) {
if (rows.length - 1 === pointToUpdate?.row || activeCellCoordinates?.row === 'header' || activeCellCoordinates?.column === 'header') {
return;
}
const newPoint = {
row: pointToUpdate.row + 1,
column: pointToUpdate.column
};
selectionAreasClone[indexOfCurrentArea].point2 = newPoint;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// Right + Shift
if (includesShift(keysPressedList) && keysPressedList.includes('ArrowRight') && keysPressedList.length === 2) {
if (columns.length - 1 === pointToUpdate?.column || activeCellCoordinates?.row === 'header' || activeCellCoordinates?.column === 'header') {
return;
}
const newPoint = {
row: pointToUpdate.row,
column: pointToUpdate.column + 1
};
selectionAreasClone[indexOfCurrentArea].point2 = newPoint;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// Up + Shift
if (includesShift(keysPressedList) && keysPressedList.includes('ArrowUp') && keysPressedList.length === 2) {
if (pointToUpdate?.row === 0 || activeCellCoordinates?.row === 'header' || activeCellCoordinates?.column === 'header') {
return;
}
const newPoint = {
row: pointToUpdate.row - 1,
column: pointToUpdate.column
};
selectionAreasClone[indexOfCurrentArea].point2 = newPoint;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// Left + Shift
if (includesShift(keysPressedList) && keysPressedList.includes('ArrowLeft') && keysPressedList.length === 2) {
if (pointToUpdate?.column === 0 || activeCellCoordinates?.row === 'header' || activeCellCoordinates?.column === 'header') {
return;
}
const newPoint = {
row: pointToUpdate.row,
column: pointToUpdate.column - 1
};
selectionAreasClone[indexOfCurrentArea].point2 = newPoint;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// CMD + a (select all)
if (includesResourceKey(keysPressedList, usingMac) && keysPressedList.includes('KeyA')) {
event.preventDefault();
const selectionPoint1 = {
row: 0,
column: 0
};
const selectionPoint2 = {
row: rows.length - 1,
column: columns.length - 1
};
// If indexOfCurrentArea is -1, it means the active cell is in a cell header position
if (indexOfCurrentArea === -1) {
selectAllCells.selectAllCells({
ref: spreadsheetRef,
setCurrentMatcher,
setSelectionAreas,
rows,
columns,
activeCellCoordinates,
updateActiveCellCoordinates
});
}
selectionAreasClone[indexOfCurrentArea].point1 = selectionPoint1;
selectionAreasClone[indexOfCurrentArea].point2 = selectionPoint2;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// CONTROL + SPACE (Select current column)
if (includesControl(keysPressedList) && keysPressedList.includes('Space')) {
const selectionPoint1 = {
row: 0,
column: activeCellCoordinates?.column === 'header' ? 0 : activeCellCoordinates?.column
};
const selectionPoint2 = {
row: rows.length - 1,
column: activeCellCoordinates?.column === 'header' ? 0 : activeCellCoordinates?.column
};
// If indexOfCurrentArea is -1, it means the active cell is in a cell header position
if (indexOfCurrentArea === -1) {
const tempMatcher = uuidv4.default();
const newSelectionArea = {
point1: selectionPoint1,
point2: selectionPoint2,
areaCreated: false,
matcher: tempMatcher
};
const coordinatesClone = {
...activeCellCoordinates
};
updateActiveCellCoordinates({
coords: coordinatesClone,
updatedValue: {
column: activeCellCoordinates?.column === 'header' ? 0 : activeCellCoordinates?.column,
row: activeCellCoordinates?.row === 'header' ? 0 : activeCellCoordinates?.row
}
});
setCurrentMatcher(tempMatcher);
return setSelectionAreas([newSelectionArea]);
}
selectionAreasClone[indexOfCurrentArea].point1 = selectionPoint1;
selectionAreasClone[indexOfCurrentArea].point2 = selectionPoint2;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// Shift + SPACE (Select current row)
if (includesShift(keysPressedList) && keysPressedList.includes('Space')) {
const selectionPoint1 = {
row: activeCellCoordinates?.row === 'header' ? 0 : activeCellCoordinates?.row,
column: 0
};
const selectionPoint2 = {
row: activeCellCoordinates?.row === 'header' ? 0 : activeCellCoordinates?.row,
column: columns.length - 1
};
// If indexOfCurrentArea is -1, it means the active cell is in a cell header position
if (indexOfCurrentArea === -1) {
const tempMatcher = uuidv4.default();
const newSelectionArea = {
point1: selectionPoint1,
point2: selectionPoint2,
areaCreated: false,
matcher: tempMatcher
};
const coordinatesClone = {
...activeCellCoordinates
};
updateActiveCellCoordinates({
coords: coordinatesClone,
updatedValue: {
column: activeCellCoordinates?.column === 'header' ? 0 : activeCellCoordinates?.column,
row: activeCellCoordinates?.row === 'header' ? 0 : activeCellCoordinates?.row
}
});
setCurrentMatcher(tempMatcher);
return setSelectionAreas([newSelectionArea]);
}
selectionAreasClone[indexOfCurrentArea].point1 = selectionPoint1;
selectionAreasClone[indexOfCurrentArea].point2 = selectionPoint2;
selectionAreasClone[indexOfCurrentArea].areaCreated = false;
setSelectionAreas(selectionAreasClone);
}
// CMD + HOME (Selects first cell in first row)
if (includesResourceKey(keysPressedList, usingMac) && keysPressedList.includes('Home')) {
const scrollElement = spreadsheetRef.current.querySelector(`.${blockClass}__list--container`);
scrollElement.scrollTop = 0;
const coordinatesClone = {
...activeCellCoordinates
};
removeCellSelections({
spreadsheetRef
});
updateActiveCellCoordinates({
coords: coordinatesClone,
updatedValue: {
column: 0,
row: 0
}
});
}
// CMD + END (Selects last cell in last row)
if (includesResourceKey(keysPressedList, usingMac) && keysPressedList.includes('End')) {
const scrollElement = spreadsheetRef.current.querySelector(`.${blockClass}__list--container`);
scrollElement.scrollTop = scrollElement.scrollHeight;
const coordinatesClone = {
...activeCellCoordinates
};
removeCellSelections({
spreadsheetRef
});
const lastCellExists = !!rows[rows?.length - 1].cells[columns?.length - 1];
const updateToLastCell = () => {
updateActiveCellCoordinates({
coords: coordinatesClone,
updatedValue: {
column: columns.length - 1,
row: rows.length - 1
}
});
};
// With the spreadsheet supporting virtualized data, it's possible that the last cell
// has never been rendered yet, if that's the case we scroll to the bottom of the spreadsheet
// and add a timeout to wait for the last row to render to the DOM before updating the active cell coordinates.
// If we're able to verify that the last row has been rendered, no timeout is used.
if (lastCellExists) {
updateToLastCell();
} else {
setTimeout(() => {
updateToLastCell();
}, 1000);
}
}
};
exports.handleMultipleKeys = handleMultipleKeys;
exports.includesResourceKey = includesResourceKey;
exports.includesShift = includesShift;