working autocomplete now, with backwards deprecation
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -475,6 +475,7 @@ name = "canvas"
|
|||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"common",
|
"common",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ thiserror = { workspace = true }
|
|||||||
|
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
|
async-trait = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio-test = "0.4.4"
|
tokio-test = "0.4.4"
|
||||||
@@ -29,7 +30,7 @@ tokio-test = "0.4.4"
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gui = ["ratatui"]
|
gui = ["ratatui"]
|
||||||
autocomplete = ["tokio"]
|
autocomplete = ["tokio", "async-trait"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "autocomplete"
|
name = "autocomplete"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ use canvas::{
|
|||||||
theme::CanvasTheme,
|
theme::CanvasTheme,
|
||||||
},
|
},
|
||||||
autocomplete::{
|
autocomplete::{
|
||||||
AutocompleteCanvasState,
|
AutocompleteCanvasState,
|
||||||
AutocompleteState,
|
AutocompleteState,
|
||||||
SuggestionItem,
|
SuggestionItem,
|
||||||
execute_with_autocomplete,
|
execute_with_autocomplete,
|
||||||
handle_autocomplete_feature_action,
|
handle_autocomplete_feature_action,
|
||||||
@@ -33,6 +33,9 @@ use canvas::{
|
|||||||
CanvasAction,
|
CanvasAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add the async_trait import
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
// Simple theme implementation
|
// Simple theme implementation
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DemoTheme;
|
struct DemoTheme;
|
||||||
@@ -64,7 +67,7 @@ struct AutocompleteFormState {
|
|||||||
mode: AppMode,
|
mode: AppMode,
|
||||||
has_changes: bool,
|
has_changes: bool,
|
||||||
debug_message: String,
|
debug_message: String,
|
||||||
|
|
||||||
// Autocomplete state
|
// Autocomplete state
|
||||||
autocomplete: AutocompleteState<EmailSuggestion>,
|
autocomplete: AutocompleteState<EmailSuggestion>,
|
||||||
}
|
}
|
||||||
@@ -97,14 +100,14 @@ impl AutocompleteFormState {
|
|||||||
impl CanvasState for AutocompleteFormState {
|
impl CanvasState for AutocompleteFormState {
|
||||||
fn current_field(&self) -> usize { self.current_field }
|
fn current_field(&self) -> usize { self.current_field }
|
||||||
fn current_cursor_pos(&self) -> usize { self.cursor_pos }
|
fn current_cursor_pos(&self) -> usize { self.cursor_pos }
|
||||||
fn set_current_field(&mut self, index: usize) {
|
fn set_current_field(&mut self, index: usize) {
|
||||||
self.current_field = index.min(self.fields.len().saturating_sub(1));
|
self.current_field = index.min(self.fields.len().saturating_sub(1));
|
||||||
// Clear autocomplete when changing fields
|
// Clear autocomplete when changing fields
|
||||||
if self.is_autocomplete_active() {
|
if self.is_autocomplete_active() {
|
||||||
self.clear_autocomplete_suggestions();
|
self.clear_autocomplete_suggestions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn set_current_cursor_pos(&mut self, pos: usize) {
|
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||||
let max_pos = if self.mode == AppMode::Edit {
|
let max_pos = if self.mode == AppMode::Edit {
|
||||||
self.fields[self.current_field].len()
|
self.fields[self.current_field].len()
|
||||||
} else {
|
} else {
|
||||||
@@ -146,6 +149,8 @@ impl CanvasState for AutocompleteFormState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the #[async_trait] attribute to the implementation
|
||||||
|
#[async_trait]
|
||||||
impl AutocompleteCanvasState for AutocompleteFormState {
|
impl AutocompleteCanvasState for AutocompleteFormState {
|
||||||
type SuggestionData = EmailSuggestion;
|
type SuggestionData = EmailSuggestion;
|
||||||
|
|
||||||
@@ -165,9 +170,9 @@ impl AutocompleteCanvasState for AutocompleteFormState {
|
|||||||
fn should_trigger_autocomplete(&self) -> bool {
|
fn should_trigger_autocomplete(&self) -> bool {
|
||||||
let current_input = self.get_current_input();
|
let current_input = self.get_current_input();
|
||||||
let current_field = self.current_field();
|
let current_field = self.current_field();
|
||||||
|
|
||||||
// Trigger for email field when we have "@" and at least 1 more character
|
// Trigger for email field when we have "@" and at least 1 more character
|
||||||
self.supports_autocomplete(current_field) &&
|
self.supports_autocomplete(current_field) &&
|
||||||
current_input.contains('@') &&
|
current_input.contains('@') &&
|
||||||
current_input.len() > current_input.find('@').unwrap_or(0) + 1 &&
|
current_input.len() > current_input.find('@').unwrap_or(0) + 1 &&
|
||||||
!self.is_autocomplete_active()
|
!self.is_autocomplete_active()
|
||||||
@@ -181,7 +186,7 @@ impl AutocompleteCanvasState for AutocompleteFormState {
|
|||||||
|
|
||||||
// 2. Get current input for querying
|
// 2. Get current input for querying
|
||||||
let query = self.get_current_input().to_string();
|
let query = self.get_current_input().to_string();
|
||||||
|
|
||||||
// 3. Extract domain part from email
|
// 3. Extract domain part from email
|
||||||
let domain_part = if let Some(at_pos) = query.find('@') {
|
let domain_part = if let Some(at_pos) = query.find('@') {
|
||||||
query[at_pos + 1..].to_string()
|
query[at_pos + 1..].to_string()
|
||||||
@@ -195,19 +200,19 @@ impl AutocompleteCanvasState for AutocompleteFormState {
|
|||||||
let suggestions = tokio::task::spawn_blocking(move || {
|
let suggestions = tokio::task::spawn_blocking(move || {
|
||||||
// Simulate network delay
|
// Simulate network delay
|
||||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||||
|
|
||||||
// Create mock suggestions based on domain input
|
// Create mock suggestions based on domain input
|
||||||
let popular_domains = vec![
|
let popular_domains = vec![
|
||||||
("gmail.com", "Gmail"),
|
("gmail.com", "Gmail"),
|
||||||
("yahoo.com", "Yahoo Mail"),
|
("yahoo.com", "Yahoo Mail"),
|
||||||
("outlook.com", "Outlook"),
|
("outlook.com", "Outlook"),
|
||||||
("hotmail.com", "Hotmail"),
|
("hotmail.com", "Hotmail"),
|
||||||
("company.com", "Company Email"),
|
("company.com", "Company Email"),
|
||||||
("university.edu", "University"),
|
("university.edu", "University"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
for (domain, provider) in popular_domains {
|
for (domain, provider) in popular_domains {
|
||||||
if domain.starts_with(&domain_part) || domain_part.is_empty() {
|
if domain.starts_with(&domain_part) || domain_part.is_empty() {
|
||||||
let full_email = format!("{}@{}", email_prefix, domain);
|
let full_email = format!("{}@{}", email_prefix, domain);
|
||||||
@@ -221,7 +226,7 @@ impl AutocompleteCanvasState for AutocompleteFormState {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results
|
results
|
||||||
}).await.unwrap_or_default();
|
}).await.unwrap_or_default();
|
||||||
|
|
||||||
@@ -246,7 +251,7 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut Aut
|
|||||||
Some(CanvasAction::NextField) // Normal tab
|
Some(CanvasAction::NextField) // Normal tab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::BackTab => {
|
KeyCode::BackTab => {
|
||||||
if state.is_autocomplete_active() {
|
if state.is_autocomplete_active() {
|
||||||
Some(CanvasAction::SuggestionUp)
|
Some(CanvasAction::SuggestionUp)
|
||||||
@@ -254,7 +259,7 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut Aut
|
|||||||
Some(CanvasAction::PrevField)
|
Some(CanvasAction::PrevField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if state.is_autocomplete_active() {
|
if state.is_autocomplete_active() {
|
||||||
Some(CanvasAction::SelectSuggestion) // Apply suggestion
|
Some(CanvasAction::SelectSuggestion) // Apply suggestion
|
||||||
@@ -262,7 +267,7 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut Aut
|
|||||||
Some(CanvasAction::NextField)
|
Some(CanvasAction::NextField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
if state.is_autocomplete_active() {
|
if state.is_autocomplete_active() {
|
||||||
Some(CanvasAction::ExitSuggestions) // Close autocomplete
|
Some(CanvasAction::ExitSuggestions) // Close autocomplete
|
||||||
@@ -280,12 +285,12 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut Aut
|
|||||||
KeyCode::End => Some(CanvasAction::MoveLineEnd),
|
KeyCode::End => Some(CanvasAction::MoveLineEnd),
|
||||||
KeyCode::Backspace => Some(CanvasAction::DeleteBackward),
|
KeyCode::Backspace => Some(CanvasAction::DeleteBackward),
|
||||||
KeyCode::Delete => Some(CanvasAction::DeleteForward),
|
KeyCode::Delete => Some(CanvasAction::DeleteForward),
|
||||||
|
|
||||||
// Character input
|
// Character input
|
||||||
KeyCode::Char(c) if !modifiers.contains(KeyModifiers::CONTROL) => {
|
KeyCode::Char(c) if !modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
Some(CanvasAction::InsertChar(c))
|
Some(CanvasAction::InsertChar(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -371,7 +376,7 @@ fn ui(f: &mut Frame, state: &AutocompleteFormState, theme: &DemoTheme) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let status_lines = vec![
|
let status_lines = vec![
|
||||||
Line::from(Span::raw(format!("Mode: {:?} | Field: {}/{} | Cursor: {}",
|
Line::from(Span::raw(format!("Mode: {:?} | Field: {}/{} | Cursor: {}",
|
||||||
state.mode, state.current_field + 1, state.fields.len(), state.cursor_pos))),
|
state.mode, state.current_field + 1, state.fields.len(), state.cursor_pos))),
|
||||||
Line::from(Span::raw(format!("Autocomplete: {}", autocomplete_status))),
|
Line::from(Span::raw(format!("Autocomplete: {}", autocomplete_status))),
|
||||||
Line::from(Span::raw(state.debug_message.clone())),
|
Line::from(Span::raw(state.debug_message.clone())),
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ use anyhow::Result;
|
|||||||
|
|
||||||
/// Enhanced execute function for states that support autocomplete
|
/// Enhanced execute function for states that support autocomplete
|
||||||
/// This is the main entry point for autocomplete-aware canvas execution
|
/// This is the main entry point for autocomplete-aware canvas execution
|
||||||
///
|
///
|
||||||
/// Use this instead of canvas::execute() if you want autocomplete behavior:
|
/// Use this instead of canvas::execute() if you want autocomplete behavior:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// execute_with_autocomplete(action, &mut state).await?;
|
/// execute_with_autocomplete(action, &mut state).await?;
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>(
|
pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState + Send>(
|
||||||
action: CanvasAction,
|
action: CanvasAction,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
) -> Result<ActionResult> {
|
) -> Result<ActionResult> {
|
||||||
match &action {
|
match &action {
|
||||||
// === AUTOCOMPLETE-SPECIFIC ACTIONS ===
|
// === AUTOCOMPLETE-SPECIFIC ACTIONS ===
|
||||||
|
|
||||||
CanvasAction::TriggerAutocomplete => {
|
CanvasAction::TriggerAutocomplete => {
|
||||||
if state.supports_autocomplete(state.current_field()) {
|
if state.supports_autocomplete(state.current_field()) {
|
||||||
state.trigger_autocomplete_suggestions().await;
|
state.trigger_autocomplete_suggestions().await;
|
||||||
@@ -61,7 +61,7 @@ pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === TEXT INSERTION WITH AUTO-TRIGGER ===
|
// === TEXT INSERTION WITH AUTO-TRIGGER ===
|
||||||
|
|
||||||
CanvasAction::InsertChar(_) => {
|
CanvasAction::InsertChar(_) => {
|
||||||
// First, execute the character insertion normally
|
// First, execute the character insertion normally
|
||||||
let result = execute(action, state).await?;
|
let result = execute(action, state).await?;
|
||||||
@@ -75,7 +75,7 @@ pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === NAVIGATION/EDITING ACTIONS (clear autocomplete first) ===
|
// === NAVIGATION/EDITING ACTIONS (clear autocomplete first) ===
|
||||||
|
|
||||||
CanvasAction::MoveLeft | CanvasAction::MoveRight |
|
CanvasAction::MoveLeft | CanvasAction::MoveRight |
|
||||||
CanvasAction::MoveUp | CanvasAction::MoveDown |
|
CanvasAction::MoveUp | CanvasAction::MoveDown |
|
||||||
CanvasAction::NextField | CanvasAction::PrevField |
|
CanvasAction::NextField | CanvasAction::PrevField |
|
||||||
@@ -84,13 +84,13 @@ pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>
|
|||||||
if state.is_autocomplete_active() {
|
if state.is_autocomplete_active() {
|
||||||
state.clear_autocomplete_suggestions();
|
state.clear_autocomplete_suggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the action normally
|
// Execute the action normally
|
||||||
execute(action, state).await
|
execute(action, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ALL OTHER ACTIONS (normal execution) ===
|
// === ALL OTHER ACTIONS (normal execution) ===
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
// For all other actions, just execute normally
|
// For all other actions, just execute normally
|
||||||
execute(action, state).await
|
execute(action, state).await
|
||||||
@@ -99,7 +99,7 @@ pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to integrate autocomplete actions with CanvasState.handle_feature_action()
|
/// Helper function to integrate autocomplete actions with CanvasState.handle_feature_action()
|
||||||
///
|
///
|
||||||
/// Use this in your CanvasState implementation like this:
|
/// Use this in your CanvasState implementation like this:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option<String> {
|
/// fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option<String> {
|
||||||
@@ -107,12 +107,12 @@ pub async fn execute_with_autocomplete<S: CanvasState + AutocompleteCanvasState>
|
|||||||
/// if let Some(result) = handle_autocomplete_feature_action(action, self) {
|
/// if let Some(result) = handle_autocomplete_feature_action(action, self) {
|
||||||
/// return Some(result);
|
/// return Some(result);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Handle your other custom actions...
|
/// // Handle your other custom actions...
|
||||||
/// None
|
/// None
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn handle_autocomplete_feature_action<S: CanvasState + AutocompleteCanvasState>(
|
pub fn handle_autocomplete_feature_action<S: CanvasState + AutocompleteCanvasState + Send>(
|
||||||
action: &CanvasAction,
|
action: &CanvasAction,
|
||||||
state: &S,
|
state: &S,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
@@ -160,7 +160,7 @@ pub fn handle_autocomplete_feature_action<S: CanvasState + AutocompleteCanvasSta
|
|||||||
/// Legacy compatibility function - kept for backward compatibility
|
/// Legacy compatibility function - kept for backward compatibility
|
||||||
/// This is the old function signature, now it just wraps the new system
|
/// This is the old function signature, now it just wraps the new system
|
||||||
#[deprecated(note = "Use execute_with_autocomplete instead")]
|
#[deprecated(note = "Use execute_with_autocomplete instead")]
|
||||||
pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + AutocompleteCanvasState>(
|
pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + AutocompleteCanvasState + Send>(
|
||||||
action: CanvasAction,
|
action: CanvasAction,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
_ideal_cursor_column: &mut usize, // Ignored - new system manages this internally
|
_ideal_cursor_column: &mut usize, // Ignored - new system manages this internally
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
// src/autocomplete/state.rs
|
// src/autocomplete/state.rs
|
||||||
|
|
||||||
use crate::canvas::state::CanvasState;
|
use crate::canvas::state::CanvasState;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
/// OPTIONAL extension trait for states that want rich autocomplete functionality.
|
/// OPTIONAL extension trait for states that want rich autocomplete functionality.
|
||||||
/// Only implement this if you need the new autocomplete features.
|
/// Only implement this if you need the new autocomplete features.
|
||||||
///
|
///
|
||||||
/// # User Workflow:
|
/// # User Workflow:
|
||||||
/// 1. User presses trigger key (Tab, Ctrl+K, etc.)
|
/// 1. User presses trigger key (Tab, Ctrl+K, etc.)
|
||||||
/// 2. User's key mapping calls CanvasAction::TriggerAutocomplete
|
/// 2. User's key mapping calls CanvasAction::TriggerAutocomplete
|
||||||
/// 3. Library calls your trigger_autocomplete_suggestions() method
|
/// 3. Library calls your trigger_autocomplete_suggestions() method
|
||||||
/// 4. You implement async fetching logic in that method
|
/// 4. You implement async fetching logic in that method
|
||||||
/// 5. You call set_autocomplete_suggestions() with results
|
/// 5. You call set_autocomplete_suggestions() with results
|
||||||
/// 6. Library manages UI state and navigation
|
/// 6. Library manages UI state and navigation
|
||||||
|
#[async_trait]
|
||||||
pub trait AutocompleteCanvasState: CanvasState {
|
pub trait AutocompleteCanvasState: CanvasState {
|
||||||
/// Associated type for suggestion data (e.g., Hit, String, CustomType)
|
/// Associated type for suggestion data (e.g., Hit, String, CustomType)
|
||||||
type SuggestionData: Clone + Send + 'static;
|
type SuggestionData: Clone + Send + 'static;
|
||||||
@@ -92,34 +94,39 @@ pub trait AutocompleteCanvasState: CanvasState {
|
|||||||
fn should_trigger_autocomplete(&self) -> bool {
|
fn should_trigger_autocomplete(&self) -> bool {
|
||||||
let current_input = self.get_current_input();
|
let current_input = self.get_current_input();
|
||||||
let current_field = self.current_field();
|
let current_field = self.current_field();
|
||||||
|
|
||||||
self.supports_autocomplete(current_field) &&
|
self.supports_autocomplete(current_field) &&
|
||||||
current_input.len() >= 2 && // Default: trigger after 2 chars
|
current_input.len() >= 2 && // Default: trigger after 2 chars
|
||||||
!self.is_autocomplete_active()
|
!self.is_autocomplete_active()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **USER MUST IMPLEMENT**: Trigger autocomplete suggestions (async)
|
/// **USER MUST IMPLEMENT**: Trigger autocomplete suggestions (async)
|
||||||
/// This is where you implement your API calls, caching, etc.
|
/// This is where you implement your API calls, caching, etc.
|
||||||
///
|
///
|
||||||
/// # Example Implementation:
|
/// # Example Implementation:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// async fn trigger_autocomplete_suggestions(&mut self) {
|
/// #[async_trait]
|
||||||
/// self.activate_autocomplete(); // Show loading state
|
/// impl AutocompleteCanvasState for MyState {
|
||||||
|
/// type SuggestionData = MyData;
|
||||||
///
|
///
|
||||||
/// let query = self.get_current_input().to_string();
|
/// async fn trigger_autocomplete_suggestions(&mut self) {
|
||||||
/// let suggestions = my_api.search(&query).await.unwrap_or_default();
|
/// self.activate_autocomplete(); // Show loading state
|
||||||
///
|
///
|
||||||
/// self.set_autocomplete_suggestions(suggestions);
|
/// let query = self.get_current_input().to_string();
|
||||||
|
/// let suggestions = my_api.search(&query).await.unwrap_or_default();
|
||||||
|
///
|
||||||
|
/// self.set_autocomplete_suggestions(suggestions);
|
||||||
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
async fn trigger_autocomplete_suggestions(&mut self) {
|
async fn trigger_autocomplete_suggestions(&mut self) {
|
||||||
// Activate autocomplete UI
|
// Activate autocomplete UI
|
||||||
self.activate_autocomplete();
|
self.activate_autocomplete();
|
||||||
|
|
||||||
// Default: just show loading state
|
// Default: just show loading state
|
||||||
// User should override this to do actual async fetching
|
// User should override this to do actual async fetching
|
||||||
self.set_autocomplete_loading(true);
|
self.set_autocomplete_loading(true);
|
||||||
|
|
||||||
// In a real implementation, you'd:
|
// In a real implementation, you'd:
|
||||||
// 1. Get current input: let query = self.get_current_input();
|
// 1. Get current input: let query = self.get_current_input();
|
||||||
// 2. Make API call: let results = api.search(query).await;
|
// 2. Make API call: let results = api.search(query).await;
|
||||||
@@ -157,7 +164,7 @@ pub trait AutocompleteCanvasState: CanvasState {
|
|||||||
// Apply the value to current field
|
// Apply the value to current field
|
||||||
*self.get_current_input_mut() = suggestion.value_to_store.clone();
|
*self.get_current_input_mut() = suggestion.value_to_store.clone();
|
||||||
self.set_has_unsaved_changes(true);
|
self.set_has_unsaved_changes(true);
|
||||||
|
|
||||||
// Clear autocomplete
|
// Clear autocomplete
|
||||||
self.clear_autocomplete_suggestions();
|
self.clear_autocomplete_suggestions();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user