dialog

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package dialog is a domain-free stack of modal overlays for a Bubble Tea application. A Dialog contributes only its parts - a title, a body, and a row of key hints - and stays ignorant of borders, centering, scrolling, and screen size. A Shell supplies that chrome: it caps a dialog to a fraction of the screen and to an absolute maximum, scrolls the body internally when it overflows the cap, and centers the framed box over a backdrop. A Stack owns the ordered dialogs (top is last) plus one Shell, routes every message to the top dialog, and pops it when the dialog reports it is done - handing the popped Dialog back so the caller can read a typed payload off it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderHints

func RenderHints(keyStyle, textStyle lg.Style, hints []key.Hint) string

RenderHints renders a hint row through primer's inline key renderer, the same "(y)es" styling the rest of a TUI uses; no hints means no row. Shared so a Footered dialog pinning its own hint row renders it identically to the Shell's.

Types

type Confirm

type Confirm struct {
	// contains filtered or unexported fields
}

Confirm is a generic y/N confirmation dialog. It renders a prompt as its body and resolves to ResultSubmit when accepted or ResultClose when declined, exposing the decision through Confirmed. It defaults to not confirmed, so a dialog abandoned any other way - escape, or a lost overlay the Stack drops - is read as a decline, the safe default for a guard in front of a destructive action.

It carries pointer semantics: Update mutates and returns the same value, so the caller reads Confirmed off the very pointer the Stack pops.

func NewConfirm

func NewConfirm(prompt string) *Confirm

NewConfirm returns a Confirm that asks prompt, initially not confirmed.

func (*Confirm) Confirmed

func (c *Confirm) Confirmed() bool

Confirmed reports whether the dialog was accepted. It is meaningful once the Stack has popped the dialog: a Confirm resolved with ResultSubmit reports true, and one that was closed - or is still open - reports false.

func (*Confirm) Content

func (c *Confirm) Content(int) string

Content renders the prompt as the body. The Shell frames, centers, and wraps it, so Confirm is box-less and draws no border of its own.

func (*Confirm) Hints

func (c *Confirm) Hints() []key.Hint

Hints advertises the accept and decline keys for the Shell's foot row.

func (*Confirm) Title

func (c *Confirm) Title() string

Title omits a heading; the prompt carries the whole message.

func (*Confirm) Update

func (c *Confirm) Update(msg tea.Msg) (Dialog, tea.Cmd, Result)

Update resolves the dialog on a decision key and ignores everything else. y/Y/enter accept and submit; n/N/esc decline and close. The outcome is recorded before returning, so the popped value already carries it.

type Dialog

type Dialog interface {
	// Title is the heading the Shell renders above the body; "" omits it.
	Title() string
	// Update routes a message into the dialog. It returns the (possibly
	// updated) dialog so value-typed implementations compose cleanly, a command
	// to run, and the Result telling the Stack whether to keep or pop it.
	Update(msg tea.Msg) (Dialog, tea.Cmd, Result)
	// Content renders the body - and only the body - to fit within width
	// columns. The Shell frames, scrolls, and centers whatever it returns.
	Content(width int) string
	// Hints are the key bindings the Shell renders as the dialog's foot row.
	Hints() []key.Hint
}

Dialog is one overlay's content, free of any chrome. Implementations render their body into the width the Shell allots and never draw their own border, title bar, or scrollbar - the Shell owns all of that. The interface is deliberately minimal: async and busy state belong to the concrete dialog and its own messages, not to this contract.

type Footered

type Footered interface {
	Footer() string
}

Footered is an optional Dialog capability. A dialog that wants a row pinned below its scrollable body - a form's hint/confirm/submitting row that must stay visible however the body scrolls - returns that row here. The Shell scrolls only Content and always renders Footer beneath the viewport (inside the same box). A dialog that does not implement this frames through Content alone, as before. Pairs with ScrollHint, whose region is measured against Content, not the footer.

type Pick

type Pick struct {
	// contains filtered or unexported fields
}

