UNPKG

@carbon/ibm-products

Version:
548 lines (546 loc) 19.7 kB
/** * 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. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_index = require("../../node_modules/classnames/index.js"); const require_settings = require("../../settings.js"); const require_usePreviousValue = require("../../global/js/hooks/usePreviousValue.js"); const require_props_helper = require("../../global/js/utils/props-helper.js"); const require_deepCloneObject = require("../../global/js/utils/deepCloneObject.js"); const require_removeCellSelections = require("./utils/removeCellSelections.js"); const require_useSpreadsheetMouseUp = require("./hooks/useSpreadsheetMouseUp.js"); const require_createCellSelectionArea = require("./utils/createCellSelectionArea.js"); const require_checkActiveHeaderCell = require("./utils/checkActiveHeaderCell.js"); const require_checkSelectedHeaderCell = require("./utils/checkSelectedHeaderCell.js"); const require_getSpreadsheetWidth = require("./utils/getSpreadsheetWidth.js"); const require_commonEventHandlers = require("./utils/commonEventHandlers.js"); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let _carbon_layout = require("@carbon/layout"); let react_window = require("react-window"); //#region src/components/DataSpreadsheet/DataSpreadsheetBody.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__ */ require_runtime.__toESM(require_index.default); const blockClass = `${require_settings.pkg.prefix}--data-spreadsheet`; const DataSpreadsheetBody = (0, react.forwardRef)(({ activeCellRef, columns, activeCellCoordinates, defaultColumn, defaultEmptyRowCount, getTableBodyProps, headerGroups, setCurrentColumns, id, onDataUpdate, renderRowHeader, renderRowHeaderDirection, hasCustomRowHeader, prepareRow, rows, selectionAreaData, setSelectionAreaData, setActiveCellCoordinates, selectedHeaderReorderActive, setSelectedHeaderReorderActive, selectionAreas, setContainerHasFocus, setSelectionAreas, scrollBarSize, totalColumnsWidth, clickAndHoldActive, setClickAndHoldActive, currentMatcher, setCurrentMatcher, onSelectionAreaChange, setActiveCellInsideSelectionArea, totalVisibleColumns, setHeaderCellHoldActive, setColumnOrder, visibleColumns }, ref) => { const [validStartingPoint, setValidStartingPoint] = (0, react.useState)(false); const contentScrollRef = (0, react.useRef)(void 0); const previousState = require_usePreviousValue.usePreviousValue({ selectionAreaData, clickAndHoldActive, rowHeight: defaultColumn?.rowHeight }) || {}; (0, react.useEffect)(() => { ref?.current.style.setProperty(`--${blockClass}--total-width`, (0, _carbon_layout.px)((totalColumnsWidth || 0) + (scrollBarSize || 0))); }, [ ref, scrollBarSize, totalColumnsWidth ]); (0, react.useEffect)(() => { if (selectionAreaData?.length) { let selectionChanged = false; if (previousState?.selectionAreaData?.length !== selectionAreaData?.length || selectionAreaData?.[0]?.cells.length !== previousState?.selectionAreaData?.[0]?.cells.length) selectionChanged = true; if (!clickAndHoldActive && previousState?.clickAndHoldActive || selectionChanged) onSelectionAreaChange?.(selectionAreaData); } }, [ previousState?.selectionAreaData, selectionAreaData, onSelectionAreaChange, clickAndHoldActive, previousState?.clickAndHoldActive ]); (0, react.useEffect)(() => { if (selectionAreas && selectionAreas.length) selectionAreas.map((area) => { if (area.areaCreated) { const rowStart = Math.min(area.point1.row, area.point2.row); const rowEnd = Math.max(area.point1.row, area.point2.row); const columnStart = Math.min(area.point1.column, area.point2.column); const columnEnd = Math.max(area.point1.column, area.point2.column); const selectionData = { rows: { start: rowStart, end: rowEnd }, columns: { start: columnStart, end: columnEnd }, cells: populateSelectionAreaCellData({ rowStart, rowEnd, columnStart, columnEnd }), selectionId: area.matcher }; setSelectionAreaData?.((prev) => { return [...require_deepCloneObject.deepCloneObject(prev).filter((item) => item.selectionId !== area.matcher), selectionData]; }); } if (!area.areaCreated && area.point1 && area.point2 && area.matcher) require_createCellSelectionArea.createCellSelectionArea({ ref, area, blockClass, defaultColumn, selectionAreas, setSelectionAreas, setActiveCellInsideSelectionArea, visibleColumns }); }); }, [ selectionAreas, setSelectionAreas, defaultColumn, onSelectionAreaChange, setSelectionAreaData, ref, activeCellCoordinates, setActiveCellInsideSelectionArea, visibleColumns, hasCustomRowHeader ]); const populateSelectionAreaCellData = ({ rowStart, rowEnd, columnStart, columnEnd }) => { const cellContainer = []; for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex++) cellContainer.push([ rowIndex, columnIndex, `${blockClass}__cell--${rowIndex}--${columnIndex}` ]); return cellContainer; }; require_useSpreadsheetMouseUp.useSpreadsheetMouseUp({ currentMatcher, setClickAndHoldActive, setSelectionAreas, setValidStartingPoint, selectedHeaderReorderActive, setSelectedHeaderReorderActive, validStartingPoint, ref, setHeaderCellHoldActive, setColumnOrder, visibleColumns, setActiveCellCoordinates, rows, activeCellCoordinates, defaultColumn, selectionAreas }); (0, react.useEffect)(() => { let listContainer; let activeCellButton; if (spreadsheetBodyRef?.current) { listContainer = spreadsheetBodyRef?.current; activeCellButton = listContainer?.querySelector(`.${blockClass}__active-cell--highlight`); } if (activeCellButton && defaultColumn?.rowHeight !== previousState.rowHeight) { activeCellButton.style.height = `${defaultColumn?.rowHeight}px`; if (activeCellCoordinates) { const activeTargetElement = ref?.current.querySelector(`[data-row-index="${activeCellCoordinates.row}"][data-column-index="${activeCellCoordinates.column}"]`); const listContainer = ref?.current.querySelector(`.${blockClass}__list--container`); let newActiveCellTopPosition; if (activeTargetElement && listContainer) newActiveCellTopPosition = activeTargetElement?.getBoundingClientRect().top - listContainer.getBoundingClientRect().top; activeCellButton.style.top = (0, _carbon_layout.px)(newActiveCellTopPosition); require_removeCellSelections.removeCellSelections({ matcher: void 0, spreadsheetRef: ref }); selectionAreas?.map((area) => { if (!area.areaCreated && area.point1 && area.point2 && area.matcher) return require_createCellSelectionArea.createCellSelectionArea({ ref, area, blockClass, defaultColumn, selectionAreas, setSelectionAreas, setActiveCellInsideSelectionArea, visibleColumns }); }); } } }, [ defaultColumn, ref, activeCellCoordinates, previousState?.rowHeight, selectionAreas, setActiveCellInsideSelectionArea, setSelectionAreas, visibleColumns, hasCustomRowHeader ]); const removeDuplicateSelections = (0, react.useCallback)(() => { const uniqueAttrArray = [], removedSelectionAreaMatcherArr = []; ref?.current.querySelectorAll(`.${blockClass}__selection-area--element`).forEach((selectorEl) => { const { top, left, height, width } = selectorEl.style; const uniqueAttrStr = `${top}${left}${height}${width}`; if (uniqueAttrArray.indexOf(uniqueAttrStr) == -1) uniqueAttrArray.push(uniqueAttrStr); else { selectorEl.remove(); removedSelectionAreaMatcherArr.push(selectorEl.getAttribute("data-matcher-id")); } }); if (removedSelectionAreaMatcherArr.length) { setSelectionAreas?.((prev) => { return require_deepCloneObject.deepCloneObject(prev).filter((item) => !removedSelectionAreaMatcherArr.includes(item.matcher)); }); setSelectionAreaData?.((prev) => { return require_deepCloneObject.deepCloneObject(prev).filter((item) => !removedSelectionAreaMatcherArr.includes(item.selectionId)); }); } }, [ ref, setSelectionAreas, setSelectionAreaData ]); (0, react.useEffect)(() => { removeDuplicateSelections(); }, [selectionAreas, removeDuplicateSelections]); const handleBodyCellClickEvent = (0, react.useCallback)((cell, columnIndex) => { return (event) => { require_commonEventHandlers.handleBodyCellClick(cell, columnIndex, event, currentMatcher, activeCellCoordinates, selectionAreas, setActiveCellCoordinates, setSelectionAreas, setContainerHasFocus, setClickAndHoldActive, setCurrentMatcher, ref, setSelectionAreaData, setActiveCellInsideSelectionArea, activeCellRef, setValidStartingPoint); }; }, [ currentMatcher, activeCellCoordinates, selectionAreas, setActiveCellCoordinates, setSelectionAreas, setContainerHasFocus, setClickAndHoldActive, setCurrentMatcher, ref, setSelectionAreaData, setActiveCellInsideSelectionArea, activeCellRef ]); const handleBodyScroll = () => { const headerRowElement = ref.current.querySelector(` .${blockClass}__header--container .${blockClass}__tr`) || new HTMLDivElement(); headerRowElement.scrollLeft = contentScrollRef?.current.scrollLeft; }; (0, react.useEffect)(() => { contentScrollRef.current.addEventListener("scroll", () => handleBodyScroll()); const contentScrollElementRef = contentScrollRef.current || new HTMLElement(); return () => { contentScrollElementRef.removeEventListener("scroll", handleBodyScroll); }; }); const handleBodyCellHoverEvent = (0, react.useCallback)((cell, columnIndex) => { return () => { require_commonEventHandlers.handleBodyCellHover(cell, columnIndex, clickAndHoldActive, currentMatcher, setSelectionAreas); }; }, [ clickAndHoldActive, currentMatcher, setSelectionAreas ]); const handleRowHeaderClickEvent = (0, react.useCallback)((index) => { return (event) => { require_commonEventHandlers.handleRowHeaderClick(index, event, columns, ref, setSelectionAreas, setCurrentMatcher, setActiveCellCoordinates, activeCellCoordinates, rows, setSelectionAreaData); }; }, [ columns, ref, setSelectionAreas, setCurrentMatcher, setActiveCellCoordinates, activeCellCoordinates, rows, setSelectionAreaData ]); (0, react.useEffect)(() => { if (!rows?.length) { const buildEmptyRows = () => { const emptyRowData = []; [...Array(defaultEmptyRowCount)].map(() => { const emptyCell = {}; headerGroups?.[0]?.headers.map((header) => { emptyCell[header.id] = null; }); emptyRowData.push(emptyCell); }); onDataUpdate?.(emptyRowData); }; buildEmptyRows(); } if (headerGroups?.[0] && typeof setCurrentColumns === "function") { const headers = headerGroups[0].headers; setCurrentColumns(headers); } }, [ rows, headerGroups, defaultEmptyRowCount, onDataUpdate, setCurrentColumns ]); const RenderEmptyRows = () => { return /* @__PURE__ */ react.default.createElement("div", null); }; const RenderRow = (0, react.useCallback)(({ index, style }) => { const row = rows?.[index]; if (rows && rows.length) { prepareRow?.(row); const rowProps = require_props_helper.prepareProps(row.getRowProps({ style }), "key"); return /* @__PURE__ */ react.default.createElement("div", { key: { ...row.getRowProps().key }, ...rowProps, className: (0, import_classnames.default)(`${blockClass}__tr`), "data-row-index": index, "aria-rowindex": index + 1, "aria-owns": `${blockClass}__cell-editor-text-area` }, /* @__PURE__ */ react.default.createElement("div", { role: "rowheader", className: `${blockClass}__td-th--cell-container` }, /* @__PURE__ */ react.default.createElement("button", { id: `${blockClass}__cell--${index}--header`, tabIndex: -1, "data-row-index": index, "data-column-index": "header", type: "button", onClick: handleRowHeaderClickEvent(index), className: (0, import_classnames.default)(`${blockClass}__td`, `${blockClass}__td-th`, `${blockClass}--interactive-cell-element`, { [`${blockClass}__td_custom`]: hasCustomRowHeader ? true : false, [`${blockClass}__td-th--active-header`]: !hasCustomRowHeader && (activeCellCoordinates?.row === index || require_checkActiveHeaderCell.checkActiveHeaderCell(index, selectionAreas, "row")), [`${blockClass}__td-th--selected-header`]: !hasCustomRowHeader && require_checkSelectedHeaderCell.checkSelectedHeaderCell(index, selectionAreas, "row", columns) }), style: { width: defaultColumn?.rowHeaderWidth, flexDirection: hasCustomRowHeader ? renderRowHeaderDirection === "Left" ? "row-reverse" : row : void 0 } }, index + 1, hasCustomRowHeader && typeof renderRowHeader === "function" && renderRowHeader(index))), row.cells.map((cell, index) => { const cellProps = require_props_helper.prepareProps(cell.getCellProps(), "key"); return /* @__PURE__ */ react.default.createElement("div", { key: `cell_${index}`, "aria-colindex": index + 1, ...cellProps, role: "gridcell", style: { ...cell.getCellProps().style, display: "grid", minWidth: cell?.column?.width || defaultColumn?.width } }, /* @__PURE__ */ react.default.createElement("button", { id: `${blockClass}__cell--${cell.row.index}--${index}`, tabIndex: -1, "data-row-index": cell.row.index, "data-column-index": index, className: (0, import_classnames.default)(`${blockClass}__td`, `${blockClass}__body--td`, `${blockClass}--interactive-cell-element`), onMouseDown: handleBodyCellClickEvent(cell, index), onMouseOver: handleBodyCellHoverEvent(cell, index), onFocus: () => {}, type: "button" }, cell.render("Cell"))); })); } }, [ prepareRow, renderRowHeader, rows, hasCustomRowHeader, activeCellCoordinates?.row, selectionAreas, handleRowHeaderClickEvent, handleBodyCellClickEvent, handleBodyCellHoverEvent, columns, defaultColumn, renderRowHeaderDirection ]); const spreadsheetBodyRef = (0, react.useRef)(void 0); return /* @__PURE__ */ react.default.createElement("div", { ref: spreadsheetBodyRef, className: (0, import_classnames.default)(`${blockClass}__body--container`), ...getTableBodyProps?.() }, /* @__PURE__ */ react.default.createElement(react_window.FixedSizeList, { className: (0, import_classnames.default)(`${blockClass}__list--container`, `${blockClass}__list--container--${id}`), height: 400, itemCount: rows?.length || defaultEmptyRowCount, itemSize: defaultColumn?.rowHeight, width: require_getSpreadsheetWidth.getSpreadsheetWidth({ headerGroup: void 0, type: void 0, scrollBarSizeValue: scrollBarSize, totalVisibleColumns, defaultColumn, totalColumnsWidth, visibleColumns }), outerRef: contentScrollRef }, rows?.length ? RenderRow : RenderEmptyRows)); }); DataSpreadsheetBody.propTypes = { /** * Object containing the active cell coordinates */ /**@ts-ignore */ activeCellCoordinates: prop_types.default.shape({ row: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]), column: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]) }), /** *This is the ref of the button input, which is the active cell element */ /**@ts-ignore */ activeCellRef: prop_types.default.object, /** * Is the user clicking and holding in the data spreadsheet body */ clickAndHoldActive: prop_types.default.bool, /** * All of the spreadsheet columns */ columns: prop_types.default.array, /** * This represents the id of the current cell selection area */ currentMatcher: prop_types.default.string, /** * Default spreadsheet sizing values */ /**@ts-ignore */ defaultColumn: prop_types.default.shape({ rowHeight: prop_types.default.number, rowHeaderWidth: prop_types.default.number, width: prop_types.default.number }), /** * Sets the number of empty rows to be created when there is no data provided */ defaultEmptyRowCount: prop_types.default.number, /** * Function to set table body prop values */ /**@ts-ignore */ getTableBodyProps: prop_types.default.func, /** * Check if spreadsheet is using custom row header component attached */ hasCustomRowHeader: prop_types.default.bool, /** * Headers provided from useTable hook */ headerGroups: prop_types.default.arrayOf(prop_types.default.object), /** * The spreadsheet id */ id: prop_types.default.oneOfType([prop_types.default.number, prop_types.default.string]), /** * The event handler that is called when the active cell changes */ onActiveCellChange: prop_types.default.func, /** * The event handler that is called to set the rows for the empty spreadsheet */ onDataUpdate: prop_types.default.func, /** * The event handler that is called when the selection areas change */ onSelectionAreaChange: prop_types.default.func, /** * Prepare row function from react-table */ prepareRow: prop_types.default.func, /** * Component next to numbering rows */ renderRowHeader: prop_types.default.func, /** * Component next to numbering rows */ renderRowHeaderDirection: prop_types.default.string, /** * All of the spreadsheet row data */ rows: prop_types.default.arrayOf(prop_types.default.object), /** * The scrollbar width */ scrollBarSize: prop_types.default.number, /** * Header reordering is active */ selectedHeaderReorderActive: prop_types.default.bool, /** * Array of selection area data */ selectionAreaData: prop_types.default.array, /** * Array of selection areas */ selectionAreas: prop_types.default.array, /** * Setter fn for activeCellCoordinates state value */ setActiveCellCoordinates: prop_types.default.func, /** * Setter fn for active cell inside of selection area */ setActiveCellInsideSelectionArea: prop_types.default.func, /** * Setter fn for clickAndHold state value */ setClickAndHoldActive: prop_types.default.func, /** * Setter fn for column ordering, provided from react-table */ setColumnOrder: prop_types.default.func, /** * Setter fn for containerHasFocus state value */ setContainerHasFocus: prop_types.default.func, /** * Set current columns after drag drop */ setCurrentColumns: prop_types.default.func, /** * Setter fn for currentMatcher state value */ setCurrentMatcher: prop_types.default.func, /** * Setter fn for header cell hold active value */ setHeaderCellHoldActive: prop_types.default.func, /** * Set header reordering active or not */ setSelectedHeaderReorderActive: prop_types.default.func, /** * Setter fn for selectionAreaData state value */ setSelectionAreaData: prop_types.default.func, /** * Setter fn for selectionAreas state value */ setSelectionAreas: prop_types.default.func, /** * The total columns width */ totalColumnsWidth: prop_types.default.number, /** * The total number of columns to be initially visible, additional columns will be rendered and * visible via horizontal scrollbar */ totalVisibleColumns: prop_types.default.number, /** * Prop from react-table used to reorder columns */ /**@ts-ignore */ visibleColumns: prop_types.default.array }; //#endregion exports.DataSpreadsheetBody = DataSpreadsheetBody;