diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 98f09fe378c..56e67fe2143 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -50,6 +50,12 @@ "./vs/base/browser/ui/scrollbar/verticalScrollbar.ts", "./vs/base/browser/ui/splitview/panelview.ts", "./vs/base/browser/ui/splitview/splitview.ts", + "./vs/base/browser/ui/tree/abstractTree.ts", + // "./vs/base/browser/ui/tree/dataTree.ts", + // "./vs/base/browser/ui/tree/indexTree.ts", + // "./vs/base/browser/ui/tree/indexTreeModel.ts", + // "./vs/base/browser/ui/tree/objectTree.ts", + // "./vs/base/browser/ui/tree/objectTreeModel.ts", "./vs/base/browser/ui/tree/tree.ts", "./vs/base/browser/ui/widget.ts", "./vs/base/common/json.ts", diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index b9815b6ef0b..cb0be292bff 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -44,7 +44,7 @@ export interface IListGestureEvent { export interface IListContextMenuEvent { browserEvent: UIEvent; - element: T; + element: T | undefined; index: number; - anchor: HTMLElement | { x: number; y: number; }; + anchor: HTMLElement | { x: number; y: number; } | undefined; } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index a893aa7d305..a045bf269a8 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getOrDefault } from 'vs/base/common/objects'; +import { getOrDefault2 } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import * as DOM from 'vs/base/browser/dom'; @@ -39,7 +39,7 @@ interface IItem { element: T; size: number; templateId: string; - row: IRow; + row: IRow | null; } export interface IListViewOptions { @@ -48,7 +48,7 @@ export interface IListViewOptions { setRowLineHeight?: boolean; } -const DefaultOptions: IListViewOptions = { +const DefaultOptions = { useShadows: true, verticalScrollMode: ScrollbarVisibility.Auto, setRowLineHeight: true @@ -105,8 +105,8 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement = new ScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, - vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), - useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows) + vertical: getOrDefault2(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), + useShadows: getOrDefault2(options, o => o.useShadows, DefaultOptions.useShadows) }); this._domNode.appendChild(this.scrollableElement.getDomNode()); @@ -125,7 +125,7 @@ export class ListView implements ISpliceable, IDisposable { const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e)); onDragOver(this.onDragOver, this, this.disposables); - this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); + this.setRowLineHeight = getOrDefault2(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.layout(); } @@ -242,7 +242,7 @@ export class ListView implements ISpliceable, IDisposable { return this.items[index].element; } - domElement(index: number): HTMLElement { + domElement(index: number): HTMLElement | null { const row = this.items[index].row; return row && row.domNode; } @@ -312,18 +312,18 @@ export class ListView implements ISpliceable, IDisposable { item.row = this.cache.alloc(item.templateId); } - if (!item.row.domNode.parentElement) { + if (!item.row.domNode!.parentElement) { if (beforeElement) { - this.rowsContainer.insertBefore(item.row.domNode, beforeElement); + this.rowsContainer.insertBefore(item.row.domNode!, beforeElement); } else { - this.rowsContainer.appendChild(item.row.domNode); + this.rowsContainer.appendChild(item.row.domNode!); } } - item.row.domNode.style.height = `${item.size}px`; + item.row.domNode!.style.height = `${item.size}px`; if (this.setRowLineHeight) { - item.row.domNode.style.lineHeight = `${item.size}px`; + item.row.domNode!.style.lineHeight = `${item.size}px`; } this.updateItemInDOM(item, index); @@ -333,11 +333,11 @@ export class ListView implements ISpliceable, IDisposable { } private updateItemInDOM(item: IItem, index: number): void { - item.row.domNode.style.top = `${this.elementTop(index)}px`; - item.row.domNode.setAttribute('data-index', `${index}`); - item.row.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); - item.row.domNode.setAttribute('aria-setsize', `${this.length}`); - item.row.domNode.setAttribute('aria-posinset', `${index + 1}`); + item.row!.domNode!.style.top = `${this.elementTop(index)}px`; + item.row!.domNode!.setAttribute('data-index', `${index}`); + item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); + item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`); + item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`); } private removeItemFromDOM(index: number): void { @@ -345,10 +345,10 @@ export class ListView implements ISpliceable, IDisposable { const renderer = this.renderers.get(item.templateId); if (renderer.disposeElement) { - renderer.disposeElement(item.element, index, item.row.templateData); + renderer.disposeElement(item.element, index, item.row!.templateData); } - this.cache.release(item.row); + this.cache.release(item.row!); item.row = null; } @@ -388,21 +388,21 @@ export class ListView implements ISpliceable, IDisposable { @memoize get onTap(): Event> { return filterEvent(mapEvent(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)), e => e.index >= 0); } private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent { - const index = this.getItemIndexFromEventTarget(browserEvent.target); + const index = this.getItemIndexFromEventTarget(browserEvent.target || null); const item = index < 0 ? undefined : this.items[index]; const element = item && item.element; return { browserEvent, index, element }; } private toTouchEvent(browserEvent: TouchEvent): IListTouchEvent { - const index = this.getItemIndexFromEventTarget(browserEvent.target); + const index = this.getItemIndexFromEventTarget(browserEvent.target || null); const item = index < 0 ? undefined : this.items[index]; const element = item && item.element; return { browserEvent, index, element }; } private toGestureEvent(browserEvent: GestureEvent): IListGestureEvent { - const index = this.getItemIndexFromEventTarget(browserEvent.initialTarget); + const index = this.getItemIndexFromEventTarget(browserEvent.initialTarget || null); const item = index < 0 ? undefined : this.items[index]; const element = item && item.element; return { browserEvent, index, element }; @@ -455,7 +455,7 @@ export class ListView implements ISpliceable, IDisposable { this.dragAndDropScrollTimeout = window.setTimeout(() => { this.cancelDragAndDropScrollInterval(); - this.dragAndDropScrollTimeout = null; + this.dragAndDropScrollTimeout = -1; }, 1000); } } @@ -463,7 +463,7 @@ export class ListView implements ISpliceable, IDisposable { private cancelDragAndDropScrollInterval(): void { if (this.dragAndDropScrollInterval) { window.clearInterval(this.dragAndDropScrollInterval); - this.dragAndDropScrollInterval = null; + this.dragAndDropScrollInterval = -1; } this.cancelDragAndDropScrollTimeout(); @@ -472,15 +472,16 @@ export class ListView implements ISpliceable, IDisposable { private cancelDragAndDropScrollTimeout(): void { if (this.dragAndDropScrollTimeout) { window.clearTimeout(this.dragAndDropScrollTimeout); - this.dragAndDropScrollTimeout = null; + this.dragAndDropScrollTimeout = -1; } } // Util - private getItemIndexFromEventTarget(target: EventTarget): number { - while (target instanceof HTMLElement && target !== this.rowsContainer) { - const element = target as HTMLElement; + private getItemIndexFromEventTarget(target: EventTarget | null): number { + let element: HTMLElement | null = target as (HTMLElement | null); + + while (element instanceof HTMLElement && element !== this.rowsContainer) { const rawIndex = element.getAttribute('data-index'); if (rawIndex) { @@ -491,7 +492,7 @@ export class ListView implements ISpliceable, IDisposable { } } - target = element.parentElement; + element = element.parentElement; } return -1; @@ -532,16 +533,14 @@ export class ListView implements ISpliceable, IDisposable { if (item.row) { const renderer = this.renderers.get(item.row.templateId); renderer.disposeTemplate(item.row.templateData); - item.row = null; } } - this.items = null; + this.items = []; } - if (this._domNode && this._domNode.parentElement) { + if (this._domNode && this._domNode.parentNode) { this._domNode.parentNode.removeChild(this._domNode); - this._domNode = null; } this.disposables = dispose(this.disposables); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 32d343568b0..84700441bdb 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -180,7 +180,6 @@ class Trait implements ISpliceable, IDisposable { } dispose() { - this.indexes = null; this._onChange = dispose(this._onChange); } } @@ -224,8 +223,9 @@ class TraitSpliceable implements ISpliceable { return this.trait.splice(start, deleteCount, elements.map(e => false)); } - const pastElementsWithTrait = this.trait.get().map(i => this.getId(this.view.element(i))); - const elementsWithTrait = elements.map(e => pastElementsWithTrait.indexOf(this.getId(e)) > -1); + const getId = this.getId; + const pastElementsWithTrait = this.trait.get().map(i => getId(this.view.element(i))); + const elementsWithTrait = elements.map(e => pastElementsWithTrait.indexOf(getId(e)) > -1); this.trait.splice(start, deleteCount, elementsWithTrait); } @@ -357,6 +357,11 @@ class DOMFocusController implements IDisposable { } const focusedDomElement = this.view.domElement(focus[0]); + + if (!focusedDomElement) { + return; + } + const tabIndexElement = focusedDomElement.querySelector('[tabIndex]'); if (!tabIndexElement || !(tabIndexElement instanceof HTMLElement) || tabIndexElement.tabIndex === -1) { @@ -421,7 +426,7 @@ class MouseController implements IDisposable { .map(event => { const index = this.list.getFocus()[0]; const element = this.view.element(index); - const anchor = this.view.domElement(index); + const anchor = this.view.domElement(index) || undefined; return { index, element, anchor, browserEvent: event.browserEvent }; }) .event; @@ -436,7 +441,7 @@ class MouseController implements IDisposable { .map(browserEvent => { const index = this.list.getFocus()[0]; const element = this.view.element(index); - const anchor = this.view.domElement(index); + const anchor = this.view.domElement(index) || undefined; return { index, element, anchor, browserEvent }; }) .filter(({ anchor }) => !!anchor) @@ -974,10 +979,7 @@ export class List implements ISpliceable, IDisposable { this.styleElement = DOM.createStyleSheet(this.view.domNode); - this.styleController = options.styleController; - if (!this.styleController) { - this.styleController = new DefaultStyleController(this.styleElement, this.idPrefix); - } + this.styleController = options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix); this.spliceable = new CombinedSpliceable([ new TraitSpliceable(this.focus, this.view, options.identityProvider), @@ -987,8 +989,8 @@ export class List implements ISpliceable, IDisposable { this.disposables = [this.focus, this.selection, this.view, this._onDidDispose]; - this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null); - this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null); + this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null!); + this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null!); this.disposables.push(new DOMFocusController(this, this.view)); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 904702b3e62..2b339b430b2 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeModelOptions } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; -export function createComposedTreeListOptions(options?: IListOptions): IListOptions { +export function createComposedTreeListOptions(options?: IListOptions): IListOptions | undefined { if (!options) { return undefined; } @@ -22,18 +22,20 @@ export function createComposedTreeListOptions(optio let identityProvider: IIdentityProvider | undefined = undefined; if (options.identityProvider) { - identityProvider = el => options.identityProvider(el.element); + const ip = options.identityProvider; + identityProvider = el => ip(el.element); } let multipleSelectionController: IMultipleSelectionController | undefined = undefined; if (options.multipleSelectionController) { + const msc = options.multipleSelectionController; multipleSelectionController = { isSelectionSingleChangeEvent(e) { - return options.multipleSelectionController.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); + return msc.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); }, isSelectionRangeChangeEvent(e) { - return options.multipleSelectionController.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + return msc.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); } }; } @@ -41,9 +43,10 @@ export function createComposedTreeListOptions(optio let accessibilityProvider: IAccessibilityProvider | undefined = undefined; if (options.accessibilityProvider) { + const ap = options.accessibilityProvider; accessibilityProvider = { getAriaLabel(e) { - return options.accessibilityProvider.getAriaLabel(e.element); + return ap.getAriaLabel(e.element); } }; } @@ -192,7 +195,7 @@ export abstract class AbstractTree implements IDisposable container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options?: ITreeOptions + options: ITreeOptions = {} ) { const treeDelegate = new ComposedTreeDelegate>(delegate); @@ -356,8 +359,12 @@ export abstract class AbstractTree implements IDisposable private onMouseClick(e: IListMouseEvent>): void { const node = e.element; - const location = this.model.getNodeLocation(node); + if (!node) { + return; + } + + const location = this.model.getNodeLocation(node); this.model.toggleCollapsed(location); } @@ -436,7 +443,5 @@ export abstract class AbstractTree implements IDisposable dispose(): void { this.disposables = dispose(this.disposables); this.view.dispose(); - this.view = null; - this.model = null; } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index f3fd7701877..2d0705b9ccb 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -94,9 +94,9 @@ export interface ITreeModel { getNodeLocation(node: ITreeNode): TRef; getParentNodeLocation(location: TRef): TRef | null; - getParentElement(location: TRef): T | null; - getFirstChildElement(location: TRef): T | null; - getLastAncestorElement(location: TRef): T | null; + getParentElement(location: TRef | null): T | null; + getFirstChildElement(location: TRef | null): T | null; + getLastAncestorElement(location: TRef | null): T | null; isCollapsed(location: TRef): boolean; setCollapsed(location: TRef, collapsed: boolean): boolean; diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 5e90f440f07..fe461fe6b7b 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -223,6 +223,11 @@ export function getOrDefault(obj: T, fn: (obj: T) => R, defaultValue: R | return typeof result === 'undefined' ? defaultValue : result; } +export function getOrDefault2(obj: T, fn: (obj: T) => R | undefined, defaultValue: R): R { + const result = fn(obj); + return typeof result === 'undefined' ? defaultValue : result; +} + type obj = { [key: string]: any; }; /** * Returns an object that has keys for each value that is different in the base object. Keys