Skip to content
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(date-picker): Add isDateDisabled functionality #7075

Merged
merged 33 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7cc70a1
Add isDateAvailable property
dethell Jan 5, 2023
6a49802
Further changes to allow disabling arbitrary dates
dethell Jan 6, 2023
7df3432
Starting to add tests for isDateAvailable changes
dethell Jan 9, 2023
e58f796
Add validation and disabled tests
dethell Jan 17, 2023
0c119cf
refactor: Refactoring per review comments
dethell Sep 21, 2023
848efbe
feat(date-picker): Add disabled function to demo
dethell Sep 26, 2023
41865b2
feat(date-picker): update month-calendar tests
dethell Oct 5, 2023
8c687f2
feat(date-picker): Update datepicker playground to ensure disabled fu…
dethell Oct 5, 2023
2474fe7
feat(date-picker): month-calendar should not use isDateDisabled funct…
dethell Oct 5, 2023
efadb48
fix(date-picker): change per review.
dethell Oct 6, 2023
077d79f
feat(date-picker): Update isDateDisabled contract to use DatePickerDa…
dethell Oct 12, 2023
f9b077e
feat(date-picker): Update date-picker html demo to provide sample isD…
dethell Oct 12, 2023
47cd2bb
feat(date-picker): Update keyboard logic for disabled dates
dethell Oct 16, 2023
93c8e1d
Merge remote-tracking branch 'upstream/main'
dethell Oct 18, 2023
e67599d
feat(date-picker): Update date-picker demo
dethell Oct 27, 2023
8bc4cfd
fix(date-picker): Fix bug in overlay mixin _selectDate function
dethell Oct 27, 2023
a963bd4
fix(date-picker): update disabled dates test to use DatePickerDate type
dethell Oct 27, 2023
46a33d4
feat(date-picker): Add tests for keyboard navigation
dethell Oct 27, 2023
1c103f8
fix(date-picker): Update getDayAriaDisabled logic to account for cust…
dethell Nov 8, 2023
459a783
fix(date-picker): update month-calendar tests for disabled date function
dethell Nov 8, 2023
3b2446d
fix(date-picker): fix lint issues
dethell Nov 10, 2023
07e8687
chore(date-picker): move keyboard-related disabled date tests to keyb…
dethell Nov 14, 2023
2268b6a
Merge remote-tracking branch 'upstream/main'
dethell Nov 14, 2023
59750ee
fix(date-picker): update keyboard validation tests
dethell Nov 15, 2023
1dd26c8
handle async property update in a Lit test
tomivirkki Nov 16, 2023
1010d3c
fix(date-picker): per review - use dateAllowed helper function instea…
dethell Nov 16, 2023
9ab7e8a
test: align tests with existing tests
tomivirkki Nov 17, 2023
4e93815
feat(date-picker): Update keyboard test for disabled dates to test ENTER
dethell Nov 21, 2023
2143f0e
Merge branch 'main' into dethell-jh
tomivirkki Nov 22, 2023
f0793b9
test: split cases into separate tests
tomivirkki Nov 22, 2023
a1d37c1
test: fix test errors in the console
tomivirkki Nov 22, 2023
05da9db
chore: update helper import
yuriy-fix Jan 15, 2024
d7685d6
Update packages/date-picker/src/vaadin-date-picker-overlay-content-mi…
web-padawan Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions dev/date-picker.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
<script type="module">
import '@vaadin/date-picker';
import '@vaadin/tooltip';
const isDateDisabled = (date) => {
// Exclude weekends and the 16th day of each month:
const checkDate = new Date(0, 0);
checkDate.setFullYear(date.year);
checkDate.setMonth(date.month);
checkDate.setDate(date.day);
return checkDate.getDay() === 0 || checkDate.getDay() === 6 || checkDate.getDate() === 16;
}
const picker = document.querySelector('vaadin-date-picker');
picker.isDateDisabled = isDateDisabled;
picker.min = '2023-11-01';
</script>
</head>
<body>
Expand Down
7 changes: 6 additions & 1 deletion packages/date-picker/src/vaadin-date-picker-helper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ declare function dateEquals(date1: Date | null, date2: Date | null): boolean;
*
* @returns True if the date is in the range
*/
declare function dateAllowed(date: Date, min: Date | null, max: Date | null): boolean;
declare function dateAllowed(
date: Date,
min: Date | null,
max: Date | null,
isDateDisabled: (DatePickerDate) => boolean | null,
): boolean;

