Repository navigation
Description
Summary
Clicking an event block always opens the built-in EventDialog. There is no prop to stop that or to
substitute our own UI: EventCalendarPremiumProps and EventTimelinePremiumProps expose no slots,
slotProps, renderEvent, onEventClick or onEventDoubleClick, and apiRef exposes only
setVisibleDate (plus goToNextVisibleDate / goToPreviousVisibleDate on the Timeline).
This is not the same request as #22866. That umbrella makes the General tab composable inside
MUI's dialog, and explicitly lists "full form replacement (Option C)" as out of scope. We do not want
to compose sections into your dialog — we need the dialog not to open, so we can open our own
application drawer, which carries our own domain fields, permissions and save path.
#22547 — Phase 2 — Add slots.eventDialog (replace the edit modal) is exactly this, and is currently
closed / "on hold".
Two ways to unblock it, cheapest first. Either one is enough for us.
Option 1 — emit the occurrence key on the Calendar event block (tiny, no new API)
TimelineGridEvent already emits data-occurrence-key, via
TimelineGridEventDataAttributes.occurrenceKey, for its own keyboard navigation.
CalendarGridTimeEvent emits no equivalent — the rendered block carries only role, tabindex,
data-palette, a React-generated id and aria-labelledby, none of which identifies the event.
Because the dialog's trigger binds onClick directly onto the block, a capture-phase listener can
already suppress the dialog. On the Timeline that is all we need, because the attribute tells us
which event was clicked. On the Calendar the same interception works but we cannot identify the
event, so it is useless.
Emitting the same attribute on the Calendar block would be a one-line parity fix, commits you to no
new public API, and would let both surfaces use ~6 lines of consumer code.
Option 2 — reopen slots.eventDialog (#22547), or add onEventClick
The cleaner long-term answer. onEventClick(occurrence, event) with the ability to prevent the
default dialog would also do it, and is a smaller surface than a full slot.
Examples
We are building a dispatch board on EventTimelinePremium (driver lanes) plus EventCalendarPremium
(week/month over the same data). Our acceptance criterion is that clicking a block opens our
scheduling drawer — the same one used elsewhere in the product, with our fields, our permission
gating and our persistence.
What we had to do to get there today:
- Timeline: capture-phase
onClickinterception keyed ondata-occurrence-key. ~6 lines, works,
but depends on an attribute you document as internal to keyboard navigation — if it is renamed our
drawer silently stops opening. - Calendar (week/month): no such attribute, so instead we rebuild what
EventCalendarPremium
does —EventCalendarProvider+ the three styled contexts +EventDialogProvider+
EventCalendarRoot— purely so we can place one child insideEventDialogProviderthat reads
useEventDialogContext()and closes the dialog in auseLayoutEffectbefore it paints.
EventCalendarPremiummounts the provider itself and overrideschildrenwith the licence
watermark, so there is no other way in.
That second workaround costs three extra direct dependencies, because @mui/x-scheduler/internals
does not resolve from an app that depends only on @mui/x-scheduler-premium:
"@mui/x-scheduler": "9.0.0-beta.7",
"@mui/x-scheduler-internals": "9.0.0-beta.7",
"@mui/x-scheduler-internals-premium": "9.0.0-beta.7",
Related packaging problem
Those four packages are not published in lockstep. As of today @mui/x-scheduler and
@mui/x-scheduler-premium are at 9.0.0-beta.9, while @mui/x-scheduler-internals-premium is only
at 9.0.0-beta.8. Anyone following the documented subpath exports cannot pin a single consistent
version across the set. Worth fixing regardless of the above.
Motivation
Replacing the edit UI is a baseline requirement for embedding a scheduler in an existing product. Our
drawer is not a styled variant of yours — it carries domain fields, role-based gating and a save path
that has to match the rest of the application.
Today the only routes are (a) a DOM-attribute hack that fails silently, available on one component
only, or (b) re-implementing a shipped component so we can inject a single child. Both are things we
would rather not own, and both are fragile across your beta releases.
A single attribute (Option 1) unblocks us with no API commitment on your side. We are a Premium
licence holder and happy to test a canary.
Search keywords: scheduler, event dialog, slots.eventDialog, onEventClick, custom edit UI, data-occurrence-key, EventTimelinePremium, EventCalendarPremium
Order ID: 127623
Activity
Anexus5919 commentedon Aug 14, 2026
I checked each claim in this issue against current
master. Summary of what holds, what needs correcting, and a proposal that fits the existing scheduler conventions.Verified
slots/slotProps/renderEvent/onEventClickon the public propsapiRefexposes onlysetVisibleDate, plusgoToNext/PreviousVisibleDateon the TimelineSchedulerStore.buildPublicAPI,EventTimelinePremiumStore.buildPublicAPI)TimelineGridEventemitsdata-occurrence-key,CalendarGridTimeEventdoes notTimelineGridEvent.tsx:180)onClickdirectly on the block, so capture-phase interception worksEventEditingContext.tsx:94-100)EventCalendarPremiummounts the provider and overrideschildrenwith the watermarkEventCalendarPremium.tsx:81-85)9.0.0-beta.9vs9.0.0-beta.8on npm todayThree corrections
Option 1 is not a one line change. Six call sites open the editing surface, across four components:
CalendarGridTimeEventCalendarGridDayEventEventItem, a plain styled div rather than a grid primitiveTimelineGridEvent, the only one emitting the attributeMonth goes through
CalendarGridDayEvent, so patchingCalendarGridTimeEventalone would not cover the week plus month setup described here.The Calendar workaround in the report no longer compiles on
master.useEventDialogContextwas renamed touseEventEditingContextand the provider was restructured around a surface-agnosticEventEditingProvider. The fragility concern is already real rather than hypothetical.There is a public escape hatch that avoids the internals packages entirely.
SchedulerEvent.classNameis a documented model field and is applied on all four renderers (TimeGridEvent.tsx:562,DayGridEvent.tsx:417,EventItem.tsx:334,EventTimelinePremiumEvent.tsx:170). Both props types extendReact.HTMLAttributes<HTMLDivElement>and the rest props reach the root node, soonClickCaptureon the component works today. That removes the three extra dependencies. Two limits: the "+N more" popover is portaled, so a root capture handler misses it, andclassNameidentifies the event rather than the individual occurrence.Proposal
onEventClickwith cancellation, reusing the pattern the store already uses foronVisibleDateChange,onViewChange,onVisibleResourcesChangeandonCollapsedResourcesChange:Applied here, on
SchedulerParameters:Fired from
EventEditingTrigger, the single funnel every event activation passes through:Why this shape:
startEditingdirectly from their own effects, so they correctly do not fire it.SchedulerParametersis the shared base ofEventCalendarParametersandEventTimelinePremiumParameters, so the prop reaches the Calendar, the Premium Calendar, the Timeline and the standalone views with no prop drilling.useButtonsynthesizes the click for Enter and Space.pnpm proptypesandpnpm docs:api.This does not touch
slots.eventDialog(#22547) or the General-tab slot (#22871). It only decides whether the built-in surface opens at all, which is the part neither of those covers.I can open a PR for this if the direction looks right.
Packaging
The version skew is real, but narrower than it looks.
workspace:^publishes as^9.0.0-beta.8, which does resolve9.0.0-beta.9, so normal consumers are unaffected. It only bites when the internals packages are added to an applicationpackage.json, which is exactly what the missing API forces today. Probably worth its own issue.cc @JCQuintas @LukasTy @rita-codes
rita-codes commentedon Aug 17, 2026
Thanks for the detailed report, and apologies for the wait @john-renterra . The use case is legitimate, embedding the scheduler in a product that already has its own editing UI is something we want to support properly, and you shouldn't have to own those workarounds.
We're going with the spirit of your Option 2, with one change: instead of
onEventClick, we'll add a cancelableonEventEditingStartcallback, available on all scheduler components:The difference: rather than guarding one entry point (the click), it fires whenever the built-in editing UI is about to open, no matter what triggered it — pointer, keyboard, touch, the "+N more" popover, and any entry point we add in the future (context menu, imperative API) — so your drawer can't silently stop working when we ship new features. Calling
eventDetails.cancel()prevents our dialog (and the mobile drawer) from opening; if you don't cancel, everything behaves as it does today. It also fires for event creation, so you can intercept a click on an empty slot and open your own creation UI prefilled with the proposed time instead of ours.One note on scope: cancelling replaces the built-in dialog — your UI owns the editing form, the recurring-event scope selection when saving from it, and persistence. Everything else keeps the built-in behavior: drag and drop, resize, the recurring scope dialog they trigger, and on touch devices the event toolbar (edit, delete and resize affordances) — there, the callback fires when the user taps the toolbar's Edit action.
@Anexus5919 thanks a lot for the thorough analysis, it checks out, and the cancellation mechanism you proposed is exactly what we're shipping; we're just moving the insertion point deeper so it covers every entry path. I'll take the implementation from here so we can move fast, since it needs a couple of product decisions I can sync with the team on directly.
Once this ships you can drop the internals packages entirely, which also takes care of the version skew concern 🙌
I'll link the PR here shortly!
Anexus5919 commentedon Aug 17, 2026
Makes sense 👍
@rita-codes One thing worth checking on the creation case. Both creation entry points only set the placeholder and never clear it (
useEventCreation.ts:34for pointer,useKeyboardEventCreation.ts:32for keyboard); the only reachable clear isstopEditing()(the othersetOccurrencePlaceholder(null)calls are all drag and resize paths). So on a cancel, editing never starts, nothing clears the placeholder, and the creation preview stays on the grid.The re-fire guard has the same root.
isEditingPlaceholderinTimeGridColumn.tsx:176-182(same shape inDayGridCellandMonthViewCell) only flips oncestore.startEditinghas run, so a cancel leaves it false and the churn it was added to absorb comes back.Smaller one: since it fires for both cases, consumers need to tell create from edit, and
(occurrence, eventDetails)doesn't carry that.BaseUIChangeEventDetailsis generic over its reason string if you want it explicit.rita-codes commentedon Aug 18, 2026
Good points @Anexus5919 🙌
The first two are already handled in my implementation: the cancel branch in
startEditingdrops the creation placeholder, so no stale preview on the grid and the re-fire guard never comes into play, there's a test pinning both.The reason suggestion is a good catch, adopting it! The callback will fire with
eventDetails.reason: 'creation' | 'edit'so consumers don't have to infer the case from the occurrence. It'll be in the PR.rita-codes commentedon Aug 18, 2026
Heyy @john-renterra ! The implementation is up in #23361 and you can already try it against your app:
Those are preview builds of the PR's latest commit, with all four packages pinned to the same version, no internals packages needed anymore, and the version skew issue can't occur here.
What you get:
The callback fires right before the built-in dialog opens, for every entry point (pointer, keyboard, touch, the "+N more" popover..) on both the Calendar and the Timeline. It also fires for event creation (
eventDetails.reasonis"creation", with the proposed dates on the occurrence), so you can open your own creation UI prefilled instead of disabling creation altogether. Docs for the released version will live on the Editing pages of both components.If everything works on your side, please confirm here, barring surprises this should make this week's release. And if you hit anything, this is the perfect moment to tell us 🙌
john-renterra commentedon Aug 20, 2026
Thank you for the incredibly quick turnaround on this, @rita-codes — going from filed to a working PR in a matter of days is hugely appreciated.
We pulled #23361 down and validated it against our use case, and onEventEditingStart is exactly what we needed. Being able to cancel() the built-in dialog and open our own edit UI — across pointer, keyboard, and creation, with the edit/view/creation reason and the read-only handling — covers both surfaces we were struggling with. On our side this lets us delete a ~90-line reimplementation of EventCalendarPremium on the Week/Month views and remove a fragile capture-phase click interception on the Timeline that depended on the undocumented data-occurrence-key attribute, along with the extra internal-package dependencies those workarounds pulled in.
Looking forward to seeing it land. Thanks again to you and @flaviendelangle for the fast, thorough work.
github-actions commentedon Aug 21, 2026
This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue.
Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.
@john-renterra Thanks again for creating this issue! If you have a moment, we would love to hear your thoughts on how we handled it with this short feedback form.
rita-codes commentedon Aug 21, 2026
Thanks a lot for the kind words and for taking the time to validate the PR against your use case! Really glad to hear the new API lets you get rid of those workarounds 👏
The feature has just been merged! We’re planning to publish a new release today, so it should be available as soon as the new version is out.
Thanks again for the detailed feedback! :)