source_utils_dialogResult.bs
' The ONE definition of the standard dialog result shape.
'
' Every dialog in the JRDialog family resolves by writing this assoc array to its
' own per-instance `result` field. Callers read the same keys no matter which
' dialog ran, which is what lets a call site swap presentation (button row <->
' scrollable list, as showChoiceDialog does) without touching its handler.
'
' These builders exist so the shape has exactly one producer. Hand-building the
' assoc array in each component is how the three copies drifted apart before
' (JRListDialog hardcoding `confirmed: false`, JRKeyboardDialog hardcoding
' `buttonIndex: -1`). The mapping from "what the user did" to
' cancelled/confirmed is the part every call site depends on, so it belongs in
' pure functions that can be unit-tested without a scene.
'
' Keys:
' cancelled - user backed out (back / Cancel); no selection was made
' confirmed - user chose the affirmative action
' buttonIndex - index into `buttons`, or -1 when the dialog has no button row
' optionIndex - index of the chosen option, or -1 when nothing was chosen
' buttonText - label of the chosen button ("" when none)
' value - entered text (keyboard dialogs only; "" otherwise)
' externallyCancelled - the CANCEL came from code, not from the user
'
' externallyCancelled is the one key that answers "who closed this?" rather than
' "what did the user choose?", and it exists for handlers that ACT on their
' result. `cancelled` cannot tell the user pressing Back from a third party
' clearing the screen, on purpose: that is what stops an owner being stranded
' with no result at all. But an owner whose handler navigates or mutates state
' needs the difference, or someone else's dialog fires its action. It is true
' only on the overlay family's cancelDialog() path (cancelOpenDialog and
' presentOverlayDialog's supersede) and false for every user-driven resolution,
' cancels included.
'
' ⚠️ The KEYBOARD dialog cannot report it, and it reports FALSE rather than
' nothing: a positive claim that the USER closed it. It resolves through Roku's
' modal `close` field, which reads identically whether the user backed out or
' code wrote it. presentOverlayDialog never supersedes that channel, but
' cancelOpenDialog DOES reach it (`scene.dialog.close = true`), so the wrong
' answer is reachable rather than theoretical. Safe today only because neither
' keyboard caller ACTS: SetServerScreen.onServerUrlEntered and
' ConfigList.onConfigValueEntered both apply their value on `confirmed`. Trust
' this key on the overlay family only, and do not build an acting handler on a
' modal dialog.
import "pkg:/source/utils/misc.bs"
' Result for a button-row dialog (JRDialog). index = -1 means cancelled.
' confirmIndex is the button that counts as the affirmative action (-1 = none),
' which is what `showConfirmDialog` sets so callers can read result.confirmed.
function buttonDialogResult(index as integer, confirmIndex as integer, buttons as object, externallyCancelled = false as boolean) as object
buttonText = ""
if index >= 0 and isValid(buttons) and index < buttons.count()
buttonText = toString(buttons[index])
end if
' optionIndex mirrors buttonIndex: for choice dialogs the buttons ARE the
' options, so showChoiceDialog callers read result.optionIndex on both routes.
return makeDialogResult(index < 0, index >= 0 and index = confirmIndex, index, index, buttonText, "", externallyCancelled)
end function
' Result for a selection-list dialog (JRListDialog). index = -1 means dismissed
' without choosing. A list dialog has NO buttons at all: its rows are the only
' focusable thing in it and Back is the exit, so both button fields stay empty
' and callers branch on optionIndex / cancelled.
'
' It used to take a `cancelText` and report it as buttonText on dismissal, from
' when the dialog had a Cancel button. Keeping that would have meant a result
' naming a control the user was never shown.
function listDialogResult(index as integer, externallyCancelled = false as boolean) as object
return makeDialogResult(index < 0, false, -1, index, "", "", externallyCancelled)
end function
' Result for a text-entry dialog (JRKeyboardDialog). `cancelled` distinguishes a
' cancel/back from an intentionally empty entry, so callers can tell them apart.
function textDialogResult(cancelled as boolean, enteredText as string) as object
value = ""
if not cancelled then value = enteredText
' externallyCancelled is left false: see the ⚠️ note at the top of this file.
return makeDialogResult(cancelled, not cancelled, -1, -1, "", value, false)
end function
function makeDialogResult(cancelled as boolean, confirmed as boolean, buttonIndex as integer, optionIndex as integer, buttonText as string, value as string, externallyCancelled = false as boolean) as object
return {
cancelled: cancelled,
confirmed: confirmed,
buttonIndex: buttonIndex,
optionIndex: optionIndex,
buttonText: buttonText,
value: value,
externallyCancelled: externallyCancelled
}
end function