terra-clinical-data-grid
Version:
An organizational component that renders a collection of data in a grid-like format.
1,063 lines (1,020 loc) • 63.2 kB
JavaScript
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ColumnSortIndicators", {
enumerable: true,
get: function get() {
return _columnDataShape.SortIndicators;
}
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _classnames = _interopRequireDefault(require("classnames"));
var _bind = _interopRequireDefault(require("classnames/bind"));
var _terraThemeContext = _interopRequireDefault(require("terra-theme-context"));
var _memoizeOne = _interopRequireDefault(require("memoize-one"));
var _resizeObserverPolyfill = _interopRequireDefault(require("resize-observer-polyfill"));
var _terraContentContainer = _interopRequireDefault(require("terra-content-container"));
var _terraVisuallyHiddenText = _interopRequireDefault(require("terra-visually-hidden-text"));
var _reactIntl = require("react-intl");
var _keycodeJs = require("keycode-js");
var _Cell = _interopRequireDefault(require("./subcomponents/Cell"));
var _HeaderCell = _interopRequireDefault(require("./subcomponents/HeaderCell"));
var _RowSelectionCell = _interopRequireDefault(require("./subcomponents/RowSelectionCell"));
var _Row = _interopRequireDefault(require("./subcomponents/Row"));
var _Scrollbar = _interopRequireDefault(require("./subcomponents/Scrollbar"));
var _SectionHeader = _interopRequireDefault(require("./subcomponents/SectionHeader"));
var _dataGridUtils = _interopRequireDefault(require("./utils/dataGridUtils"));
var _columnDataShape = require("./proptypes/columnDataShape");
var _sectionDataShape = _interopRequireDefault(require("./proptypes/sectionDataShape"));
var _DataGridModule = _interopRequireDefault(require("./DataGrid.module.scss"));
var _RowModule = _interopRequireDefault(require("./subcomponents/Row.module.scss"));
var _excluded = ["id", "pinnedColumns", "overflowColumns", "sections", "onCellSelect", "onColumnSelect", "onRequestColumnResize", "onRequestSectionCollapse", "rowHeight", "headerHeight", "hasSelectableRows", "onRowSelect", "hasResizableColumns", "defaultColumnWidth", "fill", "onRequestContent", "intl", "verticalOverflowContainerRefCallback", "horizontalOverflowContainerRefCallback", "columnHighlightId", "labelRef", "descriptionRef"];
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var cx = _bind.default.bind(_DataGridModule.default);
var cxRow = _bind.default.bind(_RowModule.default);
var propTypes = {
/**
* String that will be used to identify the DataGrid. This value will be used as the id attribute of the overall DataGrid container,
* and it will be used to prefix other id attributes used for internal componentry.
*/
id: _propTypes.default.string.isRequired,
/**
* A Unique Identifier of the [column](/components/terra-clinical-data-grid/clinical-data-grid/clinical-data-grid#columns).
* If provided, column with specified identifier will be highlighted in data-grid.
*
*  The column highlight feature should be limited specifically to
* time and timeline concepts only, best used with special instruction and guidance from User Experience to ensure proper standards.
*/
columnHighlightId: _propTypes.default.string,
/**
* Data for columns that will be pinned. Columns will be presented in the order given.
*/
pinnedColumns: _propTypes.default.arrayOf(_columnDataShape.columnDataShape),
/**
* Data for columns that will be rendered in the DataGrid's horizontal overflow. Columns will be presented in the order given.
*/
overflowColumns: _propTypes.default.arrayOf(_columnDataShape.columnDataShape),
/**
* Data for content in the body of the DataGrid. Sections will be rendered in the order given.
*/
sections: _propTypes.default.arrayOf(_sectionDataShape.default),
/**
* Function that is called when a selectable cell is selected. Parameters: `onCellSelect(sectionId, rowId, columnId)`
*/
onCellSelect: _propTypes.default.func,
/**
* Function that is called when a selectable header cell is selected. Parameters: `onColumnSelect(columnId)`
*/
onColumnSelect: _propTypes.default.func,
/**
* Function that is called when a resizable column is resized. Parameters: `onRequestColumnResize(columnId, requestedWidth)`
*/
onRequestColumnResize: _propTypes.default.func,
/**
* Function that is called when a collapsible section is selected. Parameters: `onRequestSectionCollapse(sectionId)`
*/
onRequestSectionCollapse: _propTypes.default.func,
/**
* String that specifies the row height. Values are suggested to be in `rem`s (ex `'5rem'`), but any valid CSS height value is accepted.
* This value can be overridden for a row by specifying a height on the given row.
*/
rowHeight: _propTypes.default.string,
/**
* String that specifies the DataGrid header height. Values are suggested to be in `rem`s (ex `'5rem'`), but any valid CSS height value is accepted.
*/
headerHeight: _propTypes.default.string,
/**
* Boolean indicating whether or not the DataGrid should allow entire rows to be selectable. An additional column will be
* rendered to allow for row selection to occur.
*/
hasSelectableRows: _propTypes.default.bool,
/**
* Function that will be called when a row is selected. Parameters: `onRowSelect(sectionId, rowId)`
*/
onRowSelect: _propTypes.default.func,
/**
* Boolean indicating whether or not resizable columns are enabled for the DataGrid. If this prop is not enabled, the isResizable value of columns
* will be ignored.
*/
hasResizableColumns: _propTypes.default.bool,
/**
* 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,
/**
* Function that will be called when the DataGrid's vertical overflow reaches its terminal position. This can be used to contextually
* load additional content in the DataGrid. If there is no additional content to present, this function should not be provided.
* The `fill` prop must also be provided as true; otherwise, the DataGrid will not overflow internally and will not know to request more content.
* Parameters: `onRequestContent()`
*/
onRequestContent: _propTypes.default.func,
/**
* Boolean that indicates whether or not the DataGrid should fill its parent container.
*/
fill: _propTypes.default.bool,
/**
* @private
* The intl object containing translations. This is retrieved from the context automatically by injectIntl.
*/
intl: _propTypes.default.shape({
formatMessage: _propTypes.default.func
}).isRequired,
/**
* Callback ref to pass into vertical overflow container.
*/
verticalOverflowContainerRefCallback: _propTypes.default.func,
/**
* Callback ref to pass into horizontal overflow container.
*/
horizontalOverflowContainerRefCallback: _propTypes.default.func,
/**
* A ref to the element containing the visual name/label of the grid to provide context for screen readers. This can be a ref to a textual DOM element or a string, but a ref is recommended. Necessary to meet a11y standards.
*/
labelRef: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
/**
* A ref to an element containing description, helper text, or instructions for using the grid to provide context for screen readers. This can be a ref to a textual DOM element or a string. This information should be made visible as well outside of the grid when possible.
*/
descriptionRef: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string])
};
var defaultProps = {
pinnedColumns: [],
overflowColumns: [],
rowHeight: '2.5rem',
headerHeight: '2.5rem',
defaultColumnWidth: 200,
sections: []
};
function getA11yText(ref) {
if (!ref) {
return null;
}
if (typeof ref === 'string') {
return ref;
}
if (typeof ref === 'function') {
/**
* React.createRef/useRef use 'current' property while callback ref can be accessed directly.
*/
return ref() && (ref().current && ref().current.textContent || ref().textContent) || null;
}
return null;
}
/* eslint-disable react/sort-comp, react/forbid-dom-props */
var DataGrid = /*#__PURE__*/function (_React$Component) {
_inherits(DataGrid, _React$Component);
function DataGrid(props) {
var _this;
_classCallCheck(this, DataGrid);
_this = _callSuper(this, DataGrid, [props]);
/**
* Accessibility
*/
_this.handleLeadingFocusAnchorFocus = _this.handleLeadingFocusAnchorFocus.bind(_assertThisInitialized(_this));
_this.handleTerminalFocusAnchorFocus = _this.handleTerminalFocusAnchorFocus.bind(_assertThisInitialized(_this));
_this.getLabelText = _this.getLabelText.bind(_assertThisInitialized(_this));
_this.getDescriptionText = _this.getDescriptionText.bind(_assertThisInitialized(_this));
/**
* Column Sizing
*/
_this.updateColumnWidth = _this.updateColumnWidth.bind(_assertThisInitialized(_this));
/**
* Column Highlighting
*/
_this.updateColumnHighlightRowData = _this.updateColumnHighlightRowData.bind(_assertThisInitialized(_this));
/**
* Keyboard Events
*/
_this.handleKeyDown = _this.handleKeyDown.bind(_assertThisInitialized(_this));
_this.handleKeyUp = _this.handleKeyUp.bind(_assertThisInitialized(_this));
_this.shiftIsPressed = false;
/**
* Memoized Style Generators
*/
_this.generateHeaderContainerStyle = (0, _memoizeOne.default)(_this.generateHeaderContainerStyle);
_this.generateOverflowColumnHeaderStyle = (0, _memoizeOne.default)(_this.generateOverflowColumnHeaderStyle);
_this.generatePinnedContainerWidthStyle = (0, _memoizeOne.default)(_this.generatePinnedContainerWidthStyle);
_this.generatePinnedColumnHeaderStyle = (0, _memoizeOne.default)(_this.generatePinnedColumnHeaderStyle);
/**
* Paging
*/
_this.checkForMoreContent = _this.checkForMoreContent.bind(_assertThisInitialized(_this));
/**
* Post-render Updates
*/
_this.postRenderUpdate = _this.postRenderUpdate.bind(_assertThisInitialized(_this));
/**
* Refs
*/
_this.setDataGridContainerRef = _this.setDataGridContainerRef.bind(_assertThisInitialized(_this));
_this.setHeaderOverflowContainerRef = _this.setHeaderOverflowContainerRef.bind(_assertThisInitialized(_this));
_this.setHeaderScrollbarBufferRef = _this.setHeaderScrollbarBufferRef.bind(_assertThisInitialized(_this));
_this.setHorizontalOverflowContainerRef = _this.setHorizontalOverflowContainerRef.bind(_assertThisInitialized(_this));
_this.setLeadingFocusAnchorRef = _this.setLeadingFocusAnchorRef.bind(_assertThisInitialized(_this));
_this.setOverflowedContentContainerRef = _this.setOverflowedContentContainerRef.bind(_assertThisInitialized(_this));
_this.setPinnedContentContainerRef = _this.setPinnedContentContainerRef.bind(_assertThisInitialized(_this));
_this.setScrollbarRef = _this.setScrollbarRef.bind(_assertThisInitialized(_this));
_this.setScrollbarContainerRef = _this.setScrollbarContainerRef.bind(_assertThisInitialized(_this));
_this.setTerminalFocusAnchorRef = _this.setTerminalFocusAnchorRef.bind(_assertThisInitialized(_this));
_this.setVerticalOverflowContainerRef = _this.setVerticalOverflowContainerRef.bind(_assertThisInitialized(_this));
_this.cellRefs = {};
_this.headerCellRefs = {};
_this.sectionRefs = {};
/**
* Resize Events
*/
_this.handleDataGridResize = _this.handleDataGridResize.bind(_assertThisInitialized(_this));
_this.resizeSectionHeaders = _this.resizeSectionHeaders.bind(_assertThisInitialized(_this));
_this.updateHeaderScrollbarBuffer = _this.updateHeaderScrollbarBuffer.bind(_assertThisInitialized(_this));
/**
* Scroll synchronization
*/
_this.synchronizeHeaderScroll = _this.synchronizeHeaderScroll.bind(_assertThisInitialized(_this));
_this.synchronizeContentScroll = _this.synchronizeContentScroll.bind(_assertThisInitialized(_this));
_this.synchronizeScrollbar = _this.synchronizeScrollbar.bind(_assertThisInitialized(_this));
_this.resetHeaderScrollEventMarkers = _this.resetHeaderScrollEventMarkers.bind(_assertThisInitialized(_this));
_this.resetContentScrollEventMarkers = _this.resetContentScrollEventMarkers.bind(_assertThisInitialized(_this));
_this.resetScrollbarEventMarkers = _this.resetScrollbarEventMarkers.bind(_assertThisInitialized(_this));
_this.updateScrollbarPosition = _this.updateScrollbarPosition.bind(_assertThisInitialized(_this));
_this.updateScrollbarVisibility = _this.updateScrollbarVisibility.bind(_assertThisInitialized(_this));
_this.scrollbarPosition = 0;
/**
* Rendering
*/
_this.renderCell = _this.renderCell.bind(_assertThisInitialized(_this));
_this.renderHeaderCell = _this.renderHeaderCell.bind(_assertThisInitialized(_this));
_this.renderRowSelectionCell = _this.renderRowSelectionCell.bind(_assertThisInitialized(_this));
_this.renderFixedHeaderRow = _this.renderFixedHeaderRow.bind(_assertThisInitialized(_this));
_this.renderOverflowContent = _this.renderOverflowContent.bind(_assertThisInitialized(_this));
_this.renderPinnedContent = _this.renderPinnedContent.bind(_assertThisInitialized(_this));
_this.renderRow = _this.renderRow.bind(_assertThisInitialized(_this));
_this.renderScrollbar = _this.renderScrollbar.bind(_assertThisInitialized(_this));
_this.renderSection = _this.renderSection.bind(_assertThisInitialized(_this));
_this.renderSectionHeader = _this.renderSectionHeader.bind(_assertThisInitialized(_this));
/**
* Animation Frame ID's
*/
_this.animationFrameIDPinned = null;
_this.animationFrameIDVertical = null;
/**
* Determining the widths of the pinned and overflow sections requires iterating over the prop arrays. The widths are
* generated and cached in state to limit the amount of iteration performed by the render functions. If column highlighting
* is used, the first and last row information will also be cached in state to save render iterations.
*/
_this.state = {
pinnedColumnWidth: _dataGridUtils.default.getTotalColumnWidth(_dataGridUtils.default.getPinnedColumns(props), props.defaultColumnWidth),
overflowColumnWidth: _dataGridUtils.default.getTotalColumnWidth(_dataGridUtils.default.getOverflowColumns(props), props.defaultColumnWidth),
columnHighlightRowData: !props.columnHighlightId ? {
firstRowSectionId: null,
firstRowId: null,
lastRowSectionId: null,
lastRowId: null
} : _dataGridUtils.default.getFirstAndLastVisibleRowData(props.sections),
/**
* Data Accessibility ID attribute name per data grid. This appends the unique grid ID to accessibility-id attribute name so that
* cells in the grid can be tabbed through sequencially without conflicts in case of multiple grids on a single page.
*/
accessibilityId: "data-accessibility-id-".concat(props.id),
labelText: null,
descriptionText: null
};
return _this;
}
_createClass(DataGrid, [{
key: "componentDidMount",
value: function componentDidMount() {
var _this2 = this;
/**
* A ResizeObserver is used to manage changes to the DataGrid's overall size. The handler will execute once upon the start of
* observation and on every subsequent resize.
*/
this.resizeObserver = new _resizeObserverPolyfill.default(function (entries) {
_this2.animationFrameIDVertical = window.requestAnimationFrame(function () {
_this2.handleDataGridResize(entries[0].contentRect.width, entries[0].contentRect.height);
});
});
this.resizeObserver.observe(this.verticalOverflowContainerRef);
/**
* Another ResizeObserver is used to track changes to the pinned column section height.
*/
this.pinnedColumnResizeObserver = new _resizeObserverPolyfill.default(function (entries) {
if (_this2.scrollbarRef) {
_this2.animationFrameIDPinned = window.requestAnimationFrame(function () {
/**
* The height of the overflow content region must be set to hide the horizontal scrollbar for that element. It is hidden because we
* want defer to the custom scrollbar that rendered by the DataGrid.
*/
_this2.overflowedContentContainerRef.style.height = "".concat(entries[0].contentRect.height, "px");
});
}
});
this.pinnedColumnResizeObserver.observe(this.pinnedContentContainerRef);
/**
* We need to keep track of the user's usage of SHIFT to properly handle tabbing paths.
*/
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
/**
* Get the label and description text from labelRef and descriptionRef props.
*/
if (this.props.labelRef) {
this.getLabelText();
}
if (this.props.descriptionRef) {
this.getDescriptionText();
}
this.postRenderUpdate();
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
/**
* If the sections prop has been updated, we invalidate the content request flag before potentially requesting
* more content, and update the first and last row information if needed for column highlighting.
*/
if (prevProps.sections !== this.props.sections) {
this.hasRequestedContent = false;
this.updateColumnHighlightRowData();
}
/**
* If labelRef or descriptionRef props are updated, set the new text for the label and description.
*/
if (prevProps.labelRef !== this.props.labelRef) {
this.getLabelText();
}
if (prevProps.descriptionRef !== this.props.descriptionRef) {
this.getDescriptionText();
}
this.postRenderUpdate();
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
window.cancelAnimationFrame(this.animationFrameIDVertical);
this.resizeObserver.disconnect(this.verticalOverflowContainerRef);
window.cancelAnimationFrame(this.animationFrameIDPinned);
this.pinnedColumnResizeObserver.disconnect(this.pinnedContentContainerRef);
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
/**
* If the component is unmounting, we need to cancel any post-render manipulation before the DOM elements
* go out of scope.
*/
cancelAnimationFrame(this.postRenderUpdateAnimationFrame);
cancelAnimationFrame(this.scrollSyncAnimationFrame);
}
/**
* Accessibility
*/
}, {
key: "handleLeadingFocusAnchorFocus",
value: function handleLeadingFocusAnchorFocus() {
if (!this.shiftIsPressed) {
var firstAccessibleElement = this.dataGridContainerRef.querySelector("[".concat(this.state.accessibilityId, "=\"0\"]"));
if (firstAccessibleElement) {
firstAccessibleElement.focus();
}
}
}
}, {
key: "handleTerminalFocusAnchorFocus",
value: function handleTerminalFocusAnchorFocus() {
if (this.shiftIsPressed) {
var lastAccessibleElement = this.dataGridContainerRef.querySelector("[".concat(this.state.accessibilityId, "=\"").concat(this.accessibilityStack.length - 1, "\"]"));
if (lastAccessibleElement) {
lastAccessibleElement.focus();
}
}
}
}, {
key: "getLabelText",
value: function getLabelText() {
var labelRef = this.props.labelRef;
this.setState({
labelText: getA11yText(labelRef)
});
}
}, {
key: "getDescriptionText",
value: function getDescriptionText() {
var descriptionRef = this.props.descriptionRef;
this.setState({
descriptionText: getA11yText(descriptionRef)
});
}
/**
* Column Sizing
*/
}, {
key: "updateColumnWidth",
value: function updateColumnWidth(columnId, widthDelta) {
var _this$props = this.props,
onRequestColumnResize = _this$props.onRequestColumnResize,
defaultColumnWidth = _this$props.defaultColumnWidth;
if (!onRequestColumnResize) {
return;
}
var pinnedColumns = _dataGridUtils.default.getPinnedColumns(this.props);
var columnToUpdate;
var columnIsPinned;
var allColumns = pinnedColumns.concat(_dataGridUtils.default.getOverflowColumns(this.props));
for (var i = 0, numberOfColumns = allColumns.length; i < numberOfColumns; i += 1) {
if (allColumns[i].id === columnId) {
columnToUpdate = allColumns[i];
if (i < pinnedColumns.length) {
columnIsPinned = true;
}
}
}
if (!columnToUpdate) {
return;
}
/**
* Depending on the page's layout direction, we need to manipulate the size calculation to account for
* the delta's direction-agnostic value.
*/
var pageDirection = document.documentElement.getAttribute('dir');
var deltaForDirection = pageDirection === 'rtl' ? widthDelta * -1 : widthDelta;
var newWidth = _dataGridUtils.default.getWidthForColumn(columnToUpdate, defaultColumnWidth) + deltaForDirection;
/**
* If the column being updated is a pinned column, we need to ensure that the new width will not cause the pinned columns to overflow the
* container's current width. Otherwise, the DataGrid may get into an unrecoverable state.
*/
if (columnIsPinned) {
var totalPinnedSectionWidth = pinnedColumns.reduce(function (totalWidth, pinnedColumn) {
if (pinnedColumn.id === columnId) {
return totalWidth + newWidth;
}
return totalWidth + pinnedColumn.width;
}, 0);
var containerWidth = this.dataGridContainerRef.getBoundingClientRect().width;
if (totalPinnedSectionWidth > containerWidth) {
newWidth -= totalPinnedSectionWidth - containerWidth;
}
}
onRequestColumnResize(columnId, newWidth);
}
/**
* Column Highlighting
*/
}, {
key: "updateColumnHighlightRowData",
value: function updateColumnHighlightRowData() {
var _this$props2 = this.props,
columnHighlightId = _this$props2.columnHighlightId,
sections = _this$props2.sections;
if (!columnHighlightId) {
/**
* If the column highlight id prop is not valued, there is nothing to be updated.
*/
return;
}
/**
* Determine the first and last row in non-collapsed and non-empty sections, then update state with new values.
*/
var firstAndLastVisibleRowData = _dataGridUtils.default.getFirstAndLastVisibleRowData(sections);
this.setState({
columnHighlightRowData: firstAndLastVisibleRowData
});
}
/**
* Keyboard Events
*/
}, {
key: "handleKeyDown",
value: function handleKeyDown(event) {
if (event.keyCode === _keycodeJs.KEY_SHIFT) {
this.shiftIsPressed = true;
}
if (event.keyCode === _keycodeJs.KEY_TAB) {
var _document = document,
activeElement = _document.activeElement;
if (!activeElement) {
return;
}
if (_dataGridUtils.default.matchesSelector(activeElement, "[".concat(this.state.accessibilityId, "]"))) {
var currentAccessibilityId = activeElement.getAttribute("".concat(this.state.accessibilityId));
var nextAccessibilityId = this.shiftIsPressed ? parseInt(currentAccessibilityId, 10) - 1 : parseInt(currentAccessibilityId, 10) + 1;
if (nextAccessibilityId >= 0 && nextAccessibilityId < this.accessibilityStack.length) {
var nextFocusElement = this.dataGridContainerRef.querySelector("[".concat(this.state.accessibilityId, "=\"").concat(nextAccessibilityId, "\"]"));
if (nextFocusElement) {
event.preventDefault();
nextFocusElement.focus();
}
} else if (nextAccessibilityId === -1) {
this.leadingFocusAnchorRef.focus();
} else {
this.terminalFocusAnchorRef.focus();
}
}
}
}
}, {
key: "handleKeyUp",
value: function handleKeyUp(event) {
if (event.keyCode === _keycodeJs.KEY_SHIFT) {
this.shiftIsPressed = false;
}
}
/**
* Memoized Style Generators
*
* These functions could technically be static functions on the DataGrid class, but then the cached values would
* be shared across all DataGrid instances. It is recommended to make instance-based versions. Because of this,
* the eslint rule for the usage of 'this' must be disabled.
*/
/* eslint-disable class-methods-use-this */
}, {
key: "generateHeaderContainerStyle",
value: function generateHeaderContainerStyle(headerHeight) {
return {
height: "".concat(headerHeight)
};
}
}, {
key: "generateOverflowColumnHeaderStyle",
value: function generateOverflowColumnHeaderStyle(overflowColumnWidth, headerHeight) {
return {
width: "".concat(overflowColumnWidth, "px"),
height: "".concat(headerHeight)
};
}
}, {
key: "generatePinnedColumnHeaderStyle",
value: function generatePinnedColumnHeaderStyle(pinnedColumnWidth, headerHeight) {
return {
width: "".concat(pinnedColumnWidth, "px"),
height: "".concat(headerHeight)
};
}
}, {
key: "generatePinnedContainerWidthStyle",
value: function generatePinnedContainerWidthStyle(pinnedColumnWidth) {
return {
width: "".concat(pinnedColumnWidth, "px")
};
}
/* eslint-enable class-methods-use-this */
/**
* Paging
*/
}, {
key: "checkForMoreContent",
value: function checkForMoreContent() {
var onRequestContent = this.props.onRequestContent;
if (!onRequestContent || this.hasRequestedContent) {
return;
}
var containerHeight = this.verticalOverflowContainerRef.getBoundingClientRect().height;
var containerScrollHeight = this.verticalOverflowContainerRef.scrollHeight;
var containerScrollTop = this.verticalOverflowContainerRef.scrollTop;
if (containerScrollHeight - (containerScrollTop + containerHeight) <= _dataGridUtils.default.PAGED_CONTENT_OFFSET_BUFFER) {
this.hasRequestedContent = true;
onRequestContent();
}
}
/**
* Post-render Updates
*/
}, {
key: "postRenderUpdate",
value: function postRenderUpdate() {
var _this3 = this;
/**
* The DOM is parsed after rendering to generate the accessibility identifiers used by the DataGrid's custom
* focus implementation.
*/
this.accessibilityStack = _dataGridUtils.default.generateAccessibleContentIndex(this.props, this.headerCellRefs, this.sectionRefs, this.cellRefs);
/**
* The previous animation frame is canceled if it is still pending.
*/
cancelAnimationFrame(this.postRenderUpdateAnimationFrame);
this.postRenderUpdateAnimationFrame = requestAnimationFrame(function () {
/**
* The SectionHeader widths must be updated after rendering to match the rendered DataGrid's width.
*/
_this3.resizeSectionHeaders(_this3.verticalOverflowContainerRef.clientWidth);
/**
* The scrollbar position and visibility are determined based on the size of the DataGrid after rendering.
*/
_this3.updateScrollbarPosition();
_this3.updateScrollbarVisibility();
/**
* Ensure correct padding is set on the header to account for potentially increased row counts.
*/
_this3.updateHeaderScrollbarBuffer();
if (_this3.scrollbarRef) {
/**
* The height of the overflow content region must be set to hide the horizontal scrollbar for that element. It is hidden because we
* want defer to the custom scrollbar that rendered by the DataGrid.
*/
_this3.overflowedContentContainerRef.style.height = "".concat(_this3.pinnedContentContainerRef.getBoundingClientRect().height, "px");
}
_this3.checkForMoreContent();
});
}
/**
* Refs
*/
}, {
key: "setDataGridContainerRef",
value: function setDataGridContainerRef(ref) {
this.dataGridContainerRef = ref;
}
}, {
key: "setPinnedContentContainerRef",
value: function setPinnedContentContainerRef(ref) {
this.pinnedContentContainerRef = ref;
}
}, {
key: "setHeaderOverflowContainerRef",
value: function setHeaderOverflowContainerRef(ref) {
this.headerOverflowContainerRef = ref;
}
}, {
key: "setHeaderScrollbarBufferRef",
value: function setHeaderScrollbarBufferRef(ref) {
this.headerScrollbarBufferRef = ref;
}
}, {
key: "setHorizontalOverflowContainerRef",
value: function setHorizontalOverflowContainerRef(ref) {
this.horizontalOverflowContainerRef = ref;
if (this.props.horizontalOverflowContainerRefCallback) {
this.props.horizontalOverflowContainerRefCallback(ref);
}
}
}, {
key: "setLeadingFocusAnchorRef",
value: function setLeadingFocusAnchorRef(ref) {
this.leadingFocusAnchorRef = ref;
}
}, {
key: "setOverflowedContentContainerRef",
value: function setOverflowedContentContainerRef(ref) {
this.overflowedContentContainerRef = ref;
}
}, {
key: "setScrollbarRef",
value: function setScrollbarRef(ref) {
this.scrollbarRef = ref;
}
}, {
key: "setScrollbarContainerRef",
value: function setScrollbarContainerRef(ref) {
this.scrollbarContainerRef = ref;
}
}, {
key: "setTerminalFocusAnchorRef",
value: function setTerminalFocusAnchorRef(ref) {
this.terminalFocusAnchorRef = ref;
}
}, {
key: "setVerticalOverflowContainerRef",
value: function setVerticalOverflowContainerRef(ref) {
this.verticalOverflowContainerRef = ref;
if (this.props.verticalOverflowContainerRefCallback) {
this.props.verticalOverflowContainerRefCallback(ref);
}
}
/**
* Resize Events
*/
}, {
key: "handleDataGridResize",
value: function handleDataGridResize(newWidth) {
this.resizeSectionHeaders(newWidth);
this.updateHeaderScrollbarBuffer();
this.updateScrollbarPosition();
this.updateScrollbarVisibility();
this.checkForMoreContent();
}
}, {
key: "resizeSectionHeaders",
value: function resizeSectionHeaders(width) {
/**
* The widths are applied directly the nodes (outside of the React rendering lifecycle) to improve performance and limit
* unnecessary rendering of other components.
*/
var sectionHeaderContainers = this.dataGridContainerRef.querySelectorAll('[data-terra-clinical-data-grid-section-header-resize="true"]');
/**
* querySelectorAll returns a NodeList, which does not support standard iteration functions like forEach in legacy browsers.
*/
for (var i = 0, numberOfSectionHeaders = sectionHeaderContainers.length; i < numberOfSectionHeaders; i += 1) {
sectionHeaderContainers[i].style.width = "".concat(width, "px");
}
}
}, {
key: "updateHeaderScrollbarBuffer",
value: function updateHeaderScrollbarBuffer() {
var pinnedColumnWidth = this.state.pinnedColumnWidth;
if (!this.headerScrollbarBufferRef) {
/**
* The buffer element will not be rendered if the 'fill' prop is not provided.
* If the ref to the buffer element does not exist, it must not be rendered, so there is no work to do here.
*/
return;
}
/**
* If there is a vertical overflow and fixed scrollbars are present (due to the presence of a mouse, etc.), the header columns
* and content columns can move out of alignment. We need to account for the potential presence of the scrollbar and set the size of the
* header scrollbar buffer element to equalize any differences in width.
*/
var scrollbarOffset = this.dataGridContainerRef.clientWidth - pinnedColumnWidth - this.horizontalOverflowContainerRef.clientWidth;
this.headerScrollbarBufferRef.style.width = "".concat(scrollbarOffset, "px");
}
/**
* Scroll synchronization
*/
}, {
key: "synchronizeHeaderScroll",
value: function synchronizeHeaderScroll() {
var _this4 = this;
if (this.scrollbarIsScrolling || this.contentIsScrolling) {
return;
}
this.headerIsScrolling = true;
if (this.synchronizeScrollTimeout) {
clearTimeout(this.synchronizeScrollTimeout);
}
this.synchronizeScrollTimeout = setTimeout(this.resetHeaderScrollEventMarkers, 100);
cancelAnimationFrame(this.scrollSyncAnimationFrame);
this.scrollSyncAnimationFrame = requestAnimationFrame(function () {
_this4.horizontalOverflowContainerRef.scrollLeft = _this4.headerOverflowContainerRef.scrollLeft;
_this4.updateScrollbarPosition();
});
}
}, {
key: "synchronizeContentScroll",
value: function synchronizeContentScroll() {
var _this5 = this;
if (this.scrollbarIsScrolling || this.headerIsScrolling) {
return;
}
this.contentIsScrolling = true;
if (this.synchronizeScrollTimeout) {
clearTimeout(this.synchronizeScrollTimeout);
}
this.synchronizeScrollTimeout = setTimeout(this.resetContentScrollEventMarkers, 100);
cancelAnimationFrame(this.scrollSyncAnimationFrame);
this.scrollSyncAnimationFrame = requestAnimationFrame(function () {
_this5.headerOverflowContainerRef.scrollLeft = _this5.horizontalOverflowContainerRef.scrollLeft;
_this5.updateScrollbarPosition();
});
}
}, {
key: "synchronizeScrollbar",
value: function synchronizeScrollbar(event, data) {
var _this6 = this;
if (this.headerIsScrolling || this.contentIsScrolling) {
return;
}
this.scrollbarIsScrolling = true;
var newPosition = this.scrollbarPosition + data.deltaX;
var scrollArea = this.horizontalOverflowContainerRef.clientWidth - this.scrollbarRef.clientWidth;
var finalPosition;
if (newPosition < 0) {
finalPosition = 0;
} else if (newPosition > scrollArea) {
finalPosition = scrollArea;
} else {
finalPosition = newPosition;
}
this.scrollbarPosition = finalPosition;
var positionRatio = finalPosition / scrollArea;
var maxScrollLeft = this.horizontalOverflowContainerRef.scrollWidth - this.horizontalOverflowContainerRef.clientWidth;
cancelAnimationFrame(this.scrollSyncAnimationFrame);
this.scrollSyncAnimationFrame = requestAnimationFrame(function () {
_this6.scrollbarRef.style.transform = "translateX(".concat(_this6.scrollbarPosition, "px)");
_this6.headerOverflowContainerRef.scrollLeft = maxScrollLeft * positionRatio;
_this6.horizontalOverflowContainerRef.scrollLeft = maxScrollLeft * positionRatio;
});
}
}, {
key: "resetHeaderScrollEventMarkers",
value: function resetHeaderScrollEventMarkers() {
this.headerIsScrolling = false;
}
}, {
key: "resetContentScrollEventMarkers",
value: function resetContentScrollEventMarkers() {
this.contentIsScrolling = false;
}
}, {
key: "resetScrollbarEventMarkers",
value: function resetScrollbarEventMarkers() {
this.scrollbarIsScrolling = false;
}
}, {
key: "updateScrollbarVisibility",
value: function updateScrollbarVisibility() {
if (!this.scrollbarContainerRef) {
/**
* The scrollbar will not be rendered if the 'fill' prop is not provided.
* If the ref to the scrollbar does not exist, it must not be rendered, so there is no work to do here.
*/
return;
}
if (Math.abs(this.horizontalOverflowContainerRef.scrollWidth - this.horizontalOverflowContainerRef.getBoundingClientRect().width) < 1) {
this.scrollbarContainerRef.setAttribute('aria-hidden', true);
} else {
this.scrollbarContainerRef.removeAttribute('aria-hidden');
}
}
}, {
key: "updateScrollbarPosition",
value: function updateScrollbarPosition() {
var overflowColumnWidth = this.state.overflowColumnWidth;
if (!this.scrollbarRef) {
/**
* The scrollbar will not be rendered if the 'fill' prop is not provided.
* If the ref to the scrollbar does not exist, it must not be rendered, so there is no work to do here.
*/
return;
}
/**
* The scrollbar width is determined by squaring the horizontal container width and dividing by the overflow value. The scrollbar cannot be larger than the container.
*/
var scrollbarWidth = Math.min(this.horizontalOverflowContainerRef.clientWidth, this.horizontalOverflowContainerRef.clientWidth * this.horizontalOverflowContainerRef.clientWidth / overflowColumnWidth);
/**
* The scrollbar position is determined by calculating its position within the horizontalOverflowContainerRef and applying its relative position
* to the overall horizontal container width.
*/
var positionRatio = this.horizontalOverflowContainerRef.scrollLeft / (this.horizontalOverflowContainerRef.scrollWidth - this.horizontalOverflowContainerRef.clientWidth);
var position = (this.horizontalOverflowContainerRef.clientWidth - scrollbarWidth) * positionRatio;
this.scrollbarPosition = position;
this.scrollbarRef.style.width = "".concat(scrollbarWidth, "px");
this.scrollbarRef.style.transform = "translateX(".concat(this.scrollbarPosition, "px)");
}
/**
* Rendering
*/
}, {
key: "renderHeaderCell",
value: function renderHeaderCell(columnData) {
var _this7 = this;
var columnId = columnData.id;
var _this$props3 = this.props,
onColumnSelect = _this$props3.onColumnSelect,
hasResizableColumns = _this$props3.hasResizableColumns,
defaultColumnWidth = _this$props3.defaultColumnWidth;
/**
* Rather than render an empty HeaderCell for the void column, we just render nothing.
* The width of the void column is already being accounted for.
*/
if (columnId === 'DataGrid-voidColumn') {
return undefined;
}
return /*#__PURE__*/_react.default.createElement(_HeaderCell.default, {
key: columnId,
columnId: columnId,
text: columnData.text,
sortIndicator: columnData.sortIndicator,
width: "".concat(_dataGridUtils.default.getWidthForColumn(columnData, defaultColumnWidth), "px"),
isSelectable: columnData.isSelectable,
isResizable: hasResizableColumns && columnData.isResizable,
onResizeEnd: this.updateColumnWidth,
onSelect: onColumnSelect,
selectableRefCallback: function selectableRefCallback(ref) {
_this7.headerCellRefs[columnId] = ref;
}
}, columnData.component);
}
}, {
key: "renderFixedHeaderRow",
value: function renderFixedHeaderRow() {
var _this8 = this;
var headerHeight = this.props.headerHeight;
var _this$state = this.state,
pinnedColumnWidth = _this$state.pinnedColumnWidth,
overflowColumnWidth = _this$state.overflowColumnWidth;
return /*#__PURE__*/_react.default.createElement("div", {
className: cx(['header-container', 'fixed']),
style: this.generateHeaderContainerStyle(headerHeight),
role: "row"
}, /*#__PURE__*/_react.default.createElement("div", {
className: cx('pinned-header'),
style: this.generatePinnedColumnHeaderStyle(pinnedColumnWidth, headerHeight)
}, _dataGridUtils.default.getPinnedColumns(this.props).map(function (column) {
return _this8.renderHeaderCell(column);
})), /*#__PURE__*/_react.default.createElement("div", {
className: cx('header-overflow-container'),
ref: this.setHeaderOverflowContainerRef,
onScroll: this.synchronizeHeaderScroll
}, /*#__PURE__*/_react.default.createElement("div", {
className: cx('overflow-header'),
style: this.generateOverflowColumnHeaderStyle(overflowColumnWidth, headerHeight)
}, _dataGridUtils.default.getOverflowColumns(this.props).map(function (column) {
return _this8.renderHeaderCell(column);
}))), /*#__PURE__*/_react.default.createElement("div", {
className: cx('header-scrollbar-buffer'),
ref: this.setHeaderScrollbarBufferRef
}));
}
}, {
key: "renderSectionHeader",
value: function renderSectionHeader(section, isPinned) {
var _this9 = this;
var onRequestSectionCollapse = this.props.onRequestSectionCollapse;
var shouldRenderSectionHeaderContainer = section.isCollapsible || section.text || section.startAccessory || section.endAccessory || section.component;
return shouldRenderSectionHeaderContainer ? /*#__PURE__*/_react.default.createElement("div", {
key: section.id,
className: cx('section-header-container'),
"data-terra-clinical-data-grid-section-header-resize": !!isPinned || undefined
}, isPinned ? /*#__PURE__*/_react.default.createElement(_SectionHeader.default, {
sectionId: section.id,
text: section.text,
startAccessory: section.startAccessory,
endAccessory: section.endAccessory,
isCollapsible: section.isCollapsible,
isCollapsed: section.isCollapsed,
onRequestSectionCollapse: onRequestSectionCollapse,
selectableRefCallback: function selectableRefCallback(ref) {
_this9.sectionRefs[section.id] = ref;
}
}, section.component) : null) : null;
}
}, {
key: "renderRowSelectionCell",
value: function renderRowSelectionCell(section, row, column) {
var _this10 = this;
var _this$props4 = this.props,
defaultColumnWidth = _this$props4.defaultColumnWidth,
columnHighlightId = _this$props4.columnHighlightId;
var cellKey = "".concat(section.id, "-").concat(row.id, "-").concat(column.id);
return /*#__PURE__*/_react.default.createElement(_RowSelectionCell.default, {
key: cellKey,
sectionId: section.id,
rowId: row.id,
columnId: column.id,
width: "".concat(_dataGridUtils.default.getWidthForColumn(column, defaultColumnWidth), "px"),
isSelectable: row.isSelectable && !row.isDecorative,
isSelected: row.isSelected && !row.isDecorative,
onSelect: this.props.onRowSelect,
selectableRefCallback: function selectableRefCallback(ref) {
_this10.cellRefs[cellKey] = ref;
},
onHoverStart: function onHoverStart() {
if (!row.isDecorative) {
/**
* Because the pinned and overflow rows are two separate elements, we need to retrieve them and add the appropriate hover styles
* to both to ensure a consistent row styling.
*/
var rowElements = _this10.dataGridContainerRef.querySelectorAll("[data-row][data-row-id=\"".concat(row.id, "\"][data-section-id=\"").concat(section.id, "\"]"));
for (var i = 0, numberOfRows = rowElements.length; i < numberOfRows; i += 1) {
rowElements[i].classList.add(cxRow('hover'));
if (columnHighlightId) {
rowElements[i].removeAttribute('data-allow-column-highlight');
}
}
}
},
onHoverEnd: function onHoverEnd() {
if (!row.isDecorative) {
var rowElements = _this10.dataGridContainerRef.querySelectorAll("[data-row][data-row-id=\"".concat(row.id, "\"][data-section-id=\"").concat(section.id, "\"]"));
for (var i = 0, numberOfRows = rowElements.length; i < numberOfRows; i += 1) {
rowElements[i].classList.remove(cxRow('hover'));
if (columnHighlightId && !row.isSelected) {
rowElements[i].setAttribute('data-allow-column-highlight', true);
}
}
}
},
ariaLabel: this.props.intl.formatMessage({
id: 'Terra.data-grid.row-selection-template'
}, {
rowDescription: row.ariaLabel
})
});
}
}, {
key: "renderCell",
value: function renderCell(section, row, column, isFirstRow, isLastRow, isRowHeader) {
var _this11 = this;
var _this$props5 = this.props,
onCellSelect = _this$props5.onCellSelect,
defaultColumnWidth = _this$props5.defaultColumnWidth,
columnHighlightId = _this$props5.columnHighlightId;
var cell = row.cells && row.cells.find(function (searchCell) {
return searchCell.columnId === column.id;
}) || {};
var cellKey = "".concat(section.id, "-").concat(row.id, "-").concat(column.id);
v