@@ -89,8 +89,10 @@ function M:_compute_layout()
8989 if type (input .is_bordered ) == " function" then
9090 bordered = input :is_bordered ()
9191 end
92- local top_pad = (not bordered ) and cb_pad or 0
93- local bot_pad = (not bordered ) and cb_pad or 0
92+ -- Only actual checkboxes get the configured blank padding. Spacers are
93+ -- also borderless but their `height` is the user's exact request.
94+ local top_pad = (input .type == " checkbox" ) and cb_pad or 0
95+ local bot_pad = (input .type == " checkbox" ) and cb_pad or 0
9496 local outer_h = bordered and (h + 2 ) or (h + top_pad + bot_pad )
9597 -- Editor-row offset from `parent_row` to pass as `nvim_open_win`'s `row`
9698 -- parameter for this child. `row` refers to the window's OUTER top-left
@@ -203,6 +205,11 @@ function M:show()
203205 -- (not their border column) so everything lines up visually.
204206 local border = config .options .window .border
205207 for i , input in ipairs (self ._inputs ) do
208+ -- Spacers are visual-only; they reserve layout rows but never mount a
209+ -- window and don't participate in keymaps/validation/focus.
210+ if input .type == " spacer" then
211+ goto continue
212+ end
206213 local r = layout .rows [i ]
207214 -- Bordered children get `+1` to clear the parent's left border; their
208215 -- content then sits at `+2`. Borderless children shift an extra column
@@ -220,12 +227,31 @@ function M:show()
220227 input :mount (mount_opts )
221228 self :_install_keymaps (input )
222229 self :_install_validation (input )
230+ :: continue::
223231 end
224232
225- self :_focus (1 )
233+ self :_focus (self : _first_focusable () or 1 )
226234 return self
227235end
228236
237+ --- Return `true` if `input` participates in focus navigation.
238+ local function is_focusable (input )
239+ if input == nil then
240+ return false
241+ end
242+ return input .focusable ~= false and input .type ~= " spacer"
243+ end
244+
245+ --- Index of the first focusable input, or `nil` if none exist.
246+ function M :_first_focusable ()
247+ for i , input in ipairs (self ._inputs ) do
248+ if is_focusable (input ) then
249+ return i
250+ end
251+ end
252+ return nil
253+ end
254+
229255--- Apply all configured highlight groups. Called from `show()` so live
230256--- `setup({ style = { highlights = ... } })` edits take effect on the next
231257--- form open.
@@ -263,10 +289,13 @@ function M:close()
263289end
264290
265291--- Collect current values from all inputs into a { [name] = value } table.
292+ --- Spacers have no name/value and are skipped.
266293function M :results ()
267294 local out = {}
268295 for _ , input in ipairs (self ._inputs ) do
269- out [input .name ] = input :value ()
296+ if input .type ~= " spacer" and input .name then
297+ out [input .name ] = input :value ()
298+ end
270299 end
271300 return out
272301end
@@ -484,19 +513,44 @@ function M:_help_line()
484513 return table.concat (parts , " " )
485514end
486515
516+ --- Advance from `start` by `step` (+1 or -1), wrapping, until a focusable
517+ --- input is found. Returns the new index, or `start` if no input is
518+ --- focusable.
519+ function M :_next_focusable (start , step )
520+ local n = # self ._inputs
521+ if n == 0 then
522+ return start
523+ end
524+ local idx = ((start - 1 ) % n + n ) % n + 1
525+ for _ = 1 , n do
526+ if is_focusable (self ._inputs [idx ]) then
527+ return idx
528+ end
529+ idx = ((idx - 1 + step ) % n + n ) % n + 1
530+ end
531+ return start
532+ end
533+
487534function M :_focus (idx )
488535 local n = # self ._inputs
536+ if n == 0 then
537+ return
538+ end
489539 idx = ((idx - 1 ) % n + n ) % n + 1
540+ -- If the requested index isn't focusable, advance forward to the next one.
541+ if not is_focusable (self ._inputs [idx ]) then
542+ idx = self :_next_focusable (idx , 1 )
543+ end
490544 self ._focus_idx = idx
491545 self ._inputs [idx ]:focus ()
492546end
493547
494548function M :focus_next ()
495- self :_focus (self ._focus_idx + 1 )
549+ self :_focus (self : _next_focusable ( self ._focus_idx + 1 , 1 ) )
496550end
497551
498552function M :focus_prev ()
499- self :_focus (self ._focus_idx - 1 )
553+ self :_focus (self : _next_focusable ( self ._focus_idx - 1 , - 1 ) )
500554end
501555
502556function M :_install_keymaps (input )
0 commit comments