This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -7961,6 +7961,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"derivative",
|
"derivative",
|
||||||
|
"email_address",
|
||||||
"iban_validate",
|
"iban_validate",
|
||||||
"jiff",
|
"jiff",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|||||||
142
todo.md
Normal file
142
todo.md
Normal 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`.
|
||||||
Submodule tui-canvas updated: 55bd618804...3f7df9f6fd
Submodule tui-pages updated: c52f54dd41...f215b38346
Reference in New Issue
Block a user