Setup
Inject WrWindowManager to open windows. Drop <wr-window-taskbar /> once in your shell so minimized windows have somewhere to land.
import { WrWindowManager, WrWindowTaskbar } from 'ngwr/window';
@Component({
imports: [WrWindowTaskbar],
template: '<wr-window-taskbar />',
})
export class AppRoot {
private readonly windows = inject(WrWindowManager);
open(): void {
this.windows.open(EditorComponent, { title: 'Editor' });
}
}Basic
manager.open(component, config) portals component as the window body. The returned WrWindowRef resolves afterClosed() with whatever the body passed to ref.close(value).
const ref = manager.open(EditorComponent, {
title: 'Untitled.md',
size: 'md',
id: 'editor', // singleton: same id → same window
});
const result = await ref.afterClosed();OS chrome presets
os swaps the action cluster style. 'auto' (the default) reads navigator.userAgentData.platform / navigator.platform and picks the right one for the visiting user — SSR-safe, unknown platforms fall back to windows.
manager.open(EditorComponent); // os: 'auto' (default)
manager.open(EditorComponent, { os: 'macos' });
manager.open(EditorComponent, { os: 'windows' });
manager.open(EditorComponent, { os: 'linux' });Compact chrome
chromeSize: 'sm' shrinks the title bar to 1.625rem with smaller actions. Great for utility panels / docked tools.
Persisted position
storage: { key, prefix, persist } keeps geometry in WrStorage — drag the window, close, reopen, same geometry.
manager.open(EditorComponent, {
id: 'editor', // stable id used by workspace save
storage: {
key: 'editor', // wr:window:my-app:editor
prefix: 'my-app',
persist: 'all', // 'position' | 'size' | 'all'
},
});
// Drop the persisted state when you need a fresh open:
manager.clearPersistedPosition({ key: 'editor', prefix: 'my-app' });Snap regions
snap: 'edges' snaps to halves; 'all' adds the four corners. A translucent rectangle previews the target while dragging.
manager.open(EditorComponent, {
snap: 'all', // drag to any edge to snap halves; corners give quarters
});Taskbar
<wr-window-taskbar /> lists every minimized window. Click a tab to restore + focus. Opt windows out via config.taskbar = false.
<wr-window-taskbar /> <!-- bottom (default) -->
<wr-window-taskbar position="top" />Workspace save / restore
saveLayout(name) snapshots every open window. restoreLayout(name) re-applies it once you've re-opened the matching components (matched by config.id).
// snapshot every open window's geometry + state
manager.saveLayout('default');
// later — for windows still open, geometry is re-applied in place. For
// windows that were closed, the opener callback re-creates them and the
// saved geometry is used as the seed instead of the cascade default.
manager.restoreLayout('default', (id, snap) => {
if (id.startsWith('editor:')) {
manager.open(EditorComponent, { id, title: snap.title });
} else if (id === 'settings') {
manager.open(SettingsComponent, { id, title: snap.title });
}
});
// drop a snapshot
manager.clearLayout('default');Driving the window from inside
The projected component can inject(WR_WINDOW_REF) to get its own ref + inject(WR_WINDOW_DATA) to read the payload. Pair beforeClose with ref.close(value) for confirmation prompts.
// Inside the projected component
const ref = inject<WrWindowRef<MyComponent, MyResult>>(WR_WINDOW_REF);
const data = inject<MyData>(WR_WINDOW_DATA);
ref.beforeClose(async () => {
if (!isDirty) return true;
return await confirmDiscard();
});
ref.setTitle(`${docName} — ${isDirty ? 'unsaved' : 'saved'}`);
await save();
ref.close(savedDocId);WrWindowConfig
| Name | Description | Type | Default |
|---|---|---|---|
WrWindowConfig | Options for WrWindowManager.open(component, config). | interface | — |
id | Stable identifier — used by the taskbar and workspace save. Auto-generated when omitted. | string | auto |
title | Chrome title text. | string | '' |
os | OS chrome preset. 'auto' reads the user's platform from the browser and picks the right one (SSR-safe). | 'auto' | 'macos' | 'windows' | 'linux' | 'auto' |
size | Initial size preset. Overridden by width / height when provided. | 'sm' | 'md' | 'lg' | 'md' |
chromeSize | Title-bar density. sm shrinks the bar + action dots for utility panels. | 'sm' | 'md' | 'md' |
x / y / width / height | Explicit initial geometry in px. | number | cascade |
minWidth / minHeight / maxWidth / maxHeight | Resize bounds. | number | 220 / 140 / ∞ / ∞ |
movable / resizable / keepInViewport | Drag, resize, viewport-clamp toggles. | boolean | true |
snap | Drag-to-edge snap — edges (halves + maximise) or all (also corners). | 'none' | 'edges' | 'all' | 'none' |
showMinimize / showMaximize | Render the corresponding chrome action. Omit to follow the chrome — every OS but Linux shows both, and the Linux preset is close-only by convention. Pass true to override that, which is what a Linux user needs before a window can reach the taskbar. | boolean | per-OS |
showClose | Render the close action. Not per-OS — every chrome closes. | boolean | true |
closeOnEscape | Close when the ESC key is pressed. | boolean | true |
taskbar | Show in <wr-window-taskbar> when minimized. | boolean | true |
animations | Open-fade + state transitions. Auto-disabled by prefers-reduced-motion. | boolean | true |
dragHandle | CSS selector inside the projected content that restricts the move-grab area. | string | null | null |
storage | { key, prefix?, persist? } — auto-save geometry to WrStorage, hydrate on next open. | WrWindowStorageConfig | — |
data | Arbitrary payload — read it inside the projected component via inject(WR_WINDOW_DATA). | D | null |
WrWindowManager
| Name | Description | Type | Default |
|---|---|---|---|
WrWindowManager | Singleton service — the only entry point for <wr-window>. Owns the stack, taskbar list, and workspace save. | service | — |
open(component, config?) | Open a window with component as its body. Returns a WrWindowRef you can drive imperatively. | <C, R>(c, cfg?) => WrWindowRef<C, R> | — |
closeAll() | Close every currently-open window. | () => void | — |
findById(id) | Look up an open window by its config.id — null when no match. | (id: string) => WrWindowRef | null | — |
windows | Signal of every currently-open window. | Signal<readonly WrWindowRef[]> | — |
minimized | Signal of minimized windows opted into the taskbar — drives <wr-window-taskbar>. | Signal<readonly WrWindowRef[]> | — |
clearPersistedPosition(storage) | Drop the saved geometry for a window so the next open uses config defaults. | (cfg: WrWindowStorageConfig) => void | — |
saveLayout(name) | Persist every open window's geometry + state under name. | (name: string) => void | — |
restoreLayout(name, open?) | Apply a saved workspace by id. Matching open windows get geometry re-applied in place. For missing windows, the optional open callback is invoked to re-create them — they seed at the saved coords (no cascade flicker). | (name, open?: (id, snap) => void) => void | — |
clearLayout(name) | Drop a saved workspace. | (name: string) => void | — |
WrWindowRef
| Name | Description | Type | Default |
|---|---|---|---|
WrWindowRef | Handle returned by manager.open(). Drive the window imperatively, await its result. | class | — |
id | Stable id (from config.id or auto-generated). | string | — |
componentInstance | The projected component instance. | C | — |
state / x / y / width / height / z / title | Live geometry + state signals. | Signal<…> | — |
close(result?) | Closes the window, running beforeClose if registered. | (result?: R) => Promise<void> | — |
afterClosed() | Resolves with the close result. | () => Promise<R | undefined> | — |
beforeClose(hook) | Register a guard — return falsy to veto a close. | (hook) => void | — |
minimize() / maximize() / restore() / focus() | Lifecycle controls. | () => void | — |
moveTo(x, y) / resizeTo(w, h) / center() | Programmatic geometry. | () => void | — |
setTitle(title) | Update the chrome title. | (title: string) => void | — |
CSS variables
Custom properties ngwr/window publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.
| Variable | Default | Declared on |
|---|---|---|
--wr-window-action-size | 1.5rem | .wr-window +1 variant override |
--wr-window-anim-duration | 0.18s | .wr-window |
--wr-window-bg | var(--wr-color-surface) | .wr-window |
--wr-window-border | var(--wr-color-outline) | .wr-window |
--wr-window-chrome-bg | var(--wr-color-hover) | .wr-window |
--wr-window-chrome-color | var(--wr-color-on-surface) | .wr-window |
--wr-window-chrome-height | 2.25rem | .wr-window +1 variant override |
--wr-window-handle-corner | 14px | .wr-window |
--wr-window-handle-size | 8px | .wr-window |
--wr-window-radius | var(--wr-border-radius-base) | .wr-window +1 variant override |
--wr-window-title-size | var(--wr-text-sm) | .wr-window +1 variant override |