Skip to content

Commit

Permalink
Checkbox internal state (#131)
Browse files Browse the repository at this point in the history
* Set checkbox internal state instead of attribute

* Move checked state to NodeSpecificData instead of simply reflecting in checked attribute
  • Loading branch information
cfraz89 committed Sep 10, 2024
1 parent 5847231 commit 0562690
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 35 deletions.
9 changes: 4 additions & 5 deletions examples/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ fn app() -> Element {
id: "check1",
name: "check1",
value: "check1",
checked: Some("").filter(|_| checkbox_checked()),
oninput: move |ev| {
dbg!(ev);
checkbox_checked.set(!checkbox_checked());
},
checked: checkbox_checked(),
// This works too
// checked: "{checkbox_checked}",
oninput: move |ev| checkbox_checked.set(!ev.checked()),
}
label {
r#for: "check1",
Expand Down
8 changes: 7 additions & 1 deletion packages/blitz/src/renderer/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,13 @@ impl ElementCx<'_> {
if self.element.local_name() == "input"
&& matches!(self.element.attr(local_name!("type")), Some("checkbox"))
{
let checked = self.element.attr(local_name!("checked")).is_some();
let Some(checked) = self
.element
.element_data()
.and_then(|data| data.checkbox_input_checked())
else {
return;
};
let disabled = self.element.attr(local_name!("disabled")).is_some();

// TODO this should be coming from css accent-color, but I couldn't find how to retrieve it
Expand Down
62 changes: 54 additions & 8 deletions packages/dioxus-blitz/src/documents/dioxus_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
use std::{collections::HashMap, rc::Rc};

use blitz_dom::{
events::EventData, local_name, namespace_url, node::Attribute, ns, Atom, Document,
DocumentLike, ElementNodeData, Node, NodeData, QualName, TextNodeData, Viewport, DEFAULT_CSS,
events::EventData,
local_name, namespace_url,
node::{Attribute, NodeSpecificData},
ns, Atom, Document, DocumentLike, ElementNodeData, Node, NodeData, QualName, TextNodeData,
Viewport, DEFAULT_CSS,
};

use dioxus::{
Expand Down Expand Up @@ -188,7 +191,10 @@ impl DioxusDocument {
// - if value is not specified, it defaults to 'on'
if let Some(name) = form_input.attr(local_name!("name")) {
if form_input.attr(local_name!("type")) == Some("checkbox")
&& form_input.attr(local_name!("checked")) == Some("true")
&& form_input
.element_data()
.and_then(|data| data.checkbox_input_checked())
.unwrap_or(false)
{
let value = form_input
.attr(local_name!("value"))
Expand All @@ -202,13 +208,14 @@ impl DioxusDocument {
} else {
Default::default()
};
let form_data = NativeFormData {
value: element_node_data
let value = match element_node_data.node_specific_data {
NodeSpecificData::CheckboxInput(checked) => checked.to_string(),
_ => element_node_data
.attr(local_name!("value"))
.unwrap_or_default()
.to_string(),
values,
};
let form_data = NativeFormData { value, values };
Rc::new(PlatformEventData::new(Box::new(form_data)))
}

Expand Down Expand Up @@ -561,8 +568,11 @@ impl WriteMutations for MutationWriter<'_> {
let node_id = self.state.element_to_node_id(id);
let node = self.doc.get_node_mut(node_id).unwrap();
if let NodeData::Element(ref mut element) = node.raw_dom_data {
// FIXME: support non-text attributes
if let AttributeValue::Text(val) = value {
if element.name.local == local_name!("input") && name == "checked" {
set_input_checked_state(element, value);
}
// FIXME: support other non-text attributes
else if let AttributeValue::Text(val) = value {
// FIXME check namespace
let existing_attr = element
.attrs
Expand Down Expand Up @@ -668,6 +678,42 @@ impl WriteMutations for MutationWriter<'_> {
}
}

/// Set 'checked' state on an input based on given attributevalue
fn set_input_checked_state(element: &mut ElementNodeData, value: &AttributeValue) {
let checked: bool;
match value {
AttributeValue::Bool(checked_bool) => {
checked = *checked_bool;
}
AttributeValue::Text(val) => {
if let Ok(checked_bool) = val.parse() {
checked = checked_bool;
} else {
return;
};
}
_ => {
return;
}
};
match element.node_specific_data {
NodeSpecificData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
// If we have just constructed the element, set the node attribute,
// and NodeSpecificData will be created from that later
// this simulates the checked attribute being set in html,
// and the element's checked property being set from that
NodeSpecificData::None => element.attrs.push(Attribute {
name: QualName {
prefix: None,
ns: ns!(html),
local: local_name!("checked"),
},
value: checked.to_string(),
}),
_ => {}
}
}

fn create_template_node(doc: &mut Document, node: &TemplateNode) -> NodeId {
match node {
TemplateNode::Element {
Expand Down
26 changes: 6 additions & 20 deletions packages/dom/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::events::{EventData, HitResult, RendererEvent};
use crate::node::{Attribute, NodeSpecificData, TextBrush};
use crate::node::{NodeSpecificData, TextBrush};
use crate::{ElementNodeData, Node, NodeData, TextNodeData, Viewport};
use app_units::Au;
use html5ever::{local_name, namespace_url, ns, QualName};
use html5ever::local_name;
use peniko::kurbo;
// use quadtree_rs::Quadtree;
use parley::editor::{PointerButton, TextEvent};
Expand Down Expand Up @@ -313,24 +313,10 @@ impl Document {
}

pub fn toggle_checkbox(el: &mut ElementNodeData) {
let is_checked = el
.attrs
.iter()
.any(|attr| attr.name.local == local_name!("checked"));

if is_checked {
el.attrs
.retain(|attr| attr.name.local != local_name!("checked"))
} else {
el.attrs.push(Attribute {
name: QualName {
prefix: None,
ns: ns!(html),
local: local_name!("checked"),
},
value: String::new(),
})
}
let Some(is_checked) = el.checkbox_input_checked_mut() else {
return;
};
*is_checked = !*is_checked;
}

pub fn root_node(&self) -> &Node {
Expand Down
17 changes: 17 additions & 0 deletions packages/dom/src/layout/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub(crate) fn collect_layout_children(
) {
create_text_editor(doc, container_node_id, false);
return;
} else if type_attr == Some("checkbox") {
create_checkbox_input(doc, container_node_id);
return;
}
}

Expand Down Expand Up @@ -303,6 +306,20 @@ fn create_text_editor(doc: &mut Document, input_element_id: usize, is_multiline:
}
}

fn create_checkbox_input(doc: &mut Document, input_element_id: usize) {
let node = &mut doc.nodes[input_element_id];

let element = &mut node.raw_dom_data.downcast_element_mut().unwrap();
if !matches!(
element.node_specific_data,
NodeSpecificData::CheckboxInput(_)
) {
let checked = element.attr_parsed(local_name!("checked")).unwrap_or(false);

element.node_specific_data = NodeSpecificData::CheckboxInput(checked);
}
}

pub(crate) fn build_inline_layout(
doc: &mut Document,
inline_context_root_node_id: usize,
Expand Down
17 changes: 17 additions & 0 deletions packages/dom/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,20 @@ impl ElementNodeData {
}
}

pub fn checkbox_input_checked(&self) -> Option<bool> {
match self.node_specific_data {
NodeSpecificData::CheckboxInput(checked) => Some(checked),
_ => None,
}
}

pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
match self.node_specific_data {
NodeSpecificData::CheckboxInput(ref mut checked) => Some(checked),
_ => None,
}
}

pub fn inline_layout_data(&self) -> Option<&TextLayout> {
match self.node_specific_data {
NodeSpecificData::InlineRoot(ref data) => Some(data),
Expand Down Expand Up @@ -503,6 +517,8 @@ pub enum NodeSpecificData {
TableRoot(Arc<TableContext>),
/// Parley text editor (text inputs)
TextInput(TextInputData),
/// Checkbox checked state
CheckboxInput(bool),
/// No data (for nodes that don't need any node-specific data)
None,
}
Expand All @@ -515,6 +531,7 @@ impl std::fmt::Debug for NodeSpecificData {
NodeSpecificData::InlineRoot(_) => f.write_str("NodeSpecificData::InlineRoot"),
NodeSpecificData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
NodeSpecificData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
NodeSpecificData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
NodeSpecificData::None => f.write_str("NodeSpecificData::None"),
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/dom/src/stylo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,11 @@ impl<'a> selectors::Element for BlitzNode<'a> {
&& elem.attr(local_name!("href")).is_some()
})
.unwrap_or(false),
NonTSPseudoClass::Checked => false,
NonTSPseudoClass::Checked => self
.raw_dom_data
.downcast_element()
.and_then(|elem| elem.checkbox_input_checked())
.unwrap_or(false),
NonTSPseudoClass::Valid => false,
NonTSPseudoClass::Invalid => false,
NonTSPseudoClass::Defined => false,
Expand Down

0 comments on commit 0562690

Please sign in to comment.