UNPKG

terra-table

Version:

The Terra Table component provides user a way to display data in an accessible table format.

458 lines (445 loc) 18.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactIntl = require("react-intl"); var KeyCode = _interopRequireWildcard(require("keycode-js")); var _bind = _interopRequireDefault(require("classnames/bind")); var _terraThemeContext = _interopRequireDefault(require("terra-theme-context")); var _terraVisuallyHiddenText = _interopRequireDefault(require("terra-visually-hidden-text")); var _terraButton = _interopRequireDefault(require("terra-button")); var _terraIcon = require("terra-icon"); var _validators = require("../proptypes/validators"); var _ColumnResizeHandle = _interopRequireDefault(require("./ColumnResizeHandle")); var _GridContext = _interopRequireWildcard(require("../utils/GridContext")); var _columnShape = require("../proptypes/columnShape"); var _ColumnContext = _interopRequireDefault(require("../utils/ColumnContext")); var _ColumnHeaderCellModule = _interopRequireDefault(require("./ColumnHeaderCell.module.scss")); 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; } /* eslint-disable react-hooks/exhaustive-deps */ var cx = _bind.default.bind(_ColumnHeaderCellModule.default); var propTypes = { /** * Required string representing a unique identifier for the column header cell. */ id: _propTypes.default.string.isRequired, /** * Unique identifier for the parent table */ tableId: _propTypes.default.string.isRequired, /** * Unique identifier for the column */ columnId: _propTypes.default.string.isRequired, /** * CallBack to trigger re-focusing when focused row or col didn't change, but focus update is needed */ triggerFocus: _propTypes.default.func, /** * String of text to render within the column header cell. */ displayName: _propTypes.default.string, /** * A string indicating which sorting indicator should be rendered. If not provided, no sorting indicator will be rendered. * If a `component` value is specified, `sortIndicator` will be ignored. One of `ascending`, `descending`. */ sortIndicator: _propTypes.default.oneOf(Object.values(_columnShape.SortIndicators)), /** * Boolean value indicating whether or not the column has an error in the data. */ hasError: _propTypes.default.bool, /** * Number that specifies the minimum column width in pixels. */ minimumWidth: _propTypes.default.number, /** * Number that specifies the maximum column width in pixels. */ maximumWidth: _propTypes.default.number, /** * Boolean value indicating whether or not the header cell is focused. */ isActive: _propTypes.default.bool, /** * Boolean that specifies that header cell owns a resize handle. */ ownsResizeHandle: _propTypes.default.bool, /** * Boolean value indicating whether or not the header cell text is displayed in the cell. */ isDisplayVisible: _propTypes.default.bool, /** * Boolean value indicating whether or not the column header is selectable. */ isSelectable: _propTypes.default.bool, /** * Boolean value indicating whether or not the column header cell is an action cell. * The action cell might be a placeholder cell without actual action button */ isActionCell: _propTypes.default.bool, /** * Data for action cell. */ action: _validators.validateAction, /** * Boolean value indicating whether or not the column header is resizable. */ isResizable: _propTypes.default.bool, /** * Boolean value indicating whether or not the column resize handle is active. */ isResizeHandleActive: _propTypes.default.bool, /** * A function to be executed upon the resize handler activation to pass its data to parent component. * @param {element} leftNeighborCell - `columnHeaderCellRef.current` * Skip both parameters to indicate that there is no active resize handle at the moment. */ resizeHandleStateSetter: _propTypes.default.func, /** * String that specifies the initial height for the resize handler to accommodate actions row. */ initialHeight: _propTypes.default.string, /** * Height of the parent table. */ tableHeight: _propTypes.default.number, /** * Boolean value indicating whether or not the column header is resizable. */ isResizeActive: _propTypes.default.bool, /** * Numeric increment in pixels to adjust column width when resizing via the keyboard. */ columnResizeIncrement: _propTypes.default.number, /** * The number (in px) specifying the width of the column. */ width: _propTypes.default.number, /** * String that specifies the column height. Any valid CSS height value accepted. */ headerHeight: _propTypes.default.string.isRequired, /** * The cell's column position in the grid. This is zero based. */ columnIndex: _propTypes.default.number, /** * The column span value for a column. */ columnSpan: _propTypes.default.number, /** * Function that is called when a selectable header cell is selected. Parameters: * @param {string} rowId rowId * @param {string} columnId columnId */ onColumnSelect: _propTypes.default.func, /** * Function that is called when the mouse down event is triggered on the column resize handle. */ onResizeMouseDown: _propTypes.default.func, /** * Function that is called when the the keyboard is used to adjust the column size. */ onResizeHandleChange: _propTypes.default.func, /** * @private * Object containing intl APIs */ intl: _propTypes.default.shape({ formatMessage: _propTypes.default.func }), /** * @private * The information to be conveyed to screen readers about the highlighted column. */ columnHighlightDescription: _propTypes.default.string, /** * @private * The color to be used for highlighting a column. */ columnHighlightColor: _propTypes.default.oneOf(Object.values(_columnShape.ColumnHighlightColor)) }; var defaultProps = { hasError: false, isSelectable: false, isActive: false, isDisplayVisible: true, isResizable: false, isResizeActive: false }; var ColumnHeaderCell = function ColumnHeaderCell(props) { var id = props.id, tableId = props.tableId, isActionCell = props.isActionCell, action = props.action, displayName = props.displayName, sortIndicator = props.sortIndicator, hasError = props.hasError, isActive = props.isActive, isDisplayVisible = props.isDisplayVisible, isSelectable = props.isSelectable, isResizable = props.isResizable, isResizeHandleActive = props.isResizeHandleActive, resizeHandleStateSetter = props.resizeHandleStateSetter, initialHeight = props.initialHeight, triggerFocus = props.triggerFocus, columnId = props.columnId, tableHeight = props.tableHeight, isResizeActive = props.isResizeActive, columnResizeIncrement = props.columnResizeIncrement, width = props.width, minimumWidth = props.minimumWidth, maximumWidth = props.maximumWidth, headerHeight = props.headerHeight, onColumnSelect = props.onColumnSelect, intl = props.intl, columnIndex = props.columnIndex, columnSpan = props.columnSpan, onResizeMouseDown = props.onResizeMouseDown, onResizeHandleChange = props.onResizeHandleChange, ownsResizeHandle = props.ownsResizeHandle, columnHighlightDescription = props.columnHighlightDescription, columnHighlightColor = props.columnHighlightColor; var columnContext = (0, _react.useContext)(_ColumnContext.default); var gridContext = (0, _react.useContext)(_GridContext.default); var columnHeaderCellRef = (0, _react.useRef)(); var setResizeHandleActive = (0, _react.useCallback)(function (setActive) { if (setActive) { resizeHandleStateSetter(columnId); } else { resizeHandleStateSetter(); } }, [columnId, resizeHandleStateSetter]); var isGridContext = gridContext.role === _GridContext.GridConstants.GRID; (0, _react.useEffect)(function () { if (isActive) { if (isResizable && isResizeActive) { setResizeHandleActive(true); } else { columnHeaderCellRef.current.focus(); setResizeHandleActive(false); } } else { setResizeHandleActive(false); } }, [isActive, isResizable, isResizeActive]); var onResizeHandleMouseDown = (0, _react.useCallback)(function (event) { event.stopPropagation(); if (onResizeMouseDown) { onResizeMouseDown(event, columnIndex, columnHeaderCellRef.current.offsetWidth); } }, [columnIndex, onResizeMouseDown]); // Restore focus to column header after resize action is completed. var onResizeHandleMouseUp = (0, _react.useCallback)(function () { columnHeaderCellRef.current.focus(); setResizeHandleActive(false); }, []); // Handle column header selection via the mouse click. var handleMouseDown = function handleMouseDown() { onColumnSelect(columnId); }; // Handle column header selection via the space bar. var handleKeyDown = function handleKeyDown(event, callback) { var key = event.keyCode; switch (key) { case KeyCode.KEY_SPACE: case KeyCode.KEY_RETURN: if (callback) { // for action button callback(); break; } else if (isSelectable && onColumnSelect) { onColumnSelect(columnId); } event.stopPropagation(); event.preventDefault(); // prevent the default scrolling break; case KeyCode.KEY_LEFT: if (isResizable && isResizeHandleActive && isGridContext) { setResizeHandleActive(false); if (triggerFocus) { triggerFocus(); } event.stopPropagation(); event.preventDefault(); } break; case KeyCode.KEY_RIGHT: if (isResizable && !isResizeHandleActive && isGridContext) { setResizeHandleActive(true); event.stopPropagation(); event.preventDefault(); } break; default: } }; var errorIcon = hasError && /*#__PURE__*/_react.default.createElement(_terraIcon.IconError, { className: cx('error-icon') }); // Add the sort indicator based on the sort direction var sortIndicatorIcon; var sortDescription = ''; if (sortIndicator === _columnShape.SortIndicators.ASCENDING) { sortIndicatorIcon = /*#__PURE__*/_react.default.createElement(_terraIcon.IconUp, null); sortDescription = intl.formatMessage({ id: 'Terra.table.sort-ascending' }); } else if (sortIndicator === _columnShape.SortIndicators.DESCENDING) { sortIndicatorIcon = /*#__PURE__*/_react.default.createElement(_terraIcon.IconDown, null); sortDescription = intl.formatMessage({ id: 'Terra.table.sort-descending' }); } // Add column highlight indicator based on color var columnHighlightIcon; if (columnHighlightColor === _columnShape.ColumnHighlightColor.GREEN) { columnHighlightIcon = /*#__PURE__*/_react.default.createElement("svg", { className: cx('highlight-icon-svg'), xmlns: "http://www.w3.org/2000/svg" }, /*#__PURE__*/_react.default.createElement("circle", { className: cx('highlight-icon-circle'), r: "3", cx: "110%", cy: "11", transform: "translate(-5)" })); } else if (columnHighlightColor === _columnShape.ColumnHighlightColor.ORANGE) { columnHighlightIcon = /*#__PURE__*/_react.default.createElement("svg", { className: cx('highlight-icon-svg'), xmlns: "http://www.w3.org/2000/svg" }, /*#__PURE__*/_react.default.createElement("rect", { className: cx('highlight-icon-square'), x: "110%", y: "7.5", transform: "translate(-8)" })); } // Retrieve current theme from context var theme = (0, _react.useContext)(_terraThemeContext.default); // Calculate cell left position for pinned columns due to their sticky position style var cellLeftEdge = columnIndex < columnContext.pinnedColumnHeaderOffsets.length ? columnContext.pinnedColumnHeaderOffsets[columnIndex] : null; // For tables, we want elements to be tabbable when selectable, but not anytime else. var buttonTabIndex = isSelectable ? 0 : undefined; // Determine if button element is required for column header var hasButtonElement = isSelectable && displayName || isActionCell && action; var cellTabIndex; if (isGridContext) { if (columnIndex === 0 && !isActionCell) { buttonTabIndex = isSelectable && displayName ? 0 : undefined; cellTabIndex = !hasButtonElement ? 0 : undefined; } else { // For grids, we only want 1 tab stop. We then define the focus behavior in DataGrid. buttonTabIndex = isSelectable && displayName ? -1 : undefined; cellTabIndex = !hasButtonElement ? -1 : undefined; } } // Format header description for screenreader var headerDescription = displayName; headerDescription += errorIcon ? ", ".concat(intl.formatMessage({ id: 'Terra.table.columnError' })) : ''; headerDescription += sortDescription ? ", ".concat(sortDescription) : ''; headerDescription += columnHighlightDescription ? ", ".concat(columnHighlightDescription) : ''; var isPinnedColumn = columnIndex < columnContext.pinnedColumnHeaderOffsets.length; var CellTag = !isActionCell ? 'th' : 'td'; var setColumnHeaderCellRef = function setColumnHeaderCellRef(node) { columnHeaderCellRef.current = node; }; // Create cell content var cellContent; if (isActionCell) { if (action) { cellContent = /*#__PURE__*/_react.default.createElement(_terraButton.default, { variant: "de-emphasis", isCompact: true, refCallback: setColumnHeaderCellRef, onClick: action.onClick, onKeyDown: function onKeyDown(event) { return handleKeyDown(event, action === null || action === void 0 ? void 0 : action.onClick); }, text: action.label }); } else { cellContent = /*#__PURE__*/_react.default.createElement("span", { className: cx('display-text', 'hidden') }, intl.formatMessage({ id: 'Terra.table.noAction' })); } } else { cellContent = /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({ className: cx('header-container') }, hasButtonElement && { ref: columnHeaderCellRef, role: 'button' }, { tabIndex: buttonTabIndex }), errorIcon, /*#__PURE__*/_react.default.createElement("span", { "aria-hidden": true, className: cx('display-text', { hidden: !isDisplayVisible }) }, displayName), sortIndicatorIcon, /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, { text: headerDescription }), columnHighlightIcon); } var resizeHandleId = "".concat(tableId, "-").concat(columnId, "-resizeHandle"); return ( /*#__PURE__*/ /* eslint-disable react/forbid-dom-props */ _react.default.createElement(CellTag, { ref: !hasButtonElement ? columnHeaderCellRef : undefined, id: "".concat(tableId, "-").concat(id), key: id, className: cx('column-header', theme.className, { 'action-cell': isActionCell, selectable: isSelectable, pinned: isPinnedColumn, 'last-pinned-column': columnIndex === columnContext.pinnedColumnHeaderOffsets.length - 1 }), tabIndex: cellTabIndex, role: !isActionCell ? 'columnheader' : undefined, scope: !isActionCell ? 'col' : undefined // action Cell has to own a corresponding resize handle to avoid a double announcement on handle focus , "aria-owns": ownsResizeHandle ? resizeHandleId : undefined, title: !isActionCell ? displayName : action === null || action === void 0 ? void 0 : action.label, colSpan: columnSpan, onMouseDown: isSelectable && onColumnSelect ? handleMouseDown : undefined, onKeyDown: isSelectable || isResizable ? handleKeyDown : undefined // eslint-disable-next-line react/forbid-component-props , style: { height: isActionCell ? 'auto' : headerHeight, left: cellLeftEdge, top: isActionCell ? headerHeight : undefined } }, cellContent, isResizable && !isActionCell && /*#__PURE__*/_react.default.createElement(_ColumnResizeHandle.default, { id: resizeHandleId, columnIndex: columnIndex, columnText: displayName, columnWidth: width, columnResizeIncrement: columnResizeIncrement, isActive: isResizeHandleActive, setIsActive: setResizeHandleActive, height: tableHeight, initialHeight: initialHeight, minimumWidth: minimumWidth, maximumWidth: maximumWidth, onResizeMouseDown: onResizeHandleMouseDown, onResizeMouseUp: onResizeHandleMouseUp, onResizeHandleChange: onResizeHandleChange })) ); }; ColumnHeaderCell.propTypes = propTypes; ColumnHeaderCell.defaultProps = defaultProps; var _default = exports.default = /*#__PURE__*/_react.default.memo((0, _reactIntl.injectIntl)(ColumnHeaderCell));