Skip to content

Commit e7e0720

Browse files
committed
feat(inputs): add button control
1 parent 790809a commit e7e0720

6 files changed

Lines changed: 337 additions & 7 deletions

File tree

README.md

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ dropdowns, checkboxes), collect results via an `on_submit` callback.
1010

1111
- Bordered floating window with optional title
1212
- Keyboard-navigable: `<Tab>` / `<S-Tab>` to move between inputs
13-
- Input types: `text`, `multiline`, `select`, `checkbox`, plus `spacer` (visual-only gap between
14-
fields)
13+
- Input types: `text`, `multiline`, `select`, `checkbox`, `button`, plus `spacer` (visual-only gap
14+
between fields)
1515
- Select dropdowns open with `<CR>`; arrows navigate; `<CR>` confirms
1616
- Checkbox toggles with `<Space>` or `<CR>`
17+
- Buttons activate with `<CR>` or `<Space>` and invoke a user-supplied callback
1718
- Submit with `<C-s>` — results delivered as a `{ [name] = value }` table
1819
- Cancel with `<Esc>` or `q`
1920
- Built-in toggleable help popup (`?`) listing every active keymap — updates automatically when you
@@ -164,6 +165,41 @@ glyph sits immediately next to the label, and any validation error is appended o
164165
bordered inputs. Tune via `style.checkbox.padding` (default `1`, set to `0` to pack tight).
165166
- Pair with `validators.checked()` to require the box to be ticked (see [Validation](#validation)).
166167

168+
#### `button`
169+
170+
```lua
171+
{
172+
type = "button",
173+
label = "Submit",
174+
on_activate = function(form)
175+
form:submit()
176+
end,
177+
}
178+
```
179+
180+
A focusable control that carries no value (skipped in `form:results()`) and invokes
181+
`on_activate(form)` when activated. The form passes itself as the first argument so handlers can
182+
call `form:submit()` / `form:cancel()` / inspect `form:results()` without a closure.
183+
184+
- `label` (required) — text rendered inside the button.
185+
- `on_activate` (optional) — `fun(form): nil` callback fired on activation.
186+
- `name` (optional) — buttons don't appear in `results()`, but a name can still be set for
187+
identification.
188+
- Activated with the configured `keymaps.activate` keys (default `<CR>` and `<Space>`).
189+
- Per-button style overrides — any of `bordered`, `prefix`, `suffix`, `align` can be set directly on
190+
the spec to override the global `style.button` defaults (see [Button styling](#button-styling)).
191+
192+
```lua
193+
{
194+
type = "button",
195+
label = "Save",
196+
prefix = "[ ",
197+
suffix = " ]",
198+
bordered = false,
199+
on_activate = function(form) form:submit() end,
200+
}
201+
```
202+
167203
#### `spacer`
168204

169205
```lua
@@ -284,6 +320,42 @@ require("input-form").setup({
284320
Alternatives that render well in most fonts: `[x]` / `[ ]`, `` / `·`, `` / ``. Set `padding = 0`
285321
to pack checkboxes flush against adjacent bordered inputs.
286322

323+
### Button styling
324+
325+
`button` inputs read their visual defaults from `style.button`, and any key can be overridden on the
326+
per-button spec:
327+
328+
```lua
329+
require("input-form").setup({
330+
style = {
331+
button = {
332+
bordered = true, -- wrap the button in a floating-window border
333+
prefix = "", -- e.g. "[ " for `[ Save ]`
334+
suffix = "", -- e.g. " ]" for `[ Save ]`
335+
align = "center", -- "left" | "center" | "right"
336+
},
337+
},
338+
})
339+
```
340+
341+
Focus state swaps `NormalFloat` / `FloatBorder` for `InputFormButtonFocus` /
342+
`InputFormButtonFocusBorder`, both of which default to `reverse = true` — the focused button renders
343+
with its foreground and background colors swapped. Override the highlight groups to use explicit
344+
colors instead:
345+
346+
```lua
347+
require("input-form").setup({
348+
style = {
349+
highlights = {
350+
InputFormButton = { fg = "#cdd6f4", bg = "#1e1e2e" },
351+
InputFormButtonBorder = { fg = "#585b70" },
352+
InputFormButtonFocus = { fg = "#1e1e2e", bg = "#cdd6f4", bold = true },
353+
InputFormButtonFocusBorder = { fg = "#cdd6f4" },
354+
},
355+
},
356+
})
357+
```
358+
287359
### Help popup
288360

289361
The form's bottom border shows a compact `? help` hint on the right. Press `?` (configurable via
@@ -358,6 +430,10 @@ Available groups:
358430
| `InputFormFieldErrorTitle` | Invalid field label |
359431
| `InputFormDropdown` | Select dropdown background |
360432
| `InputFormDropdownActive` | Highlighted dropdown row |
433+
| `InputFormButton` | Unfocused button background |
434+
| `InputFormButtonBorder` | Unfocused button border |
435+
| `InputFormButtonFocus` | Focused button background (default reversed) |
436+
| `InputFormButtonFocusBorder`| Focused button border (default reversed) |
361437

362438
User overrides fully **replace** the default spec per group (they are not deep-merged at the field
363439
level), so you don't need to re-specify `default = true`. Highlights are re-applied on every
@@ -388,6 +464,7 @@ require("input-form").setup({
388464
cancel = { "<Esc>", "q" }, -- list form: both keys cancel the form
389465
open_select = "<CR>",
390466
toggle = "<Space>",
467+
activate = { "<CR>", "<Space>" }, -- activate a focused `button` input
391468
help = "?", -- toggle the help popup (set `false` to hide)
392469
},
393470
select = {
@@ -402,6 +479,12 @@ require("input-form").setup({
402479
unchecked = "",
403480
padding = 1, -- blank rows above/below each checkbox
404481
},
482+
button = {
483+
bordered = true,
484+
prefix = "",
485+
suffix = "",
486+
align = "center",
487+
},
405488
-- ...chevron, highlights, etc. — see sections above.
406489
},
407490
})

doc/input-form.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ Default configuration for |input-form|.
120120
open_select = "<CR>",
121121
--- Toggle the value of a `checkbox` input.
122122
toggle = "<Space>",
123+
--- Activate a `button` input (invokes its `on_activate` callback).
124+
activate = { "<CR>", "<Space>" },
123125
--- Toggle a help popup listing every active keymap. The popup opens
124126
--- directly below the form window and closes on the same key.
125127
help = "?",
@@ -144,6 +146,19 @@ Default configuration for |input-form|.
144146
--- Glyph shown when the dropdown is open.
145147
open = "⌃",
146148
},
149+
--- Styling for `button` inputs. Each key can also be overridden on the
150+
--- individual button spec.
151+
button = {
152+
--- Wrap the button in a floating-window border.
153+
bordered = true,
154+
--- String prepended to the button label (e.g. `"[ "` for `[ Save ]`).
155+
prefix = "",
156+
--- String appended to the button label (e.g. `" ]"` for `[ Save ]`).
157+
suffix = "",
158+
--- Horizontal alignment of the label text inside the button: `"left"`,
159+
--- `"center"`, or `"right"`.
160+
align = "center",
161+
},
147162
--- Glyphs shown in `checkbox` inputs.
148163
checkbox = {
149164
--- Shown when the box is checked.
@@ -169,6 +184,13 @@ Default configuration for |input-form|.
169184
InputFormField = { link = "NormalFloat", default = true },
170185
InputFormFieldBorder = { link = "FloatBorder", default = true },
171186
InputFormFieldTitle = { link = "FloatTitle", default = true },
187+
-- Button input. Focus state reverses fg/bg so the focused button
188+
-- renders as the inverse of its normal colors (i.e. light text on dark
189+
-- becomes dark text on light, and vice-versa).
190+
InputFormButton = { link = "NormalFloat", default = true },
191+
InputFormButtonBorder = { link = "FloatBorder", default = true },
192+
InputFormButtonFocus = { reverse = true, default = true },
193+
InputFormButtonFocusBorder = { reverse = true, default = true },
172194
-- Error state for individual input fields
173195
InputFormFieldError = { fg = "Red", default = true },
174196
InputFormFieldErrorBorder = { fg = "Red", default = true },

lua/input-form/config.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ M.defaults = {
4444
open_select = "<CR>",
4545
--- Toggle the value of a `checkbox` input.
4646
toggle = "<Space>",
47+
--- Activate a `button` input (invokes its `on_activate` callback).
48+
activate = { "<CR>", "<Space>" },
4749
--- Toggle a help popup listing every active keymap. The popup opens
4850
--- directly below the form window and closes on the same key.
4951
help = "?",
@@ -68,6 +70,19 @@ M.defaults = {
6870
--- Glyph shown when the dropdown is open.
6971
open = "",
7072
},
73+
--- Styling for `button` inputs. Each key can also be overridden on the
74+
--- individual button spec.
75+
button = {
76+
--- Wrap the button in a floating-window border.
77+
bordered = true,
78+
--- String prepended to the button label (e.g. `"[ "` for `[ Save ]`).
79+
prefix = "",
80+
--- String appended to the button label (e.g. `" ]"` for `[ Save ]`).
81+
suffix = "",
82+
--- Horizontal alignment of the label text inside the button: `"left"`,
83+
--- `"center"`, or `"right"`.
84+
align = "center",
85+
},
7186
--- Glyphs shown in `checkbox` inputs.
7287
checkbox = {
7388
--- Shown when the box is checked.
@@ -93,6 +108,13 @@ M.defaults = {
93108
InputFormField = { link = "NormalFloat", default = true },
94109
InputFormFieldBorder = { link = "FloatBorder", default = true },
95110
InputFormFieldTitle = { link = "FloatTitle", default = true },
111+
-- Button input. Focus state reverses fg/bg so the focused button
112+
-- renders as the inverse of its normal colors (i.e. light text on dark
113+
-- becomes dark text on light, and vice-versa).
114+
InputFormButton = { link = "NormalFloat", default = true },
115+
InputFormButtonBorder = { link = "FloatBorder", default = true },
116+
InputFormButtonFocus = { reverse = true, default = true },
117+
InputFormButtonFocusBorder = { reverse = true, default = true },
96118
-- Error state for individual input fields
97119
InputFormFieldError = { fg = "Red", default = true },
98120
InputFormFieldErrorBorder = { fg = "Red", default = true },

lua/input-form/form.lua

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,11 @@ function M:close()
333333
end
334334

335335
--- Collect current values from all inputs into a { [name] = value } table.
336-
--- Spacers have no name/value and are skipped.
336+
--- Spacers and buttons have no value and are skipped.
337337
function M:results()
338338
local out = {}
339339
for _, input in ipairs(self._inputs) do
340-
if input.type ~= "spacer" and input.name then
340+
if input.type ~= "spacer" and input.type ~= "button" and input.name then
341341
out[input.name] = input:value()
342342
end
343343
end
@@ -572,12 +572,14 @@ function M:_help_entries()
572572
table.insert(entries, { nav, "navigate fields" })
573573
end
574574
-- Only advertise type-specific keys if the form actually has such an input.
575-
local has_select, has_checkbox = false, false
575+
local has_select, has_checkbox, has_button = false, false, false
576576
for _, input in ipairs(self._inputs) do
577577
if input.type == "select" then
578578
has_select = true
579579
elseif input.type == "checkbox" then
580580
has_checkbox = true
581+
elseif input.type == "button" then
582+
has_button = true
581583
end
582584
end
583585
if has_select then
@@ -586,6 +588,9 @@ function M:_help_entries()
586588
if has_checkbox then
587589
add(km.toggle, "toggle checkbox")
588590
end
591+
if has_button then
592+
add(km.activate, "activate button")
593+
end
589594
add(km.submit, "submit form")
590595
add(km.cancel, "cancel form")
591596
add(km.help, "toggle this help")
@@ -870,6 +875,13 @@ function M:_install_keymaps(input)
870875
map("i", "<CR>", function()
871876
vim.cmd("stopinsert")
872877
end)
878+
elseif input.type == "button" then
879+
map("n", km.activate, function()
880+
input:activate(self)
881+
end)
882+
-- Block insert mode on the button display buffer.
883+
vim.keymap.set("n", "i", "<Nop>", { buffer = buf, nowait = true, silent = true })
884+
vim.keymap.set("n", "a", "<Nop>", { buffer = buf, nowait = true, silent = true })
873885
end
874886
end
875887

0 commit comments

Comments
 (0)