Members
(static, constant) BUTTON_ROW_MAX_WIDTH
A CONSERVATIVE cap, not a mirror of any panel dimension.
JRDialog flows its button row INSIDE the panel and grows the panel to fit, up to PANEL_MAX_WIDTH (1200), so 1104px of row would actually render. This stops well short of that: past ~900 a three-button row reads as a wall of chrome, and the scrollable list is the better presentation for that many words regardless of whether it would fit.
Erring low only routes to the list dialog, which is the safe direction. Three buttons at DIALOG_BUTTON_MIN_WIDTH (204) plus spacing is 660, so the cap bites only on long labels.
(This previously claimed to mirror JRDialog's PANEL_WIDTH and to describe a row centered UNDER the panel. Neither was true: PANEL_WIDTH is 762, and the row moved inside the panel in #757.)
- Default Value
- 900
- Source
(static, constant) MAX_BUTTON_ROW_CHOICES
Mirrors JRDialog's MAX_BUTTONS design cap. Kept as its own constant because BrighterScript consts are file-scoped and the component's is not visible here.
- Default Value
- 3
- Source
(static, constant) OVERLAY_DIALOG_ID
The id every overlay dialog is stamped with. One definition, because three things depend on being able to FIND the open dialog by it: isOverlayDialogOpen, cancelOpenDialog, and presentOverlayDialog's supersede below. It identifies exactly one node because presentOverlayDialog keeps it that way.
- Default Value
- jrDialog
- Source
Methods
(static) abandonDialog(dialog) → {void}
Abandon a dialog this screen opened, delivering no result. Call from onDestroy for any dialog node you are still holding.
Overlay dialogs are appended to the SCENE, not to the screen that opened them, so they outlive their opener: a routed view destroyed while one is open (deep link, session expiry, server switch: anything that navigates without the user pressing back) would leave a modal stranded over the incoming screen, with a scoped observer pointing at a torn-down scope and returnFocusTo pointing at a dead node. Safe to call with invalid / an already-closed dialog.
| Name | Type | Description |
|---|---|---|
dialog | object |
- Source
- Type:
- void
(static) cancelOpenDialog() → {void}
Close whatever dialog is open, delivering a CANCELLED result to whoever opened it. The counterpart to abandonDialog(): use this when a THIRD PARTY needs the screen clear while the dialog's owner is still alive and waiting on a result.
The distinction matters. abandonDialog() delivers NOTHING, which is right in onDestroy: the scope that would receive the result is being torn down. Here the owner is a main-thread flow that holds state until its dialog answers (the deep-link server switch holds the pending server and the stashed link), so closing without an answer strands it. A cancelled result is the same thing the user pressing Back would have produced.
Covers BOTH channels, like isDialogOpen(): the scene-appended overlays cancel through their own once-only resolve guard, and Roku's modal channel resolves as cancelled on close (see JRKeyboardDialog.onWasClosed).
- Source
- Type:
- void
(static) choicesFitAsButtons(choices) → {boolean}
A choice set renders as JRDialog's horizontal button row only if it's short (<= 3) AND the measured row fits the screen with comfortable margins. Translations that overflow (such as long German labels) route to the list dialog.
Measured with the system font at the size the button label actually renders at (constants.fontSizeMedium, applied by LabelMedium), not roFontRegistry's default size. Those differ, so the old estimate was wrong for every label. Still an estimate: a user on the downloaded fallback font (uiFontFallback) has slightly different metrics, but fontScaleFactor normalizes those toward the system font's, so the error stays small. Erring wide merely routes to the list dialog, which is the safe direction.
| Name | Type | Description |
|---|---|---|
choices | object |
- Source
- Type:
- boolean
(static) isOverlayDialogOpen() → {boolean}
Is a scene-appended overlay dialog currently on screen?
The JRDialog family does not use Roku's modal channel (m.scene.dialog), so anything asking "is a dialog open?" via that channel alone has been answering false for every dialog in the app since the family moved to scene overlays. SceneManager.isDialogOpen() folds this in; prefer that if you have it, because it covers the keyboard dialogs on the modal channel too.
- Source
- Type:
- boolean
(static) presentOverlayDialog(dialog, onResult, returnFocusToopt) → {object}
Append an overlay dialog to the scene, capture focus, wire the caller's result observer. onResult is resolved in the CALLER's scope because these helpers execute inside the importing component's script scope.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
dialog | object | |||
onResult | string | |||
returnFocusTo | object | <optional> | invalid |
- Source
- Type:
- object
(static) showAlertDialog(title, message, onResultopt, subheadingopt, secondaryButtonTextopt) → {object}
Simple message + OK button, plus an OPTIONAL secondary action beside it.
result.buttonIndex is 0 for OK, 1 for the secondary action, and the result is cancelled on back. A caller with no secondary state to unwind should treat cancelled exactly as it treats OK: back on a one-button alert has nothing to cancel back to.
subheading is an optional BOLD lead line above the message, for a message that arrives in two parts: a heading and its text. Use it only when the two parts come from different places (the cast notice: JellyRock owns the provenance title, the sender owns the heading and the body). A message you wrote yourself belongs in one string.
secondaryButtonText adds ONE extra button to the right of OK, for an action that does not leave the alert's subject: "Details" on an error, not a second answer to a question. Two answers to a question are showConfirmDialog.
Not showChoiceDialog, which is the near miss a reader will reach for: it falls back to JRListDialog when the labels are wide (long translations), which would turn an error message into a scrollable list, and its contract says NOTHING is current yet. That is wrong for an alert whose primary action is already the default.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
message | string | |||
onResult | string | <optional> | "" | |
subheading | string | <optional> | "" | |
secondaryButtonText | string | <optional> | "" |
- Source
- Type:
- object
(static) showChoiceDialog(title, message, choices, onResultopt, defaultIndexopt) → {object}
2+ mutually exclusive options. Up to 3 short options render as the horizontal button row; more (or wider-than-panel labels, such as long translations) fall back to the scrollable list dialog. Either way the selection lands in result.optionIndex (-1 = cancelled), so callers don't care which route ran. bsc-disable-next-line dead-code keep: api: the pick-one member of the dialog family (#288); no feature has needed it yet
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
message | string | |||
choices | object | |||
onResult | string | <optional> | "" | |
defaultIndex | integer | <optional> | 0 |
- Source
- Type:
- object
(static) showConfirmDialog(title, message, onResultopt, confirmTextopt, cancelTextopt) → {object}
Yes/No-style confirmation. Cancel is LEFT, confirm is RIGHT; back = cancelled. Read result.confirmed: true only when the confirm button was selected.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
message | string | |||
onResult | string | <optional> | "" | |
confirmText | string | <optional> | "" | |
cancelText | string | <optional> | "" |
- Source
- Type:
- object
(static) showInfoDialog(title, overview, taglineopt, returnFocusToopt) → {object}
Read-only long-form text (descriptions, biographies). Self-closing on OK/back; no result to observe. Wraps the existing OverviewDialog chrome.
returnFocusTo names the node to restore focus to on close. Pass it when the OPENER is the right answer and you already hold it: a focusable element that expands into this dialog. Omitted, focus returns to whatever was focused when the dialog opened, which is the same node in most cases but is DERIVED rather than known.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
overview | string | |||
tagline | string | <optional> | "" | |
returnFocusTo | object | <optional> | invalid |
- Source
- Type:
- object
(static) showKeyboardDialog(title, onResultopt, defaultTextopt, secureopt, keyboardDomainopt) → {object}
Text entry via the OS keyboard (voice-capable, certification-compliant). Entered text lands in result.value; result.cancelled distinguishes cancel from an intentionally empty entry. secure = true masks input for passwords.
keyboardDomain drives the keyboard's VOICE dictation mode, so it must match the kind of text being entered ("email" / "numeric" / "alphanumeric" / "password" / "generic"). secure = true implies "password" unless the caller names a domain; otherwise a masked field would still dictate as free-form words.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
onResult | string | <optional> | "" | |
defaultText | string | <optional> | "" | |
secure | boolean | <optional> | false | |
keyboardDomain | string | <optional> | "" |
- Source
- Type:
- object
(static) showListDialog(title, items, onResultopt, defaultIndexopt, selectedIndexopt, iconsopt) → {object}
Select one item from a longer list (audio/subtitle tracks, video sources). result.optionIndex is the selected index, -1 on cancel.
defaultIndex is which row to FOCUS. selectedIndex is which option is already CURRENT (it gets the check, the surface, and the "currently selected" announcement), and defaults to -1, meaning none is. A picker passes both (they normally coincide); anything offering a fresh choice passes only the first. icons is optional and positionally paired with items: an array of Poster URIs drawn in the row gutter the check would otherwise occupy. It exists for ACTION lists: a "More" overflow menu whose rows should wear the same glyphs as the buttons they stand in for. A picker passes none, because the gutter is already carrying its current-option check.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
items | object | |||
onResult | string | <optional> | "" | |
defaultIndex | integer | <optional> | 0 | |
selectedIndex | integer | <optional> | -1 | |
icons | object | <optional> | invalid |
- Source
- Type:
- object
(static) showOverflowMenu(title, items, onResult, anchor, iconsopt) → {object}
An overflow ("More") menu: the same JRListDialog, presented as a MENU instead of as a centered modal.
Three presentation differences, and each is a fix for something the centred form got wrong here (#788):
ANCHORED. A centred 900px panel covers the button row the menu belongs to AND the title/metadata column on the left. Anchored to the More button and grown upward, it sits over the backdrop art on the right and leaves both readable. It opens leftward from the button where it fits and flips rightward where it does not; computeDialogLayout owns that rule. anchor is { x, y, width } in screen coordinates; build it with moreButtonAnchor() so it cannot drift from the cap that produced the button.
SIZED TO CONTENT. These rows are one-word actions. At the fixed width every label was stranded in a mostly-empty row.
NO DRAWN TITLE. The menu hangs off a button already labelled More, so the heading repeated it and cost about 100px of height (figure from 2026-09-07). It is still ANNOUNCED: the title field is set, and narration reads that, not the panel.
Everything else is JRListDialog: same chrome, same key model, same result contract, same specs. This is a presentation of the family's list dialog, not a second surface. See docs/architecture/dialogs.md for why that distinction is enforced rather than encouraged.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
items | object | |||
onResult | string | |||
anchor | object | |||
icons | object | <optional> | invalid |
- Source
- Type:
- object
(static) showQuickConnectDialog(title, instruction, code, onResultopt) → {object}
Show a Quick Connect code and wait for the user to approve it elsewhere.
A view and nothing more: it displays code with a Cancel button and resolves cancelled if the user backs out. It does not poll and does not sign anyone in. The caller owns the flow (initiate, poll, exchange) because that is a chain of fetchAsync promises and the promise registry lives on the CALLER's m. UserSelect is the reference and the only caller: startQuickConnect runs the flow, and openQuickConnectDialog shows this view.
The caller closes it on the success path with abandonDialog(): approval comes from the server, not from the user, so there is nothing to deliver back through result.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | |||
instruction | string | |||
code | string | |||
onResult | string | <optional> | "" |
- Source
- Type:
- object
(static) showReportDialog(title, status, sections, returnFocusToopt) → {object}
Show a structured read-only report: the same overlay as showInfoDialog, with a body of label/value rows instead of a paragraph.
Returns the dialog so the caller can keep reporting into it: assigning sections again RECONCILES rather than rebuilds, which is how the playback report refreshes its live figures without disturbing the scroll position. The caller owns stopping that, and owns abandonDialog() in its onDestroy.
status is the one-line verdict above the sections ("Transcoding"), carried in the tagline slot because that slot is already defined as a bold lead line and already read first by the opening announcement.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
title | string | dialog title | ||
status | string | a short lead line, or "" | ||
sections | object | [ { id, heading, wideLabels, rows: [ { id, label, value } ] } ] | ||
returnFocusTo | object | <optional> | invalid | node to restore focus to on close |
- Source
- Type:
- object
(static) showStillWatchingDialog(countdown, onResultopt) → {object}
"Are you still watching?" (issue #982). One button, and ANY key answers it, Back included (anyKeyResolves). The question is only whether someone is there, so making them find a particular button would defeat it.
countdown goes in the subheading slot, and the caller rewrites it every second. That is the one exception to setting text before presenting, and it is safe only because the slot holds a single short line: its height never changes, so the layout never has to (see JRDialog.onSubheadingChanged). The instruction stays in message, fixed.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
countdown | string | the first countdown line ("Pausing in 30 seconds") | ||
onResult | string | <optional> | "" | result handler in the caller's scope |
- Source
- Type:
- object