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:
23
src/app.rs
23
src/app.rs
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user