From f75af23d4ea06a15b10b3d021de8159bf149eef2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 10:26:51 +0000 Subject: [PATCH] M6: deleting a radio button turns the group off when no button left shows its choice CodeRabbit on #4631: a twin with the same on-state name that is currently off does not keep the choice visible, so compare the remaining buttons' appearance state (/AS), not their on-state name. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM --- crates/forms/src/author.rs | 4 +++- crates/forms/src/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/forms/src/author.rs b/crates/forms/src/author.rs index 3161209111..81e7ec9dc4 100644 --- a/crates/forms/src/author.rs +++ b/crates/forms/src/author.rs @@ -1593,8 +1593,10 @@ pub fn delete_widget(doc: &mut Document, name: &str, widget: usize) -> Result<() return delete_found_field(doc, f); } let per_widget_opt = matches!(f.kind, FieldKind::CheckBox | FieldKind::Radio) && f.options.len() == f.widgets.len(); + // Turn the group off when no button left *shows* the deleted one's state: a twin with the same + // on-state name that is currently off does not keep the choice visible. let on_only_here = w.on_state.as_deref().is_some_and(|on| { - f.value.iter().any(|v| v == on) && !f.widgets.iter().enumerate().any(|(i, o)| i != widget && o.on_state.as_deref() == Some(on)) + f.value.iter().any(|v| v == on) && !f.widgets.iter().enumerate().any(|(i, o)| i != widget && o.state.as_deref() == Some(on)) }); drop_from_pages(doc, &std::iter::once(w.obj).collect())?; detach(doc, w.obj)?; diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index 37c7a6cbf4..3e919ec7c5 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -4414,6 +4414,28 @@ fn deleting_the_chosen_radio_button_turns_the_group_off() { assert_eq!((size.export_of(0), size.export_of(1)), (Some("medium"), Some("large"))); } +#[test] +fn deleting_the_chosen_radio_button_turns_the_group_off_when_a_twin_is_not_shown_on() { + // Medium shares Small's on-state name but shows Off: once Small is gone, no button shows + // the group's /V, so the group must not keep reporting it. + let mut doc = radio_group_form(); + let twin = ObjRef::new(7, 0); + let blank = Object::Ref(ObjRef::new(12, 0)); + let mut n = Dict::new(); + n.set(b"S".to_vec(), blank.clone()); + n.set(b"Off".to_vec(), blank); + let mut ap = Dict::new(); + ap.set(b"N".to_vec(), Object::Dict(n)); + let mut w = doc.get(twin).as_dict().cloned().unwrap(); + w.set(b"AP".to_vec(), Object::Dict(ap)); + doc.set(twin, Object::Dict(w)); + delete_widget(&mut doc, "size", 0).unwrap(); + let all = fields(&reopen(&doc)); + let size = field(&all, "size"); + assert_eq!(size.widgets.iter().map(|w| w.obj.num).collect::>(), [7, 8]); + assert!(size.value.is_empty(), "no button shows S any more: {:?}", size.value); +} + #[test] fn deleting_one_copy_of_a_field_keeps_the_field_and_its_value() { let mut doc = radio_group_form();