-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change month height calculation to be based on number of weeks instead of DOM calculations #1192
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is test/utils/getNumberOfCalendarMonthWeeks_spec.js
empty?
Yes, I think this is breaking.
src/components/DayPicker.jsx
Outdated
if (this.isVertical()) { | ||
const calendarMonthWeeksHeight = this.calendarMonthWeeks[0] * (daySize - 1); | ||
translationValue = monthTitleHeight + calendarMonthWeeksHeight + 1; | ||
} | ||
|
||
if (this.isHorizontal()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like it should be an else if
src/components/DayPicker.jsx
Outdated
let translationValue = this.isVertical() ? this.calendarMonthHeights[0] : calendarMonthWidth; | ||
const newMonth = currentMonth.clone().subtract(1, 'month'); | ||
const numberOfWeeks = getNumberOfCalendarMonthWeeks(newMonth, this.getFirstDayOfWeek()); | ||
this.calendarMonthWeeks = [numberOfWeeks].concat(this.calendarMonthWeeks.slice(0, -1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[numberOfWeeks, ...this.calendarMonthWeeks.slice(0, -1)]
?
src/components/DayPicker.jsx
Outdated
|
||
let translationValue; | ||
if (this.isVertical()) { | ||
const calendarMonthWeeksHeight = this.calendarMonthWeeks[0] * (daySize - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this only need the first week?
also, could it do const [firstWeek] = this.calendarMonthWeeks;
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calendarMonthWeeks
is an array of the number of weeks in each of the calendarmonths. The vertical picker scrolls forward by the amount of weeks in the first (currently invisible) month.
src/components/DayPicker.jsx
Outdated
let translationValue; | ||
|
||
if (this.isVertical()) { | ||
const calendarMonthWeeksHeight = this.calendarMonthWeeks[0] * (daySize - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SO there is a difference between the two. in the one the weeks array has already been updated to the new state; in the other it hasnt'.
src/components/DayPicker.jsx
Outdated
|
||
if (this.isVertical()) { | ||
const calendarMonthWeeksHeight = this.calendarMonthWeeks[0] * (daySize - 1); | ||
translationValue = -(monthTitleHeight + calendarMonthWeeksHeight + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this "is vertical" logic is repeated in at least 2 places; can it be shared?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not quite the same
src/components/DayPicker.jsx
Outdated
translationValue = -(monthTitleHeight + calendarMonthWeeksHeight + 1); | ||
} | ||
|
||
this.calendarMonthWeeks = this.calendarMonthWeeks.slice(1).concat([numberOfWeeks]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const [, ...rest] = this.calendarMonthWeeks; this.calendarMonthWeeks = [...rest, numberOfWeeks];
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay
319a3fa
to
6f1da2e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL @ljharb
6f1da2e
to
8dec891
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
o/
src/components/DayPicker.jsx
Outdated
this.calendarMonthGridHeight = Math.max(0, ...visibleCalendarMonthHeights) + MONTH_PADDING; | ||
this.setState({ hasSetHeight: true }); | ||
this.calendarMonthWeeks = []; | ||
for (let i = 0; i < numberOfMonths; i += 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could tweak this to in the same way to capture months before and after the current month? And then on prev/next click, we could update the neighbor months asynchronously, the way that we might preload images in a carousel? I could be misunderstanding the logic too.
src/components/DayPicker.jsx
Outdated
|
||
if (e) e.preventDefault(); | ||
|
||
let translationValue = this.isVertical() ? this.calendarMonthHeights[0] : calendarMonthWidth; | ||
const newMonth = currentMonth.clone().subtract(1, 'month'); | ||
const numberOfWeeks = getNumberOfCalendarMonthWeeks(newMonth, this.getFirstDayOfWeek()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mostly think it would be cool to reduce the work done on prev/next click. Wondering if we can pre-calculate and key the months somehow, or even memoize getNumberOfCalendarMonthWeeks
or something so that we don't have to recalculate week count and shift calendarMonthWeeks
on each transition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. My only comments are potential optimizations that may or may not make sense and/or matter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@majapw Looks great, the approach makes sense, and works as advertised!
Looking forward to #1106, the onMonthChange
and onYearChange
functions will need to have code added to conditionally adjust the height, as you did with onPrevMonthClick
and onNextMonthClick
. I was able to verify this behavior by adding this function:
maybeAdjustDayPickerHeight(newMonth) {
const { daySize } = this.props;
const { monthTitleHeight } = this.state;
const numberOfWeeks = getNumberOfCalendarMonthWeeks(newMonth, this.getFirstDayOfWeek());
this.calendarMonthWeeks = [numberOfWeeks, ...this.calendarMonthWeeks.slice(0, -1)];
if (this.isHorizontal()) {
const calendarMonthWeeksHeight = Math.max(0, ...this.calendarMonthWeeks) * (daySize - 1);
const newMonthHeight = monthTitleHeight + calendarMonthWeeksHeight + 1;
this.adjustDayPickerHeight(newMonthHeight);
}
}
And calling it in onMonthChange
and onYearChange
just before the setState
call, and the heights were adjusted as I'd expect. There may (or may not) need to be more done there, and the transitions are still the same (0.0001), but it seems to work nicely!
For a couple quick checks/verification, I tested with two cases:
- Jumping from June 2018 to January 2018 to September 2018 -- this used to clip the month height, now it does not
- Jumping from June 2018 to June 2019 to June 2018 -- sizes up and down correctly!
That said, perhaps that maybeAdjustDayPickerHeight
(based on code from onPrevMonthClick
) could also be called from onPrevMonthClick
and onNextMonthClick
? I know one of them is slightly different, but perhaps?
Anyway, all that is for later, this PR as-is LGTM.
…d of DOM calculations
…in the click handlers
4605dde
to
0bd08cb
Compare
Sweet! Okay, I deferred some of the heavier cloning logic from I'm gonna merge this in once everything passes. |
Hello friends!
This is a precursor to getting #1106 merged in. I separated it out into its own change because it was fairly involved and I didn't want to pollute the code-change in #1106 with something not entirely related.
Basically, when playing around with the month/year selection I found an issue where if you jumped to a month of a different height (5 week month => 6 week month), the height wouldn't automatically adjust as expected. When I started trying to fix the issue, it became apparent that it wasn't possible to do without jank using the existing paradigms.
Before:
CalendarMonth
component (including the two invisible ones for animation) would measure its own height in the DOM.CalendarMonth
would then call a callback prop,setMonthHeight
, with this measured value.CalendarMonthGrid
would add these values to an ordered array ofCalendarMonth
heights which it would then pass back up to theDayPicker
via thesetCalendarMonthHeights
propDayPicker
would have all of the heights, it would calculate the expected height based on the max of the visible months and grow the calendar appropriately.The important thing to note is that the only way it could change the height while transitioning was to use the self-calculations made by the invisible months that were already rendered to the DOM. When we want to transition to an arbitrary month or year, there's guaranteed to be jank in this system because we have to render the month before we know its height. The end result is that if you transition from July 2018 to July 2017, the 6-week month will be cut off before it grows. The other option is to hide the calendar in this interim, but that flash is also very apparent.
SO, this change is designed to amend that.
Now:
renderMonth
changes), theCalendarMonth
measures itself, subtracts out the height of theCalendarDay
table (which is deterministic based on number of weeks and thedaySize
prop), and call thesetMonthTitleHeight
prop with the value.DayPicker
receives this value and saves it inthis.monthTitleHeight
DayPicker
also maintains an array of the number of weeks in each visible month.DayPicker
uses the number of weeks in the month coming into view along with thedaySize
prop and the savedmonthTitleHeight
to calculate the expected height of the incoming month and adjusts the height appropriately.I've tested this in both the vertical and the horizontal pickers and it seems pretty effective! Because we can pretty readily calculate the number of weeks in any incoming month, this should open the door to slick month transitions to any month. We also, tbh, could probably remove the invisible months from render until it's time to slide them in. That can wait for another time though.
Note, this does assume a pretty constant month title height, but tbh I think that was an implicit assumption before.
Would love some eyes! @ljharb @gabergg @GoGoCarl
These props that I'm changing are all what I would consider internal props (it would be weird if an outside consumer was relying on them for anything)... but are technically part of the API, so this change could be considered breaking. Idk.
TODO:
-[X] Write tests for
getNumberOfCalendarMonthWeeks
-[X] reset month height when renderMonth changes