Pick is a filterable single-choice dialog: it wraps a searching list.Model, letting the Shell frame and scroll a type-to-filter select list. Typing narrows the list and the arrows move the cursor - routed straight into the list - while enter accepts the highlighted row (ResultSubmit) and esc cancels (ResultClose), the two keys the list itself deliberately leaves to its caller. Selection reports the original row index, so the caller keeps a parallel slice of values and maps the index back on submit.

It carries pointer semantics: Update mutates and returns the same value, so the caller reads Selected off the very pointer the Stack pops.

func NewPick

func NewPick(title string, rows []string) *Pick

NewPick returns a Pick over rows, titled title and with the cursor on the first entry. Rows may carry styling; the filter matches their visible text.

func (*Pick) Content

func (p *Pick) Content(int) string

Content renders the list - filter line and matching rows - as the body. The list does not wrap to a width, so the parameter is unused; the Shell frames and scrolls whatever it returns.

func (*Pick) Hints

func (p *Pick) Hints() []key.Hint

Hints advertises the accept and cancel keys for the Shell's foot row.

func (*Pick) ScrollTo

func (p *Pick) ScrollTo() (int, int, bool)

ScrollTo keeps the highlighted row visible as the cursor moves through a list taller than the box. Implements ScrollHint.

func (*Pick) Selected

func (p *Pick) Selected() (int, bool)

Selected returns the original index of the highlighted row; ok is false when the filter matches nothing (or the list is empty). It is meaningful once the Stack has popped the dialog with ResultSubmit.

func (*Pick) Title

func (p *Pick) Title() string

Title is the heading the Shell renders above the list.

func (*Pick) Update

func (p *Pick) Update(msg tea.Msg) (Dialog, tea.Cmd, Result)

Update resolves the dialog on enter (accept) or esc (cancel) and routes everything else - filter typing, paste, arrow navigation - into the list. Matching on Code, not String, keeps a modified enter or escape working and mirrors the list's own key handling.

type Result

type Result int

Result is what a dialog's Update reports back to the Stack: whether the dialog stays open, or is finished and should be popped. Submit and Close are distinct so a caller reading the popped dialog can tell an accepted result from an abandoned one without inspecting the payload.

const (
	// ResultNone means the dialog consumed (or ignored) the message and stays
	// open.
	ResultNone Result = iota
	// ResultSubmit means the dialog completed successfully; the Stack pops it
	// and returns it so the caller can read a typed payload.
	ResultSubmit
	// ResultClose means the dialog was dismissed without a result; the Stack
	// pops it just the same, and the caller distinguishes the two by Result.
	ResultClose
)

func DismissResult

func DismissResult(msg tea.Msg) Result

DismissResult is the shared Update rule for momentary dialogs (a help sheet, an activity log): the first key press or mouse click closes them, every other message leaves them open.

type ScrollHint

type ScrollHint interface {
	ScrollTo() (top, height int, ok bool)
}

ScrollHint is an optional Dialog capability. A dialog whose body can grow taller than the Shell's height cap reports the body region it wants kept on screen - a form's focused field, say - so the Shell scrolls to follow it as focus moves rather than stranding the cursor below the fold. top is the region's first body line (0-based, excluding any Shell-drawn title); height is its line span. A dialog that does not implement this, or returns ok false, leaves the viewport at the top (the default before this seam existed).

type SelfFramed

type SelfFramed interface {
	SelfFramed() bool
}

SelfFramed is an optional Dialog capability. A dialog that draws its own complete frame - border, title, and sizing - reports true, and the Stack places its Content verbatim over the backdrop instead of framing and scrolling it through the Shell (which would clip and interleave a pre-drawn box). A dialog that does not implement this, or returns false, gets the Shell's chrome as usual.

type Shell

type Shell struct {
	// contains filtered or unexported fields
}

Shell frames a dialog: it caps the box to a fraction of the screen and to an absolute maximum, scrolls an overflowing body internally, and centers the result over a backdrop. It carries value semantics and is safe to copy - the Stack holds one by value.

func NewShell

func NewShell(cfg ShellConfig) Shell

NewShell builds a Shell from cfg. It constructs the viewport once so RenderScrollable reuses its default key mappings and configuration.

func (Shell) Frame

