chore(vscode): update to 1.54.2
This commit is contained in:
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>([
|
||||
['"', '"'],
|
||||
['&', '&'],
|
||||
[''', '\''],
|
||||
['<', '<'],
|
||||
['>', '>'],
|
||||
]);
|
||||
|
||||
const html = marked.parse(value, { renderer }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m);
|
||||
|
||||
return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Binary file not shown.
@ -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}'; }`;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,5 +20,4 @@ export interface IHoverDelegateOptions {
|
||||
|
||||
export interface IHoverDelegate {
|
||||
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
|
||||
hideHover(): void;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -89,7 +89,6 @@
|
||||
padding: 0.4em;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
min-height: 34px;
|
||||
margin-top: -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
.menubar .menubar-menu-items-holder {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
opacity: 1;
|
||||
z-index: 2000;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 })
|
||||
|
66
lib/vscode/src/vs/base/browser/ui/table/table.css
Normal file
66
lib/vscode/src/vs/base/browser/ui/table/table.css
Normal 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);
|
||||
} */
|
40
lib/vscode/src/vs/base/browser/ui/table/table.ts
Normal file
40
lib/vscode/src/vs/base/browser/ui/table/table.ts
Normal 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}`);
|
||||
}
|
||||
}
|
329
lib/vscode/src/vs/base/browser/ui/table/tableWidget.ts
Normal file
329
lib/vscode/src/vs/base/browser/ui/table/tableWidget.ts
Normal 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();
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -56,6 +56,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-tl-twistie::before {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.monaco-tl-twistie.collapsed::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user