## How Canvas Library Custom Functionality Works ### 1. **The Canvas Library Calls YOUR Custom Code First** When you call `ActionDispatcher::dispatch()`, here's what happens: ```rust // Inside canvas library (canvas/src/actions/edit.rs): pub async fn execute_canvas_action( action: CanvasAction, state: &mut S, ideal_cursor_column: &mut usize, ) -> Result { // 1. FIRST: Canvas library calls YOUR custom handler if let Some(result) = state.handle_feature_action(&action, &context) { return Ok(ActionResult::HandledByFeature(result)); // YOUR code handled it } // 2. ONLY IF your code returns None: Canvas handles generic actions handle_generic_canvas_action(action, state, ideal_cursor_column).await } ``` ### 2. **Your Extension Point: `handle_feature_action`** You add custom functionality by implementing `handle_feature_action` in your states: ```rust // In src/state/pages/auth.rs impl CanvasState for LoginState { // ... other methods ... fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option { match action { // Custom login-specific actions CanvasAction::Custom(action_str) if action_str == "submit_login" => { if self.username.is_empty() || self.password.is_empty() { Some("Please fill in all required fields".to_string()) } else { // Trigger login process Some(format!("Logging in user: {}", self.username)) } } CanvasAction::Custom(action_str) if action_str == "clear_form" => { self.username.clear(); self.password.clear(); self.set_has_unsaved_changes(false); Some("Login form cleared".to_string()) } // Custom behavior for standard actions CanvasAction::NextField => { // Custom validation when moving between fields if self.current_field == 0 && self.username.is_empty() { Some("Username cannot be empty".to_string()) } else { None // Let canvas library handle the normal field movement } } // Let canvas library handle everything else _ => None, } } } ``` ### 3. **Multiple Ways to Add Custom Functionality** #### A) **Custom Actions via Config** ```toml # In config.toml [keybindings.edit] submit_login = ["ctrl+enter"] clear_form = ["ctrl+r"] ``` #### B) **Override Standard Actions** ```rust fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option { match action { CanvasAction::InsertChar('p') if self.current_field == 1 => { // Custom behavior when typing 'p' in password field Some("Password field - use secure input".to_string()) } _ => None, // Let canvas handle normally } } ``` #### C) **Context-Aware Logic** ```rust fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option { match action { CanvasAction::MoveDown => { // Custom logic based on current state if context.current_field == 1 && context.current_input.len() < 8 { Some("Password should be at least 8 characters".to_string()) } else { None // Normal field movement } } _ => None, } } ``` ## The Canvas Library Philosophy **Canvas Library = Generic behavior + Your extension points** - ✅ **Canvas handles**: Character insertion, cursor movement, field navigation, etc. - ✅ **You handle**: Validation, submission, clearing, app-specific logic - ✅ **You decide**: Return `Some(message)` to override, `None` to use canvas default ## Summary You **don't communicate with the library elsewhere**. Instead: 1. **Canvas library calls your code first** via `handle_feature_action` 2. **Your code decides** whether to handle the action or let canvas handle it 3. **Canvas library handles** generic form behavior when you return `None`