/**
* Get closest date from array of dates.
Expand Down
39 changes: 23 additions & 16 deletions packages/date-picker/src/vaadin-date-picker-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,37 @@ export function dateEquals(date1, date2) {
);
}

/**
* Extracts the basic component parts of a date (day, month and year)
* to the expected format.
* @param {!Date} date
* @return {{day: number, month: number, year: number}}
*/
export function extractDateParts(date) {
return {
day: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
};
}

/**
* Check if the given date is in the range of allowed dates.
*
* @param {!Date} date The date to check
* @param {Date} min Range start
* @param {Date} max Range end
* @param {function(!DatePickerDate): boolean} isDateDisabled Callback to check if the date is disabled
* @return {boolean} True if the date is in the range
*/
export function dateAllowed(date, min, max) {
return (!min || date >= min) && (!max || date <= max);
export function dateAllowed(date, min, max, isDateDisabled) {
let dateIsDisabled = false;
if (typeof isDateDisabled === 'function' && !!date) {
const dateToCheck = extractDateParts(date);
dateIsDisabled = isDateDisabled(dateToCheck);
}

return (!min || date >= min) && (!max || date <= max) && !dateIsDisabled;
}

/**
Expand Down Expand Up @@ -90,20 +111,6 @@ export function getClosestDate(date, dates) {
});
}

/**
* Extracts the basic component parts of a date (day, month and year)
* to the expected format.
* @param {!Date} date
* @return {{day: number, month: number, year: number}}
*/
export function extractDateParts(date) {
return {
day: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
};
}

/**
* Get difference in months between today and given months value.
*
Expand Down
7 changes: 7 additions & 0 deletions packages/date-picker/src/vaadin-date-picker-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,13 @@ export declare class DatePickerMixinClass {
*/
max: string | undefined;

/**
* A function to be used to determine whether the user can select a given date.
* Receives a `DatePickerDate` object of the date to be selected and should return a
* boolean.
*/
isDateDisabled: (date: DatePickerDate) => boolean;

/**
* Opens the dropdown.
*/
Expand Down
37 changes: 31 additions & 6 deletions packages/date-picker/src/vaadin-date-picker-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,17 @@ export const DatePickerMixin = (subclass) =>
sync: true,
},

/**
* A function to be used to determine whether the user can select a given date.
* Receives a `DatePickerDate` object of the date to be selected and should return a
* boolean.
*
* @type {function(DatePickerDate): boolean | undefined}
*/
isDateDisabled: {
type: Function,
},

