5.5 KiB
TUI Pages TODO
Give form editors a dedicated page-level focus target
Current change
Dynamic form-field visibility belongs to tui-canvas. A FormEditor knows its
stable logical field indexes and decides which fields are currently visible and
navigable through DataProvider::field_visibility.
The phone-number example previously duplicated that state in tui-pages:
PageFocusBuilder::new()
.canvas_field_indices(state.editor.navigable_fields())
This rebuilt a second per-field list every time the page specification was
refreshed. When the phone extension appeared or disappeared, the page focus
targets changed. TuiPages::sync_focus_to_spec consequently called
FocusManager::register_page, which resets page-level focus state. Besides the
repeated visibility scan and allocation, this reconciliation caused observable
input lag and made focus behavior depend on two independently maintained lists.
The immediate fix replaces that dynamic list with one stable page-level target:
PageFocusBuilder::new().canvas_form_editor()
Internal field navigation and visibility are now handled only by tui-canvas.
tui-pages only needs to know whether focus is inside the form or outside it.
Concern with the immediate API
canvas_form_editor() currently represents the complete form using
FocusTarget::CanvasField(0). The zero is a marker, not the editor's current
field. This is correct for the current example because it contains one form
editor and the form-editor key hook only checks FocusTarget::is_canvas().
However, overloading CanvasField(0) is ambiguous and should not become the
finished public design:
- Two form editors on the same page cannot be identified independently.
- A form editor and a standalone Canvas widget using field index
0have the same page-level identity. - Every form-editor hook currently accepts any Canvas focus target, so multiple registered form-editor hooks could compete for the same key event.
- Application code inspecting
FocusTarget::CanvasField(index)may reasonably assume thatindexis a real field index. For a form editor it is now only a sentinel. - Directly setting page focus cannot express which form editor should receive focus.
- Future focus restoration cannot reliably distinguish multiple editors.
Recommended design
Add a dedicated focus-target variant carrying the form-editor ID:
pub enum FocusTarget<O = ()> {
CanvasFormEditor(usize),
CanvasField(usize),
InternalCanvasField(usize),
// ...
}
Change the builder API to require the same ID used when registering the Canvas hook:
PageFocusBuilder::new().canvas_form_editor(0)
The responsibilities would then be explicit:
CanvasFormEditor(id)identifies one complete form at the page level.CanvasField(index)remains for independently focusable Canvas widgets.InternalCanvasField(index)retains its existing internal/non-top-level meaning where it is still needed.FormEditorremains the sole owner of its current field, internal navigation, hidden fields, and computed/non-navigable fields.tui-pagesowns navigation between the form and other page components such as buttons, sections, and other form editors.
The form-editor key hook must match its registered editor ID instead of merely
checking FocusTarget::is_canvas():
matches!(
ctx.focus.as_ref(),
Some(FocusTarget::CanvasFormEditor(focused_id)) if focused_id == id
)
Cursor behavior and input-layer selection should use the same ID-aware check.
FocusTarget::is_canvas() should include the new variant so generic canvas
boundary behavior continues to work.
Migration work
- Add
FocusTarget::CanvasFormEditor(usize). - Change
PageFocusBuilder::canvas_form_editor()tocanvas_form_editor(editor_id). - Update form-editor key dispatch, paste dispatch, cursor behavior, and input context selection to require the matching editor ID.
- Keep standalone text inputs and text areas matched through their existing
CanvasField(index)identities. - Update examples to pass the same ID to the page focus builder and
TuiPagesBuilder::canvas_form_editor(id). - Search applications for per-field form focus lists and replace those lists
with one
CanvasFormEditor(id)target at the correct position among the other page targets.
Do not retain CanvasField(0) as a compatibility sentinel unless backwards
compatibility is explicitly requested.
Required tests
- One form editor keeps the same page focus target while internal field visibility changes.
- Showing and hiding a dependent field does not call page focus registration or reset the current page-level target.
- A page containing two form editors routes keys only to the focused editor ID.
- A form editor and standalone Canvas text input can coexist without identity collisions.
- Exiting the first or last navigable form field moves to the correct adjacent page component.
- Returning focus to a form preserves or deliberately restores the editor's internal current field according to the chosen policy.
- Paste and cursor-style routing use the same editor-ID matching as key input.
Performance acceptance criteria
- Page refresh must not enumerate a form editor's visible or navigable fields.
- Changing internal field visibility must not change the page-level focus target vector.
sync_focus_to_specmust not callregister_pagemerely because a form row appeared or disappeared.- Typing into a visibility-controlling field must not allocate a second list of
form-field focus targets in
tui-pages.