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
105
106
#![doc = include_str!("../docs/api/debugging.md")]
use dioxus::prelude::*;
use crate::core::components::drop_query;
use crate::core::{use_dnd, use_joined_window, use_zone_registry};
/// Draws every registered zone of one payload world as a tinted outline
/// (color derived from the zone id, so it's stable across renders), with
/// the zone's label and id in a tag, live `data-over` highlighting, and
/// per-zone acceptance state while a drag is in flight. Render one per
/// provider, anywhere inside it. **Dev-only** - see the module docs.
///
/// Click-through by design (`pointer-events: none`), so it never changes
/// the interaction it inspects. Zones the registry hasn't measured yet
/// draw no outline; the chip counts them so absence is visible too.
#[component]
pub fn DndDebugOverlay<T: Clone + PartialEq + 'static>(
/// Internal marker; never set this.
#[props(default)]
phantom: std::marker::PhantomData<T>,
) -> Element {
let _ = phantom;
let dnd = use_dnd::<T>();
let joined = use_joined_window::<T>();
let registry = use_zone_registry::<T>();
// The core only measures rects at drag start; an inspector wants
// outlines while idle. Re-measure whenever the zone set changes or a
// zone's DOM handle arrives. The registry exposes a separate revision
// for those events so the rect writes this triggers cannot loop.
use_effect(move || {
registry.track_mounts();
registry.refresh_rects();
});
let payload = dnd.payload();
let over = dnd.over();
let records = registry.records();
let unmeasured = records.iter().filter(|z| z.rect.is_none()).count();
let status = match (dnd.dragging(), over) {
(false, _) => format!("{} zones ({unmeasured} unmeasured) - idle", records.len()),
(true, Some(z)) => format!("dragging - over zone {}", z.0),
(true, None) => "dragging - over nothing".to_string(),
};
rsx! {
div {
"data-dnd-debug": "true",
style: "position: fixed; inset: 0; pointer-events: none; z-index: 9998; \
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;",
for record in records {
{
let id = record.id;
let rect = record.cached_rect();
// Stable per-id tint; the multiplier scatters neighbors
// around the wheel.
let hue = (id.0.wrapping_mul(47)) % 360;
let accepts = payload.as_ref().map(|payload| {
let query = drop_query(&dnd, payload.clone(), dnd.proposed_effect());
registry.negotiate_zone(id, &query).is_some()
});
let is_over = match joined {
Some(joined) => joined.is_over(id),
None => over == Some(id),
};
let name = record.label.clone().unwrap_or_else(|| "zone".to_string());
rsx! {
if let Some(r) = rect {
div {
key: "{id.0}",
"data-debug-zone": "{id.0}",
"data-over": if is_over { "true" },
"data-accepts": accepts.map(|a| if a { "true" } else { "false" }),
style: format!(
"position: fixed; left: {}px; top: {}px; width: {}px; height: {}px; \
box-sizing: border-box; border: 2px {} hsl({hue} 70% 42%); \
background: hsl({hue} 70% 42% / {}); opacity: {};",
r.x, r.y, r.width, r.height,
if accepts == Some(false) { "dashed" } else { "solid" },
if is_over { "0.18" } else { "0.04" },
if accepts == Some(false) { "0.45" } else { "1" },
),
span {
style: "position: absolute; top: 0; left: 0; transform: translateY(-100%); \
background: hsl({hue} 70% 42%); color: #fff; font-size: 10px; \
line-height: 1.6; padding: 0 4px; white-space: nowrap;",
"{name} #{id.0}"
if accepts == Some(false) { " - rejects" }
if is_over { " - over" }
}
}
}
}
}
}
div {
"data-debug-status": "true",
style: "position: fixed; right: 8px; bottom: 8px; background: #1a1a1a; color: #fff; \
font-size: 11px; line-height: 1; padding: 6px 8px; border-radius: 6px; \
opacity: 0.85;",
"{status}"
}
}
}
}