class

Phosphor::ConfirmMock

Inherits Phosphor::MockWidget < Reference < Object

A small modal overlay for yes/no confirmation.

Generic and app-agnostic: no knowledge of AppState, Msg, or the application theme. The Widget wrapper resolves theme → Style values and sets them in Props before each render. ConfirmMock never knows what it's confirming — that's a domain concern for the owning Widget and Screen#handle. It only renders and emits confirm/cancel signals via handle_cmd.

Meant to be rendered last, over the main layout, in a centered sub-frame the owning Screen computes with Layout + Flex::Center — see DESIGN.md § "Overlay widgets" and .claude/rules/screens.md § "Overlay pattern". ConfirmMock itself just fills whatever frame it's given; it does no centering of its own.

Focus navigation (the Charm confirm pattern): WidgetState#focused_button (:confirm or :cancel) tracks which button is currently prominent, toggled via Cmd::Tab/Cmd::ShiftTab/the arrow-key Cmds (see #handle_cmd) and rendered via Theme#focused_confirm_button_style/#focused_cancel_button_style — see Theme's own doc comment. Cmd::Confirm (y) and Cmd::Cancel (n/Esc) are unaffected by focus — they're direct shortcuts, always meaning exactly what they say. Only Enter is focus-dependent: the owning Widget reads #state.focused_button back to decide whether an Enter keypress should send Cmd::Confirm or Cmd::Cancel (see ConfirmWidget#on_event in Echo). Touch input bypasses focus entirely — tapping a button (#button_zones) resolves immediately, matching direct-manipulation touch conventions rather than the two-step focus-then-activate keyboard pattern.

Constructors

new(props : Props, state : WidgetState = WidgetState.new)

Initialises the dialog with props and an optional state.

Source

Instance methods

button_zones(frame : Frame) : NamedTuple(confirm: Frame, cancel: Frame) | Nil

Returns the absolute Frame each button occupies, in the same coordinate space as frame (the same outer frame passed to #render_content/#render) — nil while nothing would actually draw there (mirrors #render_content's own guards, plus BorderMock's own frame.inset(1)).

MockWidget has no Widget#register_zone of its own — view/mount_id live on Widget, not here (see Widget#register_zone's own doc comment) — so a Widget wrapper that wants touch zones for these buttons (e.g. ConfirmWidget) calls this after #render and registers them itself, using ContentMock.button_layout's same underlying math #render_content draws with, so the two can never disagree about where a button actually is.

Source
handle_cmd(cmd : Cmd) : CmdResult

Responds to focus-navigation, confirm, and cancel signals.

Cmd::Tab/Cmd::ShiftTab/Cmd::MoveUp/Cmd::MoveDown/Cmd::MoveLeft/Cmd::MoveRight all just toggle state.focused_button between the two buttons — with exactly two options, "next" and "previous" are the same operation, so every navigation Cmd a caller might plausibly send is accepted rather than requiring one specific one. Cmd::Confirm/Cmd::Cancel still bubble up unconsumed, same as before — ConfirmMock has no idea what it's confirming, only which button (if either) state.focused_button currently names. The owning Widget decides which Cmd to send for a given keypress (y always sends Cmd::Confirm; Enter sends whichever Cmd matches state.focused_button — see ConfirmWidget#on_event in Echo) and translates a Bubble result into the real domain Msg. Any other command returns CmdResult::None.

Source
props
Source
props=(props : Props)
Source
render_content(frame : Frame, buf : Buffer) : Void

Draws a bordered box around frame, using BorderMock composition — the same box-drawing characters, with props.title in the top border row. props.message and the confirm/cancel buttons render into frame.inset(1), handled by BorderMock passing that inset region to an internal content widget.

Source
reset

Resets keyboard focus to the confirm button. Called by the owning Widget (e.g. ConfirmWidget#sync) whenever a fresh confirmation opens, so a stale focus position left over from a previous dialog never carries into a new one.

Source
state

Narrows the inherited state getter to ConfirmMock::WidgetState — safe, since #initialize/#reset only ever store a WidgetState into the base @state ivar (same pattern FormMock#state uses, see its own doc comment). Public, not just used internally — a Widget wrapper reads focused_button back to decide which Cmd an Enter keypress should resolve to (see ConfirmWidget#on_event in Echo).

Source

Nested types