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