feat: enhance input component and task modal UX
- Text selection & clipboard: Add full text selection support with Ctrl+A/C/X/V operations, visual highlight, and proper cursor positioning - Multiple annotations: Fix annotation creation to properly append multiple entries instead of overwriting - Navigation improvements: Improve annotation list navigation to cycle between items and input field - UI layout fixes: Add flex wrapping to tag displays to handle long lists gracefully - Focus management: Fix focus retention when switching between edit/view modes in task modal - Tag suggestions: Prevent auto-acceptance of suggestions on Enter, allowing custom tag input
This commit is contained in:
+249
-26
@@ -14,6 +14,7 @@ pub struct Input {
|
||||
placeholder: gpui::SharedString,
|
||||
|
||||
cursor_pos: usize,
|
||||
selection_anchor: Option<usize>,
|
||||
multiline: bool,
|
||||
|
||||
suggestions: Vec<Suggestion>,
|
||||
@@ -39,6 +40,7 @@ impl Input {
|
||||
placeholder: placeholder.into(),
|
||||
|
||||
cursor_pos: 0,
|
||||
selection_anchor: None,
|
||||
multiline: false,
|
||||
|
||||
suggestions: vec![],
|
||||
@@ -101,6 +103,11 @@ impl Input {
|
||||
self.suggestions_open
|
||||
}
|
||||
|
||||
pub fn close_suggestions(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
self.suggestions_open = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, value: impl Into<String>, cx: &mut gpui::Context<Self>) {
|
||||
self.value = value.into();
|
||||
self.cursor_pos = self.value.len();
|
||||
@@ -130,6 +137,67 @@ impl Input {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn has_selection(&self) -> bool {
|
||||
self.selection_anchor.is_some()
|
||||
}
|
||||
|
||||
fn selection_range(&self) -> Option<std::ops::Range<usize>> {
|
||||
self.selection_anchor.map(|anchor| {
|
||||
let start = anchor.min(self.cursor_pos);
|
||||
let end = anchor.max(self.cursor_pos);
|
||||
start..end
|
||||
})
|
||||
}
|
||||
|
||||
fn clear_selection(&mut self) {
|
||||
self.selection_anchor = None;
|
||||
}
|
||||
|
||||
fn set_selection(&mut self, anchor: usize, cursor: usize) {
|
||||
self.selection_anchor = Some(anchor);
|
||||
self.cursor_pos = cursor;
|
||||
}
|
||||
|
||||
fn select_all(&mut self) {
|
||||
self.selection_anchor = Some(0);
|
||||
self.cursor_pos = self.value.len();
|
||||
}
|
||||
|
||||
fn delete_selection(&mut self, cx: &mut gpui::Context<Self>) -> bool {
|
||||
if let Some(range) = self.selection_range() {
|
||||
self.value.drain(range.clone());
|
||||
self.cursor_pos = range.start;
|
||||
self.clear_selection();
|
||||
if let Some(on_change) = self.on_change.clone() {
|
||||
on_change(&self.value, cx);
|
||||
}
|
||||
self.refresh_suggestions(cx);
|
||||
cx.notify();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn replace_selection(&mut self, text: &str, cx: &mut gpui::Context<Self>) {
|
||||
let range = self.selection_range();
|
||||
let insert_pos = range.as_ref().map(|r| r.start).unwrap_or(self.cursor_pos);
|
||||
let delete_range = range.unwrap_or(self.cursor_pos..self.cursor_pos);
|
||||
|
||||
self.value.replace_range(delete_range.clone(), text);
|
||||
self.cursor_pos = insert_pos + text.len();
|
||||
self.clear_selection();
|
||||
|
||||
if let Some(on_change) = self.on_change.clone() {
|
||||
on_change(&self.value, cx);
|
||||
}
|
||||
self.refresh_suggestions(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn selected_text(&self) -> Option<&str> {
|
||||
self.selection_range().map(|range| &self.value[range])
|
||||
}
|
||||
|
||||
fn word_start_before(&self, pos: usize) -> usize {
|
||||
if pos == 0 {
|
||||
return 0;
|
||||
@@ -167,24 +235,44 @@ impl Input {
|
||||
i
|
||||
}
|
||||
|
||||
fn move_left(&mut self) {
|
||||
fn move_left(&mut self, cx: &mut gpui::Context<Self>, clear_selection: bool) {
|
||||
if self.cursor_pos > 0 {
|
||||
let mut new_pos = self.cursor_pos - 1;
|
||||
while new_pos > 0 && !self.value.is_char_boundary(new_pos) {
|
||||
new_pos -= 1;
|
||||
}
|
||||
self.cursor_pos = new_pos;
|
||||
if clear_selection {
|
||||
self.clear_selection();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn move_right(&mut self) {
|
||||
fn move_right(&mut self, cx: &mut gpui::Context<Self>, clear_selection: bool) {
|
||||
if self.cursor_pos < self.value.len() {
|
||||
let mut new_pos = self.cursor_pos + 1;
|
||||
while new_pos < self.value.len() && !self.value.is_char_boundary(new_pos) {
|
||||
new_pos += 1;
|
||||
}
|
||||
self.cursor_pos = new_pos;
|
||||
if clear_selection {
|
||||
self.clear_selection();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn move_word_left(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
self.cursor_pos = self.word_start_before(self.cursor_pos);
|
||||
self.clear_selection();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn move_word_right(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
self.cursor_pos = self.word_end_after(self.cursor_pos);
|
||||
self.clear_selection();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn refresh_suggestions(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
@@ -271,23 +359,20 @@ impl Input {
|
||||
}
|
||||
|
||||
fn insert_text(&mut self, text: &str, cx: &mut gpui::Context<Self>) {
|
||||
self.value.insert_str(self.cursor_pos, text);
|
||||
self.cursor_pos += text.len();
|
||||
|
||||
if let Some(on_change) = self.on_change.clone() {
|
||||
on_change(&self.value, cx);
|
||||
}
|
||||
self.refresh_suggestions(cx);
|
||||
cx.notify();
|
||||
self.replace_selection(text, cx);
|
||||
}
|
||||
|
||||
fn delete_backward(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
if self.has_selection() {
|
||||
self.delete_selection(cx);
|
||||
return;
|
||||
}
|
||||
if self.cursor_pos == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_pos = self.cursor_pos;
|
||||
self.move_left();
|
||||
self.move_left(cx, false);
|
||||
self.value.drain(self.cursor_pos..old_pos);
|
||||
|
||||
if let Some(on_change) = self.on_change.clone() {
|
||||
@@ -298,6 +383,10 @@ impl Input {
|
||||
}
|
||||
|
||||
fn delete_forward(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
if self.has_selection() {
|
||||
self.delete_selection(cx);
|
||||
return;
|
||||
}
|
||||
if self.cursor_pos >= self.value.len() {
|
||||
return;
|
||||
}
|
||||
@@ -316,6 +405,10 @@ impl Input {
|
||||
}
|
||||
|
||||
fn delete_word_backward(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
if self.has_selection() {
|
||||
self.delete_selection(cx);
|
||||
return;
|
||||
}
|
||||
if self.cursor_pos == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -332,6 +425,10 @@ impl Input {
|
||||
}
|
||||
|
||||
fn delete_word_forward(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
if self.has_selection() {
|
||||
self.delete_selection(cx);
|
||||
return;
|
||||
}
|
||||
if self.cursor_pos >= self.value.len() {
|
||||
return;
|
||||
}
|
||||
@@ -381,6 +478,43 @@ impl Input {
|
||||
return;
|
||||
}
|
||||
|
||||
if ctrl {
|
||||
match key {
|
||||
"a" => {
|
||||
self.select_all();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
"c" => {
|
||||
if let Some(text) = self.selected_text() {
|
||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
"x" => {
|
||||
if let Some(text) = self.selected_text() {
|
||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
|
||||
self.delete_selection(cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
"v" => {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
if let Some(text) = item.text() {
|
||||
let sanitized = if !self.multiline {
|
||||
text.replace('\n', " ").replace("\r\n", " ")
|
||||
} else {
|
||||
text.replace("\r\n", "\n")
|
||||
};
|
||||
self.replace_selection(&sanitized, cx);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match key {
|
||||
"enter" => {
|
||||
// For multiline, Enter inserts newline (modal handles exit via commands)
|
||||
@@ -433,32 +567,59 @@ impl Input {
|
||||
|
||||
"left" => {
|
||||
if ctrl {
|
||||
self.cursor_pos = self.word_start_before(self.cursor_pos);
|
||||
cx.notify();
|
||||
self.move_word_left(cx);
|
||||
} else if shift {
|
||||
if self.selection_anchor.is_none() {
|
||||
self.selection_anchor = Some(self.cursor_pos);
|
||||
}
|
||||
self.move_left(cx, false);
|
||||
} else {
|
||||
self.move_left();
|
||||
cx.notify();
|
||||
self.move_left(cx, true);
|
||||
}
|
||||
}
|
||||
|
||||
"right" => {
|
||||
if ctrl {
|
||||
self.cursor_pos = self.word_end_after(self.cursor_pos);
|
||||
cx.notify();
|
||||
self.move_word_right(cx);
|
||||
} else if shift {
|
||||
if self.selection_anchor.is_none() {
|
||||
self.selection_anchor = Some(self.cursor_pos);
|
||||
}
|
||||
self.move_right(cx, false);
|
||||
} else {
|
||||
self.move_right();
|
||||
cx.notify();
|
||||
self.move_right(cx, true);
|
||||
}
|
||||
}
|
||||
|
||||
"home" => {
|
||||
if shift && self.selection_anchor.is_none() {
|
||||
self.selection_anchor = Some(self.cursor_pos);
|
||||
self.cursor_pos = 0;
|
||||
cx.notify();
|
||||
} else if shift {
|
||||
self.cursor_pos = 0;
|
||||
cx.notify();
|
||||
} else {
|
||||
self.cursor_pos = 0;
|
||||
self.clear_selection();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
"end" => {
|
||||
self.cursor_pos = self.value.len();
|
||||
let len = self.value.len();
|
||||
if shift && self.selection_anchor.is_none() {
|
||||
self.selection_anchor = Some(self.cursor_pos);
|
||||
self.cursor_pos = len;
|
||||
cx.notify();
|
||||
} else if shift {
|
||||
self.cursor_pos = len;
|
||||
cx.notify();
|
||||
} else {
|
||||
self.cursor_pos = len;
|
||||
self.clear_selection();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
"backspace" => {
|
||||
@@ -481,11 +642,6 @@ impl Input {
|
||||
self.delete_word_backward(cx);
|
||||
}
|
||||
|
||||
"a" if ctrl => {
|
||||
self.cursor_pos = 0;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
"e" if ctrl => {
|
||||
self.cursor_pos = self.value.len();
|
||||
cx.notify();
|
||||
@@ -729,8 +885,69 @@ impl gpui::Render for Input {
|
||||
gpui::div().id(self.id.clone()).into_any_element()
|
||||
};
|
||||
|
||||
let content = if self.has_selection() {
|
||||
let range = self.selection_range().unwrap();
|
||||
let cursor_at_end = self.cursor_pos >= range.end;
|
||||
|
||||
let before_sel = &self.value[..range.start];
|
||||
let selected_text = &self.value[range.clone()];
|
||||
let after_sel = &self.value[range.end..];
|
||||
|
||||
let cursor = if is_focused {
|
||||
gpui::div().w_px().h_4().bg(theme.accent).into_any_element()
|
||||
} else {
|
||||
gpui::div().into_any_element()
|
||||
};
|
||||
|
||||
if cursor_at_end {
|
||||
gpui::div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.child(
|
||||
gpui::div()
|
||||
.text_color(theme.foreground)
|
||||
.child(before_sel.to_string()),
|
||||
)
|
||||
.child(
|
||||
gpui::div()
|
||||
.bg(theme.selection)
|
||||
.text_color(theme.selection_foreground)
|
||||
.child(selected_text.to_string()),
|
||||
)
|
||||
.child(cursor)
|
||||
.child(
|
||||
gpui::div()
|
||||
.text_color(theme.foreground)
|
||||
.child(after_sel.to_string()),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
gpui::div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.child(
|
||||
gpui::div()
|
||||
.text_color(theme.foreground)
|
||||
.child(before_sel.to_string()),
|
||||
)
|
||||
.child(cursor)
|
||||
.child(
|
||||
gpui::div()
|
||||
.bg(theme.selection)
|
||||
.text_color(theme.selection_foreground)
|
||||
.child(selected_text.to_string()),
|
||||
)
|
||||
.child(
|
||||
gpui::div()
|
||||
.text_color(theme.foreground)
|
||||
.child(after_sel.to_string()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
} else {
|
||||
gpui::div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
@@ -748,6 +965,12 @@ impl gpui::Render for Input {
|
||||
.into_any_element()
|
||||
};
|
||||
|
||||
gpui::div()
|
||||
.id(self.id.clone())
|
||||
.child(content)
|
||||
.into_any_element()
|
||||
};
|
||||
|
||||
let focus_handle = self.focus.clone();
|
||||
|
||||
let base = gpui::div()
|
||||
|
||||
+42
-45
@@ -401,14 +401,18 @@ impl App {
|
||||
annotations_add,
|
||||
annotations_delete,
|
||||
} = update;
|
||||
|
||||
let has_description = description.is_some();
|
||||
let has_project = project.is_some();
|
||||
let has_priority = priority.is_some();
|
||||
let has_due = due.is_some();
|
||||
let has_tags = tags.is_some();
|
||||
let has_status = status.is_some();
|
||||
let has_annotations = !annotations_add.is_empty() || !annotations_delete.is_empty();
|
||||
|
||||
let mut latest_task: Option<task::Task> = None;
|
||||
|
||||
if description.is_some()
|
||||
|| project.is_some()
|
||||
|| priority.is_some()
|
||||
|| tags.is_some()
|
||||
|| due.is_some()
|
||||
{
|
||||
if has_description || has_project || has_priority || has_tags || has_due {
|
||||
match self.task_service.update_task(
|
||||
task_id,
|
||||
description,
|
||||
@@ -433,39 +437,22 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(status) = status {
|
||||
let status_result = match status {
|
||||
task::TaskStatus::Completed => self.task_service.complete_task(task_id),
|
||||
task::TaskStatus::Pending => self.task_service.reopen_task(task_id),
|
||||
task::TaskStatus::Deleted => {
|
||||
self.task_service.delete_task(task_id).and_then(|_| {
|
||||
self.task_service
|
||||
.get_task(task_id)
|
||||
.and_then(|task| task.ok_or(task::TaskError::NotFound(task_id)))
|
||||
})
|
||||
if has_status {
|
||||
match status {
|
||||
Some(task::TaskStatus::Completed) => {
|
||||
latest_task = self.task_service.complete_task(task_id).ok()
|
||||
}
|
||||
_ => self
|
||||
.task_service
|
||||
.get_task(task_id)
|
||||
.and_then(|task| task.ok_or(task::TaskError::NotFound(task_id))),
|
||||
};
|
||||
|
||||
match status_result {
|
||||
Ok(task) => latest_task = Some(task),
|
||||
Err(e) => {
|
||||
log::error!("[App] Failed to update status: {}", e);
|
||||
self.toast_host.update(cx, |host, cx| {
|
||||
host.push(
|
||||
ToastKind::Error,
|
||||
format!("Failed to update status: {}", e),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
if let Some(task) = latest_task {
|
||||
self.apply_task_update(task, cx);
|
||||
Some(task::TaskStatus::Pending) => {
|
||||
latest_task = self.task_service.reopen_task(task_id).ok()
|
||||
}
|
||||
return;
|
||||
Some(task::TaskStatus::Deleted) => {
|
||||
let _ = self.task_service.delete_task(task_id);
|
||||
latest_task = self.task_service.get_task(task_id).ok().flatten();
|
||||
}
|
||||
_ => {
|
||||
latest_task = self.task_service.get_task(task_id).ok().flatten();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,10 +468,6 @@ impl App {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
if let Some(task) = latest_task {
|
||||
self.sync_task_detail(task, cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,15 +484,29 @@ impl App {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
if let Some(task) = latest_task {
|
||||
self.sync_task_detail(task, cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(task) = latest_task {
|
||||
let task_to_sync = if latest_task.is_some() {
|
||||
latest_task.take()
|
||||
} else if has_description
|
||||
|| has_project
|
||||
|| has_priority
|
||||
|| has_tags
|
||||
|| has_due
|
||||
|| has_status
|
||||
|| has_annotations
|
||||
{
|
||||
self.task_service.get_task(task_id).ok().flatten()
|
||||
} else {
|
||||
self.task_detail_modal.update(cx, |modal, cx| {
|
||||
modal.cancel_edit(None, cx);
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(task) = task_to_sync {
|
||||
self.apply_task_update(task, cx);
|
||||
} else {
|
||||
self.task_detail_modal.update(cx, |modal, cx| {
|
||||
|
||||
+6
-4
@@ -610,10 +610,12 @@ impl TaskService {
|
||||
.map_err(|e| TaskError::Storage(e.to_string()))?
|
||||
.ok_or(TaskError::NotFound(uuid))?;
|
||||
|
||||
let annotation = taskchampion::Annotation {
|
||||
entry: Utc::now(),
|
||||
description,
|
||||
};
|
||||
let mut entry = Utc::now();
|
||||
while tc_task.get_annotations().any(|a| a.entry == entry) {
|
||||
entry = entry + chrono::Duration::nanoseconds(1);
|
||||
}
|
||||
|
||||
let annotation = taskchampion::Annotation { entry, description };
|
||||
|
||||
tc_task
|
||||
.add_annotation(annotation, &mut ops)
|
||||
|
||||
@@ -41,11 +41,10 @@ pub(super) enum AnnotationOrigin {
|
||||
},
|
||||
}
|
||||
|
||||
fn annotation_id(entry: DateTime<Utc>, text: &str, index: usize) -> AnnotationId {
|
||||
fn annotation_id(entry: DateTime<Utc>, text: &str) -> AnnotationId {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
entry.timestamp_millis().hash(&mut hasher);
|
||||
text.hash(&mut hasher);
|
||||
index.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
@@ -54,9 +53,8 @@ impl AnnotationState {
|
||||
let items: Vec<AnnotationView> = detail
|
||||
.annotations
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, annotation)| AnnotationView {
|
||||
id: annotation_id(annotation.entry, &annotation.content, index),
|
||||
.map(|annotation| AnnotationView {
|
||||
id: annotation_id(annotation.entry, &annotation.content),
|
||||
created_at: annotation.entry,
|
||||
text: annotation.content.clone().into(),
|
||||
origin: AnnotationOrigin::Original,
|
||||
@@ -78,7 +76,7 @@ impl AnnotationState {
|
||||
}
|
||||
|
||||
pub(super) fn add_local(&mut self, text: SharedString, created_at: DateTime<Utc>) {
|
||||
let id = annotation_id(created_at, text.as_ref(), self.items.len());
|
||||
let id = annotation_id(created_at, text.as_ref());
|
||||
self.items.push(AnnotationView {
|
||||
id,
|
||||
created_at,
|
||||
|
||||
@@ -877,6 +877,8 @@ impl TaskDetailModal {
|
||||
self.entities.annotation_input.update(cx, |input, cx| {
|
||||
input.clear(cx);
|
||||
});
|
||||
|
||||
self.state.annotation_selected = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -1334,6 +1336,10 @@ impl TaskDetailModal {
|
||||
|
||||
match self.state.modal_focus {
|
||||
ModalFocus::TagsInput => {
|
||||
self.entities.tags_input.update(cx, |input, cx| {
|
||||
input.close_suggestions(cx);
|
||||
});
|
||||
|
||||
self.sync_form_from_inputs(cx);
|
||||
|
||||
if let Some(InlineEditTarget::Tag(i)) = self.state.inline_edit {
|
||||
@@ -1581,7 +1587,6 @@ impl TaskDetailModal {
|
||||
ConfirmAction::DeleteTag { index, .. } => {
|
||||
if index < self.state.form.tags.len() {
|
||||
self.state.form.tags.remove(index);
|
||||
// Adjust selection
|
||||
if self.state.form.tags.is_empty() {
|
||||
self.state.tag_selected = None;
|
||||
} else if let Some(sel) = self.state.tag_selected {
|
||||
@@ -1710,16 +1715,6 @@ impl TaskDetailModal {
|
||||
|
||||
match ann.origin {
|
||||
AnnotationOrigin::Added => {
|
||||
self.state.inline_edit =
|
||||
Some(InlineEditTarget::Annotation(actual_index));
|
||||
|
||||
let ann_value = ann.text.to_string();
|
||||
self.entities.annotation_input.update(cx, |input, cx| {
|
||||
input.set_value(ann_value, cx);
|
||||
});
|
||||
|
||||
self.enter_edit_field(window, cx);
|
||||
|
||||
return CommandResult::Handled;
|
||||
}
|
||||
AnnotationOrigin::Original => {
|
||||
|
||||
@@ -61,6 +61,7 @@ pub(super) fn render_task_detail_modal(
|
||||
tags_input,
|
||||
status_dropdown,
|
||||
priority_dropdown,
|
||||
focus_handle,
|
||||
form_focus_handle,
|
||||
scroll_handle,
|
||||
&theme,
|
||||
|
||||
@@ -120,6 +120,7 @@ pub(super) fn render_task_detail_panel<OnCloseClick>(
|
||||
tags_input: &gpui::Entity<Input>,
|
||||
status_dropdown: &gpui::Entity<Dropdown>,
|
||||
priority_dropdown: &gpui::Entity<Dropdown>,
|
||||
focus_handle: &gpui::FocusHandle,
|
||||
form_focus_handle: &gpui::FocusHandle,
|
||||
scroll_handle: &gpui::ScrollHandle,
|
||||
theme: &Theme,
|
||||
@@ -300,7 +301,11 @@ where
|
||||
.min_h_0()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(scroll_handle)
|
||||
.track_focus(form_focus_handle)
|
||||
.track_focus(if mode == ModalMode::View {
|
||||
focus_handle
|
||||
} else {
|
||||
form_focus_handle
|
||||
})
|
||||
.px(gpui::rems(1.0))
|
||||
.py(gpui::rems(0.75))
|
||||
.gap_4()
|
||||
@@ -678,6 +683,7 @@ fn render_tags_section(
|
||||
|
||||
gpui::div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.children(chips)
|
||||
@@ -694,6 +700,7 @@ fn render_tags_section(
|
||||
|
||||
gpui::div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap_2()
|
||||
.children(chips)
|
||||
.into_any_element()
|
||||
@@ -711,7 +718,14 @@ fn render_tags_section(
|
||||
.custom(Theme::alpha(theme.muted, 0.2), theme.muted)
|
||||
.into_any_element()
|
||||
});
|
||||
div.child(gpui::div().flex().gap_2().children(vchips).text_sm())
|
||||
div.child(
|
||||
gpui::div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap_2()
|
||||
.children(vchips)
|
||||
.text_sm(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl Default for SortState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
column: SortColumn::Priority,
|
||||
direction: SortDirection::Desc,
|
||||
direction: SortDirection::Asc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user