/**
* The earliest date that can be selected. All earlier dates will be disabled.
* @type {Date | undefined}
Expand Down Expand Up @@ -365,7 +376,7 @@ export const DatePickerMixin = (subclass) =>
return [
'_selectedDateChanged(_selectedDate, i18n)',
'_focusedDateChanged(_focusedDate, i18n)',
'__updateOverlayContent(_overlayContent, i18n, label, _minDate, _maxDate, _focusedDate, _selectedDate, showWeekNumbers)',
'__updateOverlayContent(_overlayContent, i18n, label, _minDate, _maxDate, _focusedDate, _selectedDate, showWeekNumbers, isDateDisabled)',
'__updateOverlayContentTheme(_overlayContent, _theme)',
'__updateOverlayContentFullScreen(_overlayContent, _fullscreen)',
];
Expand Down Expand Up @@ -599,7 +610,8 @@ export const DatePickerMixin = (subclass) =>
checkValidity() {
const inputValue = this._inputElementValue;
const inputValid = !inputValue || (!!this._selectedDate && inputValue === this.__formatDate(this._selectedDate));
const minMaxValid = !this._selectedDate || dateAllowed(this._selectedDate, this._minDate, this._maxDate);
const isDateValid =
!this._selectedDate || dateAllowed(this._selectedDate, this._minDate, this._maxDate, this.isDateDisabled);

let inputValidity = true;
if (this.inputElement) {
Expand All @@ -611,7 +623,7 @@ export const DatePickerMixin = (subclass) =>
}
}

return inputValid && minMaxValid && inputValidity;
return inputValid && isDateValid && inputValidity;
}

/**
Expand Down Expand Up @@ -850,7 +862,17 @@ export const DatePickerMixin = (subclass) =>

/** @private */
// eslint-disable-next-line max-params
__updateOverlayContent(overlayContent, i18n, label, minDate, maxDate, focusedDate, selectedDate, showWeekNumbers) {
__updateOverlayContent(
overlayContent,
i18n,
label,
minDate,
maxDate,
focusedDate,
selectedDate,
showWeekNumbers,
isDateDisabled,
) {
if (overlayContent) {
overlayContent.i18n = i18n;
overlayContent.label = label;
Expand All @@ -859,6 +881,7 @@ export const DatePickerMixin = (subclass) =>
overlayContent.focusedDate = focusedDate;
overlayContent.selectedDate = selectedDate;
overlayContent.showWeekNumbers = showWeekNumbers;
overlayContent.isDateDisabled = isDateDisabled;
}
}

Expand Down Expand Up @@ -930,9 +953,11 @@ export const DatePickerMixin = (subclass) =>
const initialPosition =
this._selectedDate || this._overlayContent.initialPosition || parsedInitialPosition || new Date();

return parsedInitialPosition || dateAllowed(initialPosition, this._minDate, this._maxDate)
return parsedInitialPosition || dateAllowed(initialPosition, this._minDate, this._maxDate, this.isDateDisabled)
? initialPosition
: getClosestDate(initialPosition, [this._minDate, this._maxDate]);
: this._minDate || this._maxDate
? getClosestDate(initialPosition, [this._minDate, this._maxDate])
: new Date();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
import { addListener, setTouchAction } from '@vaadin/component-base/src/gestures.js';
import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
import { dateAfterXMonths, dateEquals, extractDateParts, getClosestDate } from './vaadin-date-picker-helper.js';
import {
dateAfterXMonths,
dateAllowed,
dateEquals,
extractDateParts,
getClosestDate,
} from './vaadin-date-picker-helper.js';

/**
* @polymerMixin
Expand Down Expand Up @@ -107,6 +113,17 @@ export const DatePickerOverlayContentMixin = (superClass) =>
sync: true,
},

/**
* A function to be used to determine whether the user can select a given date.
* Receives a `DatePickerDate` object of the date to be selected and should return a
* boolean.
*
* @type {function(DatePickerDate): boolean | undefined}
*/
isDateDisabled: {
type: Function,
},

/**
* Input label
*/
Expand Down Expand Up @@ -134,9 +151,9 @@ export const DatePickerOverlayContentMixin = (superClass) =>

static get observers() {
return [
'__updateCalendars(calendars, i18n, minDate, maxDate, selectedDate, focusedDate, showWeekNumbers, _ignoreTaps, _theme)',
'__updateCalendars(calendars, i18n, minDate, maxDate, selectedDate, focusedDate, showWeekNumbers, _ignoreTaps, _theme, isDateDisabled)',
'__updateCancelButton(_cancelButton, i18n)',
'__updateTodayButton(_todayButton, i18n, minDate, maxDate)',
'__updateTodayButton(_todayButton, i18n, minDate, maxDate, isDateDisabled)',
'__updateYears(years, selectedDate, _theme)',
];
}
Expand Down Expand Up @@ -303,10 +320,10 @@ export const DatePickerOverlayContentMixin = (superClass) =>
}

