This commit is contained in:
Priec
2026-07-19 18:40:44 +02:00
parent e4aa11fdaf
commit 445bc6925d
4 changed files with 145 additions and 2 deletions

1
Cargo.lock generated
View File

@@ -7961,6 +7961,7 @@ dependencies = [
"async-trait",
"crossterm",
"derivative",
"email_address",
"iban_validate",
"jiff",
"once_cell",

142
todo.md Normal file
View File

@@ -0,0 +1,142 @@
# 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`:
```rust
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:
```rust
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 `0` have 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 that `index` is 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:
```rust
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:
```rust
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.
- `FormEditor` remains the sole owner of its current field, internal navigation,
hidden fields, and computed/non-navigable fields.
- `tui-pages` owns 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()`:
```rust
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
1. Add `FocusTarget::CanvasFormEditor(usize)`.
2. Change `PageFocusBuilder::canvas_form_editor()` to
`canvas_form_editor(editor_id)`.
3. Update form-editor key dispatch, paste dispatch, cursor behavior, and input
context selection to require the matching editor ID.
4. Keep standalone text inputs and text areas matched through their existing
`CanvasField(index)` identities.
5. Update examples to pass the same ID to the page focus builder and
`TuiPagesBuilder::canvas_form_editor(id)`.
6. 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_spec` must not call `register_page` merely 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`.