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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
use crate::closure;
use wasm_bindgen::prelude::*;

/// Since the `copy` option can be either a function or a boolean, this enum
/// encapsulates the possible values for the copy option.
///
/// The closure signature is `(el, handle)`, the element to check and the
/// element that was directly clicked on.
pub enum CopyValue {
    Bool(bool),
    Func(Box<dyn FnMut(JsValue, JsValue) -> bool>),
}

impl From<CopyValue> for JsValue {
    fn from(copy: CopyValue) -> JsValue {
        match copy {
            CopyValue::Bool(copy) => JsValue::from(copy),
            CopyValue::Func(copy) => closure::to_js_2_ret(copy),
        }
    }
}

/// The axis to be considered when determining the location an element will be
/// placed when dropped.
///
/// When an element is dropped onto a container, it will be placed near the
/// point where the mouse was released. If the `direction` is `Vertical`,
/// the default value, the Y axis will be considered. Otherwise, if the
/// `direction` is `Horizontal`, the X axis will be considered.
pub enum Direction {
    Vertical,
    Horizontal,
}

impl ToString for Direction {
    fn to_string(&self) -> String {
        const VERTICAL: &str = "vertical";
        const HORIZONTAL: &str = "horizontal";

        match self {
            Direction::Vertical => String::from(VERTICAL),
            Direction::Horizontal => String::from(HORIZONTAL),
        }
    }
}

