From d41cf9c61fac636fca4089e47ab914443a0920a2 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Sat, 20 Jun 2026 23:26:21 +0800 Subject: [PATCH] fix(panels): anchor caret blink on per-frame search inputs The component/icon/font/image search boxes rebuild their TextInputState from the filter String each frame; with_text left the blink anchor at 0, so the caret could be in its hidden phase right after a keystroke. touch() the rebuilt state with now_ms so the caret stays steadily visible while the box is focused (codex review follow-up). --- crates/op-editor-ui/src/widgets/component_browser_panel.rs | 4 ++++ crates/op-editor-ui/src/widgets/icon_picker_panel.rs | 3 +++ .../src/widgets/property_panel_image_popovers.rs | 7 +++++-- .../op-editor-ui/src/widgets/property_panel_typography.rs | 5 ++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/op-editor-ui/src/widgets/component_browser_panel.rs b/crates/op-editor-ui/src/widgets/component_browser_panel.rs index f48e1212e..e7d9a37e1 100644 --- a/crates/op-editor-ui/src/widgets/component_browser_panel.rs +++ b/crates/op-editor-ui/src/widgets/component_browser_panel.rs @@ -520,6 +520,10 @@ impl<'a> ComponentBrowserPanel<'a> { let mut search_input = jian_core::text_input::TextInputState::with_text( self.state.editor_ui.component_browser_search.clone(), ); + // Anchor the blink to this frame so the caret stays visible while the + // box is focused (the buffer is rebuilt each frame, so there is no + // per-keystroke anchor to thread). + search_input.touch(self.now_ms); if self.state.editor_ui.component_browser_select_all && !search_input.text().is_empty() { search_input.select_all(); } diff --git a/crates/op-editor-ui/src/widgets/icon_picker_panel.rs b/crates/op-editor-ui/src/widgets/icon_picker_panel.rs index 8912828e6..29fe2ec67 100644 --- a/crates/op-editor-ui/src/widgets/icon_picker_panel.rs +++ b/crates/op-editor-ui/src/widgets/icon_picker_panel.rs @@ -346,6 +346,9 @@ impl<'a> IconPickerPanel<'a> { let mut search_input = jian_core::text_input::TextInputState::with_text( self.state.editor_ui.icon_picker_search.clone(), ); + // Anchor the blink to this frame so the caret stays visible while the + // box is focused (the buffer is rebuilt each frame). + search_input.touch(self.now_ms); if self.state.editor_ui.icon_picker_select_all && !search_input.text().is_empty() { search_input.select_all(); } diff --git a/crates/op-editor-ui/src/widgets/property_panel_image_popovers.rs b/crates/op-editor-ui/src/widgets/property_panel_image_popovers.rs index e1c4f48bf..91cba8d64 100644 --- a/crates/op-editor-ui/src/widgets/property_panel_image_popovers.rs +++ b/crates/op-editor-ui/src/widgets/property_panel_image_popovers.rs @@ -109,7 +109,9 @@ pub fn paint_search_popover( cx.backend .stroke_round_rect(layout.input, 5.0, theme.border, 1.0); let baseline = layout.input.origin.y + layout.input.size.y / 2.0 + 4.0; - let search_input = jian_core::text_input::TextInputState::with_text(state.search_query.clone()); + let mut search_input = + jian_core::text_input::TextInputState::with_text(state.search_query.clone()); + search_input.touch(now_ms); // keep the caret visible while the popover is open crate::widgets::property_panel_text_input::paint_text_input_view( cx, theme, @@ -321,8 +323,9 @@ pub fn paint_generate_popover( // rebuilt from the prompt String each frame; the open popover // owns the keyboard, so it reads as focused. let line = Rect::xywh(ta.origin.x, ta.origin.y, ta.size.x, 26.0); - let generate_input = + let mut generate_input = jian_core::text_input::TextInputState::with_text(state.generate_prompt.clone()); + generate_input.touch(now_ms); // keep the caret visible while open crate::widgets::property_panel_text_input::paint_text_input_view( cx, theme, diff --git a/crates/op-editor-ui/src/widgets/property_panel_typography.rs b/crates/op-editor-ui/src/widgets/property_panel_typography.rs index 2f5fde6f8..00f8d7637 100644 --- a/crates/op-editor-ui/src/widgets/property_panel_typography.rs +++ b/crates/op-editor-ui/src/widgets/property_panel_typography.rs @@ -345,7 +345,10 @@ pub fn paint_font_picker( // Draft + placeholder + caret render through the unified jian TextInputView // (family-aware caret, no hand-rolled drift). The buffer is rebuilt from the // search String each frame; the open picker reads as focused. - let search_input = jian_core::text_input::TextInputState::with_text(search.to_string()); + let mut search_input = jian_core::text_input::TextInputState::with_text(search.to_string()); + // Anchor the blink to this frame so the caret stays visible while the + // picker is open (the buffer is rebuilt each frame). + search_input.touch(now_ms); crate::widgets::property_panel_text_input::paint_text_input_view( cx, theme,