[scheduler] preserve multi-day event continuity across overflow#22797
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
|
Hey @rita-codes , Can i get a review on this? |
There was a problem hiding this comment.
Pull request overview
This PR adjusts the Scheduler day-grid positioning logic to preserve multi-day event continuity when rows “compact” (e.g., when earlier events end and lower row indexes become available), which is a key piece of fixing Month view overflow behavior where a continuation bar could disappear mid-span.
Changes:
- Reworked
useEventOccurrencesWithDayGridPositionto allow multi-day events to “re-segment” into a lower available row by shortening the previous segment and starting a new segment. - Updated internal positioning tests to reflect the new segmented behavior (and added/adjusted assertions).
- Updated WeekView all-day column-span expectation to reflect the new segmented span behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/x-scheduler/src/week-view/tests/WeekView.test.tsx | Updates all-day span expectation; needs comment alignment with new segmented behavior. |
| packages/x-scheduler-internals/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.ts | Core change: track active segments and split/compact multi-day bars when lower rows open up. |
| packages/x-scheduler-internals/src/use-event-occurrences-with-day-grid-position/useEventOccurrencesWithDayGridPosition.test.ts | Test expectations updated; some descriptions/comments need to be brought in sync with new behavior. |
| packages/x-scheduler-internals/src/calendar-grid/use-placeholder-in-day/useCalendarGridPlaceholderInDay.ts | Placeholder index selection updated to avoid always using row 1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Should span 4 columns (4 days) | ||
| expect(gridColumnSpan).to.equal('4'); | ||
| expect(gridColumnSpan).to.equal('3'); |
|
|
||
| expect(result.maxIndex).to.equal(2); | ||
| expect(result.days[1].withPosition[1].id).to.equal('B'); | ||
| expect(result.days[1].withPosition[1].position).to.deep.equal({ index: 2, daySpan: 2 }); | ||
| expect(result.days[1].withPosition[1].position).to.deep.equal({ index: 2, daySpan: 1 }); | ||
| expect(result.days[2].withPosition[0].id).to.equal('B'); |
| // Event A is not present on day 3, so event C should use index 1 on that day instead of using index 3 below Event B | ||
| expect(result.maxIndex).to.equal(2); | ||
| expect(result.days[2].withPosition[0].id).to.equal('C'); | ||
| expect(result.days[2].withPosition[0].id).to.equal('B'); | ||
| expect(result.days[2].withPosition[0].position).to.deep.equal({ index: 1, daySpan: 1 }); | ||
| }); |
|
Thanks for tackling this, @mustafajw07! I caught a regression in the Argos diff we should sort out before this goes in. In the week view, The compaction in
This ties into the two changes we agreed on in yesterday's sync:
Also please make sure drag & drop stays accurate with whatever new ordering/index logic you land on. What do you think? If you have any doubts or would prefer we take it over, just let me know 🙌
|
|
Thanks for the detailed review and for catching the regression. You're right — the current activeSegments compaction logic reassigns a lower index whenever one becomes available, without distinguishing between normal compaction and an event that was previously hidden by overflow. That explains the week-view regression where the bar gets split even though there is no "+N more" involved. I'll rework the logic so that multi-day events keep their original row and render continuously unless they were actually hidden in overflow. I'll also revisit the ordering to prioritize multi-day events, add the continuation-arrow behavior for resurfacing events (including in the "+N more" popover), and verify drag & drop behavior against the updated positioning logic. I'll push an update once I've worked through those changes. Thanks again for the clear explanation and screenshots! |
|
Hi @rita-codes , I've also revisited the positioning logic and updated the related tests accordingly. Could you please take another look when you have a chance? Thanks again for the detailed review and guidance. |
|
Hey @rita-codes , can you review this PR? |
|
Hi @mustafajw07, thanks for taking this on! I pulled the branch and ran it locally. The direction is right, but as it stands the fix doesn’t take effect yet, and while testing I found a second positioning bug. Here’s everything, with repros. 1. The issue still reproduces on this branchWith this exact example (straight from #22735), a multi-day event pushed into overflow stays stuck on its overflow row instead of collapsing once it runs alone — identical to import * as React from 'react';
import { SchedulerEvent } from '@mui/x-scheduler/models';
import { StandaloneMonthView } from '@mui/x-scheduler/month-view';
const initialEvents: SchedulerEvent[] = [
{ id: 'A', title: 'A', start: '2025-07-01T09:00:00', end: '2025-07-07T17:00:00' },
{ id: 'B', title: 'B', start: '2025-07-02T09:00:00', end: '2025-07-07T17:00:00' },
{ id: 'C', title: 'C', start: '2025-07-03T09:00:00', end: '2025-07-07T17:00:00' },
{ id: 'D', title: 'D', start: '2025-07-04T09:00:00', end: '2025-07-07T17:00:00' },
{ id: 'T', title: 'Long trip', start: '2025-07-07T08:00:00', end: '2025-07-20T17:00:00' },
];
export default function Repro() {
const [events, setEvents] = React.useState(initialEvents);
return (
<div style={{ height: 460 }}>
<StandaloneMonthView
events={events}
defaultVisibleDate={new Date('2025-07-01T00:00:00')}
onEventsChange={setEvents}
/>
</div>
);
}
The reason: the new 2. Once
|
|
Thanks! I'd like to try implementing it myself first so I can better understand the changes. That said, if this is blocking any issue or is urgent, I'm totally okay with you pushing the changes to my branch. 🙌 |
…in occurrence positioning and add tests for continuation logic
|
Hey @rita-codes , I have update a few changes can you review those? |
… continuation logic
|
We are closer! 🙌 One thing still open: the continuation cue in the popover isn't rendering yet. It's keyed on Playing with it, I think I tested this approach locally and it works nicely — both the grid continuity and the popover arrows come out right. Two smaller things while you're in there:
You're on the right track, this is going to be a great fix 🙌 |
|
Hey @rita-codes , I have update a few changes can you review those? |
|
Really nice catch spotting the "+N more" button getting covered — and good instinct adding a pass for it 🙌 Testing it though, the current version trades that bug for another: the spanning bar now vanishes for the rest of the week after the overflow day. Repro — two all-day events fill rows 1–2 across the month; a single event on Jul 9 makes that cell overflow. The "+N more" is no longer covered (👍), but import * as React from 'react';
import { SchedulerEvent } from '@mui/x-scheduler/models';
import { StandaloneMonthView } from '@mui/x-scheduler/month-view';
const initialEvents: SchedulerEvent[] = [
{ id: 'A', title: 'All-day A', start: '2025-07-01T00:00:00', end: '2025-07-31T23:59:59', allDay: true },
{ id: 'B', title: 'All-day B', start: '2025-07-01T00:00:00', end: '2025-07-31T23:59:59', allDay: true },
{ id: 'C', title: 'New on Jul 9', start: '2025-07-09T09:00:00', end: '2025-07-09T10:00:00' },
];
export default function Repro() {
const [events, setEvents] = React.useState(initialEvents);
return (
<div style={{ height: 460 }}>
<StandaloneMonthView
events={events}
defaultVisibleDate={new Date('2025-07-01T00:00:00')}
onEventsChange={setEvents}
/>
</div>
);
}The root cause is that the hook and The direction that worked for me: make the hook aware the button reserves a row on overflow days (effective capacity Happy to share the diff if it helps, or leave it to you 🙌 |
|
Yes, that would be really helpful. If you don't mind, could you share the diff? |
|
Actually, I just pushed it straight to the branch as a commit instead — easier to read and test than a pasted diff, and fully revertible if you'd rather take your own route (just drop the commit). Key idea: a per-day Take it or leave it — whatever's easiest for you 🙌 |
Track a per-day effective capacity (maxEvents - 1 on overflow days, where the "+N more" button takes a row) plus a `hidden` flag on the active segments, so a multi-day bar bumped by the button ends before the overflow day and resurfaces after it — instead of only being truncated, which made it vanish for the rest of the week.
|
Thanks, I appreciate it! I'll take a look at the commit and go through the implementation to understand the approach. If everything looks good, I'll keep it. |
|
@rita-codes I took a look at the commit and the changes look good. I'll keep them and go through the implementation to better understand the approach. Thanks for putting this together! 🙌 |
|
@rita-codes , any further changes required on this pr? |
There was a problem hiding this comment.
Nice fix 🙌 the resurfacing logic and the overflow accounting in MonthViewCell hold up, and the new hook tests cover the tricky cases well. Two cleanups before merge:
-
Dead code in
useCalendarGridPlaceholderInDay.ts(lines 103–120). Thecreationandexternal-dragbranches already route throughfindAvailableIndex(), and this branch's return now usesfindAvailableIndex(rawPlaceholder.occurrenceKey)too — so the inlinepositionIndexcomputation here (thetargetDaylookup, thewhileloop, and themaxEventsclamp) is that same logic duplicated, and its result is never read. It was live onmaster(index: positionIndex), but extractingfindAvailableIndexand re-pointing the return at it orphaned this block. Could you drop lines 103–120 so the branch relies solely onfindAvailableIndex? Leaving the duplicate risks the two copies drifting apart. -
Misleading sort comment in
useEventOccurrencesWithDayGridPosition.ts(line 48). The comment says "multi-day events first (so they claim top rows), then by start date", butsortEventOccurrencessorts by start date ascending (then end date descending) — the span-based ordering only comes from themaxEvents != nullsecondary sort right below. As written, the first line contradicts the code the reader hits next; could you reword it so the base sort and the overflow-only span sort aren't conflated?
|
Thanks for the review! I've addressed both points:
The PR has been updated. Could you take another look when you have a chance? Thanks! 🙌 |
|
Thanks so much for tackling this, @mustafajw07 — this was an important fix and you nailed it. The resurfacing logic came out clean and well-tested, and the overflow accounting reads really well. Great work! 🎉 Heads-up: I pushed 3 small experiment demos to your branch ( Why: anything under Thanks again! 🙌 |
|
Thank you so much for all the detailed reviews and guidance throughout this PR! I really appreciate the time you took to explain the reasoning behind the feedback—it helped me understand the problem much better. I also appreciate you pushing the final changes for the edge case; it was really helpful to learn from. Thanks again for all your support throughout this PR! 🙌 |
Guard the mui#22735 fix with three experiment demos picked up by Argos: continuation after overflow, overflow persisting when no row frees up, and the "+N more" button behind spanning bars.
074a516 to
50fd2bc
Compare




Closes #22735
Changelog