/// Used to pass options when activating Dragula
///
/// When passed to the [`dragula_options`](crate::dragula_options) function,
/// this struct can be used to specify options to control the behaviour of the
/// drag-and-drop functionality.
///
/// For example:
/// ```no_run
/// use dragula::*;
/// use dragula::options::CopyValue;
/// use web_sys::Element;
/// # use wasm_bindgen::JsValue;
///
/// # let element = JsValue::TRUE;
/// //--snip--
///
/// let options = Options {
///     invalid: Box::new(|el, _handle| {
///         Element::from(el).tag_name() == String::from("A")
///     }),
///     copy: CopyValue::Bool(true),
///     copy_sort_source: true,
///     remove_on_spill: true,
///     slide_factor_x: 10,
///     slide_factor_y: 10,
///     ..Options::default()
/// };
///
/// let drake = dragula_options(&[element], options);
///
/// //--snip--
/// ```
pub struct Options {
    /// Besides the containers that you pass to [`dragula`](crate::dragula()),
    /// or the containers you dynamically add, you can also use this closure to
    /// specify any sort of logic that defines what is a container
    /// for this particular [`Drake`](crate::Drake) instance.
    ///
    /// This closure will be invoked with the element that is being checked for
    /// whether it is a container.
    pub is_container: Box<dyn FnMut(JsValue) -> bool>,
    /// You can define a `moves` closure which will be invoked with `(el, source,
    /// handle, sibling)` whenever an element is clicked. If this closure returns
    /// `false`, a drag event won't begin, and the event won't be prevented
    /// either. The `handle` element will be the original click target, which
    /// comes in handy to test if that element is an expected _"drag handle"_.
    pub moves: Box<dyn FnMut(JsValue, JsValue, JsValue, JsValue) -> bool>,
    /// You can set `accepts` to a closure with the following signature: `(el,
    /// target, source, sibling)`. It'll be called to make sure that an element
    /// `el`, that came from container `source`, can be dropped on container
    /// `target` before a `sibling` element. The `sibling` can be `null`, which
    /// would mean that the element would be placed as the last element in the
    /// container. Note that if [`copy`](Options::copy) is set to `true`, `el` will be
    /// set to the copy, instead of the originally dragged element.
    pub accepts: Box<dyn FnMut(JsValue, JsValue, JsValue, JsValue) -> bool>,
    /// You can provide an `invalid` closure with a `(el, handle)` signature.
    /// This closure should return `true` for elements that shouldn't trigger a
    /// drag. The `handle` argument is the element that was clicked, while `el`
    /// is the item that would be dragged.
    pub invalid: Box<dyn FnMut(JsValue, JsValue) -> bool>,
    /// If `copy` is set to `true` _(or a closure that returns `true`)_, items
    /// will be copied rather than moved. This implies the following differences:
    ///
    /// Event     | Move                                     | Copy
    /// ----------|------------------------------------------|---------------------------------------------
    /// `drag`    | Element will be concealed from `source`  | Nothing happens
    /// `drop`    | Element will be moved into `target`      | Element will be cloned into `target`
    /// `remove`  | Element will be removed from DOM         | Nothing happens
    /// `cancel`  | Element will stay in `source`            | Nothing happens
    ///
    /// If a closure is passed, it'll be called whenever an element starts being
    /// dragged in order to decide whether it should follow `copy` behavior or
    /// not. This closure will be passed the element to be dragged as well as
    /// its source container, in other words, the signature is `(el, handle)`.
    ///
    /// `false` by default.
    pub copy: CopyValue,
    /// If [`copy`](Options::copy) is set to `true` _(or a closure that
    /// returns `true`)_ and `copy_sort_source` is `true` as well, users will
    /// be able to sort elements in `copy`-source containers.
    ///
    /// `false` by default.
    pub copy_sort_source: bool,
    /// By default, spilling an element outside of any containers will move the
    /// element back to the _drop position previewed by the feedback shadow_.
    /// Setting `revert_on_spill` to `true` will ensure elements dropped outside
    /// of any approved containers are moved back to the source element where
    /// the drag event began, rather than stay at the _drop position previewed
    /// by the feedback shadow_.
    ///
    /// `false` by default.
    pub revert_on_spill: bool,
    /// By default, spilling an element outside of any containers will move the
    /// element back to the _drop position previewed by the feedback shadow_.
    /// Setting `remove_on_spill` to `true` will ensure elements dropped outside
    /// of any approved containers are removed from the DOM. Note that `remove`
    /// events won't fire if [`copy`](Options::copy) is set to `true`.
    ///
    /// `false` by default.
    pub remove_on_spill: bool,
    /// When an element is dropped onto a container, it'll be placed near the
    /// point where the mouse was released. If the `direction` is
    /// [`Vertical`](Direction::Vertical),
    /// the default value, the Y axis will be considered. Otherwise, if the
    /// `direction` is [`Horizontal`](Direction::Horizontal),
    ///  the X axis will be considered.
    ///
    /// [`Vertical`](Direction::Vertical), by default.
    pub direction: Direction,
    /// The DOM element where the mirror element displayed while dragging will
    /// be appended to.
    ///
    /// `document.body` by default.
    pub mirror_container: JsValue,
    /// When this option is enabled, if the user clicks on an input element the
    /// drag won't start until their mouse pointer exits the input. This
    /// translates into the user being able to select text in inputs contained
    /// inside draggable elements, and still drag the element by moving their
    /// mouse outside of the input -- so you get the best of both worlds.
    ///
    /// `true` by default.
    pub ignore_input_text_selection: bool,
    /// The amount of horizontal movement (in pixels) for a click to be
    /// considered a drag
    ///
    /// `0` by default.
    pub slide_factor_x: i32,
    /// The amount of vertical movement (in pixels) for a click to be
    /// considered a drag
    ///
    /// `0` by default.
    pub slide_factor_y: i32,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            is_container: Box::new(|_| false),
            moves: Box::new(|_, _, _, _| true),
            accepts: Box::new(|_, _, _, _| true),
            invalid: Box::new(|_, _| false),
            copy: CopyValue::Bool(false),
            copy_sort_source: false,
            revert_on_spill: false,
            remove_on_spill: false,
            direction: Direction::Vertical,
            // Will default to document.body (avoiding web_sys dependency)
            mirror_container: JsValue::UNDEFINED,
            ignore_input_text_selection: true,
            slide_factor_x: 0,
            slide_factor_y: 0,
        }
    }
}

#[doc(hidden)]
#[wasm_bindgen]
pub struct OptionsImpl {
    is_container_func: JsValue,
    moves_func: JsValue,
    accepts_func: JsValue,
    invalid_func: JsValue,
    copy_func_or_bool: JsValue,

