Added intelligent viewport boundary detection that repositions context menus when they would appear outside the screen: - Detects right/bottom edge overflow and repositions menus accordingly - Uses accurate size estimates based on actual menu content - Event menus: 280×200px (recurring) / 180×100px (non-recurring) - Calendar/generic menus: 180×60px for single items - Maintains 5px minimum margins from screen edges - Graceful fallback to original positioning if viewport detection fails 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
85 lines
2.5 KiB
Rust
85 lines
2.5 KiB
Rust
use web_sys::MouseEvent;
|
|
use yew::prelude::*;
|
|
|
|
#[derive(Properties, PartialEq)]
|
|
pub struct ContextMenuProps {
|
|
pub is_open: bool,
|
|
pub x: i32,
|
|
pub y: i32,
|
|
pub on_delete: Callback<MouseEvent>,
|
|
pub on_close: Callback<()>,
|
|
}
|
|
|
|
#[function_component(ContextMenu)]
|
|
pub fn context_menu(props: &ContextMenuProps) -> Html {
|
|
let menu_ref = use_node_ref();
|
|
|
|
// Close menu when clicking outside (handled by parent component)
|
|
|
|
if !props.is_open {
|
|
return html! {};
|
|
}
|
|
|
|
// Smart positioning to keep menu within viewport
|
|
let (x, y) = {
|
|
let mut x = props.x;
|
|
let mut y = props.y;
|
|
|
|
// Try to get actual viewport dimensions
|
|
if let Some(window) = web_sys::window() {
|
|
if let (Ok(width), Ok(height)) = (window.inner_width(), window.inner_height()) {
|
|
if let (Some(w), Some(h)) = (width.as_f64(), height.as_f64()) {
|
|
let viewport_width = w as i32;
|
|
let viewport_height = h as i32;
|
|
|
|
// Generic context menu: "Delete Calendar"
|
|
let menu_width = 180; // "Delete Calendar" text + padding
|
|
let menu_height = 60; // Single item + padding + margins
|
|
|
|
// Adjust horizontally if too close to right edge
|
|
if x + menu_width > viewport_width - 10 {
|
|
x = x.saturating_sub(menu_width);
|
|
}
|
|
|
|
// Adjust vertically if too close to bottom edge
|
|
if y + menu_height > viewport_height - 10 {
|
|
y = y.saturating_sub(menu_height);
|
|
}
|
|
|
|
// Ensure minimum margins from edges
|
|
x = x.max(5);
|
|
y = y.max(5);
|
|
}
|
|
}
|
|
}
|
|
|
|
(x, y)
|
|
};
|
|
|
|
let style = format!(
|
|
"position: fixed; left: {}px; top: {}px; z-index: 1001;",
|
|
x, y
|
|
);
|
|
|
|
let on_delete_click = {
|
|
let on_delete = props.on_delete.clone();
|
|
let on_close = props.on_close.clone();
|
|
Callback::from(move |e: MouseEvent| {
|
|
on_delete.emit(e);
|
|
on_close.emit(());
|
|
})
|
|
};
|
|
|
|
html! {
|
|
<div
|
|
ref={menu_ref}
|
|
class="context-menu"
|
|
style={style}
|
|
>
|
|
<div class="context-menu-item context-menu-delete" onclick={on_delete_click}>
|
|
{"Delete Calendar"}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|