use crate::ComponentTheme;
use crate::theme::ThemeExt;
use gpui::prelude::*;
use gpui::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
const MAX_THREAD_LOCAL_INPUT_STATES: usize = 1000;
thread_local! {
static FOCUS_HANDLES: RefCell<HashMap<ElementId, FocusHandle>> = RefCell::new(HashMap::new());
}
thread_local! {
static EDIT_STATES: RefCell<HashMap<ElementId, Rc<RefCell<EditState>>>> = RefCell::new(HashMap::new());
}
fn trim_thread_local_storage() -> (usize, usize) {
let mut focus_evicted = 0;
let mut edit_evicted = 0;
FOCUS_HANDLES.with(|handles| {
let mut handles = handles.borrow_mut();
while handles.len() > MAX_THREAD_LOCAL_INPUT_STATES {
if let Some(key) = handles.keys().next().cloned() {
handles.remove(&key);
focus_evicted += 1;
}
}
});
EDIT_STATES.with(|states| {
let mut states = states.borrow_mut();
while states.len() > MAX_THREAD_LOCAL_INPUT_STATES {
if let Some(key) = states.keys().next().cloned() {
states.remove(&key);
edit_evicted += 1;
}
}
});
(focus_evicted, edit_evicted)
}
pub fn cleanup_input_state(id: &ElementId) {
FOCUS_HANDLES.with(|handles| {
handles.borrow_mut().remove(id);
});
EDIT_STATES.with(|states| {
states.borrow_mut().remove(id);
});
}
pub fn cleanup_stale_input_states(retained_ids: &std::collections::HashSet<ElementId>) {
FOCUS_HANDLES.with(|handles| {
handles
.borrow_mut()
.retain(|id, _| retained_ids.contains(id));
});
EDIT_STATES.with(|states| {
states
.borrow_mut()
.retain(|id, _| retained_ids.contains(id));
});
}
pub fn input_state_count() -> (usize, usize) {
let _ = trim_thread_local_storage();
let focus_count = FOCUS_HANDLES.with(|handles| handles.borrow().len());
let edit_count = EDIT_STATES.with(|states| states.borrow().len());
(focus_count, edit_count)
}
pub fn clear_all_input_states() {
FOCUS_HANDLES.with(|handles| {
handles.borrow_mut().clear();
});
EDIT_STATES.with(|states| {
states.borrow_mut().clear();
});
}
#[derive(Debug, Clone, ComponentTheme)]
pub struct InputTheme {
#[theme(default = 0x1e1e1e, from = background)]
pub background: Rgba,
#[theme(default = 0x2a2a2a, from = surface)]
pub filled_bg: Rgba,
#[theme(default = 0xffffff, from = text_primary)]
pub text: Rgba,
#[theme(default = 0x666666, from = text_muted)]
pub placeholder: Rgba,
#[theme(default = 0xcccccc, from = text_secondary)]
pub label: Rgba,
#[theme(default = 0x3a3a3a, from = border)]
pub border: Rgba,
#[theme(default = 0x007acc, from = accent)]
pub border_hover: Rgba,
#[theme(default = 0x007acc, from = accent)]
pub border_focus: Rgba,
#[theme(default = 0xcc3333, from = error)]
pub error: Rgba,
#[theme(default = 0x007acc, from = accent)]
pub cursor: Rgba,
#[theme(
default = 0x007acc44,
from_expr = "Rgba { r: theme.accent.r, g: theme.accent.g, b: theme.accent.b, a: 0.3 }"
)]
pub selection_bg: Rgba,
#[theme(default = 0x00000000, from = transparent)]
pub transparent: Rgba,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InputSize {
Sm,
#[default]
Md,
Lg,
}
impl From<crate::ComponentSize> for InputSize {
fn from(size: crate::ComponentSize) -> Self {
match size {
crate::ComponentSize::Xs | crate::ComponentSize::Sm => Self::Sm,
crate::ComponentSize::Md => Self::Md,
crate::ComponentSize::Lg | crate::ComponentSize::Xl => Self::Lg,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InputVariant {
#[default]
Default,
Filled,
Flushed,
}
#[derive(Clone, Default)]
struct EditState {
editing: bool,
text: String,
cursor: usize,
selection_anchor: Option<usize>,
is_dragging: bool,
}
impl EditState {
fn new(value: &str) -> Self {
let len = value.chars().count();
Self {
editing: true,
text: value.to_string(),
cursor: len,
selection_anchor: Some(0), is_dragging: false,
}
}
#[allow(dead_code)]
fn has_selection(&self) -> bool {
if let Some(anchor) = self.selection_anchor {
anchor != self.cursor
} else {
false
}
}
fn selection_range(&self) -> Option<(usize, usize)> {
self.selection_anchor.map(|anchor| {
let start = anchor.min(self.cursor);
let end = anchor.max(self.cursor);
(start, end)
})
}
#[allow(dead_code)]
fn is_all_selected(&self) -> bool {
if let Some((start, end)) = self.selection_range() {
start == 0 && end == self.text.chars().count()
} else {
false
}
}
fn get_selected_text(&self) -> Option<String> {
if let Some((start, end)) = self.selection_range()
&& start != end
{
let chars: Vec<char> = self.text.chars().collect();
return Some(chars[start..end].iter().collect());
}
None
}
fn clear_selection(&mut self) {
self.selection_anchor = None;
}
fn move_to_start(&mut self) {
self.cursor = 0;
self.clear_selection();
}
fn move_to_end(&mut self) {
self.cursor = self.text.chars().count();
self.clear_selection();
}
fn move_forward(&mut self) {
let len = self.text.chars().count();
if self.cursor < len {
self.cursor += 1;
}
self.clear_selection();
}
fn move_backward(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
self.clear_selection();
}
fn select_all(&mut self) {
self.selection_anchor = Some(0);
self.cursor = self.text.chars().count();
}
fn kill_to_end(&mut self) {
let chars: Vec<char> = self.text.chars().collect();
self.text = chars[..self.cursor].iter().collect();
self.clear_selection();
}
fn kill_to_start(&mut self) {
let chars: Vec<char> = self.text.chars().collect();
self.text = chars[self.cursor..].iter().collect();
self.cursor = 0;
self.clear_selection();
}
fn kill_word_backward(&mut self) {
if self.cursor == 0 {
return;
}
let chars: Vec<char> = self.text.chars().collect();
let mut new_pos = self.cursor;
while new_pos > 0 && chars[new_pos - 1].is_whitespace() {
new_pos -= 1;
}
while new_pos > 0 && !chars[new_pos - 1].is_whitespace() {
new_pos -= 1;
}
let mut new_chars = chars[..new_pos].to_vec();
new_chars.extend_from_slice(&chars[self.cursor..]);
self.text = new_chars.into_iter().collect();
self.cursor = new_pos;
self.clear_selection();
}
fn delete_selection(&mut self) -> bool {
if let Some((start, end)) = self.selection_range()
&& start != end
{
let chars: Vec<char> = self.text.chars().collect();
let mut new_chars = chars[..start].to_vec();
new_chars.extend_from_slice(&chars[end..]);
self.text = new_chars.into_iter().collect();
self.cursor = start;
self.clear_selection();
return true;
}
false
}
fn do_backspace(&mut self) {
if self.delete_selection() {
return;
}
if self.cursor > 0 {
let byte_pos = self
.text
.char_indices()
.nth(self.cursor - 1)
.map(|(i, _)| i)
.unwrap_or(0);
let next_byte = self
.text
.char_indices()
.nth(self.cursor)
.map(|(i, _)| i)
.unwrap_or(self.text.len());
self.text.replace_range(byte_pos..next_byte, "");
self.cursor -= 1;
}
}
fn do_delete(&mut self) {
if self.delete_selection() {
return;
}
let len = self.text.chars().count();
if self.cursor < len {
let byte_pos = self
.text
.char_indices()
.nth(self.cursor)
.map(|(i, _)| i)
.unwrap_or(self.text.len());
let next_byte = self
.text
.char_indices()
.nth(self.cursor + 1)
.map(|(i, _)| i)
.unwrap_or(self.text.len());
self.text.replace_range(byte_pos..next_byte, "");
}
}
fn insert_text(&mut self, char_text: &str) {
self.delete_selection();
let byte_pos = self
.text
.char_indices()
.nth(self.cursor)
.map(|(i, _)| i)
.unwrap_or(self.text.len());
self.text.insert_str(byte_pos, char_text);
self.cursor += char_text.chars().count();
}
fn start_selection(&mut self, pos: usize) {
self.cursor = pos;
self.selection_anchor = Some(pos);
self.is_dragging = true;
}
fn select_word_at(&mut self, pos: usize) {
let text = &self.text;
let len = text.chars().count();
if len == 0 {
return;
}
let pos = pos.min(len);
let chars: Vec<char> = text.chars().collect();
let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
let mut start = pos;
if start < len && !is_word_char(chars[start]) && start > 0 && is_word_char(chars[start - 1])
{
start -= 1;
}
let target_is_word = start < len && is_word_char(chars[start]);
while start > 0 {
let prev = chars[start - 1];
if is_word_char(prev) != target_is_word {
break;
}
start -= 1;
}
let mut end = pos;
if end < start {
end = start;
}
while end < len {
let curr = chars[end];
if is_word_char(curr) != target_is_word {
break;
}
end += 1;
}
self.selection_anchor = Some(start);
self.cursor = end;
}
fn update_selection(&mut self, pos: usize) {
self.cursor = pos;
}
fn end_selection(&mut self) {
self.is_dragging = false;
if let Some(anchor) = self.selection_anchor
&& anchor == self.cursor
{
self.selection_anchor = None;
}
}
}
pub struct Input {
id: ElementId,
value: SharedString,
placeholder: Option<SharedString>,
label: Option<SharedString>,
size: InputSize,
variant: InputVariant,
disabled: bool,
readonly: bool,
error: Option<SharedString>,
icon_left: Option<SharedString>,
icon_right: Option<SharedString>,
bg_color: Option<Rgba>,
text_color: Option<Rgba>,
border_color: Option<Rgba>,
placeholder_color: Option<Rgba>,
on_change: Option<Box<dyn Fn(&str, &mut Window, &mut App) + 'static>>,
on_edit_start: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
on_edit_end: Option<Box<dyn Fn(Option<String>, &mut Window, &mut App) + 'static>>,
on_text_change: Option<Box<dyn Fn(String, &mut Window, &mut App) + 'static>>,
focus_handle: Option<FocusHandle>,
}
impl Input {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
value: "".into(),
placeholder: None,
label: None,
size: InputSize::default(),
variant: InputVariant::default(),
disabled: false,
readonly: false,
error: None,
icon_left: None,
icon_right: None,
bg_color: None,
text_color: None,
border_color: None,
placeholder_color: None,
on_change: None,
on_edit_start: None,
on_edit_end: None,
on_text_change: None,
focus_handle: None,
}
}
pub fn focus_handle(mut self, handle: FocusHandle) -> Self {
self.focus_handle = Some(handle);
self
}
pub fn value(mut self, value: impl Into<SharedString>) -> Self {
self.value = value.into();
self
}
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.placeholder = Some(placeholder.into());
self
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn size(mut self, size: InputSize) -> Self {
self.size = size;
self
}
pub fn variant(mut self, variant: InputVariant) -> Self {
self.variant = variant;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn readonly(mut self, readonly: bool) -> Self {
self.readonly = readonly;
self
}
pub fn error(mut self, error: impl Into<SharedString>) -> Self {
self.error = Some(error.into());
self
}
pub fn icon_left(mut self, icon: impl Into<SharedString>) -> Self {
self.icon_left = Some(icon.into());
self
}
pub fn icon_right(mut self, icon: impl Into<SharedString>) -> Self {
self.icon_right = Some(icon.into());
self
}
pub fn bg_color(mut self, color: impl Into<Rgba>) -> Self {
self.bg_color = Some(color.into());
self
}
pub fn text_color(mut self, color: impl Into<Rgba>) -> Self {
self.text_color = Some(color.into());
self
}
pub fn border_color(mut self, color: impl Into<Rgba>) -> Self {
self.border_color = Some(color.into());
self
}
pub fn placeholder_color(mut self, color: impl Into<Rgba>) -> Self {
self.placeholder_color = Some(color.into());
self
}
pub fn on_change(mut self, handler: impl Fn(&str, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Box::new(handler));
self
}
pub fn on_edit_start(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_edit_start = Some(Box::new(handler));
self
}
pub fn on_edit_end(
mut self,
handler: impl Fn(Option<String>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_edit_end = Some(Box::new(handler));
self
}
pub fn on_text_change(
mut self,
handler: impl Fn(String, &mut Window, &mut App) + 'static,
) -> Self {
self.on_text_change = Some(Box::new(handler));
self
}
}
impl RenderOnce for Input {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let global_theme = cx.theme();
let theme = InputTheme::from(&global_theme);
let (py, _text_size_class) = match self.size {
InputSize::Sm => (px(4.0), "text_xs"),
InputSize::Md => (px(8.0), "text_sm"),
InputSize::Lg => (px(12.0), "text_base"),
};
let has_error = self.error.is_some();
let disabled = self.disabled;
let readonly = self.readonly;
let current_value = self.value.clone();
let focus_handle = self.focus_handle.unwrap_or_else(|| {
FOCUS_HANDLES.with(|handles| {
let mut handles = handles.borrow_mut();
handles
.entry(self.id.clone())
.or_insert_with(|| cx.focus_handle())
.clone()
})
});
let is_focused = focus_handle.is_focused(window);
let editing = is_focused && !disabled && !readonly;
let edit_state = EDIT_STATES.with(|states| {
let mut states = states.borrow_mut();
states
.entry(self.id.clone())
.or_insert_with(|| Rc::new(RefCell::new(EditState::default())))
.clone()
});
let state = edit_state.borrow();
let selection_anchor = if editing {
state.selection_anchor
} else {
None
};
let cursor_pos = state.cursor;
let _is_dragging = state.is_dragging;
let edit_text = if editing && state.editing {
state.text.clone()
} else {
current_value.to_string()
};
drop(state);
let border_color = if has_error {
theme.error
} else if editing {
theme.border_focus
} else {
self.border_color.unwrap_or(theme.border)
};
let mut container = div().flex().flex_col().gap_1();
if let Some(label) = &self.label {
container = container.child(
div()
.text_sm()
.text_color(theme.label)
.font_weight(FontWeight::MEDIUM)
.child(label.clone()),
);
}
let field_id = ElementId::Name(SharedString::from(format!("{:?}-field", self.id)));
let mut input_wrapper = div()
.id(self.id.clone())
.track_focus(&focus_handle)
.flex()
.items_center()
.gap_2()
.px_3()
.py(py)
.rounded_md()
.border_1()
.border_color(border_color)
.focusable();
match self.variant {
InputVariant::Default => {
input_wrapper = input_wrapper.bg(self.bg_color.unwrap_or(theme.background));
}
InputVariant::Filled => {
input_wrapper = input_wrapper
.bg(self.bg_color.unwrap_or(theme.filled_bg))
.border_color(theme.transparent);
}
InputVariant::Flushed => {
input_wrapper = input_wrapper
.bg(theme.transparent)
.border_0()
.border_b_1()
.border_color(border_color)
.rounded_none();
}
}
let border_hover = theme.border_hover;
if disabled {
input_wrapper = input_wrapper.opacity(0.5).cursor_not_allowed();
} else if !readonly {
input_wrapper = input_wrapper
.cursor_text()
.hover(move |s| s.border_color(border_hover));
}
let placeholder_color = self.placeholder_color.unwrap_or(theme.placeholder);
let text_color = self.text_color.unwrap_or(theme.text);
let selection_bg = theme.selection_bg;
let cursor_color = theme.cursor;
let on_change_rc = self.on_change.map(Rc::new);
let on_edit_start_rc = self.on_edit_start.map(Rc::new);
let on_edit_end_rc = self.on_edit_end.map(Rc::new);
let on_text_change_rc = self.on_text_change.map(Rc::new);
if !disabled && !readonly {
let focus_handle_for_click = focus_handle.clone();
let edit_state_for_click = edit_state.clone();
let value_for_click = current_value.to_string();
let on_edit_start_click = on_edit_start_rc.clone();
let edit_text_for_click = edit_text.clone();
input_wrapper =
input_wrapper.on_mouse_down(MouseButton::Left, move |event, window, cx| {
window.focus(&focus_handle_for_click);
let mut state = edit_state_for_click.borrow_mut();
let text_len = edit_text_for_click.chars().count();
let char_width = 8.0_f32; let click_x: f32 = event.position.x.into();
let char_pos = ((click_x / char_width).round() as usize).min(text_len);
if event.click_count == 2 {
if !state.editing {
*state = EditState::new(&value_for_click);
}
state.select_word_at(char_pos);
drop(state);
window.refresh();
return;
}
if !state.editing {
*state = EditState::new(&value_for_click);
state.cursor = char_pos;
state.clear_selection();
drop(state);
if let Some(ref handler) = on_edit_start_click {
handler(window, cx);
}
} else {
state.start_selection(char_pos);
drop(state);
}
window.refresh();
});
let edit_state_for_move = edit_state.clone();
let edit_text_for_move = edit_text.clone();
input_wrapper = input_wrapper.on_mouse_move(move |event, window, _cx| {
let mut state = edit_state_for_move.borrow_mut();
if state.is_dragging && state.editing {
let text_len = edit_text_for_move.chars().count();
let char_width = 8.0_f32;
let move_x: f32 = event.position.x.into();
let char_pos = ((move_x / char_width).round() as usize).min(text_len);
state.update_selection(char_pos);
drop(state);
window.refresh();
}
});
let edit_state_for_up = edit_state.clone();
input_wrapper =
input_wrapper.on_mouse_up(MouseButton::Left, move |_event, window, _cx| {
let mut state = edit_state_for_up.borrow_mut();
if state.is_dragging {
state.end_selection();
drop(state);
window.refresh();
}
});
}
if !disabled && !readonly {
let edit_state_for_key = edit_state.clone();
let on_edit_end_key = on_edit_end_rc.clone();
let on_text_change_key = on_text_change_rc.clone();
let on_change_key = on_change_rc.clone();
let focus_handle_for_key = focus_handle.clone();
let current_value_for_key = current_value.to_string();
input_wrapper = input_wrapper.on_key_down(move |event, window, cx| {
if !focus_handle_for_key.is_focused(window) {
return;
}
cx.stop_propagation();
let key = event.keystroke.key.as_str();
let ctrl = event.keystroke.modifiers.control;
let cmd = event.keystroke.modifiers.platform;
let mut state = edit_state_for_key.borrow_mut();
if !state.editing {
state.text = current_value_for_key.clone();
state.editing = true;
state.cursor = state.text.chars().count();
state.selection_anchor = Some(0);
}
if cmd {
match key {
"c" => {
if let Some(selected) = state.get_selected_text() {
drop(state);
cx.write_to_clipboard(ClipboardItem::new_string(selected));
}
return;
}
"x" => {
if let Some(selected) = state.get_selected_text() {
cx.write_to_clipboard(ClipboardItem::new_string(selected));
state.delete_selection();
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
}
return;
}
"v" => {
if let Some(clipboard) = cx.read_from_clipboard()
&& let Some(paste_text) = clipboard.text()
{
state.insert_text(&paste_text);
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
}
return;
}
"a" => {
state.select_all();
drop(state);
window.refresh();
return;
}
_ => {}
}
}
if ctrl {
match key {
"a" => state.move_to_start(),
"e" => state.move_to_end(),
"k" => state.kill_to_end(),
"u" => state.kill_to_start(),
"w" => state.kill_word_backward(),
"h" => state.do_backspace(),
"d" => state.do_delete(),
"f" => state.move_forward(),
"b" => state.move_backward(),
_ => {}
}
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
return;
}
match key {
"enter" => {
let text = state.text.clone();
state.editing = false;
state.clear_selection();
drop(state);
window.blur();
if let Some(ref handler) = on_change_key {
handler(&text, window, cx);
}
if let Some(ref handler) = on_edit_end_key {
handler(Some(text), window, cx);
}
}
"escape" => {
state.editing = false;
state.clear_selection();
drop(state);
window.blur();
if let Some(ref handler) = on_edit_end_key {
handler(None, window, cx);
}
}
"backspace" => {
state.do_backspace();
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
}
"delete" => {
state.do_delete();
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
}
"left" => {
state.move_backward();
drop(state);
window.refresh();
}
"right" => {
state.move_forward();
drop(state);
window.refresh();
}
"home" => {
state.move_to_start();
drop(state);
window.refresh();
}
"end" => {
state.move_to_end();
drop(state);
window.refresh();
}
_ => {
if let Some(char_text) = event.keystroke.key_char.as_ref() {
state.insert_text(char_text);
let text = state.text.clone();
drop(state);
if let Some(ref handler) = on_text_change_key {
handler(text, window, cx);
}
window.refresh();
}
}
}
});
}
if let Some(icon) = &self.icon_left {
input_wrapper =
input_wrapper.child(div().text_color(placeholder_color).child(icon.clone()));
}
let display_text = if editing {
edit_text
} else if current_value.is_empty() {
self.placeholder
.as_ref()
.map(|s| s.to_string())
.unwrap_or_default()
} else {
current_value.to_string()
};
let mut text_el = div().id(field_id).flex_1().flex().items_center();
text_el = match self.size {
InputSize::Sm => text_el.text_xs(),
InputSize::Md => text_el.text_sm(),
InputSize::Lg => text_el,
};
let cursor_el = || {
div()
.w(px(1.5))
.h(px(14.0)) .bg(cursor_color)
};
if editing {
let chars: Vec<char> = display_text.chars().collect();
let len = chars.len();
let (sel_start, sel_end) = if let Some(anchor) = selection_anchor {
(cursor_pos.min(anchor), cursor_pos.max(anchor))
} else {
(cursor_pos, cursor_pos)
};
let part1_end = sel_start;
let part2_end = sel_end;
let part1: String = chars[0..part1_end].iter().collect();
let part2: String = chars[part1_end..part2_end].iter().collect();
let part3: String = chars[part2_end..len].iter().collect();
if !part1.is_empty() {
text_el = text_el.child(div().text_color(text_color).child(part1));
}
if cursor_pos == sel_start {
text_el = text_el.child(cursor_el());
}
if !part2.is_empty() {
text_el = text_el.child(div().bg(selection_bg).text_color(text_color).child(part2));
}
if cursor_pos == sel_end && cursor_pos != sel_start {
text_el = text_el.child(cursor_el());
} else if cursor_pos == sel_end && sel_start == sel_end {
}
if !part3.is_empty() {
text_el = text_el.child(div().text_color(text_color).child(part3));
}
} else if !editing && current_value.is_empty() {
text_el = text_el.text_color(placeholder_color).child(display_text);
} else {
text_el = text_el.text_color(text_color).child(display_text);
}
input_wrapper = input_wrapper.child(text_el);
if let Some(icon) = &self.icon_right {
input_wrapper =
input_wrapper.child(div().text_color(placeholder_color).child(icon.clone()));
}
container = container.child(input_wrapper);
if let Some(error) = &self.error {
container =
container.child(div().text_xs().text_color(theme.error).child(error.clone()));
}
container
}
}
impl IntoElement for Input {
type Element = gpui::Component<Self>;
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}
}