Skip to main content

dioxus_dnd/core/components/
mod.rs

1#![doc = include_str!("../../../docs/api/drag-and-drop.md")]
2
3use dioxus::prelude::*;
4
5mod delivery;
6mod draggable;
7mod drop_zone;
8mod handle;
9mod overlay;
10mod pointer;
11mod provider;
12
13pub use draggable::Draggable;
14pub(crate) use drop_zone::FlatDropZone;
15pub use drop_zone::{
16    use_parent_zone, BridgeDropZone, BridgeParentZoneBoundary, DropZone, ParentZone,
17};
18pub use handle::{DragHandle, NoDrag};
19pub use overlay::{DragOverlay, SettleSlot};
20pub use provider::DndProvider;
21
22pub(crate) use delivery::{
23    deliver_drop, drop_query, resolve_drag_hover, resolve_drag_target, DropCompletion, SettleRoute,
24    RELEASE_RECOVERY_MOVES,
25};
26pub(crate) use handle::ActivatorContext;
27pub(crate) use overlay::overlay_style;
28pub(crate) use pointer::{primary_press, touch_style, HoldTimer};
29
30fn take_text_styles(attributes: &mut Vec<Attribute>) -> String {
31    let mut styles = Vec::new();
32    attributes.retain(|attribute| {
33        if attribute.name != "style" {
34            return true;
35        }
36        if let dioxus::core::AttributeValue::Text(style) = &attribute.value {
37            styles.push(style.clone());
38        }
39        false
40    });
41    styles.join(" ")
42}
43
44/// Merge forwarded styles after configurable component defaults.
45pub(crate) fn merge_style_user_last(attributes: &mut Vec<Attribute>, defaults: &str) -> String {
46    let user = take_text_styles(attributes);
47    format!("{defaults} {user}")
48}
49
50/// Merge behavior-critical component styles after every forwarded style.
51///
52/// Dioxus spreads land after static attributes, so all caller `style`
53/// attributes must first be removed from the spread. Putting invariants last
54/// then prevents declarations such as `touch-action` or `transform` from
55/// disabling the component's behavior.
56pub(crate) fn merge_style_invariant_last(
57    attributes: &mut Vec<Attribute>,
58    invariant: &str,
59    invariant_properties: &[&str],
60) -> String {
61    // Dioxus also accepts each CSS declaration as an individual attribute
62    // (`touch_action:`, `transform:`, ...). Those arrive with the `style`
63    // namespace and, because the spread is later, would otherwise overwrite
64    // the invariant even after every textual `style` fragment was merged.
65    attributes.retain(|attribute| {
66        attribute.namespace != Some("style") || !invariant_properties.contains(&attribute.name)
67    });
68    let user = take_text_styles(attributes);
69    format!("{user} {invariant}")
70}
71
72/// Remove caller attributes whose later spread would replace an invariant
73/// listener, state marker, or accessibility attribute owned by a component.
74pub(crate) fn protect_attributes(attributes: &mut Vec<Attribute>, protected: &[&str]) {
75    attributes.retain(|attribute| !protected.contains(&attribute.name));
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn protected_listener_and_state_names_are_removed_but_other_attrs_survive() {
84        let mut attributes = vec![
85            Attribute::new("onclick", "caller", None, false),
86            Attribute::new("data-active", "caller", None, false),
87            Attribute::new("class", "card", None, false),
88        ];
89
90        protect_attributes(&mut attributes, &["onclick", "data-active"]);
91
92        assert_eq!(attributes.len(), 1);
93        assert_eq!(attributes[0].name, "class");
94    }
95
96    #[test]
97    fn every_forwarded_style_is_consumed_in_order() {
98        let mut attributes = vec![
99            Attribute::new("style", "color: red;", None, false),
100            Attribute::new("class", "card", None, false),
101            Attribute::new("style", "opacity: .5;", None, false),
102        ];
103
104        let style = merge_style_user_last(&mut attributes, "display: grid;");
105
106        assert_eq!(style, "display: grid; color: red; opacity: .5;");
107        assert_eq!(attributes.len(), 1);
108        assert_eq!(attributes[0].name, "class");
109    }
110
111    #[test]
112    fn invariant_styles_are_emitted_after_user_declarations() {
113        let mut attributes = vec![Attribute::new(
114            "style",
115            "touch-action: auto; transform: scale(2);",
116            None,
117            false,
118        )];
119
120        let style = merge_style_invariant_last(
121            &mut attributes,
122            "touch-action: none; transform: translate(4px);",
123            &["touch-action", "transform"],
124        );
125
126        assert_eq!(
127            style,
128            "touch-action: auto; transform: scale(2); touch-action: none; transform: translate(4px);"
129        );
130        assert!(attributes.is_empty());
131    }
132
133    #[test]
134    fn invariant_style_namespace_properties_are_removed_selectively() {
135        let mut attributes = vec![
136            Attribute::new("touch-action", "auto", Some("style"), false),
137            Attribute::new("transform", "scale(2)", Some("style"), false),
138            Attribute::new("opacity", "0.5", Some("style"), false),
139        ];
140
141        let style = merge_style_invariant_last(
142            &mut attributes,
143            "touch-action: none; transform: none;",
144            &["touch-action", "transform"],
145        );
146
147        assert_eq!(style, " touch-action: none; transform: none;");
148        assert_eq!(attributes.len(), 1);
149        assert_eq!(attributes[0].name, "opacity");
150        assert_eq!(attributes[0].namespace, Some("style"));
151    }
152}