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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! A drag and drop library for sycamore
//!
//! Adds the [`create_draggable`] and [`create_droppable`] functions that abstract the difficult
//! parts of drag and drop.
//! This library is still fairly low level and makes no assumptions about drop behaviour. This makes
//! it possible to do things like reading a file, but requires custom code for things like sortable
//! lists or other common drag and drop scenarios.
//!
//! # Example Usage
//!
//! ```
//! # use sycamore::prelude::*;
//! # use sycamore_dnd::*;
//! #[component]
//! fn App<'cx, G: Html>(cx: Scope<'cx>) -> View<G> {
//!     let inside = create_signal(cx, false);
//!
//!     let drop_inside = create_droppable(cx)
//!         .on_drop(move |_: ()| inside.set(true))
//!         .hovering_class("drag-over")
//!         .build();
//!     let drop_outside = create_droppable(cx)
//!         .on_drop(move |_: ()| inside.set(false))
//!         .hovering_class("drag-over")
//!         .build();
//!     let drag = create_draggable(cx)
//!         .dragging_class("dragging")
//!         .build();
//!
//!     view! { cx,
//!         div(class = "container") {
//!             div(style = "min-height:100px;width:100%;", ref = drop_outside) {
//!                 (if !*inside.get() {
//!                     view! { cx,
//!                         div(class = "item", ref = drag) {
//!                             "Drag me"
//!                         }
//!                     }
//!                 } else {
//!                     View::empty()
//!                 })
//!             }
//!             div(class="box", ref = drop_inside) {
//!                 (if *inside.get() {
//!                     view! { cx,
//!                         div(class = "item", ref = drag) {
//!                             "Drag me"
//!                         }
//!                     }
//!                 } else {
//!                     View::empty()
//!                 })
//!             }
//!         }
//!     }
//! }
//! ```

#![deny(missing_docs)]

use serde::{de::DeserializeOwned, Serialize};
use std::ops::Deref;

mod drag;
mod drop;

pub use drag::*;
pub use drop::*;
pub use web_sys::DataTransfer;

/// The effect allowed when dropping an item.
#[derive(Clone, Copy, Default)]
pub enum DropEffect {
    /// No effect
    None,
    /// Copy the item
    Copy,
    /// Copy the item as a link
    CopyLink,
    /// Copy and move the item
    CopyMove,
    /// Link the item
    Link,
    /// Link and move the item
    LinkMove,
    /// Move the item
    Move,
    /// Any effect
    #[default]
    All,
}

impl DropEffect {
    fn as_js(&self) -> &str {
        match self {
            DropEffect::None => "none",
            DropEffect::Copy => "copy",
            DropEffect::CopyLink => "copyLink",
            DropEffect::CopyMove => "copyMove",
            DropEffect::Link => "link",
            DropEffect::LinkMove => "linkMove",
            DropEffect::Move => "move",
            DropEffect::All => "all",
        }
    }
}

/// A trait implemented for any value that can be written to a drag and drop [`DataTransfer`]
pub trait AsTransfer {
    /// Write the data to the [`DataTransfer`]
    fn write_to_transfer(&self, transfer: &DataTransfer);
}

impl<T: Serialize> AsTransfer for T {
    fn write_to_transfer(&self, transfer: &DataTransfer) {
        transfer
            .set_data("data/json", &serde_json::to_string(self).unwrap())
            .unwrap();
    }
}

/// A trait implemented for any value that can be read from a drag and drop [`DataTransfer`]
/// Note that to get the raw transfer you need to use [`RawTransfer`] because of limiations in Rust's
/// trait system.
pub trait FromTransfer: Sized {
    /// Read the data from the [`DataTransfer`]
    fn from_transfer(transfer: &DataTransfer) -> Option<Self>;
}

/// A wrapper type for a raw [`DataTransfer`]
pub struct RawTransfer(DataTransfer);

impl Deref for RawTransfer {
    type Target = DataTransfer;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl FromTransfer for RawTransfer {
    fn from_transfer(transfer: &DataTransfer) -> Option<Self> {
        Some(RawTransfer(transfer.clone()))
    }
}

impl<T: DeserializeOwned> FromTransfer for T {
    fn from_transfer(transfer: &DataTransfer) -> Option<Self> {
        let data = transfer.get_data("data/json").ok()?;
        serde_json::from_str(&data).ok()
    }
}