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 ¶
- func RenderHints(keyStyle, textStyle lg.Style, hints []key.Hint) string
- type ClickMsg
- type Confirm
- type ConfirmButton
- type Dialog
- type Footered
- type Form
- type Info
- type Pick
- type Result
- type ScrollHint
- type SelfFramed
- type Shell
- type ShellConfig
- type Stack
- func (s *Stack) Active() bool
- func (s *Stack) Pop() Dialog
- func (s *Stack) Push(d Dialog)
- func (s *Stack) PushWithGrace(d Dialog)
- func (s *Stack) ScrollbarHitbox() (scrollbar.Hitbox, bool)
- func (s *Stack) SetClock(now func() time.Time)
- func (s *Stack) SetShell(shell Shell)
- func (s *Stack) Top() Dialog
- func (s *Stack) Update(msg tea.Msg) (tea.Cmd, Dialog, Result)
- func (s *Stack) View(backdrop string, screenW, screenH int) string
- type Styles
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderHints ¶
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 ClickMsg ¶ added in v0.2.1
type ClickMsg struct {
X, Y int
}
ClickMsg is a left mouse click translated into the top dialog's content space: X is the column and Y the row within the body the dialog rendered through Content, already adjusted for the Shell's frame, title, and scroll offset. The Stack delivers it through Update in place of the raw screen-space click whenever the click lands inside the framed content; clicks on the Shell's chrome or outside the box keep arriving as tea.MouseClickMsg. A click on a Shell-drawn title row arrives with a negative Y, so a dialog checks its own layout before acting.
type Confirm ¶
type Confirm struct {
// contains filtered or unexported fields
}
Confirm is a generic 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.
Constructed with NewConfirmButtons it renders a focusable button row beneath the prompt: the arrow keys (or h/l, tab) move the focus, enter presses the focused button, each button's Keys press it directly, and a mouse click (delivered as ClickMsg) presses the button it lands on. The plain NewConfirm form keeps the key-only y/n contract and advertises it in the Shell's hint row instead.
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 ¶
NewConfirm returns a Confirm that asks prompt, initially not confirmed.
func NewConfirmButtons ¶ added in v0.2.1
func NewConfirmButtons(prompt string, buttons ...ConfirmButton) *Confirm
NewConfirmButtons returns a Confirm that asks prompt above a button row built from buttons, in the order given. Focus starts on the first button marked Default (or the first button outright), so the caller decides whether a bare enter lands on the safe choice or the common one.
func (*Confirm) Confirmed ¶
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 ¶
Content renders the prompt, with the button row centered beneath it in button mode. The Shell frames, centers, and wraps it, so Confirm is box-less and draws no border of its own.
func (*Confirm) Hints ¶
Hints advertises the accept and decline keys for the Shell's foot row. In button mode the buttons are the affordance and the foot row is omitted.
func (*Confirm) Update ¶
Update resolves the dialog on a decision and ignores everything else. In key mode y/Y/enter accept and n/N/esc decline. In button mode enter presses the focused button, the arrows (h/l, tab) move the focus, each button's Keys press it directly, esc declines, and a translated click presses the button under it. The outcome is recorded before returning, so the popped value already carries it.
type ConfirmButton ¶ added in v0.2.1
type ConfirmButton struct {
button.Button
// Accept makes pressing this button accept the confirmation; a button
// without it declines.
Accept bool
// Keys, when set, are key names (as tea.KeyPressMsg.String() reports them)
// that press this button directly from the keyboard, e.g. "y" and "Y" for
// a Yes button. They are optional: a button without Keys is still pressed
// through focus+enter or a click. Esc always declines regardless of Keys.
Keys []string
// Default marks the button the focus starts on. The first button marked
// Default wins; with none marked, focus starts on the first button.
Default bool
}
ConfirmButton is one button of a NewConfirmButtons dialog: its rendering plus its meaning. The caller chooses the labels, the order, and the styles - "No/Yes", "Cancel/OK", or anything else - and the semantics travel with each button rather than its position.
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 {
}
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 Form ¶ added in v0.2.1
type Form struct {
// HoldOnSubmit keeps the form open when it submits: instead of resolving
// ResultSubmit, the adapter reports EventSubmit through OnEvent and stays
// put - for owners that freeze the form (SetSubmitting) around an async
// write and close it themselves with [Stack.Pop] when the write resolves.
HoldOnSubmit bool
// contains filtered or unexported fields
}
Form adapts a form.Model into a Dialog: the form's scrollable Body becomes Content, its pinned Foot the Footer, and its FocusRegion the scroll hint, so a tall form follows its focused field inside the Shell while the hint/confirm/submitting row stays visible. EventSubmit resolves to ResultSubmit and EventCancel to ResultClose - the owner reads the values off the popped *Form through Model. Events that keep the form open but need the owner to act - EventEditor, EventChanged, and EventSubmit under HoldOnSubmit - are delivered through the OnEvent callback.
func NewForm ¶ added in v0.2.1
NewForm wraps m as a dialog. onEvent, when non-nil, observes the events that need the owner to act while the form stays open: EventEditor (take the draft to an external editor), EventChanged (a Notify cycle field stepped - refetch dependent options through Model), and EventSubmit when HoldOnSubmit is set.
func (*Form) Content ¶ added in v0.2.1
Content is the form's scrollable body; the form is fixed-measure (its Config.Width), so the offered width is ignored.
func (*Form) Footer ¶ added in v0.2.1
Footer pins the form's hint/confirm/submitting row below the viewport.
func (*Form) Model ¶ added in v0.2.1
Model returns the wrapped form, for reads and owner-driven mutations: Values after a submit, SetOptions after an EventChanged, SetSubmitting and SetError around an async write.
func (*Form) ScrollTo ¶ added in v0.2.1
ScrollTo reports the focused field's region so the Shell follows it.
type Info ¶ added in v0.2.1
type Info struct {
// contains filtered or unexported fields
}
Info is a message-only dialog: a prompt above a single OK button. It always resolves to ResultClose - there is no decision to carry - on enter, esc, space, or a click on the button; other keys are ignored, so it cannot be dismissed by an accidental keystroke the way a momentary overlay can.
func NewInfo ¶ added in v0.2.1
NewInfo returns an Info that shows prompt above the injected OK button.
func (*Info) Content ¶ added in v0.2.1
Content renders the prompt with the OK button centered beneath it.
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 ¶
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 ¶
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) ScrollTo ¶
ScrollTo keeps the highlighted row visible as the cursor moves through a list taller than the box. Implements ScrollHint.
func (*Pick) Selected ¶
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.
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 ¶
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. A click inside the framed content arrives as ClickMsg rather than tea.MouseClickMsg, so both count.
type ScrollHint ¶
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.
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 (*Stack) Pop ¶ added in v0.2.1
Pop removes and returns the top dialog without a result, or nil when none is open - the seam an owner uses to close a dialog it resolved itself, such as a held-open form (see Form.HoldOnSubmit) whose async write completed. For the grace bookkeeping it counts as a close, so a graced dialog of the same kind reopening right after skips its grace exactly as a keyed close would.
func (*Stack) Push ¶
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 ¶
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) ScrollbarHitbox ¶ added in v0.2.1
ScrollbarHitbox returns the screen-space hitbox of the top dialog's internal scrollbar as of the last View, for owners that hit-test mouse presses or drags against it. It reports false when no dialog is open, the body fits without scrolling, or nothing has rendered since the stack last changed.
func (*Stack) SetClock ¶
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 ¶
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) Update ¶
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.
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.