@eyeauras/workbench
Host-neutral TypeScript workbench services, contribution points, layouts, styles, and an imperative DOM shell shaped after Visual Studio Code.
Quickstart
Use an ESM browser bundler with CSS-import support (for example Vite), a browser DOM, and a sized host.
npm install @eyeauras/[email protected] monaco-editor@^0.52.2 @vscode/codicons
import { createWorkbench } from "@eyeauras/workbench";
import "@vscode/codicons/dist/codicon.css";
const host = document.createElement("div");
host.style.cssText = "width:100%;height:600px";
document.body.append(host);
const workbench = createWorkbench(host, { productName: "My application" });
workbench.registerView({
id: "files", title: "Files", location: "sidebar", icon: "files",
render(container) { container.textContent = "Your files"; },
});
workbench.openEditor({ id: "welcome", title: "Welcome", editorType: "text", data: "Hello" });
// Call on host unmount; renderers may return their own resource disposable.
export function dispose() { workbench.dispose(); host.remove(); }
The shell installs shared styles and observes resizing. Standalone controls require
installWorkbenchStyles(document) from @eyeauras/workbench/styles once. Codicon classes also need
the font CSS above. Editor workers are only needed by actual Monaco editors. See the
full quickstart for React, build prerequisites and package selection.
API and controls
Rich action UI (/controls): WorkbenchDropdownMenu opens a menu from the whole trigger;
WorkbenchButtonWithDropdown separates primary action from alternatives; createWorkbenchToolbar
provides live VS Code ToolBar rendering. WorkbenchContextMenuService is shared with Problems.
Opt the shell in with createWorkbench(host, { actionUI: createWorkbenchActionUI }); existing
menus.createToolbar(menu, className?) calls remain compatible. See the repository's
Actions guide.
monaco-editor is a required peer dependency because the package reuses VS Code's context-key parser.
@vscode/codicons is optional. Import @eyeauras/workbench/vscode-grid,
@eyeauras/workbench/controls, or @eyeauras/workbench/problems only when the host bundler can resolve
CSS imports from JavaScript. The aggregate @eyeauras/workbench root is a browser-oriented,
Monaco-bearing surface and imports grid/splitview CSS.
The owning notification service, toast renderer, split layout, and style installer are also available as independent light subpaths. Each module is Node-loadable without installing a DOM shim; DOM values are required only when invoking the renderer, layout, or style installer that operates on them.
The grid's tab overflow picker uses a borrowed contextMenus service supplied in its create/restore
options. The shell supplies its actionUI service; the React grid adapter owns a service for its lifetime.
Standalone imperative grids can pass WorkbenchContextMenuService from /controls. Without a menu
service the grid renders its tab strip without an overflow picker. Disposing a grid closes its own
popup, never the borrowed service.
Set WorkbenchGridGroup.preserveEmpty: true for a document group whose position and size
must survive closing or moving its last tab. It keeps the normal empty placeholder and
roundtrips through layout serialization/restoration. The default still removes empty groups.
Trees
Use @eyeauras/workbench/tree with optional selection, input, guides and drag/drop subpaths.
The consumer supplies a TreeSource and independently bound content VMs; state flows through
revisioned channels. See the package guide.
import { WorkbenchNotificationService } from "@eyeauras/workbench/notifications";
import { renderNotificationToasts } from "@eyeauras/workbench/notification-toasts";
import { createWorkbenchSplitLayout } from "@eyeauras/workbench/split-layout";
import { installWorkbenchStyles, workbenchStyles } from "@eyeauras/workbench/styles";
import { WorkbenchComboBox, WorkbenchTagEditor } from "@eyeauras/workbench/controls";
import { WorkbenchConfirmButton } from "@eyeauras/workbench/controls/confirm-button";
import { WorkbenchToggleTextBox } from "@eyeauras/workbench/controls/toggle-text-box";
import { WorkbenchProblemsIndicator, WorkbenchProblemsView } from "@eyeauras/workbench/problems";
The controls subpath wraps VS Code's InputBox, Button, Toggle, ProgressBar, SelectBox, List,
and ContextView behind stable framework-neutral APIs. ComboBox<T> adds cancellable suggestions and
an accessible disclosure button that opens the complete suggestion set;
TagEditor composes it for string tags and normalized duplicate rejection. InputBox's placeholder is
its native watermark; string prefix and suffix adorn the field without entering its value.
The shared ContextView portal uses native popover="auto" when available, so anchored controls receive one
browser-managed top layer, light-dismiss, and Esc contract instead of implementing dismissal themselves.
FeedbackButton
WorkbenchFeedbackButton is a separate control exported from @eyeauras/workbench/controls.
It composes the VS Code Button; existing Button and ConfirmButton behavior is unchanged.
import { WorkbenchFeedbackButton } from "@eyeauras/workbench/controls";
const copy = new WorkbenchFeedbackButton(container, {
action: () => navigator.clipboard.writeText(text),
idle: { label: "Copy" },
pending: { label: "Copying…" },
success: { label: "Copied" },
error: { label: "Copy failed" },
resetMs: 3000,
});
copy.onDidFail(error => reportError(error));
// Dispose with the containing view.
Each face accepts label, a decorative icon: Node, and ariaLabel (required when icon-only).
Supply distinct icon nodes per face; do not nest interactive content. Result names are announced through
a polite live region. Resolving the action means success, including a resolved false; throw/reject for
failure. Repeated activation is ignored only while pending or disabled. update changes options without
restarting a running action or its result timer; an omitted face uses the default English text, and passing
undefined restores that default. dispose removes UI/timers but does not cancel the host operation.
All faces share icon/text columns and reserve layout space from the start, so neither the icon nor the
start of the label jumps between states. Pending blocks reentry without applying the host-disabled dimming.
The control is constrained by the parent width; long labels truncate with
a full tooltip. Style .ea-workbench-feedback-button and its [data-state],
.ea-workbench-feedback-face, .ea-workbench-feedback-icon, .ea-workbench-feedback-label slots;
use the usual inherited --vscode-button-background, --vscode-button-hoverBackground,
--vscode-button-foreground, and --vscode-button-border tokens. The demo Controls tab includes success
and failure examples; the compact Problems popup uses it for Copy All.
TagEditor keyboard and styling contract
The field stays on one line. Chips and the existing input share an internal horizontal viewport; keyboard focus reveals the active item without scrolling the hosting document. Scroll buttons keep a stable footprint and move by one viewport width; native scroll limits clamp them at either end. They remain available at the ends and are disabled with the editor. CSS owns sizing and clipping; there is no resize observer or polling to decide the layout.
When its input is empty, ArrowLeft or the first Backspace moves to the final tag without mutating
the value. ArrowLeft / ArrowRight then move among tags and back to the input. Enter or a click
edits the focused tag in place; Enter or comma commits, Escape cancels, and a valid free-form edit
commits on blur. Backspace or Delete removes a keyboard-focused tag. Set editable: false when
existing tags may be removed but not renamed. IME composition is never confirmed as a tag.
The default accessible description explains the empty-input shortcuts; set ariaDescription to
provide localized instructions. Add, edit, and remove changes are announced through a polite live
region. Empty and duplicate edits remain open so the user can correct them.
The following slot classes are stable customization points: .ea-workbench-tag-editor,
.ea-workbench-tag-viewport, .ea-workbench-tag-scroll, .ea-workbench-tags, .ea-workbench-tag, .ea-workbench-tag-label,
.ea-workbench-tag-remove, and .ea-workbench-tag-input. The tag being edited also carries
.ea-workbench-tag.is-editing. The existing ComboBox input is moved into that slot, so customization
does not have to account for a second input or a separate editing model. Prefer inherited custom
properties for colors:
| Property | Purpose |
|---|---|
--ea-tag-editor-background | editor background |
--ea-tag-editor-border-color | idle border |
--ea-tag-editor-focus-border-color | focus-within border |
--ea-tag-editor-tag-background | tag background |
--ea-tag-editor-tag-foreground | tag text/remove foreground |
--ea-tag-editor-tag-remove-hover-background | remove-button hover background |
Each property falls back to the matching VS Code theme variable. Internal Monaco classes and DOM beyond the documented slots are not customization API.
ConfirmButton and ToggleTextBox are framework-neutral forms of the established React composites.
Expanded confirmation may use an icon-only cancel action by pairing cancelIcon with cancelAriaLabel.
Their direct subpaths are intentionally light: they do not import the aggregate CSS-bearing VS Code
controls slice.
workbench.services.diagnostics is the shared host-neutral store for validation, compiler, and runtime
findings. Publishers identify themselves with a stable owner and publish against an exact resource
string; set(owner, resource, diagnostics) replaces one tuple, while replaceOwner(owner, entries)
atomically replaces that owner's full snapshot without touching other owners. Reads are immutable and
can be filtered by owner, resource, or severity. Monaco marker projection is supplied by
@eyeauras/workbench-monaco; other presentations can read the same store independently.
The @eyeauras/workbench/problems subpath supplies two reusable presentations without adding another
store. WorkbenchProblemsView is an IDE-style List-backed grid with Severity, Code, Message, File/location,
and Source columns, text filtering across displayed fields, severity toggles, stable multi-selection, and a
visible Showing X of Y summary. Ctrl/Cmd+C copies the selection; its context/overflow menu also exposes
Open, Copy Selected, and Copy All Problems. Copy All always means the current filtered and sorted projection,
not only virtualized rows. Truncated cells disclose their full text on hover.
WorkbenchProblemsIndicator is the self-contained presentation for windows and dialogs that cannot fit the
full grid. Its indicator shows the highest non-zero severity and opens a native-popover list containing every
diagnostic across all severities. maxVisible limits only the viewport before it scrolls; it never truncates
the data. The popup activates rows, copies the focused row with Ctrl/Cmd+C, and copies the complete list from
its header without requiring a separate Problems view. Both
widgets update from
onDidChangeDiagnostics, accept an optional openDiagnostic(diagnostic) callback, and accept
formatResource(resource) for consumer-owned display labels. The default formatter preserves the exact
resource string; the framework never parses it as a path.
const problems = new WorkbenchProblemsView(container, workbench.services.diagnostics, {
formatResource: (resource) => ({ label: resource }),
openDiagnostic: (diagnostic) => openResource(diagnostic.resource, diagnostic.range),
// Optional host boundary and row serialization; navigator.clipboard and IDE-style text are the defaults.
writeClipboardText: (text) => hostClipboard.writeText(text),
formatClipboardRow: (row) => `${row.diagnostic.resource}: ${row.diagnostic.message}`,
});
const indicator = new WorkbenchProblemsIndicator(statusHost, workbench.services.diagnostics, {
openDiagnostic: (diagnostic) => openResource(diagnostic.resource, diagnostic.range),
maxVisible: 6,
writeClipboardText: (text) => hostClipboard.writeText(text),
});
Dispose both widgets with their host view. Text and severity filters are presentation state; diagnostics remain owned solely by their publishers.
The package is developed in the EyeAuras UI Workbench repository. See its root README for API orientation, architecture, and consumer requirements.
Message boxes and input dialogs
Use workbench.services.messageBoxes.show() for save prompts and custom dialogs. Content receives a
typed action context for validation, guarded form submission and input focus. React consumers can use
renderReactMessageBoxContent(context => <YourContent context={context} />) from
@eyeauras/workbench-react/core. See the Message-box API and complete React input example.
Tooltips and hover cards
Use workbench.services.hover.setupDelayedHover(target, options, { groupId }), or create a standalone
WorkbenchHoverService. Content accepts text, a DOM element or a disposable renderer. Choose a side
with position and matching edges or centers with alignment: "start" | "center" | "end".
Related targets can share groupId to switch immediately after the first preview appears. Alt keeps
the current card open; Alt+ArrowDown enters it and Escape closes it with guarded focus restoration.
Dispose registrations with their owning view.
The optional React adapter is WorkbenchTooltip from @eyeauras/workbench-react/hover.
See the Hover guide for
DOM/React examples, defaults, native Popover fallbacks and lifecycle details. The Controls demo includes
Hover / Tooltip and the separate twelve-point Hover anchors example.
Host rail toggles registered with registerToggle accept position: "primary" for the top cluster or "secondary" for the bottom cluster. Omitted placement stays secondary. This changes placement only; host-controlled state and retained button identity are unchanged.
Message boxes accept an optional width in CSS pixels for table-based content; the shell still limits the dialog to the viewport. Other dialogs retain the standard width.