leptos_use/
use_drop_zone.rs

1use crate::core::IntoElementMaybeSignal;
2use cfg_if::cfg_if;
3use default_struct_builder::DefaultBuilder;
4use leptos::prelude::*;
5use leptos::reactive::wrappers::read::Signal;
6use send_wrapper::SendWrapper;
7use std::fmt::{Debug, Formatter};
8use std::sync::Arc;
9
10cfg_if! { if #[cfg(not(feature = "ssr"))] {
11    use crate::use_event_listener;
12    use leptos::ev::{dragenter, dragleave, dragover, drop};
13}}
14
15/// Create a zone where files can be dropped.
16///
17/// ## Demo
18///
19/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_drop_zone)
20///
21/// ## Usage
22///
23/// ```
24/// # use leptos::prelude::*;
25/// # use leptos::html::Div;
26/// # use leptos_use::{use_drop_zone_with_options, UseDropZoneOptions, UseDropZoneReturn};
27/// #
28/// # #[component]
29/// # fn Demo() -> impl IntoView {
30/// let drop_zone_el = NodeRef::<Div>::new();
31///
32/// let on_drop = |event| {
33///     // called when files are dropped on zone
34/// };
35///
36/// let UseDropZoneReturn {
37///     is_over_drop_zone,
38///     ..
39/// } = use_drop_zone_with_options(
40///     drop_zone_el,
41///     UseDropZoneOptions::default().on_drop(on_drop)
42/// );
43///
44/// view! {
45///     <div node_ref=drop_zone_el>
46///         "Drop files here"
47///     </div>
48/// }
49/// # }
50/// ```
51///
52/// ## Server-Side Rendering
53///
54/// On the server the returned `file` signal always contains an empty `Vec` and
55/// `is_over_drop_zone` contains always `false`
56pub fn use_drop_zone<El, M>(target: El) -> UseDropZoneReturn
57where
58    El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
59{
60    use_drop_zone_with_options(target, UseDropZoneOptions::default())
61}
62
63/// Version of [`use_drop_zone`] that takes a `UseDropZoneOptions`. See [`use_drop_zone`] for how to use.
64#[cfg_attr(feature = "ssr", allow(unused_variables))]
65pub fn use_drop_zone_with_options<El, M>(
66    target: El,
67    options: UseDropZoneOptions,
68) -> UseDropZoneReturn
69where
70    El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
71{
72    let (is_over_drop_zone, set_over_drop_zone) = signal(false);
73    let (files, set_files) = signal(Vec::<SendWrapper<web_sys::File>>::new());
74
75    #[cfg(not(feature = "ssr"))]
76    {
77        use std::ops::Deref;
78
79        let UseDropZoneOptions {
80            on_drop,
81            on_enter,
82            on_leave,
83            on_over,
84        } = options;
85
86        let counter = StoredValue::new(0_usize);
87
88        let update_files = move |event: &web_sys::DragEvent| {
89            if let Some(data_transfer) = event.data_transfer() {
90                let files: Vec<_> = data_transfer
91                    .files()
92                    .map(|f| js_sys::Array::from(&f).to_vec())
93                    .unwrap_or_default()
94                    .into_iter()
95                    .map(web_sys::File::from)
96                    .map(SendWrapper::new)
97                    .collect();
98
99                set_files.update(move |f| *f = files);
100            }
101        };
102
103        let target = target.into_element_maybe_signal();
104
105        let use_drop_zone_event = move |event| UseDropZoneEvent {
106            files: files
107                .read_untracked()
108                .iter()
109                .map(|f| f.deref().clone())
110                .collect(),
111            event,
112        };
113
114        let _ = use_event_listener(target, dragenter, move |event| {
115            event.prevent_default();
116            counter.update_value(|counter| *counter += 1);
117            set_over_drop_zone.set(true);
118
119            update_files(&event);
120
121            #[cfg(debug_assertions)]
122            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
123
124            on_enter(use_drop_zone_event(event));
125        });
126
127        let _ = use_event_listener(target, dragover, move |event| {
128            event.prevent_default();
129            update_files(&event);
130
131            #[cfg(debug_assertions)]
132            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
133
134            on_over(use_drop_zone_event(event));
135        });
136
137        let _ = use_event_listener(target, dragleave, move |event| {
138            event.prevent_default();
139            counter.update_value(|counter| *counter -= 1);
140            if counter.get_value() == 0 {
141                set_over_drop_zone.set(false);
142            }
143
144            update_files(&event);
145
146            #[cfg(debug_assertions)]
147            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
148
149            on_leave(use_drop_zone_event(event));
150        });
151
152        let _ = use_event_listener(target, drop, move |event| {
153            event.prevent_default();
154            counter.update_value(|counter| *counter = 0);
155            set_over_drop_zone.set(false);
156
157            update_files(&event);
158
159            #[cfg(debug_assertions)]
160            let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
161
162            on_drop(use_drop_zone_event(event));
163        });
164    }
165
166    UseDropZoneReturn {
167        files: files.into(),
168        is_over_drop_zone: is_over_drop_zone.into(),
169    }
170}
171
172/// Options for [`use_drop_zone_with_options`].
173#[derive(DefaultBuilder, Clone)]
174#[cfg_attr(feature = "ssr", allow(dead_code))]
175pub struct UseDropZoneOptions {
176    /// Event handler for the [`drop`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event) event
177    on_drop: Arc<dyn Fn(UseDropZoneEvent) + Send + Sync>,
178    /// Event handler for the [`dragenter`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragenter_event) event
179    on_enter: Arc<dyn Fn(UseDropZoneEvent) + Send + Sync>,
180    /// Event handler for the [`dragleave`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragleave_event) event
181    on_leave: Arc<dyn Fn(UseDropZoneEvent) + Send + Sync>,
182    /// Event handler for the [`dragover`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event) event
183    on_over: Arc<dyn Fn(UseDropZoneEvent) + Send + Sync>,
184}
185
186impl Default for UseDropZoneOptions {
187    fn default() -> Self {
188        Self {
189            on_drop: Arc::new(|_| {}),
190            on_enter: Arc::new(|_| {}),
191            on_leave: Arc::new(|_| {}),
192            on_over: Arc::new(|_| {}),
193        }
194    }
195}
196
197impl Debug for UseDropZoneOptions {
198    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199        write!(f, "UseDropZoneOptions")
200    }
201}
202
203/// Event passed as argument to the event handler functions of `UseDropZoneOptions`.
204#[derive(Clone, Debug)]
205pub struct UseDropZoneEvent {
206    /// Files being handled
207    pub files: Vec<web_sys::File>,
208    /// The original drag event
209    pub event: web_sys::DragEvent,
210}
211
212/// Return type of [`use_drop_zone`].
213#[derive(Clone, Copy)]
214pub struct UseDropZoneReturn {
215    /// Files being handled
216    pub files: Signal<Vec<SendWrapper<web_sys::File>>>,
217    /// Whether the files (dragged by the pointer) are over the drop zone
218    pub is_over_drop_zone: Signal<bool>,
219}