mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 09:35:39 -05:00
Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)
This commit is contained in:
@@ -28,7 +28,7 @@ class WindowManager {
|
||||
}
|
||||
|
||||
this._zoomLevel = zoomLevel;
|
||||
// See https://github.com/Microsoft/vscode/issues/26151
|
||||
// See https://github.com/microsoft/vscode/issues/26151
|
||||
this._lastZoomLevelChangeTime = isTrusted ? 0 : Date.now();
|
||||
this._onDidChangeZoomLevel.fire(this._zoomLevel);
|
||||
}
|
||||
@@ -115,7 +115,6 @@ 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 = (userAgent.indexOf('Edge/') >= 0) && (userAgent.indexOf('WebView/') >= 0);
|
||||
export const isElectron = (userAgent.indexOf('Electron/') >= 0);
|
||||
export const isAndroid = (userAgent.indexOf('Android') >= 0);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { FileAccess, RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
import { insane, InsaneOptions } from 'vs/base/common/insane/insane';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export function clearNode(node: HTMLElement): void {
|
||||
while (node.firstChild) {
|
||||
@@ -25,79 +26,12 @@ export function clearNode(node: HTMLElement): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `node.remove()` instead
|
||||
* @deprecated Use node.isConnected directly
|
||||
*/
|
||||
export function removeNode(node: HTMLElement): void {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
export function trustedInnerHTML(node: Element, value: TrustedHTML): void {
|
||||
// this is a workaround for innerHTML not allowing for "asymetric" accessors
|
||||
// see https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393
|
||||
// and https://github.com/microsoft/TypeScript/issues/30024
|
||||
node.innerHTML = value as unknown as string;
|
||||
}
|
||||
|
||||
export function isInDOM(node: Node | null): boolean {
|
||||
return node?.isConnected ?? false;
|
||||
}
|
||||
|
||||
interface IDomClassList {
|
||||
hasClass(node: HTMLElement | SVGElement, className: string): boolean;
|
||||
addClass(node: HTMLElement | SVGElement, className: string): void;
|
||||
addClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void;
|
||||
removeClass(node: HTMLElement | SVGElement, className: string): void;
|
||||
removeClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void;
|
||||
toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void;
|
||||
}
|
||||
|
||||
const _classList: IDomClassList = new class implements IDomClassList {
|
||||
hasClass(node: HTMLElement, className: string): boolean {
|
||||
return Boolean(className) && node.classList && node.classList.contains(className);
|
||||
}
|
||||
|
||||
addClasses(node: HTMLElement, ...classNames: string[]): void {
|
||||
classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.addClass(node, name)));
|
||||
}
|
||||
|
||||
addClass(node: HTMLElement, className: string): void {
|
||||
if (className && node.classList) {
|
||||
node.classList.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
removeClass(node: HTMLElement, className: string): void {
|
||||
if (className && node.classList) {
|
||||
node.classList.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
removeClasses(node: HTMLElement, ...classNames: string[]): void {
|
||||
classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.removeClass(node, name)));
|
||||
}
|
||||
|
||||
toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void {
|
||||
if (node.classList) {
|
||||
node.classList.toggle(className, shouldHaveIt);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function hasClass(node: HTMLElement | SVGElement, className: string): boolean { return _classList.hasClass(node, className); }
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function addClass(node: HTMLElement | SVGElement, className: string): void { return _classList.addClass(node, className); }
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function addClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void { return _classList.addClasses(node, ...classNames); }
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function removeClass(node: HTMLElement | SVGElement, className: string): void { return _classList.removeClass(node, className); }
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function removeClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void { return _classList.removeClasses(node, ...classNames); }
|
||||
/** @deprecated ES6 - use classList*/
|
||||
export function toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void { return _classList.toggleClass(node, className, shouldHaveIt); }
|
||||
|
||||
class DomListener implements IDisposable {
|
||||
|
||||
private _handler: (e: any) => void;
|
||||
@@ -414,16 +348,7 @@ export function getClientArea(element: HTMLElement): Dimension {
|
||||
|
||||
// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
|
||||
if (platform.isIOS && window.visualViewport) {
|
||||
const width = window.visualViewport.width;
|
||||
const height = window.visualViewport.height - (
|
||||
browser.isStandalone
|
||||
// in PWA mode, the visual viewport always includes the safe-area-inset-bottom (which is for the home indicator)
|
||||
// even when you are using the onscreen monitor, the visual viewport will include the area between system statusbar and the onscreen keyboard
|
||||
// plus the area between onscreen keyboard and the bottom bezel, which is 20px on iOS.
|
||||
? (20 + 4) // + 4px for body margin
|
||||
: 0
|
||||
);
|
||||
return new Dimension(width, height);
|
||||
return new Dimension(window.visualViewport.width, window.visualViewport.height);
|
||||
}
|
||||
|
||||
// Try innerWidth / innerHeight
|
||||
@@ -1254,28 +1179,44 @@ export function computeScreenAwareSize(cssPx: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Open safely a new window. This is the best way to do so, but you cannot tell
|
||||
* if the window was opened or if it was blocked by the brower's popup blocker.
|
||||
* If you want to tell if the browser blocked the new window, use `windowOpenNoOpenerWithSuccess`.
|
||||
*
|
||||
* See https://github.com/microsoft/monaco-editor/issues/601
|
||||
* To protect against malicious code in the linked site, particularly phishing attempts,
|
||||
* the window.opener should be set to null to prevent the linked site from having access
|
||||
* to change the location of the current page.
|
||||
* See https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
export function windowOpenNoOpener(url: string): boolean {
|
||||
if (browser.isElectron || browser.isEdgeLegacyWebView) {
|
||||
// In VSCode, window.open() always returns null...
|
||||
// The same is true for a WebView (see https://github.com/microsoft/monaco-editor/issues/628)
|
||||
// Also call directly window.open in sandboxed Electron (see https://github.com/microsoft/monaco-editor/issues/2220)
|
||||
window.open(url);
|
||||
export function windowOpenNoOpener(url: string): void {
|
||||
// By using 'noopener' in the `windowFeatures` argument, the newly created window will
|
||||
// not be able to use `window.opener` to reach back to the current page.
|
||||
// See https://stackoverflow.com/a/46958731
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Window/open#noopener
|
||||
// However, this also doesn't allow us to realize if the browser blocked
|
||||
// the creation of the window.
|
||||
window.open(url, '_blank', 'noopener');
|
||||
}
|
||||
|
||||
/**
|
||||
* Open safely a new window. This technique is not appropiate in certain contexts,
|
||||
* like for example when the JS context is executing inside a sandboxed iframe.
|
||||
* If it is not necessary to know if the browser blocked the new window, use
|
||||
* `windowOpenNoOpener`.
|
||||
*
|
||||
* See https://github.com/microsoft/monaco-editor/issues/601
|
||||
* See https://github.com/microsoft/monaco-editor/issues/2474
|
||||
* See https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
export function windowOpenNoOpenerWithSuccess(url: string): boolean {
|
||||
const newTab = window.open();
|
||||
if (newTab) {
|
||||
(newTab as any).opener = null;
|
||||
newTab.location.href = url;
|
||||
return true;
|
||||
} else {
|
||||
let newTab = window.open();
|
||||
if (newTab) {
|
||||
(newTab as any).opener = null;
|
||||
newTab.location.href = url;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function animate(fn: () => void): IDisposable {
|
||||
@@ -1333,6 +1274,29 @@ export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void
|
||||
setTimeout(() => document.body.removeChild(anchor));
|
||||
}
|
||||
|
||||
export function triggerUpload(): Promise<FileList | undefined> {
|
||||
return new Promise<FileList | undefined>(resolve => {
|
||||
|
||||
// In order to upload to the browser, create a
|
||||
// input element of type `file` and click it
|
||||
// to gather the selected files
|
||||
const input = document.createElement('input');
|
||||
document.body.appendChild(input);
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
|
||||
// Resolve once the input event has fired once
|
||||
Event.once(Event.fromDOMEventEmitter(input, 'input'))(() => {
|
||||
resolve(withNullAsUndefined(input.files));
|
||||
});
|
||||
|
||||
input.click();
|
||||
|
||||
// Ensure to remove the element from DOM eventually
|
||||
setTimeout(() => document.body.removeChild(input));
|
||||
});
|
||||
}
|
||||
|
||||
export enum DetectedFullscreenMode {
|
||||
|
||||
/**
|
||||
@@ -1518,6 +1482,9 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
};
|
||||
|
||||
this._subscriptions.add(domEvent(window, 'keydown', true)(e => {
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
// If Alt-key keydown event is repeated, ignore it #112347
|
||||
@@ -1552,6 +1519,10 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
}));
|
||||
|
||||
this._subscriptions.add(domEvent(window, 'keyup', true)(e => {
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.altKey && this._keyStatus.altKey) {
|
||||
this._keyStatus.lastKeyReleased = 'alt';
|
||||
} else if (!e.ctrlKey && this._keyStatus.ctrlKey) {
|
||||
@@ -1642,3 +1613,13 @@ export function getCookieValue(name: string): string | undefined {
|
||||
|
||||
return match ? match.pop() : undefined;
|
||||
}
|
||||
|
||||
export function addMatchMediaChangeListener(query: string, callback: () => void): void {
|
||||
const mediaQueryList = window.matchMedia(query);
|
||||
if (typeof mediaQueryList.addEventListener === 'function') {
|
||||
mediaQueryList.addEventListener('change', callback);
|
||||
} else {
|
||||
// Safari 13.x
|
||||
mediaQueryList.addListener(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { GestureEvent } from 'vs/base/browser/touch';
|
||||
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export type EventHandler = HTMLElement | HTMLDocument | Window;
|
||||
|
||||
@@ -12,6 +14,9 @@ export interface IDomEvent {
|
||||
(element: EventHandler, type: string, useCapture?: boolean): BaseEvent<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `DomEmitter` instead
|
||||
*/
|
||||
export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapture?: boolean) => {
|
||||
const fn = (e: Event) => emitter.fire(e);
|
||||
const emitter = new Emitter<Event>({
|
||||
@@ -26,6 +31,35 @@ export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapt
|
||||
return emitter.event;
|
||||
};
|
||||
|
||||
export interface DOMEventMap extends HTMLElementEventMap {
|
||||
'-monaco-gesturetap': GestureEvent;
|
||||
'-monaco-gesturechange': GestureEvent;
|
||||
'-monaco-gesturestart': GestureEvent;
|
||||
'-monaco-gesturesend': GestureEvent;
|
||||
'-monaco-gesturecontextmenu': GestureEvent;
|
||||
}
|
||||
|
||||
export class DomEmitter<K extends keyof DOMEventMap> implements IDisposable {
|
||||
|
||||
private emitter: Emitter<DOMEventMap[K]>;
|
||||
|
||||
get event(): BaseEvent<DOMEventMap[K]> {
|
||||
return this.emitter.event;
|
||||
}
|
||||
|
||||
constructor(element: EventHandler, type: K, useCapture?: boolean) {
|
||||
const fn = (e: Event) => this.emitter.fire(e as DOMEventMap[K]);
|
||||
this.emitter = new Emitter({
|
||||
onFirstListenerAdd: () => element.addEventListener(type, fn, useCapture),
|
||||
onLastListenerRemove: () => element.removeEventListener(type, fn, useCapture)
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.emitter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface CancellableEvent {
|
||||
preventDefault(): void;
|
||||
stopPropagation(): void;
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { IframeUtils } from 'vs/base/browser/iframe';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isIOS } from 'vs/base/common/platform';
|
||||
|
||||
export interface IStandardMouseMoveEventData {
|
||||
leftButton: boolean;
|
||||
@@ -88,7 +89,7 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
|
||||
this._onStopCallback = onStopCallback;
|
||||
|
||||
const windowChain = IframeUtils.getSameOriginWindowChain();
|
||||
const mouseMove = 'mousemove';
|
||||
const mouseMove = isIOS ? 'pointermove' : 'mousemove'; // Safari sends wrong event, workaround for #122653
|
||||
const mouseUp = 'mouseup';
|
||||
|
||||
const listenTo: (Document | ShadowRoot)[] = windowChain.map(element => element.window.document);
|
||||
|
||||
@@ -9,4 +9,4 @@ export interface IHistoryNavigationWidget {
|
||||
|
||||
showNextValue(): void;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,18 +43,6 @@ function getParentWindowIfSameOrigin(w: Window): Window | null {
|
||||
return w.parent;
|
||||
}
|
||||
|
||||
function findIframeElementInParentWindow(parentWindow: Window, childWindow: Window): HTMLIFrameElement | null {
|
||||
let parentWindowIframes = parentWindow.document.getElementsByTagName('iframe');
|
||||
let iframe: HTMLIFrameElement;
|
||||
for (let i = 0, len = parentWindowIframes.length; i < len; i++) {
|
||||
iframe = parentWindowIframes[i];
|
||||
if (iframe.contentWindow === childWindow) {
|
||||
return iframe;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class IframeUtils {
|
||||
|
||||
/**
|
||||
@@ -72,7 +60,7 @@ export class IframeUtils {
|
||||
if (parent) {
|
||||
sameOriginWindowChainCache.push({
|
||||
window: w,
|
||||
iframeElement: findIframeElementInParentWindow(parent, w)
|
||||
iframeElement: w.frameElement || null
|
||||
});
|
||||
} else {
|
||||
sameOriginWindowChainCache.push({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom'; // {{SQL CARBON EDIT}} added missing import to fix build break
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
@@ -180,15 +180,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
});
|
||||
});
|
||||
|
||||
// const promise = Promise.all([value, withInnerHTML]).then(values => {
|
||||
// const span = <HTMLDivElement>element.querySelector(`div[data-code="${id}"]`);
|
||||
// if (span) {
|
||||
// DOM.reset(span, values[0]);
|
||||
// }
|
||||
// }).catch(_err => {
|
||||
// // ignore
|
||||
// });
|
||||
|
||||
if (options.asyncRenderCallback) {
|
||||
promise.then(options.asyncRenderCallback);
|
||||
}
|
||||
@@ -324,6 +315,14 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips all markdown from `string`, if it's an IMarkdownString. For example
|
||||
* `# Header` would be output as `Header`. If it's not, the string is returned.
|
||||
*/
|
||||
export function renderStringAsPlaintext(string: IMarkdownString | string) {
|
||||
return typeof string === 'string' ? string : renderMarkdownAsPlaintext(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips all markdown from `markdown`. For example `# Header` would be output as `Header`.
|
||||
*/
|
||||
@@ -398,6 +397,7 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString) {
|
||||
|
||||
const unescapeInfo = new Map<string, string>([
|
||||
['"', '"'],
|
||||
[' ', ' '],
|
||||
['&', '&'],
|
||||
[''', '\''],
|
||||
['<', '<'],
|
||||
|
||||
@@ -75,6 +75,16 @@
|
||||
margin-right: .8em;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.separator {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
margin: 5px 4px !important;
|
||||
cursor: default;
|
||||
min-width: 1px;
|
||||
padding: 0;
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
.secondary-actions .monaco-action-bar .action-label {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
.monaco-aria-container {
|
||||
position: absolute; /* try to hide from window but not from screen readers */
|
||||
left:-999em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
.monaco-button-dropdown {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown > .monaco-dropdown-button {
|
||||
|
||||
@@ -185,6 +185,7 @@ export class Button extends Disposable implements IButton {
|
||||
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
|
||||
this.buttonBorder = styles.buttonBorder;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.buttonSecondaryBorder = styles.buttonSecondaryBorder;
|
||||
this.buttonDisabledBackground = styles.buttonDisabledBackground;
|
||||
this.buttonDisabledForeground = styles.buttonDisabledForeground;
|
||||
|
||||
@@ -165,6 +165,7 @@ export class CenteredViewLayout implements IDisposable {
|
||||
this.splitView = undefined;
|
||||
this.emptyViews = undefined;
|
||||
this.container.appendChild(this.view.element);
|
||||
this.view.layout(this.width, this.height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -87,7 +87,7 @@ export class Dialog extends Disposable {
|
||||
private readonly inputs: InputBox[];
|
||||
private readonly buttons: string[];
|
||||
|
||||
constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) {
|
||||
constructor(private container: HTMLElement, private message: string, buttons: string[] | undefined, private options: IDialogOptions) {
|
||||
super();
|
||||
|
||||
this.modalElement = this.container.appendChild($(`.monaco-dialog-modal-block.dimmed`));
|
||||
@@ -96,21 +96,22 @@ export class Dialog extends Disposable {
|
||||
this.element.setAttribute('role', 'dialog');
|
||||
hide(this.element);
|
||||
|
||||
this.buttons = buttons.length ? buttons : [nls.localize('ok', "OK")]; // If no button is provided, default to OK
|
||||
this.buttons = Array.isArray(buttons) && buttons.length ? buttons : [nls.localize('ok', "OK")]; // If no button is provided, default to OK
|
||||
const buttonsRowElement = this.element.appendChild($('.dialog-buttons-row'));
|
||||
this.buttonsContainer = buttonsRowElement.appendChild($('.dialog-buttons'));
|
||||
|
||||
const messageRowElement = this.element.appendChild($('.dialog-message-row'));
|
||||
this.iconElement = messageRowElement.appendChild($('.dialog-icon'));
|
||||
this.iconElement = messageRowElement.appendChild($('#monaco-dialog-icon.dialog-icon'));
|
||||
this.iconElement.setAttribute('aria-label', this.getIconAriaLabel());
|
||||
this.messageContainer = messageRowElement.appendChild($('.dialog-message-container'));
|
||||
|
||||
if (this.options.detail || this.options.renderBody) {
|
||||
const messageElement = this.messageContainer.appendChild($('.dialog-message'));
|
||||
const messageTextElement = messageElement.appendChild($('.dialog-message-text'));
|
||||
const messageTextElement = messageElement.appendChild($('#monaco-dialog-message-text.dialog-message-text'));
|
||||
messageTextElement.innerText = this.message;
|
||||
}
|
||||
|
||||
this.messageDetailElement = this.messageContainer.appendChild($('.dialog-message-detail'));
|
||||
this.messageDetailElement = this.messageContainer.appendChild($('#monaco-dialog-message-detail.dialog-message-detail'));
|
||||
if (this.options.detail || !this.options.renderBody) {
|
||||
this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message;
|
||||
} else {
|
||||
@@ -118,8 +119,12 @@ export class Dialog extends Disposable {
|
||||
}
|
||||
|
||||
if (this.options.renderBody) {
|
||||
const customBody = this.messageContainer.appendChild($('.dialog-message-body'));
|
||||
const customBody = this.messageContainer.appendChild($('#monaco-dialog-message-body.dialog-message-body'));
|
||||
this.options.renderBody(customBody);
|
||||
|
||||
for (const el of this.messageContainer.querySelectorAll('a')) {
|
||||
el.tabIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.inputs) {
|
||||
@@ -157,7 +162,7 @@ export class Dialog extends Disposable {
|
||||
this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar'));
|
||||
}
|
||||
|
||||
private getAriaLabel(): string {
|
||||
private getIconAriaLabel(): string {
|
||||
let typeLabel = nls.localize('dialogInfoMessage', 'Info');
|
||||
switch (this.options.type) {
|
||||
case 'error':
|
||||
@@ -176,7 +181,7 @@ export class Dialog extends Disposable {
|
||||
break;
|
||||
}
|
||||
|
||||
return `${typeLabel}: ${this.message} ${this.options.detail || ''}`;
|
||||
return typeLabel;
|
||||
}
|
||||
|
||||
updateMessage(message: string): void {
|
||||
@@ -218,6 +223,10 @@ export class Dialog extends Disposable {
|
||||
this._register(domEvent(window, 'keydown', true)((e: KeyboardEvent) => {
|
||||
const evt = new StandardKeyboardEvent(e);
|
||||
|
||||
if (evt.equals(KeyMod.Alt)) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
if (evt.equals(KeyCode.Enter)) {
|
||||
|
||||
// Enter in input field should OK the dialog
|
||||
@@ -246,6 +255,17 @@ export class Dialog extends Disposable {
|
||||
// Build a list of focusable elements in their visual order
|
||||
const focusableElements: { focus: () => void }[] = [];
|
||||
let focusedIndex = -1;
|
||||
|
||||
if (this.messageContainer) {
|
||||
const links = this.messageContainer.querySelectorAll('a');
|
||||
for (const link of links) {
|
||||
focusableElements.push(link);
|
||||
if (link === document.activeElement) {
|
||||
focusedIndex = focusableElements.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const input of this.inputs) {
|
||||
focusableElements.push(input);
|
||||
if (input.hasFocus()) {
|
||||
@@ -371,7 +391,7 @@ export class Dialog extends Disposable {
|
||||
|
||||
this.applyStyles();
|
||||
|
||||
this.element.setAttribute('aria-label', this.getAriaLabel());
|
||||
this.element.setAttribute('aria-labelledby', 'monaco-dialog-icon monaco-dialog-message-text monaco-dialog-message-detail monaco-dialog-message-body');
|
||||
show(this.element);
|
||||
|
||||
// Focus first element (input or button)
|
||||
|
||||
@@ -37,3 +37,10 @@
|
||||
line-height: 16px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.monaco-dropdown-with-primary > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label {
|
||||
display: block;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
|
||||
this._register(addDisposableListener(this._label, event, e => {
|
||||
if (e instanceof MouseEvent && e.detail > 1) {
|
||||
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
|
||||
return; // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363)
|
||||
}
|
||||
|
||||
if (this.visible) {
|
||||
@@ -71,7 +71,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
this._register(addDisposableListener(this._label, EventType.KEY_UP, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
EventHelper.stop(e, true); // https://github.com/Microsoft/vscode/issues/57997
|
||||
EventHelper.stop(e, true); // https://github.com/microsoft/vscode/issues/57997
|
||||
|
||||
if (this.visible) {
|
||||
this.hide();
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
|
||||
private _primaryAction: ActionViewItem;
|
||||
private _dropdown: DropdownMenuActionViewItem;
|
||||
private _container: HTMLElement | null = null;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
primaryAction: IAction,
|
||||
dropdownAction: IAction,
|
||||
dropdownMenuActions: IAction[],
|
||||
_className: string,
|
||||
private readonly _contextMenuProvider: IContextMenuProvider,
|
||||
dropdownIcon?: string
|
||||
) {
|
||||
super(null, primaryAction);
|
||||
this._primaryAction = new ActionViewItem(undefined, primaryAction, {
|
||||
icon: true,
|
||||
label: false
|
||||
});
|
||||
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
|
||||
menuAsChild: true
|
||||
});
|
||||
this.toDispose = [];
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
this._container = container;
|
||||
super.render(this._container);
|
||||
this._container.classList.add('monaco-dropdown-with-primary');
|
||||
const primaryContainer = DOM.$('.action-container');
|
||||
this._primaryAction.render(DOM.append(this._container, primaryContainer));
|
||||
const dropdownContainer = DOM.$('.dropdown-action-container');
|
||||
this._dropdown.render(DOM.append(this._container, dropdownContainer));
|
||||
|
||||
this.toDispose.push(DOM.addDisposableListener(primaryContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.RightArrow)) {
|
||||
this._primaryAction.element!.tabIndex = -1;
|
||||
this._dropdown.focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(DOM.addDisposableListener(dropdownContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
this._primaryAction.element!.tabIndex = 0;
|
||||
this._dropdown.setFocusable(false);
|
||||
this._primaryAction.element?.focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
override focus(fromRight?: boolean): void {
|
||||
if (fromRight) {
|
||||
this._dropdown.focus();
|
||||
} else {
|
||||
this._primaryAction.element!.tabIndex = 0;
|
||||
this._primaryAction.element!.focus();
|
||||
}
|
||||
}
|
||||
|
||||
override blur(): void {
|
||||
this._primaryAction.element!.tabIndex = -1;
|
||||
this._dropdown.blur();
|
||||
this._container!.blur();
|
||||
}
|
||||
|
||||
override setFocusable(focusable: boolean): void {
|
||||
if (focusable) {
|
||||
this._primaryAction.element!.tabIndex = 0;
|
||||
} else {
|
||||
this._primaryAction.element!.tabIndex = -1;
|
||||
this._dropdown.setFocusable(false);
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
|
||||
update(dropdownAction: IAction, dropdownMenuActions: IAction[], dropdownIcon?: string): void {
|
||||
this._dropdown?.dispose();
|
||||
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
|
||||
menuAsChild: true,
|
||||
classNames: ['codicon', dropdownIcon || 'codicon-chevron-down']
|
||||
});
|
||||
if (this.element) {
|
||||
this._dropdown.render(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,4 +62,4 @@
|
||||
0% { background: rgba(255, 255, 255, 0.44); }
|
||||
/* Made intentionally different such that the CSS minifier does not collapse the two animations into a single one*/
|
||||
99% { background: transparent; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class FindInput extends Widget {
|
||||
const flexibleMaxHeight = options.flexibleMaxHeight;
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
dom.addClass(this.domNode, 'monaco-findInput');
|
||||
this.domNode.classList.add('monaco-findInput');
|
||||
|
||||
this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, {
|
||||
placeholder: this.placeholder || '',
|
||||
@@ -263,7 +263,7 @@ export class FindInput extends Widget {
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
dom.removeClass(this.domNode, 'disabled');
|
||||
this.domNode.classList.remove('disabled');
|
||||
this.inputBox.enable();
|
||||
this.regex.enable();
|
||||
this.wholeWords.enable();
|
||||
@@ -271,7 +271,7 @@ export class FindInput extends Widget {
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
dom.addClass(this.domNode, 'disabled');
|
||||
this.domNode.classList.add('disabled');
|
||||
this.inputBox.disable();
|
||||
this.regex.disable();
|
||||
this.wholeWords.disable();
|
||||
@@ -403,9 +403,9 @@ export class FindInput extends Widget {
|
||||
|
||||
private _lastHighlightFindOptions: number = 0;
|
||||
public highlightFindOptions(): void {
|
||||
dom.removeClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions));
|
||||
this.domNode.classList.remove('highlight-' + (this._lastHighlightFindOptions));
|
||||
this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions;
|
||||
dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions));
|
||||
this.domNode.classList.add('highlight-' + (this._lastHighlightFindOptions));
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
|
||||
@@ -136,7 +136,7 @@ export class ReplaceInput extends Widget {
|
||||
const flexibleMaxHeight = options.flexibleMaxHeight;
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
dom.addClass(this.domNode, 'monaco-findInput');
|
||||
this.domNode.classList.add('monaco-findInput');
|
||||
|
||||
this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, {
|
||||
ariaLabel: this.label || '',
|
||||
@@ -234,13 +234,13 @@ export class ReplaceInput extends Widget {
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
dom.removeClass(this.domNode, 'disabled');
|
||||
this.domNode.classList.remove('disabled');
|
||||
this.inputBox.enable();
|
||||
this.preserveCase.enable();
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
dom.addClass(this.domNode, 'disabled');
|
||||
this.domNode.classList.add('disabled');
|
||||
this.inputBox.disable();
|
||||
this.preserveCase.disable();
|
||||
}
|
||||
@@ -347,9 +347,9 @@ export class ReplaceInput extends Widget {
|
||||
|
||||
private _lastHighlightFindOptions: number = 0;
|
||||
public highlightFindOptions(): void {
|
||||
dom.removeClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions));
|
||||
this.domNode.classList.remove('highlight-' + (this._lastHighlightFindOptions));
|
||||
this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions;
|
||||
dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions));
|
||||
this.domNode.classList.add('highlight-' + (this._lastHighlightFindOptions));
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
|
||||
@@ -210,7 +210,9 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
get minimumHeight(): number { return this.gridview.minimumHeight; }
|
||||
get maximumWidth(): number { return this.gridview.maximumWidth; }
|
||||
get maximumHeight(): number { return this.gridview.maximumHeight; }
|
||||
get onDidChange(): Event<{ width: number; height: number; } | undefined> { return this.gridview.onDidChange; }
|
||||
|
||||
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
|
||||
readonly onDidScroll: Event<void>;
|
||||
|
||||
get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; }
|
||||
set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; }
|
||||
@@ -232,8 +234,8 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
} else {
|
||||
this.gridview = new GridView(options);
|
||||
}
|
||||
this._register(this.gridview);
|
||||
|
||||
this._register(this.gridview);
|
||||
this._register(this.gridview.onDidSashReset(this.onDidSashReset, this));
|
||||
|
||||
const size: number | GridViewSizing = typeof options.firstViewVisibleCachedSize === 'number'
|
||||
@@ -243,6 +245,9 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
if (!(view instanceof GridView)) {
|
||||
this._addView(view, size, [0]);
|
||||
}
|
||||
|
||||
this.onDidChange = this.gridview.onDidChange;
|
||||
this.onDidScroll = this.gridview.onDidScroll;
|
||||
}
|
||||
|
||||
style(styles: IGridStyles): void {
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
.monaco-grid-branch-node {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { SplitView, IView as ISplitView, Sizing, LayoutPriority, ISplitViewStyles } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { tail2 as tail } from 'vs/base/common/arrays';
|
||||
import { equals as arrayEquals, tail2 as tail } from 'vs/base/common/arrays';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
|
||||
export { Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
@@ -242,6 +243,10 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
private readonly _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
private _onDidScroll = new Emitter<void>();
|
||||
private onDidScrollDisposable: IDisposable = Disposable.None;
|
||||
readonly onDidScroll: Event<void> = this._onDidScroll.event;
|
||||
|
||||
private childrenChangeDisposable: IDisposable = Disposable.None;
|
||||
|
||||
private readonly _onDidSashReset = new Emitter<number[]>();
|
||||
@@ -343,11 +348,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]);
|
||||
this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
|
||||
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
|
||||
this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange);
|
||||
|
||||
const onDidChildrenSashReset = Event.any(...this.children.map((c, i) => Event.map(c.onDidSashReset, location => [i, ...location])));
|
||||
this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
this.updateChildrenEvents();
|
||||
}
|
||||
|
||||
style(styles: IGridViewStyles): void {
|
||||
@@ -566,6 +567,11 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
}
|
||||
|
||||
private onDidChildrenChange(): void {
|
||||
this.updateChildrenEvents();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
private updateChildrenEvents(): void {
|
||||
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
|
||||
this.childrenChangeDisposable.dispose();
|
||||
this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange);
|
||||
@@ -574,7 +580,9 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this.childrenSashResetDisposable.dispose();
|
||||
this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
|
||||
this._onDidChange.fire(undefined);
|
||||
const onDidScroll = Event.any(Event.signal(this.splitview.onDidScroll), ...this.children.map(c => c.onDidScroll));
|
||||
this.onDidScrollDisposable.dispose();
|
||||
this.onDidScrollDisposable = onDidScroll(this._onDidScroll.fire, this._onDidScroll);
|
||||
}
|
||||
|
||||
trySet2x2(other: BranchNode): IDisposable {
|
||||
@@ -646,6 +654,25 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a latched event that avoids being fired when the view
|
||||
* constraints do not change at all.
|
||||
*/
|
||||
function createLatchedOnDidChangeViewEvent(view: IView): Event<IViewSize | undefined> {
|
||||
const [onDidChangeViewConstraints, onDidSetViewSize] = Event.split<undefined, IViewSize>(view.onDidChange, isUndefined);
|
||||
|
||||
return Event.any(
|
||||
onDidSetViewSize,
|
||||
Event.map(
|
||||
Event.latch(
|
||||
Event.map(onDidChangeViewConstraints, _ => ([view.minimumWidth, view.maximumWidth, view.minimumHeight, view.maximumHeight])),
|
||||
arrayEquals
|
||||
),
|
||||
_ => undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
|
||||
private _size: number = 0;
|
||||
@@ -657,6 +684,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
private absoluteOffset: number = 0;
|
||||
private absoluteOrthogonalOffset: number = 0;
|
||||
|
||||
readonly onDidScroll: Event<void> = Event.None;
|
||||
readonly onDidSashReset: Event<number[]> = Event.None;
|
||||
|
||||
private _onDidLinkedWidthNodeChange = new Relay<number | undefined>();
|
||||
@@ -691,7 +719,8 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this._size = size;
|
||||
|
||||
this._onDidViewChange = Event.map(this.view.onDidChange, e => e && (this.orientation === Orientation.VERTICAL ? e.width : e.height));
|
||||
const onDidChange = createLatchedOnDidChangeViewEvent(view);
|
||||
this._onDidViewChange = Event.map(onDidChange, e => e && (this.orientation === Orientation.VERTICAL ? e.width : e.height));
|
||||
this.onDidChange = Event.any(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event);
|
||||
}
|
||||
|
||||
@@ -778,7 +807,25 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this._orthogonalSize = ctx.orthogonalSize;
|
||||
this.absoluteOffset = ctx.absoluteOffset + offset;
|
||||
this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
|
||||
this.view.layout(this.width, this.height, this.top, this.left);
|
||||
|
||||
this._layout(this.width, this.height, this.top, this.left);
|
||||
}
|
||||
|
||||
private cachedWidth: number = 0;
|
||||
private cachedHeight: number = 0;
|
||||
private cachedTop: number = 0;
|
||||
private cachedLeft: number = 0;
|
||||
|
||||
private _layout(width: number, height: number, top: number, left: number): void {
|
||||
if (this.cachedWidth === width && this.cachedHeight === height && this.cachedTop === top && this.cachedLeft === left) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cachedWidth = width;
|
||||
this.cachedHeight = height;
|
||||
this.cachedTop = top;
|
||||
this.cachedLeft = left;
|
||||
this.view.layout(width, height, top, left);
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
@@ -852,6 +899,7 @@ export class GridView implements IDisposable {
|
||||
this.element.appendChild(root.element);
|
||||
this.onDidSashResetRelay.input = root.onDidSashReset;
|
||||
this._onDidChange.input = Event.map(root.onDidChange, () => undefined); // TODO
|
||||
this._onDidScroll.input = root.onDidScroll;
|
||||
}
|
||||
|
||||
get orientation(): Orientation {
|
||||
@@ -877,6 +925,9 @@ export class GridView implements IDisposable {
|
||||
get maximumWidth(): number { return this.root.maximumHeight; }
|
||||
get maximumHeight(): number { return this.root.maximumHeight; }
|
||||
|
||||
private _onDidScroll = new Relay<void>();
|
||||
readonly onDidScroll = this._onDidScroll.event;
|
||||
|
||||
private _onDidChange = new Relay<IViewSize | undefined>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
|
||||
@@ -138,3 +138,8 @@
|
||||
margin-bottom: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.monaco-hover-content .action-container a {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ export interface IHoverDelegateOptions {
|
||||
text: IMarkdownString | string;
|
||||
target: IHoverDelegateTarget | HTMLElement;
|
||||
hoverPosition?: HoverPosition;
|
||||
showPointer?: boolean;
|
||||
}
|
||||
|
||||
export interface IHoverDelegate {
|
||||
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
|
||||
delay: number;
|
||||
placement?: 'mouse' | 'element';
|
||||
}
|
||||
|
||||
@@ -10,13 +10,10 @@ import { IMatch } from 'vs/base/common/filters';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/base/common/range';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { isFunction, isString } from 'vs/base/common/types';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
@@ -91,17 +88,17 @@ class FastLabelNode {
|
||||
|
||||
export class IconLabel extends Disposable {
|
||||
|
||||
private domNode: FastLabelNode;
|
||||
private readonly domNode: FastLabelNode;
|
||||
|
||||
private nameNode: Label | LabelWithHighlights;
|
||||
private readonly nameNode: Label | LabelWithHighlights;
|
||||
|
||||
private descriptionContainer: FastLabelNode;
|
||||
private readonly descriptionContainer: FastLabelNode;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
private readonly descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
private labelContainer: HTMLElement;
|
||||
private readonly labelContainer: HTMLElement;
|
||||
|
||||
private hoverDelegate: IHoverDelegate | undefined = undefined;
|
||||
private readonly hoverDelegate: IHoverDelegate | undefined;
|
||||
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
|
||||
|
||||
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
|
||||
@@ -114,7 +111,7 @@ export class IconLabel extends Disposable {
|
||||
const nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container'));
|
||||
this.descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container'))));
|
||||
|
||||
if (options?.supportHighlights) {
|
||||
if (options?.supportHighlights || options?.supportIcons) {
|
||||
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportIcons);
|
||||
} else {
|
||||
this.nameNode = new Label(nameContainer);
|
||||
@@ -126,9 +123,7 @@ export class IconLabel extends Disposable {
|
||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
|
||||
}
|
||||
|
||||
if (options?.hoverDelegate) {
|
||||
this.hoverDelegate = options.hoverDelegate;
|
||||
}
|
||||
this.hoverDelegate = options?.hoverDelegate;
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
@@ -185,116 +180,21 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
|
||||
if (!this.hoverDelegate) {
|
||||
return this.setupNativeHover(htmlElement, tooltip);
|
||||
setupNativeHover(htmlElement, tooltip);
|
||||
} else {
|
||||
return this.setupCustomHover(this.hoverDelegate, htmlElement, tooltip);
|
||||
const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip);
|
||||
if (hoverDisposable) {
|
||||
this.customHovers.set(htmlElement, hoverDisposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return hoverDelegate.showHover(hoverOptions);
|
||||
public override dispose() {
|
||||
super.dispose();
|
||||
for (const disposable of this.customHovers.values()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise<string | IMarkdownString | undefined> {
|
||||
if (isString(markdownTooltip)) {
|
||||
return async () => markdownTooltip;
|
||||
} else if (isFunction(markdownTooltip.markdown)) {
|
||||
return markdownTooltip.markdown;
|
||||
} else {
|
||||
const markdown = markdownTooltip.markdown;
|
||||
return async () => markdown;
|
||||
}
|
||||
}
|
||||
|
||||
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString): void {
|
||||
htmlElement.setAttribute('title', '');
|
||||
htmlElement.removeAttribute('title');
|
||||
let tooltip = this.getTooltipForCustom(markdownTooltip);
|
||||
|
||||
let hoverOptions: IHoverDelegateOptions | undefined;
|
||||
let mouseX: number | undefined;
|
||||
let isHovering = false;
|
||||
let tokenSource: CancellationTokenSource;
|
||||
let hoverDisposable: IDisposable | undefined;
|
||||
function mouseOver(this: HTMLElement, e: MouseEvent): void {
|
||||
if (isHovering) {
|
||||
return;
|
||||
}
|
||||
tokenSource = new CancellationTokenSource();
|
||||
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): void {
|
||||
const isMouseDown = e.type === dom.EventType.MOUSE_DOWN;
|
||||
if (isMouseDown) {
|
||||
hoverDisposable?.dispose();
|
||||
hoverDisposable = undefined;
|
||||
}
|
||||
if (isMouseDown || (<any>e).fromElement === htmlElement) {
|
||||
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));
|
||||
isHovering = true;
|
||||
|
||||
function mouseMove(this: HTMLElement, e: MouseEvent): void {
|
||||
mouseX = e.x;
|
||||
}
|
||||
const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement));
|
||||
setTimeout(async () => {
|
||||
if (isHovering && tooltip) {
|
||||
// Re-use the already computed hover options if they exist.
|
||||
if (!hoverOptions) {
|
||||
const target: IHoverDelegateTarget = {
|
||||
targetElements: [this],
|
||||
dispose: () => { }
|
||||
};
|
||||
hoverOptions = {
|
||||
text: localize('iconLabel.loading', "Loading..."),
|
||||
target,
|
||||
hoverPosition: HoverPosition.BELOW
|
||||
};
|
||||
hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
|
||||
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined);
|
||||
if (resolvedTooltip) {
|
||||
hoverOptions = {
|
||||
text: resolvedTooltip,
|
||||
target,
|
||||
hoverPosition: HoverPosition.BELOW
|
||||
};
|
||||
// awaiting the tooltip could take a while. Make sure we're still hovering.
|
||||
hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
} else if (hoverDisposable) {
|
||||
hoverDisposable.dispose();
|
||||
hoverDisposable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
mouseMoveDisposable.dispose();
|
||||
}, hoverDelegate.delay);
|
||||
}
|
||||
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
|
||||
this.customHovers.set(htmlElement, mouseOverDisposable);
|
||||
}
|
||||
|
||||
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
|
||||
let stringTooltip: string = '';
|
||||
if (isString(tooltip)) {
|
||||
stringTooltip = tooltip;
|
||||
} else if (tooltip?.markdownNotSupportedFallback) {
|
||||
stringTooltip = tooltip.markdownNotSupportedFallback;
|
||||
}
|
||||
htmlElement.title = stringTooltip;
|
||||
this.customHovers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
130
src/vs/base/browser/ui/iconLabel/iconLabelHover.ts
Normal file
130
src/vs/base/browser/ui/iconLabel/iconLabelHover.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isFunction, isString } from 'vs/base/common/types';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DomEmitter } from 'vs/base/browser/event';
|
||||
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
|
||||
export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
|
||||
if (isString(tooltip)) {
|
||||
htmlElement.title = tooltip;
|
||||
} else if (tooltip?.markdownNotSupportedFallback) {
|
||||
htmlElement.title = tooltip.markdownNotSupportedFallback;
|
||||
} else {
|
||||
htmlElement.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
|
||||
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString | undefined): IDisposable | undefined {
|
||||
if (!markdownTooltip) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tooltip = getTooltipForCustom(markdownTooltip);
|
||||
|
||||
let hoverOptions: IHoverDelegateOptions | undefined;
|
||||
let mouseX: number | undefined;
|
||||
let isHovering = false;
|
||||
let tokenSource: CancellationTokenSource;
|
||||
let hoverDisposable: IDisposable | undefined;
|
||||
|
||||
const mouseOverDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_OVER, true);
|
||||
mouseOverDomEmitter.event((e: MouseEvent) => {
|
||||
if (isHovering) {
|
||||
return;
|
||||
}
|
||||
tokenSource = new CancellationTokenSource();
|
||||
function mouseLeaveOrDown(e: MouseEvent): void {
|
||||
const isMouseDown = e.type === dom.EventType.MOUSE_DOWN;
|
||||
if (isMouseDown) {
|
||||
hoverDisposable?.dispose();
|
||||
hoverDisposable = undefined;
|
||||
}
|
||||
if (isMouseDown || (<any>e).fromElement === htmlElement) {
|
||||
isHovering = false;
|
||||
hoverOptions = undefined;
|
||||
tokenSource.dispose(true);
|
||||
mouseLeaveDomEmitter.dispose();
|
||||
mouseDownDomEmitter.dispose();
|
||||
}
|
||||
}
|
||||
const mouseLeaveDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_LEAVE, true);
|
||||
mouseLeaveDomEmitter.event(mouseLeaveOrDown);
|
||||
const mouseDownDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_DOWN, true);
|
||||
mouseDownDomEmitter.event(mouseLeaveOrDown);
|
||||
isHovering = true;
|
||||
|
||||
function mouseMove(e: MouseEvent): void {
|
||||
mouseX = e.x;
|
||||
}
|
||||
const mouseMoveDomEmitter = new DomEmitter(htmlElement, dom.EventType.MOUSE_MOVE, true);
|
||||
mouseMoveDomEmitter.event(mouseMove);
|
||||
setTimeout(async () => {
|
||||
if (isHovering && tooltip) {
|
||||
// Re-use the already computed hover options if they exist.
|
||||
if (!hoverOptions) {
|
||||
const target: IHoverDelegateTarget = {
|
||||
targetElements: [htmlElement],
|
||||
dispose: () => { }
|
||||
};
|
||||
hoverOptions = {
|
||||
text: localize('iconLabel.loading', "Loading..."),
|
||||
target,
|
||||
hoverPosition: HoverPosition.BELOW
|
||||
};
|
||||
hoverDisposable = adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
|
||||
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined);
|
||||
if (resolvedTooltip) {
|
||||
hoverOptions = {
|
||||
text: resolvedTooltip,
|
||||
target,
|
||||
showPointer: hoverDelegate.placement === 'element',
|
||||
hoverPosition: HoverPosition.BELOW
|
||||
};
|
||||
// awaiting the tooltip could take a while. Make sure we're still hovering.
|
||||
hoverDisposable = adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering);
|
||||
} else if (hoverDisposable) {
|
||||
hoverDisposable.dispose();
|
||||
hoverDisposable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
mouseMoveDomEmitter.dispose();
|
||||
}, hoverDelegate.delay);
|
||||
});
|
||||
return mouseOverDomEmitter;
|
||||
}
|
||||
|
||||
|
||||
function getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise<string | IMarkdownString | undefined> {
|
||||
if (isString(markdownTooltip)) {
|
||||
return async () => markdownTooltip;
|
||||
} else if (isFunction(markdownTooltip.markdown)) {
|
||||
return markdownTooltip.markdown;
|
||||
} else {
|
||||
const markdown = markdownTooltip.markdown;
|
||||
return async () => markdown;
|
||||
}
|
||||
}
|
||||
|
||||
function adjustXAndShowCustomHover(hoverOptions: IHoverDelegateOptions | undefined, mouseX: number | undefined, hoverDelegate: IHoverDelegate, isHovering: boolean): IDisposable | undefined {
|
||||
if (hoverOptions && isHovering) {
|
||||
if (mouseX !== undefined && (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse')) {
|
||||
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
|
||||
}
|
||||
return hoverDelegate.showHover(hoverOptions);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
vertical-align: top;
|
||||
|
||||
flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
|
||||
flex-shrink: 0; /* fix for https://github.com/microsoft/vscode/issues/13787 */
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-container {
|
||||
|
||||
@@ -898,7 +898,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onContextMenu(): Event<IListMouseEvent<T> | IListGestureEvent<T>> { return Event.any(Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)), Event.map(domEvent(this.domNode, TouchEventType.Contextmenu) as Event<GestureEvent>, e => this.toGestureEvent(e))); }
|
||||
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
|
||||
@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e as GestureEvent)); }
|
||||
|
||||
|
||||
@@ -660,9 +660,15 @@ export class MouseController<T> implements IDisposable {
|
||||
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
const focus = e.index!;
|
||||
const anchor = this.list.getAnchor();
|
||||
let anchor = this.list.getAnchor();
|
||||
|
||||
if (this.isSelectionRangeChangeEvent(e)) {
|
||||
if (typeof anchor === 'undefined') {
|
||||
const currentFocus = this.list.getFocus()[0];
|
||||
anchor = currentFocus ?? focus;
|
||||
this.list.setAnchor(anchor);
|
||||
}
|
||||
|
||||
if (this.isSelectionRangeChangeEvent(e) && typeof anchor === 'number') {
|
||||
const min = Math.min(anchor, focus);
|
||||
const max = Math.max(anchor, focus);
|
||||
const rangeSelection = range(min, max + 1);
|
||||
@@ -1201,7 +1207,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
||||
|
||||
const fromMouse = Event.chain(this.view.onContextMenu)
|
||||
.filter(_ => !didJustPressContextMenuKey)
|
||||
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent }))
|
||||
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.pageX + 1, y: browserEvent.pageY }, browserEvent }))
|
||||
.event;
|
||||
|
||||
return Event.any<IListContextMenuEvent<T>>(fromKeyDown, fromKeyUp, fromMouse);
|
||||
|
||||
@@ -16,4 +16,4 @@ export class CombinedSpliceable<T> implements ISpliceable<T> {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
this.spliceables.forEach(s => s.splice(start, deleteCount, elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./sash';
|
||||
import { IDisposable, dispose, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { 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';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
|
||||
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 { getElementsByTagName, EventHelper, createStyleSheet, append, $, EventLike } from 'vs/base/browser/dom';
|
||||
import { DomEmitter } from 'vs/base/browser/event';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
let DEBUG = false;
|
||||
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
|
||||
@@ -88,6 +87,78 @@ export function setGlobalHoverDelay(size: number): void {
|
||||
onDidChangeHoverDelay.fire(size);
|
||||
}
|
||||
|
||||
interface PointerEvent extends EventLike {
|
||||
readonly pageX: number;
|
||||
readonly pageY: number;
|
||||
readonly altKey: boolean;
|
||||
readonly target: EventTarget | null;
|
||||
}
|
||||
|
||||
interface IPointerEventFactory {
|
||||
readonly onPointerMove: Event<PointerEvent>;
|
||||
readonly onPointerUp: Event<PointerEvent>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
class MouseEventFactory implements IPointerEventFactory {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
@memoize
|
||||
get onPointerMove(): Event<PointerEvent> {
|
||||
return this.disposables.add(new DomEmitter(window, 'mousemove')).event;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get onPointerUp(): Event<PointerEvent> {
|
||||
return this.disposables.add(new DomEmitter(window, 'mouseup')).event;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class GestureEventFactory implements IPointerEventFactory {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
@memoize
|
||||
get onPointerMove(): Event<PointerEvent> {
|
||||
return this.disposables.add(new DomEmitter(this.el, EventType.Change)).event;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get onPointerUp(): Event<PointerEvent> {
|
||||
return this.disposables.add(new DomEmitter(this.el, EventType.End)).event;
|
||||
}
|
||||
|
||||
constructor(private el: HTMLElement) { }
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class OrthogonalPointerEventFactory implements IPointerEventFactory {
|
||||
|
||||
@memoize
|
||||
get onPointerMove(): Event<PointerEvent> {
|
||||
return this.factory.onPointerMove;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get onPointerUp(): Event<PointerEvent> {
|
||||
return this.factory.onPointerUp;
|
||||
}
|
||||
|
||||
constructor(private factory: IPointerEventFactory) { }
|
||||
|
||||
dispose(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class Sash extends Disposable {
|
||||
|
||||
private el: HTMLElement;
|
||||
@@ -146,9 +217,9 @@ export class Sash extends Disposable {
|
||||
if (state !== SashState.Disabled) {
|
||||
this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start'));
|
||||
this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle!.remove()));
|
||||
domEvent(this._orthogonalStartDragHandle, 'mouseenter')
|
||||
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseenter')).event
|
||||
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables);
|
||||
domEvent(this._orthogonalStartDragHandle, 'mouseleave')
|
||||
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseleave')).event
|
||||
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables);
|
||||
}
|
||||
};
|
||||
@@ -176,9 +247,9 @@ export class Sash extends Disposable {
|
||||
if (state !== SashState.Disabled) {
|
||||
this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end'));
|
||||
this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle!.remove()));
|
||||
domEvent(this._orthogonalEndDragHandle, 'mouseenter')
|
||||
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseenter')).event
|
||||
(() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables);
|
||||
domEvent(this._orthogonalEndDragHandle, 'mouseleave')
|
||||
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseleave')).event
|
||||
(() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables);
|
||||
}
|
||||
};
|
||||
@@ -205,13 +276,28 @@ export class Sash extends Disposable {
|
||||
this.el.classList.add('mac');
|
||||
}
|
||||
|
||||
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)));
|
||||
const onMouseDown = this._register(new DomEmitter(this.el, 'mousedown')).event;
|
||||
this._register(onMouseDown(e => this.onPointerStart(e, new MouseEventFactory()), this));
|
||||
const onMouseDoubleClick = this._register(new DomEmitter(this.el, 'dblclick')).event;
|
||||
this._register(onMouseDoubleClick(this.onPointerDoublePress, this));
|
||||
const onMouseEnter = this._register(new DomEmitter(this.el, 'mouseenter')).event;
|
||||
this._register(onMouseEnter(() => Sash.onMouseEnter(this)));
|
||||
const onMouseLeave = this._register(new DomEmitter(this.el, 'mouseleave')).event;
|
||||
this._register(onMouseLeave(() => Sash.onMouseLeave(this)));
|
||||
|
||||
this._register(Gesture.addTarget(this.el));
|
||||
this._register(domEvent(this.el, EventType.Start)(e => this.onTouchStart(e as GestureEvent), this));
|
||||
|
||||
const onTouchStart = Event.map(this._register(new DomEmitter(this.el, EventType.Start)).event, e => ({ ...e, target: e.initialTarget ?? null }));
|
||||
this._register(onTouchStart(e => this.onPointerStart(e, new GestureEventFactory(this.el)), this));
|
||||
const onTap = this._register(new DomEmitter(this.el, EventType.Tap)).event;
|
||||
const onDoubleTap = Event.map(
|
||||
Event.filter(
|
||||
Event.debounce<GestureEvent, { event: GestureEvent, count: number }>(onTap, (res, event) => ({ event, count: (res?.count ?? 0) + 1 }), 250),
|
||||
({ count }) => count === 2
|
||||
),
|
||||
({ event }) => ({ ...event, target: event.initialTarget ?? null })
|
||||
);
|
||||
this._register(onDoubleTap(this.onPointerDoublePress, this));
|
||||
|
||||
if (typeof options.size === 'number') {
|
||||
this.size = options.size;
|
||||
@@ -252,24 +338,24 @@ export class Sash extends Disposable {
|
||||
this.layout();
|
||||
}
|
||||
|
||||
private onMouseDown(e: MouseEvent): void {
|
||||
EventHelper.stop(e, false);
|
||||
private onPointerStart(event: PointerEvent, pointerEventFactory: IPointerEventFactory): void {
|
||||
EventHelper.stop(event);
|
||||
|
||||
let isMultisashResize = false;
|
||||
|
||||
if (!(e as any).__orthogonalSashEvent) {
|
||||
const orthogonalSash = this.getOrthogonalSash(e);
|
||||
if (!(event as any).__orthogonalSashEvent) {
|
||||
const orthogonalSash = this.getOrthogonalSash(event);
|
||||
|
||||
if (orthogonalSash) {
|
||||
isMultisashResize = true;
|
||||
(e as any).__orthogonalSashEvent = true;
|
||||
orthogonalSash.onMouseDown(e);
|
||||
(event as any).__orthogonalSashEvent = true;
|
||||
orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.linkedSash && !(e as any).__linkedSashEvent) {
|
||||
(e as any).__linkedSashEvent = true;
|
||||
this.linkedSash.onMouseDown(e);
|
||||
if (this.linkedSash && !(event as any).__linkedSashEvent) {
|
||||
(event as any).__linkedSashEvent = true;
|
||||
this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
|
||||
}
|
||||
|
||||
if (!this.state) {
|
||||
@@ -287,10 +373,9 @@ export class Sash extends Disposable {
|
||||
iframe.style.pointerEvents = 'none'; // disable mouse events on iframes as long as we drag the sash
|
||||
}
|
||||
|
||||
const mouseDownEvent = new StandardMouseEvent(e);
|
||||
const startX = mouseDownEvent.posx;
|
||||
const startY = mouseDownEvent.posy;
|
||||
const altKey = mouseDownEvent.altKey;
|
||||
const startX = event.pageX;
|
||||
const startY = event.pageY;
|
||||
const altKey = event.altKey;
|
||||
const startEvent: ISashEvent = { startX, currentX: startX, startY, currentY: startY, altKey };
|
||||
|
||||
this.el.classList.add('active');
|
||||
@@ -332,15 +417,14 @@ export class Sash extends Disposable {
|
||||
this.onDidEnablementChange(updateStyle, null, disposables);
|
||||
}
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
EventHelper.stop(e, false);
|
||||
const mouseMoveEvent = new StandardMouseEvent(e);
|
||||
const event: ISashEvent = { startX, currentX: mouseMoveEvent.posx, startY, currentY: mouseMoveEvent.posy, altKey };
|
||||
const event: ISashEvent = { startX, currentX: e.pageX, startY, currentY: e.pageY, altKey };
|
||||
|
||||
this._onDidChange.fire(event);
|
||||
};
|
||||
|
||||
const onMouseUp = (e: MouseEvent) => {
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
EventHelper.stop(e, false);
|
||||
|
||||
this.el.removeChild(style);
|
||||
@@ -355,11 +439,12 @@ export class Sash extends Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
domEvent(window, 'mousemove')(onMouseMove, null, disposables);
|
||||
domEvent(window, 'mouseup')(onMouseUp, null, disposables);
|
||||
pointerEventFactory.onPointerMove(onPointerMove, null, disposables);
|
||||
pointerEventFactory.onPointerUp(onPointerUp, null, disposables);
|
||||
disposables.add(pointerEventFactory);
|
||||
}
|
||||
|
||||
private onMouseDoubleClick(e: MouseEvent): void {
|
||||
private onPointerDoublePress(e: MouseEvent): void {
|
||||
const orthogonalSash = this.getOrthogonalSash(e);
|
||||
|
||||
if (orthogonalSash) {
|
||||
@@ -373,41 +458,6 @@ export class Sash extends Disposable {
|
||||
this._onDidReset.fire();
|
||||
}
|
||||
|
||||
private onTouchStart(event: GestureEvent): void {
|
||||
EventHelper.stop(event);
|
||||
|
||||
const listeners: IDisposable[] = [];
|
||||
|
||||
const startX = event.pageX;
|
||||
const startY = event.pageY;
|
||||
const altKey = event.altKey;
|
||||
|
||||
this._onDidStart.fire({
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY,
|
||||
altKey
|
||||
});
|
||||
|
||||
listeners.push(addDisposableListener(this.el, EventType.Change, (event: GestureEvent) => {
|
||||
if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) {
|
||||
this._onDidChange.fire({
|
||||
startX: startX,
|
||||
currentX: event.pageX,
|
||||
startY: startY,
|
||||
currentY: event.pageY,
|
||||
altKey
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
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();
|
||||
@@ -476,7 +526,7 @@ export class Sash extends Disposable {
|
||||
return this.hidden;
|
||||
}
|
||||
|
||||
private getOrthogonalSash(e: MouseEvent): Sash | undefined {
|
||||
private getOrthogonalSash(e: PointerEvent): Sash | undefined {
|
||||
if (!e.target || !(e.target instanceof HTMLElement)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
}
|
||||
|
||||
if (this.styles.listFocusForeground) {
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`);
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { color: ${this.styles.listFocusForeground} !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.decoratorRightForeground) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
|
||||
export interface IPaneOptions {
|
||||
minimumBodySize?: number;
|
||||
@@ -444,6 +445,7 @@ export class PaneView extends Disposable {
|
||||
|
||||
orientation: Orientation;
|
||||
readonly onDidSashChange: Event<number>;
|
||||
readonly onDidScroll: Event<ScrollEvent>;
|
||||
|
||||
constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
|
||||
super();
|
||||
@@ -453,6 +455,7 @@ export class PaneView extends Disposable {
|
||||
this.element = append(container, $('.monaco-pane-view'));
|
||||
this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation }));
|
||||
this.onDidSashChange = this.splitview.onDidSashChange;
|
||||
this.onDidScroll = this.splitview.onDidScroll;
|
||||
}
|
||||
|
||||
addPane(pane: Pane, size: number, index = this.splitview.length): void {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { $, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export interface ISplitViewStyles {
|
||||
@@ -237,6 +237,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
private _onDidSashReset = this._register(new Emitter<number>());
|
||||
readonly onDidSashReset = this._onDidSashReset.event;
|
||||
|
||||
readonly onDidScroll: Event<ScrollEvent>;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
@@ -317,7 +319,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden
|
||||
}, this.scrollable));
|
||||
|
||||
this._register(this.scrollableElement.onScroll(e => {
|
||||
this.onDidScroll = this.scrollableElement.onScroll;
|
||||
this._register(this.onDidScroll(e => {
|
||||
this.viewContainer.scrollTop = e.scrollTop;
|
||||
this.viewContainer.scrollLeft = e.scrollLeft;
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-table {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IListContextMenuEvent, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./table';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { splice, tail2 } from 'vs/base/common/arrays';
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { LcsDiff } from 'vs/base/common/diff/diff';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
@@ -256,7 +256,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
}
|
||||
|
||||
const deletedNodes = splice(parentNode.children, lastIndex, deleteCount, nodesToInsert);
|
||||
const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert);
|
||||
|
||||
// figure out what is the count of deleted visible children
|
||||
let deletedVisibleChildrenCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user