@carbon/ibm-products
Version:
Carbon for IBM Products
297 lines (295 loc) • 11.3 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 { __toESM } from "../../_virtual/_rolldown/runtime.js";
import { require_classnames } from "../../node_modules/classnames/index.js";
import { pkg } from "../../settings.js";
import { usePreviousValue } from "../../global/js/hooks/usePreviousValue.js";
import { prepareProps } from "../../global/js/utils/props-helper.js";
import { selectAllCells } from "./utils/selectAllCells.js";
import { useSpreadsheetMouseMove } from "./hooks/useSpreadsheetMouseMove.js";
import { checkActiveHeaderCell } from "./utils/checkActiveHeaderCell.js";
import { checkSelectedHeaderCell } from "./utils/checkSelectedHeaderCell.js";
import { getSpreadsheetWidth } from "./utils/getSpreadsheetWidth.js";
import { checkForHoldingKey } from "./utils/checkForHoldingKey.js";
import { handleHeaderCellSelection } from "./utils/handleHeaderCellSelection.js";
import React, { forwardRef, useEffect, useState } from "react";
import PropTypes from "prop-types";
import { px } from "@carbon/layout";
//#region src/components/DataSpreadsheet/DataSpreadsheetHeader.tsx
/**
* Copyright IBM Corp. 2022, 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
var import_classnames = /* @__PURE__ */ __toESM(require_classnames());
const blockClass = `${pkg.prefix}--data-spreadsheet`;
const DataSpreadsheetHeader = forwardRef(({ activeCellCoordinates, cellSize, columns, currentMatcher, defaultColumn, headerGroups, scrollBarSize, selectionAreas, selectedHeaderReorderActive, setSelectedHeaderReorderActive, setActiveCellCoordinates, setCurrentMatcher, setSelectionAreas, readOnlyTable, disableColumnSwapping, setSelectionAreaData, rows, totalVisibleColumns, updateActiveCellCoordinates, setHeaderCellHoldActive, headerCellHoldActive, selectAllAriaLabel, visibleColumns }, ref) => {
const [scrollBarSizeValue, setScrollBarSizeValue] = useState(0);
const previousState = usePreviousValue({ cellSize }) || {};
useEffect(() => {
if (previousState?.cellSize !== cellSize) {
const scrollContainer = ref?.current?.querySelector(`.${blockClass}__list--container`);
const scrollBarValue = scrollContainer?.scrollHeight && scrollContainer?.clientHeight && scrollContainer?.scrollHeight > scrollContainer?.clientHeight ? 0 : scrollBarSize;
if (!scrollContainer?.getAttribute("tabIndex")) scrollContainer?.setAttribute("tabIndex", "0");
setScrollBarSizeValue(scrollBarValue);
}
}, [
cellSize,
ref,
scrollBarSize,
previousState?.cellSize
]);
const handleColumnHeaderClick = (index) => {
return (event) => {
handleHeaderCellSelection({
type: "column",
activeCellCoordinates,
rows,
columns,
currentMatcher,
setActiveCellCoordinates,
setCurrentMatcher,
setSelectionAreas,
spreadsheetRef: ref,
index,
isKeyboard: void 0,
setSelectionAreaData,
isHoldingCommandKey: checkForHoldingKey(event, "cmd"),
isHoldingShiftKey: checkForHoldingKey(event, "shiftKey")
});
};
};
const handleSelectAllClick = () => {
selectAllCells({
ref,
setCurrentMatcher,
setSelectionAreas,
rows,
columns,
activeCellCoordinates,
updateActiveCellCoordinates
});
};
const handleHeaderMouseDown = (index) => {
return (event) => {
if (event.shiftKey) return;
const selectionAreaToClone = selectionAreas?.filter((item) => item?.matcher === currentMatcher);
const selectionAreaElement = ref.current.querySelector(`[data-matcher-id="${selectionAreaToClone?.[0]?.matcher}"]`);
if (selectionAreaElement) selectionAreaElement.classList.add(`${blockClass}__selection-area--element`);
if (typeof setSelectedHeaderReorderActive === "function") setSelectedHeaderReorderActive(true);
const clickXPosition = event.clientX;
const headerButtonCoords = event.target.getBoundingClientRect();
const headerIndex = event.target.getAttribute("data-column-index");
const offsetXValue = clickXPosition - headerButtonCoords.left;
const lowestColumnIndexFromSelectionArea = Math.min(selectionAreaToClone?.[0]?.point1?.column || 0, selectionAreaToClone?.[0]?.point2?.column || 0);
const selectionAreaCoords = selectionAreaElement?.getBoundingClientRect() || new DOMRect();
const updatedOffsetDifference = lowestColumnIndexFromSelectionArea < parseInt(headerIndex) ? offsetXValue + (headerButtonCoords.left - selectionAreaCoords?.left) : offsetXValue;
const bodyContainer = (ref?.current ?? document).querySelector(`.${blockClass}__list--container`)?.firstElementChild;
const selectionAreaClonedElement = selectionAreaElement?.cloneNode();
const reorderIndicatorLine = selectionAreaElement?.cloneNode();
if (reorderIndicatorLine) {
reorderIndicatorLine.className = `${blockClass}__reorder-indicator-line`;
reorderIndicatorLine.style.width = px(2);
}
selectionAreaClonedElement.classList.add(`${blockClass}__selection-area--element-cloned`);
selectionAreaClonedElement.setAttribute("data-clone-offset-x", `${updatedOffsetDifference}`);
selectionAreaClonedElement.setAttribute("data-column-index-original", index);
bodyContainer?.appendChild(selectionAreaClonedElement);
bodyContainer?.appendChild(reorderIndicatorLine);
setHeaderCellHoldActive?.(true);
};
};
useSpreadsheetMouseMove({
ref,
headerCellHoldActive,
defaultColumn
});
return /* @__PURE__ */ React.createElement("div", {
className: (0, import_classnames.default)(`${blockClass}__header--container`),
role: "rowgroup"
}, headerGroups?.map((headerGroup, index) => {
const headerProps = prepareProps(headerGroup.getHeaderGroupProps(), "key");
return /* @__PURE__ */ React.createElement("div", {
key: `header_${index}`,
...headerProps,
style: {
...headerGroup.getHeaderGroupProps().style,
width: getSpreadsheetWidth({
type: "header",
headerGroup,
scrollBarSizeValue,
totalVisibleColumns,
defaultColumn,
totalColumnsWidth: void 0,
visibleColumns
}),
overflow: "hidden"
},
className: `${blockClass}__tr`
}, /* @__PURE__ */ React.createElement("div", {
role: "columnheader",
className: `${blockClass}__select-all-cell-container`,
style: {
width: defaultColumn?.rowHeaderWidth,
height: defaultColumn?.rowHeight
}
}, /* @__PURE__ */ React.createElement("button", {
id: `${blockClass}__cell--header--header`,
"data-row-index": "header",
"data-column-index": "header",
type: "button",
style: { width: defaultColumn?.rowHeaderWidth },
tabIndex: -1,
"aria-label": selectAllAriaLabel,
onClick: handleSelectAllClick,
className: (0, import_classnames.default)(`${blockClass}__th`, `${blockClass}--interactive-cell-element`, `${blockClass}__th--select-all`, { [`${blockClass}__th--active-header`]: activeCellCoordinates?.column === "header" && activeCellCoordinates?.row === "header" })
}, "\xA0")), headerGroup.headers.map((column, index) => {
const colProps = prepareProps(column.getHeaderProps(), "key");
const selectedHeader = checkSelectedHeaderCell(index, selectionAreas, "column", rows);
return /* @__PURE__ */ React.createElement("div", {
key: `column_${index}`,
role: "columnheader",
className: `${blockClass}__columnheader`,
...colProps
}, /* @__PURE__ */ React.createElement("button", {
id: `${blockClass}__cell--header--${index}`,
"data-row-index": "header",
"data-column-index": index,
tabIndex: -1,
onMouseDown: selectedHeader && !readOnlyTable && !disableColumnSwapping ? handleHeaderMouseDown(index) : void 0,
onMouseUp: selectedHeader && !readOnlyTable && !disableColumnSwapping && typeof setSelectedHeaderReorderActive === "function" ? () => setSelectedHeaderReorderActive(false) : void 0,
onClick: !selectedHeader ? handleColumnHeaderClick(index) : void 0,
style: {
height: defaultColumn?.rowHeight,
width: column?.width || defaultColumn?.width
},
className: (0, import_classnames.default)(`${blockClass}__th`, `${blockClass}--interactive-cell-element`, {
[`${blockClass}__th--active-header`]: activeCellCoordinates?.column === index || checkActiveHeaderCell(index, selectionAreas, "column"),
[`${blockClass}__th--active-header-disabledSwapping`]: disableColumnSwapping || readOnlyTable,
[`${blockClass}__th--selected-header`]: selectedHeader,
[`${blockClass}__th--selected-header-reorder-active`]: selectedHeaderReorderActive
}),
type: "button"
}, column.render("Header")));
}));
}));
});
DataSpreadsheetHeader.propTypes = {
/**
* Object containing the active cell coordinates
*/
/**@ts-ignore */
activeCellCoordinates: PropTypes.shape({
row: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
column: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}),
/**
* Specifies the cell height
*/
cellSize: PropTypes.oneOf([
"xs",
"sm",
"md",
"lg"
]),
/**
* All of the spreadsheet columns
*/
columns: PropTypes.array,
/**
* uuid that corresponds to the current selection area
*/
currentMatcher: PropTypes.string,
/**
* Default spreadsheet sizing values
*/
/**@ts-ignore */
defaultColumn: PropTypes.shape({
rowHeight: PropTypes.number,
rowHeaderWidth: PropTypes.number,
width: PropTypes.number
}),
/**
* Disable column swapping, default false
*/
disableColumnSwapping: PropTypes.bool,
/**
* Whether or not a click/hold is active on a header cell
*/
headerCellHoldActive: PropTypes.bool,
/**
* Headers provided from useTable hook
*/
headerGroups: PropTypes.arrayOf(PropTypes.object),
/**
* Read-only table
*/
readOnlyTable: PropTypes.bool,
/**
* All of the spreadsheet row data
*/
/**@ts-ignore */
rows: PropTypes.arrayOf(PropTypes.object),
/**
* The scrollbar width
*/
scrollBarSize: PropTypes.number,
/**
* The aria label applied to the Select all button
*/
selectAllAriaLabel: PropTypes.string.isRequired,
/**
* Header reordering is active
*/
selectedHeaderReorderActive: PropTypes.bool,
/**
* All of the cell selection area items
*/
/**@ts-ignore */
selectionAreas: PropTypes.arrayOf(PropTypes.object),
/**
* Setter fn for activeCellCoordinates value
*/
setActiveCellCoordinates: PropTypes.func,
/**
* Setter fn for currentMatcher value
*/
setCurrentMatcher: PropTypes.func,
/**
* Setter fn for header cell hold active value
*/
setHeaderCellHoldActive: PropTypes.func,
/**
* Set header reordering active or not
*/
setSelectedHeaderReorderActive: PropTypes.func,
/**
* Setter fn for selectionAreaData state value
*/
setSelectionAreaData: PropTypes.func,
/**
* Setter fn for selectionAreas value
*/
setSelectionAreas: PropTypes.func,
/**
* The total number of columns to be initially visible, additional columns will be rendered and
* visible via horizontal scrollbar
*/
totalVisibleColumns: PropTypes.number,
/**
* Function used to update the active cell coordinates
*/
updateActiveCellCoordinates: PropTypes.func,
/**
* Array of visible columns provided by react-table useTable hook
*/
/**@ts-ignore */
visibleColumns: PropTypes.array
};
//#endregion
export { DataSpreadsheetHeader };