Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
de44e80
[dialog] Implement openEventId management for event dialog interactions
mustafajw07 May 19, 2026
8774b84
[scheduler][eventDialog] Add openEventId management to SchedulerStore…
mustafajw07 May 20, 2026
eaf622f
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 May 20, 2026
b0c4149
[scheduler][eventDialog] updated EventDialog and test cases
mustafajw07 May 20, 2026
517b0f3
[scheduler][SchedulerStore] Refactor SchedulerState to include recurr…
mustafajw07 May 21, 2026
689dfc8
[scheduler][createModal] Enhance modal functionality with imperative …
mustafajw07 May 26, 2026
3457d1a
refactor(EventDialog): format imports and improve readability of impe…
mustafajw07 May 26, 2026
15dd7be
[test] Update EventDialog tests to use mouseDown for ClickAwayListene…
mustafajw07 May 26, 2026
31c1086
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 May 27, 2026
a4ce114
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 May 29, 2026
fd651ff
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 May 30, 2026
6972f29
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 Jun 2, 2026
df990f1
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 Jun 3, 2026
f4cb40b
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 Jun 4, 2026
cb15dc0
Merge branch 'master' into feature/21921-Allow-interacting-with-event…
mustafajw07 Jun 8, 2026
2d28094
[fix] Update ClickAwayListener mouse event to 'onClick' for correct b…
mustafajw07 Jun 8, 2026
3e61437
[fix] Update ClickAwayListener interaction to use 'onClick' for closi…
mustafajw07 Jun 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/pages/x/api/scheduler/event-calendar-premium.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"props": {
"apiRef": {
"type": { "name": "shape", "description": "{ current?: { setVisibleDate?: func } }" }
"type": {
"name": "shape",
"description": "{ current?: { setOpenEventId?: func, setVisibleDate?: func } }"
}
},
"areEventsDraggable": { "type": { "name": "bool" }, "default": "true" },
"areEventsResizable": {
Expand Down
5 changes: 4 additions & 1 deletion docs/pages/x/api/scheduler/event-calendar.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"props": {
"apiRef": {
"type": { "name": "shape", "description": "{ current?: { setVisibleDate?: func } }" }
"type": {
"name": "shape",
"description": "{ current?: { setOpenEventId?: func, setVisibleDate?: func } }"
}
},
"areEventsDraggable": { "type": { "name": "bool" }, "default": "true" },
"areEventsResizable": {
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/x/api/scheduler/event-timeline-premium.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"apiRef": {
"type": {
"name": "shape",
"description": "{ current?: { goToNextVisibleDate?: func, goToPreviousVisibleDate?: func, setVisibleDate?: func } }"
"description": "{ current?: { goToNextVisibleDate?: func, goToPreviousVisibleDate?: func, setOpenEventId?: func, setVisibleDate?: func } }"
}
},
"areEventsDraggable": { "type": { "name": "bool" }, "default": "true" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('Core - EventTimelinePremiumStore', () => {
eventModelStructure: undefined,
displayTimezone: 'default',
editedEventId: null,
openEventId: null,
nowUpdatedEveryMinute: adapter.now('default'),
occurrencePlaceholder: null,
pendingUpdateRecurringEventParameters: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export class SchedulerStore<
adapter.startOfDay(adapter.now(stateFromParameters.displayTimezone)),
errors: [],
isLoading: !!parameters.dataSource,
openEventId: null,
recurringEventsPlugin,
};

Expand Down Expand Up @@ -691,12 +692,21 @@ export class SchedulerStore<
this.set('editedEventId', eventId);
};

/**
* Sets the ID of the event currently open in the event dialog.
* Pass `null` to clear the open event.
*/
public setOpenEventId = (eventId: SchedulerEventId | null) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to only change the store state but doesn't open the dialog?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated the implementation to ensure the dialog open state is handled correctly alongside the selected event state.

this.set('openEventId', eventId);
};

/**
* Builds an object containing the methods that should be exposed publicly by the scheduler components.
*/
public buildPublicAPI() {
return {
setVisibleDate: this.setVisibleDate,
setOpenEventId: this.setOpenEventId,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ export interface SchedulerState<TEvent extends object = any> {
* directly as a React key and as the argument to `store.dismissError(key)`.
*/
errors: readonly StoredError[];
/**
* The ID of the event currently open in the event dialog.
* `null` when no event dialog is open.
*/
openEventId: SchedulerEventId | null;
/**
* Plugin that provides recurring-events support. `null` when not attached.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ export function useEventCreation(
}

const handler = (event: React.MouseEvent<HTMLDivElement>) => {
const { occurrencePlaceholder, openEventId } = store.state;
if (occurrencePlaceholder?.type === 'creation' || openEventId !== null) {
store.setOccurrencePlaceholder(null);
return;
}

const target = event.target as HTMLElement;
// Don't create events when clicking on existing events
if (target !== event.currentTarget && target.closest('button, [role="button"]')) {
return;
}

event.stopPropagation();
store.setOccurrencePlaceholder({
type: 'creation',
...getCreationPlaceholder({ event, creationConfig }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export const schedulerOtherSelectors = {
),
isLoading: createSelector((state: State) => state.isLoading),
errors: createSelector((state: State) => state.errors),
openEventId: createSelector((state: State) => state.openEventId),
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('Core - EventCalendarStore', () => {
editedEventId: null,
nowUpdatedEveryMinute: adapter.now('default'),
occurrencePlaceholder: null,
openEventId: null,
pendingUpdateRecurringEventParameters: null,
preferences: EMPTY_OBJECT,
preferencesMenuConfig: DEFAULT_PREFERENCES_MENU_CONFIG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ EventCalendarPremium.propTypes = {
*/
apiRef: PropTypes.shape({
current: PropTypes.shape({
setOpenEventId: PropTypes.func,
setVisibleDate: PropTypes.func,
}),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ EventTimelinePremium.propTypes = {
current: PropTypes.shape({
goToNextVisibleDate: PropTypes.func,
goToPreviousVisibleDate: PropTypes.func,
setOpenEventId: PropTypes.func,
setVisibleDate: PropTypes.func,
}),
}),
Expand Down
1 change: 1 addition & 0 deletions packages/x-scheduler/src/event-calendar/EventCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ EventCalendar.propTypes = {
*/
apiRef: PropTypes.shape({
current: PropTypes.shape({
setOpenEventId: PropTypes.func,
setVisibleDate: PropTypes.func,
}),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ export function createModal<TData>(config: CreateModalConfig) {
return context;
}

const EMPTY_ANCHOR_REF: React.RefObject<HTMLElement | null> = { current: null };

function Provider(props: ProviderProps<TData>) {
const { children, render, onOpen: onOpenProp, onClose: onCloseProp } = props;
const { children, render, onOpen: onOpenProp, onClose: onCloseProp, imperativeRef } = props;
const anchorRef = React.useRef<HTMLElement | null>(null);
const eventManager = React.useRef(new EventManager());

Expand All @@ -48,6 +50,19 @@ export function createModal<TData>(config: CreateModalConfig) {
eventManager.current.emit('close');
});

React.useImperativeHandle(
imperativeRef,
() => ({
open(data: TData, forwardedAnchorRef?: React.RefObject<HTMLElement | null>) {
onOpen(forwardedAnchorRef ?? EMPTY_ANCHOR_REF, data);
},
close() {
onClose();
},
}),
[onOpen, onClose],
);

const subscribeCloseHandler = React.useCallback((handler: () => void) => {
eventManager.current.on('close', handler);
return () => {
Expand Down Expand Up @@ -86,13 +101,19 @@ export function createModal<TData>(config: CreateModalConfig) {
ref: React.ForwardedRef<HTMLElement | null>,
) {
const { data, onClick, children } = props;
const { onOpen } = useModalContext();
const { onOpen, onClose, isOpen, data: currentData } = useModalContext();

return React.cloneElement(children as React.ReactElement<any>, {
ref,
onClick: (event: React.MouseEvent) => {
onClick?.(event);
onOpen(ref as React.RefObject<HTMLElement | null>, data);
const dataKey = (data as any)?.key;
const currentKey = (currentData as any)?.key;
if (isOpen && dataKey != null && dataKey === currentKey) {
onClose();
} else {
onOpen(ref as React.RefObject<HTMLElement | null>, data);
}
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ export type ContextValue<TData> = {
subscribeCloseHandler: (handler: () => void) => () => void;
};

export interface ModalImperativeHandle<TData> {
/**
* Imperatively open the modal with the given data and optional anchor element.
*/
open: (data: TData, anchorRef?: React.RefObject<HTMLElement | null>) => void;
/**
* Imperatively close the modal.
*/
close: () => void;
}

export interface ProviderProps<TData> {
children: React.ReactNode;
/**
Expand All @@ -37,6 +48,10 @@ export interface ProviderProps<TData> {
}) => React.ReactNode;
onOpen?: (data: TData) => void;
onClose?: () => void;
/**
* Ref that exposes imperative open/close methods.
*/
imperativeRef?: React.Ref<ModalImperativeHandle<TData>>;
}

export interface TriggerProps<TData> {
Expand Down
Loading
Loading