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
JavaScript
"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;