# Dialogs `win.dialogs()` and `wm.dialogs()` are the promise-based `alert` / `confirm` / `prompt` factory built into the SGApps window-manager, plus a `customPopup` escape hatch for dialogs that don't fit those three shapes. Every dialog is a real WM window — parented to the caller (for `win.dialogs()`) or floating at the manager root (for `wm.dialogs()`) — so it inherits the same focus cascade, close cascade, chrome, and styling as any other app window. The API contract mirrors `electron.dialog.showMessageBox(browserWindow, opts)`, so an Electron adapter can later swap the DOM path for native OS dialogs without call-site changes. Related: [Window Manager](window-manager.md) covers how to obtain a `wm`; [Window](window.md) covers the `win` object. ## Try it live The buttons below drive a real window-manager loaded via `exporter.js` on this page — the same `wm` instance the code snippets use. First click boots the SDK (~1 s); subsequent clicks reuse it. **Alerts:**
**Confirms:**
Result appears here… **Prompts:**
Result appears here… **Custom popups:**
Result appears here… ## Two entry points ```js // Parented to a specific window — closes when that window closes, // raises on top when that window is focused this.window().dialogs().alert("File saved"); // Rooted at the manager — no owning parent, no cascade. // Use for global flows (session expired, logout confirm, app-level errors). wm.dialogs().alert("Session expired", { icon: "paper/icons/status/dialog-warning" }); ``` The two entry points return an object with the same four methods. `alert` / `confirm` / `prompt` share the same promise-based contract; `customPopup` is the escape hatch for dialogs that don't fit those three shapes (see [customPopup](#custompopup-opts) below). Only the parent binding differs. ## Methods `alert` / `confirm` / `prompt` each return `Promise<{ window, result }>`: - `window` — the dialog's `WindowApplication` instance (already closed by the time the promise resolves; useful only for logging or subclass extensions) - `result` — the user's answer; type depends on the method `customPopup` returns a **context object synchronously** instead of a promise — see its own section for the contract. ### `alert(message, opts?)` One message, one OK button. Escape and Enter both resolve with `undefined`. ```js win.dialogs().alert("Backup complete.").then(function (r) { // r.result === undefined }); ``` ### `confirm(message, opts?)` One message, Yes/No buttons. Enter → `true`, Escape → `false`. Pass `opts.destructive: true` to turn the Yes button red. ```js win.dialogs().confirm("Delete file?", { destructive: true, yesLabel: "Delete", noLabel: "Cancel" }).then(function (r) { if (r.result) doDelete(); }); ``` ### `prompt(message, defaultValue?, opts?)` One message, one input, OK/Cancel. Enter (single-line) or Ctrl/Cmd+Enter (multiline) submits the value. Escape resolves `null` — matches native `window.prompt`. ```js win.dialogs().prompt("New file name:", "untitled.txt", { placeholder: "name.ext" }).then(function (r) { if (r.result !== null) rename(r.result); }); // Multiline win.dialogs().prompt("Describe the change:", "", { multiline: true, rows: 5, placeholder: "what changed…" }).then(function (r) { if (r.result !== null) commitWithMessage(r.result); }); ``` ### `.prompt(message, opts)` — signature tolerance If the second argument is an options object (not a string default), it's treated as `opts` and `opts.defaultValue` fills in: ```js win.dialogs().prompt("Your name?", { placeholder: "type here…" }); // same as .prompt("Your name?", undefined, { placeholder: "type here…" }) ``` ### `customPopup(opts?)` Escape hatch for dialogs that don't fit `alert` / `confirm` / `prompt`. Opens a window pre-wired with all the shared plumbing (parent-child focus / close cascade, ESC → resolve, default geometry centred on parent), then hands back a **context object** so the caller owns the body. **Contract differences vs the other three methods** | | `alert / confirm / prompt` | `customPopup` | |---|---|---| | **Returns** | `Promise<{window, result}>` | `{ dlg, promise, resolve, container, focusFirst }` synchronously | | **Message arg** | Required (string / HTMLElement / `{html}`) | None — you populate `container` yourself | | **Buttons** | Built-in (OK / Yes-No / OK-Cancel) | None — you add whatever you want | | **ESC → resolve** | Method-specific default (`undefined` / `false` / `null`) | `opts.escResult`, defaults to `undefined` | | **Sizing** | JS defaults 380×200 (opts.width/height override); body scrolls when content overflows | JS defaults 380×200 (opts.width/height override); caller owns the body, so overflow behaviour is whatever they build | **Returned context** | Property | Type | Purpose | |---|---|---| | `dlg` | `WindowApplication` | The dialog window handle — same surface as any WM window. Getter/setter accessors `dlg.title(str?)`, `dlg.width(px?)`, `dlg.height(px?)`, `dlg.left(px?)`, `dlg.top(px?)`, `dlg.icon(id?)`; lifecycle `dlg.close(force)`, `dlg.focus()`, `dlg.maximize / minimize / restore()`; events `dlg.on(evt, fn)` / `dlg.once` / `dlg.off`; DOM `dlg.container()` / `dlg.node()`; class helpers `dlg.addClass / removeClass`. Note there is no `dlg.resize(w, h)` — set width and height with the individual accessors. | | `promise` | `Promise<{window, result}>` | Resolves when `resolve(v)` is called or the dialog closes by another path (ESC, parent cascade, external close). On non-resolve close, `result === opts.escResult` | | `resolve(value)` | `function` | Close the dialog and resolve `promise` with `value`. Idempotent — safe to bind to multiple events | | `container` | `HTMLElement` | The dialog's inner container. Cleared before return — populate with any DOM (buttons, inputs, canvas, iframe, …) | | `focusFirst()` | `function` | Focus the first `button.is-primary` / `button` / `input` / `textarea` inside `container`. Call it after populating | **Example — colour picker** ```js var ctx = win.dialogs().customPopup({ title: "Pick a colour", width: 320, height: 180, escResult: null // ESC → cancelled }); var input = document.createElement("input"); input.type = "color"; input.value = "#a6bb11"; ctx.container.appendChild(input); input.addEventListener("change", function () { ctx.resolve(input.value); // close + settle with hex }); ctx.focusFirst(); ctx.promise.then(function (r) { if (r.result !== null) applyBrandColour(r.result); }); ``` **When to reach for `customPopup`** - The dialog has more than one input, or an input the built-in `prompt` doesn't cover (radio group, colour, file, date range, canvas signature pad, …). - The dialog is a multi-step wizard whose result depends on which "Next" branch the user takes. - You need an embedded iframe, video, or third-party widget as the dialog body. - You want a dialog whose body updates over time (progress bar, live log stream) and closes on a specific event rather than an OK click. **When NOT to reach for `customPopup`** - A single message + OK / Yes-No / one input covers your case — `alert` / `confirm` / `prompt` are shorter, keyboard-mapped by default, and match the Electron adapter surface. - You just need rich HTML content inside a standard OK dialog — pass an `HTMLElement` or `{html: "..."}` as the message to `alert` / `confirm` and skip the manual wiring. **Scrolling — `scrollable: true`** The dialog window's inner `
` has `overflow: hidden` by default. That is intentional for the standard shell: `.windowManager--dialog-body` owns the scroll region, buttons stay pinned to the bottom via a flex column, and hiding the section prevents phantom scrollbars from 1-pixel rounding drift. When you build a **custom** body that does **not** follow that shell (`.windowManager--dialog` > `.windowManager--dialog-body` + `.windowManager--dialog-buttons`), the section-level `overflow: hidden` clips anything that overruns the viewport — on small screens the operator can't reach content past the bottom of the section. Pass `scrollable: true` to opt out. ```js // Long form that doesn't use the standard shell — let the section scroll. var ctx = win.dialogs().customPopup({ title: "Edit user", width: 560, height: 480, escResult: "cancel", scrollable: true // <-- section can scroll now }); // Fill ctx.container directly, no .dialog-body wrapper needed. ``` Rule of thumb: - **Standard shell** (you mount `.windowManager--dialog` + `.dialog-body` + `.dialog-buttons`): leave `scrollable` **off**. The body scrolls in place while buttons stay pinned. - **Free-form container** (you paint straight into `ctx.container` with no body/button separation): turn `scrollable` **on**. Otherwise a body taller than the viewport is unreachable. Under the hood the option toggles the `windowManager--window-class--dialog-scroll` class on the dialog window; the CSS rule that reacts to it lives next to the base dialog section rule in `window-manager/style.css`. ## Content — string, HTMLElement, or `{html}` The first argument accepts three shapes. Everything else in the API is unchanged. ### 1. Plain text Pre-wrap + word-break so long content stays readable at any dialog width. ```js win.dialogs().alert("File saved successfully."); ``` ### 2. HTMLElement Appended directly. You control the structure; the WM stylesheet provides sensible defaults for `h1-h4`, `p`, `ul/ol`, `code`, `pre`, `table`, `a`, `hr`, and media elements. ```js var el = document.createElement("div"); el.innerHTML = '

Backup done

' + ''; win.dialogs().alert(el, { title: "Backup complete" }); ``` ### 3. `{ html: "..." }` shortcut For static HTML you'd otherwise stringify. Semantically identical to option 2 but skips the `createElement + innerHTML` boilerplate. ```js win.dialogs().confirm({ html: '

Not undoable.

' + '

Revoke access for 2 devices and 5 sessions?

' }, { title: "Confirm revoke", destructive: true, yesLabel: "Revoke" }); ``` ## Options Everything is optional. Only sensible defaults apply when omitted. ```js opts = { title, // window title (defaults: "Notification" / "Confirm" / "Input" / "Dialog") icon, // e.g. "paper/icons/status/dialog-warning" okLabel, // default "OK" — alert / prompt cancelLabel, // default "Cancel" — prompt yesLabel, // default "Yes" — confirm (also accepts okLabel) noLabel, // default "No" — confirm (also accepts cancelLabel) details, // string | HTMLElement | {html} — collapsible info block under the message destructive, // confirm only — Yes button turns red ("is-danger" style) multiline, // prompt only —