extract tree/list css into dynamic stylesheet

This commit is contained in:
Benjamin Pasero 2017-04-12 17:17:47 +02:00 committed by Martin Aeschlimann
parent 2efc376a80
commit 37d4fa2bd0
8 changed files with 146 additions and 58 deletions

View file

@ -669,11 +669,11 @@ export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClaz
return null;
}
export function createStyleSheet(): HTMLStyleElement {
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
let style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
document.getElementsByTagName('head')[0].appendChild(style);
container.appendChild(style);
return style;
}

View file

@ -40,26 +40,4 @@
/* for OS X ballistic scrolling */
.monaco-list-row.scrolling {
display: none !important;
}
/* Hover */
.vs .monaco-list-row:hover { background-color: #F0F0F0; }
.vs-dark .monaco-list-row:hover { background-color: rgba(255, 255, 255, 0.08); }
.hc-black .monaco-list-row:hover { outline: 1px dashed #f38518; outline-offset: -1px; background: transparent; }
/* Focus */
.monaco-list.element-focused { outline: 0 !important; }
.vs .monaco-list:focus .monaco-list-row.focused { background-color: #DCEBFC; }
.vs-dark .monaco-list:focus .monaco-list-row.focused { background-color: #073655; }
.hc-black .monaco-list:focus .monaco-list-row.focused { outline: 1px solid #f38518; outline-offset: -1px; background: transparent }
/* Selection */
.vs .monaco-list .monaco-list-row.selected { background-color: #CCCEDB; }
.vs .monaco-list:focus .monaco-list-row.selected { background-color: #4FA7FF; color: white; }
.vs-dark .monaco-list .monaco-list-row.selected { background-color: #3F3F46; }
.vs-dark .monaco-list:focus .monaco-list-row.selected { background-color: #0E639C; color: white; }
.hc-black .monaco-list .monaco-list-row.selected { outline: 1px dotted #f38518; color: white; }
/* Selection and focus */
.vs .monaco-list:focus .monaco-list-row.selected.focused { background-color: #3399FF; color: white; }
.vs-dark .monaco-list:focus .monaco-list-row.selected.focused { background-color: #094771; color: white; }
}

View file

@ -18,6 +18,8 @@ import Event, { Emitter, EventBufferer, chain, mapEvent, fromCallback, createEmp
import { domEvent } from 'vs/base/browser/event';
import { IDelegate, IRenderer, IListEvent, IListMouseEvent, IListContextMenuEvent } from './list';
import { ListView, IListViewOptions } from './listView';
import { Color } from "vs/base/common/color";
import { mixin } from "vs/base/common/objects";
export interface IIdentityProvider<T> {
(element: T): string;
@ -394,13 +396,36 @@ class MouseController<T> implements IDisposable {
}
}
export interface IListOptions<T> extends IListViewOptions, IMouseControllerOptions {
export interface IListOptions<T> extends IListViewOptions, IMouseControllerOptions, IListStyles {
identityProvider?: IIdentityProvider<T>;
ariaLabel?: string;
mouseSupport?: boolean;
keyboardSupport?: boolean;
}
export interface IListStyles {
listFocusBackground?: Color;
listActiveSelectionBackground?: Color;
listActiveSelectionForeground?: Color;
listFocusAndSelectionBackground?: Color;
listFocusAndSelectionForeground?: Color;
listInactiveSelectionBackground?: Color;
listHoverBackground?: Color;
listDropBackground?: Color;
listFocusOutline?: Color;
}
const defaultStyles: IListStyles = {
listFocusBackground: Color.fromHex('#073655'),
listActiveSelectionBackground: Color.fromHex('#0E639C'),
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
listFocusAndSelectionBackground: Color.fromHex('#094771'),
listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'),
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
listHoverBackground: Color.fromHex('#2A2D2E'),
listDropBackground: Color.fromHex('#383B3D')
};
const DefaultOptions: IListOptions<any> = {
keyboardSupport: true,
mouseSupport: true
@ -522,6 +547,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private view: ListView<T>;
private spliceable: ISpliceable<T>;
private disposables: IDisposable[];
private styleElement: HTMLStyleElement;
@memoize get onFocusChange(): Event<IListEvent<T>> {
return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
@ -559,13 +585,17 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.focus = new FocusTrait(i => this.getElementDomId(i));
this.selection = new Trait('selected');
this.eventBufferer = new EventBufferer();
mixin(options, defaultStyles, false);
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [this.focus.renderer, this.selection.renderer, r]));
this.view = new ListView(container, delegate, renderers, options);
this.view.domNode.setAttribute('role', 'tree');
DOM.addClass(this.view.domNode, this.idPrefix);
this.view.domNode.tabIndex = 0;
this.styleElement = DOM.createStyleSheet(this.view.domNode);
this.spliceable = new CombinedSpliceable([
new TraitSpliceable(this.focus, this.view, options.identityProvider),
new TraitSpliceable(this.selection, this.view, options.identityProvider),
@ -595,6 +625,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
if (options.ariaLabel) {
this.view.domNode.setAttribute('aria-label', options.ariaLabel);
}
this.style(options);
}
splice(start: number, deleteCount: number, elements: T[] = []): void {
@ -774,6 +806,29 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this._onOpen.fire(indexes);
}
style(styles: IListStyles): void {
// Indicate selection/focus via background color
if (!styles.listFocusOutline) {
this.styleElement.innerHTML = `
.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }
.monaco-list.${this.idPrefix}:focus .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; color: ${styles.listActiveSelectionForeground}; }
.monaco-list.${this.idPrefix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; color: ${styles.listFocusAndSelectionForeground}; }
.monaco-list.${this.idPrefix} .monaco-list-row.selected { background-color: ${styles.listInactiveSelectionBackground}; }
.monaco-list.${this.idPrefix} .monaco-list-row:hover { background-color: ${styles.listHoverBackground}; }
`;
}
// Indicate selection/focus via outline
else {
this.styleElement.innerHTML = `
.monaco-list.${this.idPrefix} .monaco-list-row.selected { outline: 1px dotted ${styles.listFocusOutline}; color: white; }
.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; background: transparent }
.monaco-list.${this.idPrefix} .monaco-list-row:hover { outline: 1px dashed ${styles.listFocusOutline}; outline-offset: -1px; background: transparent; }
`;
}
}
private toListEvent({ indexes }: ITraitChangeEvent) {
return { indexes, elements: indexes.map(i => this.view.element(i)) };
}

View file

@ -89,26 +89,6 @@
opacity: 0.3;
}
/* Default style */
.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: #DCEBFC; }
.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: #4FA7FF; color: white; }
.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #3399FF; color: white; }
.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: #CCCEDB; }
.monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: #F0F0F0; }
.monaco-tree .monaco-tree-wrapper.drop-target,
.monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: #DDECFF !important; color: inherit !important; }
/* VS Dark */
.vs-dark .monaco-tree.focused .monaco-tree-row.focused:not(.highlighted) { background-color: #073655; }
.vs-dark .monaco-tree.focused .monaco-tree-row.selected:not(.highlighted) { background-color: #0E639C; color: white; }
.vs-dark .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #094771; color: white; }
.vs-dark .monaco-tree .monaco-tree-row.selected:not(.highlighted) { background-color: #3F3F46; }
.vs-dark .monaco-tree .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: #2A2D2E; }
.vs-dark .monaco-tree-wrapper.drop-target,
.vs-dark .monaco-tree .monaco-tree-row.drop-target { background-color: #383B3D !important; color: inherit !important; }
.vs-dark .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
background-image: url('collapsed-dark.svg');
}
@ -121,16 +101,6 @@
background-image: url('loading-dark.svg');
}
/* High Contrast Theming */
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row { background: none !important; border: 1px solid transparent; }
.hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted #f38518; }
.hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid #f38518; }
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid #f38518; }
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed #f38518; }
.hc-black .monaco-tree .monaco-tree-wrapper.drop-target,
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background: none !important; border: 1px dashed #f38518; }
.hc-black .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
background-image: url('collapsed-hc.svg');
}

View file

@ -13,6 +13,7 @@ import { INavigator } from 'vs/base/common/iterator';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import Event from 'vs/base/common/event';
import { IAction, IActionItem } from "vs/base/common/actions";
import { Color } from "vs/base/common/color";
export interface ITree extends Events.IEventEmitter {
@ -337,6 +338,11 @@ export interface ITree extends Events.IEventEmitter {
*/
getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator<any>;
/**
* Apply styles to the tree.
*/
style(styles: ITreeStyles): void;
/**
* Disposes the tree
*/
@ -644,7 +650,7 @@ export interface ITreeConfiguration {
accessibilityProvider?: IAccessibilityProvider;
}
export interface ITreeOptions {
export interface ITreeOptions extends ITreeStyles {
twistiePixels?: number;
showTwistie?: boolean;
indentPixels?: number;
@ -657,6 +663,18 @@ export interface ITreeOptions {
keyboardSupport?: boolean;
}
export interface ITreeStyles {
listFocusBackground?: Color;
listActiveSelectionBackground?: Color;
listActiveSelectionForeground?: Color;
listFocusAndSelectionBackground?: Color;
listFocusAndSelectionForeground?: Color;
listInactiveSelectionBackground?: Color;
listHoverBackground?: Color;
listDropBackground?: Color;
listFocusOutline?: Color;
}
export interface ITreeContext extends ITreeConfiguration {
tree: ITree;
options: ITreeOptions;

View file

@ -14,6 +14,8 @@ import _ = require('vs/base/parts/tree/browser/tree');
import { INavigator, MappedNavigator } from 'vs/base/common/iterator';
import Event, { Emitter } from 'vs/base/common/event';
import Lifecycle = require('vs/base/common/lifecycle');
import { Color } from "vs/base/common/color";
import { mixin } from "vs/base/common/objects";
export class TreeContext implements _.ITreeContext {
@ -48,6 +50,17 @@ export class TreeContext implements _.ITreeContext {
}
}
const defaultStyles: _.ITreeStyles = {
listFocusBackground: Color.fromHex('#073655'),
listActiveSelectionBackground: Color.fromHex('#0E639C'),
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
listFocusAndSelectionBackground: Color.fromHex('#094771'),
listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'),
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
listHoverBackground: Color.fromHex('#2A2D2E'),
listDropBackground: Color.fromHex('#383B3D')
};
export class Tree extends Events.EventEmitter implements _.ITree {
private container: HTMLElement;
@ -76,6 +89,7 @@ export class Tree extends Events.EventEmitter implements _.ITree {
this.container = container;
this.configuration = configuration;
this.options = options;
mixin(this.options, defaultStyles, false);
this.options.twistiePixels = typeof this.options.twistiePixels === 'number' ? this.options.twistiePixels : 32;
this.options.showTwistie = this.options.showTwistie === false ? false : true;
@ -96,6 +110,10 @@ export class Tree extends Events.EventEmitter implements _.ITree {
this.toDispose.push(this.model.addListener2('highlight', () => this._onHighlightChange.fire()));
}
public style(styles: _.ITreeStyles): void {
this.view.applyStyles(styles);
}
get onDOMFocus(): Event<void> {
return this.view && this.view.onDOMFocus;
}

View file

@ -362,6 +362,9 @@ export class TreeView extends HeightMap {
static BINDING = 'monaco-tree-row';
static LOADING_DECORATION_DELAY = 800;
private static counter: number = 0;
private instance: number;
private static currentExternalDragAndDropData: _.IDragAndDropData = null;
private context: IViewContext;
@ -371,6 +374,7 @@ export class TreeView extends HeightMap {
private viewListeners: Lifecycle.IDisposable[];
private domNode: HTMLElement;
private wrapper: HTMLElement;
private styleElement: HTMLStyleElement;
private rowsContainer: HTMLElement;
private scrollableElement: ScrollableElement;
private wrapperGesture: Touch.Gesture;
@ -413,6 +417,9 @@ export class TreeView extends HeightMap {
constructor(context: _.ITreeContext, container: HTMLElement) {
super();
TreeView.counter++;
this.instance = TreeView.counter;
this.context = {
dataSource: context.dataSource,
renderer: context.renderer,
@ -434,9 +441,11 @@ export class TreeView extends HeightMap {
this.items = {};
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-tree no-focused-item';
this.domNode.className = `monaco-tree no-focused-item monaco-tree-instance-${this.instance}`;
this.domNode.tabIndex = 0;
this.styleElement = DOM.createStyleSheet(this.domNode);
// ARIA
this.domNode.setAttribute('role', 'tree');
if (this.context.options.ariaLabel) {
@ -540,6 +549,37 @@ export class TreeView extends HeightMap {
this.layout();
this.setupMSGesture();
this.applyStyles(context.options);
}
public applyStyles(styles: _.ITreeStyles): void {
// Indicate selection/focus via background color
if (!styles.listFocusOutline) {
this.styleElement.innerHTML = `
.monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: ${styles.listFocusBackground}; }
.monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listActiveSelectionBackground}; color: ${styles.listActiveSelectionForeground}; }
.monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: ${styles.listFocusAndSelectionBackground}; color: ${styles.listFocusAndSelectionForeground}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listInactiveSelectionBackground}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-wrapper.drop-target,
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; }
`;
}
// Indicate selection/focus via outline
else {
this.styleElement.innerHTML = `
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row { background: none !important; border: 1px solid transparent; }
.monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted ${styles.listFocusOutline}; }
.monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed ${styles.listFocusOutline}; }
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-wrapper.drop-target,
.monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.drop-target { background: none !important; border: 1px dashed ${styles.listFocusOutline}; }
`;
}
}
protected createViewItem(item: Model.Item): IViewItem {

View file

@ -144,6 +144,15 @@ export const selectBackground = registerColor('dropdownBackground', { dark: '#3C
export const selectForeground = registerColor('dropdownForeground', { dark: '#F0F0F0', light: null, hc: Color.white }, nls.localize('dropdownForeground', "Dropdown foreground."));
export const selectBorder = registerColor('dropdownBorder', { dark: selectBackground, light: '#CECECE', hc: selectBackground }, nls.localize('dropdownBorder', "Dropdown border."));
export const listFocusBackground = registerColor('listFocusBackground', { dark: '#073655', light: '#DCEBFC', hc: null }, nls.localize('listFocusBackground', "List/Tree focus background when active"));
export const listActiveSelectionBackground = registerColor('listActiveSelectionBackground', { dark: '#0E639C', light: '#4FA7FF', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree selection background when active"));
export const listActiveSelectionForeground = registerColor('listActiveSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree selection foreground when active"));
export const listFocusAndSelectionBackground = registerColor('listFocusAndSelectionBackground', { dark: '#094771', light: '#3399FF', hc: null }, nls.localize('listFocusAndSelectionBackground', "List/Tree focus and selection background"));
export const listFocusAndSelectionForeground = registerColor('listFocusAndSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listFocusAndSelectionForeground', "List/Tree focus and selection foreground"));
export const listInactiveSelectionBackground = registerColor('listInactiveSelectionBackground', { dark: '#3F3F46', light: '#CCCEDB', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree selection background when inactive"));
export const listHoverBackground = registerColor('listHoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree hover background"));
export const listDropBackground = registerColor('listDropBackground', { dark: '#383B3D', light: '#DDECFF', hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background"));
/**
* Editor background color.
* Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254