UNPKG

terra-data-grid

Version:

Package containing data grid container components that enable users to navigate the grid information using directional navigation keys.

410 lines (401 loc) 18 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _bind = _interopRequireDefault(require("classnames/bind")); var KeyCode = _interopRequireWildcard(require("keycode-js")); var _terraTable = require("terra-table"); var _WorklistDataGridModule = _interopRequireDefault(require("./WorklistDataGrid.module.scss")); var _DataGrid = _interopRequireDefault(require("./DataGrid")); var _WorklistDataGridUtils = _interopRequireDefault(require("./utils/WorklistDataGridUtils")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* eslint-disable jsx-a11y/no-static-element-interactions */ var cx = _bind.default.bind(_WorklistDataGridModule.default); var propTypes = { /** * String that identifies the element (or elements) that labels the grid. */ ariaLabelledBy: _propTypes.default.string, /** * String that labels the grid for accessibility. If ariaLabelledBy is specified, ariaLabel will not be used. */ ariaLabel: _propTypes.default.string, /** * String that will be used to identify the Grid. If multiple grids are on the same page, each grid should have * a unique id. */ id: _propTypes.default.string.isRequired, /** * Data for content in the body of the Grid. Rows will be rendered in the order given. */ rows: _propTypes.default.arrayOf(_terraTable.rowShape), /** * Data for pinned columns. Pinned columns are the stickied leftmost columns of the grid. * Columns are rendered in the order in which they are provided. */ pinnedColumns: _propTypes.default.arrayOf(_terraTable.columnShape), /** * Data for overflow columns. Overflow columns are rendered in the Worklist Data Grid's horizontal overflow. * Columns are rendered in the order in which they are provided. */ overflowColumns: _propTypes.default.arrayOf(_terraTable.columnShape), /** * Number indicating the default column width in px. This value will be used if no overriding width value is provided on a per-column basis. */ defaultColumnWidth: _propTypes.default.number, /** * String that specifies the column height. Any valid CSS height value is accepted. */ columnHeaderHeight: _propTypes.default.string, /** * String that specifies the height for the rows in the grid. Any valid CSS value is accepted. */ rowHeight: _propTypes.default.string, /** * Number indicating the index of the column that represents row header. Index is 0 based and cannot exceed one less than the number of columns in the grid. */ rowHeaderIndex: _terraTable.validateRowHeaderIndex, /** * Function that is called when a resizable column is resized. Parameters: * @param {string} columnId columnId * @param {string} requestedWidth requestedWidth */ onColumnResize: _propTypes.default.func, /** * Callback function that is called when a selectable cell is selected. Parameters: * @param {string} rowId rowId * @param {string} columnId columnId * @param {object} event event */ onCellSelect: _propTypes.default.func, /** * Callback function that is called when one or more rows are selected or unselected. Parameters: * @param {arrayOf({string, boolean})} rowsToSelectAndUnSelect rowsToSelectAndUnSelect */ onRowSelect: _propTypes.default.func, /** * Callback function that is called when all rows are selected. Parameters: none. */ onRowSelectAll: _propTypes.default.func, /** * Callback function that is called when a selectable column is selected. Parameters: * @param {string} columnId columnId */ onColumnSelect: _propTypes.default.func, /** * Callback function that is called when all selected rows need to be unselected. Parameters: none. */ onClearSelectedRows: _propTypes.default.func, /** * Callback function that is called when all selected cells need to be unselected. Parameters: none. */ onClearSelectedCells: _propTypes.default.func, /** * Callback function that is called when no row is selected and the row selection mode needs to be disabled. Parameters: none. */ onDisableSelectableRows: _propTypes.default.func, /** * Callback function to enable row selection mode. Parameters: `none` */ onEnableRowSelection: _propTypes.default.func, /** * Boolean indicating whether or not the Worklist Data Grid should allow entire rows to be selectable. An additional column will be * rendered to allow for row selection to occur. */ hasSelectableRows: _propTypes.default.bool, /** * Determines if focus is moved to the interactive element of a cell when a single button or hyperlink element is the only interactive element. */ isAutoFocusEnabled: _propTypes.default.bool }; var defaultProps = { rowHeaderIndex: 0, defaultColumnWidth: 200, columnHeaderHeight: '2.5rem', rowHeight: '2.5rem', pinnedColumns: [], overflowColumns: [], rows: [] }; function WorklistDataGrid(props) { var _document$getElementB; var id = props.id, ariaLabelledBy = props.ariaLabelledBy, ariaLabel = props.ariaLabel, rows = props.rows, pinnedColumns = props.pinnedColumns, overflowColumns = props.overflowColumns, onColumnResize = props.onColumnResize, defaultColumnWidth = props.defaultColumnWidth, columnHeaderHeight = props.columnHeaderHeight, rowHeight = props.rowHeight, onColumnSelect = props.onColumnSelect, onCellSelect = props.onCellSelect, onRowSelect = props.onRowSelect, onRowSelectAll = props.onRowSelectAll, onClearSelectedRows = props.onClearSelectedRows, onClearSelectedCells = props.onClearSelectedCells, onDisableSelectableRows = props.onDisableSelectableRows, onEnableRowSelection = props.onEnableRowSelection, hasSelectableRows = props.hasSelectableRows, rowHeaderIndex = props.rowHeaderIndex, isAutoFocusEnabled = props.isAutoFocusEnabled; var inShiftUpDownMode = (0, _react.useRef)(false); var multiSelectRange = (0, _react.useRef)({ start: null, end: null }); var dataGridFuncRef = (0, _react.useRef)(); var gridReceivedFocus = (0, _react.useRef)(false); var gridHasFocus = (_document$getElementB = document.getElementById("".concat(id, "-worklist-data-grid-container"))) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.contains(document.activeElement); var makeWorklistDataGridColumns = function makeWorklistDataGridColumns(columns) { return columns.map(function (column) { return _objectSpread(_objectSpread({}, column), {}, { isResizable: column.isResizable !== false, isSelectable: column.isSelectable !== false }); }); }; var worklistDataGridPinnedColumns = (0, _react.useMemo)(function () { return makeWorklistDataGridColumns(pinnedColumns); }, [pinnedColumns]); var worklistDataGridOverflowColumns = (0, _react.useMemo)(function () { return makeWorklistDataGridColumns(overflowColumns); }, [overflowColumns]); // ------------------------------------- // useEffect Hooks // useEffect for row selection (0, _react.useEffect)(function () { if (!hasSelectableRows) { multiSelectRange.current = {}; } if (gridReceivedFocus.current) { var newFocusCell = dataGridFuncRef.current.getFocusedCell(); newFocusCell = { row: newFocusCell.row, col: Math.max(newFocusCell.col + (hasSelectableRows ? 1 : -1), 0) }; dataGridFuncRef.current.setFocusedRowCol(newFocusCell.row, newFocusCell.col, gridHasFocus); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasSelectableRows]); var handleClearSelection = (0, _react.useCallback)(function () { if (!hasSelectableRows) { if (onClearSelectedCells) { onClearSelectedCells(); } } else { multiSelectRange.current = {}; // Clear the information used for selecting multiple rows. if (rows.some(function (row) { return row.isSelected; })) { // Esc (while in row selection mode and rows are selected): Clear selection if (onClearSelectedRows) { onClearSelectedRows(); } } else if (onDisableSelectableRows) { onDisableSelectableRows(); } } }, [hasSelectableRows, onClearSelectedCells, onClearSelectedRows, onDisableSelectableRows, rows]); var selectAllRows = function selectAllRows() { if (onRowSelectAll) { onRowSelectAll(); } }; var getRowsInRangeToUnselect = (0, _react.useCallback)(function (newEndOfRange) { var rowIdsToUnselect = []; if (!multiSelectRange.current.end) { // short-circuit since there was no previous range, there is nothing to unselect. return rowIdsToUnselect; } if (multiSelectRange.current.start > multiSelectRange.current.end) { // The range extends upward from the anchor row if (newEndOfRange > multiSelectRange.current.end) { // The range was moved down towards the anchor so rows that no longer qualify for the range need to be unselected. rowIdsToUnselect = rows.slice(multiSelectRange.current.end - 1, Math.min(newEndOfRange, multiSelectRange.current.start) - 1).map(function (row) { return { id: row.id, selected: false }; }); } } else if (multiSelectRange.current.start < multiSelectRange.current.end) { // The range extends downward from the anchor row if (newEndOfRange < multiSelectRange.current.end) { // The range was moved up towards the anchor so rows that no longer qualify for the range need to be unselected. New endRangeIndex becomes ordered End rowIdsToUnselect = rows.slice(Math.max(multiSelectRange.current.start, newEndOfRange), multiSelectRange.current.end).map(function (row) { return { id: row.id, selected: false }; }); } } return rowIdsToUnselect; }, [rows]); var selectMultipleRows = (0, _react.useCallback)(function (newEndOfRange) { if (!hasSelectableRows) { onEnableRowSelection(); } if (!onRowSelect) { return; } // Rows in range to remain selected var selectionStartRowIndex = Math.min(multiSelectRange.current.start, newEndOfRange); var selectionEndRowIndex = Math.max(multiSelectRange.current.start, newEndOfRange); // We are subtracting 1 to accommodate for the column header in the grid. var rowsToSelectAndUnSelect = rows.slice(selectionStartRowIndex - 1, selectionEndRowIndex).map(function (row) { return { id: row.id, selected: true }; }); // Determine if there are rows that are no longer in range that need to be unselected. rowsToSelectAndUnSelect = rowsToSelectAndUnSelect.concat(getRowsInRangeToUnselect(newEndOfRange)); onRowSelect(rowsToSelectAndUnSelect); multiSelectRange.current.end = newEndOfRange; }, [hasSelectableRows, onEnableRowSelection, onRowSelect, rows, getRowsInRangeToUnselect]); var selectRow = (0, _react.useCallback)(function (rowId, rowIndex) { var rowsToSelectAndUnSelect = []; if (!rows[rowIndex - 1].isSelected) { multiSelectRange.current = { start: rowIndex, end: null }; // Establish new starting point for future range. rowsToSelectAndUnSelect.push({ id: rowId, selected: true }); } else { if (rowIndex === multiSelectRange.current.start) { // The row that denotes the start for multiselect has been cleared. multiSelectRange.current = {}; } rowsToSelectAndUnSelect.push({ id: rowId, selected: false }); } if (onRowSelect) { onRowSelect(rowsToSelectAndUnSelect); } }, [onRowSelect, rows]); var onRangeSelection = (0, _react.useCallback)(function (rowIndex, columnIndex, direction) { var nextRow = rowIndex; if (direction === KeyCode.KEY_UP) { // Since range is being extended upward, row above the starting row should now // be added to the range of selected rows. nextRow -= 1; if (nextRow === 0) { // If the row above is the header, do not extend the range. nextRow = 1; } } else if (direction === KeyCode.KEY_DOWN) { // Since range is being extended downward, row below the starting row should now // be added to the range of selected rows. nextRow += 1; } if (!inShiftUpDownMode.current) { // Start of range selection using Shift+Up/Down so save this as the anchor/start for the range. inShiftUpDownMode.current = true; multiSelectRange.current = { start: rowIndex, end: null }; } selectMultipleRows(nextRow); }, [selectMultipleRows]); var handleRowSelection = (0, _react.useCallback)(function (selectionDetails) { if (!hasSelectableRows) { if (selectionDetails.isShiftPressed) { // Shift+Space, Shift+Click, Shift+Up, Shift+Down multiSelectRange.current = { start: selectionDetails.rowIndex, end: null }; selectMultipleRows(selectionDetails.rowIndex); } } else if (selectionDetails.isShiftPressed && multiSelectRange.current.start) { // Select multiple rows based on previously established anchor. selectMultipleRows(selectionDetails.rowIndex); } else { // There is no anchor or the shift key is not pressed so select the row and establish an anchor. selectRow(selectionDetails.rowId, selectionDetails.rowIndex); } }, [hasSelectableRows, selectMultipleRows, selectRow]); var handleCellSelection = (0, _react.useCallback)(function (selectionDetails, event) { if (hasSelectableRows || selectionDetails.isShiftPressed) { handleRowSelection(selectionDetails); } else if (selectionDetails.isCellSelectable && onCellSelect) { onCellSelect(selectionDetails.rowId, selectionDetails.columnId, event); } }, [handleRowSelection, hasSelectableRows, onCellSelect]); var handleRowSelectionHeaderSelect = (0, _react.useCallback)(function () { onColumnSelect(_WorklistDataGridUtils.default.ROW_SELECTION_COLUMN.id); }, [onColumnSelect]); var handleKeyUp = function handleKeyUp(event) { var key = event.keyCode; switch (key) { case KeyCode.KEY_SHIFT: inShiftUpDownMode.current = false; break; default: } }; // ------------------------------------- // event handlers var handleKeyDown = function handleKeyDown(event) { switch (event.keyCode) { case KeyCode.KEY_A: if (hasSelectableRows && (event.ctrlKey || event.metaKey)) { selectAllRows(); event.preventDefault(); // prevent the default selection of everything on the page. } break; default: } }; var onFocus = function onFocus() { gridReceivedFocus.current = true; }; // ------------------------------------- return /*#__PURE__*/_react.default.createElement("div", { id: "".concat(id, "-worklist-data-grid-container"), onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, className: cx('worklist-data-grid-container'), onFocus: !gridReceivedFocus.current ? onFocus : undefined }, /*#__PURE__*/_react.default.createElement(_DataGrid.default, { id: id, ariaLabel: ariaLabel, ariaLabelledBy: ariaLabelledBy, rows: rows, rowHeight: rowHeight, rowHeaderIndex: rowHeaderIndex, pinnedColumns: worklistDataGridPinnedColumns, overflowColumns: worklistDataGridOverflowColumns, defaultColumnWidth: defaultColumnWidth, columnHeaderHeight: columnHeaderHeight, onColumnSelect: onColumnSelect, onRowSelectionHeaderSelect: onColumnSelect ? handleRowSelectionHeaderSelect : undefined, onColumnResize: onColumnResize, onCellSelect: handleCellSelection, onClearSelection: handleClearSelection, onRangeSelection: onRangeSelection, hasSelectableRows: hasSelectableRows, isAutoFocusEnabled: isAutoFocusEnabled, ref: dataGridFuncRef })); } WorklistDataGrid.propTypes = propTypes; WorklistDataGrid.defaultProps = defaultProps; var _default = exports.default = WorklistDataGrid;