orbital_base_components/table/header_cell/
mod.rs1mod resize;
2
3use leptos::{either::Either, ev, html, leptos_dom::helpers::WindowListenerHandle, prelude::*};
4
5use resize::{clamp_column_width, column_width_style};
6
7#[component]
8pub fn BaseTableHeaderCell(
9 #[prop(optional, into)] class: MaybeProp<String>,
10 #[prop(optional, into)] style: MaybeProp<String>,
11 #[prop(optional, default = false)] resizable: bool,
12 #[prop(default = None)] min_width: Option<f64>,
13 #[prop(default = None)] max_width: Option<f64>,
14 #[prop(default = None)] colspan: Option<u32>,
15 #[prop(default = None)] rowspan: Option<u32>,
16 #[prop(default = None)] on_resize_end: Option<Callback<f64, ()>>,
17 #[prop(default = None)] on_autosize: Option<Callback<(), ()>>,
18 #[prop(optional)] children: Option<Children>,
19) -> impl IntoView {
20 let th_ref = NodeRef::<html::Th>::new();
21 let pointer_down_x = RwSignal::new(0.0);
22 let pointer_down_th_width = RwSignal::new(0.0);
23 let last_width = RwSignal::new(0.0);
24 let resize_width = RwSignal::new(None::<f64>);
25 let listeners = StoredValue::new(Vec::<WindowListenerHandle>::new());
26
27 let th_style = Signal::derive(move || {
28 if let Some(width) = resize_width.get() {
29 column_width_style(width, min_width, max_width)
30 } else {
31 style.get().unwrap_or_default()
32 }
33 });
34
35 let begin_resize = move |client_x: f64| {
36 pointer_down_x.set(client_x);
37 let Some(th_el) = th_ref.get_untracked() else {
38 return;
39 };
40 let width = th_el.get_bounding_client_rect().width();
41 let width = clamp_column_width(width, min_width, max_width);
42 if width <= 0.0 {
43 return;
44 }
45 pointer_down_th_width.set(width);
46 last_width.set(width);
47 resize_width.set(Some(width));
48
49 let on_pointer_move = window_event_listener(ev::pointermove, move |e: ev::PointerEvent| {
50 let delta = f64::from(e.client_x()) - pointer_down_x.get_untracked();
51 let new_width = clamp_column_width(
52 pointer_down_th_width.get_untracked() + delta,
53 min_width,
54 max_width,
55 );
56 last_width.set(new_width);
57 resize_width.set(Some(new_width));
58 });
59 let on_mouse_move = window_event_listener(ev::mousemove, move |e: ev::MouseEvent| {
60 let delta = f64::from(e.client_x()) - pointer_down_x.get_untracked();
61 let new_width = clamp_column_width(
62 pointer_down_th_width.get_untracked() + delta,
63 min_width,
64 max_width,
65 );
66 last_width.set(new_width);
67 resize_width.set(Some(new_width));
68 });
69 let on_pointer_up = window_event_listener(ev::pointerup, move |_| {
70 let width = last_width.get_untracked();
71 if let Some(cb) = on_resize_end {
72 cb.run(width);
73 }
74 resize_width.set(Some(width));
75 listeners.update_value(|value| {
76 for handle in value.drain(..) {
77 handle.remove();
78 }
79 });
80 });
81 let on_mouse_up = window_event_listener(ev::mouseup, move |_| {
82 let width = last_width.get_untracked();
83 if let Some(cb) = on_resize_end {
84 cb.run(width);
85 }
86 resize_width.set(Some(width));
87 listeners.update_value(|value| {
88 for handle in value.drain(..) {
89 handle.remove();
90 }
91 });
92 });
93 listeners.update_value(|value| {
94 value.push(on_pointer_move);
95 value.push(on_mouse_move);
96 value.push(on_pointer_up);
97 value.push(on_mouse_up);
98 });
99 };
100
101 let on_pointer_down = move |e: ev::PointerEvent| {
102 if !resizable {
103 return;
104 }
105 e.prevent_default();
106 e.stop_propagation();
107 begin_resize(f64::from(e.client_x()));
108 };
109
110 let on_mouse_down = move |e: ev::MouseEvent| {
111 if !resizable {
112 return;
113 }
114 e.prevent_default();
115 e.stop_propagation();
116 begin_resize(f64::from(e.client_x()));
117 };
118
119 let on_dblclick = move |_| {
120 if let Some(cb) = on_autosize {
121 cb.run(());
122 }
123 };
124
125 on_cleanup(move || {
126 listeners.update_value(|value| {
127 for handle in value.drain(..) {
128 handle.remove();
129 }
130 });
131 });
132
133 view! {
134 <th
135 class=move || {
136 let extra = class.get().unwrap_or_default();
137 if extra.is_empty() {
138 "orbital-table-header-cell".to_string()
139 } else {
140 format!("orbital-table-header-cell {extra}")
141 }
142 }
143 style=move || th_style.get()
144 colspan=colspan
145 rowspan=rowspan
146 node_ref=th_ref
147 >
148 <div class="orbital-table-header-cell__button" role="presentation">
149 {if let Some(children) = children {
150 Either::Left(children())
151 } else {
152 Either::Right(())
153 }}
154 </div>
155 {if resizable {
156 Either::Left(view! {
157 <span
158 class="orbital-table-header-cell__aside"
159 on:pointerdown=on_pointer_down
160 on:mousedown=on_mouse_down
161 on:dblclick=on_dblclick
162 >
163 <div
164 class="orbital-table-resize-handle"
165 role="separator"
166 aria-hidden="true"
167 on:pointerdown=on_pointer_down
168 on:mousedown=on_mouse_down
169 on:dblclick=on_dblclick
170 ></div>
171 </span>
172 })
173 } else {
174 Either::Right(())
175 }}
176 </th>
177 }
178}