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()
}
}