    #[wasm_bindgen(js_name = copySortSource)]
    pub copy_sort_source: bool,

    #[wasm_bindgen(js_name = revertOnSpill)]
    pub revert_on_spill: bool,

    #[wasm_bindgen(js_name = removeOnSpill)]
    pub remove_on_spill: bool,

    direction: String,

    mirror_container_elem: JsValue,

    #[wasm_bindgen(js_name = ignoreInputTextSelection)]
    pub ignore_input_text_selection: bool,

    #[wasm_bindgen(js_name = slideFactorX)]
    pub slide_factor_x: i32,

    #[wasm_bindgen(js_name = slideFactorY)]
    pub slide_factor_y: i32,
}

impl From<Options> for OptionsImpl {
    fn from(options: Options) -> Self {
        OptionsImpl {
            is_container_func: closure::to_js_1_ret(options.is_container),
            moves_func: closure::to_js_4_ret(options.moves),
            accepts_func: closure::to_js_4_ret(options.accepts),
            invalid_func: closure::to_js_2_ret(options.invalid),
            copy_func_or_bool: JsValue::from(options.copy),
            mirror_container_elem: options.mirror_container,
            copy_sort_source: options.copy_sort_source,
            revert_on_spill: options.revert_on_spill,
            remove_on_spill: options.remove_on_spill,
            direction: options.direction.to_string(),
            ignore_input_text_selection: options.ignore_input_text_selection,
            slide_factor_x: options.slide_factor_x,
            slide_factor_y: options.slide_factor_y,
        }
    }
}

impl Default for OptionsImpl {
    fn default() -> Self {
        OptionsImpl::from(Options::default())
    }
}

#[wasm_bindgen]
#[doc(hidden)]
impl OptionsImpl {
    #[wasm_bindgen(getter = isContainer)]
    pub fn is_container_func(&self) -> JsValue {
        self.is_container_func.clone()
    }

    #[wasm_bindgen(setter = isContainer)]
    pub fn set_is_container_func(&mut self, val: JsValue) {
        self.is_container_func = val;
    }

    #[wasm_bindgen(getter = moves)]
    pub fn moves_func(&self) -> JsValue {
        self.moves_func.clone()
    }

    #[wasm_bindgen(setter = moves)]
    pub fn set_moves_func(&mut self, val: JsValue) {
        self.moves_func = val;
    }

    #[wasm_bindgen(getter = accepts)]
    pub fn accepts_func(&self) -> JsValue {
        self.accepts_func.clone()
    }

    #[wasm_bindgen(setter = accepts)]
    pub fn set_accepts_func(&mut self, val: JsValue) {
        self.accepts_func = val;
    }

    #[wasm_bindgen(getter = invalid)]
    pub fn invalid_func(&self) -> JsValue {
        self.invalid_func.clone()
    }

    #[wasm_bindgen(setter = invalid)]
    pub fn set_invalid_func(&mut self, val: JsValue) {
        self.invalid_func = val;
    }

    #[wasm_bindgen(getter = copy)]
    pub fn copy_func_or_bool(&self) -> JsValue {
        self.copy_func_or_bool.clone()
    }

    #[wasm_bindgen(setter = copy)]
    pub fn set_copy_func_or_bool(&mut self, val: JsValue) {
        self.copy_func_or_bool = val;
    }

    #[wasm_bindgen(getter = mirrorContainer)]
    pub fn mirror_container_elem(&self) -> JsValue {
        self.mirror_container_elem.clone()
    }

    #[wasm_bindgen(setter = mirrorContainer)]
    pub fn set_mirror_container_elem(&mut self, val: JsValue) {
        self.mirror_container_elem = val;
    }

    #[wasm_bindgen(getter)]
    pub fn direction(&self) -> String {
        self.direction.clone()
    }

    #[wasm_bindgen(setter)]
    pub fn set_direction(&mut self, val: String) {
        self.direction = val;
    }
}

#[cfg(test)]
mod test;