From f3191b0520a245840815f562f00aaf0a881f60c1 Mon Sep 17 00:00:00 2001 From: Kenneth Marut Date: Tue, 10 Nov 2020 00:08:24 +0100 Subject: [PATCH] File dialog enhancements - Add text input to locationList Renderer - Add 'navigate upward' icon - Fix icon focus behavior when disabled Signed-off-by: Kenneth Marut --- CHANGELOG.md | 3 +- packages/core/src/browser/style/tabs.css | 3 +- .../browser/file-dialog/file-dialog-model.ts | 5 + .../src/browser/file-dialog/file-dialog.ts | 75 +++++- .../src/browser/file-tree/file-tree-model.ts | 2 + .../browser/location/location-renderer.tsx | 250 +++++++++++++++++- .../src/browser/style/file-dialog.css | 87 +++++- 7 files changed, 393 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb69de05b0af4..b34ad914641c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ - [plugin] added `createDeployQuickOpenItem` method to create `DeployQuickOpenItem` in order to make extension deploy command extensible [#8919] (https://github.com/eclipse-theia/theia/pull/8919) - [dependencies] updated to use fixed versions when publishing, `"x.y.z"` instead of `"^x.y.z"` in dependencies [#8880](https://github.com/eclipse-theia/theia/pull/8880) - [scm] update code required to highlight nodes on search in the `ScmTreeWidget` [#8929](https://github.com/eclipse-theia/theia/pull/8929) +- [filesystem] add text input and navigate up icon to file dialog [#8748](https://github.com/eclipse-theia/theia/pull/8748) [Breaking Changes:](#breaking_changes_1.10.0) - [scm] add `caption` field to `ScmTreeWidget.Props` interface. Remove `name` from `ScmResourceComponent.Props`, `groupLabel` from `ScmResourceGroupComponent.Props`, and `path` from `ScmResourceFolderElement.Props` interfaces. [#8929](https://github.com/eclipse-theia/theia/pull/8929) - +- [filesystem] `FileDialog` and `LocationListRenderer` now require `FileService` to be passed into constructor for text-based file dialog navigation in browser [#8748](https://github.com/eclipse-theia/theia/pull/8748) ## v1.9.0 - 16/12/2020 diff --git a/packages/core/src/browser/style/tabs.css b/packages/core/src/browser/style/tabs.css index 699e8efb3be8c..4288a8832d8c4 100644 --- a/packages/core/src/browser/style/tabs.css +++ b/packages/core/src/browser/style/tabs.css @@ -8,6 +8,7 @@ --theia-private-horizontal-tab-scrollbar-rail-height: 7px; --theia-private-horizontal-tab-scrollbar-height: 5px; --theia-tabbar-toolbar-z-index: 1001; + --theia-toolbar-active-transform-scale: 1.272019649; } /*----------------------------------------------------------------------------- @@ -352,7 +353,7 @@ body.theia-editor-highlightModifiedTabs } .p-TabBar-toolbar .item.enabled.active { - transform: scale(1.272019649); + transform: scale(var(--theia-toolbar-active-transform-scale)); } .p-TabBar-toolbar .item > div { diff --git a/packages/filesystem/src/browser/file-dialog/file-dialog-model.ts b/packages/filesystem/src/browser/file-dialog/file-dialog-model.ts index 42bcfd39061e1..29f57467b19cc 100644 --- a/packages/filesystem/src/browser/file-dialog/file-dialog-model.ts +++ b/packages/filesystem/src/browser/file-dialog/file-dialog-model.ts @@ -88,4 +88,9 @@ export class FileDialogModel extends FileTreeModel { private isFileStatNodeSelectable(node: FileStatNode): boolean { return !(!node.fileStat.isDirectory && this._disableFileSelection); } + + canNavigateUpward(): boolean { + const treeRoot = this.tree.root; + return FileStatNode.is(treeRoot) && !treeRoot.uri.path.isRoot; + } } diff --git a/packages/filesystem/src/browser/file-dialog/file-dialog.ts b/packages/filesystem/src/browser/file-dialog/file-dialog.ts index fac4cac3bceb9..72935ef80371d 100644 --- a/packages/filesystem/src/browser/file-dialog/file-dialog.ts +++ b/packages/filesystem/src/browser/file-dialog/file-dialog.ts @@ -26,6 +26,7 @@ import { FileDialogWidget } from './file-dialog-widget'; import { FileDialogTreeFiltersRenderer, FileDialogTreeFilters } from './file-dialog-tree-filters-renderer'; import URI from '@theia/core/lib/common/uri'; import { Panel } from '@phosphor/widgets'; +import { FileService } from '../file-service'; export const OpenFileDialogFactory = Symbol('OpenFileDialogFactory'); export interface OpenFileDialogFactory { @@ -43,6 +44,7 @@ export const NAVIGATION_PANEL_CLASS = 'theia-NavigationPanel'; export const NAVIGATION_BACK_CLASS = 'theia-NavigationBack'; export const NAVIGATION_FORWARD_CLASS = 'theia-NavigationForward'; export const NAVIGATION_HOME_CLASS = 'theia-NavigationHome'; +export const NAVIGATION_UP_CLASS = 'theia-NavigationUp'; export const NAVIGATION_LOCATION_LIST_PANEL_CLASS = 'theia-LocationListPanel'; export const FILTERS_PANEL_CLASS = 'theia-FiltersPanel'; @@ -54,6 +56,7 @@ export const FILENAME_LABEL_CLASS = 'theia-FileNameLabel'; export const FILENAME_TEXTFIELD_CLASS = 'theia-FileNameTextField'; export const CONTROL_PANEL_CLASS = 'theia-ControlPanel'; +export const TOOLBAR_ITEM_TRANSFORM_TIMEOUT = 100; export class FileDialogProps extends DialogProps { @@ -116,13 +119,15 @@ export abstract class FileDialog extends AbstractDialog { protected readonly back: HTMLSpanElement; protected readonly forward: HTMLSpanElement; protected readonly home: HTMLSpanElement; + protected readonly up: HTMLSpanElement; protected readonly locationListRenderer: LocationListRenderer; protected readonly treeFiltersRenderer: FileDialogTreeFiltersRenderer | undefined; protected readonly treePanel: Panel; constructor( @inject(FileDialogProps) readonly props: FileDialogProps, - @inject(FileDialogWidget) readonly widget: FileDialogWidget + @inject(FileDialogWidget) readonly widget: FileDialogWidget, + @inject(FileService) readonly fileService: FileService ) { super(props); this.treePanel = new Panel(); @@ -145,8 +150,13 @@ export abstract class FileDialog extends AbstractDialog { navigationPanel.appendChild(this.home = createIconButton('fa', 'fa-home')); this.home.classList.add(NAVIGATION_HOME_CLASS); this.home.title = 'Go To Initial Location'; + navigationPanel.appendChild(this.up = createIconButton('fa', 'fa-level-up')); + this.up.classList.add(NAVIGATION_UP_CLASS); + this.up.title = 'Navigate Up One Directory'; - this.locationListRenderer = this.createLocationListRenderer(); + const locationListRendererHost = document.createElement('div'); + this.locationListRenderer = this.createLocationListRenderer(locationListRendererHost); + this.toDispose.push(this.locationListRenderer); this.locationListRenderer.host.classList.add(NAVIGATION_LOCATION_LIST_PANEL_CLASS); navigationPanel.appendChild(this.locationListRenderer.host); @@ -157,8 +167,8 @@ export abstract class FileDialog extends AbstractDialog { return this.widget.model; } - protected createLocationListRenderer(): LocationListRenderer { - return new LocationListRenderer(this.model); + protected createLocationListRenderer(host?: HTMLElement): LocationListRenderer { + return new LocationListRenderer(this.model, this.fileService, host); } protected createFileTreeFiltersRenderer(): FileDialogTreeFiltersRenderer | undefined { @@ -176,6 +186,7 @@ export abstract class FileDialog extends AbstractDialog { setEnabled(this.home, !!this.model.initialLocation && !!this.model.location && this.model.initialLocation.toString() !== this.model.location.toString()); + setEnabled(this.up, this.model.canNavigateUpward()); this.locationListRenderer.render(); if (this.treeFiltersRenderer) { @@ -185,6 +196,28 @@ export abstract class FileDialog extends AbstractDialog { this.widget.update(); } + protected handleEnter(event: KeyboardEvent): boolean | void { + if (event.target instanceof HTMLTextAreaElement || this.targetIsDirectoryInput(event.target) || this.targetIsInputToggle(event.target)) { + return false; + } + this.accept(); + } + + protected handleEscape(event: KeyboardEvent): boolean | void { + if (event.target instanceof HTMLTextAreaElement || this.targetIsDirectoryInput(event.target)) { + return false; + } + this.close(); + } + + protected targetIsDirectoryInput(target: EventTarget | null): boolean { + return target instanceof HTMLInputElement && target.classList.contains(LocationListRenderer.Styles.LOCATION_TEXT_INPUT_CLASS); + } + + protected targetIsInputToggle(target: EventTarget | null): boolean { + return target instanceof HTMLSpanElement && target.classList.contains(LocationListRenderer.Styles.LOCATION_INPUT_TOGGLE_CLASS); + } + protected appendFiltersPanel(): void { if (this.treeFiltersRenderer) { const filtersPanel = document.createElement('div'); @@ -216,16 +249,36 @@ export abstract class FileDialog extends AbstractDialog { this.appendCloseButton('Cancel'); this.appendAcceptButton(this.getAcceptButtonLabel()); - this.addKeyListener(this.back, Key.ENTER, () => this.model.navigateBackward(), 'click'); - this.addKeyListener(this.forward, Key.ENTER, () => this.model.navigateForward(), 'click'); + this.addKeyListener(this.back, Key.ENTER, () => { + this.addTransformEffectToIcon(this.back); + this.model.navigateBackward(); + }, 'click'); + + this.addKeyListener(this.forward, Key.ENTER, () => { + this.addTransformEffectToIcon(this.forward); + this.model.navigateForward(); + }, 'click'); this.addKeyListener(this.home, Key.ENTER, () => { + this.addTransformEffectToIcon(this.home); if (this.model.initialLocation) { this.model.location = this.model.initialLocation; } }, 'click'); + this.addKeyListener(this.up, Key.ENTER, () => { + this.addTransformEffectToIcon(this.up); + if (this.model.location) { + this.model.location = this.model.location.parent; + } + }, 'click'); super.onAfterAttach(msg); } + protected addTransformEffectToIcon(element: HTMLSpanElement): void { + const icon = element.getElementsByTagName('i')[0]; + icon.classList.add('active'); + setTimeout(() => icon.classList.remove('active'), TOOLBAR_ITEM_TRANSFORM_TIMEOUT); + } + protected abstract getAcceptButtonLabel(): string; protected onActivateRequest(msg: Message): void { @@ -239,9 +292,10 @@ export class OpenFileDialog extends FileDialog> { constructor( @inject(OpenFileDialogProps) readonly props: OpenFileDialogProps, - @inject(FileDialogWidget) readonly widget: FileDialogWidget + @inject(FileDialogWidget) readonly widget: FileDialogWidget, + @inject(FileService) readonly fileService: FileService ) { - super(props, widget); + super(props, widget, fileService); if (props.canSelectFiles !== undefined) { this.widget.disableFileSelection = !props.canSelectFiles; } @@ -288,9 +342,10 @@ export class SaveFileDialog extends FileDialog { constructor( @inject(SaveFileDialogProps) readonly props: SaveFileDialogProps, - @inject(FileDialogWidget) readonly widget: FileDialogWidget + @inject(FileDialogWidget) readonly widget: FileDialogWidget, + @inject(FileService) readonly fileService: FileService ) { - super(props, widget); + super(props, widget, fileService); widget.addClass(SAVE_DIALOG_CLASS); } diff --git a/packages/filesystem/src/browser/file-tree/file-tree-model.ts b/packages/filesystem/src/browser/file-tree/file-tree-model.ts index 1f8440e41157d..b4dd92bf41b3a 100644 --- a/packages/filesystem/src/browser/file-tree/file-tree-model.ts +++ b/packages/filesystem/src/browser/file-tree/file-tree-model.ts @@ -61,6 +61,8 @@ export class FileTreeModel extends TreeModelImpl implements LocationService { const node = DirNode.createRoot(fileStat); this.navigateTo(node); } + }).catch(() => { + // no-op, allow failures for file dialog text input }); } else { this.navigateTo(undefined); diff --git a/packages/filesystem/src/browser/location/location-renderer.tsx b/packages/filesystem/src/browser/location/location-renderer.tsx index 1ae65e21ac259..ae8c7092665a4 100644 --- a/packages/filesystem/src/browser/location/location-renderer.tsx +++ b/packages/filesystem/src/browser/location/location-renderer.tsx @@ -18,13 +18,80 @@ import URI from '@theia/core/lib/common/uri'; import { LocationService } from './location-service'; import { ReactRenderer } from '@theia/core/lib/browser/widgets/react-renderer'; import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { FileService } from '../file-service'; +import { DisposableCollection, Emitter } from '@theia/core/lib/common'; +interface AutoSuggestDataEvent { + input: string; + parent: string; + children: string[]; +} + +class ResolvedDirectoryCache { + protected pendingResolvedDirectories = new Map }>(); + protected cachedDirectories = new Map(); + + protected directoryResolvedEmitter = new Emitter(); + readonly onDirectoryDidResolve = this.directoryResolvedEmitter.event; + + constructor(protected readonly fileService: FileService) { } + + tryResolveChildDirectories(inputAsURI: URI): string[] | undefined { + const parentDirectory = inputAsURI.path.dir.toString(); + const input = inputAsURI.path.toString(); + if (this.cachedDirectories.has(parentDirectory)) { + return this.cachedDirectories.get(parentDirectory); + } else if (!this.pendingResolvedDirectories.has(parentDirectory)) { + const pendingDirectories = this.createResolutionPromise(parentDirectory); + this.pendingResolvedDirectories.set(parentDirectory, { input, pendingDirectories: pendingDirectories }); + } else if (this.pendingResolvedDirectories.has(parentDirectory)) { + // if promise has still not resolved, update its input value to latest + const pendingDirectoriesData = (this.pendingResolvedDirectories.get(parentDirectory)); + if (pendingDirectoriesData) { + pendingDirectoriesData.input = input; + } + } + return undefined; + } + + protected async createResolutionPromise(directoryToResolve: string): Promise { + return this.fileService.resolve(new URI(directoryToResolve)).then(({ children }) => { + if (children) { + const latestInput = this.pendingResolvedDirectories.get(directoryToResolve)?.input || ''; + const childDirectories = children.filter(child => child.isDirectory) + .map(directory => `${directory.resource.path}/`) + .sort(); + this.cachedDirectories.set(directoryToResolve, childDirectories); + this.directoryResolvedEmitter.fire({ parent: directoryToResolve, input: latestInput, children: childDirectories }); + } + }).catch(e => { + // no-op + }); + } +} export class LocationListRenderer extends ReactRenderer { + protected directoryCache: ResolvedDirectoryCache; + protected toDisposeOnNewCache = new DisposableCollection(); protected _drives: URI[] | undefined; + protected _doShowTextInput = false; + get doShowTextInput(): boolean { + return this._doShowTextInput; + } + set doShowTextInput(doShow: boolean) { + this._doShowTextInput = doShow; + if (doShow) { + this.initResolveDirectoryCache(); + } + } + protected lastUniqueTextInputLocation: URI | undefined; + protected previousAutocompleteMatch: string; + protected doAttemptAutocomplete = true; constructor( protected readonly service: LocationService, + protected readonly fileService: FileService, host?: HTMLElement ) { super(host); @@ -32,18 +99,110 @@ export class LocationListRenderer extends ReactRenderer { } render(): void { - super.render(); + ReactDOM.render(this.doRender(), this.host, this.doAfterRender); + } + + protected initResolveDirectoryCache(): void { + this.toDisposeOnNewCache.dispose(); + this.directoryCache = new ResolvedDirectoryCache(this.fileService); + this.toDisposeOnNewCache.push(this.directoryCache.onDirectoryDidResolve(({ parent, children, input }) => { + if (this.locationTextInput?.value === input) { + this.doRenderAutoSuggestion(this.locationTextInput, children); + } + })); + } + + protected doAfterRender = (): void => { const locationList = this.locationList; + const locationListTextInput = this.locationTextInput; if (locationList) { const currentLocation = this.service.location; locationList.value = currentLocation ? currentLocation.toString() : ''; + } else if (locationListTextInput) { + locationListTextInput.focus(); } - } + }; protected readonly handleLocationChanged = (e: React.ChangeEvent) => this.onLocationChanged(e); - protected doRender(): React.ReactNode { + protected readonly handleTextInputOnChange = (e: React.ChangeEvent) => this.trySuggestDirectory(e); + protected readonly handleTextInputKeyDown = (e: React.KeyboardEvent) => this.handleControlKeys(e); + protected readonly handleIconKeyDown = (e: React.KeyboardEvent) => this.toggleInputOnKeyDown(e); + protected readonly handleTextInputOnBlur = () => this.toggleToSelectInput(); + protected readonly handleTextInputMouseDown = (e: React.MouseEvent) => this.toggleToTextInputOnMouseDown(e); + + protected doRender(): React.ReactElement { + return ( + <> + {this.renderInputIcon()} + {this.doShowTextInput + ? this.renderTextInput() + : this.renderSelectInput() + } + + ); + } + + protected renderInputIcon(): React.ReactNode { + return ( + + + + ); + } + + protected renderTextInput(): React.ReactNode { + return ( + + ); + } + + protected renderSelectInput(): React.ReactNode { const options = this.collectLocations().map(value => this.renderLocation(value)); - return ; + return ( + + ); + } + + protected toggleInputOnKeyDown(e: React.KeyboardEvent): void { + if (e.key === 'Enter') { + this.doShowTextInput = true; + this.render(); + } + } + + protected toggleToTextInputOnMouseDown(e: React.MouseEvent): void { + if (e.currentTarget.id === 'select-input') { + e.preventDefault(); + this.doShowTextInput = true; + this.render(); + } + } + + protected toggleToSelectInput(): void { + if (this.doShowTextInput) { + this.doShowTextInput = false; + this.render(); + } } /** @@ -104,9 +263,69 @@ export class LocationListRenderer extends ReactRenderer { if (locationList) { const value = locationList.value; const uri = new URI(value); - this.service.location = uri; + this.trySetNewLocation(uri); + e.preventDefault(); + e.stopPropagation(); + } + } + + protected trySetNewLocation(newLocation: URI): void { + if (this.lastUniqueTextInputLocation === undefined) { + this.lastUniqueTextInputLocation = this.service.location; + } + // prevent consecutive repeated locations from being added to location history + if (this.lastUniqueTextInputLocation?.path.toString() !== newLocation.path.toString()) { + this.lastUniqueTextInputLocation = newLocation; + this.service.location = newLocation; + } + } + + protected trySuggestDirectory(e: React.ChangeEvent): void { + if (this.doAttemptAutocomplete) { + const inputElement = e.currentTarget; + const { value } = inputElement; + if (value.slice(-1) !== '/') { + const valueAsURI = new URI(value); + const autocompleteDirectories = this.directoryCache.tryResolveChildDirectories(valueAsURI); + if (autocompleteDirectories) { + this.doRenderAutoSuggestion(inputElement, autocompleteDirectories); + } + } + } + } + + protected doRenderAutoSuggestion(inputElement: HTMLInputElement, children: string[]): void { + const { value, selectionStart } = inputElement; + if (this.locationTextInput) { + const firstMatch = children?.find(child => child.includes(value)); + if (firstMatch) { + this.locationTextInput.value = firstMatch; + this.locationTextInput.selectionStart = selectionStart; + this.locationTextInput.selectionEnd = firstMatch.length; + } + } + } + + protected handleControlKeys(e: React.KeyboardEvent): void { + this.doAttemptAutocomplete = e.key !== 'Backspace'; + if (e.key === 'Enter') { + const locationTextInput = this.locationTextInput; + if (locationTextInput) { + // remove extra whitespace and any trailing slashes or periods. + const sanitizedInput = locationTextInput.value.trim().replace(/[\/\\.]*$/, ''); + const uri = new URI(sanitizedInput); + this.trySetNewLocation(uri); + this.toggleToSelectInput(); + } + } else if (e.key === 'Escape') { + this.toggleToSelectInput(); + } else if (e.key === 'Tab') { + e.preventDefault(); + const textInput = this.locationTextInput; + if (textInput) { + textInput.selectionStart = textInput.value.length; + } } - e.preventDefault(); e.stopPropagation(); } @@ -118,12 +337,31 @@ export class LocationListRenderer extends ReactRenderer { return undefined; } + get locationTextInput(): HTMLInputElement | undefined { + const locationTextInput = this.host.getElementsByClassName(LocationListRenderer.Styles.LOCATION_TEXT_INPUT_CLASS)[0]; + if (locationTextInput instanceof HTMLInputElement) { + return locationTextInput; + } + return undefined; + } + + dispose(): void { + super.dispose(); + this.toDisposeOnNewCache.dispose(); + } } export namespace LocationListRenderer { export namespace Styles { export const LOCATION_LIST_CLASS = 'theia-LocationList'; + export const LOCATION_INPUT_TOGGLE_CLASS = 'theia-LocationInputToggle'; + export const LOCATION_TEXT_INPUT_CLASS = 'theia-LocationTextInput'; + } + + export namespace Tooltips { + export const TOGGLE_TEXT_INPUT = 'Switch to text-based input'; + export const TOGGLE_SELECT_INPUT = 'Switch to location list'; } export interface Location { diff --git a/packages/filesystem/src/browser/style/file-dialog.css b/packages/filesystem/src/browser/style/file-dialog.css index bcb4963ef5476..d63a0d3fe5832 100644 --- a/packages/filesystem/src/browser/style/file-dialog.css +++ b/packages/filesystem/src/browser/style/file-dialog.css @@ -14,6 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +:root { + --theia-private-file-dialog-input-height: 21px; + --theia-private-location-list-panel-left: 82px; + --theia-private-location-list-panel-width: 417px; + --theia-private-navigation-panel-icon-size: 21px; + --theia-private-navigation-panel-line-height: 23px; +} + /* * Open and Save file dialogs */ @@ -56,23 +64,45 @@ /* * Navigation panel items */ - -.dialogContent .theia-NavigationBack, -.dialogContent .theia-NavigationForward, -.dialogContent .theia-NavigationHome { + +.dialogContent .theia-NavigationPanel span + { position: absolute; top: 0px; - line-height: 23px; + line-height: var(--theia-private-navigation-panel-line-height); cursor: pointer; + width: var(--theia-private-navigation-panel-icon-size); + text-align: center; +} + +.dialogContent .theia-NavigationPanel span:not(.theia-mod-disabled) i.active +{ + transform: scale(var(--theia-toolbar-active-transform-scale)); +} + +.dialogContent .theia-NavigationPanel span:focus +{ outline: none; + box-shadow: none; +} + +.dialogContent .theia-NavigationPanel span:focus-visible +{ + outline-width: 1px; + outline-style: solid; + outline-offset: -1px; + opacity: 1 !important; + outline-color: var(--theia-focusBorder); } -.dialogContent .theia-NavigationBack:focus, -.dialogContent .theia-NavigationForward:focus, -.dialogContent .theia-NavigationHome:focus { +.dialogContent .theia-NavigationBack.theia-mod-disabled:focus, +.dialogContent .theia-NavigationForward.theia-mod-disabled:focus, +.dialogContent .theia-NavigationHome.theia-mod-disabled:focus, +.dialogContent .theia-NavigationUp.theia-mod-disabled:focus +{ outline: none; border: none; - box-shadow: none; + opacity: var(--theia-mod-disabled-opacity) !important; } .dialogContent .theia-NavigationBack { @@ -87,15 +117,44 @@ left: 41px; } +.dialogContent .theia-NavigationUp { + left: 61px; +} + .dialogContent .theia-LocationListPanel { position: absolute; - left: 72px; + display: flex; top: 1px; + left: var(--theia-private-location-list-panel-left); + width: var(--theia-private-location-list-panel-width); + height: var(--theia-private-file-dialog-input-height); } -.dialogContent .theia-LocationList { - width: 427px; - height: 21px; +.dialogContent .theia-LocationInputToggle { + text-align: center; + left: 0; + width: var(--theia-private-navigation-panel-icon-size); + height: var(--theia-private-navigation-panel-icon-size); + z-index: 1; +} + +.dialogContent .theia-LocationList, +.dialogContent .theia-LocationTextInput +{ + box-sizing: content-box; + padding: unset; + position: absolute; + top: 0; + left: 0; + height: var(--theia-private-file-dialog-input-height); + border: var(--theia-border-width) solid var(--theia-input-border); +} + +.dialogContent .theia-LocationList, +.dialogContent .theia-LocationTextInput +{ + padding-left: var(--theia-private-navigation-panel-icon-size); + width: calc(100% - var(--theia-private-navigation-panel-icon-size)); } /* @@ -117,7 +176,7 @@ .dialogContent .theia-FileTreeFiltersList { width: 427px; - height: 21px; + height: var(--theia-private-file-dialog-input-height); } /*