Add pattern and validate to Input for limit input format. (#59)

This commit is contained in:
Jason Lee 2024-07-23 15:36:08 +08:00 committed by GitHub
parent 3ec35929ae
commit db14f0a2e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 54 additions and 6 deletions

2
Cargo.lock generated
View file

@ -5119,6 +5119,7 @@ dependencies = [
"fake", "fake",
"gpui", "gpui",
"image", "image",
"regex",
"serde", "serde",
"ui", "ui",
"workspace", "workspace",
@ -5696,6 +5697,7 @@ dependencies = [
"once_cell", "once_cell",
"paste", "paste",
"raw-window-handle 0.6.2", "raw-window-handle 0.6.2",
"regex",
"resvg", "resvg",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -13,6 +13,7 @@ workspace.workspace = true
charts-rs = "0.3" charts-rs = "0.3"
image = "0.25.1" image = "0.25.1"
wry = "0" wry = "0"
regex = "1"
[lints] [lints]
workspace = true workspace = true

View file

@ -75,7 +75,8 @@ impl InputStory {
let suffix_input1 = cx.new_view(|cx| { let suffix_input1 = cx.new_view(|cx| {
TextInput::new(cx) TextInput::new(cx)
.suffix(|_| IconName::Info) .suffix(|_| IconName::Info)
.placeholder("Info here...") .placeholder("This input only support [a-zA-Z0-9] characters.")
.pattern(regex::Regex::new(r"^[a-zA-Z0-9]*$").unwrap())
.cleanable(true) .cleanable(true)
}); });
let both_input1 = cx.new_view(|cx| { let both_input1 = cx.new_view(|cx| {
@ -114,7 +115,8 @@ impl InputStory {
small_input: cx.new_view(|cx| { small_input: cx.new_view(|cx| {
TextInput::new(cx) TextInput::new(cx)
.size(Size::Small) .size(Size::Small)
.placeholder("Small input") .validate(|s| s.parse::<f32>().is_ok())
.placeholder("validate to limit float number.")
}), }),
prefix_input1, prefix_input1,
suffix_input1, suffix_input1,

View file

@ -28,6 +28,7 @@ raw-window-handle = "0.6.2"
winit = "0.30.3" winit = "0.30.3"
wry = "0" wry = "0"
smol = "1" smol = "1"
regex = "1"
[lints] [lints]
workspace = true workspace = true

View file

@ -127,6 +127,8 @@ pub struct TextInput {
appearance: bool, appearance: bool,
cleanable: bool, cleanable: bool,
size: Size, size: Size,
pattern: Option<regex::Regex>,
validate: Option<Box<dyn Fn(&str) -> bool + 'static>>,
} }
impl EventEmitter<InputEvent> for TextInput {} impl EventEmitter<InputEvent> for TextInput {}
@ -157,6 +159,8 @@ impl TextInput {
prefix: None, prefix: None,
suffix: None, suffix: None,
size: Size::Medium, size: Size::Medium,
pattern: None,
validate: None,
}; };
// Observe the blink cursor to repaint the view when it changes. // Observe the blink cursor to repaint the view when it changes.
@ -268,6 +272,18 @@ impl TextInput {
self self
} }
/// Set the regular expression pattern of the input field.
pub fn pattern(mut self, pattern: regex::Regex) -> Self {
self.pattern = Some(pattern);
self
}
/// Set the validation function of the input field.
pub fn validate(mut self, f: impl Fn(&str) -> bool + 'static) -> Self {
self.validate = Some(Box::new(f));
self
}
/// Set true to show indicator at the input right. /// Set true to show indicator at the input right.
pub fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) { pub fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) {
self.loading = loading; self.loading = loading;
@ -619,6 +635,23 @@ impl TextInput {
fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) { fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) {
self.pause_blink_cursor(cx) self.pause_blink_cursor(cx)
} }
fn is_valid_input(&self, new_text: &str) -> bool {
if new_text.is_empty() {
return true;
}
if let Some(validate) = &self.validate {
if !validate(new_text) {
return false;
}
}
self.pattern
.as_ref()
.map(|p| p.is_match(new_text))
.unwrap_or(true)
}
} }
impl ViewInputHandler for TextInput { impl ViewInputHandler for TextInput {
@ -661,9 +694,14 @@ impl ViewInputHandler for TextInput {
.or(self.marked_range.clone()) .or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone()); .unwrap_or(self.selected_range.clone());
self.push_history(&range, new_text, cx); let pending_text: SharedString =
self.text =
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into(); (self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into();
if !self.is_valid_input(&pending_text) {
return;
}
self.push_history(&range, new_text, cx);
self.text = pending_text;
self.selected_range = range.start + new_text.len()..range.start + new_text.len(); self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take(); self.marked_range.take();
cx.emit(InputEvent::Change(self.text.clone())); cx.emit(InputEvent::Change(self.text.clone()));
@ -686,10 +724,14 @@ impl ViewInputHandler for TextInput {
.map(|range_utf16| self.range_from_utf16(range_utf16)) .map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone()) .or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone()); .unwrap_or(self.selected_range.clone());
let pending_text: SharedString =
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into();
if !self.is_valid_input(&pending_text) {
return;
}
self.push_history(&range, new_text, cx); self.push_history(&range, new_text, cx);
self.text = self.text = pending_text;
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into();
self.marked_range = Some(range.start..range.start + new_text.len()); self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16 self.selected_range = new_selected_range_utf16
.as_ref() .as_ref()