From de2bdd62b90880c1662cf03abb887ee4252bbb06 Mon Sep 17 00:00:00 2001 From: Vitali sharp8n Date: Fri, 21 Aug 2026 11:15:28 +0300 Subject: [PATCH] fix(keyboard): route edit keys to scene template search field Backspace, forward-delete, and arrow keys were falling through to the canvas when the Scene Template (Asset) Center was open, so the search field could not be edited and the selection behind the overlay could be deleted. Route these keys to the gallery's focused field for native parity and add regression tests. --- crates/op-host-web/src/canvaskit/mount.rs | 2 + .../op-host-web/src/widget_host/keyboard.rs | 21 +++++ .../src/widget_host/keyboard_edit_ops.rs | 11 +++ .../widget_host/scene_template_ime_tests.rs | 92 +++++++++++++++++++ 4 files changed, 126 insertions(+) diff --git a/crates/op-host-web/src/canvaskit/mount.rs b/crates/op-host-web/src/canvaskit/mount.rs index d99674ccf..2bb5c43cc 100644 --- a/crates/op-host-web/src/canvaskit/mount.rs +++ b/crates/op-host-web/src/canvaskit/mount.rs @@ -612,6 +612,7 @@ pub(super) async fn mount_ck(canvas_id: String) -> Result<(), JsValue> { "ArrowRight" if is_mod => consumed = b.host.apply_text_edit_line_edge(true), "ArrowLeft" if !is_mod => { consumed = b.host.apply_prompt_center_caret(false, shift) + || b.host.apply_scene_template_caret(false, shift) || b.host.apply_image_panel_caret(false, shift) || b.host.apply_settings_caret(false) || b.host.apply_chat_model_picker_caret(false) @@ -623,6 +624,7 @@ pub(super) async fn mount_ck(canvas_id: String) -> Result<(), JsValue> { } "ArrowRight" if !is_mod => { consumed = b.host.apply_prompt_center_caret(true, shift) + || b.host.apply_scene_template_caret(true, shift) || b.host.apply_image_panel_caret(true, shift) || b.host.apply_settings_caret(true) || b.host.apply_chat_model_picker_caret(true) diff --git a/crates/op-host-web/src/widget_host/keyboard.rs b/crates/op-host-web/src/widget_host/keyboard.rs index ad2edef15..853240039 100644 --- a/crates/op-host-web/src/widget_host/keyboard.rs +++ b/crates/op-host-web/src/widget_host/keyboard.rs @@ -138,6 +138,17 @@ impl WidgetHost { } return true; } + // Native parity (`op-host-native/widget_host/keyboard_delete.rs`). The + // Asset Center owns Backspace while it is open; without this arm the + // key fell through to the canvas and either did nothing visible in the + // search box or deleted the selection behind the overlay. + if let Some(changed) = shared::scene_template_backspace(&mut self.editor_state, self.now_ms) + { + if changed { + self.mark_dirty(); + } + return true; + } if self.editor_state.editor_ui.collab_join_input_active() { let changed = op_editor_ui::widgets::collab_ui::join_address_backspace( &mut self.editor_state.editor_ui, @@ -347,6 +358,16 @@ impl WidgetHost { } return true; } + // Native parity: forward-Delete belongs to the Asset Center's focused + // field while it is open (same gap as Backspace above). + if let Some(changed) = + shared::scene_template_delete_forward(&mut self.editor_state, self.now_ms) + { + if changed { + self.mark_dirty(); + } + return true; + } if self.apply_image_panel_delete() { return true; } diff --git a/crates/op-host-web/src/widget_host/keyboard_edit_ops.rs b/crates/op-host-web/src/widget_host/keyboard_edit_ops.rs index 076029b1f..df5ae1af9 100644 --- a/crates/op-host-web/src/widget_host/keyboard_edit_ops.rs +++ b/crates/op-host-web/src/widget_host/keyboard_edit_ops.rs @@ -315,6 +315,17 @@ impl WidgetHost { false } + /// Left / Right arrow in the Scene Template Center's focused input + /// (native parity, `keyboard_caret.rs`). Without this the arrow keys + /// nudged the canvas behind the open gallery instead of the caret. + pub fn apply_scene_template_caret(&mut self, forward: bool, extend: bool) -> bool { + if shared::scene_template_caret(&mut self.editor_state, forward, extend, self.now_ms) { + self.mark_dirty(); + return true; + } + false + } + /// Cmd/Ctrl+C — copy the selection into the clipboard. pub fn apply_copy(&mut self) -> bool { if self.editor_state.editor_ui.image_panel.search_open diff --git a/crates/op-host-web/src/widget_host/scene_template_ime_tests.rs b/crates/op-host-web/src/widget_host/scene_template_ime_tests.rs index bcb6dba7a..1b250a666 100644 --- a/crates/op-host-web/src/widget_host/scene_template_ime_tests.rs +++ b/crates/op-host-web/src/widget_host/scene_template_ime_tests.rs @@ -47,6 +47,98 @@ fn plain_characters_reach_the_panel() { assert_eq!(search(&host), "rect"); } +/// Backspace had the same native-parity gap as `apply_text` did: the web +/// `apply_backspace` ladder skipped the Asset Center, so the key fell through +/// to the canvas and could not edit the search box (and would delete the +/// selection behind the overlay). +#[test] +fn backspace_edits_the_search_field() { + let mut host = gallery_host(); + for c in "rect".chars() { + host.apply_text(c); + } + assert_eq!(search(&host), "rect"); + + assert!(host.apply_backspace()); + assert_eq!(search(&host), "rec"); + + assert!(host.apply_backspace()); + assert_eq!(search(&host), "re"); +} + +/// Backspace is swallowed even when the search box is empty — the open +/// gallery owns the key and it must never fall through to node deletion. +#[test] +fn backspace_on_an_empty_search_is_swallowed() { + let mut host = gallery_host(); + host.editor_state.set_single_selection(op_editor_core::NodeId::new("n1")); + + assert!(host.apply_backspace()); + assert!(search(&host).is_empty()); + assert!( + !host.editor_state.selection.is_empty(), + "Backspace behind the open gallery must not delete the selection" + ); +} + +/// Forward-Delete edits the focused field (native parity); it must not fall +/// through to the canvas selection either. +#[test] +fn delete_edits_the_search_field() { + let mut host = gallery_host(); + for c in "rect".chars() { + host.apply_text(c); + } + { + let input = &mut host.editor_state.editor_ui.scene_template_center.search; + input.set_caret(0, 0); + } + + assert!(host.apply_delete()); + assert_eq!(search(&host), "ect"); + + assert!(host.apply_delete()); + assert_eq!(search(&host), "ct"); +} + +/// Left / Right arrows move the search caret instead of nudging the canvas +/// behind the open gallery (native parity, `apply_scene_template_caret`). +#[test] +fn arrows_move_the_search_caret() { + let mut host = gallery_host(); + for c in "rect".chars() { + host.apply_text(c); + } + assert_eq!( + host.editor_state + .editor_ui + .scene_template_center + .search + .caret(), + 4 + ); + + assert!(host.apply_scene_template_caret(false, false)); + assert_eq!( + host.editor_state + .editor_ui + .scene_template_center + .search + .caret(), + 3 + ); + + assert!(host.apply_scene_template_caret(true, false)); + assert_eq!( + host.editor_state + .editor_ui + .scene_template_center + .search + .caret(), + 4 + ); +} + /// The gate the tool shortcuts read, so a letter typed into the gallery is /// never also a tool switch. #[test]