Fix context menu click-outside behavior to prevent underlying actions

When a context menu is open, clicking outside should only close the menu
without triggering underlying actions (like drag-to-create in week view).

Implementation:
- Enhanced global click handler to prevent default actions when menus are open
- Added context menu state propagation through component hierarchy
- Modified interactive components to check context menu state before acting
- Provides double protection at both global and component levels

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-29 11:58:17 -04:00
parent edb216347d
commit 4fbef8a5dc
4 changed files with 42 additions and 1 deletions

View File

@@ -138,7 +138,20 @@ pub fn App() -> Html {
let context_menu_open = context_menu_open.clone(); let context_menu_open = context_menu_open.clone();
let event_context_menu_open = event_context_menu_open.clone(); let event_context_menu_open = event_context_menu_open.clone();
let calendar_context_menu_open = calendar_context_menu_open.clone(); let calendar_context_menu_open = calendar_context_menu_open.clone();
Callback::from(move |_: MouseEvent| { Callback::from(move |e: MouseEvent| {
// Check if any context menu or color picker is open
let any_menu_open = color_picker_open.is_some() ||
*context_menu_open ||
*event_context_menu_open ||
*calendar_context_menu_open;
if any_menu_open {
// Prevent the default action and stop event propagation
e.prevent_default();
e.stop_propagation();
}
// Close all open menus/pickers
color_picker_open.set(None); color_picker_open.set(None);
context_menu_open.set(false); context_menu_open.set(false);
event_context_menu_open.set(false); event_context_menu_open.set(false);
@@ -146,6 +159,12 @@ pub fn App() -> Html {
}) })
}; };
// Compute if any context menu is open
let any_context_menu_open = color_picker_open.is_some() ||
*context_menu_open ||
*event_context_menu_open ||
*calendar_context_menu_open;
let on_color_change = { let on_color_change = {
let user_info = user_info.clone(); let user_info = user_info.clone();
let color_picker_open = color_picker_open.clone(); let color_picker_open = color_picker_open.clone();
@@ -401,6 +420,7 @@ pub fn App() -> Html {
on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())} on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())}
view={(*current_view).clone()} view={(*current_view).clone()}
on_create_event_request={Some(on_event_create.clone())} on_create_event_request={Some(on_event_create.clone())}
context_menus_open={any_context_menu_open}
/> />
</main> </main>
</> </>
@@ -415,6 +435,7 @@ pub fn App() -> Html {
on_event_context_menu={Some(on_event_context_menu.clone())} on_event_context_menu={Some(on_event_context_menu.clone())}
on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())} on_calendar_context_menu={Some(on_calendar_date_context_menu.clone())}
on_create_event_request={Some(on_event_create.clone())} on_create_event_request={Some(on_event_create.clone())}
context_menus_open={any_context_menu_open}
/> />
</div> </div>
} }

View File

@@ -23,6 +23,8 @@ pub struct CalendarProps {
pub view: ViewMode, pub view: ViewMode,
#[prop_or_default] #[prop_or_default]
pub on_create_event_request: Option<Callback<EventCreationData>>, pub on_create_event_request: Option<Callback<EventCreationData>>,
#[prop_or_default]
pub context_menus_open: bool,
} }
#[function_component] #[function_component]
@@ -209,6 +211,7 @@ pub fn Calendar(props: &CalendarProps) -> Html {
on_event_context_menu={props.on_event_context_menu.clone()} on_event_context_menu={props.on_event_context_menu.clone()}
on_calendar_context_menu={props.on_calendar_context_menu.clone()} on_calendar_context_menu={props.on_calendar_context_menu.clone()}
on_create_event={Some(on_create_event)} on_create_event={Some(on_create_event)}
context_menus_open={props.context_menus_open}
/> />
}, },
} }

View File

