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;