diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index bb4fd1ccc35..92d2e2adfa3 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -20,6 +20,7 @@ "typings/thenable.d.ts", "typings/vscode-globals-product.d.ts", "typings/vscode-globals-nls.d.ts", + "typings/editContext.d.ts", "vs/loader.d.ts", "vs/monaco.d.ts", "vs/editor/*", diff --git a/src/typings/editContext.d.ts b/src/typings/editContext.d.ts new file mode 100644 index 00000000000..3e1107a6d2f --- /dev/null +++ b/src/typings/editContext.d.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +type DOMString = string; + +declare class EditContext extends EventTarget { + constructor(options?: EditContextInit); + + updateText(rangeStart: number, rangeEnd: number, text: DOMString): void; + updateSelection(start: number, end: number): void; + updateControlBounds(controlBounds: DOMRect): void; + updateSelectionBounds(selectionBounds: DOMRect): void; + updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void; + + attachedElements(): HTMLElement[]; + + get text(): DOMString; + get selectionStart(): number; + get selectionEnd(): number; + get characterBoundsRangeStart(): number; + characterBounds(): DOMRect[]; + + get ontextupdate(): EventHandler | null; + set ontextupdate(value: EventHandler | null); + + get ontextformatupdate(): EventHandler | null; + set ontextformatupdate(value: EventHandler | null); + + get oncharacterboundsupdate(): EventHandler | null; + set oncharacterboundsupdate(value: EventHandler | null); + + get oncompositionstart(): EventHandler | null; + set oncompositionstart(value: EventHandler | null); + + get oncompositionend(): EventHandler | null; + set oncompositionend(value: EventHandler | null); + + addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +interface EditContextInit { + text: DOMString; + selectionStart: number; + selectionEnd: number; +} + +interface EditContextEventHandlersEventMap { + textupdate: TextUpdateEvent; + textformatupdate: TextFormatUpdateEvent; + characterboundsupdate: CharacterBoundsUpdateEvent; + compositionstart: Event; + compositionend: Event; +} + +type EventHandler = (event: TEvent) => void; + +declare class TextUpdateEvent extends Event { + new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent; + + readonly updateRangeStart: number; + readonly updateRangeEnd: number; + readonly text: DOMString; + readonly selectionStart: number; + readonly selectionEnd: number; +} + +interface TextUpdateEventInit extends EventInit { + updateRangeStart: number; + updateRangeEnd: number; + text: DOMString; + selectionStart: number; + selectionEnd: number; + compositionStart: number; + compositionEnd: number; +} + +declare class TextFormat { + new(options?: TextFormatInit): TextFormat; + + readonly rangeStart: number; + readonly rangeEnd: number; + readonly underlineStyle: UnderlineStyle; + readonly underlineThickness: UnderlineThickness; +} + +interface TextFormatInit { + rangeStart: number; + rangeEnd: number; + underlineStyle: UnderlineStyle; + underlineThickness: UnderlineThickness; +} + +type UnderlineStyle = "none" | "solid" | "dotted" | "dashed" | "wavy"; +type UnderlineThickness = "none" | "thin" | "thick"; + +declare class TextFormatUpdateEvent extends Event { + new(type: DOMString, options?: TextFormatUpdateEventInit): TextFormatUpdateEvent; + getTextFormats(): TextFormat[]; +} + +interface TextFormatUpdateEventInit extends EventInit { + textFormats: TextFormat[]; +} + +declare class CharacterBoundsUpdateEvent extends Event { + new(type: DOMString, options?: CharacterBoundsUpdateEventInit): CharacterBoundsUpdateEvent; + + readonly rangeStart: number; + readonly rangeEnd: number; +} + +interface CharacterBoundsUpdateEventInit extends EventInit { + rangeStart: number; + rangeEnd: number; +} + +interface HTMLElement { + editContext?: EditContext; +} diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 2489a8c0a9c..1b937448c24 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -2516,6 +2516,10 @@ export function trackAttributes(from: Element, to: Element, filter?: string[]): return disposables; } +export function isEditableElement(element: Element): boolean { + return element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea' || isHTMLElement(element) && !!element.editContext; +} + /** * Helper for calculating the "safe triangle" occluded by hovers to avoid early dismissal. * @see https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/ for example diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index c89cce90da7..67b8e18a3a6 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isHTMLElement, isMouseEvent } from '../../dom.js'; +import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isEditableElement, isHTMLElement, isMouseEvent } from '../../dom.js'; import { DomEmitter } from '../../event.js'; import { IKeyboardEvent, StandardKeyboardEvent } from '../../keyboardEvent.js'; import { Gesture } from '../../touch.js'; @@ -246,10 +246,6 @@ class TraitSpliceable implements ISpliceable { } } -export function isInputElement(e: HTMLElement): boolean { - return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA'; -} - function isListElementDescendantOfClass(e: HTMLElement, className: string): boolean { if (e.classList.contains(className)) { return true; @@ -317,7 +313,7 @@ class KeyboardController implements IDisposable { private get onKeyDown(): Event { return Event.chain( this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event, $ => - $.filter(e => !isInputElement(e.target as HTMLElement)) + $.filter(e => !isEditableElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)) ); } @@ -493,7 +489,7 @@ class TypeNavigationController implements IDisposable { let typing = false; const onChar = Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event, $ => - $.filter(e => !isInputElement(e.target as HTMLElement)) + $.filter(e => !isEditableElement(e.target as HTMLElement)) .filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered) .map(event => new StandardKeyboardEvent(event)) .filter(e => typing || this.keyboardNavigationEventFilter(e)) @@ -607,7 +603,7 @@ class DOMFocusController implements IDisposable { private view: IListView ) { const onKeyDown = Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event, $ => $ - .filter(e => !isInputElement(e.target as HTMLElement)) + .filter(e => !isEditableElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)) ); @@ -739,7 +735,7 @@ export class MouseController implements IDisposable { } protected onContextMenu(e: IListContextMenuEvent): void { - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isEditableElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -752,7 +748,7 @@ export class MouseController implements IDisposable { return; } - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isEditableElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -785,7 +781,7 @@ export class MouseController implements IDisposable { } protected onDoubleClick(e: IListMouseEvent): void { - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isEditableElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 610dd665507..b40fe8a52e3 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, asCssValueWithDefault, isKeyboardEvent, addDisposableListener } from '../../dom.js'; +import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, asCssValueWithDefault, isKeyboardEvent, addDisposableListener, isEditableElement } from '../../dom.js'; import { DomEmitter } from '../../event.js'; import { StandardKeyboardEvent } from '../../keyboardEvent.js'; import { ActionBar } from '../actionbar/actionbar.js'; @@ -13,7 +13,7 @@ import { FindInput } from '../findinput/findInput.js'; import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from '../inputbox/inputBox.js'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from '../list/list.js'; import { ElementsDragAndDropData, ListViewTargetSector } from '../list/listView.js'; -import { IListAccessibilityProvider, IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollContainer, isStickyScrollElement, List, MouseController, TypeNavigationMode } from '../list/listWidget.js'; +import { IListAccessibilityProvider, IListOptions, IListStyles, isActionItem, isButton, isMonacoCustomToggle, isMonacoEditor, isStickyScrollContainer, isStickyScrollElement, List, MouseController, TypeNavigationMode } from '../list/listWidget.js'; import { IToggleStyles, Toggle, unthemedToggleStyles } from '../toggle/toggle.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from './tree.js'; @@ -2239,7 +2239,7 @@ class TreeNodeListMouseController extends MouseController< protected override onViewPointer(e: IListMouseEvent>): void { if (isButton(e.browserEvent.target as HTMLElement) || - isInputElement(e.browserEvent.target as HTMLElement) || + isEditableElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -2588,7 +2588,7 @@ export abstract class AbstractTree implements IDisposable if (_options.keyboardSupport !== false) { const onKeyDown = Event.chain(this.view.onKeyDown, $ => - $.filter(e => !isInputElement(e.target as HTMLElement)) + $.filter(e => !isEditableElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)) ); diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index 466df2dd5b2..8f1459c1fea 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -29,7 +29,7 @@ import { KeybindingWeight, KeybindingsRegistry } from '../../platform/keybinding import { EditorOption } from '../common/config/editorOptions.js'; import { IViewModel } from '../common/viewModel.js'; import { ISelection } from '../common/core/selection.js'; -import { getActiveElement } from '../../base/browser/dom.js'; +import { getActiveElement, isEditableElement } from '../../base/browser/dom.js'; import { EnterOperation } from '../common/cursor/cursorTypeEditOperations.js'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -318,7 +318,7 @@ abstract class EditorOrNativeTextInputCommand { target.addImplementation(1000, 'generic-dom-input-textarea', (accessor: ServicesAccessor, args: unknown) => { // Only if focused on an element that allows for entering text const activeElement = getActiveElement(); - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + if (activeElement && isEditableElement(activeElement)) { this.runDOMCommand(activeElement); return true; } diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 3fc65a190e5..249aed774b7 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -12,7 +12,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { HoverWidget } from './hoverWidget.js'; import { IContextViewProvider, IDelegate } from '../../../../base/browser/ui/contextview/contextview.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement } from '../../../../base/browser/dom.js'; +import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement, isEditableElement } from '../../../../base/browser/dom.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ResultKind } from '../../../../platform/keybinding/common/keybindingResolver.js'; @@ -293,8 +293,7 @@ export class HoverService extends Disposable implements IHoverService { // Do not show hover when focusing an input or textarea let focusDomEmitter: undefined | IDisposable; - const tagName = targetElement.tagName.toLowerCase(); - if (tagName !== 'input' && tagName !== 'textarea') { + if (!isEditableElement(targetElement)) { focusDomEmitter = addDisposableListener(targetElement, EventType.FOCUS, onFocus, true); } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index dc72d9eaa3f..e5192e1dd69 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from '../../base/common/lifecycle.js'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js'; import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext } from '../common/contextkeys.js'; -import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow } from '../../base/browser/dom.js'; +import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow, isEditableElement } from '../../base/browser/dom.js'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { IWorkbenchEnvironmentService } from '../services/environment/common/environmentService.js'; @@ -300,7 +300,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateInputContextKeys(ownerDocument: Document): void { function activeElementIsInput(): boolean { - return !!ownerDocument.activeElement && (ownerDocument.activeElement.tagName === 'INPUT' || ownerDocument.activeElement.tagName === 'TEXTAREA'); + return !!ownerDocument.activeElement && isEditableElement(ownerDocument.activeElement); } const isInputFocused = activeElementIsInput(); diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 562dbeb1a6c..2b6b43bb753 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -10,7 +10,7 @@ import Severity from '../../../../base/common/severity.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, isEditorOpenError } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { Dimension, show, hide, IDomNodePagePosition, isAncestor, getActiveElement, getWindowById } from '../../../../base/browser/dom.js'; +import { Dimension, show, hide, IDomNodePagePosition, isAncestor, getActiveElement, getWindowById, isEditableElement } from '../../../../base/browser/dom.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IEditorPaneRegistry, IEditorPaneDescriptor } from '../../editor.js'; import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; @@ -287,7 +287,7 @@ export class EditorPanes extends Disposable { return true; // restore focus if same element is still active } - if (activeElement.tagName !== 'INPUT' && activeElement.tagName !== 'TEXTAREA') { + if (!isEditableElement(activeElement)) { // This is to avoid regressions from not restoring focus as we used to: // Only allow a different input element (or textarea) to remain focused diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 4ce19e5af62..d2b1ad9ba3b 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -53,7 +53,6 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { IEditorResolverService } from '../../../../services/editor/common/editorResolverService.js'; import { EditorOpenSource } from '../../../../../platform/editor/common/editor.js'; import { ResourceMap } from '../../../../../base/common/map.js'; -import { isInputElement } from '../../../../../base/browser/ui/list/listWidget.js'; import { AbstractTreePart } from '../../../../../base/browser/ui/tree/abstractTree.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; @@ -602,7 +601,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } private async onContextMenu(e: ITreeContextMenuEvent): Promise { - if (isInputElement(e.browserEvent.target as HTMLElement)) { + if (DOM.isEditableElement(e.browserEvent.target as HTMLElement)) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 156bfdb4424..4f4673e1990 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -28,7 +28,7 @@ import { Categories } from '../../../../../../platform/action/common/actionCommo import { ILogService } from '../../../../../../platform/log/common/log.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; import { showWindowLogActionId } from '../../../../../services/log/common/logConstants.js'; -import { getActiveElement, getWindow, isAncestor, isHTMLElement } from '../../../../../../base/browser/dom.js'; +import { getActiveElement, getWindow, isAncestor, isEditableElement, isHTMLElement } from '../../../../../../base/browser/dom.js'; let _logging: boolean = false; function toggleLogging() { @@ -360,7 +360,7 @@ export class NotebookClipboardContribution extends Disposable { const loggerService = accessor.get(ILogService); const activeElement = getActiveElement(); - if (isHTMLElement(activeElement) && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + if (isHTMLElement(activeElement) && isEditableElement(activeElement)) { _log(loggerService, '[NotebookEditor] focus is on input or textarea element, bypass'); return false; } @@ -387,7 +387,7 @@ export class NotebookClipboardContribution extends Disposable { runPasteAction(accessor: ServicesAccessor) { const activeElement = getActiveElement(); - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + if (activeElement && isEditableElement(activeElement)) { return false; } @@ -408,7 +408,7 @@ export class NotebookClipboardContribution extends Disposable { runCutAction(accessor: ServicesAccessor) { const activeElement = getActiveElement(); - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + if (activeElement && isEditableElement(activeElement)) { return false; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index f0f61681ebe..d453a802e62 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -196,7 +196,7 @@ async function webviewPreloads(ctx: PreloadContext) { } const id = lastFocusedOutput?.id; - if (id && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.tagName === 'SELECT')) { + if (id && (isEditableElement(activeElement) || activeElement.tagName === 'SELECT')) { postNotebookMessage('outputInputFocus', { inputFocused: true, id }); activeElement.addEventListener('blur', () => { @@ -303,7 +303,7 @@ async function webviewPreloads(ctx: PreloadContext) { return; } const activeElement = window.document.activeElement; - if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') { + if (activeElement && isEditableElement(activeElement)) { (activeElement as HTMLInputElement).select(); } }; @@ -329,7 +329,7 @@ async function webviewPreloads(ctx: PreloadContext) { return; } const activeElement = window.document.activeElement; - if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') { + if (activeElement && isEditableElement(activeElement)) { // Leave for default behavior. return; } @@ -357,7 +357,7 @@ async function webviewPreloads(ctx: PreloadContext) { return; } const activeElement = window.document.activeElement; - if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') { + if (activeElement && isEditableElement(activeElement)) { // The input element will handle this. return; } @@ -696,7 +696,7 @@ async function webviewPreloads(ctx: PreloadContext) { focusableElement.tabIndex = -1; postNotebookMessage('outputInputFocus', { inputFocused: false, id }); } else { - const inputFocused = focusableElement.tagName === 'INPUT' || focusableElement.tagName === 'TEXTAREA'; + const inputFocused = isEditableElement(focusableElement); postNotebookMessage('outputInputFocus', { inputFocused, id }); } @@ -3106,3 +3106,7 @@ export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOp JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(ctx))}")) )\n//# sourceURL=notebookWebviewPreloads.js\n`; } + +export function isEditableElement(element: Element): boolean { + return element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea' || 'editContext' in element; +} diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 7c5d0632d11..a55ca5dd334 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -877,8 +877,7 @@ export class SettingsEditor2 extends EditorPane { if ( e.keyCode === KeyCode.KeyA && (platform.isMacintosh ? e.metaKey : e.ctrlKey) && - e.target.tagName !== 'TEXTAREA' && - e.target.tagName !== 'INPUT' + !DOM.isEditableElement(e.target) ) { // Avoid browser ctrl+a e.browserEvent.stopPropagation();