use dioxus::prelude::*;
#[derive(Clone, Copy, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MarqueeRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
#[component]
pub fn MarqueeSelection(
#[props(default = true)]
enabled: bool,
#[props(default)]
on_change: Option<EventHandler<MarqueeRect>>,
#[props(default)]
on_select_indices: Option<EventHandler<Vec<usize>>>,
#[props(default)]
class: String,
children: Element,
) -> Element {
let container_id = crate::a11y::use_unique_id("marquee", None);
let box_id = format!("{container_id}-box");
use_effect(use_reactive((&enabled, &container_id, &box_id), move |(enabled, cid, bid)| {
if !enabled {
return;
}
let js = format!(
r#"
(function() {{
const container = document.getElementById("{cid}");
const box = document.getElementById("{bid}");
if (!container || !box) return;
let isDragging = false;
let startX = 0;
let startY = 0;
let currentRect = {{ x: 0, y: 0, width: 0, height: 0 }};
function getChildIndices(rect) {{
const items = container.querySelectorAll("[data-marquee-item]");
const indices = [];
const cRect = container.getBoundingClientRect();
const rLeft = cRect.left + rect.x;
const rTop = cRect.top + rect.y;
const rRight = rLeft + rect.width;
const rBottom = rTop + rect.height;
items.forEach((item) => {{
const raw = item.getAttribute("data-marquee-item");
const idx = parseInt(raw, 10);
const b = item.getBoundingClientRect();
const overlap = !(b.right < rLeft || b.left > rRight || b.bottom < rTop || b.top > rBottom);
if (overlap && !isNaN(idx)) {{
indices.push(idx);
item.classList.add("sd-marquee-item--selected");
}} else {{
item.classList.remove("sd-marquee-item--selected");
}}
}});
return indices;
}}
function onPointerDown(e) {{
if (e.button !== 0) return;
const cRect = container.getBoundingClientRect();
startX = e.clientX - cRect.left;
startY = e.clientY - cRect.top;
isDragging = true;
try {{
container.setPointerCapture(e.pointerId);
}} catch (_) {{}}
currentRect = {{ x: startX, y: startY, width: 0, height: 0 }};
box.style.left = startX + "px";
box.style.top = startY + "px";
box.style.width = "0px";
box.style.height = "0px";
box.style.display = "none";
}}
function onPointerMove(e) {{
if (!isDragging) return;
const cRect = container.getBoundingClientRect();
const curX = e.clientX - cRect.left;
const curY = e.clientY - cRect.top;
const x = Math.min(startX, curX);
const y = Math.min(startY, curY);
const width = Math.abs(curX - startX);
const height = Math.abs(curY - startY);
currentRect = {{ x: x, y: y, width: width, height: height }};
if (width > 3 || height > 3) {{
box.style.display = "block";
box.style.left = x + "px";
box.style.top = y + "px";
box.style.width = width + "px";
box.style.height = height + "px";
getChildIndices(currentRect);
}}
}}
function onPointerUp(e) {{
if (!isDragging) return;
isDragging = false;
try {{
container.releasePointerCapture(e.pointerId);
}} catch (_) {{}}
const hadMoved = (currentRect.width > 3 || currentRect.height > 3);
box.style.display = "none";
if (hadMoved) {{
const indices = getChildIndices(currentRect);
dioxus.send({{
x: currentRect.x,
y: currentRect.y,
width: currentRect.width,
height: currentRect.height,
indices: indices
}});
}}
}}
container.addEventListener("pointerdown", onPointerDown);
container.addEventListener("pointermove", onPointerMove);
container.addEventListener("pointerup", onPointerUp);
container.addEventListener("pointercancel", onPointerUp);
}})();
"#
);
spawn(async move {
let mut eval = document::eval(&js);
while let Ok(msg) = eval.recv::<serde_json::Value>().await {
if let Some(obj) = msg.as_object() {
let x = obj.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
let y = obj.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
let width = obj.get("width").and_then(|v| v.as_f64()).unwrap_or(0.0);
let height = obj.get("height").and_then(|v| v.as_f64()).unwrap_or(0.0);
let rect = MarqueeRect { x, y, width, height };
if let Some(h) = on_change {
h.call(rect);
}
if let Some(h) = on_select_indices {
if let Some(arr) = obj.get("indices").and_then(|v| v.as_array()) {
let indices: Vec<usize> = arr
.iter()
.filter_map(|v| v.as_u64().map(|n| n as usize))
.collect();
h.call(indices);
}
}
}
}
});
}));
rsx! {
div {
id: "{container_id}",
class: "sd-marquee-container {class}",
{children}
div {
id: "{box_id}",
class: "sd-marquee-box",
style: "display: none;",
}
}
}
}