UNPKG

monaco-editor

Version:
292 lines (289 loc) • 12.6 kB
import './iconlabel.css'; import { append, $, isHTMLElement, isAncestor, after } from '../../dom.js'; import { asCSSUrl } from '../../cssValue.js'; import { HighlightedLabel } from '../highlightedlabel/highlightedLabel.js'; import { Disposable } from '../../../common/lifecycle.js'; import { equals } from '../../../common/objects.js'; import { Range } from '../../../common/range.js'; import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; import { ThemeIcon } from '../../../common/themables.js'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ class FastLabelNode { constructor(_element) { this._element = _element; } get element() { return this._element; } set textContent(content) { if (this.disposed || content === this._textContent) { return; } this._textContent = content; this._element.textContent = content; } set classNames(classNames) { if (this.disposed || equals(classNames, this._classNames)) { return; } this._classNames = classNames; this._element.classList.value = ''; this._element.classList.add(...classNames); } set empty(empty) { if (this.disposed || empty === this._empty) { return; } this._empty = empty; this._element.style.marginLeft = empty ? '0' : ''; } dispose() { this.disposed = true; } } class IconLabel extends Disposable { constructor(container, options) { super(); this.customHovers = new Map(); this.creationOptions = options; this.domNode = this._register(new FastLabelNode(append(container, $('.monaco-icon-label')))); this.labelContainer = append(this.domNode.element, $('.monaco-icon-label-container')); this.nameContainer = append(this.labelContainer, $('span.monaco-icon-name-container')); if (options?.supportHighlights || options?.supportIcons) { this.nameNode = this._register(new LabelWithHighlights(this.nameContainer, !!options.supportIcons)); } else { this.nameNode = new Label(this.nameContainer); } this.hoverDelegate = options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); } get element() { return this.domNode.element; } setLabel(label, description, options) { const labelClasses = ['monaco-icon-label']; const containerClasses = ['monaco-icon-label-container']; let ariaLabel = ''; if (options) { if (options.extraClasses) { labelClasses.push(...options.extraClasses); } if (options.bold) { labelClasses.push('bold'); } if (options.italic) { labelClasses.push('italic'); } if (options.strikethrough) { labelClasses.push('strikethrough'); } if (options.disabledCommand) { containerClasses.push('disabled'); } if (options.title) { if (typeof options.title === 'string') { ariaLabel += options.title; } else { ariaLabel += label; } } } // eslint-disable-next-line no-restricted-syntax const existingIconNode = this.domNode.element.querySelector('.monaco-icon-label-iconpath'); if (options?.iconPath) { let iconNode; if (!existingIconNode || !(isHTMLElement(existingIconNode))) { iconNode = $('.monaco-icon-label-iconpath'); this.domNode.element.prepend(iconNode); } else { iconNode = existingIconNode; } if (ThemeIcon.isThemeIcon(options.iconPath)) { const iconClass = ThemeIcon.asClassName(options.iconPath); iconNode.className = `monaco-icon-label-iconpath ${iconClass}`; iconNode.style.backgroundImage = ''; } else { iconNode.style.backgroundImage = asCSSUrl(options?.iconPath); } iconNode.style.backgroundRepeat = 'no-repeat'; iconNode.style.backgroundPosition = 'center'; iconNode.style.backgroundSize = 'contain'; } else if (existingIconNode) { existingIconNode.remove(); } this.domNode.classNames = labelClasses; this.domNode.element.setAttribute('aria-label', ariaLabel); this.labelContainer.classList.value = ''; this.labelContainer.classList.add(...containerClasses); this.setupHover(options?.descriptionTitle ? this.labelContainer : this.element, options?.title); this.nameNode.setLabel(label, options); if (description || this.descriptionNode) { const descriptionNode = this.getOrCreateDescriptionNode(); if (descriptionNode instanceof HighlightedLabel) { const supportIcons = options?.supportIcons ?? this.creationOptions?.supportIcons; descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, supportIcons); this.setupHover(descriptionNode.element, options?.descriptionTitle); } else { descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || ''); this.setupHover(descriptionNode.element, options?.descriptionTitle || ''); descriptionNode.empty = !description; } } if (options?.suffix || this.suffixNode) { const suffixNode = this.getOrCreateSuffixNode(); suffixNode.textContent = options?.suffix ?? ''; } } setupHover(htmlElement, tooltip) { const previousCustomHover = this.customHovers.get(htmlElement); if (previousCustomHover) { previousCustomHover.dispose(); this.customHovers.delete(htmlElement); } if (!tooltip) { htmlElement.removeAttribute('title'); return; } let hoverTarget = htmlElement; if (this.creationOptions?.hoverTargetOverride) { if (!isAncestor(htmlElement, this.creationOptions.hoverTargetOverride)) { throw new Error('hoverTargetOverrride must be an ancestor of the htmlElement'); } hoverTarget = this.creationOptions.hoverTargetOverride; } const hoverDisposable = getBaseLayerHoverDelegate().setupManagedHover(this.hoverDelegate, hoverTarget, tooltip); if (hoverDisposable) { this.customHovers.set(htmlElement, hoverDisposable); } } dispose() { super.dispose(); for (const disposable of this.customHovers.values()) { disposable.dispose(); } this.customHovers.clear(); } getOrCreateSuffixNode() { if (!this.suffixNode) { const suffixContainer = this._register(new FastLabelNode(after(this.nameContainer, $('span.monaco-icon-suffix-container')))); this.suffixNode = this._register(new FastLabelNode(append(suffixContainer.element, $('span.label-suffix')))); } return this.suffixNode; } getOrCreateDescriptionNode() { if (!this.descriptionNode) { const descriptionContainer = this._register(new FastLabelNode(append(this.labelContainer, $('span.monaco-icon-description-container')))); if (this.creationOptions?.supportDescriptionHighlights) { this.descriptionNode = this._register(new HighlightedLabel(append(descriptionContainer.element, $('span.label-description')))); } else { this.descriptionNode = this._register(new FastLabelNode(append(descriptionContainer.element, $('span.label-description')))); } } return this.descriptionNode; } } class Label { constructor(container) { this.container = container; this.label = undefined; this.singleLabel = undefined; } setLabel(label, options) { if (this.label === label && equals(this.options, options)) { return; } this.label = label; this.options = options; if (typeof label === 'string') { if (!this.singleLabel) { this.container.textContent = ''; this.container.classList.remove('multiple'); this.singleLabel = append(this.container, $('a.label-name', { id: options?.domId })); } this.singleLabel.textContent = label; } else { this.container.textContent = ''; this.container.classList.add('multiple'); this.singleLabel = undefined; for (let i = 0; i < label.length; i++) { const l = label[i]; const id = options?.domId && `${options?.domId}_${i}`; append(this.container, $('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }, l)); if (i < label.length - 1) { append(this.container, $('span.label-separator', undefined, options?.separator || '/')); } } } } } function splitMatches(labels, separator, matches) { if (!matches) { return undefined; } let labelStart = 0; return labels.map(label => { const labelRange = { start: labelStart, end: labelStart + label.length }; const result = matches .map(match => Range.intersect(labelRange, match)) .filter(range => !Range.isEmpty(range)) .map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart })); labelStart = labelRange.end + separator.length; return result; }); } class LabelWithHighlights extends Disposable { constructor(container, supportIcons) { super(); this.container = container; this.supportIcons = supportIcons; this.label = undefined; this.singleLabel = undefined; } setLabel(label, options) { if (this.label === label && equals(this.options, options)) { return; } this.label = label; this.options = options; // Determine supportIcons: use option if provided, otherwise use constructor value const supportIcons = options?.supportIcons ?? this.supportIcons; if (typeof label === 'string') { if (!this.singleLabel) { this.container.textContent = ''; this.container.classList.remove('multiple'); this.singleLabel = this._register(new HighlightedLabel(append(this.container, $('a.label-name', { id: options?.domId })))); } this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines, supportIcons); } else { this.container.textContent = ''; this.container.classList.add('multiple'); this.singleLabel = undefined; const separator = options?.separator || '/'; const matches = splitMatches(label, separator, options?.matches); for (let i = 0; i < label.length; i++) { const l = label[i]; const m = matches ? matches[i] : undefined; const id = options?.domId && `${options?.domId}_${i}`; const name = $('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); const highlightedLabel = this._register(new HighlightedLabel(append(this.container, name))); highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines, supportIcons); if (i < label.length - 1) { append(name, $('span.label-separator', undefined, separator)); } } } } } export { IconLabel };