Documentation
¶
Overview ¶
Package focus owns "which component currently has the keyboard" for a screen with more than one interactive component.
Before this package, every multi-component screen hand-rolled the same thing: an int index, a switch in Update for tab/shift-tab, and an applyFocus helper that blurred everything and focused one. Group replaces that, and adds the piece a hand-rolled index can't do — granting focus in response to a mouse click, which arrives at the clicked component rather than at the screen that owns the ordering.
A screen holds a Group alongside its components, forwards messages to it, and reads Focused to decide where its own keys go:
func newScreen(t theme.Theme) *screen {
s := &screen{…}
s.focus = focus.NewGroup(&s.query, &s.results, &s.caseTgl)
return s
}
func (s *screen) Update(msg tea.Msg) (screen.Screen, tea.Cmd) {
var cmd tea.Cmd
s.focus, cmd = s.focus.Update(msg)
…
}
Click-to-focus ¶
A component that decides a click landed on it returns Request(itself) as a tea.Cmd. The resulting RequestMsg travels up to whatever Group holds it; the Group blurs everything else and focuses the target. The component never has to know its siblings exist, and the Group never has to know how any component decides it was clicked.
A RequestMsg naming something the Group doesn't hold is ignored, so nesting Groups is safe: each takes only the requests it recognises.
Index ¶
- func Request(target Focusable) tea.Cmd
- func RequestSelf(tk Token) tea.Cmd
- type Capturer
- type Focusable
- type Group
- func (g Group) Focused() Focusable
- func (g Group) Help() []key.Binding
- func (g Group) Index() int
- func (g Group) Init() tea.Cmd
- func (g Group) Is(f Focusable) bool
- func (g Group) IsCapturingKeys() bool
- func (g Group) Len() int
- func (g *Group) SetIndex(i int) tea.Cmd
- func (g Group) Update(msg tea.Msg) (Group, tea.Cmd)
- func (g Group) WithKeys(k Keys) Group
- func (g Group) WithoutWrap() Group
- type Identified
- type Keys
- type RequestMsg
- type Token
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Request ¶
Request returns a command carrying a focus request for target. Use it when you hold the component — a screen focusing one of its own panes.
func RequestSelf ¶
RequestSelf returns a command by which a component asks for focus on its own behalf, naming itself by token. This is what a component returns from Update when a click lands inside its rect.
Types ¶
type Capturer ¶
type Capturer interface {
IsCapturingKeys() bool
}
Capturer is an optional interface for components that swallow printable keys while focused — a text field, or a list with its filter engaged. A Group reports IsCapturingKeys from its focused member when that member implements this; the screen forwards the answer to the app shell so global keys (q, theme-cycle, esc-pop) stay out of the way.
type Focusable ¶
Focusable is anything a Group can move focus between. Every interactive component in tuilib satisfies it.
Focus returns a tea.Cmd because some components need one — a text input returns its cursor-blink command. Components with nothing to start return nil.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group holds an ordered set of focusables and grants focus to exactly one.
func NewGroup ¶
NewGroup returns a Group over items in tab order, with the first item focused. Pass pointers — the Group stores the interface values and calls through them, so a component rebuilt in place (as SetTheme does) stays addressed correctly as long as the field address is stable.
Call the returned Group's Init to focus the first item and collect its command.
func (Group) Focused ¶
Focused returns the item that currently owns focus, or nil for an empty group.
func (Group) Help ¶
Help returns the cycling bindings, plus the focused item's own when it exposes them. Compose into the screen's Help so the hint strip tracks whichever pane is active.
func (Group) Init ¶
Init focuses the first item and returns its command. Batch it into the screen's Init.
func (Group) Is ¶
Is reports whether f is the currently focused item. Screens use it to route their own shortcuts to the right pane.
func (Group) IsCapturingKeys ¶
IsCapturingKeys reports whether the focused item is currently swallowing printable keys. Screens forward this from their own IsCapturingKeys so the app shell suppresses its global keys while a text field or an engaged filter owns input. Items that don't implement Capturer never capture.
func (*Group) SetIndex ¶
SetIndex focuses the item at i, clamped to the group's bounds. Useful for carrying focus across a SetTheme rebuild.
func (Group) Update ¶
Update handles the cycling keys and focus requests. Everything else passes through untouched — the screen still forwards each message to its components itself, since a Group tracks focus, not content.
func (Group) WithKeys ¶
WithKeys returns a copy of g using custom cycling bindings. Zero-valued fields keep their defaults.
func (Group) WithoutWrap ¶
WithoutWrap returns a copy of g where cycling stops at the ends rather than wrapping around.
type Identified ¶
type Identified interface {
FocusToken() Token
}
Identified is implemented by components carrying a Token. A Group matches incoming requests against it, which is how a click that started inside a component finds its way back to the Group that owns focus ordering.
type Keys ¶
Keys are the bindings a Group dispatches against. Zero-valued fields fall back to the defaults from DefaultKeys.
func DefaultKeys ¶
func DefaultKeys() Keys
DefaultKeys returns tab / shift+tab, the library-wide focus cycling pair. These are deliberately not rebound elsewhere: pkg/tab uses shift+left and shift+right for tab switching precisely so tab stays free for focus.
type RequestMsg ¶
RequestMsg asks whichever Group owns the named component to give it focus. Target is set when the requester has the component's address; Token is set when a component is asking on its own behalf. A Group matches on either.
type Token ¶
type Token *struct {
// contains filtered or unexported fields
}
Token is a component's stable identity, handed out by NewToken and held as a field. It exists because bubbletea components take a value receiver on Update and return a new copy, so a component cannot refer to its own address — &m inside Update names a temporary. The token is copied along with every copy of the model, so it stays the same value no matter how many times the model is passed around.