Skip to content

Commit

Permalink
feat: onvalidate function for Input
Browse files Browse the repository at this point in the history
  • Loading branch information
marc2332 committed Jan 20, 2025
1 parent 621f105 commit 54e7442
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 3 deletions.
1 change: 1 addition & 0 deletions crates/components/src/hooks/use_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl<Id: Clone + Hash + Eq + Display> UseForm<Id> {
value,
placeholder: Some(placeholder),
auto_focus: false,
onvalidate: None,
}
}

Expand Down
118 changes: 117 additions & 1 deletion crates/components/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
use std::{
cell::{
Ref,
RefCell,
},
rc::Rc,
};

use dioxus::prelude::*;
use freya_elements::{
elements as dioxus_elements,
Expand Down Expand Up @@ -49,6 +57,35 @@ pub enum InputStatus {
Hovering,
}

#[derive(Clone)]
pub struct InputValidator {
valid: Rc<RefCell<bool>>,
text: Rc<RefCell<String>>,
}

impl InputValidator {
pub fn new(text: String) -> Self {
Self {
valid: Rc::new(RefCell::new(true)),
text: Rc::new(RefCell::new(text)),
}
}

pub fn text(&self) -> Ref<String> {
self.text.borrow()
}

/// Mark the text as valid.
pub fn set_valid(&self, is_valid: bool) {
*self.valid.borrow_mut() = is_valid;
}

/// Check if the text was marked as valid.
pub fn is_valid(&self) -> bool {
*self.valid.borrow()
}
}

/// Properties for the [`Input`] component.
#[derive(Props, Clone, PartialEq)]
pub struct InputProps {
Expand All @@ -66,6 +103,8 @@ pub struct InputProps {
/// Automatically focus this Input upon creation. Default `false`.
#[props(default = false)]
pub auto_focus: bool,
/// Handler for the `onvalidate` function.
pub onvalidate: Option<EventHandler<InputValidator>>,
}

/// Small box to edit text.
Expand Down Expand Up @@ -102,6 +141,7 @@ pub fn Input(
mode,
placeholder,
auto_focus,
onvalidate,
}: InputProps,
) -> Element {
let platform = use_platform();
Expand All @@ -118,6 +158,7 @@ pub fn Input(

if &value != editable.editor().read().rope() {
editable.editor_mut().write().set(&value);
editable.editor_mut().write().editor_history().clear();
}

use_drop(move || {
Expand All @@ -130,7 +171,29 @@ pub fn Input(
if e.data.key != Key::Enter && e.data.key != Key::Tab {
e.stop_propagation();
editable.process_event(&EditableEvent::KeyDown(e.data));
onchange.call(editable.editor().peek().to_string());
let text = editable.editor().peek().to_string();

let apply_change = if let Some(onvalidate) = onvalidate {
let validator = InputValidator::new(text.clone());
onvalidate(validator.clone());
let is_valid = validator.is_valid();

if !is_valid {
let undo_result = editable.editor_mut().write().undo();
if let Some(idx) = undo_result {
editable.editor_mut().write().set_cursor_pos(idx);
}
editable.editor_mut().write().editor_history().clear_redos();
}

is_valid
} else {
true
};

if apply_change {
onchange.call(text);
}
}
};

Expand Down Expand Up @@ -312,4 +375,57 @@ mod test {
// Check that "d" has been written into the input.
assert_eq!(text.get(0).text(), Some("Hello, World"));
}

#[tokio::test]
pub async fn validate() {
fn input_app() -> Element {
let mut value = use_signal(|| "A".to_string());

rsx!(Input {
value: value.read().clone(),
onvalidate: |validator: InputValidator| {
if validator.text().len() > 3 {
validator.set_valid(false)
}
},
onchange: move |new_value| {
value.set(new_value);
}
},)
}

let mut utils = launch_test(input_app);
let root = utils.root();
let text = root.get(0).get(0).get(0).get(0).get(0).get(0);
utils.wait_for_update().await;

// Default value
assert_eq!(text.get(0).text(), Some("A"));

// Focus the input in the end of the text
utils.push_event(PlatformEvent::Mouse {
name: EventName::MouseDown,
cursor: (115., 25.).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
utils.wait_for_update().await;
utils.wait_for_update().await;

assert_ne!(utils.focus_id(), ACCESSIBILITY_ROOT_ID);

// Try to write "BCDEFG"
for c in ['B', 'C', 'D', 'E', 'F', 'G'] {
utils.push_event(PlatformEvent::Keyboard {
name: EventName::KeyDown,
key: Key::Character(c.to_string()),
code: Code::Unidentified,
modifiers: Modifiers::default(),
});
utils.wait_for_update().await;
}

// Check that only "BC" was been written to the input.
assert_eq!(text.get(0).text(), Some("ABC"));
}
}
12 changes: 12 additions & 0 deletions crates/hooks/src/editor_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ impl EditorHistory {
None
}
}

pub fn clear_redos(&mut self) {
if self.can_redo() {
self.changes.drain(self.current_change..);
}
}

pub fn clear(&mut self) {
self.changes.clear();
self.current_change = 0;
self.version = 0;
}
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions crates/hooks/src/rope_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ impl TextEditor for RopeEditor {
self.history.redo(&mut self.rope)
}

fn editor_history(&mut self) -> &mut EditorHistory {
&mut self.history
}

fn get_identation(&self) -> u8 {
self.identation
}
Expand Down
4 changes: 4 additions & 0 deletions crates/hooks/src/text_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use freya_elements::events::keyboard::{
Modifiers,
};

use crate::EditorHistory;

/// Holds the position of a cursor in a text
#[derive(Clone, Default, PartialEq, Debug)]
pub struct TextCursor(usize);
Expand Down Expand Up @@ -498,6 +500,8 @@ pub trait TextEditor {

fn redo(&mut self) -> Option<usize>;

fn editor_history(&mut self) -> &mut EditorHistory;

fn get_selection_range(&self) -> Option<(usize, usize)>;

fn get_identation(&self) -> u8;
Expand Down
9 changes: 7 additions & 2 deletions examples/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn app() -> Element {
"Your name:"
}
Input {
value: values.read().0.clone(),
value: values().0,
placeholder: "Name",
onchange: move |txt| {
values.write().0 = txt;
Expand All @@ -35,8 +35,13 @@ fn app() -> Element {
"Your age:"
}
Input {
value: values.read().1.clone(),
value: values().1,
placeholder: "Age",
onvalidate: |validator: InputValidator| {
if validator.text().parse::<u8>().is_err() {
validator.set_valid(false)
}
},
onchange: move |txt| {
values.write().1 = txt;
}
Expand Down

0 comments on commit 54e7442

Please sign in to comment.