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
Initialises the dialog with props and an optional state.
Instance methods
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.
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.
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.
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.
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).