# Window

> Free-floating OS-style window opened programmatically via `WrWindowManager`. Drag the header to move, drag any edge or corner to resize, minimize to the taskbar. There is no declarative `<wr-window>` component — the manager is the only entry point so the API stays single-track. Need a backdrop + focus trap? That's `WrDialog`, not this.

Source: https://ngwr.dev/reference/components/window  
Kind: Service, a11y

## Setup

Inject `WrWindowManager` to open windows. Drop `<wr-window-taskbar />` once in your shell so minimized windows have somewhere to land.

```angular-ts
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)`.

```angular-ts
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`.

```angular-ts
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.

```angular-ts
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.

```angular-ts
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`.

```angular-html
<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`).

```angular-ts
// 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.

```angular-ts
// 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 |
