From 4e36fb65854e3fac13aa4443d67b9212c660b70a Mon Sep 17 00:00:00 2001 From: daniel Date: Sat, 23 May 2026 12:29:54 +0100 Subject: [PATCH 1/3] Implement support for beforeinput event Adds onbeforeinput exposing inputType, data, isComposing and the pre-change value. Cancellable via event.prevent_default(). The JS serializer gates the new fields on event.type === "beforeinput" so payloads for oninput and the other callers of serializeInputEvent are unchanged. SerializedBeforeInputData::input_type is required (no serde default) to serve as the discriminator in the untagged EventData enum. Drive-by: serializeInputEvent now falls back to target.textContent for HTMLElement targets when no form-control branch matches, mirroring WebBeforeInputData::value / WebFormData::value. This closes a gap where event.value() returned "" on desktop/liveview but textContent on dioxus-web for contenteditable elements. --- packages/core-types/src/bubbles.rs | 1 + packages/desktop/headless_tests/events.rs | 91 ++++++++ packages/desktop/src/events.rs | 8 + packages/html/src/events/before_input.rs | 269 ++++++++++++++++++++++ packages/html/src/events/generated.rs | 35 +++ packages/html/src/events/mod.rs | 2 + packages/html/src/transit.rs | 42 ++++ packages/interpreter/src/js/hash.txt | 2 +- packages/interpreter/src/js/native.js | 2 +- packages/interpreter/src/ts/serialize.ts | 18 +- packages/liveview/src/events.rs | 8 + packages/native-dom/src/events.rs | 18 +- packages/web/src/events/before_input.rs | 74 ++++++ packages/web/src/events/mod.rs | 13 +- 14 files changed, 572 insertions(+), 11 deletions(-) create mode 100644 packages/html/src/events/before_input.rs create mode 100644 packages/web/src/events/before_input.rs diff --git a/packages/core-types/src/bubbles.rs b/packages/core-types/src/bubbles.rs index d3c70235d9..8e41e8111a 100644 --- a/packages/core-types/src/bubbles.rs +++ b/packages/core-types/src/bubbles.rs @@ -26,6 +26,7 @@ pub fn event_bubbles(evt: &str) -> bool { "blur" => false, "change" => true, "input" => true, + "beforeinput" => true, "invalid" => true, "reset" => true, "submit" => true, diff --git a/packages/desktop/headless_tests/events.rs b/packages/desktop/headless_tests/events.rs index 27396a2327..5ba6962c3c 100644 --- a/packages/desktop/headless_tests/events.rs +++ b/packages/desktop/headless_tests/events.rs @@ -48,6 +48,9 @@ fn app() -> Element { test_form_submit {} test_select_multiple_options {} test_unicode {} + test_before_input {} + test_before_input_composing {} + test_before_input_contenteditable {} } } } @@ -629,3 +632,91 @@ fn test_unicode() -> Element { } } } + +fn test_before_input() -> Element { + // Set the element's value *before* dispatching so the handler observes the + // pre-change value (semantically what `beforeinput` exposes). + utils::mock_event_with_extra( + "before_input", + r#"new InputEvent("beforeinput", { + inputType: 'insertText', + data: 'x', + bubbles: true, + cancelable: true, + isComposing: false, + })"#, + r#" + element.value = "hello"; + "#, + ); + + rsx! { + input { + id: "before_input", + onbeforeinput: move |event| { + println!("{:?}", event.data); + assert_eq!(event.data.input_type(), "insertText"); + assert_eq!(event.data.data().as_deref(), Some("x")); + assert!(!event.data.is_composing()); + assert_eq!(event.data.value(), "hello"); + RECEIVED_EVENTS.with_mut(|x| *x += 1); + } + } + } +} + +fn test_before_input_composing() -> Element { + utils::mock_event( + "before_input_composing", + r#"new InputEvent("beforeinput", { + inputType: 'insertCompositionText', + data: 'あ', + bubbles: true, + cancelable: true, + isComposing: true, + })"#, + ); + + rsx! { + input { + id: "before_input_composing", + onbeforeinput: move |event| { + assert_eq!(event.data.input_type(), "insertCompositionText"); + assert_eq!(event.data.data().as_deref(), Some("あ")); + assert!(event.data.is_composing()); + RECEIVED_EVENTS.with_mut(|x| *x += 1); + } + } + } +} + +fn test_before_input_contenteditable() -> Element { + // Contenteditable elements have no `value` property; the JS serializer should + // fall back to `textContent` so desktop matches the wasm renderer. + utils::mock_event_with_extra( + "before_input_contenteditable", + r#"new InputEvent("beforeinput", { + inputType: 'insertText', + data: 'z', + bubbles: true, + cancelable: true, + isComposing: false, + })"#, + r#" + element.textContent = "draft"; + "#, + ); + + rsx! { + div { + id: "before_input_contenteditable", + contenteditable: "true", + onbeforeinput: move |event| { + assert_eq!(event.data.input_type(), "insertText"); + assert_eq!(event.data.data().as_deref(), Some("z")); + assert_eq!(event.data.value(), "draft"); + RECEIVED_EVENTS.with_mut(|x| *x += 1); + } + } + } +} diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index f5a2f20f25..12a7cd1e2b 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -38,6 +38,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter { .into() } + fn convert_before_input_data(&self, event: &PlatformEventData) -> BeforeInputData { + event + .downcast::() + .cloned() + .unwrap() + .into() + } + fn convert_cancel_data(&self, event: &PlatformEventData) -> CancelData { event .downcast::() diff --git a/packages/html/src/events/before_input.rs b/packages/html/src/events/before_input.rs new file mode 100644 index 0000000000..44d51a6473 --- /dev/null +++ b/packages/html/src/events/before_input.rs @@ -0,0 +1,269 @@ +use dioxus_core::Event; +use std::fmt::Debug; + +pub type BeforeInputEvent = Event; + +/// Data fired alongside the `beforeinput` event. +/// +/// The `beforeinput` event fires before an editable element (an ``, `