1use dioxus_core::Event;
2use std::fmt::{self, Debug, Display};
3
4pub type BeforeInputEvent = Event<BeforeInputData>;
5
6macro_rules! string_enum {
13 (
14 $(#[$enum_meta:meta])*
15 $vis:vis enum $name:ident {
16 $(
17 $variant:ident => $value:literal,
18 )*
19 $(#[$unknown_meta:meta])*
20 _ => $unknown:ident($unknown_ty:ty),
21 }
22 ) => {
23 $(#[$enum_meta])*
24 $vis enum $name {
25 $(
26 $variant,
27 )*
28 $(#[$unknown_meta])*
29 $unknown($unknown_ty),
30 }
31
32 impl $name {
33 pub fn as_str(&self) -> &str {
36 match self {
37 $( Self::$variant => $value, )*
38 Self::$unknown(value) => value,
39 }
40 }
41 }
42
43 impl From<&str> for $name {
44 fn from(value: &str) -> Self {
45 match value {
46 $( $value => Self::$variant, )*
47 other => Self::$unknown(other.to_string()),
48 }
49 }
50 }
51 };
52}
53
54string_enum! {
55 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
63 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
64 #[cfg_attr(feature = "serialize", serde(from = "String", into = "String"))]
65 pub enum InputType {
66 InsertText => "insertText",
67 InsertReplacementText => "insertReplacementText",
68 InsertLineBreak => "insertLineBreak",
69 InsertParagraph => "insertParagraph",
70 InsertOrderedList => "insertOrderedList",
71 InsertUnorderedList => "insertUnorderedList",
72 InsertHorizontalRule => "insertHorizontalRule",
73 InsertFromYank => "insertFromYank",
74 InsertFromDrop => "insertFromDrop",
75 InsertFromPaste => "insertFromPaste",
76 InsertFromPasteAsQuotation => "insertFromPasteAsQuotation",
77 InsertTranspose => "insertTranspose",
78 InsertCompositionText => "insertCompositionText",
79 InsertLink => "insertLink",
80 DeleteWordBackward => "deleteWordBackward",
81 DeleteWordForward => "deleteWordForward",
82 DeleteSoftLineBackward => "deleteSoftLineBackward",
83 DeleteSoftLineForward => "deleteSoftLineForward",
84 DeleteEntireSoftLine => "deleteEntireSoftLine",
85 DeleteHardLineBackward => "deleteHardLineBackward",
86 DeleteHardLineForward => "deleteHardLineForward",
87 DeleteByDrag => "deleteByDrag",
88 DeleteByCut => "deleteByCut",
89 DeleteContent => "deleteContent",
90 DeleteContentBackward => "deleteContentBackward",
91 DeleteContentForward => "deleteContentForward",
92 HistoryUndo => "historyUndo",
93 HistoryRedo => "historyRedo",
94 FormatBold => "formatBold",
95 FormatItalic => "formatItalic",
96 FormatUnderline => "formatUnderline",
97 FormatStrikeThrough => "formatStrikeThrough",
98 FormatSuperscript => "formatSuperscript",
99 FormatSubscript => "formatSubscript",
100 FormatJustifyFull => "formatJustifyFull",
101 FormatJustifyCenter => "formatJustifyCenter",
102 FormatJustifyRight => "formatJustifyRight",
103 FormatJustifyLeft => "formatJustifyLeft",
104 FormatIndent => "formatIndent",
105 FormatOutdent => "formatOutdent",
106 FormatRemove => "formatRemove",
107 FormatSetBlockTextDirection => "formatSetBlockTextDirection",
108 FormatSetInlineTextDirection => "formatSetInlineTextDirection",
109 FormatBackColor => "formatBackColor",
110 FormatFontColor => "formatFontColor",
111 FormatFontName => "formatFontName",
112 _ => Unknown(String),
114 }
115}
116
117impl From<String> for InputType {
118 fn from(value: String) -> Self {
119 InputType::from(value.as_str())
122 }
123}
124
125impl From<InputType> for String {
126 fn from(value: InputType) -> Self {
127 match value {
128 InputType::Unknown(value) => value,
129 other => other.as_str().to_string(),
130 }
131 }
132}
133
134impl Display for InputType {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 f.write_str(self.as_str())
137 }
138}
139
140pub struct BeforeInputData {
153 inner: Box<dyn HasBeforeInputData>,
154}
155
156impl BeforeInputData {
157 pub fn new(inner: impl HasBeforeInputData + 'static) -> Self {
159 Self {
160 inner: Box::new(inner),
161 }
162 }
163
164 pub fn input_type(&self) -> InputType {
172 self.inner.input_type()
173 }
174
175 pub fn data(&self) -> Option<String> {
180 self.inner.data()
181 }
182
183 pub fn is_composing(&self) -> bool {
185 self.inner.is_composing()
186 }
187
188 pub fn value(&self) -> String {
190 self.inner.value()
191 }
192
193 #[inline(always)]
195 pub fn downcast<T: 'static>(&self) -> Option<&T> {
196 self.inner.as_any().downcast_ref::<T>()
197 }
198}
199
200impl<E: HasBeforeInputData> From<E> for BeforeInputData {
201 fn from(e: E) -> Self {
202 Self { inner: Box::new(e) }
203 }
204}
205
206impl PartialEq for BeforeInputData {
207 fn eq(&self, other: &Self) -> bool {
208 self.input_type() == other.input_type()
209 && self.data() == other.data()
210 && self.is_composing() == other.is_composing()
211 && self.value() == other.value()
212 }
213}
214
215impl Debug for BeforeInputData {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("BeforeInputData")
218 .field("input_type", &self.input_type())
219 .field("data", &self.data())
220 .field("is_composing", &self.is_composing())
221 .field("value", &self.value())
222 .finish()
223 }
224}
225
226pub trait HasBeforeInputData: std::any::Any {
228 fn input_type(&self) -> InputType;
230
231 fn data(&self) -> Option<String>;
233
234 fn is_composing(&self) -> bool;
236
237 fn value(&self) -> String;
239
240 fn as_any(&self) -> &dyn std::any::Any;
242}
243
244#[cfg(feature = "serialize")]
245pub use serialize::*;
246
247#[cfg(feature = "serialize")]
248mod serialize {
249 use super::*;
250
251 #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, Default)]
254 pub struct SerializedBeforeInputData {
255 pub input_type: String,
256
257 #[serde(default)]
258 pub data: Option<String>,
259
260 #[serde(default)]
261 pub is_composing: bool,
262
263 #[serde(default)]
264 pub value: String,
265 }
266
267 impl SerializedBeforeInputData {
268 pub fn new(
269 input_type: String,
270 data: Option<String>,
271 is_composing: bool,
272 value: String,
273 ) -> Self {
274 Self {
275 input_type,
276 data,
277 is_composing,
278 value,
279 }
280 }
281 }
282
283 impl HasBeforeInputData for SerializedBeforeInputData {
284 fn input_type(&self) -> InputType {
285 InputType::from(self.input_type.as_str())
286 }
287
288 fn data(&self) -> Option<String> {
289 self.data.clone()
290 }
291
292 fn is_composing(&self) -> bool {
293 self.is_composing
294 }
295
296 fn value(&self) -> String {
297 self.value.clone()
298 }
299
300 fn as_any(&self) -> &dyn std::any::Any {
301 self
302 }
303 }
304
305 impl From<&BeforeInputData> for SerializedBeforeInputData {
306 fn from(data: &BeforeInputData) -> Self {
307 Self {
308 input_type: data.input_type().to_string(),
309 data: data.data(),
310 is_composing: data.is_composing(),
311 value: data.value(),
312 }
313 }
314 }
315
316 impl serde::Serialize for BeforeInputData {
317 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
318 SerializedBeforeInputData::from(self).serialize(serializer)
319 }
320 }
321
322 impl<'de> serde::Deserialize<'de> for BeforeInputData {
323 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
324 let data = SerializedBeforeInputData::deserialize(deserializer)?;
325 Ok(Self {
326 inner: Box::new(data),
327 })
328 }
329 }
330}
331
332#[cfg(all(test, feature = "serialize"))]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn serialized_before_input_data_deserializes_missing_optional_fields() {
338 let data: SerializedBeforeInputData =
342 serde_json::from_str(r#"{"input_type": "insertText"}"#).unwrap();
343 assert_eq!(data.input_type, "insertText");
344 assert_eq!(data.data, None);
345 assert!(!data.is_composing);
346 assert_eq!(data.value, "");
347 }
348
349 #[test]
350 fn serialized_before_input_data_rejects_missing_input_type() {
351 assert!(serde_json::from_str::<SerializedBeforeInputData>("{}").is_err());
352 }
353
354 #[test]
355 fn before_input_data_exposes_serialized_fields() {
356 let event = BeforeInputData::new(SerializedBeforeInputData::new(
357 "insertText".to_string(),
358 Some("a".to_string()),
359 false,
360 "hello".to_string(),
361 ));
362
363 assert_eq!(event.input_type(), InputType::InsertText);
364 assert_eq!(event.data().as_deref(), Some("a"));
365 assert!(!event.is_composing());
366 assert_eq!(event.value(), "hello");
367 }
368
369 #[test]
374 fn deserializes_payload_from_js_interpreter() {
375 let payload = r#"{
376 "input_type": "insertFromPaste",
377 "data": "pasted",
378 "is_composing": false,
379 "value": "hello pasted",
380 "values": [],
381 "valid": true
382 }"#;
383
384 let data: SerializedBeforeInputData = serde_json::from_str(payload).unwrap();
385 assert_eq!(data.input_type, "insertFromPaste");
386 assert_eq!(data.data.as_deref(), Some("pasted"));
387 assert!(!data.is_composing);
388 assert_eq!(data.value, "hello pasted");
389 }
390
391 #[test]
392 fn input_type_maps_known_values_and_preserves_unknown() {
393 assert_eq!(InputType::from("insertText"), InputType::InsertText);
394 assert_eq!(
395 InputType::from("deleteContentBackward"),
396 InputType::DeleteContentBackward
397 );
398
399 assert_eq!(InputType::InsertFromPaste.as_str(), "insertFromPaste");
401 assert_eq!(InputType::InsertFromPaste.to_string(), "insertFromPaste");
402
403 let unknown = InputType::from("insertSomethingNew");
405 assert_eq!(
406 unknown,
407 InputType::Unknown("insertSomethingNew".to_string())
408 );
409 assert_eq!(unknown.as_str(), "insertSomethingNew");
410 assert_eq!(String::from(unknown), "insertSomethingNew");
411 }
412
413 #[test]
414 fn round_trips_through_serde() {
415 let original = SerializedBeforeInputData::new(
416 "deleteContentBackward".to_string(),
417 None,
418 true,
419 "hell".to_string(),
420 );
421
422 let json = serde_json::to_string(&original).unwrap();
423 let back: SerializedBeforeInputData = serde_json::from_str(&json).unwrap();
424 assert_eq!(original, back);
425 }
426}