Freya provides a powerful low-level text editing system through the use_editable hook and freya-edit crate. This guide will help you understand how to implement text editing functionality in your applications.
For most use cases, you should use the pre-built Input component instead of working with the editable system directly. This guide covers the low-level API for custom text editing implementations.
The easiest way to add text input to your app:
use freya::prelude::*;
fn app() -> impl IntoElement {
let mut value = use_state(|| String::from(""));
Input::new()
.value(value.read().clone())
.on_change(move |new_value| {
value.set(new_value);
})
}
Low-Level Text Editing
Basic Editable Text
Implement a simple editable text field using the low-level API:
use freya::{
prelude::*,
text_edit::*,
};
fn app() -> impl IntoElement {
let holder = use_state(ParagraphHolder::default);
let mut editable = use_editable(|| "Hello, World!".to_string(), EditableConfig::new);
let focus = use_focus();
paragraph()
.a11y_id(focus.a11y_id())
.cursor_index(editable.editor().read().cursor_pos())
.highlights(
editable
.editor()
.read()
.get_selection()
.map(|selection| vec![selection])
.unwrap_or_default(),
)
.on_mouse_down(move |e: Event<MouseEventData>| {
focus.request_focus();
editable.process_event(EditableEvent::Down {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_mouse_move(move |e: Event<MouseEventData>| {
editable.process_event(EditableEvent::Move {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_global_mouse_up(move |_| editable.process_event(EditableEvent::Release))
.on_key_down(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyDown {
key: &e.key,
modifiers: e.modifiers,
});
})
.on_key_up(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyUp { key: &e.key });
})
.span(editable.editor().read().to_string())
.holder(holder.read().clone())
}
Editable System Components
UseEditable Hook
The use_editable hook creates an editable text instance:
let mut editable = use_editable(
|| "Initial content".to_string(), // Initial text
EditableConfig::new // Configuration
);
EditableConfig
Configure the editable behavior:
let config = EditableConfig {
indentation: Indentation::Spaces(4), // Tab behavior
};
let mut editable = use_editable(
|| String::new(),
|| config
);
Accessing the Editor
The editor state can be accessed through the editable:
// Read the current text
let text = editable.editor().read().to_string();
// Get cursor position
let cursor_pos = editable.editor().read().cursor_pos();
// Get text selection
let selection = editable.editor().read().get_selection();
Editable Events
The editable system processes various events:
Mouse Events
Mouse Down - Start text selection:
.on_mouse_down(move |e: Event<MouseEventData>| {
editable.process_event(EditableEvent::Down {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
Mouse Move - Update selection:
.on_mouse_move(move |e: Event<MouseEventData>| {
editable.process_event(EditableEvent::Move {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
Mouse Up - End selection:
.on_global_mouse_up(move |_| {
editable.process_event(EditableEvent::Release)
})
Keyboard Events
Key Down - Handle key presses:
.on_key_down(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyDown {
key: &e.key,
modifiers: e.modifiers,
});
})
Key Up - Handle key releases:
.on_key_up(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyUp { key: &e.key });
})
Text Selection and Highlighting
Display text selection:
paragraph()
.cursor_index(editable.editor().read().cursor_pos())
.highlights(
editable
.editor()
.read()
.get_selection()
.map(|selection| vec![selection])
.unwrap_or_default(),
)
.span(editable.editor().read().to_string())
Focus Management
Manage focus for the editable field:
let focus = use_focus();
paragraph()
.a11y_id(focus.a11y_id()) // Associate with accessibility ID
.on_mouse_down(move |_| {
focus.request_focus(); // Request focus on click
})
Editor Operations
Programmatic Text Manipulation
Modify text programmatically:
use freya::prelude::*;
#[derive(PartialEq)]
struct EditorDemo;
impl Component for EditorDemo {
fn render(&self) -> impl IntoElement {
let mut editable = use_editable(
|| "Edit me".to_string(),
EditableConfig::new
);
let clear_text = move || {
editable.editor_mut().write().clear();
};
let append_text = move |text: &str| {
let mut editor = editable.editor_mut().write();
editor.insert_text(text);
};
rect()
.spacing(8.)
.child(Button::new().text("Clear").on_click(clear_text))
.child(
Button::new()
.text("Append")
.on_click(move |_| append_text(" - appended"))
)
}
}
Multi-line Text Editing
For multi-line text areas, use EditorLine::MultiParagraph:
editable.process_event(EditableEvent::Down {
location: e.element_location,
editor_line: EditorLine::MultiParagraph, // Support multiple lines
holder: &holder.read(),
});
Code Editor Example
For a full-featured code editor, use the CodeEditor component:
use freya::prelude::*;
fn app() -> impl IntoElement {
let content = use_state(|| "fn main() {\n println!(\"Hello!\");\n}".to_string());
CodeEditor::new(
content.read().clone(),
SyntaxTheme::default(),
)
.language(Language::Rust)
.on_change(move |new_content| {
content.set(new_content);
})
}
Clipboard Integration
The editable system automatically handles clipboard operations:
- Ctrl/Cmd + C - Copy selected text
- Ctrl/Cmd + X - Cut selected text
- Ctrl/Cmd + V - Paste from clipboard
Common Patterns
Create a controlled input with external state:
fn controlled_input() -> impl IntoElement {
let mut value = use_state(|| String::new());
Input::new()
.value(value.read().clone())
.on_change(move |new_value| {
// Validate or transform input
let sanitized = new_value.trim().to_string();
value.set(sanitized);
})
}
Input::new()
.value(password.read().clone())
.input_type(InputType::Password) // Mask characters
.on_change(move |new_value| {
password.set(new_value);
})
Text Input with Validation
fn validated_input() -> impl IntoElement {
let mut value = use_state(|| String::new());
let mut error = use_state(|| None::<String>);
let validate = move |text: String| {
if text.len() < 3 {
error.set(Some("Must be at least 3 characters".to_string()));
} else {
error.set(None);
}
value.set(text);
};
rect()
.child(
Input::new()
.value(value.read().clone())
.on_change(validate)
)
.child(
error.read().as_ref().map(|err| {
label().text(err.clone()).color((255, 0, 0))
})
)
}
fn number_input() -> impl IntoElement {
let mut value = use_state(|| 0i32);
Input::new()
.value(value.read().to_string())
.on_change(move |text| {
if let Ok(num) = text.parse::<i32>() {
value.set(num);
}
})
}
Best Practices
-
Use the Input component - Unless you need custom behavior, use the built-in
Input component
-
Handle focus properly - Always manage focus for editable fields to ensure proper keyboard event handling
-
Validate input - Implement validation in the
on_change callback
-
Provide feedback - Show visual feedback for invalid input or successful operations
-
Accessibility - Use proper accessibility IDs with
a11y_id()
-
Performance - For large text documents, consider using the
CodeEditor component which is optimized for performance
Advanced Features
Undo/Redo
The editor includes built-in undo/redo support:
- Ctrl/Cmd + Z - Undo
- Ctrl/Cmd + Shift + Z or Ctrl/Cmd + Y - Redo
Custom Key Bindings
Intercept key events for custom behavior:
.on_key_down(move |e: Event<KeyboardEventData>| {
// Custom key handling
if e.modifiers.ctrl() && e.key == Key::S {
// Handle Ctrl+S (save)
return;
}
// Default handling
editable.process_event(EditableEvent::KeyDown {
key: &e.key,
modifiers: e.modifiers,
});
})
Next Steps