Implement smart context menu positioning
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>
This commit is contained in:
@@ -18,9 +18,45 @@ pub fn calendar_context_menu(props: &CalendarContextMenuProps) -> Html {
|
||||
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;
|
||||
|
||||
// Calendar context menu: "Create Event" with icon
|
||||
let menu_width = 180; // "Create Event" text + icon + 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;",
|
||||
props.x, props.y
|
||||
x, y
|
||||
);
|
||||
|
||||
let on_create_event_click = {
|
||||
|
||||
@@ -20,9 +20,45 @@ pub fn context_menu(props: &ContextMenuProps) -> Html {
|
||||
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;",
|
||||
props.x, props.y
|
||||
x, y
|
||||
);
|
||||
|
||||
let on_delete_click = {
|
||||
|
||||
@@ -35,9 +35,53 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
|
||||
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;
|
||||
|
||||
// More accurate menu dimensions based on actual CSS and content
|
||||
let menu_width = if props.event.as_ref().map_or(false, |e| e.rrule.is_some()) {
|
||||
280 // Recurring: "Edit This and Future Events" is long text + padding
|
||||
} else {
|
||||
180 // Non-recurring: "Edit Event" + "Delete Event" + padding
|
||||
};
|
||||
let menu_height = if props.event.as_ref().map_or(false, |e| e.rrule.is_some()) {
|
||||
200 // 6 items × ~32px per item (12px padding top/bottom + text height + borders)
|
||||
} else {
|
||||
100 // 2 items × ~32px per item + some extra margin
|
||||
};
|
||||
|
||||
// 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;",
|
||||
props.x, props.y
|
||||
x, y
|
||||
);
|
||||
|
||||
// Check if the event is recurring
|
||||
|
||||
Reference in New Issue
Block a user