Archived
1
0

chore(vscode): update to 1.54.2

This commit is contained in:
Joe Previte
2021-03-11 10:27:10 -07:00
1459 changed files with 53404 additions and 51004 deletions

View File

@ -110,13 +110,13 @@ export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscree
const userAgent = navigator.userAgent;
export const isEdgeLegacy = (userAgent.indexOf('Edge/') >= 0);
export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
export const isChrome = (userAgent.indexOf('Chrome') >= 0);
export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
export const isIPad = (userAgent.indexOf('iPad') >= 0 || (isSafari && navigator.maxTouchPoints > 0));
export const isEdgeLegacyWebView = isEdgeLegacy && (userAgent.indexOf('WebView/') >= 0);
export const isEdgeLegacyWebView = (userAgent.indexOf('Edge/') >= 0) && (userAgent.indexOf('WebView/') >= 0);
export const isElectron = (userAgent.indexOf('Electron/') >= 0);
export const isAndroid = (userAgent.indexOf('Android') >= 0);
export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);

View File

@ -25,19 +25,7 @@ export const BrowserFeatures = {
readText: (
platform.isNative
|| !!(navigator && navigator.clipboard && navigator.clipboard.readText)
),
richText: (() => {
if (browser.isEdgeLegacy) {
let index = navigator.userAgent.indexOf('Edge/');
let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
if (!version || (version >= 12 && version <= 16)) {
return false;
}
}
return true;
})()
)
},
keyboard: (() => {
if (platform.isNative || browser.isStandalone) {

View File

@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions';
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
export interface IContextMenuEvent {
readonly shiftKey?: boolean;

View File

@ -284,7 +284,7 @@ export function modify(callback: () => void): IDisposable {
}
/**
* Add a throttled listener. `handler` is fired at most every 16ms or with the next animation frame (if browser supports it).
* Add a throttled listener. `handler` is fired at most every 8.33333ms or with the next animation frame (if browser supports it).
*/
export interface IEventMerger<R, E> {
(lastEvent: R | null, currentEvent: E): R;
@ -295,7 +295,7 @@ export interface DOMEvent {
stopPropagation(): void;
}
const MINIMUM_TIME_MS = 16;
const MINIMUM_TIME_MS = 8;
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
return currentEvent;
};
@ -841,7 +841,7 @@ export const EventType = {
MOUSE_OUT: 'mouseout',
MOUSE_ENTER: 'mouseenter',
MOUSE_LEAVE: 'mouseleave',
MOUSE_WHEEL: browser.isEdgeLegacy ? 'mousewheel' : 'wheel',
MOUSE_WHEEL: 'wheel',
POINTER_UP: 'pointerup',
POINTER_DOWN: 'pointerdown',
POINTER_MOVE: 'pointermove',
@ -1230,6 +1230,10 @@ export function asCSSUrl(uri: URI): string {
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
}
export function asCSSPropertyValue(value: string) {
return `'${value.replace(/'/g, '%27')}'`;
}
export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void {
// If the data is provided as Buffer, we create a
@ -1471,7 +1475,7 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
metaKey: false
};
this._subscriptions.add(domEvent(document.body, 'keydown', true)(e => {
this._subscriptions.add(domEvent(window, 'keydown', true)(e => {
const event = new StandardKeyboardEvent(e);
// If Alt-key keydown event is repeated, ignore it #112347
@ -1505,7 +1509,7 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
}
}));
this._subscriptions.add(domEvent(document.body, 'keyup', true)(e => {
this._subscriptions.add(domEvent(window, 'keyup', true)(e => {
if (!e.altKey && this._keyStatus.altKey) {
this._keyStatus.lastKeyReleased = 'alt';
} else if (!e.ctrlKey && this._keyStatus.ctrlKey) {

View File

@ -31,10 +31,12 @@ export interface CancellableEvent {
stopPropagation(): void;
}
export function stopEvent<T extends CancellableEvent>(event: T): T {
event.preventDefault();
event.stopPropagation();
return event;
}
export function stop<T extends CancellableEvent>(event: BaseEvent<T>): BaseEvent<T> {
return BaseEvent.map(event, e => {
e.preventDefault();
e.stopPropagation();
return e;
});
}
return BaseEvent.map(event, stopEvent);
}

View File

@ -383,5 +383,16 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString) {
if (value.length > 100_000) {
value = `${value.substr(0, 100_000)}`;
}
return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString();
const unescapeInfo = new Map<string, string>([
['&quot;', '"'],
['&amp;', '&'],
['&#39;', '\''],
['&lt;', '<'],
['&gt;', '>'],
]);
const html = marked.parse(value, { renderer }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m);
return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString();
}

View File

@ -8,13 +8,14 @@ import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions';
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator } from 'vs/base/common/actions';
import * as types from 'vs/base/common/types';
import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { DataTransfers } from 'vs/base/browser/dnd';
import { isFirefox } from 'vs/base/browser/browser';
import { $, addDisposableListener, append, EventHelper, EventLike, EventType, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom';
import { $, addDisposableListener, append, EventHelper, EventLike, EventType } from 'vs/base/browser/dom';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
export interface IBaseActionViewItemOptions {
draggable?: boolean;
@ -163,8 +164,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
this.actionRunner.run(this._action, context);
}
// Only set the tabIndex on the element once it is about to get focused
// That way this element wont be a tab stop when it is not needed #106441
focus(): void {
if (this.element) {
this.element.tabIndex = 0;
this.element.focus();
this.element.classList.add('focused');
}
@ -173,10 +177,21 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
blur(): void {
if (this.element) {
this.element.blur();
this.element.tabIndex = -1;
this.element.classList.remove('focused');
}
}
setFocusable(focusable: boolean): void {
if (this.element) {
this.element.tabIndex = focusable ? 0 : -1;
}
}
get trapsArrowNavigation(): boolean {
return false;
}
protected updateEnabled(): void {
// implement in subclass
}
@ -259,14 +274,27 @@ export class ActionViewItem extends BaseActionViewItem {
this.updateChecked();
}
// Only set the tabIndex on the element once it is about to get focused
// That way this element wont be a tab stop when it is not needed #106441
focus(): void {
super.focus();
if (this.label) {
this.label.tabIndex = 0;
this.label.focus();
}
}
blur(): void {
if (this.label) {
this.label.tabIndex = -1;
}
}
setFocusable(focusable: boolean): void {
if (this.label) {
this.label.tabIndex = focusable ? 0 : -1;
}
}
updateLabel(): void {
if (this.options.label && this.label) {
this.label.textContent = this.getAction().label;
@ -320,7 +348,6 @@ export class ActionViewItem extends BaseActionViewItem {
if (this.label) {
this.label.removeAttribute('aria-disabled');
this.label.classList.remove('disabled');
this.label.tabIndex = 0;
}
if (this.element) {
@ -330,7 +357,6 @@ export class ActionViewItem extends BaseActionViewItem {
if (this.label) {
this.label.setAttribute('aria-disabled', 'true');
this.label.classList.add('disabled');
removeTabIndexAndUpdateFocus(this.label);
}
if (this.element) {

View File

@ -55,6 +55,7 @@
}
.monaco-action-bar .action-item.disabled .action-label,
.monaco-action-bar .action-item.disabled .action-label::before,
.monaco-action-bar .action-item.disabled .action-label:hover {
opacity: 0.4;
}

View File

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./actionbar';
import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator, IActionViewItem, IActionViewItemProvider } from 'vs/base/common/actions';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@ -13,6 +13,19 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Emitter } from 'vs/base/common/event';
import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
export interface IActionViewItem extends IDisposable {
actionRunner: IActionRunner;
setActionContext(context: any): void;
render(element: HTMLElement): void;
isEnabled(): boolean;
focus(fromRight?: boolean): void; // TODO@isidorn what is this?
blur(): void;
}
export interface IActionViewItemProvider {
(action: IAction): IActionViewItem | undefined;
}
export const enum ActionsOrientation {
HORIZONTAL,
HORIZONTAL_REVERSE,
@ -35,7 +48,7 @@ export interface IActionBarOptions {
readonly triggerKeys?: ActionTrigger;
readonly allowContextMenu?: boolean;
readonly preventLoopNavigation?: boolean;
readonly ignoreOrientationForPreviousAndNextKey?: boolean;
readonly focusOnlyEnabledItems?: boolean;
}
export interface IActionOptions extends IActionViewItemOptions {
@ -63,6 +76,8 @@ export class ActionBar extends Disposable implements IActionRunner {
// Trigger Key Tracking
private triggerKeyDown: boolean = false;
private focusable: boolean = true;
// Elements
domNode: HTMLElement;
protected actionsList: HTMLElement;
@ -117,22 +132,22 @@ export class ActionBar extends Disposable implements IActionRunner {
switch (this._orientation) {
case ActionsOrientation.HORIZONTAL:
previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.LeftArrow];
nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.RightArrow];
previousKeys = [KeyCode.LeftArrow];
nextKeys = [KeyCode.RightArrow];
break;
case ActionsOrientation.HORIZONTAL_REVERSE:
previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.RightArrow];
nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.LeftArrow];
previousKeys = [KeyCode.RightArrow];
nextKeys = [KeyCode.LeftArrow];
this.domNode.className += ' reverse';
break;
case ActionsOrientation.VERTICAL:
previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.UpArrow];
nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.DownArrow];
previousKeys = [KeyCode.UpArrow];
nextKeys = [KeyCode.DownArrow];
this.domNode.className += ' vertical';
break;
case ActionsOrientation.VERTICAL_REVERSE:
previousKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.RightArrow, KeyCode.DownArrow] : [KeyCode.DownArrow];
nextKeys = this.options.ignoreOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow, KeyCode.UpArrow] : [KeyCode.UpArrow];
previousKeys = [KeyCode.DownArrow];
nextKeys = [KeyCode.UpArrow];
this.domNode.className += ' vertical reverse';
break;
}
@ -140,6 +155,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
const focusedItem = typeof this.focusedItem === 'number' ? this.viewItems[this.focusedItem] : undefined;
if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
eventHandled = this.focusPrevious();
@ -147,6 +163,8 @@ export class ActionBar extends Disposable implements IActionRunner {
eventHandled = this.focusNext();
} else if (event.equals(KeyCode.Escape) && this.cancelHasListener) {
this._onDidCancel.fire();
} else if (event.equals(KeyCode.Tab) && focusedItem instanceof BaseActionViewItem && focusedItem.trapsArrowNavigation) {
this.focusNext();
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered
if (this._triggerKeys.keyDown) {
@ -216,6 +234,25 @@ export class ActionBar extends Disposable implements IActionRunner {
}
}
// Some action bars should not be focusable at times
// When an action bar is not focusable make sure to make all the elements inside it not focusable
// When an action bar is focusable again, make sure the first item can be focused
setFocusable(focusable: boolean): void {
this.focusable = focusable;
if (this.focusable) {
const firstEnabled = this.viewItems.find(vi => vi instanceof BaseActionViewItem && vi.isEnabled());
if (firstEnabled instanceof BaseActionViewItem) {
firstEnabled.setFocusable(true);
}
} else {
this.viewItems.forEach(vi => {
if (vi instanceof BaseActionViewItem) {
vi.setFocusable(false);
}
});
}
}
private isTriggerKeyEvent(event: StandardKeyboardEvent): boolean {
let ret = false;
this._triggerKeys.keys.forEach(keyCode => {
@ -294,6 +331,11 @@ export class ActionBar extends Disposable implements IActionRunner {
item.setActionContext(this.context);
item.render(actionViewItemElement);
if (this.focusable && item instanceof BaseActionViewItem && this.viewItems.length === 0) {
// We need to allow for the first enabled item to be focused on using tab navigation #106441
item.setFocusable(true);
}
if (index === null || index < 0 || index >= this.actionsList.children.length) {
this.actionsList.appendChild(actionViewItemElement);
this.viewItems.push(item);
@ -305,7 +347,7 @@ export class ActionBar extends Disposable implements IActionRunner {
index++;
}
});
if (this.focusedItem) {
if (typeof this.focusedItem === 'number') {
// After a clear actions might be re-added to simply toggle some actions. We should preserve focus #97128
this.focus(this.focusedItem);
}
@ -390,8 +432,8 @@ export class ActionBar extends Disposable implements IActionRunner {
const startIndex = this.focusedItem;
let item: IActionViewItem;
do {
if (this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) {
this.focusedItem = startIndex;
return false;
@ -399,11 +441,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this.focusedItem = (this.focusedItem + 1) % this.viewItems.length;
item = this.viewItems[this.focusedItem];
} while (this.focusedItem !== startIndex && !item.isEnabled());
if (this.focusedItem === startIndex && !item.isEnabled()) {
this.focusedItem = undefined;
}
} while (this.focusedItem !== startIndex && this.options.focusOnlyEnabledItems && !item.isEnabled());
this.updateFocus();
return true;
@ -419,7 +457,6 @@ export class ActionBar extends Disposable implements IActionRunner {
do {
this.focusedItem = this.focusedItem - 1;
if (this.focusedItem < 0) {
if (this.options.preventLoopNavigation) {
this.focusedItem = startIndex;
@ -428,13 +465,9 @@ export class ActionBar extends Disposable implements IActionRunner {
this.focusedItem = this.viewItems.length - 1;
}
item = this.viewItems[this.focusedItem];
} while (this.focusedItem !== startIndex && !item.isEnabled());
} while (this.focusedItem !== startIndex && this.options.focusOnlyEnabledItems && !item.isEnabled());
if (this.focusedItem === startIndex && !item.isEnabled()) {
this.focusedItem = undefined;
}
this.updateFocus(true);
return true;
@ -450,12 +483,20 @@ export class ActionBar extends Disposable implements IActionRunner {
const actionViewItem = item;
if (i === this.focusedItem) {
if (types.isFunction(actionViewItem.isEnabled)) {
if (actionViewItem.isEnabled() && types.isFunction(actionViewItem.focus)) {
actionViewItem.focus(fromRight);
} else {
this.actionsList.focus({ preventScroll });
}
let focusItem = true;
if (!types.isFunction(actionViewItem.focus)) {
focusItem = false;
}
if (this.options.focusOnlyEnabledItems && types.isFunction(item.isEnabled) && !item.isEnabled()) {
focusItem = false;
}
if (focusItem) {
actionViewItem.focus(fromRight);
} else {
this.actionsList.focus({ preventScroll });
}
} else {
if (types.isFunction(actionViewItem.blur)) {

View File

@ -12,7 +12,7 @@ import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom';
import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset } from 'vs/base/browser/dom';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
import { CSSIcon, Codicon } from 'vs/base/common/codicons';
@ -214,7 +214,6 @@ export class Button extends Disposable implements IButton {
} else {
this._element.classList.add('disabled');
this._element.setAttribute('aria-disabled', String(true));
removeTabIndexAndUpdateFocus(this._element);
}
}

View File

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./checkbox';
import * as DOM from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Widget } from 'vs/base/browser/ui/widget';
import { Color } from 'vs/base/common/color';
@ -19,6 +18,7 @@ export interface ICheckboxOpts extends ICheckboxStyles {
readonly icon?: CSSIcon;
readonly title: string;
readonly isChecked: boolean;
readonly notFocusable?: boolean;
}
export interface ICheckboxStyles {
@ -51,7 +51,8 @@ export class CheckboxActionViewItem extends BaseActionViewItem {
this.checkbox = new Checkbox({
actionClassName: this._action.class,
isChecked: this._action.checked,
title: this._action.label
title: this._action.label,
notFocusable: true
});
this.disposables.add(this.checkbox);
this.disposables.add(this.checkbox.onChange(() => this._action.checked = !!this.checkbox && this.checkbox.checked, this));
@ -74,6 +75,26 @@ export class CheckboxActionViewItem extends BaseActionViewItem {
}
}
focus(): void {
if (this.checkbox) {
this.checkbox.domNode.tabIndex = 0;
this.checkbox.focus();
}
}
blur(): void {
if (this.checkbox) {
this.checkbox.domNode.tabIndex = -1;
this.checkbox.domNode.blur();
}
}
setFocusable(focusable: boolean): void {
if (this.checkbox) {
this.checkbox.domNode.tabIndex = focusable ? 0 : -1;
}
}
dispose(): void {
this.disposables.dispose();
super.dispose();
@ -113,7 +134,9 @@ export class Checkbox extends Widget {
this.domNode = document.createElement('div');
this.domNode.title = this._opts.title;
this.domNode.classList.add(...classes);
this.domNode.tabIndex = 0;
if (!this._opts.notFocusable) {
this.domNode.tabIndex = 0;
}
this.domNode.setAttribute('role', 'checkbox');
this.domNode.setAttribute('aria-checked', String(this._checked));
this.domNode.setAttribute('aria-label', this._opts.title);
@ -187,12 +210,10 @@ export class Checkbox extends Widget {
}
enable(): void {
this.domNode.tabIndex = 0;
this.domNode.setAttribute('aria-disabled', String(false));
}
disable(): void {
DOM.removeTabIndexAndUpdateFocus(this.domNode);
this.domNode.setAttribute('aria-disabled', String(true));
}
}

View File

@ -13,7 +13,7 @@
}
}
.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin{
.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin, .codicon-gear.codicon-modifier-spin {
/* Use steps to throttle FPS to reduce CPU usage */
animation: codicon-spin 1.5s steps(30) infinite;
}

View File

@ -13,5 +13,5 @@ export function formatRule(c: Codicon) {
while (def instanceof Codicon) {
def = def.definition;
}
return `.codicon-${c.id}:before { content: '${def.character}'; }`;
return `.codicon-${c.id}:before { content: '${def.fontCharacter}'; }`;
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dropdown';
import { Action, IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
@ -14,6 +14,7 @@ import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseAction
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { Codicon } from 'vs/base/common/codicons';
import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
export interface IKeybindingProvider {
(action: IAction): ResolvedKeybinding | undefined;
@ -78,7 +79,6 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.element.classList.add(...classNames);
this.element.tabIndex = 0;
this.element.setAttribute('role', 'button');
this.element.setAttribute('aria-haspopup', 'true');
this.element.setAttribute('aria-expanded', 'false');
@ -173,12 +173,14 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
const menuActionsProvider = {
getActions: () => {
const actionsProvider = (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider;
return [this._action, ...(Array.isArray(actionsProvider) ? actionsProvider : actionsProvider.getActions())];
return [this._action, ...(Array.isArray(actionsProvider)
? actionsProvider
: (actionsProvider as IActionProvider).getActions()) // TODO: microsoft/TypeScript#42768
];
}
};
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
this.dropdownMenuActionViewItem.render(this.element);
}
}
}

View File

@ -20,5 +20,4 @@ export interface IHoverDelegateOptions {
export interface IHoverDelegate {
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
hideHover(): void;
}

View File

@ -192,13 +192,14 @@ export class IconLabel extends Disposable {
}
}
private static adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean) {
private static adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean): IDisposable | undefined {
if (hoverOptions && isHovering) {
if (mouseX !== undefined) {
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
}
hoverDelegate.showHover(hoverOptions);
return hoverDelegate.showHover(hoverOptions);
}
return undefined;
}
private getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise<string | IMarkdownString | undefined> {
@ -224,17 +225,22 @@ export class IconLabel extends Disposable {
let mouseX: number | undefined;
let isHovering = false;
let tokenSource: CancellationTokenSource;
let hoverDisposable: IDisposable | undefined;
function mouseOver(this: HTMLElement, e: MouseEvent): any {
if (isHovering) {
return;
}
tokenSource = new CancellationTokenSource();
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
isHovering = false;
hoverOptions = undefined;
tokenSource.dispose(true);
mouseLeaveDisposable.dispose();
mouseDownDisposable.dispose();
if ((e.type === dom.EventType.MOUSE_DOWN) || (<any>e).fromElement === htmlElement) {
hoverDisposable?.dispose();
hoverDisposable = undefined;
isHovering = false;
hoverOptions = undefined;
tokenSource.dispose(true);
mouseLeaveDisposable.dispose();
mouseDownDisposable.dispose();
}
}
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeaveOrDown.bind(htmlElement));
const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement));
@ -257,7 +263,7 @@ export class IconLabel extends Disposable {
target,
anchorPosition: AnchorPosition.BELOW
};
IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined);
if (resolvedTooltip) {
@ -267,11 +273,13 @@ export class IconLabel extends Disposable {
anchorPosition: AnchorPosition.BELOW
};
// awaiting the tooltip could take a while. Make sure we're still hovering.
IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
} else {
hoverDelegate.hideHover();
hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
} else if (hoverDisposable) {
hoverDisposable.dispose();
hoverDisposable = undefined;
}
}
}
mouseMoveDisposable.dispose();
}, hoverDelay);

View File

@ -6,8 +6,7 @@
import * as dom from 'vs/base/browser/dom';
import { CSSIcon } from 'vs/base/common/codicons';
const labelWithIconsRegex = /(\\)?\$\(([a-z\-]+(?:~[a-z\-]+)?)\)/gi;
const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g');
export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | string> {
const elements = new Array<HTMLSpanElement | string>();
let match: RegExpMatchArray | null;

View File

@ -89,7 +89,6 @@
padding: 0.4em;
font-size: 12px;
line-height: 17px;
min-height: 34px;
margin-top: -1px;
word-wrap: break-word;
}

View File

@ -292,6 +292,9 @@ export class InputBox extends Widget {
if (range) {
this.input.setSelectionRange(range.start, range.end);
if (range.end === this.input.value.length) {
this.input.scrollLeft = this.input.scrollWidth;
}
}
}

View File

@ -15,7 +15,7 @@ export interface IListVirtualDelegate<T> {
}
export interface IListRenderer<T, TTemplateData> {
templateId: string;
readonly templateId: string;
renderTemplate(container: HTMLElement): TTemplateData;
renderElement(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeElement?(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;

View File

@ -64,6 +64,7 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate {
readonly mouseSupport?: boolean;
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
readonly transformOptimization?: boolean;
readonly alwaysConsumeMouseWheel?: boolean;
}
const DefaultOptions = {
@ -80,7 +81,8 @@ const DefaultOptions = {
drop() { }
},
horizontalScrolling: false,
transformOptimization: true
transformOptimization: true,
alwaysConsumeMouseWheel: true,
};
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
@ -327,6 +329,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel),
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
@ -433,7 +436,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const removeRange = Range.intersect(previousRenderRange, deleteRange);
// try to reuse rows, avoid removing them from DOM
const rowsToDispose = new Map<string, [IRow, T, number, number][]>();
const rowsToDispose = new Map<string, IRow[]>();
for (let i = removeRange.start; i < removeRange.end; i++) {
const item = this.items[i];
item.dragStartDisposable.dispose();
@ -446,7 +449,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
rowsToDispose.set(item.templateId, rows);
}
rows.push([item.row, item.element, i, item.size]);
const renderer = this.renderers.get(item.templateId);
if (renderer && renderer.disposeElement) {
renderer.disposeElement(item.element, i, item.row.templateData, item.size);
}
rows.push(item.row);
}
item.row = null;
@ -476,8 +485,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (start === 0 && deleteCount >= this.items.length) {
this.rangeMap = new RangeMap();
this.rangeMap.splice(0, 0, inserted);
deleted = this.items;
this.items = inserted;
deleted = [];
} else {
this.rangeMap.splice(start, deleteCount, inserted);
deleted = this.items.splice(start, deleteCount, ...inserted);
@ -509,31 +518,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
for (let i = range.start; i < range.end; i++) {
const item = this.items[i];
const rows = rowsToDispose.get(item.templateId);
const rowData = rows?.pop();
if (!rowData) {
this.insertItemInDOM(i, beforeElement);
} else {
const [row, element, index, size] = rowData;
const renderer = this.renderers.get(item.templateId);
if (renderer && renderer.disposeElement) {
renderer.disposeElement(element, index, row.templateData, size);
}
this.insertItemInDOM(i, beforeElement, row);
}
const row = rows?.pop();
this.insertItemInDOM(i, beforeElement, row);
}
}
for (const [templateId, rows] of rowsToDispose) {
for (const [row, element, index, size] of rows) {
const renderer = this.renderers.get(templateId);
if (renderer && renderer.disposeElement) {
renderer.disposeElement(element, index, row.templateData, size);
}
for (const rows of rowsToDispose.values()) {
for (const row of rows) {
this.cache.release(row);
}
}

View File

@ -13,7 +13,7 @@ import { Gesture } from 'vs/base/browser/touch';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { domEvent, stopEvent } from 'vs/base/browser/event';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list';
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView';
import { Color } from 'vs/base/common/color';
@ -761,6 +761,11 @@ export class DefaultStyleController implements IStyleController {
`);
}
if (styles.listInactiveFocusForeground) {
content.push(`.monaco-list${suffix} .monaco-list-row.focused { color: ${styles.listInactiveFocusForeground}; }`);
content.push(`.monaco-list${suffix} .monaco-list-row.focused:hover { color: ${styles.listInactiveFocusForeground}; }`); // overwrite :hover style in this case!
}
if (styles.listInactiveFocusBackground) {
content.push(`.monaco-list${suffix} .monaco-list-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`);
content.push(`.monaco-list${suffix} .monaco-list-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case!
@ -776,7 +781,7 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listHoverBackground) {
content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`);
content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`);
}
if (styles.listHoverForeground) {
@ -826,6 +831,14 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
if (styles.tableColumnsBorder) {
content.push(`
.monaco-table:hover > .monaco-split-view2,
.monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before {
border-color: ${styles.tableColumnsBorder};
}`);
}
this.styleElement.textContent = content.join('\n');
}
}
@ -854,6 +867,7 @@ export interface IListOptions<T> {
readonly additionalScrollHeight?: number;
readonly transformOptimization?: boolean;
readonly smoothScrolling?: boolean;
readonly alwaysConsumeMouseWheel?: boolean;
}
export interface IListStyles {
@ -866,6 +880,7 @@ export interface IListStyles {
listFocusAndSelectionForeground?: Color;
listInactiveSelectionBackground?: Color;
listInactiveSelectionForeground?: Color;
listInactiveFocusForeground?: Color;
listInactiveFocusBackground?: Color;
listHoverBackground?: Color;
listHoverForeground?: Color;
@ -879,6 +894,7 @@ export interface IListStyles {
listFilterWidgetNoMatchesOutline?: Color;
listMatchesShadow?: Color;
treeIndentGuidesStroke?: Color;
tableColumnsBorder?: Color;
}
const defaultStyles: IListStyles = {
@ -890,7 +906,8 @@ const defaultStyles: IListStyles = {
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
listHoverBackground: Color.fromHex('#2A2D2E'),
listDropBackground: Color.fromHex('#383B3D'),
treeIndentGuidesStroke: Color.fromHex('#a9a9a9')
treeIndentGuidesStroke: Color.fromHex('#a9a9a9'),
tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2)
};
const DefaultOptions: IListOptions<any> = {
@ -1148,35 +1165,43 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
get onTouchStart(): Event<IListTouchEvent<T>> { return this.view.onTouchStart; }
get onTap(): Event<IListGestureEvent<T>> { return this.view.onTap; }
private didJustPressContextMenuKey: boolean = false;
/**
* Possible context menu trigger events:
* - ContextMenu key
* - Shift F10
* - Ctrl Option Shift M (macOS with VoiceOver)
* - Mouse right click
*/
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown'))
let didJustPressContextMenuKey = false;
const fromKeyDown = Event.chain(domEvent(this.view.domNode, 'keydown'))
.map(e => new StandardKeyboardEvent(e))
.filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
.filter(e => { e.preventDefault(); e.stopPropagation(); return false; })
.filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
.map(stopEvent)
.filter(() => false)
.event as Event<any>;
const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup'))
.filter(() => {
const didJustPressContextMenuKey = this.didJustPressContextMenuKey;
this.didJustPressContextMenuKey = false;
return didJustPressContextMenuKey;
})
.filter(() => this.getFocus().length > 0 && !!this.view.domElement(this.getFocus()[0]))
.map(browserEvent => {
const index = this.getFocus()[0];
const element = this.view.element(index);
const anchor = this.view.domElement(index) as HTMLElement;
const fromKeyUp = Event.chain(domEvent(this.view.domNode, 'keyup'))
.forEach(() => didJustPressContextMenuKey = false)
.map(e => new StandardKeyboardEvent(e))
.filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
.map(stopEvent)
.map(({ browserEvent }) => {
const focus = this.getFocus();
const index = focus.length ? focus[0] : undefined;
const element = typeof index !== 'undefined' ? this.view.element(index) : undefined;
const anchor = typeof index !== 'undefined' ? this.view.domElement(index) as HTMLElement : this.view.domNode;
return { index, element, anchor, browserEvent };
})
.event;
const fromMouse = Event.chain(this.view.onContextMenu)
.filter(() => !this.didJustPressContextMenuKey)
.filter(_ => !didJustPressContextMenuKey)
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent }))
.event;
return Event.any<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
return Event.any<IListContextMenuEvent<T>>(fromKeyDown, fromKeyUp, fromMouse);
}
get onKeyDown(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keydown'); }
@ -1380,7 +1405,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
}
domFocus(): void {
this.view.domNode.focus();
this.view.domNode.focus({ preventScroll: true });
}
layout(height?: number, width?: number): void {

View File

@ -5,10 +5,10 @@
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider, EmptySubmenuAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IActionRunner, IAction, SubmenuAction, Separator, EmptySubmenuAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, addDisposableListener, append, $, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom';
import { EventType, EventHelper, EventLike, isAncestor, addDisposableListener, append, $, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { RunOnceScheduler } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
@ -86,6 +86,7 @@ export class Menu extends ActionBar {
context: options.context,
actionRunner: options.actionRunner,
ariaLabel: options.ariaLabel,
focusOnlyEnabledItems: true,
triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true }
});
@ -617,20 +618,23 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
if (this.getAction().enabled) {
if (this.element) {
this.element.classList.remove('disabled');
this.element.removeAttribute('aria-disabled');
}
if (this.item) {
this.item.classList.remove('disabled');
this.item.removeAttribute('aria-disabled');
this.item.tabIndex = 0;
}
} else {
if (this.element) {
this.element.classList.add('disabled');
this.element.setAttribute('aria-disabled', 'true');
}
if (this.item) {
this.item.classList.add('disabled');
removeTabIndexAndUpdateFocus(this.item);
this.item.setAttribute('aria-disabled', 'true');
}
}
}

View File

@ -42,7 +42,7 @@
}
.menubar .menubar-menu-items-holder {
position: absolute;
position: fixed;
left: 0px;
opacity: 1;
z-index: 2000;

View File

@ -746,7 +746,7 @@ export class MenuBar extends Disposable {
private setUnfocusedState(): void {
if (this.options.visibility === 'toggle' || this.options.visibility === 'hidden') {
this.focusState = MenubarState.HIDDEN;
} else if (this.options.visibility === 'default' && browser.isFullscreen()) {
} else if (this.options.visibility === 'classic' && browser.isFullscreen()) {
this.focusState = MenubarState.HIDDEN;
} else {
this.focusState = MenubarState.VISIBLE;
@ -838,6 +838,22 @@ export class MenuBar extends Disposable {
this._mnemonicsInUse = value;
}
private get shouldAltKeyFocus(): boolean {
if (isMacintosh) {
return false;
}
if (!this.options.disableAltFocus) {
return true;
}
if (this.options.visibility === 'toggle') {
return true;
}
return false;
}
public get onVisibilityChange(): Event<boolean> {
return this._onVisibilityChange.event;
}
@ -869,7 +885,7 @@ export class MenuBar extends Disposable {
}
// Prevent alt-key default if the menu is not hidden and we use alt to focus
if (modifierKeyStatus.event && !this.options.disableAltFocus) {
if (modifierKeyStatus.event && this.shouldAltKeyFocus) {
if (ScanCodeUtils.toEnum(modifierKeyStatus.event.code) === ScanCode.AltLeft) {
modifierKeyStatus.event.preventDefault();
}
@ -885,7 +901,7 @@ export class MenuBar extends Disposable {
// Clean alt key press and release
if (allModifiersReleased && modifierKeyStatus.lastKeyPressed === 'alt' && modifierKeyStatus.lastKeyReleased === 'alt') {
if (!this.awaitingAltRelease) {
if (!this.isFocused && !(this.options.disableAltFocus && this.options.visibility !== 'toggle')) {
if (!this.isFocused && this.shouldAltKeyFocus) {
this.mnemonicsInUse = true;
this.focusedMenu = { index: this.numMenusShown > 0 ? 0 : MenuBar.OVERFLOW_INDEX };
this.focusState = MenubarState.FOCUSED;

View File

@ -60,8 +60,7 @@
height: var(--sash-size);
}
.monaco-sash:not(.disabled).orthogonal-start::before,
.monaco-sash:not(.disabled).orthogonal-end::after {
.monaco-sash:not(.disabled) > .orthogonal-drag-handle {
content: " ";
height: calc(var(--sash-size) * 2);
width: calc(var(--sash-size) * 2);
@ -71,30 +70,34 @@
position: absolute;
}
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-start::before,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-end::after {
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled)
> .orthogonal-drag-handle.start,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled)
> .orthogonal-drag-handle.end {
cursor: nwse-resize;
}
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-end::after,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-start::before {
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled)
> .orthogonal-drag-handle.end,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled)
> .orthogonal-drag-handle.start {
cursor: nesw-resize;
}
.monaco-sash.orthogonal-start.vertical::before {
left: -calc(var(--sash-size) / 2);
.monaco-sash.vertical > .orthogonal-drag-handle.start {
left: calc(var(--sash-size) / -2);
top: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-end.vertical::after {
left: -calc(var(--sash-size) / 2);
.monaco-sash.vertical > .orthogonal-drag-handle.end {
left: calc(var(--sash-size) / -2);
bottom: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-start.horizontal::before {
top: -calc(var(--sash-size) / 2);
.monaco-sash.horizontal > .orthogonal-drag-handle.start {
top: calc(var(--sash-size) / -2);
left: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-end.horizontal::after {
top: -calc(var(--sash-size) / 2);
.monaco-sash.horizontal > .orthogonal-drag-handle.end {
top: calc(var(--sash-size) / -2);
right: calc(var(--sash-size) * -1);
}
@ -113,7 +116,6 @@
background: rgba(0, 255, 255, 0.2);
}
.monaco-sash.debug:not(.disabled).orthogonal-start::before,
.monaco-sash.debug:not(.disabled).orthogonal-end::after {
.monaco-sash.debug:not(.disabled) > .orthogonal-drag-handle {
background: red;
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./sash';
import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import * as types from 'vs/base/common/types';
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
@ -12,8 +12,10 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Event, Emitter } from 'vs/base/common/event';
import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $ } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { Delayer } from 'vs/base/common/async';
const DEBUG = false;
let DEBUG = false;
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
export interface ISashLayoutProvider { }
@ -86,6 +88,7 @@ export class Sash extends Disposable {
private hidden: boolean;
private orientation!: Orientation;
private size: number;
private hoverDelayer = this._register(new Delayer(300));
private _state: SashState = SashState.Enabled;
get state(): SashState { return this._state; }
@ -121,15 +124,29 @@ export class Sash extends Disposable {
private readonly orthogonalStartSashDisposables = this._register(new DisposableStore());
private _orthogonalStartSash: Sash | undefined;
private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalStartDragHandle: HTMLElement | undefined;
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
set orthogonalStartSash(sash: Sash | undefined) {
this.orthogonalStartDragHandleDisposables.clear();
this.orthogonalStartSashDisposables.clear();
if (sash) {
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalStartSashEnablementChange, this));
this.onOrthogonalStartSashEnablementChange(sash.state);
} else {
this.onOrthogonalStartSashEnablementChange(SashState.Disabled);
const onChange = (state: SashState) => {
this.orthogonalStartDragHandleDisposables.clear();
if (state !== SashState.Disabled) {
this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start'));
this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle!.remove()));
domEvent(this._orthogonalStartDragHandle, 'mouseenter')
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables);
domEvent(this._orthogonalStartDragHandle, 'mouseleave')
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables);
}
};
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(onChange, this));
onChange(sash.state);
}
this._orthogonalStartSash = sash;
@ -137,15 +154,29 @@ export class Sash extends Disposable {
private readonly orthogonalEndSashDisposables = this._register(new DisposableStore());
private _orthogonalEndSash: Sash | undefined;
private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalEndDragHandle: HTMLElement | undefined;
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
set orthogonalEndSash(sash: Sash | undefined) {
this.orthogonalEndDragHandleDisposables.clear();
this.orthogonalEndSashDisposables.clear();
if (sash) {
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalEndSashEnablementChange, this));
this.onOrthogonalEndSashEnablementChange(sash.state);
} else {
this.onOrthogonalEndSashEnablementChange(SashState.Disabled);
const onChange = (state: SashState) => {
this.orthogonalEndDragHandleDisposables.clear();
if (state !== SashState.Disabled) {
this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end'));
this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle!.remove()));
domEvent(this._orthogonalEndDragHandle, 'mouseenter')
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables);
domEvent(this._orthogonalEndDragHandle, 'mouseleave')
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables);
}
};
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(onChange, this));
onChange(sash.state);
}
this._orthogonalEndSash = sash;
@ -168,6 +199,8 @@ export class Sash extends Disposable {
this._register(domEvent(this.el, 'mousedown')(this.onMouseDown, this));
this._register(domEvent(this.el, 'dblclick')(this.onMouseDoubleClick, this));
this._register(domEvent(this.el, 'mouseenter')(() => Sash.onMouseEnter(this)));
this._register(domEvent(this.el, 'mouseleave')(() => Sash.onMouseLeave(this)));
this._register(Gesture.addTarget(this.el));
this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
@ -359,12 +392,34 @@ export class Sash extends Disposable {
}
}));
listeners.push(addDisposableListener(this.el, EventType.End, (event: GestureEvent) => {
listeners.push(addDisposableListener(this.el, EventType.End, () => {
this._onDidEnd.fire();
dispose(listeners);
}));
}
private static onMouseEnter(sash: Sash, fromLinkedSash: boolean = false): void {
if (sash.el.classList.contains('active')) {
sash.hoverDelayer.cancel();
sash.el.classList.add('hover');
} else {
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'));
}
if (!fromLinkedSash && sash.linkedSash) {
Sash.onMouseEnter(sash.linkedSash, true);
}
}
private static onMouseLeave(sash: Sash, fromLinkedSash: boolean = false): void {
sash.hoverDelayer.cancel();
sash.el.classList.remove('hover');
if (!fromLinkedSash && sash.linkedSash) {
Sash.onMouseLeave(sash.linkedSash, true);
}
}
layout(): void {
if (this.orientation === Orientation.VERTICAL) {
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
@ -407,27 +462,13 @@ export class Sash extends Disposable {
return this.hidden;
}
private onOrthogonalStartSashEnablementChange(state: SashState): void {
this.el.classList.toggle('orthogonal-start', state !== SashState.Disabled);
}
private onOrthogonalEndSashEnablementChange(state: SashState): void {
this.el.classList.toggle('orthogonal-end', state !== SashState.Disabled);
}
private getOrthogonalSash(e: MouseEvent): Sash | undefined {
if (this.orientation === Orientation.VERTICAL) {
if (e.offsetY <= this.size) {
return this.orthogonalStartSash;
} else if (e.offsetY >= this.el.clientHeight - this.size) {
return this.orthogonalEndSash;
}
} else {
if (e.offsetX <= this.size) {
return this.orthogonalStartSash;
} else if (e.offsetX >= this.el.clientWidth - this.size) {
return this.orthogonalEndSash;
}
if (!e.target || !(e.target instanceof HTMLElement)) {
return undefined;
}
if (e.target.classList.contains('orthogonal-drag-handle')) {
return e.target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash;
}
return undefined;

View File

@ -736,8 +736,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(e => this.onUpArrow(e), this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(e => this.onDownArrow(e), this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this));
this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this));
@ -916,8 +916,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
// List navigation - have to handle a disabled option (jump over)
private onDownArrow(): void {
private onDownArrow(e: StandardKeyboardEvent): void {
if (this.selected < this.options.length - 1) {
dom.EventHelper.stop(e, true);
// Skip disabled options
const nextOptionDisabled = this.options[this.selected + 1].isDisabled;
@ -937,8 +938,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
}
private onUpArrow(): void {
private onUpArrow(e: StandardKeyboardEvent): void {
if (this.selected > 0) {
dom.EventHelper.stop(e, true);
// Skip disabled options
const previousOptionDisabled = this.options[this.selected - 1].isDisabled;
if (previousOptionDisabled && this.selected > 1) {

View File

@ -54,6 +54,7 @@
/* TODO: actions should be part of the pane, but they aren't yet */
.monaco-pane-view .pane:hover > .pane-header.expanded > .actions,
.monaco-pane-view .pane:focus-within > .pane-header.expanded > .actions,
.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions,
.monaco-pane-view .pane > .pane-header.focused.expanded > .actions {
display: initial;

View File

@ -432,7 +432,7 @@ export class PaneView extends Disposable {
private dnd: IPaneDndController | undefined;
private dndContext: IDndContext = { draggable: null };
private el: HTMLElement;
readonly element: HTMLElement;
private paneItems: IPaneItem[] = [];
private orthogonalSize: number = 0;
private size: number = 0;
@ -450,8 +450,8 @@ export class PaneView extends Disposable {
this.dnd = options.dnd;
this.orientation = options.orientation ?? Orientation.VERTICAL;
this.el = append(container, $('.monaco-pane-view'));
this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation }));
this.element = append(container, $('.monaco-pane-view'));
this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation }));
this.onDidSashChange = this.splitview.onDidSashChange;
}
@ -534,9 +534,9 @@ export class PaneView extends Disposable {
const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane));
this.splitview.dispose();
clearNode(this.el);
clearNode(this.element);
this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation }));
this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation }));
const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height;
const newSize = this.orientation === Orientation.HORIZONTAL ? width : height;
@ -560,11 +560,11 @@ export class PaneView extends Disposable {
window.clearTimeout(this.animationTimer);
}
this.el.classList.add('animated');
this.element.classList.add('animated');
this.animationTimer = window.setTimeout(() => {
this.animationTimer = undefined;
this.el.classList.remove('animated');
this.element.classList.remove('animated');
}, 200);
}

View File

@ -33,6 +33,8 @@ export interface ISplitViewOptions<TLayoutContext = undefined> {
readonly inverseAltBehavior?: boolean;
readonly proportionalLayout?: boolean; // default true,
readonly descriptor?: ISplitViewDescriptor<TLayoutContext>;
readonly scrollbarVisibility?: ScrollbarVisibility;
readonly getSashOrthogonalSize?: () => number;
}
/**
@ -200,7 +202,7 @@ export namespace Sizing {
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
}
export interface ISplitViewDescriptor<TLayoutContext> {
export interface ISplitViewDescriptor<TLayoutContext = undefined> {
size: number;
views: {
visible?: boolean;
@ -227,6 +229,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
private state: State = State.Idle;
private inverseAltBehavior: boolean;
private proportionalLayout: boolean;
private readonly getSashOrthogonalSize: { (): number } | undefined;
private _onDidSashChange = this._register(new Emitter<number>());
readonly onDidSashChange = this._onDidSashChange.event;
@ -298,6 +301,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.inverseAltBehavior = !!options.inverseAltBehavior;
this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout;
this.getSashOrthogonalSize = options.getSashOrthogonalSize;
this.el = document.createElement('div');
this.el.classList.add('monaco-split-view2');
@ -309,8 +313,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame);
this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, {
vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden,
horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden
}, this.scrollable));
this._register(this.scrollableElement.onScroll(e => {
@ -706,17 +710,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
// Add sash
if (this.viewItems.length > 1) {
let opts = { orthogonalStartSash: this.orthogonalStartSash, orthogonalEndSash: this.orthogonalEndSash };
const sash = this.orientation === Orientation.VERTICAL
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.HORIZONTAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
})
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.VERTICAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
? new Sash(this.sashContainer, { getHorizontalSashTop: s => this.getSashPosition(s), getHorizontalSashWidth: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.HORIZONTAL })
: new Sash(this.sashContainer, { getVerticalSashLeft: s => this.getSashPosition(s), getVerticalSashHeight: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.VERTICAL });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey })

View File

@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-table {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
width: 100%;
white-space: nowrap;
}
.monaco-table > .monaco-split-view2 {
border-bottom: 1px solid transparent;
}
.monaco-table > .monaco-list {
flex: 1;
}
.monaco-table-tr {
display: flex;
}
.monaco-table-th {
width: 100%;
height: 100%;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
.monaco-table-th,
.monaco-table-td {
box-sizing: border-box;
padding-left: 10px;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.monaco-table-th[data-col-index="0"],
.monaco-table-td[data-col-index="0"] {
padding-left: 20px;
}
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
content: "";
position: absolute;
left: calc(var(--sash-size) / 2);
width: 0;
border-left: 1px solid transparent;
}
.monaco-table > .monaco-split-view2,
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
transition: border-color 0.2s ease-out;
}
/*
.monaco-table:hover > .monaco-split-view2,
.monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before {
border-color: rgba(204, 204, 204, 0.2);
} */

View File

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IListContextMenuEvent, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
export interface ITableColumn<TRow, TCell> {
readonly label: string;
readonly tooltip?: string;
readonly weight: number;
readonly templateId: string;
readonly minimumWidth?: number;
readonly maximumWidth?: number;
readonly onDidChangeWidthConstraints?: Event<void>;
project(row: TRow): TCell;
}
export interface ITableVirtualDelegate<TRow> {
readonly headerRowHeight: number;
getHeight(row: TRow): number;
}
export interface ITableRenderer<TCell, TTemplateData> extends IListRenderer<TCell, TTemplateData> { }
export interface ITableEvent<TRow> extends IListEvent<TRow> { }
export interface ITableMouseEvent<TRow> extends IListMouseEvent<TRow> { }
export interface ITableTouchEvent<TRow> extends IListTouchEvent<TRow> { }
export interface ITableGestureEvent<TRow> extends IListGestureEvent<TRow> { }
export interface ITableContextMenuEvent<TRow> extends IListContextMenuEvent<TRow> { }
export class TableError extends Error {
constructor(user: string, message: string) {
super(`TableError [${user}] ${message}`);
}
}

View File

@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./table';
import { IListOptions, IListOptionsUpdate, IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table';
import { ISpliceable } from 'vs/base/common/sequence';
import { IThemable } from 'vs/base/common/styler';
import { IDisposable } from 'vs/base/common/lifecycle';
import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Emitter, Event } from 'vs/base/common/event';
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
// TODO@joao
type TCell = any;
interface RowTemplateData {
readonly container: HTMLElement;
readonly cellContainers: HTMLElement[];
readonly cellTemplateData: unknown[];
}
class TableListRenderer<TRow> implements IListRenderer<TRow, RowTemplateData> {
static TemplateId = 'row';
readonly templateId = TableListRenderer.TemplateId;
private renderers: ITableRenderer<TCell, unknown>[];
private renderedTemplates = new Set<RowTemplateData>();
constructor(
private columns: ITableColumn<TRow, TCell>[],
renderers: ITableRenderer<TCell, unknown>[],
private getColumnSize: (index: number) => number
) {
const rendererMap = new Map(renderers.map(r => [r.templateId, r]));
this.renderers = [];
for (const column of columns) {
const renderer = rendererMap.get(column.templateId);
if (!renderer) {
throw new Error(`Table cell renderer for template id ${column.templateId} not found.`);
}
this.renderers.push(renderer);
}
}
renderTemplate(container: HTMLElement) {
const rowContainer = append(container, $('.monaco-table-tr'));
const cellContainers: HTMLElement[] = [];
const cellTemplateData: unknown[] = [];
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
const cellContainer = append(rowContainer, $('.monaco-table-td', { 'data-col-index': i }));
cellContainer.style.width = `${this.getColumnSize(i)}px`;
cellContainers.push(cellContainer);
cellTemplateData.push(renderer.renderTemplate(cellContainer));
}
const result = { container, cellContainers, cellTemplateData };
this.renderedTemplates.add(result);
return result;
}
renderElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void {
for (let i = 0; i < this.columns.length; i++) {
const column = this.columns[i];
const cell = column.project(element);
const renderer = this.renderers[i];
renderer.renderElement(cell, index, templateData.cellTemplateData[i], height);
}
}
disposeElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void {
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
if (renderer.disposeElement) {
const column = this.columns[i];
const cell = column.project(element);
renderer.disposeElement(cell, index, templateData.cellTemplateData[i], height);
}
}
}
disposeTemplate(templateData: RowTemplateData): void {
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
renderer.disposeTemplate(templateData.cellTemplateData[i]);
}
clearNode(templateData.container);
this.renderedTemplates.delete(templateData);
}
layoutColumn(index: number, size: number): void {
for (const { cellContainers } of this.renderedTemplates) {
cellContainers[index].style.width = `${size}px`;
}
}
}
function asListVirtualDelegate<TRow>(delegate: ITableVirtualDelegate<TRow>): IListVirtualDelegate<TRow> {
return {
getHeight(row) { return delegate.getHeight(row); },
getTemplateId() { return TableListRenderer.TemplateId; },
};
}
class ColumnHeader<TRow, TCell> implements IView {
readonly element: HTMLElement;
get minimumSize() { return this.column.minimumWidth ?? 120; }
get maximumSize() { return this.column.maximumWidth ?? Number.POSITIVE_INFINITY; }
get onDidChange() { return this.column.onDidChangeWidthConstraints ?? Event.None; }
private _onDidLayout = new Emitter<[number, number]>();
readonly onDidLayout = this._onDidLayout.event;
constructor(readonly column: ITableColumn<TRow, TCell>, private index: number) {
this.element = $('.monaco-table-th', { 'data-col-index': index, title: column.tooltip }, column.label);
}
layout(size: number): void {
this._onDidLayout.fire([this.index, size]);
}
}
export interface ITableOptions<TRow> extends IListOptions<TRow> { }
export interface ITableOptionsUpdate extends IListOptionsUpdate { }
export interface ITableStyles extends IListStyles { }
export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
private static InstanceCount = 0;
readonly domId = `table_id_${++Table.InstanceCount}`;
readonly domNode: HTMLElement;
private splitview: SplitView;
private list: List<TRow>;
private columnLayoutDisposable: IDisposable;
private cachedHeight: number = 0;
private styleElement: HTMLStyleElement;
get onDidChangeFocus(): Event<ITableEvent<TRow>> { return this.list.onDidChangeFocus; }
get onDidChangeSelection(): Event<ITableEvent<TRow>> { return this.list.onDidChangeSelection; }
get onDidScroll(): Event<ScrollEvent> { return this.list.onDidScroll; }
get onMouseClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseClick; }
get onMouseDblClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDblClick; }
get onMouseMiddleClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMiddleClick; }
get onPointer(): Event<ITableMouseEvent<TRow>> { return this.list.onPointer; }
get onMouseUp(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseUp; }
get onMouseDown(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDown; }
get onMouseOver(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOver; }
get onMouseMove(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMove; }
get onMouseOut(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOut; }
get onTouchStart(): Event<ITableTouchEvent<TRow>> { return this.list.onTouchStart; }
get onTap(): Event<ITableGestureEvent<TRow>> { return this.list.onTap; }
get onContextMenu(): Event<ITableContextMenuEvent<TRow>> { return this.list.onContextMenu; }
get onDidFocus(): Event<void> { return this.list.onDidFocus; }
get onDidBlur(): Event<void> { return this.list.onDidBlur; }
get scrollTop(): number { return this.list.scrollTop; }
set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; }
get scrollLeft(): number { return this.list.scrollLeft; }
set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; }
get scrollHeight(): number { return this.list.scrollHeight; }
get renderHeight(): number { return this.list.renderHeight; }
get onDidDispose(): Event<void> { return this.list.onDidDispose; }
constructor(
user: string,
container: HTMLElement,
private virtualDelegate: ITableVirtualDelegate<TRow>,
columns: ITableColumn<TRow, TCell>[],
renderers: ITableRenderer<TCell, unknown>[],
_options?: ITableOptions<TRow>
) {
this.domNode = append(container, $(`.monaco-table.${this.domId}`));
const headers = columns.map((c, i) => new ColumnHeader(c, i));
const descriptor: ISplitViewDescriptor = {
size: headers.reduce((a, b) => a + b.column.weight, 0),
views: headers.map(view => ({ size: view.column.weight, view }))
};
this.splitview = new SplitView(this.domNode, {
orientation: Orientation.HORIZONTAL,
scrollbarVisibility: ScrollbarVisibility.Hidden,
getSashOrthogonalSize: () => this.cachedHeight,
descriptor
});
this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`;
this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`;
const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i));
this.list = new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options);
this.columnLayoutDisposable = Event.any(...headers.map(h => h.onDidLayout))
(([index, size]) => renderer.layoutColumn(index, size));
this.styleElement = createStyleSheet(this.domNode);
this.style({});
}
updateOptions(options: ITableOptionsUpdate): void {
this.list.updateOptions(options);
}
splice(start: number, deleteCount: number, elements: TRow[] = []): void {
this.list.splice(start, deleteCount, elements);
}
rerender(): void {
this.list.rerender();
}
row(index: number): TRow {
return this.list.element(index);
}
indexOf(element: TRow): number {
return this.list.indexOf(element);
}
get length(): number {
return this.list.length;
}
getHTMLElement(): HTMLElement {
return this.domNode;
}
layout(height?: number, width?: number): void {
height = height ?? getContentHeight(this.domNode);
width = width ?? getContentWidth(this.domNode);
this.cachedHeight = height;
this.splitview.layout(width);
this.list.layout(height - this.virtualDelegate.headerRowHeight, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
}
style(styles: ITableStyles): void {
const content: string[] = [];
content.push(`.monaco-table.${this.domId} > .monaco-split-view2 .monaco-sash.vertical::before {
top: ${this.virtualDelegate.headerRowHeight + 1}px;
height: calc(100% - ${this.virtualDelegate.headerRowHeight}px);
}`);
this.styleElement.textContent = content.join('\n');
this.list.style(styles);
}
domFocus(): void {
this.list.domFocus();
}
getSelectedElements(): TRow[] {
return this.list.getSelectedElements();
}
setSelection(indexes: number[], browserEvent?: UIEvent): void {
this.list.setSelection(indexes, browserEvent);
}
getSelection(): number[] {
return this.list.getSelection();
}
setFocus(indexes: number[], browserEvent?: UIEvent): void {
this.list.setFocus(indexes, browserEvent);
}
focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
this.list.focusNext(n, loop, browserEvent);
}
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
this.list.focusPrevious(n, loop, browserEvent);
}
focusNextPage(browserEvent?: UIEvent): void {
this.list.focusNextPage(browserEvent);
}
focusPreviousPage(browserEvent?: UIEvent): void {
this.list.focusPreviousPage(browserEvent);
}
focusFirst(browserEvent?: UIEvent): void {
this.list.focusFirst(browserEvent);
}
focusLast(browserEvent?: UIEvent): void {
this.list.focusLast(browserEvent);
}
getFocus(): number[] {
return this.list.getFocus();
}
reveal(index: number, relativeTop?: number): void {
this.list.reveal(index, relativeTop);
}
dispose(): void {
this.splitview.dispose();
this.list.dispose();
this.columnLayoutDisposable.dispose();
}
}

View File

@ -5,8 +5,8 @@
import 'vs/css!./toolbar';
import * as nls from 'vs/nls';
import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action, IActionRunner, IAction, SubmenuAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';

View File

@ -961,7 +961,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly filterOnType?: boolean;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
readonly expandOnlyOnDoubleClick?: boolean;
readonly expandOnDoubleClick?: boolean;
readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T
}
@ -1121,7 +1121,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
return super.onViewPointer(e);
}
if (this.tree.expandOnlyOnDoubleClick && e.browserEvent.detail !== 2 && !onTwistie) {
if (!this.tree.expandOnDoubleClick && e.browserEvent.detail === 2) {
return super.onViewPointer(e);
}
@ -1129,6 +1129,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
const model = ((this.tree as any).model as ITreeModel<T, TFilterData, TRef>); // internal
const location = model.getNodeLocation(node);
const recursive = e.browserEvent.altKey;
this.tree.setFocus([location]);
model.setCollapsed(location, undefined, recursive);
if (expandOnlyOnTwistieClick && onTwistie) {
@ -1142,7 +1143,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
protected onDoubleClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
const onTwistie = (e.browserEvent.target as HTMLElement).classList.contains('monaco-tl-twistie');
if (onTwistie) {
if (onTwistie || !this.tree.expandOnDoubleClick) {
return;
}
@ -1262,8 +1263,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get filterOnType(): boolean { return !!this._options.filterOnType; }
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
get expandOnlyOnDoubleClick(): boolean { return this._options.expandOnlyOnDoubleClick ?? false; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; }
private readonly _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
readonly onDidUpdateOptions: Event<IAbstractTreeOptions<T, TFilterData>> = this._onDidUpdateOptions.event;

View File

@ -9,7 +9,7 @@ import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOve
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { timeout, CancelablePromise, createCancelablePromise, Promises } from 'vs/base/common/async';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { Iterable } from 'vs/base/common/iterator';
import { IDragAndDropData } from 'vs/base/browser/dnd';
@ -740,7 +740,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
node.stale = false;
await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
} finally {
done!();
}
@ -990,16 +990,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
const expanded: string[] = [];
const root = this.tree.getNode();
const queue = [root];
const stack = [root];
while (queue.length > 0) {
const node = queue.shift()!;
while (stack.length > 0) {
const node = stack.pop()!;
if (node !== root && node.collapsible && !node.collapsed) {
expanded.push(getId(node.element!.element as T));
}
queue.push(...node.children);
stack.push(...node.children);
}
return { focus, selection, expanded, scrollTop: this.scrollTop };

View File

@ -56,6 +56,10 @@
overflow: hidden;
}
.monaco-tl-twistie::before {
border-radius: 20px;
}
.monaco-tl-twistie.collapsed::before {
transform: rotate(-90deg);
}

View File

@ -69,7 +69,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
this.model.updateElementHeight(element, height);
}
resort(element: T, recursive = true): void {
resort(element: T | null, recursive = true): void {
this.model.resort(element, recursive);
}