UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

149 lines (148 loc) 6.59 kB
import { __assign, __extends } from "tslib"; import "../../CommonImports"; import "../../Core/core.css"; import * as React from "react"; var FocusWithinContext = React.createContext({}); var FocusWithin = /** @class */ (function (_super) { __extends(FocusWithin, _super); function FocusWithin() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.blurTimeout = -1; _this.focusCount = 0; _this.focus = false; /** * onBlur method that should be attached to the onBlur handler of the * continers root element. */ _this.onBlur = function () { // Don't let the focus count go below 0. // We have seen cases where we get a blur event, even when we // do not have focus. _this.focusCount = Math.max(0, _this.focusCount - 1); // Clear any previous timeout if we somehow got a second blur event before // ever processing the timeout from the first one. if (_this.blurTimeout !== -1) { window.clearTimeout(_this.blurTimeout); } // We must delay the blur processing for two basic reasons: // 1) If focus is transitioning to a child element we will fire a Blur // followed quickly by a Focus even though focus never left the element. // This causes problems for things like menus that close on loss of focus. // 2) IE 11 fires the blur before the focus (no other browser does this) // and this causes the same issue above but also causes focusCount // inconsistencies. _this.blurTimeout = window.setTimeout(function () { _this.blurTimeout = -1; if (!_this.focusCount) { _this.focus = false; // If we are tracking the focus state we will force a component update. if (_this.props.updateStateOnFocusChange) { _this.forceUpdate(); } if (_this.props.onBlur) { _this.props.onBlur(); } } }, 0); }; /** * onFocus method that should be attached to the onFocus handler of the * continer's root element. */ _this.onFocus = function (event) { _this.focusCount++; // If focus is just entering one of the child components and not just moving // one child to another we will call the onFocus delegate if supplied. if (!_this.focus) { _this.focus = true; // If we are tracking the focus state we will force a component update. if (_this.props.updateStateOnFocusChange) { _this.forceUpdate(); } if (_this.props.onFocus) { _this.props.onFocus(event); } } }; return _this; } FocusWithin.prototype.render = function () { var _this = this; return (React.createElement(FocusWithinContext.Consumer, null, function (focusWithinContext) { var children; var newProps = { onBlur: _this.onBlur, onFocus: _this.onFocus }; // Save ou parent focus within for potential communication. _this.parentFocusWithin = focusWithinContext.focusWithin; if (typeof _this.props.children === "function") { var child = _this.props.children; // For functional components we pass the hasFocus attribute as well. newProps.hasFocus = _this.focus; children = child(newProps); } else { var child = React.Children.only(_this.props.children); children = React.cloneElement(child, __assign(__assign({}, child.props), newProps), child.props.children); } return React.createElement(FocusWithinContext.Provider, { value: { focusWithin: _this } }, children); })); }; /** * componentWillUnmount is used to cleanup the component state. * * @NOTE: The main thing we need to deal with is when this component is unmounted * while it has focus. We need to get this FocusWithin and all of its parents state * updated since focus will move directly to the body without a blur event. */ FocusWithin.prototype.componentWillUnmount = function () { if (this.blurTimeout !== -1) { window.clearTimeout(this.blurTimeout); this.blurTimeout = -1; } if (this.focusCount > 0) { this.unmountWithFocus(false); } }; /** * hasFocus returns true if the focus is contained within the focus component * hierarchy. This includes portals, the element may or may not * be a direct descendant of the focus component in the DOM structure. */ FocusWithin.prototype.hasFocus = function () { return this.focusCount > 0; }; /** * When the focusWithin unmounts we need to determine if we currently have focus. * If we do, focus will be moved silently to the body. We need to cleanup the * focusWithin's that are affected by this silent change. */ FocusWithin.prototype.unmountWithFocus = function (fromParent) { if (this.focusCount > 0) { this.focusCount--; if (this.focusCount > 0) { // If we are tracking the focus state we will force a component update. if (fromParent) { this.focusCount = 0; this.focus = false; if (this.props.updateStateOnFocusChange) { this.forceUpdate(); } if (this.props.onBlur) { this.props.onBlur(); } } } // Notify the parent focus within that the mounted focus component is unmounting. if (this.parentFocusWithin) { this.parentFocusWithin.unmountWithFocus(true); } } }; FocusWithin.defaultProps = { updateStateOnFocusChange: true }; return FocusWithin; }(React.Component)); export { FocusWithin };