UNPKG

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
"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. * * ![IMPORTANT](https://badgen.net/badge/UX/Design-Standards/blue) 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