Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions src/web/CustomScrollbar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Scrollbar.ts
/**
* CustomScrollbar.ts
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT license.
Expand All @@ -14,7 +14,6 @@ import React = require('react');
var UNIT = 'px';
var SCROLLER_MIN_SIZE = 15;
var SCROLLER_NEGATIVE_MARGIN = 30;
var LTR_OVERRIDE_CLASS = 'ltroverride';
var NEUTRAL_OVERRIDE_CLASS = 'neutraloverride';

interface IScrollbarInfo {
Expand Down Expand Up @@ -173,6 +172,7 @@ export class Scrollbar {
private _handleWheelCallback = this._handleWheel.bind(this);
private _handleMouseDownCallback = this._handleMouseDown.bind(this);
private _updateCallback = this.update.bind(this);
private _asyncInitTimer: number|undefined;

static getNativeScrollbarWidth() {
// Have we cached the value alread?
Expand Down Expand Up @@ -253,12 +253,8 @@ export class Scrollbar {
isNeutral = isLeftBound && isRightBound;

this._container.classList.remove(NEUTRAL_OVERRIDE_CLASS);
this._container.classList.remove(LTR_OVERRIDE_CLASS);

if (isNeutral) {
this._container.classList.add(NEUTRAL_OVERRIDE_CLASS);
} else if (isLeftBound) {
this._container.classList.add(LTR_OVERRIDE_CLASS);
}

rtlbox.innerHTML = '';
Expand Down Expand Up @@ -511,14 +507,23 @@ export class Scrollbar {
}
}
Scrollbar._installStyleSheet();
this._tryLtrOverride();
this._addScrollbars();
this.show();
this.update();
this._container.addEventListener('mouseenter', this._updateCallback);

// Defer remaining init work to avoid triggering sync layout
this._asyncInitTimer = window.setTimeout(() => {
this._asyncInitTimer = undefined;
this._tryLtrOverride();
this.update();
}, 0);
}

dispose() {
if (this._asyncInitTimer) {
window.clearInterval(this._asyncInitTimer);
this._asyncInitTimer = undefined;
}
this._stopDrag();
this._container.removeEventListener('mouseenter', this._updateCallback);
this.hide();
Expand Down
15 changes: 14 additions & 1 deletion src/web/ViewBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import _ = require('./utils/lodashMini');
import ReactDOM = require('react-dom');

import { default as FrontLayerViewManager } from './FrontLayerViewManager';
import AppConfig from '../common/AppConfig';
import RX = require('../common/Interfaces');
import SyncTasks = require('synctasks');
Expand All @@ -30,6 +31,7 @@ export abstract class ViewBase<P extends Types.ViewProps, S> extends RX.ViewBase
abstract render(): JSX.Element;
protected abstract _getContainer(): HTMLElement|null;
private _isMounted = false;
private _isPopupDisplayed = false;

// Sets the activation state so we can stop our periodic timer
// when the app is in the background.
Expand Down Expand Up @@ -199,9 +201,20 @@ export abstract class ViewBase<P extends Types.ViewProps, S> extends RX.ViewBase
}

componentDidUpdate() {
const isPopupDisplayed = FrontLayerViewManager.isPopupDisplayed();
if (this.props.onLayout) {
this._checkAndReportLayout();
if (isPopupDisplayed && !this._isPopupDisplayed) {
// A popup was just added to DOM. Checking layout now would stall script
// execution because the browser would have to do a reflow. Avoid that
// by deferring the work.
setTimeout(() => {
this._checkAndReportLayout();
}, 0);
} else {
this._checkAndReportLayout();
}
}
this._isPopupDisplayed = isPopupDisplayed;
}

private static _onResize() {
Expand Down
45 changes: 38 additions & 7 deletions src/web/utils/FocusManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { applyFocusableComponentMixin, FocusableComponentStateCallback } from '
export { applyFocusableComponentMixin, FocusableComponentStateCallback };

export class FocusManager extends FocusManagerBase {
private static _setTabIndexTimer: number|undefined;
private static _setTabIndexElement: HTMLElement|undefined;

constructor(parent: FocusManager | undefined) {
super(parent);
Expand Down Expand Up @@ -157,10 +159,18 @@ export class FocusManager extends FocusManagerBase {
// In order to avoid losing this first Tab press, we're making <body>
// focusable, focusing it, removing the focus and making it unfocusable
// back again.
const prevTabIndex = FocusManager._setTabIndex(document.body, 0);
document.body.focus();
document.body.blur();
FocusManager._setTabIndex(document.body, prevTabIndex);
// Defer the work to avoid triggering sync layout.
FocusManager._resetFocusTimer = setTimeout(() => {
FocusManager._resetFocusTimer = undefined;
const prevTabIndex = FocusManager._setTabIndex(document.body, 0);
const activeElement = document.activeElement;
document.body.focus();
document.body.blur();
FocusManager._setTabIndex(document.body, prevTabIndex);
if (activeElement instanceof HTMLElement) {
activeElement.focus();
}
}, 0);
}
}

Expand Down Expand Up @@ -194,14 +204,35 @@ export class FocusManager extends FocusManagerBase {
}

private static _setTabIndex(element: HTMLElement, value: number|undefined): number|undefined {
const prev = element.hasAttribute(ATTR_NAME_TAB_INDEX) ? element.tabIndex : undefined;
// If a tabIndex assignment is pending for this element, cancel it now.
if (FocusManager._setTabIndexTimer && element === FocusManager._setTabIndexElement) {
clearTimeout(FocusManager._setTabIndexTimer);
FocusManager._setTabIndexTimer = undefined;
}

const prev = element.hasAttribute(ATTR_NAME_TAB_INDEX) ? element.tabIndex : undefined;
if (value === undefined) {
if (prev !== undefined) {
element.removeAttribute(ATTR_NAME_TAB_INDEX);
}
} else {
element.tabIndex = value;
} else if (value !== prev) {
// Setting tabIndex to -1 on the active element would trigger sync layout. Defer it.
if (value === -1 && element === document.activeElement) {
// If a tabIndex assignment is pending for another element, run it now as we know
// that it's not active anymore.
if (FocusManager._setTabIndexTimer) {
FocusManager._setTabIndexElement!!!.tabIndex = -1;
clearTimeout(FocusManager._setTabIndexTimer);
FocusManager._setTabIndexTimer = undefined;
}

FocusManager._setTabIndexElement = element;
FocusManager._setTabIndexTimer = setTimeout(() => {
element.tabIndex = value;
}, 0);
} else {
element.tabIndex = value;
}
}

return prev;
Expand Down