Core framework for building cloud and desktop IDE applications using modern web technologies with TypeScript and dependency injection.
—
Theia's widget system provides comprehensive UI components built on Phosphor/Lumino with React integration, layout management, and extensible widget types for building modern IDE interfaces.
Foundation widget class providing core widget functionality and lifecycle management.
/**
* Base widget class for all UI components
*/
class Widget {
/** Unique widget identifier */
readonly id: string;
/** Widget title with label, icon, and other metadata */
readonly title: Title<Widget>;
/** Parent widget if this widget is attached */
readonly parent: Widget | null;
/** True if widget is attached to DOM */
readonly isAttached: boolean;
/** True if widget is visible */
readonly isVisible: boolean;
/** True if widget is disposed */
readonly isDisposed: boolean;
/**
* Show the widget
*/
show(): void;
/**
* Hide the widget
*/
hide(): void;
/**
* Close the widget
*/
close(): void;
/**
* Dispose the widget and clean up resources
*/
dispose(): void;
/**
* Activate the widget (give it focus)
*/
activate(): void;
/**
* Update the widget's appearance
*/
update(): void;
}Widget base class for React-based components with lifecycle integration.
/**
* Widget that renders React components
*/
class ReactWidget extends Widget {
/**
* Create React widget with optional props
* @param props - React component props
*/
constructor(props?: any);
/**
* Render the React component
* @returns React element to render
*/
protected render(): React.ReactNode;
/**
* Called after widget is attached to DOM
*/
protected onAfterAttach(): void;
/**
* Called before widget is detached from DOM
*/
protected onBeforeDetach(): void;
/**
* Called when widget is updated
*/
protected onUpdateRequest(): void;
/**
* Set React component state
* @param state - New state object
* @param callback - Optional callback after state update
*/
protected setState(state: any, callback?: () => void): void;
}Usage Example:
import React from "react";
import { ReactWidget } from "@theia/core/lib/browser";
interface MyWidgetState {
count: number;
}
export class MyCounterWidget extends ReactWidget {
protected state: MyWidgetState = { count: 0 };
constructor() {
super();
this.id = 'counter-widget';
this.title.label = 'Counter';
this.title.iconClass = 'fa fa-calculator';
}
protected render(): React.ReactNode {
return (
<div className="counter-widget">
<h3>Count: {this.state.count}</h3>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
);
}
private increment = (): void => {
this.setState({ count: this.state.count + 1 });
};
private decrement = (): void => {
this.setState({ count: this.state.count - 1 });
};
}Main application container that manages widget layout and areas.
/**
* Main application shell managing widget layout
*/
class ApplicationShell extends Widget {
/**
* Add widget to specified area
* @param widget - Widget to add
* @param area - Shell area to add to
* @param options - Optional layout options
*/
addWidget(widget: Widget, area?: ApplicationShell.Area, options?: ApplicationShell.WidgetOptions): void;
/**
* Activate a widget
* @param id - Widget ID to activate
*/
activateWidget(id: string): Widget | undefined;
/**
* Close all widgets in area
* @param area - Shell area to close
*/
closeAll(area?: ApplicationShell.Area): void;
/**
* Get all widgets in area
* @param area - Shell area
* @returns Array of widgets in area
*/
getWidgets(area: ApplicationShell.Area): Widget[];
}
namespace ApplicationShell {
/**
* Shell areas where widgets can be placed
*/
enum Area {
left = 'left',
right = 'right',
main = 'main',
bottom = 'bottom',
top = 'top'
}
/**
* Options for adding widgets to shell
*/
interface WidgetOptions {
/** Area to add widget to */
area?: Area;
/** Tab index for ordering */
rank?: number;
/** Reference widget for relative positioning */
ref?: Widget;
/** Insertion mode relative to reference */
mode?: 'tab-after' | 'tab-before' | 'split-top' | 'split-bottom' | 'split-left' | 'split-right';
}
}
/**
* Service token for ApplicationShell
*/
const ApplicationShell: symbol;Manages widget lifecycle, creation, and disposal.
/**
* Manages widget lifecycle and factories
*/
interface WidgetManager {
/**
* Get or create widget by ID
* @param id - Widget ID
* @returns Promise resolving to widget
*/
getOrCreateWidget<T extends Widget>(id: string): Promise<T>;
/**
* Try to get existing widget
* @param id - Widget ID
* @returns Widget if exists, undefined otherwise
*/
tryGetWidget<T extends Widget>(id: string): T | undefined;
/**
* Get all widgets of a specific type
* @param factoryId - Widget factory ID
* @returns Array of matching widgets
*/
getWidgets<T extends Widget>(factoryId: string): T[];
}
/**
* Factory for creating widgets
*/
interface WidgetFactory {
/** Unique factory identifier */
readonly id: string;
/**
* Create widget instance
* @param options - Creation options
* @returns Promise resolving to widget
*/
createWidget(options?: any): Promise<Widget>;
}
/**
* Service tokens
*/
const WidgetManager: symbol;
const WidgetFactory: symbol;Widget mixin for widgets that contain saveable content.
/**
* Widget that contains saveable content
*/
interface SaveableWidget extends Widget {
/**
* Save the widget's content
* @returns Promise that resolves when save completes
*/
save(): Promise<void>;
/**
* True if widget has unsaved changes
*/
readonly dirty: boolean;
/**
* Event fired when dirty state changes
*/
readonly onDirtyChanged: Event<void>;
}
namespace SaveableWidget {
/**
* Type guard for saveable widgets
* @param widget - Widget to check
* @returns True if widget is saveable
*/
function is(widget: Widget): widget is SaveableWidget;
}Usage Example:
import { ReactWidget, SaveableWidget } from "@theia/core/lib/browser";
import { Emitter, Event } from "@theia/core";
export class MyEditorWidget extends ReactWidget implements SaveableWidget {
private readonly onDirtyChangedEmitter = new Emitter<void>();
readonly onDirtyChanged: Event<void> = this.onDirtyChangedEmitter.event;
private _dirty = false;
private content = '';
get dirty(): boolean {
return this._dirty;
}
private setDirty(dirty: boolean): void {
if (this._dirty !== dirty) {
this._dirty = dirty;
this.onDirtyChangedEmitter.fire();
this.title.className = dirty ? 'dirty' : '';
}
}
async save(): Promise<void> {
await this.saveContent(this.content);
this.setDirty(false);
}
private updateContent(newContent: string): void {
this.content = newContent;
this.setDirty(true);
this.update();
}
private async saveContent(content: string): Promise<void> {
// Save implementation
}
}Additional widget utilities and extensions for specialized use cases.
/**
* Widget that can be extracted to separate window
*/
interface ExtractableWidget extends Widget {
/**
* True if widget can be extracted
*/
readonly isExtractable: boolean;
/**
* Extract widget to separate window
*/
extractWidget(): void;
}
/**
* Stateful widget that can save/restore state
*/
interface StatefulWidget extends Widget {
/**
* Store widget state
* @returns State object
*/
storeState(): any;
/**
* Restore widget state
* @param oldState - Previously stored state
*/
restoreState(oldState: any): void;
}
/**
* Widget with custom context menu
*/
interface ContextMenuWidget extends Widget {
/**
* Create context menu for widget
* @param event - Mouse event
* @returns Context menu items
*/
createContextMenu(event: MouseEvent): ContextMenu;
}Widget title management and tab behavior.
/**
* Widget title containing label, icon, and metadata
*/
class Title<T> {
/** Display label */
label: string;
/** Icon CSS class */
iconClass: string;
/** Icon label for accessibility */
iconLabel: string;
/** Caption/tooltip text */
caption: string;
/** CSS class name */
className: string;
/** True if title can be closed */
closable: boolean;
/** Dataset for custom attributes */
dataset: Record<string, string>;
/** Owner widget */
readonly owner: T;
/** Event fired when title changes */
readonly changed: Event<void>;
}Common CSS classes used by Theia widgets:
/**
* Standard CSS classes for widgets
*/
namespace WidgetCSS {
const WIDGET = 'theia-widget';
const PANEL = 'theia-panel';
const TOOLBAR = 'theia-toolbar';
const TAB_BAR = 'theia-tab-bar';
const DOCK_PANEL = 'theia-dock-panel';
const SPLIT_PANEL = 'theia-split-panel';
}/**
* Layout configuration for widgets
*/
interface LayoutOptions {
/** Minimum width in pixels */
minWidth?: number;
/** Minimum height in pixels */
minHeight?: number;
/** Maximum width in pixels */
maxWidth?: number;
/** Maximum height in pixels */
maxHeight?: number;
/** Stretch factor for resizing */
stretch?: number;
}/**
* Widget-related type definitions
*/
type WidgetConstructor = new (...args: any[]) => Widget;
interface WidgetOptions {
id?: string;
title?: Partial<Title.Dataset>;
}
namespace Widget {
/**
* Widget message types
*/
enum MessageType {
BeforeAttach = 'before-attach',
AfterAttach = 'after-attach',
BeforeDetach = 'before-detach',
AfterDetach = 'after-detach',
BeforeShow = 'before-show',
AfterShow = 'after-show',
BeforeHide = 'before-hide',
AfterHide = 'after-hide',
CloseRequest = 'close-request',
Resize = 'resize',
UpdateRequest = 'update-request',
FitRequest = 'fit-request',
ActivateRequest = 'activate-request'
}
}Install with Tessl CLI
npx tessl i tessl/npm-theia--core