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
//! Context menu component for the data grid.
use leptos::prelude::*;
/// Actions available from the context menu.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextAction {
/// Copy selected rows as TSV.
Copy,
/// Select all rows.
SelectAll,
/// Download visible/selected rows as a CSV file.
Download,
}
/// Placement coordinates for the context menu.
#[derive(Clone, Copy, Debug)]
pub struct MenuPosition {
/// Page X coordinate.
pub x: f64,
/// Page Y coordinate.
pub y: f64,
}
/// Context menu component rendered inside the grid container.
///
/// Positioned at `(x, y)` in page coordinates. Backdrop covers
/// the full viewport to catch dismiss clicks.
#[component]
pub fn GridContextMenu(
/// Position of the menu (None = hidden).
position: Signal<Option<MenuPosition>>,
/// Callback when an action is selected.
on_action: Callback<ContextAction>,
/// Callback to close the menu.
on_close: Callback<()>,
/// Number of currently selected rows.
selected_count: Signal<usize>,
) -> impl IntoView {
let copy_label = Signal::derive(move || {
let count = selected_count.get();
if count > 0 {
format!("Copy ({count} rows)")
} else {
"Copy".to_string()
}
});
view! {
<Show when=move || position.get().is_some()>
{move || {
let Some(pos) = position.get() else {
return view! { <div /> }.into_any();
};
view! {
<div
class="dg-context-backdrop"
on:pointerdown=move |_| on_close.run(())
on:contextmenu=move |ev| {
ev.prevent_default();
on_close.run(());
}
/>
<div
class="dg-context-menu"
style:left=format!("{}px", pos.x)
style:top=format!("{}px", pos.y)
>
<button
class="dg-context-item"
on:click=move |_| {
on_action.run(ContextAction::Copy);
on_close.run(());
}
>
{copy_label}
<span class="dg-context-shortcut">{"Ctrl+C"}</span>
</button>
<button
class="dg-context-item"
on:click=move |_| {
on_action.run(ContextAction::SelectAll);
on_close.run(());
}
>
"Select All"
<span class="dg-context-shortcut">{"Ctrl+A"}</span>
</button>
<button
class="dg-context-item"
on:click=move |_| {
on_action.run(ContextAction::Download);
on_close.run(());
}
>
"Download CSV"
<span class="dg-context-shortcut">{"Ctrl+S"}</span>
</button>
</div>
}.into_any()
}}
</Show>
}
}