UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

137 lines (136 loc) 6.75 kB
import "../../CommonImports"; import "../../Core/core.css"; import * as React from "react"; import { FocusGroupContext } from '../../FocusGroup'; import { FocusZoneContext } from '../../FocusZone'; import { MouseWithin } from '../../MouseWithin'; import { css, getSafeId, KeyCode, setFocusVisible } from '../../Util'; import { getTabIndex } from '../../Utilities/Focus'; export class Expandable extends React.Component { constructor() { super(...arguments); this.state = { expanded: false }; this.collapse = () => { if (this.state.expanded) { this.setState({ expanded: false }); if (this.props.onCollapse) { this.props.onCollapse(); } } }; this.expand = () => { if (!this.state.expanded) { this.setState({ expanded: !this.state.expanded }); if (this.props.onExpand) { this.props.onExpand(); } } }; this.onClick = (event) => { if (!this.props.disabled) { if (!event.defaultPrevented) { if (!this.state.expanded && !this.ignoreClick) { this.expand(); event.preventDefault(); } } } else { event.preventDefault(); } }; this.onKeyDown = (event) => { if (!event.defaultPrevented) { // If the control key is pressed we want to navigate in a focus zone and not open the menu if (event.ctrlKey) { return; } if (!this.state.expanded && this.isExpandKey(event)) { this.ignoreClick = false; this.expand(); event.preventDefault(); } } }; this.onMouseDown = () => { // If the callout is expanded when we click on it, we want to ignore the click // and let the blur occur and close the callout. this.ignoreClick = this.state.expanded; }; } render() { const { expanded } = this.state; const child = this.props.children; return (React.createElement(React.Fragment, null, child({ expanded, onClick: this.onClick, onKeyDown: this.onKeyDown, onMouseDown: this.onMouseDown }), expanded && this.props.renderCallout && this.props.renderCallout())); } isExpandKey(event) { return (Array.isArray(this.props.expandKey) && this.props.expandKey.indexOf(event.which) !== -1) || event.which === this.props.expandKey; } } Expandable.defaultProps = { expandKey: KeyCode.downArrow }; let expandableContainerId = 1; /** * ExpandableContainer is a specialized form of Expandable and generally shouldn't be used. * It's main goal is to provider mouse enter/leave behavior for collapsing. * * @NOTE: This component MAY be deprecated in the future, use <Expandable /> instead. */ export class ExpandableContainer extends React.Component { constructor() { super(...arguments); this.element = React.createRef(); this.expandedOnHover = false; this.expandable = React.createRef(); this.expandableId = "expandableContainer-" + expandableContainerId++; this.collapse = () => { if (this.expandable.current) { this.expandable.current.collapse(); } }; this.expand = () => { if (this.expandable.current) { this.expandable.current.expand(); } }; this.onFocus = (event) => { // Only call onFocus for the FocusGroup is the expandable itself is getting focus. // Otherwise it is going to a child element and it should manage focus appropriately. if (this.props.id && (!this.element.current || this.element.current === event.target)) { this.context.onFocus(this.props.id); } }; this.onMouseEnter = () => { if (this.props.expandOnHover) { this.expandedOnHover = true; setFocusVisible(false); this.expand(); } }; this.onMouseLeave = () => { if (this.expandedOnHover) { this.collapse(); } }; this.renderCallout = () => { return this.props.renderCallout(this.expandable.current, this.expandableId, this.element.current); }; } render() { return (React.createElement(FocusZoneContext.Consumer, null, zoneContext => (React.createElement(MouseWithin, { enterDelay: this.props.expandDelay, leaveDelay: this.props.collapseDelay, onMouseEnter: this.onMouseEnter, onMouseLeave: this.onMouseLeave, updateStateOnMouseChange: false }, (mouseContext) => (React.createElement(Expandable, Object.assign({}, this.props, { renderCallout: undefined, ref: this.expandable }), (expandableProps) => (React.createElement("div", { "aria-controls": this.props.ariaControls === undefined ? (expandableProps.expanded ? getSafeId(this.props.expandableId) : undefined) : this.props.ariaControls ? this.props.ariaControls : undefined, "aria-expanded": this.props.ariaExpanded === undefined ? expandableProps.expanded : this.props.ariaExpanded === null ? undefined : this.props.ariaExpanded, "aria-haspopup": this.props.ariaHasPopup === undefined ? true : this.props.ariaHasPopup === null ? undefined : this.props.ariaHasPopup, "aria-label": this.props.ariaLabel, className: css(this.props.className, "bolt-expandable-container flex-row flex-center", expandableProps.expanded && "expanded"), "data-focuszone": !this.props.disabled && !this.props.excludeFocusZone ? zoneContext.focuszoneId : undefined, id: getSafeId(this.props.id), onClick: expandableProps.onClick, onFocus: this.onFocus, onKeyDown: expandableProps.onKeyDown, onMouseDown: expandableProps.onMouseDown, onMouseEnter: mouseContext.onMouseEnter, onMouseLeave: mouseContext.onMouseLeave, role: this.props.role || "button", tabIndex: getTabIndex(this.props, this.context), ref: this.element }, this.props.children, expandableProps.expanded && this.renderCallout())))))))); } } ExpandableContainer.contextType = FocusGroupContext; ExpandableContainer.defaultProps = { collapseDelay: 250, expandDelay: 250, expandKey: [KeyCode.downArrow, KeyCode.enter] };