use std::collections::HashMap; use dioxus_html::{input_data::keyboard_types::Key, HasKeyboardData}; use dioxus_native_core::{ custom_element::CustomElement, node::OwnedAttributeDiscription, node_ref::AttributeMask, prelude::NodeType, real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut}, NodeId, }; use shipyard::UniqueView; use crate::hooks::FormData; use super::{RinkWidget, WidgetContext}; #[derive(Debug, Default)] pub(crate) struct CheckBox { div_id: NodeId, text_id: NodeId, value: String, checked: bool, } impl CheckBox { fn width(el: &ElementNodeMut) -> String { if let Some(value) = el .get_attribute(&OwnedAttributeDiscription { name: "width".to_string(), namespace: None, }) .and_then(|value| value.as_text()) .map(|value| value.to_string()) { value } else { "1px".to_string() } } fn height(el: &ElementNodeMut) -> String { if let Some(value) = el .get_attribute(&OwnedAttributeDiscription { name: "height".to_string(), namespace: None, }) .and_then(|value| value.as_text()) .map(|value| value.to_string()) { value } else { "1px".to_string() } } fn update_size_attr(&mut self, el: &mut ElementNodeMut) { let width = Self::width(el); let height = Self::height(el); let single_char = width == "1px" || height == "1px"; let border_style = if single_char { "none" } else { "solid" }; el.set_attribute( OwnedAttributeDiscription { name: "border-style".to_string(), namespace: Some("style".to_string()), }, border_style.to_string(), ); } fn update_value_attr(&mut self, el: &ElementNodeMut) { self.value = el .get_attribute(&OwnedAttributeDiscription { name: "value".to_string(), namespace: None, }) .and_then(|value| value.as_text()) .map(|value| value.to_string()) .unwrap_or_else(|| "on".to_string()); } fn update_checked_attr(&mut self, el: &ElementNodeMut) { self.checked = el .get_attribute(&OwnedAttributeDiscription { name: "checked".to_string(), namespace: None, }) .and_then(|value| value.as_text()) .map(|value| value.to_string()) .unwrap_or_else(|| "false".to_string()) == "true"; } fn write_value(&self, mut root: NodeMut) { let single_char = { let node_type = root.node_type_mut(); let NodeTypeMut::Element(el) = node_type else { panic!("input must be an element") }; Self::width(&el) == "1px" || Self::height(&el) == "1px" }; let rdom = root.real_dom_mut(); if let Some(mut text) = rdom.get_mut(self.text_id) { let node_type = text.node_type_mut(); let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; let value = if single_char { if self.checked { "☑" } else { "☐" } } else if self.checked { "✓" } else { " " }; *text.text_mut() = value.to_string(); } } fn switch(&mut self, mut node: NodeMut) { let new_state = !self.checked; let data = FormData { value: new_state .then(|| self.value.to_string()) .unwrap_or_default(), values: HashMap::new(), files: None, }; { let ctx: UniqueView = node .real_dom_mut() .raw_world_mut() .borrow() .expect("expected widget context"); ctx.send(crate::Event { id: self.div_id, name: "input", data: crate::EventData::Form(data), bubbles: true, }); } self.checked = new_state; self.write_value(node); } } impl CustomElement for CheckBox { const NAME: &'static str = "input"; fn roots(&self) -> Vec { vec![self.text_id] } fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { let node_type = root.node_type(); let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; let value = el .attributes .get(&OwnedAttributeDiscription { name: "value".to_string(), namespace: None, }) .and_then(|value| value.as_text()) .map(|value| value.to_string()); drop(node_type); let rdom = root.real_dom_mut(); let text = rdom.create_node(String::new()); let text_id = text.id(); root.add_event_listener("click"); root.add_event_listener("keydown"); let div_id = root.id(); let myself = Self { div_id, text_id, value: value.unwrap_or_default(), checked: false, }; myself.write_value(root); myself } fn attributes_changed( &mut self, mut root: dioxus_native_core::real_dom::NodeMut, attributes: &dioxus_native_core::node_ref::AttributeMask, ) { match attributes { AttributeMask::All => { { let node_type = root.node_type_mut(); let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; self.update_value_attr(&el); self.update_size_attr(&mut el); self.update_checked_attr(&el); } self.write_value(root); } AttributeMask::Some(attrs) => { { let node_type = root.node_type_mut(); let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; if attrs.contains("width") || attrs.contains("height") { self.update_size_attr(&mut el); } if attrs.contains("value") { self.update_value_attr(&el); } if attrs.contains("checked") { self.update_checked_attr(&el); } } if attrs.contains("checked") { self.write_value(root); } } } } } impl RinkWidget for CheckBox { fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) { match event.name { "click" => self.switch(node), "keydown" => { if let crate::EventData::Keyboard(data) = &event.data { if !data.is_auto_repeating() && match data.key() { Key::Character(c) if c == " " => true, Key::Enter => true, _ => false, } { self.switch(node); } } } _ => {} } } }