Canvas - tui-pages

## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# Canvas

Enable first-class canvas support with one feature:

    tui-pages = { version = "...", features = ["canvas"] }

The `canvas` feature intentionally enables the full canvas surface at once: form editor, ratatui rendering, suggestions, validation, computed fields, cursor style helpers, textarea, textinput, keymap handling, and crossterm input session helpers.

## App Action Shape

Use a wrapper action and implement `From<CanvasAction>`:

    #![allow(unused)]
    fn main() {
    use tui_pages::canvas::CanvasAction;

    enum Action {
        Canvas(CanvasAction),
        Quit,
    }

    impl From<CanvasAction> for Action {
        fn from(action: CanvasAction) -> Self {
            Action::Canvas(action)
        }
    }
    }

Then install standard bindings and typed text routing:

    #![allow(unused)]
    fn main() {
    let app = TuiPages::builder(View::Editor)
        .page_fn(page_spec)
        .handler(Handler)
        .canvas_defaults()
        .build();
    }

`PageSpec::canvas_editor(&editor)` or `PageSpec::canvas_mode(mode)` keeps the active input mode stack aligned with the focused canvas widget.

For custom wiring, `.canvas_keymaps()` installs only the standard NORMAL, INSERT, and SELECT keymaps, while `.canvas_text_actions()` installs only the typed-character mapper that turns plain text into `CanvasAction::InsertChar`. For lower-level standalone textarea and textinput wiring, route raw key events through the helpers below.

If you want `tui-pages` to own the canvas key dispatch directly, implement `CanvasWidgetState` for your app state and use the widget builders:

    #![allow(unused)]
    fn main() {
    impl tui_pages::canvas::CanvasWidgetState for State {
        fn canvas_form_editor(
            &mut self,
            id: usize,
        ) -> Option<&mut dyn tui_pages::canvas::CanvasFormEditorHost> {
            match id {
                0 => Some(&mut self.editor),
                _ => None,
            }
        }

        fn canvas_textarea(
            &mut self,
            focus_index: usize,
        ) -> Option<&mut dyn tui_pages::canvas::CanvasTextAreaHost> {
            match focus_index {
                0 => Some(&mut self.body),
                _ => None,
            }
        }

        fn canvas_textarea_entered(&mut self, focus_index: usize) -> Option<&mut bool> {
            match focus_index {
                0 => Some(&mut self.body_entered),
                _ => None,
            }
        }

        fn canvas_textinput_suggestion_suffix(
            &mut self,
            focus_index: usize,
            text: &str,
        ) -> Option<String> {
            match (focus_index, text) {
                (1, "adm") => Some("in".to_string()),
                _ => None,
            }
        }
    }

    let app = TuiPages::builder(View::Editor)
        .page_fn(page_spec)
        .handler(Handler)
        .canvas_form_editor(0)
        .canvas_textarea_widget(0)
        .build();
    }

`canvas_form_editor` dispatches canvas actions into the `FormEditor` and returns focus effects at editor boundaries, so your application action enum does not need a `Canvas(CanvasAction)` variant. The textarea and textinput builders treat their focused widget as one top-level stop until Enter sets the supplied `entered` flag. Once entered, NORMAL-mode keys drive the widget's modal editor, INSERT-mode keys go directly to the text widget, Esc exits edit mode and then the widget, and boundary/submit keys hand focus back to `tui-pages`. For textinput widgets, `canvas_textinput_suggestion_suffix` is optional; when it returns `Some(suffix)`, the widget shows that suffix inline and accepts it with Tab.

## FormEditor Handoff

The app owns editor state. In the action handler, dispatch canvas actions and return focus effects when the editor reaches a boundary:

    #![allow(unused)]
    fn main() {
    match tui_pages::dispatch_canvas_action(&mut state.editor, action) {
        CanvasDispatchOutcome::Focus(intent) => {
            ActionOutcome::effect(TuiEffect::Focus(intent))
        }
        CanvasDispatchOutcome::Applied(result) => {
            // handle messages, validation errors, persistence flags, recompute, etc.
            ActionOutcome::none()
        }
    }
    }

`ExitCanvasForward` and `ExitCanvasBackward` are handled by the normal `FocusManager`, so fields can sit beside buttons, sections, modals, buffers, and panes without custom focus code.

## Textarea And Textinput

For top-level textarea and textinput widgets, route raw key events through the typed helpers:

    #![allow(unused)]
    fn main() {
    let outcome = tui_pages::dispatch_canvas_text_area_key(&mut state.notes, key);
    let outcome = tui_pages::dispatch_canvas_text_input_key(&mut state.command, key);
    }

These return `Handled`, `Submitted`, `NotHandled`, or a focus intent. `Tab` and vertical boundary keys map to the same forward/backward canvas exits used by `FormEditor`.

## Rendering

Canvas renderers are re-exported from `tui_pages::canvas`. The common form-editor path is:

    #![allow(unused)]
    fn main() {
    tui_pages::render_canvas_with_suggestions_default(
        frame,
        frame.area(),
        form_area,
        &state.editor,
    );
    }

This draws the editor and, when suggestions are active, anchors the dropdown to the active input rectangle returned by the renderer. Textarea and textinput use the re-exported stateful widgets and expose `cursor(area, block)` helpers; set the ratatui cursor from those values after rendering the focused widget.

## Cursor Style

Cursor style is opt-in. Call `update_cursor_style_for_editor(&editor)` or `update_cursor_style_for_mode(mode)` from your event/render loop when you want terminal cursor shape updates. `tui-pages` does not mutate terminal cursor style unless the host calls these helpers.

## Example

See `examples/canvas/minimal` for the smallest form editor setup, `examples/canvas/textarea` for direct textinput/textarea dispatch, and `examples/canvas/full` for form editor, suggestions, validation, computed fields, cursor/session helpers, textarea, textinput, and canvas focus handoff together.