diff --git a/crates/ui-egui/src/dialogs/insert_function.rs b/crates/ui-egui/src/dialogs/insert_function.rs index 6a93530f7..38a695a80 100644 --- a/crates/ui-egui/src/dialogs/insert_function.rs +++ b/crates/ui-egui/src/dialogs/insert_function.rs @@ -29,7 +29,10 @@ pub(super) fn insert_function(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dia } else { lib::category_id(&saved).map(lib::category_label).unwrap_or(MOST_RECENTLY_USED).to_string() }; - ui.horizontal(|ui| { + // The category list is the dialog's only menu, so a menu open as the frame starts is its own + // (Enter on a row picks it and closes the menu in this frame). + let menu_was_open = egui::Popup::is_any_open(ui.ctx()); + let picker = ui.horizontal(|ui| { let label = ui.label(language.tr("Or select a category:")); craft_ui::ComboBox::from_id_salt("insertFunctionCategory") .width(220.0) @@ -40,8 +43,14 @@ pub(super) fn insert_function(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dia } }) .response - .labelled_by(label.id); + .labelled_by(label.id) }); + // Enter on the category list or in its menu opens it or picks a category, as Space does + // (egui clicks a focused control on either key); it does not also confirm the dialog. + let picker = picker.inner; + if menu_was_open || picker.has_focus() || craft_ui::ComboBox::is_open(ui.ctx(), picker.id) { + ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Enter)); + } if category != saved { d.list_index = 0; } diff --git a/crates/ui-egui/tests/it/function_library.rs b/crates/ui-egui/tests/it/function_library.rs index dd86f4652..a1a7a1a37 100644 --- a/crates/ui-egui/tests/it/function_library.rs +++ b/crates/ui-egui/tests/it/function_library.rs @@ -263,6 +263,41 @@ fn insert_function_lets_tab_reach_the_category_list() { assert_eq!(listed(&h), before + 1, "the list is open"); } +#[test] +fn enter_on_the_category_list_does_not_confirm_insert_function() { + use egui::accesskit::Role; + let mut h = formulas_tab(1440.0); + click(&mut h, "Insert Function"); + h.run_steps(3); + let open = |h: &Harness| h.state().dialog.as_ref().is_some_and(|d| d.name == "insertFunction"); + let listed = |h: &Harness| h.get_all_by_label("Date & Time").count(); + let closed = listed(&h); + h.input_mut().events.extend([key_event(Key::Tab, true), key_event(Key::Tab, false)]); + h.run_steps(5); + assert!(h.get_by_role(Role::ComboBox).is_focused(), "Tab reaches the category list"); + // Enter on the focused list opens it, as Space does; the dialog stays open and nothing is + // inserted. + h.input_mut().events.extend([key_event(Key::Enter, true), key_event(Key::Enter, false)]); + h.run_steps(3); + assert!(open(&h), "Enter on the category list leaves the dialog open"); + assert_eq!(editor_text(&h), None, "no function is inserted"); + assert_eq!(listed(&h), closed + 1, "Enter opens the list"); + // Enter again, with the list open, closes it and still does not confirm. + h.input_mut().events.extend([key_event(Key::Enter, true), key_event(Key::Enter, false)]); + h.run_steps(3); + assert!(open(&h), "Enter in the open list leaves the dialog open"); + assert_eq!(editor_text(&h), None); + // Back in the search box (Shift+Tab), Enter confirms as before. + let shift_tab = |pressed| Event::Key { key: Key::Tab, physical_key: None, pressed, repeat: false, modifiers: Modifiers::SHIFT }; + h.input_mut().events.extend([shift_tab(true), shift_tab(false)]); + h.run_steps(3); + assert!(!h.get_by_role(Role::ComboBox).is_focused(), "Shift+Tab leaves the category list"); + h.input_mut().events.extend([key_event(Key::Enter, true), key_event(Key::Enter, false)]); + h.run_steps(3); + assert!(!open(&h), "Enter in the search box confirms"); + assert!(editor_text(&h).is_some_and(|t| t.starts_with('=') && t.ends_with('(')), "{:?}", editor_text(&h)); +} + /// The text shapes the last frame painted, with their left edge and width. fn painted_text(h: &Harness) -> Vec<(String, f32, f32)> { let mut out = Vec::new();