dragula/options/mod.rs
1use crate::closure;
2use wasm_bindgen::prelude::*;
3
4/// Since the `copy` option can be either a function or a boolean, this enum
5/// encapsulates the possible values for the copy option.
6///
7/// The closure signature is `(el, handle)`, the element to check and the
8/// element that was directly clicked on.
9pub enum CopyValue {
10 Bool(bool),
11 Func(Box<dyn FnMut(JsValue, JsValue) -> bool>),
12}
13
14impl From<CopyValue> for JsValue {
15 fn from(copy: CopyValue) -> JsValue {
16 match copy {
17 CopyValue::Bool(copy) => JsValue::from(copy),
18 CopyValue::Func(copy) => closure::to_js_2_ret(copy),
19 }
20 }
21}
22
23/// The axis to be considered when determining the location an element will be
24/// placed when dropped.
25///
26/// When an element is dropped onto a container, it will be placed near the
27/// point where the mouse was released. If the `direction` is `Vertical`,
28/// the default value, the Y axis will be considered. Otherwise, if the
29/// `direction` is `Horizontal`, the X axis will be considered.
30pub enum Direction {
31 Vertical,
32 Horizontal,
33}
34
35impl ToString for Direction {
36 fn to_string(&self) -> String {
37 const VERTICAL: &str = "vertical";
38 const HORIZONTAL: &str = "horizontal";
39
40 match self {
41 Direction::Vertical => String::from(VERTICAL),
42 Direction::Horizontal => String::from(HORIZONTAL),
43 }
44 }
45}
46
47/// Used to pass options when activating Dragula
48///
49/// When passed to the [`dragula_options`](crate::dragula_options) function,
50/// this struct can be used to specify options to control the behaviour of the
51/// drag-and-drop functionality.
52///
53/// For example:
54/// ```no_run
55/// use dragula::*;
56/// use dragula::options::CopyValue;
57/// use web_sys::Element;
58/// # use wasm_bindgen::JsValue;
59///
60/// # let element = JsValue::TRUE;
61/// //--snip--
62///
63/// let options = Options {
64/// invalid: Box::new(|el, _handle| {
65/// Element::from(el).tag_name() == String::from("A")
66/// }),
67/// copy: CopyValue::Bool(true),
68/// copy_sort_source: true,
69/// remove_on_spill: true,
70/// slide_factor_x: 10,
71/// slide_factor_y: 10,
72/// ..Options::default()
73/// };
74///
75/// let drake = dragula_options(&[element], options);
76///
77/// //--snip--
78/// ```
79pub struct Options {
80 /// Besides the containers that you pass to [`dragula`](crate::dragula()),
81 /// or the containers you dynamically add, you can also use this closure to
82 /// specify any sort of logic that defines what is a container
83 /// for this particular [`Drake`](crate::Drake) instance.
84 ///
85 /// This closure will be invoked with the element that is being checked for
86 /// whether it is a container.
87 pub is_container: Box<dyn FnMut(JsValue) -> bool>,
88 /// You can define a `moves` closure which will be invoked with `(el, source,
89 /// handle, sibling)` whenever an element is clicked. If this closure returns
90 /// `false`, a drag event won't begin, and the event won't be prevented
91 /// either. The `handle` element will be the original click target, which
92 /// comes in handy to test if that element is an expected _"drag handle"_.
93 pub moves: Box<dyn FnMut(JsValue, JsValue, JsValue, JsValue) -> bool>,
94 /// You can set `accepts` to a closure with the following signature: `(el,
95 /// target, source, sibling)`. It'll be called to make sure that an element
96 /// `el`, that came from container `source`, can be dropped on container
97 /// `target` before a `sibling` element. The `sibling` can be `null`, which
98 /// would mean that the element would be placed as the last element in the
99 /// container. Note that if [`copy`](Options::copy) is set to `true`, `el` will be
100 /// set to the copy, instead of the originally dragged element.
101 pub accepts: Box<dyn FnMut(JsValue, JsValue, JsValue, JsValue) -> bool>,
102 /// You can provide an `invalid` closure with a `(el, handle)` signature.
103 /// This closure should return `true` for elements that shouldn't trigger a
104 /// drag. The `handle` argument is the element that was clicked, while `el`
105 /// is the item that would be dragged.
106 pub invalid: Box<dyn FnMut(JsValue, JsValue) -> bool>,
107 /// If `copy` is set to `true` _(or a closure that returns `true`)_, items
108 /// will be copied rather than moved. This implies the following differences:
109 ///
110 /// Event | Move | Copy
111 /// ----------|------------------------------------------|---------------------------------------------
112 /// `drag` | Element will be concealed from `source` | Nothing happens
113 /// `drop` | Element will be moved into `target` | Element will be cloned into `target`
114 /// `remove` | Element will be removed from DOM | Nothing happens
115 /// `cancel` | Element will stay in `source` | Nothing happens
116 ///
117 /// If a closure is passed, it'll be called whenever an element starts being
118 /// dragged in order to decide whether it should follow `copy` behavior or
119 /// not. This closure will be passed the element to be dragged as well as
120 /// its source container, in other words, the signature is `(el, handle)`.
121 ///
122 /// `false` by default.
123 pub copy: CopyValue,
124 /// If [`copy`](Options::copy) is set to `true` _(or a closure that
125 /// returns `true`)_ and `copy_sort_source` is `true` as well, users will
126 /// be able to sort elements in `copy`-source containers.
127 ///
128 /// `false` by default.
129 pub copy_sort_source: bool,
130 /// By default, spilling an element outside of any containers will move the
131 /// element back to the _drop position previewed by the feedback shadow_.
132 /// Setting `revert_on_spill` to `true` will ensure elements dropped outside
133 /// of any approved containers are moved back to the source element where
134 /// the drag event began, rather than stay at the _drop position previewed
135 /// by the feedback shadow_.
136 ///
137 /// `false` by default.
138 pub revert_on_spill: bool,
139 /// By default, spilling an element outside of any containers will move the
140 /// element back to the _drop position previewed by the feedback shadow_.
141 /// Setting `remove_on_spill` to `true` will ensure elements dropped outside
142 /// of any approved containers are removed from the DOM. Note that `remove`
143 /// events won't fire if [`copy`](Options::copy) is set to `true`.
144 ///
145 /// `false` by default.
146 pub remove_on_spill: bool,
147 /// When an element is dropped onto a container, it'll be placed near the
148 /// point where the mouse was released. If the `direction` is
149 /// [`Vertical`](Direction::Vertical),
150 /// the default value, the Y axis will be considered. Otherwise, if the
151 /// `direction` is [`Horizontal`](Direction::Horizontal),
152 /// the X axis will be considered.
153 ///
154 /// [`Vertical`](Direction::Vertical), by default.
155 pub direction: Direction,
156 /// The DOM element where the mirror element displayed while dragging will
157 /// be appended to.
158 ///
159 /// `document.body` by default.
160 pub mirror_container: JsValue,
161 /// When this option is enabled, if the user clicks on an input element the
162 /// drag won't start until their mouse pointer exits the input. This
163 /// translates into the user being able to select text in inputs contained
164 /// inside draggable elements, and still drag the element by moving their
165 /// mouse outside of the input -- so you get the best of both worlds.
166 ///
167 /// `true` by default.
168 pub ignore_input_text_selection: bool,
169 /// The amount of horizontal movement (in pixels) for a click to be
170 /// considered a drag
171 ///
172 /// `0` by default.
173 pub slide_factor_x: i32,
174 /// The amount of vertical movement (in pixels) for a click to be
175 /// considered a drag
176 ///
177 /// `0` by default.
178 pub slide_factor_y: i32,
179}
180
181impl Default for Options {
182 fn default() -> Self {
183 Self {
184 is_container: Box::new(|_| false),
185 moves: Box::new(|_, _, _, _| true),
186 accepts: Box::new(|_, _, _, _| true),
187 invalid: Box::new(|_, _| false),
188 copy: CopyValue::Bool(false),
189 copy_sort_source: false,
190 revert_on_spill: false,
191 remove_on_spill: false,
192 direction: Direction::Vertical,
193 // Will default to document.body (avoiding web_sys dependency)
194 mirror_container: JsValue::UNDEFINED,
195 ignore_input_text_selection: true,
196 slide_factor_x: 0,
197 slide_factor_y: 0,
198 }
199 }
200}
201
202#[doc(hidden)]
203#[wasm_bindgen]
204pub struct OptionsImpl {
205 is_container_func: JsValue,
206 moves_func: JsValue,
207 accepts_func: JsValue,
208 invalid_func: JsValue,
209 copy_func_or_bool: JsValue,
210
211 #[wasm_bindgen(js_name = copySortSource)]
212 pub copy_sort_source: bool,
213
214 #[wasm_bindgen(js_name = revertOnSpill)]
215 pub revert_on_spill: bool,
216
217 #[wasm_bindgen(js_name = removeOnSpill)]
218 pub remove_on_spill: bool,
219
220 direction: String,
221
222 mirror_container_elem: JsValue,
223
224 #[wasm_bindgen(js_name = ignoreInputTextSelection)]
225 pub ignore_input_text_selection: bool,
226
227 #[wasm_bindgen(js_name = slideFactorX)]
228 pub slide_factor_x: i32,
229
230 #[wasm_bindgen(js_name = slideFactorY)]
231 pub slide_factor_y: i32,
232}
233
234impl From<Options> for OptionsImpl {
235 fn from(options: Options) -> Self {
236 OptionsImpl {
237 is_container_func: closure::to_js_1_ret(options.is_container),
238 moves_func: closure::to_js_4_ret(options.moves),
239 accepts_func: closure::to_js_4_ret(options.accepts),
240 invalid_func: closure::to_js_2_ret(options.invalid),
241 copy_func_or_bool: JsValue::from(options.copy),
242 mirror_container_elem: options.mirror_container,
243 copy_sort_source: options.copy_sort_source,
244 revert_on_spill: options.revert_on_spill,
245 remove_on_spill: options.remove_on_spill,
246 direction: options.direction.to_string(),
247 ignore_input_text_selection: options.ignore_input_text_selection,
248 slide_factor_x: options.slide_factor_x,
249 slide_factor_y: options.slide_factor_y,
250 }
251 }
252}
253
254impl Default for OptionsImpl {
255 fn default() -> Self {
256 OptionsImpl::from(Options::default())
257 }
258}
259
260#[wasm_bindgen]
261#[doc(hidden)]
262impl OptionsImpl {
263 #[wasm_bindgen(getter = isContainer)]
264 pub fn is_container_func(&self) -> JsValue {
265 self.is_container_func.clone()
266 }
267
268 #[wasm_bindgen(setter = isContainer)]
269 pub fn set_is_container_func(&mut self, val: JsValue) {
270 self.is_container_func = val;
271 }
272
273 #[wasm_bindgen(getter = moves)]
274 pub fn moves_func(&self) -> JsValue {
275 self.moves_func.clone()
276 }
277
278 #[wasm_bindgen(setter = moves)]
279 pub fn set_moves_func(&mut self, val: JsValue) {
280 self.moves_func = val;
281 }
282
283 #[wasm_bindgen(getter = accepts)]
284 pub fn accepts_func(&self) -> JsValue {
285 self.accepts_func.clone()
286 }
287
288 #[wasm_bindgen(setter = accepts)]
289 pub fn set_accepts_func(&mut self, val: JsValue) {
290 self.accepts_func = val;
291 }
292
293 #[wasm_bindgen(getter = invalid)]
294 pub fn invalid_func(&self) -> JsValue {
295 self.invalid_func.clone()
296 }
297
298 #[wasm_bindgen(setter = invalid)]
299 pub fn set_invalid_func(&mut self, val: JsValue) {
300 self.invalid_func = val;
301 }
302
303 #[wasm_bindgen(getter = copy)]
304 pub fn copy_func_or_bool(&self) -> JsValue {
305 self.copy_func_or_bool.clone()
306 }
307
308 #[wasm_bindgen(setter = copy)]
309 pub fn set_copy_func_or_bool(&mut self, val: JsValue) {
310 self.copy_func_or_bool = val;
311 }
312
313 #[wasm_bindgen(getter = mirrorContainer)]
314 pub fn mirror_container_elem(&self) -> JsValue {
315 self.mirror_container_elem.clone()
316 }
317
318 #[wasm_bindgen(setter = mirrorContainer)]
319 pub fn set_mirror_container_elem(&mut self, val: JsValue) {
320 self.mirror_container_elem = val;
321 }
322
323 #[wasm_bindgen(getter)]
324 pub fn direction(&self) -> String {
325 self.direction.clone()
326 }
327
328 #[wasm_bindgen(setter)]
329 pub fn set_direction(&mut self, val: String) {
330 self.direction = val;
331 }
332}
333
334#[cfg(test)]
335mod test;