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;