UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

743 lines (742 loc) 43.1 kB
import "../../CommonImports"; import "../../Core/core.css"; import "./Table.css"; import { ObservableLike } from '../../Core/Observable'; import * as Utils_Accessibility from '../../Core/Util/Accessibility'; import { format } from '../../Core/Util/String'; import { FocusOrMouseWithin } from '../../FocusOrMouseWithin'; import { FocusWithin } from '../../FocusWithin'; import { FocusZone, FocusZoneContext, FocusZoneDirection, FocusZoneKeyStroke } from '../../FocusZone'; import { Icon } from '../../Icon'; import { Intersection } from '../../Intersection'; import { getDefaultLinkProps } from '../../Link'; import { List, renderListCell, DropdownList } from '../../List'; import { Observer } from '../../Observer'; import * as Resources from '../../Resources.Widgets'; import { Orientation, Position, Sizer } from '../../Sizer'; import { Tooltip } from '../../TooltipEx'; import { KeyCode, css, getSafeId, getSafeIdWithSymbolConversion } from '../../Util'; import { EventDispatch } from '../../Utilities/Dispatch'; import { getTabIndex } from '../../Utilities/Focus'; import { ScreenSizeConditional } from '../../Utilities/ScreenSize'; import * as React from "react"; import { ColumnJustification, IMeasurementStyle, SortOrder, TableColumnLayout, TableColumnStyle } from "./Table.Props"; import { TableBreakpoint } from "./TableBreakpoint"; /** Id used for the ColumnFill */ export const ColumnFillId = "_fill"; /** * ColumnFill is used to fill the remaining space in the parent element with an * empty column. This column can be used anywhere in the column order. Columns * that appear after this will be pushed to the right. */ export const ColumnFill = { columnLayout: TableColumnLayout.none, id: ColumnFillId, renderCell: (rowIndex, columnIndex) => { return (React.createElement("td", { key: "col-fill", "aria-colindex": columnIndex + 1, "aria-hidden": true, className: css("bolt-table-cell bolt-list-cell", "col-" + columnIndex), "data-column-index": columnIndex, role: "presentation" })); }, renderHeaderCell: (columnIndex, tableColumn) => { return (React.createElement("th", { key: "col-fill", "aria-hidden": true, className: css(tableColumn.headerClassName, "bolt-table-header-cell bolt-table-header-cell-empty", "col-header-" + columnIndex), "data-column-index": columnIndex, role: "presentation" })); }, width: -100 }; /** * The Table is a multi-column List component with an optional header. */ export class Table extends React.Component { constructor(props) { super(props); this.currentElement = React.createRef(); // Reference to the underlying list interface. this.list = React.createRef(); this.dropdownList = React.createRef(); this.onBreakpoint = () => { const visibleColumnsKey = this.props.tableBreakpoints ? getVisibleColumnsAndIndices(this.props.columns) .map(({ originalIndex }) => originalIndex) .join(",") : ""; // If any column has toggled its visibility, we have to re-render. if (this.state.renderInvisible || this.state.visibleColumnsKey !== visibleColumnsKey) { this.setState({ renderInvisible: false, visibleColumnsKey }); } }; this.onColumnsChanged = () => { this.forceUpdate(); return false; }; this.renderHeader = () => { const { columns, showHeader, renderHeader, spacerWidth } = this.props; const widths = []; let proportionalTotal = 0; // Determine the percentage for proportionally sized columns. for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { const width = ObservableLike.getValue(columns[columnIndex].width); if (width < 0) { proportionalTotal += width; } } // Compute the width of all columns based on the fixed/proportional values for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { const width = ObservableLike.getValue(columns[columnIndex].width); widths[columnIndex] = width >= 0 ? width : -((width / proportionalTotal) * 100); } let header = null; // 0 is a valid ScreenSize, so we have strict inequality checking here if (showHeader !== false) { header = renderHeader ? renderHeader(columns) : React.createElement(TableHeader, { tableColumns: columns }); } if (typeof showHeader === "function") { header = React.createElement(ScreenSizeConditional, { condition: screenSize => showHeader(screenSize) }, header); } return (React.createElement(React.Fragment, null, React.createElement("colgroup", null, React.createElement("col", { key: "col-group-left-spacer", style: { width: spacerWidth === 0 ? spacerWidth + "%" : spacerWidth + "px" } }), getVisibleColumnsAndIndices(columns).map(({ column, originalIndex }) => { const { widthStyle = IMeasurementStyle.Pixel } = column; return (React.createElement(Observer, { key: "col-group-" + originalIndex, width: { filter: this.onColumnsChanged, observableValue: column.width } }, () => (React.createElement("col", { style: { width: Math.abs(widths[originalIndex]) + (widths[originalIndex] < 0 ? "%" : widthStyle === IMeasurementStyle.Pixel ? "px" : "rem") } })))); }), React.createElement("col", { key: "col-group-right-spacer", style: { width: spacerWidth === 0 ? spacerWidth + "%" : spacerWidth + "px" } })), header)); }; this.renderLoadingRow = (rowIndex, details) => { var _a; const { columns, renderLoadingRow } = this.props; const rowDetails = { ariaBusy: true, ariaRowOffset: details.ariaRowOffset, data: details.data, eventDispatch: this.state.eventDispatch, excludeFocusZone: true, excludeTabStop: (_a = this.props.excludeTabStop) !== null && _a !== void 0 ? _a : details.excludeTabStop, itemProvider: this.props.itemProvider, listProps: details.listProps, onFocusItem: details.onFocusItem, renderSpacer: this.props.renderSpacer, selection: this.props.selection, singleClickActivation: this.props.onActivate && this.props.singleClickActivation }; // If a custom row loading animation is available use it. if (renderLoadingRow) { return renderLoadingRow(rowIndex, rowDetails); } return React.createElement(TableLoadingRow, { columns: columns, details: rowDetails, rowIndex: rowIndex }); }; this.renderRow = (rowIndex, item, details) => { var _a; const rowDetails = { selectableText: details.selectableText, ariaRowOffset: details.ariaRowOffset, eventDispatch: this.state.eventDispatch, data: details.data, excludeTabStop: (_a = this.props.excludeTabStop) !== null && _a !== void 0 ? _a : details.excludeTabStop, itemProvider: this.props.itemProvider, listProps: details.listProps, onFocusItem: details.onFocusItem, renderSpacer: this.props.renderSpacer, selection: this.props.selection, singleClickActivation: this.props.onActivate && this.props.singleClickActivation, role: this.props.role === "tree" ? "treeitem" : undefined }; // First determine if the item supplied a custom row rendering function, if not // attempt to use the global row rendering function. const renderRow = item.renderRow || this.props.renderRow; if (renderRow) { return renderRow(rowIndex, item, rowDetails); } // If no custom row renderer is available use the default row renderer. return (React.createElement(TableRow, { details: rowDetails, index: rowIndex, linkProps: item.linkProps }, renderColumns(rowIndex, this.props.columns, item, rowDetails))); }; this.state = { columnBehaviors: [], eventDispatch: props.eventDispatch || new EventDispatch(), renderInvisible: !!props.tableBreakpoints, visibleColumnsKey: "", tableBehaviors: [], tableWidth: "" }; // Initialize any column behaviors. for (let columnIndex = 0; columnIndex < props.columns.length; columnIndex++) { const tableColumn = props.columns[columnIndex]; if (tableColumn.behaviors) { for (const behavior of tableColumn.behaviors) { if (behavior && behavior.initialize) { behavior.initialize({ tableProps: props, columnIndex: columnIndex }, {}, this.state.eventDispatch); } } } } // Initialize the supplied behaviors. if (props.behaviors) { for (const behavior of props.behaviors) { if (behavior.initialize) { behavior.initialize(props, this, this.state.eventDispatch); } } } } static getDerivedStateFromProps(props, state) { const tableBehaviors = []; const columnBehaviors = []; // Build the set of behaviors columns have attached to them for (let columnIndex = 0; columnIndex < props.columns.length; columnIndex++) { const tableColumn = props.columns[columnIndex]; if (tableColumn.behaviors) { for (const behavior of tableColumn.behaviors) { columnBehaviors.push(behavior); } } } // Build the set of behaviors the table has attached to it if (props.behaviors) { tableBehaviors.splice(tableBehaviors.length, 0, ...props.behaviors); } return { columnBehaviors, tableBehaviors }; } render() { var _a; const { eventDispatch, renderInvisible, visibleColumnsKey } = this.state; const { selectableText, className, columns, containerClassName, enforceSingleSelect, excludeTabStop, focuszoneProps, showLines, id, itemProvider, maxHeight, onActivate, onFocus, onSelect, pageSize, role, rowHeight, rowHeights, scrollable, selection, singleClickActivation, selectRowOnClick, showScroll, tableBreakpoints, virtualize } = this.props; const columnWidths = []; const spacerWidth = (this.props.spacerWidth || 0) * 2; let tableMaxWidth = spacerWidth; let tableMinWidth = spacerWidth; let tableWidth = spacerWidth; let hasBoundedColumn = false; let hasUnboundedColumn = false; let fill = false; let columnCount = 0; let columnFillCount = 0; // Compute the table size based on the current column definition. Size the sizes // are observable, we need to recompute each render to ensure we have the // proper values. for (const column of columns) { const { maxWidth, minWidth, widthStyle = IMeasurementStyle.Pixel } = column; const width = ObservableLike.getValue(column.width); // Add the column width the set of available columnWidths. columnWidths.push(column.width); if (width < 0) { // This is a variable width columnn so we will fill the container. fill = true; // Update the min/max width of the table based on the supplied value. // We use a minumum width of 100px if one isnt specified. tableMinWidth += minWidth ? minWidth : 0; // If all variable width columns have a maxWidth, let the table fill the available // space, but set the table's max width to the sum of the column widths/maxWidths. if (maxWidth) { hasBoundedColumn = true; tableMaxWidth += maxWidth; } else { // If there are any variable width columns without maxWidth, let the table // fill the available space with no maxWidth. hasUnboundedColumn = true; } columnCount++; } else if (width > 0) { if (widthStyle === IMeasurementStyle.Pixel) { tableWidth += width; tableMinWidth += width; tableMaxWidth += width; } else { // @NOTE: For now we are going to estimate a rem = 16px which is the default. // We could attempt to measure this if an exact measurement is really important. tableWidth += width * 16; tableMinWidth += width * 16; tableMaxWidth += width * 16; } columnCount++; } if (column.id === "_fill") { columnFillCount++; } } const listProps = { selectableText, ariaColumnCount: columnCount - columnFillCount, ariaLabel: this.props.ariaLabel, ariaRowOffset: this.props.showHeader ? 1 : 0, className: css(className, "bolt-table", showLines && "bolt-table-show-lines"), columnCount: columnCount + 2, enforceSingleSelect, eventDispatch, excludeTabStop, focuszoneProps, id, itemProvider, maxWidth: hasBoundedColumn && !hasUnboundedColumn ? tableMaxWidth + "px" : undefined, maxHeight, minWidth: tableMinWidth !== tableWidth ? tableMinWidth + "px" : undefined, onActivate, onFocus, onSelect, pageSize, renderHeader: this.renderHeader, renderLoadingRow: this.renderLoadingRow, renderRow: this.renderRow, role, rowHeight, rowHeights, selection, selectRowOnClick, singleClickActivation, showScroll, virtualize, width: fill ? "100%" : tableWidth + "px" }; const firstActionableHeaderIndex = getActionableIndex(columns); if (firstActionableHeaderIndex >= 0) { // If the header is tabbable, the rows do not need to be since they are // in the focus zone. listProps.defaultTabbableRow = -1; } let table = (React.createElement("div", { className: css(containerClassName, "bolt-table-container flex-grow", renderInvisible && "invisible", scrollable && "v-scroll-auto", tableBreakpoints && "h-scroll-hidden"), ref: this.currentElement }, tableBreakpoints ? (React.createElement(TableBreakpoint, { columnWidths: columnWidths, onBreakpoint: this.onBreakpoint, breakpoints: tableBreakpoints })) : undefined, listProps.role === "listbox" && !((_a = listProps.className) === null || _a === void 0 ? void 0 : _a.includes("bolt-list-box-tree")) ? React.createElement(DropdownList, Object.assign({}, listProps, { key: visibleColumnsKey, ref: this.dropdownList, renderHeader: () => React.createElement(React.Fragment, null) })) : React.createElement(List, Object.assign({}, listProps, { key: visibleColumnsKey, ref: this.list })))); if (scrollable) { table = React.createElement(Intersection, null, table); } return table; } componentDidMount() { // Mount any of the attached tableBehaviors. for (const behavior of this.state.tableBehaviors) { if (behavior.componentDidMount) { behavior.componentDidMount(this.props); } } for (const behavior of this.state.columnBehaviors) { if (behavior.componentDidMount) { behavior.componentDidMount({ tableProps: this.props }); } } } componentDidUpdate() { // Update any of the attached tableBehaviors. for (const behavior of this.state.tableBehaviors) { if (behavior.componentDidUpdate) { behavior.componentDidUpdate(this.props); } } for (const behavior of this.state.columnBehaviors) { if (behavior.componentDidUpdate) { behavior.componentDidUpdate({ tableProps: this.props }); } } } componentWillUnmount() { // Unmount any of the attached tableBehaviors. for (const behavior of this.state.tableBehaviors) { if (behavior.componentWillUnmount) { behavior.componentWillUnmount(); } } for (const behavior of this.state.columnBehaviors) { if (behavior.componentDidUpdate) { behavior.componentDidUpdate({ tableProps: this.props }); } } } addOverlay(id, rowIndex, render, zIndex = 0, columnIndex) { if (this.list.current) { return this.list.current.addOverlay(id, rowIndex, render, zIndex, columnIndex); } } removeOverlay(id) { if (this.list.current) { return this.list.current.removeOverlay(id); } } focusRow(rowIndex, direction = 1) { if (this.list.current) { return this.list.current.focusRow(rowIndex, direction); } else { return Promise.resolve(); } } getFocusIndex() { if (this.list.current) { return this.list.current.getFocusIndex(); } return -1; } getStats() { if (this.list.current) { return this.list.current.getStats(); } return { firstMaterialized: -1, firstRendered: -1, lastMaterialized: -1, lastRendered: -1 }; } scrollIntoView(rowIndex, options) { if (this.dropdownList.current) { return this.dropdownList.current.scrollIntoView(rowIndex, options); } else if (this.list.current) { return this.list.current.scrollIntoView(rowIndex, options); } } } Table.defaultProps = { role: "grid", selectRowOnClick: true, showHeader: true, showLines: true, singleClickActivation: true, spacerWidth: 8 }; export function renderColumns(rowIndex, columns, item, details) { return getVisibleColumnsAndIndices(columns).map(({ column }, columnIndex) => { return column.renderCell(rowIndex, columnIndex, column, item, rowIndex + (details.ariaRowOffset ? details.ariaRowOffset : 1), details.role); }); } function getVisibleColumnsAndIndices(columns) { return columns.map((column, index) => ({ column, originalIndex: index })).filter(({ column }) => ObservableLike.getValue(column.width)); } function getActionableIndex(columns) { return columns.findIndex(column => ObservableLike.getValue(column.width) !== 0 && ((column.behaviors && column.behaviors.length > 0) || !!column.sortProps)); } class TableHeader extends React.Component { render() { const firstActionableIndex = getActionableIndex(this.props.tableColumns); return (React.createElement(FocusZoneContext.Consumer, null, rowContext => { return (React.createElement(FocusZone, { direction: FocusZoneDirection.Horizontal }, React.createElement("thead", null, React.createElement(FocusWithin, null, (focusStatus) => { return (React.createElement("tr", { "aria-rowindex": 1, className: css("bolt-table-header-row", focusStatus.hasFocus && "focused"), "data-row-index": -1, onBlur: focusStatus.onBlur, onFocus: focusStatus.onFocus, role: "row" }, React.createElement("th", { "aria-hidden": "true", key: "left-spacer", role: "presentation", className: "bolt-table-header-border" }), getVisibleColumnsAndIndices(this.props.tableColumns).map(({ column, originalIndex }, columnIndex) => { if (column.renderHeaderCell) { return column.renderHeaderCell(columnIndex, column, rowContext.focuszoneId, originalIndex === firstActionableIndex); } else if (column.iconProps || column.name) { return (React.createElement(TableHeaderCell, { key: "col-header-" + columnIndex, ariaLabel: column.ariaLabel || column.name, column: column, columnIndex: columnIndex, focuszoneId: rowContext.focuszoneId, isFirstActionableHeader: originalIndex === firstActionableIndex }, React.createElement(Tooltip, { overflowOnly: true, text: column.name }, React.createElement("div", { className: "bolt-table-header-cell-text text-ellipsis body-s" }, column.iconProps && Icon(column.iconProps), React.createElement("span", { className: css(column.headerTitleClassName, column.required && "bolt-table-header-cell-text--required") }, column.name))))); } else { return (React.createElement("th", { "aria-colindex": columnIndex + 1, "aria-label": column.ariaLabel || Resources.EmptyColumnHeaderLabel, "aria-readonly": column.readonly !== undefined ? column.readonly : "true", className: "bolt-table-header-border", key: "col-header-" + columnIndex })); } }), React.createElement("th", { "aria-hidden": "true", key: "right-spacer", role: "presentation", className: "bolt-table-header-border" }))); })))); })); } } let boltTableHeaderCellCount = 0; export class TableHeaderCell extends React.Component { constructor(props) { super(props); this.element = React.createRef(); this.sizerElement = React.createRef(); this.state = { measuredWidth: 0, isFocused: false }; this.onSize = (event, updatedSize) => { const { column } = this.props; // Ensure we havent had our column definition updated and onSize removed. if (column.onSize) { column.onSize(event, this.props.columnIndex, updatedSize, column); } }; // Registered via VSSUI external shortcuts this.moveToSizer = (e) => { var _a, _b; const { column } = this.props; const keyCombo = e.ctrlKey && e.altKey && e.key === "r"; if (column.onSize && keyCombo) { (_b = (_a = this.sizerElement.current) === null || _a === void 0 ? void 0 : _a.sizerRef.current) === null || _b === void 0 ? void 0 : _b.focus(); } return FocusZoneKeyStroke.IgnoreNone; }; this.headerCellId = boltTableHeaderCellCount++; } render() { const { ariaLabel, column, columnIndex, focuszoneId, isFirstActionableHeader, role } = this.props; let sizer; if (column.onSize) { var showDivider = !!column.showSizerDivider; sizer = (React.createElement(Observer, { width: column.width }, (props) => { // If we are sizable we will either use the supplied width (desired), or the // measured width if we are a proportional column. let width = props.width; if (width < 0) { width = this.state.measuredWidth; } return (React.createElement(Sizer, { className: "bolt-table-header-sizer", divider: showDivider, keyboardStepMultiplier: 24, maxSize: column.maxWidth, minSize: column.minWidth, onSize: this.onSize, onSizeEnd: this.props.column.onSizeEnd, orientation: Orientation.row, position: Position.near, ref: this.sizerElement, size: width, tabIndex: -1 })); })); } return (React.createElement(FocusZoneContext.Consumer, null, cellContext => { const actionable = (column.behaviors && column.behaviors.length > 0) || !!column.sortProps; const { sortProps = {} } = column; const sortIcon = column.sortProps && sortProps.sortOrder !== undefined ? Icon({ className: "bolt-table-header-sort-icon body-s", iconName: sortProps.sortOrder === SortOrder.ascending ? "SortUp" : "SortDown" }) : null; let justificationClassName; if (column.justification === ColumnJustification.Left) { justificationClassName = "justify-start"; } else if (column.justification === ColumnJustification.Right) { justificationClassName = "justify-end"; } const colIndex = columnIndex + 1; const childId = getSafeId("th-col-content-" + this.headerCellId); return (React.createElement(FocusZone, { postprocessKeyStroke: this.moveToSizer }, React.createElement("th", { id: getSafeId("th-col-" + colIndex + "-" + column.id), role: role !== null && role !== void 0 ? role : "columnheader", "aria-colindex": colIndex, "aria-label": ariaLabel, "aria-labelledby": !ariaLabel ? childId : undefined, "aria-readonly": "true", "aria-sort": sortProps.sortOrder !== undefined ? sortProps.sortOrder === SortOrder.ascending ? "ascending" : "descending" : undefined, className: css(column.headerClassName, "bolt-table-header-cell", "col-header-" + columnIndex, actionable && "bolt-table-header-cell-actionable"), "data-column-index": columnIndex, "data-focuszone": actionable && css(isFirstActionableHeader && focuszoneId, cellContext.focuszoneId), ref: this.element, onFocus: () => this.setState({ isFocused: true }), onBlur: () => this.setState({ isFocused: false }), tabIndex: actionable || sizer ? 0 : -1 }, React.createElement("div", { className: css("bolt-table-header-cell-content flex-row", justificationClassName) }, column.justification === ColumnJustification.Right && sortIcon, React.createElement("div", { id: childId, className: "scroll-hidden" }, this.props.children), column.justification !== ColumnJustification.Right && sortIcon, React.createElement("div", null, sizer))))); })); } componentDidMount() { this.updateMeasuredWidth(); } componentDidUpdate() { this.updateMeasuredWidth(); } updateMeasuredWidth() { const { column } = this.props; if (column.onSize && this.element.current && ObservableLike.getValue(column.width) < 0) { const measuredWidth = this.element.current.getBoundingClientRect().width; if (measuredWidth !== this.state.measuredWidth) { this.setState({ measuredWidth }); } } } } export function TableRow(props) { const onFocus = function (event) { var _a; props.details.onFocusItem(props.index, event); const rowNumber = (_a = props.details.ariaPosInSet) !== null && _a !== void 0 ? _a : props.index + ariaRowOffset; if (props.details.ariaSetSize) { if (props.linkProps) { Utils_Accessibility.announce(format(Resources.ClickableRowAnnouncementWithSize, rowNumber, props.details.ariaSetSize), true); } else if (role == "option") { Utils_Accessibility.announce(Resources.ListItem, true); } else { Utils_Accessibility.announce(format(Resources.RowAnnouncementWithSize, rowNumber, props.details.ariaSetSize), true); } } else { if (props.linkProps) { Utils_Accessibility.announce(format(Resources.ClickableRowAnnouncement, rowNumber), true); } else { Utils_Accessibility.announce(format(Resources.RowAnnouncement, rowNumber), true); } } }; const postprocessKeyStroke = function (event) { const nodeName = event.target.nodeName; if (!event.defaultPrevented && nodeName !== "INPUT" && nodeName !== "TEXTAREA") { if (event.which === KeyCode.leftArrow && rowElement.current) { rowElement.current.focus(); event.preventDefault(); } } return FocusZoneKeyStroke.IgnoreNone; }; const [rowElement] = React.useState(() => React.createRef()); const { details, index, linkProps } = props; const { selectableText, ariaLabel, ariaBusy, ariaDescribedBy, ariaPosInSet, ariaRowOffset, ariaSetSize, excludeFocusZone, id, renderSpacer, role, selection, singleClickActivation } = details; let ariaChecked; let ariaSelected; if (role === "menuitemcheckbox") { ariaChecked = selection && selection.selected(index); } else { ariaSelected = selection && selection.selected(index); } const rowElem = (React.createElement(FocusOrMouseWithin, { onFocus: onFocus }, (focusOrMouseWithinStatus) => { return (React.createElement(FocusZoneContext.Consumer, null, rowContext => { var _a, _b, _c; const rowProps = { "aria-busy": ariaBusy, "aria-checked": ariaChecked, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, "aria-rowindex": role === "menuitemcheckbox" || role === "option" || role === "presentation" ? undefined : index + ariaRowOffset, "aria-posinset": ariaPosInSet === null ? undefined : ariaPosInSet, "aria-selected": role === "presentation" ? undefined : ariaSelected, "aria-setsize": ariaSetSize === null ? undefined : ariaSetSize, className: css(props.className, "bolt-table-row bolt-list-row", index === 0 && "first-row", focusOrMouseWithinStatus.hasFocus && "focused", selection && selection.selected(index) && "selected", singleClickActivation && "single-click-activation", linkProps && "v-align-middle", selectableText && "selectable-text", ((_a = props === null || props === void 0 ? void 0 : props.className) === null || _a === void 0 ? void 0 : _a.includes("bolt-list-box-row")) && "dropdown-list"), "data-focuszone": excludeFocusZone || (selection && !selection.selectable(index)) ? undefined : rowContext.focuszoneId, "data-row-index": props.index, id: getSafeIdWithSymbolConversion(id), role: role || "row", tabIndex: getTabIndex(details), onBlur: focusOrMouseWithinStatus.onBlur, onFocus: focusOrMouseWithinStatus.onFocus, onMouseEnter: focusOrMouseWithinStatus.onMouseEnter, onMouseLeave: focusOrMouseWithinStatus.onMouseLeave, ref: rowElement }; let rowChildren = []; if ((_b = rowProps === null || rowProps === void 0 ? void 0 : rowProps.className) === null || _b === void 0 ? void 0 : _b.includes("dropdown-list")) { rowChildren = [props.children]; } else { rowChildren = [ React.createElement("td", { "aria-hidden": "true", key: "left-spacer", className: "bolt-table-cell-compact bolt-table-cell bolt-list-cell bolt-table-spacer-cell", role: "presentation" }, renderSpacer && renderSpacer(index, true)), props.children, React.createElement("td", { "aria-hidden": "true", key: "right-spacer", className: "bolt-table-cell-compact bolt-table-cell bolt-list-cell bolt-table-spacer-cell", role: "presentation" }, renderSpacer && renderSpacer(index, false)) ]; } return (React.createElement(FocusZone, { direction: FocusZoneDirection.Horizontal, postprocessKeyStroke: postprocessKeyStroke }, linkProps ? (React.createElement("a", Object.assign({}, getDefaultLinkProps(linkProps), rowProps), rowChildren)) : (((_c = rowProps === null || rowProps === void 0 ? void 0 : rowProps.className) === null || _c === void 0 ? void 0 : _c.includes("dropdown-list")) ? (React.createElement("li", Object.assign({}, rowProps), rowChildren)) : (React.createElement("tr", Object.assign({}, rowProps), rowChildren))))); })); })); if (details.tooltipProps) { return React.createElement(Tooltip, Object.assign({}, details.tooltipProps), rowElem); } return rowElem; } export function TableLoadingRow(props) { return ( // Return the default row loading animation. React.createElement(TableRow, { className: "bolt-list-row-loading", details: props.details, index: props.rowIndex }, getVisibleColumnsAndIndices(props.columns).map(({ column }, columnIndex) => { return SimpleTableCell({ columnIndex, children: renderLoadingCell(column.columnLayout) }); }))); } export function TableCell(props) { var _a, _b, _c; const { ariaLabel, ariaRowIndex, className, colspan, columnIndex, role, tableColumn } = props; let justificationClassName; if (tableColumn) { if (tableColumn.justification === ColumnJustification.Left) { justificationClassName = "justify-cell-start"; } else if (tableColumn.justification === ColumnJustification.Right) { justificationClassName = "justify-cell-end"; } } return (((_b = (_a = props.tableColumn) === null || _a === void 0 ? void 0 : _a.className) === null || _b === void 0 ? void 0 : _b.includes("dropdown-list")) || ((_c = props.className) === null || _c === void 0 ? void 0 : _c.includes("dropdown-list")) ? (React.createElement("span", { "aria-colindex": role === "presentation" || role === "treeitem" ? undefined : columnIndex + 1, "aria-label": role === "presentation" || role === "treeitem" ? undefined : ariaLabel, "aria-readonly": role === "presentation" || role === "treeitem" ? undefined : tableColumn && tableColumn.readonly, "aria-rowindex": role === "presentation" || role === "treeitem" ? undefined : ariaRowIndex, className: css(className, tableColumn && tableColumn.className, "bolt-table-cell bolt-list-cell", justificationClassName), "data-column-index": columnIndex, key: "col-" + columnIndex, role: role === "treeitem" ? "presentation" : role || "gridcell" }, props.children)) : (React.createElement("td", { "aria-colindex": role === "presentation" || role === "treeitem" ? undefined : columnIndex + 1, "aria-label": role === "presentation" || role === "treeitem" ? undefined : ariaLabel, "aria-readonly": role === "presentation" || role === "treeitem" ? undefined : tableColumn && tableColumn.readonly, "aria-rowindex": role === "presentation" || role === "treeitem" ? undefined : ariaRowIndex, className: css(className, tableColumn && tableColumn.className, "bolt-table-cell bolt-list-cell", justificationClassName), colSpan: colspan, "data-column-index": columnIndex, key: "col-" + columnIndex, role: role === "treeitem" ? "presentation" : role || "gridcell" }, props.children))); } export function SimpleTableCell(props) { const children = React.createElement("div", { className: css(props.contentClassName, "bolt-table-cell-content flex-row flex-center") }, props.children); return TableCell({ ariaLabel: props.ariaLabel, ariaRowIndex: props.ariaRowIndex, children: children, className: props.className, colspan: props.colspan, columnIndex: props.columnIndex, role: props.role, tableColumn: props.tableColumn }); } export function TwoLineTableCell(props) { const rowClasses = css(props.rowClassName, "bolt-table-two-line-cell-item flex-row scroll-hidden"); const line1 = (React.createElement("div", { className: rowClasses }, props.line1, props.trailingLine1IconProps && Icon(Object.assign(Object.assign({}, props.trailingLine1IconProps), { className: css(props.trailingLine1IconProps.className, "flex-noshrink") })))); const line2 = React.createElement("div", { className: rowClasses }, props.line2); const lines = (React.createElement(React.Fragment, null, line1, line2)); let children; if (props.iconProps) { children = (React.createElement("div", { className: css(props.className, "bolt-table-cell-content flex-row flex-center") }, Icon(Object.assign(Object.assign({}, props.iconProps), { className: css(props.iconProps.className, "bolt-table-two-line-cell-icon flex-noshrink") })), React.createElement("div", { className: "flex-column scroll-hidden" }, lines), props.trailingIconProps && Icon(Object.assign(Object.assign({}, props.trailingIconProps), { className: css(props.trailingIconProps.className, "bolt-table-two-line-cell-icon flex-noshrink") })))); } else { children = (React.createElement("div", { className: css(props.className, "bolt-table-cell-content flex-column") }, lines, props.trailingIconProps && Icon(Object.assign(Object.assign({}, props.trailingIconProps), { className: css(props.trailingIconProps.className, "bolt-table-two-line-cell-icon flex-noshrink") })))); } return TableCell({ ariaRowIndex: props.ariaRowIndex, children: children, colspan: props.colspan, columnIndex: props.columnIndex, className: css(props.tableCellClassName, "bolt-table-two-line-cell"), tableColumn: props.tableColumn }); } export function renderEmptyCell(rowIndex, columnIndex) { return React.createElement(TableCell, { columnIndex: columnIndex, key: columnIndex, role: "presentation" }); } /** * A basic cell renderer that works well for most simple columns. Gets the value of the * the {column.id} property in the given table item and displays it as a string * * @param rowIndex Index of the row being rendered * @param columnIndex Index of the column being rendered * @param tableColumn Column definition * @param tableItem The data item being rendered for the current row */ export function renderSimpleCell(rowIndex, columnIndex, tableColumn, tableItem, ariaRowIndex) { return renderSimpleCellValue(columnIndex, tableColumn, tableItem[tableColumn.id], ariaRowIndex); } /** * Renders a simple table cell value * * @param columnIndex Index of the column being rendered * @param tableColumn Column definition * @param tableCell Simple value to render as text */ export function renderSimpleCellValue(columnIndex, tableColumn, tableCell, ariaRowIndex) { const { columnStyle } = tableColumn; // Do not include padding if the table cell has an href const hasLink = !!(tableCell && typeof tableCell !== "string" && typeof tableCell !== "number" && tableCell.href); return (React.createElement(SimpleTableCell, { ariaRowIndex: ariaRowIndex, className: css(columnStyle === TableColumnStyle.Primary && "bolt-table-cell-primary", columnStyle === TableColumnStyle.Secondary && "bolt-table-cell-secondary", columnStyle === TableColumnStyle.Tertiary && "bolt-table-cell-tertiary"), columnIndex: columnIndex, contentClassName: hasLink ? "bolt-table-cell-content-with-link" : undefined, key: columnIndex, tableColumn: tableColumn }, tableCell && renderListCell(tableCell))); } function getVariableLength() { return Math.random() * 80 + 20 + "%"; } export function renderLoadingCell(columnLayout) { if (columnLayout === TableColumnLayout.singleLine || columnLayout === undefined) { return (React.createElement("div", { className: "shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0")); } else if (columnLayout === TableColumnLayout.singleLinePrefix) { return (React.createElement(React.Fragment, null, React.createElement("div", { className: "shimmer shimmer-circle-small flex-noshrink" }), React.createElement("div", { className: "shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0"))); } else if (columnLayout === TableColumnLayout.twoLine) { return (React.createElement("div", { className: "flex-column flex-grow" }, React.createElement("div", { className: "bolt-table-two-line-cell-item shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0"), React.createElement("div", { className: "bolt-table-two-line-cell-item shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0"))); } else if (columnLayout === TableColumnLayout.twoLinePrefix) { return (React.createElement(React.Fragment, null, React.createElement("div", { className: "shimmer shimmer-circle-large flex-noshrink" }), React.createElement("div", { className: "flex-column flex-grow" }, React.createElement("div", { className: "bolt-table-two-line-cell-item shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0"), React.createElement("div", { className: "bolt-table-two-line-cell-item shimmer shimmer-line", style: { width: getVariableLength() } }, "\u00A0")))); } return null; }