func (s Shell) Frame(backdrop string, d Dialog, screenW, screenH int) string

Frame renders d's title, body, and hints into a box, scrolls the composed content when it exceeds the height cap, and composites it centered over backdrop, which must already be screenW columns by screenH rows.

type ShellConfig

type ShellConfig struct {
	Styles Styles
	// MaxWidth and MaxHeight cap the box's total size in columns and rows; 0
	// disables the cap. They keep a long prefill or a tall body from bleeding
	// past a large screen.
	MaxWidth  int
	MaxHeight int
	// WidthFraction and HeightFraction cap the box to a share of the screen in
	// (0,1]; 0 disables. They bind on small screens where the absolute caps do
	// not.
	WidthFraction  float64
	HeightFraction float64
	// Margin keeps this many columns and rows clear at the screen edges when
	// the screen, not a cap, is the binding constraint.
	Margin int
	// Scrollbar configures the internal scrollbar's glyphs and geometry.
	Scrollbar scrollbar.Config
}

ShellConfig declares a Shell's chrome and sizing bounds.

type Stack

type Stack struct {
	// contains filtered or unexported fields
}

Stack owns an ordered set of modal dialogs - the top is the last element, the one the user interacts with - plus the Shell that frames them. Only the top dialog is live; the rest wait beneath it and are redrawn only when they return to the top. The zero value is unusable; construct with New.

func New

func New(shell Shell) *Stack

New returns an empty Stack that frames its dialogs with shell.

func (*Stack) Active

func (s *Stack) Active() bool

Active reports whether any dialog is open.

func (*Stack) Push

func (s *Stack) Push(d Dialog)

Push opens d as the new top dialog. A push is user-driven, so it also ends any input grace a previous top was holding.

func (*Stack) PushWithGrace

func (s *Stack) PushWithGrace(d Dialog)

PushWithGrace opens d as the new top dialog behind a brief input grace, for dialogs that open on an async result (a picker pushed when a fetch lands) rather than on a keypress. Keys are absorbed until the keyboard has been quiet for graceQuiet, bounded by graceCeiling from the open; every other message routes normally. When a graced dialog of the same concrete type closed within graceReopenExempt, the grace is skipped - a rapid prompt loop is user-driven by then, and each deliberate key must land.

func (*Stack) SetClock

func (s *Stack) SetClock(now func() time.Time)

SetClock replaces the clock the input-grace windows are measured on - the injected-now seam that lets tests drive the grace deterministically instead of sleeping through it.

func (*Stack) SetShell

func (s *Stack) SetShell(shell Shell)

SetShell swaps the frame the Stack draws its dialogs with, letting the owner refresh chrome styles (e.g. after a theme change) without dropping any open dialog.

func (*Stack) Top

func (s *Stack) Top() Dialog

Top returns the dialog currently receiving input, or nil when none is open.

func (*Stack) Update

func (s *Stack) Update(msg tea.Msg) (tea.Cmd, Dialog, Result)

Update routes a message to the top dialog. When it reports ResultSubmit or ResultClose it is popped and returned, so the caller can type-assert it and read a typed payload; otherwise the returned Dialog is nil. While an input grace is active (see PushWithGrace) key presses are absorbed instead of routed; the first key that arrives after the quiet window - or after the ceiling - ends the grace and drives the dialog.

func (*Stack) View

func (s *Stack) View(backdrop string, screenW, screenH int) string

View composites the top dialog over backdrop, which must already be screenW columns by screenH rows. With no dialog open it returns backdrop unchanged.

type Styles

type Styles struct {
	// Box draws the border and padding around the whole dialog; inject the
	// app's overlay style.
	Box lg.Style
	// Title styles the heading line above the body.
	Title lg.Style
	// HintKey and HintText style the key and description halves of the foot
	// row, matching the rest of the TUI's hint rendering.
	HintKey  lg.Style
	HintText lg.Style
	// Scrollbar styles the internal scrollbar shown when the body overflows
	// the height cap.
	Scrollbar scrollbar.Styles
}

Styles are the Shell's render styles, injected by the owner so the package stays theme-agnostic.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL