leptos_struct_table/components/
thead_drag.rs1use std::{marker::PhantomData, sync::Arc};
2
3use leptos::prelude::*;
4use wasm_bindgen::JsCast;
5
6#[derive(Clone)]
7pub struct HeadDragHandler<Column>(pub(crate) Arc<dyn DragHandler<Column> + Send + Sync + 'static>);
8
9impl<Column> HeadDragHandler<Column>
10where
11 Column: Clone + PartialEq + Send + Sync + 'static,
12{
13 pub fn new<H>(handler: H) -> Self
14 where
15 H: DragHandler<Column> + Send + Sync + 'static,
16 {
17 Self(Arc::new(handler))
18 }
19}
20
21impl<Column> Default for HeadDragHandler<Column>
22where
23 Column: Clone + PartialEq + Send + Sync + 'static,
24{
25 fn default() -> Self {
26 Self(Arc::new(DefaultDragHandler::<Column>::default()))
27 }
28}
29
30pub trait DragHandler<Column>: Send + Sync
32where
33 Column: Clone + PartialEq + Send + Sync + 'static,
34{
35 fn received_drop(
37 &self,
38 drag_state: DragStateRwSignal<Column>,
39 columns: RwSignal<Vec<Column>>,
40 _column: Column,
41 _event: web_sys::DragEvent,
42 ) {
43 drag_state.update(|drag_state| {
44 if let Some(drag_state) = drag_state.take() {
45 columns.update(|columns| drag_state.reorder_columns(columns))
46 }
47 });
48 }
49
50 fn dragging_over(
52 &self,
53 drag_state_carrier: DragStateRwSignal<Column>,
54 column: Column,
55 event: web_sys::DragEvent,
56 ) {
57 let Some(mut drag_state) = drag_state_carrier.get() else {
58 return;
59 };
60
61 event.prevent_default();
63
64 let hovering_side = if let Some(target) = event.target() {
65 let Ok(thead) = target.dyn_into::<web_sys::HtmlTableCellElement>() else {
66 return;
67 };
68 let thead_rect = thead.get_bounding_client_rect();
69 let thead_center_x = thead_rect.x() + thead_rect.width() / 2.0;
70 let mouse_x = event.x();
71 if (mouse_x as f64) < thead_center_x {
72 DragSide::Left
73 } else {
74 DragSide::Right
75 }
76 } else {
77 DragSide::Left
79 };
80
81 if drag_state.hovering_over != column || drag_state.hovering_side != hovering_side {
83 drag_state.hovering_over = column;
84 drag_state.hovering_side = hovering_side;
85 *drag_state_carrier.write() = Some(drag_state);
86 }
87 }
88
89 fn drag_leave(
91 &self,
92 _drag_state: DragStateRwSignal<Column>,
93 _column: Column,
94 _event: web_sys::DragEvent,
95 ) {
96 }
98
99 fn drag_start(
101 &self,
102 drag_state: DragStateRwSignal<Column>,
103 column: Column,
104 _event: web_sys::DragEvent,
105 ) {
106 drag_state.set(Some(DragState {
107 grabbed: column.clone(),
108 hovering_over: column,
109 hovering_side: DragSide::Left,
110 }));
111 }
112
113 fn drag_end(
115 &self,
116 drag_state: DragStateRwSignal<Column>,
117 columns: RwSignal<Vec<Column>>,
118 _column: Column,
119 _event: web_sys::DragEvent,
120 ) {
121 drag_state.update(|drag_state| {
122 if let Some(drag_state) = drag_state.take() {
123 columns.update(|columns| drag_state.reorder_columns(columns))
124 }
125 });
126 }
127
128 fn get_drag_classes(
131 &self,
132 drag_state: DragStateRwSignal<Column>,
133 column: Column,
134 columns: RwSignal<Vec<Column>>,
135 ) -> Signal<String> {
136 let grabbed_class = self.grabbed_class();
137 let hover_left_class = self.hover_left_class();
138 let hover_right_class = self.hover_right_class();
139
140 Signal::derive(move || {
141 let Some(drag_state) = drag_state.get() else {
142 return String::new();
143 };
144
145 if drag_state.grabbed == column {
146 grabbed_class.to_string()
147 } else if drag_state.hovering_over == column {
148 let mut resorted_cols = columns.get();
149
150 drag_state.reorder_columns(&mut resorted_cols);
151
152 if resorted_cols == *columns.read() {
153 String::new()
154 } else {
155 match drag_state.hovering_side {
156 DragSide::Left => hover_left_class.to_string(),
157 DragSide::Right => hover_right_class.to_string(),
158 }
159 }
160 } else {
161 String::new()
162 }
163 })
164 }
165
166 fn grabbed_class(&self) -> &'static str {
167 "grabbed"
168 }
169
170 fn hover_left_class(&self) -> &'static str {
171 "hover-left"
172 }
173
174 fn hover_right_class(&self) -> &'static str {
175 "hover-right"
176 }
177}
178
179#[derive(Copy, Clone)]
180pub struct DefaultDragHandler<C>(PhantomData<C>);
181
182impl<C> DragHandler<C> for DefaultDragHandler<C> where C: Clone + PartialEq + Send + Sync + 'static {}
183
184impl<C> Default for DefaultDragHandler<C>
185where
186 C: Clone + PartialEq + Send + Sync + 'static,
187{
188 fn default() -> Self {
189 DefaultDragHandler(PhantomData)
190 }
191}
192
193pub type DragStateRwSignal<Column> = RwSignal<Option<DragState<Column>>>;
194
195#[derive(Clone, PartialEq)]
196pub struct DragState<Column> {
197 pub grabbed: Column,
199 pub hovering_over: Column,
201 pub hovering_side: DragSide,
204}
205
206impl<Column> DragState<Column>
207where
208 Column: Clone + PartialEq,
209{
210 pub fn reorder_columns(&self, columns: &mut Vec<Column>) {
211 let index = columns
212 .iter()
213 .position(|c| *c == self.hovering_over)
214 .unwrap();
215 let grabbed_index = columns.iter().position(|c| *c == self.grabbed).unwrap();
216
217 if grabbed_index == index {
218 return;
219 }
220
221 columns.remove(grabbed_index);
222
223 let mut index = match self.hovering_side {
224 DragSide::Left => index,
225 DragSide::Right => index + 1,
226 };
227 if index > grabbed_index {
228 index -= 1;
229 }
230
231 columns.insert(index, self.grabbed.clone());
232 }
233}
234
235#[derive(Clone, Copy, PartialEq, Debug)]
238pub enum DragSide {
239 Left,
240 Right,
241}