1use std::rc::Rc;
11
12use wasm_bindgen::closure::Closure;
13use wasm_bindgen::{JsCast, JsValue};
14
15mod js {
16 use wasm_bindgen::prelude::*;
17
18 #[wasm_bindgen(module = "/sortable.esm.js")]
19 extern "C" {
20 #[wasm_bindgen(extends = js_sys::Object)]
21 pub type Sortable;
22
23 #[wasm_bindgen(constructor)]
24 pub fn new(elt: &web_sys::Element, opts: &js_sys::Object) -> Sortable;
25
26 #[wasm_bindgen(method)]
27 pub fn destroy(item: &Sortable);
28 }
29}
30
31#[derive(Clone, Debug)]
36pub struct Event {
37 pub raw_event: js_sys::Object,
38 pub to: web_sys::HtmlElement,
39 pub from: web_sys::HtmlElement,
40 pub item: web_sys::HtmlElement,
41 pub clone: web_sys::HtmlElement,
42 pub old_index: Option<usize>,
43 pub new_index: Option<usize>,
44 pub old_draggable_index: Option<usize>,
45 pub new_draggable_index: Option<usize>,
46 }
48
49impl Event {
50 fn from_raw_event(raw_event: js_sys::Object) -> Event {
51 macro_rules! get {
52 ($field:expr) => {
53 js_sys::Reflect::get(&raw_event, &JsValue::from_str($field))
54 .expect("failed retrieving field from raw event")
55 .dyn_into()
56 .expect("failed casting field of raw event to proper type")
57 };
58 }
59 macro_rules! get_optint {
60 ($field:expr) => {
61 js_sys::Reflect::get(&raw_event, &JsValue::from_str($field))
62 .ok()
63 .map(|evt| {
64 let float = evt
65 .as_f64()
66 .expect("failed casting field of raw event to proper type");
67 let int = float as usize;
68 assert!(
69 (int as f64 - float).abs() < 0.1,
70 "received index that is not an integer: {}",
71 float
72 );
73 int
74 })
75 };
76 }
77 Event {
78 to: get!("to"),
79 from: get!("from"),
80 item: get!("item"),
81 clone: get!("clone"),
82 old_index: get_optint!("oldIndex"),
83 new_index: get_optint!("newIndex"),
84 old_draggable_index: get_optint!("oldDraggableIndex"),
85 new_draggable_index: get_optint!("newDraggableIndex"),
86 raw_event,
87 }
88 }
89}
90
91#[derive(Clone, Debug)]
96pub struct MoveEvent {
97 pub raw_event: js_sys::Object,
98 pub to: web_sys::HtmlElement,
99 pub from: web_sys::HtmlElement,
100 pub dragged: web_sys::HtmlElement,
101 pub related: web_sys::HtmlElement,
103 pub will_insert_after: bool,
105}
106
107impl MoveEvent {
108 fn from_raw_event(raw_event: js_sys::Object) -> MoveEvent {
109 macro_rules! get {
110 ($field:expr) => {
111 js_sys::Reflect::get(&raw_event, &JsValue::from_str($field))
112 .expect("failed retrieving field from raw event")
113 .dyn_into()
114 .expect("failed casting field of raw event to proper type")
115 };
116 }
117 let will_insert_after =
118 js_sys::Reflect::get(&raw_event, &JsValue::from_str("willInsertAfter"))
119 .expect("failed retrieving field from raw event")
120 .as_bool()
121 .expect("willInsertAfter was not a boolean");
122 MoveEvent {
123 to: get!("to"),
124 from: get!("from"),
125 dragged: get!("dragged"),
126 related: get!("related"),
128 will_insert_after,
130 raw_event,
131 }
132 }
133}
134
135pub enum MoveResponse {
136 Cancel,
137 InsertBefore,
138 InsertAfter,
139 InsertDefault,
140}
141
142#[repr(usize)]
143enum CallbackId {
144 Choose,
145 Unchoose,
146 Start,
147 End,
148 Add,
149 Update,
150 Sort,
151 Remove,
152 Filter,
153 Clone,
154 Change,
155 Spill,
156 _Total,
157}
158
159pub struct Options {
161 options: js_sys::Object,
162 callbacks: [Option<Rc<Closure<dyn FnMut(js_sys::Object)>>>; CallbackId::_Total as usize],
163 on_move_cb: Option<Rc<Closure<dyn FnMut(js_sys::Object, js_sys::Object) -> JsValue>>>,
164}
165
166macro_rules! option {
167 ( $setter:ident, $jsname:expr, $typ:ty, $builder:ident ) => {
168 pub fn $setter(&mut self, value: $typ) -> &mut Options {
169 let res = js_sys::Reflect::set(
170 &self.options,
171 &JsValue::from_str($jsname),
172 &JsValue::$builder(value),
173 )
174 .expect("setting property on object failed");
175 assert!(res, "failed setting property on object");
176 self
177 }
178 };
179}
180
181macro_rules! callback {
182 ( $setter:ident, $jsname:expr, $id:ident ) => {
183 pub fn $setter(&mut self, mut cb: impl 'static + FnMut(Event)) -> &Options {
184 let cb = Closure::new(move |e: js_sys::Object| cb(Event::from_raw_event(e)));
185 let res = js_sys::Reflect::set(&self.options, &JsValue::from_str($jsname), cb.as_ref())
186 .expect("setting callback on object failed");
187 assert!(res, "failed setting callback on object");
188 self.callbacks[CallbackId::$id as usize] = Some(Rc::new(cb));
189 self
190 }
191 };
192}
193
194impl Options {
195 pub fn new() -> Options {
200 Options {
201 options: js_sys::Object::new(),
202 callbacks: std::array::from_fn(|_| None),
203 on_move_cb: None,
204 }
205 }
206
207 option!(group, "group", &str, from_str);
208 option!(sort, "sort", bool, from_bool);
209 option!(delay, "delay", f64, from_f64);
210 option!(delay_on_touch_only, "delayOnTouchOnly", bool, from_bool);
211 option!(touch_start_threshold, "touchStartThreshold", f64, from_f64);
212 option!(disabled, "disabled", bool, from_bool);
213 option!(animation_ms, "animation", f64, from_f64);
215 option!(easing, "easing", &str, from_str);
216 option!(handle, "handle", &str, from_str);
217 option!(filter, "filter", &str, from_str);
218 option!(prevent_on_filter, "preventOnFilter", bool, from_bool);
219 option!(draggable, "draggable", &str, from_str);
220
221 option!(data_id_attr, "dataIdAttr", &str, from_str);
222
223 option!(ghost_class, "ghostClass", &str, from_str);
224 option!(chosen_class, "chosenClass", &str, from_str);
225 option!(drag_class, "dragClass", &str, from_str);
226
227 option!(swap_threshold, "swapThreshold", f64, from_f64);
228 option!(invert_swap, "invertSwap", bool, from_bool);
229 option!(
230 inverted_swap_threshold,
231 "invertedSwapThreshold",
232 f64,
233 from_f64
234 );
235 option!(direction, "direction", &str, from_str);
236
237 option!(force_fallback, "forceFallback", bool, from_bool);
238
239 option!(fallback_class, "fallbackClass", &str, from_str);
240 option!(fallback_on_body, "fallbackOnBody", bool, from_bool);
241 option!(fallback_tolerance, "fallbackTolerance", f64, from_f64);
242
243 option!(dragover_bubble, "dragoverBubble", bool, from_bool);
244 option!(remove_clone_on_hide, "removeCloneOnHide", bool, from_bool);
245 option!(
246 empty_insert_threshold,
247 "emptyInsertThreshold",
248 f64,
249 from_f64
250 );
251 option!(revert_dom, "revertDOM", bool, from_bool);
252
253 callback!(on_choose, "onChoose", Choose);
254 callback!(on_unchoose, "onUnchoose", Unchoose);
255 callback!(on_start, "onStart", Start);
256 callback!(on_end, "onEnd", End);
257 callback!(on_add, "onAdd", Add);
258 callback!(on_update, "onUpdate", Update);
259 callback!(on_sort, "onSort", Sort);
260 callback!(on_remove, "onRemove", Remove);
261 callback!(on_filter, "onFilter", Filter);
262 callback!(on_clone, "onClone", Clone);
263 callback!(on_change, "onChange", Change);
264
265 pub fn on_move(
266 &mut self,
267 mut cb: impl 'static + FnMut(MoveEvent, js_sys::Object) -> MoveResponse,
268 ) -> &Options {
269 let cb = Closure::new(move |evt: js_sys::Object, original_evt: js_sys::Object| {
270 match cb(MoveEvent::from_raw_event(evt), original_evt) {
271 MoveResponse::Cancel => JsValue::FALSE,
272 MoveResponse::InsertBefore => JsValue::from_f64(-1.),
273 MoveResponse::InsertAfter => JsValue::from_f64(1.),
274 MoveResponse::InsertDefault => JsValue::TRUE,
275 }
276 });
277 let res = js_sys::Reflect::set(&self.options, &JsValue::from_str("onMove"), cb.as_ref())
278 .expect("setting callback on object failed");
279 assert!(res, "failed setting callback on object");
280 self.on_move_cb = Some(Rc::new(cb));
281 self
282 }
283
284 option!(revert_on_spill, "revertOnSpill", bool, from_bool);
286 option!(remove_on_spill, "removeOnSpill", bool, from_bool);
287 callback!(on_spill, "onSpill", Spill);
288
289 option!(scroll, "scroll", bool, from_bool);
291 option!(force_autoscroll_fallback, "forceAutoscrollFallback", bool, from_bool);
292 option!(scroll_sensitivity_px, "scrollSensitivity", f64, from_f64);
293 option!(scroll_speed_px, "scrollSpeed", f64, from_f64);
294 option!(bubble_scroll, "bubbleScroll", bool, from_bool);
295 pub fn options(&self) -> &js_sys::Object {
303 &self.options
304 }
305
306 pub fn apply(&self, elt: &web_sys::Element) -> Sortable {
312 let sortable = js::Sortable::new(elt, &self.options);
313 let object_ref: &js_sys::Object = sortable.as_ref();
314 let raw_object = object_ref.clone();
315 Sortable {
316 raw_object,
317 sortable,
318 _callbacks: self.callbacks.clone(),
319 _on_move_cb: self.on_move_cb.clone(),
320 }
321 }
322}
323
324pub struct Sortable {
330 pub raw_object: js_sys::Object,
333
334 sortable: js::Sortable,
336
337 _callbacks: [Option<Rc<Closure<dyn FnMut(js_sys::Object)>>>; CallbackId::_Total as usize],
339 _on_move_cb: Option<Rc<Closure<dyn FnMut(js_sys::Object, js_sys::Object) -> JsValue>>>,
340}
341
342impl Drop for Sortable {
343 fn drop(&mut self) {
344 self.sortable.destroy();
345 }
346}