UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

279 lines (278 loc) 17.6 kB
import { __assign, __extends } from "tslib"; import "../../CommonImports"; import "../../Core/core.css"; import "./Tree.css"; import "./TreeExpand.css"; import * as React from "react"; import { ObservableLike } from '../../Core/Observable'; import { FocusWithin } from '../../FocusWithin'; import { FocusZone, FocusZoneContext, FocusZoneDirection, FocusZoneKeyStroke } from '../../FocusZone'; import { getDefaultAnchorProps } from '../../Link'; import { renderListCell } from '../../List'; import { UncheckedObserver } from '../../Observer'; import { renderColumns, renderLoadingCell, SimpleTableCell, Table } from '../../Table'; import { css, getSafeId, KeyCode, preventDefault } from '../../Util'; import { getTabIndex } from '../../Utilities/Focus'; import { TreeExpand } from "./TreeExpand"; var Tree = /** @class */ (function (_super) { __extends(Tree, _super); function Tree() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.table = React.createRef(); _this.onActivateExpand = function (event, tableRow) { if (!event.defaultPrevented && tableRow.data.underlyingItem.childItems) { _this.props.onToggle && _this.props.onToggle(event, tableRow.data); event.preventDefault(); } }; _this.renderRow = function (rowIndex, item, details) { // If onActivate for tree is not specified but onToggle is, tree passes on onActivateExpand as onActivate to table. // In that case, onActivate can be different for tree and table details.singleClickActivation = _this.props.onActivate && details.singleClickActivation; if (_this.props.columns.length <= 1 && !details.role) { details.role = "treeitem"; } // Since the underlying table is unable to determine whether or not a row // is in a loading state since the observable value is within the ITreeItemEx // we need to handle this in the row rendering. return (React.createElement(UncheckedObserver, { data: item.underlyingItem.data, key: item.underlyingItem.id }, function (props) { if (props.data) { // We need to forward the onToggle handler to the treeItemEx before it is rendered. var canToggle = item.underlyingItem.childItems && item.underlyingItem.childItems.length !== 0; // edge case: if row is a pipeline folder, we just know childItems exist, but not the length var anyData = props.data; if (anyData.folder) { canToggle = item.underlyingItem.childItems !== undefined; } item.onToggle = canToggle ? _this.props.onToggle : undefined; // First determine if the item supplied a custom row rendering function, if not // attempt to use the global row rendering function. var renderRow = item.underlyingItem.data.renderRow || _this.props.renderRow; if (renderRow) { return renderRow(rowIndex, item, details); } return renderTreeRow(rowIndex, item, details, _this.props.columns, props.data); } else { var renderLoadingRow = _this.props.renderLoadingRow; // If a custom row loading animation is available use it. if (renderLoadingRow) { return renderLoadingRow(rowIndex, details); } // Return the default row loading animation. return (React.createElement(TreeRow, { index: rowIndex, details: details }, _this.props.columns.map(function (treeColumn, columnIndex) { var children = renderLoadingCell(treeColumn.columnLayout); if (treeColumn.hierarchical) { children = React.createElement(TreeExpand, { depth: details.data.depth }, children); } return SimpleTableCell({ className: "bolt-tree-cell", columnIndex: columnIndex, children: children }); }))); } })); }; return _this; } Tree.prototype.render = function () { var _a = this.props.role, role = _a === void 0 ? this.props.role ? this.props.role : this.props.columns.length > 1 ? "treegrid" : "tree" : _a; // If we haven't specified an onActivate, but have specified an onToggle, toggle on activate. var onActivate = this.props.onActivate ? this.props.onActivate : this.props.onToggle ? this.onActivateExpand : undefined; return (React.createElement(Table, { ariaLabel: this.props.ariaLabel, behaviors: this.props.behaviors, className: this.props.className, columns: this.props.columns, containerClassName: this.props.containerClassName, eventDispatch: this.props.eventDispatch, focuszoneProps: this.props.focuszoneProps, id: this.props.id, itemProvider: this.props.itemProvider, maxHeight: this.props.maxHeight, onActivate: onActivate, onFocus: this.props.onFocus, onSelect: this.props.onSelect, pageSize: this.props.pageSize, renderHeader: this.props.renderHeader, renderRow: this.renderRow, renderSpacer: this.props.renderSpacer, role: role, rowHeight: this.props.rowHeight, ref: this.table, scrollable: this.props.scrollable, selectableText: this.props.selectableText, selection: this.props.selection, singleClickActivation: this.props.singleClickActivation, showHeader: this.props.showHeader, showLines: this.props.showLines, showScroll: this.props.showScroll, tableBreakpoints: this.props.tableBreakpoints, virtualize: this.props.virtualize })); }; Tree.prototype.addOverlay = function (id, rowIndex, render, zIndex) { if (zIndex === void 0) { zIndex = 0; } if (this.table.current) { return this.table.current.addOverlay(id, rowIndex, render, zIndex); } }; Tree.prototype.removeOverlay = function (id) { if (this.table.current) { return this.table.current.removeOverlay(id); } }; Tree.prototype.focusRow = function (rowIndex, direction) { if (direction === void 0) { direction = 1; } if (this.table.current) { return this.table.current.focusRow(rowIndex, direction); } else { return Promise.resolve(); } }; Tree.prototype.getFocusIndex = function () { if (this.table.current) { return this.table.current.getFocusIndex(); } return -1; }; Tree.prototype.getStats = function () { if (this.table.current) { return this.table.current.getStats(); } return { firstMaterialized: -1, firstRendered: -1, lastMaterialized: -1, lastRendered: -1 }; }; Tree.prototype.scrollIntoView = function (rowIndex, options) { if (this.table.current) { return this.table.current.scrollIntoView(rowIndex, options); } }; return Tree; }(React.Component)); export { Tree }; var TreeRow = /** @class */ (function (_super) { __extends(TreeRow, _super); function TreeRow() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.rowElement = React.createRef(); _this.onFocus = function (event) { _this.props.details.onFocusItem(_this.props.index, event); }; _this.onKeyDown = function (event) { if (!event.defaultPrevented) { if (_this.rowElement.current === event.nativeEvent.srcElement) { var data = _this.props.details.data; if (data) { if (data.onToggle) { var expanded = data.underlyingItem.expanded; if ((event.which === KeyCode.rightArrow && !expanded) || (event.which === KeyCode.leftArrow && expanded)) { data.onToggle(event, data); event.preventDefault(); } } } } } }; _this.onPostprocessKeyStroke = function (event) { var _a; if (event.defaultPrevented) return FocusZoneKeyStroke.IgnoreNone; if (event.which !== KeyCode.leftArrow) return FocusZoneKeyStroke.IgnoreNone; var currentElement = _this.rowElement.current; if (event.nativeEvent.srcElement !== currentElement) { currentElement === null || currentElement === void 0 ? void 0 : currentElement.focus(); return FocusZoneKeyStroke.IgnoreNone; } var data = _this.props.details.data; if (!data || !data.parentItem) return FocusZoneKeyStroke.IgnoreNone; var prevElement = currentElement === null || currentElement === void 0 ? void 0 : currentElement.previousElementSibling; var currentElementAriaLevel = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('aria-level'); var prevElementAriaLevel = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getAttribute('aria-level'); while (prevElement && currentElement && currentElementAriaLevel && prevElementAriaLevel) { var currentLevel = Number.parseInt(currentElementAriaLevel); var prevElementLevel = Number.parseInt(prevElementAriaLevel); if (prevElementLevel < currentLevel) break; prevElement = prevElement.previousElementSibling; } if (data.parentItem.onToggle && (prevElement === null || prevElement === void 0 ? void 0 : prevElement.id)) { (_a = document.getElementById(prevElement.id)) === null || _a === void 0 ? void 0 : _a.focus(); data.parentItem.onToggle(event, data.parentItem); } event.preventDefault(); return FocusZoneKeyStroke.IgnoreNone; }; return _this; } TreeRow.prototype.render = function () { var _this = this; var _a = this.props, details = _a.details, index = _a.index, linkProps = _a.linkProps; var ariaRowOffset = details.ariaRowOffset, data = details.data, excludeFocusZone = details.excludeFocusZone, renderSpacer = details.renderSpacer, selectableText = details.selectableText, selection = details.selection, singleClickActivation = details.singleClickActivation; // If the row is being rendered as a link we will use an anchor, otherwise we will // use a standard table row. var RowType = linkProps ? "a" : "tr"; // Build the set of props needed from the link to forward on to the row element. var linkForwardProps = getDefaultAnchorProps(linkProps); return (React.createElement(FocusWithin, { onFocus: this.onFocus }, function (focusStatus) { return (React.createElement(FocusZoneContext.Consumer, null, function (rowContext) { var _a; return (React.createElement(FocusZone, { direction: FocusZoneDirection.Horizontal, postprocessKeyStroke: _this.onPostprocessKeyStroke }, React.createElement(RowType, __assign({}, linkForwardProps, { "aria-busy": data === undefined, "aria-current": details.ariaCurrent ? details.ariaCurrent : undefined, "aria-expanded": // default to false if the item has children without an expanded value data && data.underlyingItem.childItems ? data.underlyingItem.expanded === undefined ? false : data.underlyingItem.expanded : undefined, "aria-level": data ? data.depth + 1 : undefined, "aria-rowindex": (details === null || details === void 0 ? void 0 : details.role) === "treeitem" ? undefined : index + ariaRowOffset, "aria-selected": selection && selection.selected(index) ? true : undefined, className: css(_this.props.className, "bolt-tree-row bolt-table-row bolt-list-row", index === 0 && "first-row", focusStatus.hasFocus && "focused", selection && selection.selected(index) && "selected", singleClickActivation && "single-click-activation", selectableText && "selectable-text", linkProps && "v-align-middle"), "data-focuszone": excludeFocusZone || (selection && !selection.selectable(index)) ? undefined : rowContext.focuszoneId, "data-row-index": index, id: getSafeId((_a = details.data.underlyingItem.id) !== null && _a !== void 0 ? _a : index.toString()), onBlur: focusStatus.onBlur, onFocus: focusStatus.onFocus, onKeyDown: _this.onKeyDown, ref: _this.rowElement, role: details.role || "row", tabIndex: getTabIndex(details) }), React.createElement("td", { key: "left-spacer", className: "bolt-table-cell-compact bolt-table-cell bolt-list-cell", role: "presentation" }, renderSpacer && renderSpacer(index, true)), _this.props.children, React.createElement("td", { key: "right-spacer", className: "bolt-table-cell-compact bolt-table-cell bolt-list-cell", role: "presentation" }, renderSpacer && renderSpacer(index, false))))); })); })); }; return TreeRow; }(React.Component)); export { TreeRow }; export function renderTreeRow(rowIndex, item, details, columns, data, className, key) { return (React.createElement(TreeRow, { index: rowIndex, details: details, linkProps: data ? data.linkProps : undefined, className: className, key: key }, renderColumns(rowIndex, columns, item, details))); } /** * Standard cell renderer for a tree cell with expandable children. This will use the tree items * state to determine whether or not the row is expanded etc. */ export function ExpandableTreeCell(props) { var colspan = props.colspan, columnIndex = props.columnIndex, contentClassName = props.contentClassName, treeItem = props.treeItem, treeColumn = props.treeColumn, role = props.role; var depth = treeItem.depth, onToggle = treeItem.onToggle, underlyingItem = treeItem.underlyingItem; var expanded = underlyingItem.expanded; var children = (React.createElement(TreeExpand, { expanded: expanded, depth: depth, indentationSize: treeColumn && treeColumn.indentationSize, onClick: preventDefault, onToggle: onToggle ? function (event) { return onToggle(event, treeItem); } : undefined }, props.children)); return SimpleTableCell({ children: children, className: css(props.className, "bolt-tree-cell"), colspan: colspan, columnIndex: columnIndex, contentClassName: contentClassName, tableColumn: treeColumn, role: role }); } export function renderExpandableTreeCell(rowIndex, columnIndex, treeColumn, treeItem, ariaRowIndex, role) { var underlyingItem = treeItem.underlyingItem; var data = ObservableLike.getValue(underlyingItem.data); var treeCell = data && data[treeColumn.id]; // Do not include padding if the table cell has an href var hasLink = !!(treeCell && typeof treeCell !== "string" && typeof treeCell !== "number" && treeCell.href); return ExpandableTreeCell({ children: treeCell && renderListCell(treeCell), className: treeColumn.className, columnIndex: columnIndex, contentClassName: hasLink ? "bolt-table-cell-content-with-link" : undefined, treeItem: treeItem, treeColumn: treeColumn, role: role }); } export function renderTreeCell(rowIndex, columnIndex, treeColumn, treeItem, ariaRowIndex, role) { var underlyingItem = treeItem.underlyingItem; var data = ObservableLike.getValue(underlyingItem.data); var treeCell = data && data[treeColumn.id]; // Do not include padding if the table cell has an href var hasLink = !!(treeCell && typeof treeCell !== "string" && typeof treeCell !== "number" && treeCell.href); return SimpleTableCell({ className: treeColumn.className, children: treeCell && renderListCell(treeCell), columnIndex: columnIndex, contentClassName: hasLink ? "bolt-table-cell-content-with-link" : undefined, tableColumn: treeColumn, role: role }); } export function renderTreeCellWithClassName(rowIndex, columnIndex, treeColumn, treeItem, contentClassName) { var underlyingItem = treeItem.underlyingItem; var data = ObservableLike.getValue(underlyingItem.data); var treeCell = data && data[treeColumn.id]; // Do not include padding if the table cell has an href var hasLink = !!(treeCell && typeof treeCell !== "string" && typeof treeCell !== "number" && treeCell.href); return SimpleTableCell({ className: treeColumn.className, children: treeCell && renderListCell(treeCell), columnIndex: columnIndex, contentClassName: css(contentClassName, hasLink ? "bolt-table-cell-content-with-link" : undefined), tableColumn: treeColumn }); }