Window
oge-window is a non-modal floating window: no backdrop, no focus trap and no scroll lock, so the page stays usable and several windows can be open at once. A press or focus brings a window to the front, the title bar drags it, eight edge handles resize it and the title-bar buttons minimize, maximize and restore it — every gesture has a keyboard twin. For a blocking dialog use oge-modal.
Basics
Open it through the two-way opened model or open() / close() / toggle(). The window is a role="dialog" without aria-modal, labelled by its title: nothing behind it is blocked, focus moves into it on open (autoFocus) and back to the opener when it closes. Escape closes it only while focus is inside and no popup is open — a window never joins the modal Escape stack.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeWindow } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton, OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Open window" (clicked)="opened.set(true)" />
<!-- non-modal: no backdrop, no focus trap, no scroll lock -->
<oge-window title="Quick notes" [(opened)]="opened" [width]="360">
<textarea aria-label="Notes"></textarea>
</oge-window>
`,
})
export class Demo {
protected readonly opened = signal(false);
}Multiple windows & stacking
Any number of windows can be open. They share one z-order — across component trees and both render layers — on top of the --oge-z-window token (below anchored popups, so a select opened inside a window still shows above it). A press or focus brings a window to the front and fires activated; the frontmost window's title bar carries the active accent. zIndex sets a base of your own.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeWindow } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- any number at once; a press or focus brings one to the front -->
<oge-window title="Inspector" [(opened)]="inspector" placement="top-start"
(activated)="active.set('Inspector')">…</oge-window>
<oge-window title="Layers" [(opened)]="layers" placement="top-end"
(activated)="active.set('Layers')">…</oge-window>
<oge-window title="Console" [(opened)]="console" placement="bottom"
[zIndex]="2000" (activated)="active.set('Console')">…</oge-window>
`,
})
export class Demo {
protected readonly inspector = signal(true);
protected readonly layers = signal(true);
protected readonly console = signal(true);
protected readonly active = signal('');
}Drag & resize (keyboard too)
The title bar drags the window and eight edge and corner handles resize it — both through the shared pointer gesture (3px threshold, touch-safe, Escape mid-gesture puts the window back). The keyboard twin works on the focused frame (Tab reaches it, a title-bar press focuses it): arrows move by 10px, Ctrl/⌘ + arrows resize, Shift makes either step 1px, and the result is announced. minWidth/minHeight/maxWidth/maxHeight bound the size; moved and resized report the new box and the source.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeWindow } from '@oge-ui/overlay';
import type { OgeWindowMovedEvent, OgeWindowResizedEvent } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- title bar drags, eight edge handles resize; with the frame focused:
arrows move (10px, Shift 1px), Ctrl/⌘+arrows resize -->
<oge-window title="Drag me" [(opened)]="opened"
[minWidth]="260" [minHeight]="160" [maxWidth]="640"
(moved)="onMoved($event)" (resized)="onResized($event)">
…
</oge-window>
`,
})
export class Demo {
protected readonly opened = signal(false);
protected onMoved(event: OgeWindowMovedEvent): void {
console.log(event.x, event.y, event.source); // 'pointer' | 'keyboard' | 'api'
}
protected onResized(event: OgeWindowResizedEvent): void {
console.log(event.width, event.height, event.edge);
}
}Minimize / maximize
Minimize collapses the window to its title bar, maximize fills the viewport (inside the safe-area insets), restore brings the previous box back. Title-bar buttons (labels from the overlay messages), a title-bar double-click, Alt+↑ / Alt+↓, the two-way state model and minimize() / maximize() / restore() all run the cancelable stateChanging → stateChanged pipeline.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeWindow } from '@oge-ui/overlay';
import type { OgeWindowState, OgeWindowStateChangingEvent } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- title-bar buttons, a title-bar double-click, Alt+↑ / Alt+↓,
the two-way state model or minimize() / maximize() / restore() -->
<oge-window #report title="Report" [(opened)]="opened" [(state)]="state"
(stateChanging)="onStateChanging($event)">
…
</oge-window>
`,
})
export class Demo {
protected readonly opened = signal(false);
protected readonly state = signal<OgeWindowState>('normal');
// cancelable, like every -ing event
protected onStateChanging(event: OgeWindowStateChangingEvent): void {
if (event.state === 'maximized' && this.locked()) event.cancel = true;
}
protected readonly locked = signal(false);
}Placement & constraints
Without a position the window opens at its placement — the centre, an edge (top, bottom, logical start / end) or a corner — and a new placement re-places an open window. position ({ x, y } in viewport px) wins and moves the window when it changes. keepInViewport (default) keeps the whole window on screen; with false it may hang off the sides and bottom, but its title bar always stays reachable. A viewport resize re-clamps open windows.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeWindow } from '@oge-ui/overlay';
import type { OgeWindowPlacement } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- placement applies when there is no position; start/end mirror in RTL.
keepInViewport=false lets the window hang off the edges, but the
title bar always stays reachable -->
<oge-window title="Placed window" [(opened)]="opened"
[placement]="placement()" [keepInViewport]="contained()" />
<!-- an explicit position (viewport px) wins and moves it when it changes -->
<oge-window title="Pinned" [(opened)]="pinned" [position]="{ x: 24, y: 96 }" />
`,
})
export class Demo {
protected readonly opened = signal(false);
protected readonly pinned = signal(false);
protected readonly placement = signal<OgeWindowPlacement>('bottom-end');
protected readonly contained = signal(true);
}Events
Every interaction reports through an event: cancelable opening, closing (with the reason — 'escape', 'closeButton', 'api') and stateChanging; past-tense closed, moved, resized, stateChanged and activated. Interact with the window and watch the log.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeWindow } from '@oge-ui/overlay';
import type { OgeWindowClosingEvent, OgeWindowClosedEvent } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeWindow],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-window title="Event log" [(opened)]="opened"
(opening)="log('opening')" (closing)="onClosing($event)"
(closed)="onClosed($event)" (moved)="log('moved')"
(resized)="log('resized')" (stateChanged)="log('stateChanged')"
(activated)="log('activated')">
…
</oge-window>
`,
})
export class Demo {
protected readonly opened = signal(false);
// Escape (focus inside, no popup open), ✕ and close() — all cancelable
protected onClosing(event: OgeWindowClosingEvent): void {
this.log(`closing (${event.reason})`);
}
protected onClosed(event: OgeWindowClosedEvent): void {
this.log(`closed (${event.reason})`);
}
protected log(entry: string): void {
console.log(entry);
}
}Notes
- Window or modal? A window never blocks the page: use it for tool palettes, inspectors and side-by-side work. A decision the user must take before going on belongs in a modal (or the
confirm()helper). - Coordinates are viewport pixels (
xis the left edge, likeclientX) in both reading directions; only placements name logical edges. - The title-bar button labels localize through the overlay messages (
windowMinimize,modalMaximize,modalRestore,modalClose) and the keyboard announcements throughwindowMoved/windowResized.