-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(datepicker): align multi-year-view based on minDate and maxDate #16018
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
feat(datepicker): align multi-year-view based on minDate and maxDate #16018
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.
This is awesome, thanks!! Only request: could you add a test that verifies the behavior for min set, max set, and both set?
@mmalerba Cheers! |
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.
Thank you for the contribution! I only had a few minor nits.
src/material/datepicker/calendar.ts
Outdated
} | ||
|
||
/** mod that handles case where first number is negative */ | ||
private _mod(a: number, b: number) { |
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.
nit: this can be an un-exported file-level function
@@ -69,11 +69,24 @@ export class MatCalendarHeader<D> { | |||
if (this.calendar.currentView == 'year') { | |||
return this._dateAdapter.getYearName(this.calendar.activeDate); | |||
} | |||
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
src/material/datepicker/calendar.ts
Outdated
if (this.calendar.maxDate) { | ||
let maxYear = this._dateAdapter.getYear(this.calendar.maxDate); | ||
minYear = maxYear - yearsPerPage + 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 block is repeated a few times; could you make it a shared function?
minYear = maxYear - yearsPerPage + 1; | ||
} | ||
let activeOffset = this._mod((activeYear - minYear), yearsPerPage); | ||
let minYearOfPage = activeYear - activeOffset; |
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 would also be good to deduplicate this logic a bit
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.
For some reason GitHub lost one of my comments from the last pass
src/material/datepicker/calendar.ts
Outdated
@@ -149,8 +162,21 @@ export class MatCalendarHeader<D> { | |||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2); | |||
} | |||
// Otherwise we are in 'multi-year' view. | |||
return Math.floor(this._dateAdapter.getYear(date1) / yearsPerPage) == | |||
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage); | |||
let minYear = 0; |
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 would tweak this slightly:
// We want a range years such that we maximize the number of
// enabled dates visible at once. This prevents issues where the minimum year
// is the last year of a page or the maximum year is the first item of a page.
// We pick a "starting" year such that either the maximum year would be at the end
// or the minimum year would be at the begining of a page.
let startingYear = 0;
if (this.calendar.maxDate) {
let maxYear = this._dateAdapter.getYear(this.calendar.maxDate);
startingYear = maxYear - yearsPerPage + 1;
} else if (this.calendar.minDate) {
startingYear = this._dateAdapter.getYear(this.calendar.minDate);
}
// When the multi-year view is first opened, the active year will be in view.
// So we compute how many years are between the active year and the *slot* where our
// "starting" year year will render when paged into view.
const activeYear = this._dateAdapter.getYear(this.calendar.activeDate);
const activeOffset = this._mod((activeYear - startingYear), yearsPerPage);
// The offset from the active year to the "slot" for the starting year is the
// *actual* first rendered year in the multi-year view, and the lasy year is
// just one yearsPerPage away.
const minYearOfPage = activeYear - activeOffset;
const maxYearOfPage = minYearOfPage + yearsPerPage - 1;
const firstYearInView = this._dateAdapter.getYearName(
this._dateAdapter.createDate(minYearOfPage, 0, 1));
const lastYearInView = this._dateAdapter.getYearName(
this._dateAdapter.createDate(maxYearOfPage, 0, 1));
return `${firstYearInView} \u2013 ${lastYearInView}`;
- Adds some explanatory comments
- Changes
let
toconst
where appropriate - Combines two separate
if
blocks into anif-elseif
- Changes
minDate
tostartingDate
since it's not necessarily theminDate
Thank you for the comments! :)
Please let me know if you have any suggestions for improving the implementation. |
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 other than a couple minor nits.
@@ -39,6 +39,43 @@ export const yearsPerPage = 24; | |||
|
|||
export const yearsPerRow = 4; | |||
|
|||
/** Gets remainder that is non-negative, even if first number is negative */ | |||
function euclideanModulo (a: number, b: number): number { |
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.
Could you move these helper functions to the bottom of the file? We usually organize them that way
function getStartingYear(minDate: any, maxDate: any): number { | ||
let startingYear = 0; | ||
if (maxDate) { | ||
const maxYear = maxDate.getFullYear(); |
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 looks like these helper functions aren't going through the adapter any more, so they're assuming native JS Date
(here and in getActiveOffset
). You could make these private
class methods so you still have access to the adapter.
You also would want to avoid using any
in the types because it masks issues like this
* So we compute how many years are between the active year and the *slot* where our | ||
* "startingYear" will render when paged into view. | ||
*/ | ||
export function getActiveOffset(activeDate: any, minDate: any, maxDate: any): number { |
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.
If there are new exported helper functions like this, then they need to be omitted from datepicker/public-api.ts
in order to avoid introducing them as public APIs.
const year1 = date1.getFullYear(); | ||
const year2 = date2.getFullYear(); | ||
const startingYear = getStartingYear(minDate, maxDate); | ||
return Math.floor( (year1 - startingYear) / yearsPerPage) === |
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.
Extra spaces inside parens
this._years = []; | ||
for (let i = 0, row: number[] = []; i < yearsPerPage; i++) { | ||
row.push(activeYear - activeOffset + i); | ||
row.push( minYearOfPage + i); |
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.
Extra space
1c44b75
to
20c6256
Compare
src/material/datepicker/calendar.ts
Outdated
return this._isSameMultiYearView(date1, date2); | ||
} | ||
|
||
private _isSameMultiYearView(date1: D, date2: D): boolean { |
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.
These functions are still duplicated; it would be better to extract them to one place where they accept a dateAdapter
as an argument rather than defining them in both classes.
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 aside from one small formatting nit
src/material/datepicker/calendar.ts
Outdated
import {MatMultiYearView, | ||
yearsPerPage, | ||
getActiveOffset, | ||
isSameMultiYearView} from './multi-year-view'; |
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 formatting seems a bit off
@jysoo that's likely just a flakey test or a issue with CircleCI and not likely a problem with your changes. It looks like Safari on macOS isn't launching properly and timing out. It looks like your branch is still based on |
If minDate is set (and maxDate not set), let first page begin with minYear. If maxDate is set, let last page end with maxYear, and disable year(s) < minYear (if any) in first page. Fixes angular#10646
refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate
9150ac0
to
f708475
Compare
@jysoo I rebased and fixed the formatting nit for you, hopefully CI should pass. Thanks for all your work on this! |
Thank you! |
…ngular#16018) * feat(datepicker): align multi-year-view based on minDate and maxDate If minDate is set (and maxDate not set), let first page begin with minYear. If maxDate is set, let last page end with maxYear, and disable year(s) < minYear (if any) in first page. Fixes angular#10646 * add tests for feat(datepicker): align multi-year-view based on minDate and maxDate * refactor feat(datepicker): align multi-year-view based on minDate and maxDate * omit helper func from public api * fixup, resolve conflict refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate * share func feat(datepicker): align multi-year-view based on minDate and maxDate * fix formatting nit
…16018) * feat(datepicker): align multi-year-view based on minDate and maxDate If minDate is set (and maxDate not set), let first page begin with minYear. If maxDate is set, let last page end with maxYear, and disable year(s) < minYear (if any) in first page. Fixes #10646 * add tests for feat(datepicker): align multi-year-view based on minDate and maxDate * refactor feat(datepicker): align multi-year-view based on minDate and maxDate * omit helper func from public api * fixup, resolve conflict refactor with private methods feat(datepicker): align multi-year-view based on minDate and maxDate * share func feat(datepicker): align multi-year-view based on minDate and maxDate * fix formatting nit
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
If minDate is set (and maxDate not set), let first page begin with minYear.
If maxDate is set, let last page end with maxYear,
and disable year(s) < minYear (if any) in first page.
Fixes #10646