@@ -26,6 +26,8 @@ pub struct RouteHandlerProps {
pub view: ViewMode, pub view: ViewMode,
#[prop_or_default] #[prop_or_default]
pub on_create_event_request: Option<Callback<crate::components::EventCreationData>>, pub on_create_event_request: Option<Callback<crate::components::EventCreationData>>,
#[prop_or_default]
pub context_menus_open: bool,
} }
#[function_component(RouteHandler)] #[function_component(RouteHandler)]
@@ -37,6 +39,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html {
let on_calendar_context_menu = props.on_calendar_context_menu.clone(); let on_calendar_context_menu = props.on_calendar_context_menu.clone();
let view = props.view.clone(); let view = props.view.clone();
let on_create_event_request = props.on_create_event_request.clone(); let on_create_event_request = props.on_create_event_request.clone();
let context_menus_open = props.context_menus_open;
html! { html! {
<Switch<Route> render={move |route| { <Switch<Route> render={move |route| {
@@ -47,6 +50,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html {
let on_calendar_context_menu = on_calendar_context_menu.clone(); let on_calendar_context_menu = on_calendar_context_menu.clone();
let view = view.clone(); let view = view.clone();
let on_create_event_request = on_create_event_request.clone(); let on_create_event_request = on_create_event_request.clone();
let context_menus_open = context_menus_open;
match route { match route {
Route::Home => { Route::Home => {
@@ -72,6 +76,7 @@ pub fn route_handler(props: &RouteHandlerProps) -> Html {
on_calendar_context_menu={on_calendar_context_menu} on_calendar_context_menu={on_calendar_context_menu}
view={view} view={view}
on_create_event_request={on_create_event_request} on_create_event_request={on_create_event_request}
context_menus_open={context_menus_open}
/> />
} }
} else { } else {
@@ -94,6 +99,8 @@ pub struct CalendarViewProps {
pub view: ViewMode, pub view: ViewMode,
#[prop_or_default] #[prop_or_default]
pub on_create_event_request: Option<Callback<crate::components::EventCreationData>>, pub on_create_event_request: Option<Callback<crate::components::EventCreationData>>,
#[prop_or_default]
pub context_menus_open: bool,
} }
use gloo_storage::{LocalStorage, Storage}; use gloo_storage::{LocalStorage, Storage};
@@ -254,6 +261,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html {
on_calendar_context_menu={props.on_calendar_context_menu.clone()} on_calendar_context_menu={props.on_calendar_context_menu.clone()}
view={props.view.clone()} view={props.view.clone()}
on_create_event_request={props.on_create_event_request.clone()} on_create_event_request={props.on_create_event_request.clone()}
context_menus_open={props.context_menus_open}
/> />
</div> </div>
} }
@@ -268,6 +276,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html {
on_calendar_context_menu={props.on_calendar_context_menu.clone()} on_calendar_context_menu={props.on_calendar_context_menu.clone()}
view={props.view.clone()} view={props.view.clone()}
on_create_event_request={props.on_create_event_request.clone()} on_create_event_request={props.on_create_event_request.clone()}
context_menus_open={props.context_menus_open}
/> />
} }
} }

View File

@@ -20,6 +20,8 @@ pub struct WeekViewProps {
pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, NaiveDate)>>, pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, NaiveDate)>>,
#[prop_or_default] #[prop_or_default]
pub on_create_event: Option<Callback<(NaiveDate, NaiveDateTime, NaiveDateTime)>>, pub on_create_event: Option<Callback<(NaiveDate, NaiveDateTime, NaiveDateTime)>>,
#[prop_or_default]
pub context_menus_open: bool,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@@ -119,7 +121,13 @@ pub fn week_view(props: &WeekViewProps) -> Html {
let onmousedown = { let onmousedown = {
let drag_state = drag_state_clone.clone(); let drag_state = drag_state_clone.clone();
let context_menus_open = props.context_menus_open;
Callback::from(move |e: MouseEvent| { Callback::from(move |e: MouseEvent| {
// Don't start drag if any context menu is open
if context_menus_open {
return;
}
// Only handle left-click (button 0) // Only handle left-click (button 0)
if e.button() != 0 { if e.button() != 0 {
return; return;