azure-devops-ui
Version:
React components for building web UI in Azure DevOps
743 lines (742 loc) • 43.1 kB
JavaScript
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;
}