Skip to content

fix(calendar): don't range-highlight adjacent-month spillover days#3574

Draft
cixzhang wants to merge 3 commits into
mainfrom
navi/fix/issue-2715-daterange-spillover
Draft

fix(calendar): don't range-highlight adjacent-month spillover days#3574
cixzhang wants to merge 3 commits into
mainfrom
navi/fix/issue-2715-daterange-spillover

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

In the two-month DateRangeInput / Calendar range view, spillover days from the neighbouring month were rendered with the same range-highlight styling as the real selection. With July 1–31 selected, the August pane showed July 26–31 in its first row and highlighted that row as part of the selection; symmetrically the July pane highlighted early-August spillover days. Closes #2715.

Root cause

computeDayCellState (packages/core/src/Calendar/dayCellUtils.ts) derived isInRange / isRangeStart / isRangeEnd / preview / isSelected purely from date membership, ignoring the cell's isOutside flag. Because every visible pane renders a fixed grid that includes adjacent-month days (hasOutsideDays, default true), the same date appears in two panes — and both copies matched the range predicate, so the outside/spillover copy got the range-highlight background and data-in-range state.

Fix

Outside (adjacent-month) days no longer receive selection, range, or preview state. Outside days are already non-interactive (effectivelyDisabled), so this is the minimal, consistent change: the in-month copy in the correct pane still highlights, and the spillover copy in the other pane renders as a plain grayed outside day.

Validation

  • Repro (unit, red→green): added tests to dayCellUtils.test.ts asserting an isOutside day gets no range/preview/selected state. Failed before the fix, passes after.
  • Repro (DOM, red→green): added a test to Calendar.test.tsx rendering the two-month range (July 1–31, July+August visible) and asserting the August-pane spillover copies of July 26–31 do not carry data-in-range. This exercises the real render path; it failed before the fix (expected 'in-range' to be null) and passes after.
  • Full Calendar suite (59 tests) green; lint clean.

Why this validation: the bug is deterministic rendering logic, so a failing→passing automated assertion on the highlight state is the definitive proof — no visual snapshot needed for correctness.

What to eyeball

In Storybook / the sandbox, open the two-month range calendar, select a range within one month (e.g. July 1–31), and confirm the neighbouring pane's spillover row is a plain grayed row with no range background or endpoint highlight.

Outside (adjacent-month) days now never receive selection, range, or
preview state. In the two-month range view the same date renders in both
panes, so the spillover copy on the neighbouring month's pane was drawn
as part of the selection. (#2715)
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 5, 2026 1:50am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 4, 2026
@cixzhang

cixzhang commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

image

Solution is reasonable. The we will need to apply the end cap radius on highlighted days next to disabled days.

Address review follow-up: a highlighted range day adjacent to a disabled or
adjacent-month (outside) day now gets a rounded end cap on that side, so the
run reads as terminating at the gap instead of running square-edged into it.

computeRangeRounding gains optional neighbour continuity info; a new
isRangeHighlighted helper derives whether a neighbouring cell continues the
run (enabled, in-month, in-range). MonthGrid computes prev/next continuity per
day and passes it down.
@cixzhang

cixzhang commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Added the end caps in d708fd2. A highlighted range day that sits next to a disabled or adjacent-month (outside) day now gets a rounded cap on that side, so the run terminates cleanly at the gap instead of running square-edged into it.

Mechanism: computeRangeRounding now takes optional neighbour-continuity info, and a small isRangeHighlighted helper decides whether the day before/after continues the run (enabled, in-month, in-range). MonthGrid computes prev/next continuity per day from the week row and passes it down. Covered by unit tests on the rounding + helper, plus a Calendar-level test that disables a mid-range day and asserts the neighbours get the cap radius.

One interpretation call I want to flag: I kept the rounding change only — a disabled day that falls inside the selected range still paints its own range background (unchanged behavior), and its highlighted neighbours are what get capped. If your screenshot intends the disabled day itself to be a true gap (no fill at all), that's a one-line follow-up (isInRange also excludes disabled). Let me know which you want and I'll adjust.

@cixzhang

cixzhang commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

It's looking better but the computeRangeRounding seems to only apply after selecting the range. There's the highlight during hover and interacting with the calendar that's missing the rounding.

Comment thread packages/core/src/Calendar/Calendar.tsx Outdated
Comment on lines +789 to +814
const prevDay = week[dayIndex - 1];
const nextDay = week[dayIndex + 1];
// A neighbour continues the highlighted run only if it is an
// enabled, in-month range day. A disabled or adjacent-month
// (outside) neighbour breaks continuity, so this day gets an
// end cap on that side (#2715).
const prevInRange = prevDay
? isRangeHighlighted({
date: prevDay.date,
mode,
rangeStart,
rangeEnd,
isDisabled: isDateDisabled(prevDay.date),
isOutside: prevDay.isOutside,
})
: false;
const nextInRange = nextDay
? isRangeHighlighted({
date: nextDay.date,
mode,
rangeStart,
rangeEnd,
isDisabled: isDateDisabled(nextDay.date),
isOutside: nextDay.isOutside,
})
: false;

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.

This logic is best broken out into a util and tested as well.

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.

Done in 65a895e — pulled the neighbour logic out of the render path into computeDayNeighborContinuity in dayCellUtils.ts, with an isPreviewHighlighted helper alongside the existing isRangeHighlighted. Both are unit-tested (continuity through disabled/outside neighbours, row edges, and preview vs range independence). DayCell now just receives a single neighbors object.

Address review: break the per-day neighbour-continuity logic out of the render
path into a tested util (computeDayNeighborContinuity) and apply the same
disabled/outside end-cap rounding to the hover PREVIEW highlight, not just the
committed range — the preview was previously missing the caps.

- new computeDayNeighborContinuity + isPreviewHighlighted helpers, unit tested
- computePreviewRounding now takes optional neighbour info (mirrors range)
- DayCell takes a single neighbors object instead of two booleans
@cixzhang

cixzhang commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — the caps only ran on the committed range, not the hover preview. Fixed in 65a895e: computePreviewRounding now takes the same neighbour-continuity info and caps the preview highlight at disabled/outside days just like the range does. The new computeDayNeighborContinuity util derives both range and preview continuity per day, so hovering across a disabled day now rounds the preview end cleanly. Added preview-cap unit tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] [bug-bash] DateRangeInput: two-month calendar shows (and highlights) spillover days from the other month's pane

2 participants