perspective_viewer/ui/
dragdrop.rs1use std::cell::Cell;
14use std::rc::Rc;
15
16use perspective_client::clone;
17use perspective_js::utils::*;
18use wasm_bindgen::prelude::*;
19use web_sys::*;
20use yew::prelude::*;
21
22use crate::js::{IntersectionObserver, IntersectionObserverEntry};
23
24pub type DragEndCallback = Closure<dyn FnMut(DragEvent)>;
25pub type PointerDownCallback = Closure<dyn FnMut(PointerEvent)>;
26
27pub fn closest_draggable(event: &Event) -> Option<HtmlElement> {
28 event
29 .composed_path()
30 .get(0)
31 .dyn_into::<Element>()
32 .ok()?
33 .closest("[draggable=\"true\"]")
34 .ok()
35 .flatten()?
36 .dyn_into::<HtmlElement>()
37 .ok()
38}
39
40pub fn clear_document_selection() -> ApiResult<()> {
41 if let Some(selection) = global::window().get_selection()?
42 && !selection.is_collapsed()
43 {
44 selection.remove_all_ranges()?;
45 }
46
47 Ok(())
48}
49
50pub fn dragenter_helper(callback: impl Fn() + 'static, target: NodeRef) -> Callback<DragEvent> {
51 Callback::from({
52 move |event: DragEvent| {
53 let r = (|| -> ApiResult<()> {
54 event.stop_propagation();
55 event.prevent_default();
56 if event.related_target().is_none() {
57 target
58 .cast::<HtmlElement>()
59 .into_apierror()?
60 .dataset()
61 .set("safaridragleave", "true")?;
62 }
63 Ok(())
64 })();
65
66 if let Err(e) = r {
67 web_sys::console::warn_1(&e.into());
68 }
69
70 callback();
71 }
72 })
73}
74
75pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Callback<DragEvent> {
76 Callback::from({
77 clone!(drag_ref);
78 move |event: DragEvent| {
79 let r = (|| -> ApiResult<()> {
80 event.stop_propagation();
81 event.prevent_default();
82
83 let mut related_target = event
84 .related_target()
85 .or_else(|| Some(JsValue::UNDEFINED.unchecked_into::<EventTarget>()))
86 .and_then(|x| x.dyn_into::<Element>().ok());
87
88 if related_target
89 .as_ref()
90 .map(|x| x.has_attribute("aria-hidden"))
91 .unwrap_or_default()
92 {
93 related_target = Some(
94 related_target
95 .into_apierror()?
96 .parent_node()
97 .into_apierror()?
98 .dyn_ref::<ShadowRoot>()
99 .ok_or_else(|| JsValue::from("Chrome drag/drop bug detection failed"))?
100 .host()
101 .unchecked_into::<Element>(),
102 )
103 }
104
105 let current_target = drag_ref.cast::<HtmlElement>().unwrap();
106 match related_target {
107 Some(ref related) => {
108 if !current_target.contains(Some(related))
109 && related.parent_element().is_some()
110 {
111 callback();
112 }
113 },
114 None => {
115 let dataset = current_target.dataset();
116 if dataset.get("safaridragleave").is_some() {
117 dataset.delete("safaridragleave");
118 } else {
119 callback();
120 }
121 },
122 };
123 Ok(())
124 })();
125
126 if let Err(e) = r {
127 web_sys::console::warn_1(&e.into());
128 }
129 }
130 })
131}
132
133#[derive(Clone)]
134pub struct DragDropContainer {
135 pub noderef: NodeRef,
136 pub dragenter: Callback<DragEvent>,
137 pub dragleave: Callback<DragEvent>,
138}
139
140impl DragDropContainer {
141 pub fn new<F: Fn() + 'static, G: Fn() + 'static>(ondragenter: F, ondragleave: G) -> Self {
142 let noderef = NodeRef::default();
143 Self {
144 dragenter: dragenter_helper(ondragenter, noderef.clone()),
145 dragleave: dragleave_helper(ondragleave, noderef.clone()),
146 noderef,
147 }
148 }
149}
150
151pub struct DragTargetState {
152 target: HtmlElement,
153 shadow_root: ShadowRoot,
154 alive: Rc<Cell<bool>>,
155 observer: IntersectionObserver,
156}
157
158impl DragTargetState {
159 pub fn new(host: HtmlElement, target: HtmlElement) -> Self {
160 let shadow_root = host.shadow_root().unwrap();
161 let alive = Rc::new(Cell::new(true));
162 let observer = IntersectionObserver::new(
163 &Closure::<dyn FnMut(js_sys::Array)>::new({
164 clone!(target, shadow_root, alive);
165 move |records: js_sys::Array| {
166 if !alive.get() {
167 return;
168 }
169
170 for record in records.iter() {
171 let record: IntersectionObserverEntry = record.unchecked_into();
172 if !record.is_intersecting() {
173 shadow_root.append_child(&target).unwrap();
174 return;
175 }
176 }
177 }
178 })
179 .into_js_value()
180 .unchecked_into(),
181 );
182
183 observer.observe(target.as_ref());
184 Self {
185 target,
186 shadow_root,
187 alive,
188 observer,
189 }
190 }
191}
192
193impl Drop for DragTargetState {
194 fn drop(&mut self) {
195 self.alive.set(false);
196 self.observer.unobserve(&self.target);
197 if self.target.is_connected() {
198 let _ = self.shadow_root.remove_child(&self.target);
199 }
200 }
201}