/** @private */
__updateTodayButton(todayButton, i18n, minDate, maxDate) {
__updateTodayButton(todayButton, i18n, minDate, maxDate, isDateDisabled) {
if (todayButton) {
todayButton.textContent = i18n && i18n.today;
todayButton.disabled = !this._isTodayAllowed(minDate, maxDate);
todayButton.disabled = !this._isTodayAllowed(minDate, maxDate, isDateDisabled);
}
}

Expand All @@ -321,12 +338,14 @@ export const DatePickerOverlayContentMixin = (superClass) =>
showWeekNumbers,
ignoreTaps,
theme,
isDateDisabled,
) {
if (calendars && calendars.length) {
calendars.forEach((calendar) => {
calendar.i18n = i18n;
calendar.minDate = minDate;
calendar.maxDate = maxDate;
calendar.isDateDisabled = isDateDisabled;
calendar.focusedDate = focusedDate;
calendar.selectedDate = selectedDate;
calendar.showWeekNumbers = showWeekNumbers;
Expand Down Expand Up @@ -361,10 +380,14 @@ export const DatePickerOverlayContentMixin = (superClass) =>
* @protected
*/
_selectDate(dateToSelect) {
if (!this._dateAllowed(dateToSelect)) {
return false;
}
this.selectedDate = dateToSelect;
this.dispatchEvent(
new CustomEvent('date-selected', { detail: { date: dateToSelect }, bubbles: true, composed: true }),
);
return true;
}

/** @private */
Expand Down Expand Up @@ -775,9 +798,10 @@ export const DatePickerOverlayContentMixin = (superClass) =>
handled = true;
break;
case 'Enter':
this._selectDate(this.focusedDate);
this._close();
handled = true;
if (this._selectDate(this.focusedDate)) {
this._close();
handled = true;
}
break;
case ' ':
this.__toggleDate(this.focusedDate);
Expand Down Expand Up @@ -931,7 +955,8 @@ export const DatePickerOverlayContentMixin = (superClass) =>

/** @private */
_focusAllowedDate(dateToFocus, diff, keepMonth) {
if (this._dateAllowed(dateToFocus)) {
// For this check we do consider the isDateDisabled function because disabled dates are allowed to be focused, just not outside min/max
if (this._dateAllowed(dateToFocus, undefined, undefined, () => false)) {
this.focusDate(dateToFocus, keepMonth);
} else if (this._dateAllowed(this.focusedDate)) {
// Move to min or max date
Expand Down Expand Up @@ -1009,18 +1034,18 @@ export const DatePickerOverlayContentMixin = (superClass) =>
}

/** @private */
_dateAllowed(date, min = this.minDate, max = this.maxDate) {
return (!min || date >= min) && (!max || date <= max);
_dateAllowed(date, min = this.minDate, max = this.maxDate, isDateDisabled = this.isDateDisabled) {
return dateAllowed(date, min, max, isDateDisabled);
}

/** @private */
_isTodayAllowed(min, max) {
_isTodayAllowed(min, max, isDateDisabled) {
const today = new Date();
const todayMidnight = new Date(0, 0);
todayMidnight.setFullYear(today.getFullYear());
todayMidnight.setMonth(today.getMonth());
todayMidnight.setDate(today.getDate());
return this._dateAllowed(todayMidnight, min, max);
return this._dateAllowed(todayMidnight, min, max, isDateDisabled);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/date-picker/src/vaadin-lit-month-calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class MonthCalendar extends MonthCalendarMixin(ThemableMixin(PolylitMixin(LitEle
${week.map((date) => {
const isFocused = dateEquals(date, this.focusedDate);
const isSelected = dateEquals(date, this.selectedDate);
const isDisabled = !dateAllowed(date, this.minDate, this.maxDate);
const isDisabled = !dateAllowed(date, this.minDate, this.maxDate, this.isDateDisabled);

const parts = [
'date',
Expand Down
11 changes: 11 additions & 0 deletions packages/date-picker/src/vaadin-month-calendar-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ export const MonthCalendarMixin = (superClass) =>
sync: true,
},

/**
* A function to be used to determine whether the user can select a given date.
* Receives a `DatePickerDate` object of the date to be selected and should return a
* boolean.
* @type {Function | undefined}
*/
isDateDisabled: {
type: Function,
value: () => false,
},

disabled: {
type: Boolean,
reflectToAttribute: true,
Expand Down
Loading