1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use web_sys::Element;
use yew::prelude::*;

use super::{use_event, use_mut_latest};

/// Options for drag.
#[derive(Default)]
pub struct UseDragOptions {
    /// Callback for `dragstart`.
    pub ondragstart: Option<Box<dyn FnMut(DragEvent)>>,
    /// Callback for `dragend`.
    pub ondragend: Option<Box<dyn FnMut(DragEvent)>>,
}

/// State handle for the [`use_drag`] hook.
pub struct UseDragHandle {
    /// State for whether is dragging.
    pub dragging: UseStateHandle<bool>,
}

/// This hook tracks file, link and copy-paste drags.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::prelude::*;
///
/// #[function_component(UseDrag)]
/// fn drag() -> Html {
///     let node = use_node_ref();
///     let state = use_drag(node.clone());
///
///     html! {
///         <div ref={node}>
///             <p>
///                 <b>{ " Dragging: " }</b>
///                 { *state.dragging }
///             </p>
///             <p>
///                 { "Try to drag this area" }
///             </p>
///         </div>
///     }
/// }
/// ```
#[hook]
pub fn use_drag(node: NodeRef) -> UseDragHandle {
    use_drag_with_options(node, UseDragOptions::default())
}

/// This hook tracks file, link and copy-paste drags.
/// [`use_drag`] hook with options.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::prelude::*;
///
/// #[function_component(UseDrag)]
/// fn drag() -> Html {
///     let node = use_node_ref();
///     let state = use_drag_with_options(node.clone(), UseDragOptions {
///         ondragstart: Some(Box::new(move |e| {
///             if let Some(data_transfer) = e.data_transfer() {
///                 let _ = data_transfer.set_data("text", "hello");
///             }
///         })),
///         ondragend: Some(Box::new(move |e| {
///         })),
///     });
///
///     html! {
///         <div ref={node}>
///             <p>
///                 <b>{ " Dragging: " }</b>
///                 { *state.dragging }
///             </p>
///             <p>
///                 { "Try to drag this area" }
///             </p>
///         </div>
///     }
/// }
/// ```
#[hook]
pub fn use_drag_with_options(node: NodeRef, options: UseDragOptions) -> UseDragHandle {
    let dragging = use_state(|| false);

    let ondragstart_ref = use_mut_latest(options.ondragstart);
    let ondragend_ref = use_mut_latest(options.ondragend);

    {
        let dragging = dragging.clone();
        use_event(node.clone(), "dragstart", move |e: DragEvent| {
            dragging.set(true);
            let ondragstart_ref = ondragstart_ref.current();
            let ondragstart = &mut *ondragstart_ref.borrow_mut();
            if let Some(ondragstart) = ondragstart {
                ondragstart(e);
            }
        });
    }

    {
        let dragging = dragging.clone();
        use_event(node.clone(), "dragend", move |e: DragEvent| {
            dragging.set(false);
            let ondragend_ref = ondragend_ref.current();
            let ondragend = &mut *ondragend_ref.borrow_mut();
            if let Some(ondragend) = ondragend {
                ondragend(e);
            }
        });
    }

    use_effect_with(node, move |node| {
        if let Some(element) = &node.cast::<Element>() {
            let _ = element.set_attribute("draggable", "true");
        }

        || ()
    });

    UseDragHandle { dragging }
}