feat(datetime): isDateEnabled to enable/disable specific days (#24898)

Resolves #24209
This commit is contained in:
Sean Perkins
2022-03-11 14:40:03 -05:00
committed by GitHub
parent dda2b9f91c
commit e932a04223
16 changed files with 616 additions and 46 deletions

View File

@ -1,4 +1,5 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
import { printIonError } from '@utils/logging';
import {
caretDownSharp,
caretUpSharp,
@ -153,6 +154,21 @@ export class Datetime implements ComponentInterface {
*/
@Prop() readonly = false;
/**
* Returns if an individual date (calendar day) is enabled or disabled.
*
* If `true`, the day will be enabled/interactive.
* If `false`, the day will be disabled/non-interactive.
*
* The function accepts an ISO 8601 date string of a given day.
* By default, all days are enabled. Developers can use this function
* to write custom logic to disable certain days.
*
* The function is called for each rendered calendar day, for the previous, current and next month.
* Custom implementations should be optimized for performance to avoid jank.
*/
@Prop() isDateEnabled?: (dateIsoString: string) => boolean;
@Watch('disabled')
protected disabledChanged() {
this.emitStyle();
@ -1275,9 +1291,25 @@ export class Datetime implements ComponentInterface {
<div class="calendar-month-grid">
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
const { day, dayOfWeek } = dateObject;
const { isDateEnabled } = this;
const referenceParts = { month, day, year };
const { isActive, isToday, ariaLabel, ariaSelected, disabled } = getCalendarDayState(this.locale, referenceParts, this.activePartsClone, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues);
let isCalDayDisabled = isCalMonthDisabled || disabled;
if (!isCalDayDisabled && isDateEnabled !== undefined) {
try {
/**
* The `isDateEnabled` implementation is try-catch wrapped
* to prevent exceptions in the user's function from
* interrupting the calendar rendering.
*/
isCalDayDisabled = !isDateEnabled(convertDataToISO(referenceParts));
} catch (e) {
printIonError('Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', e);
}
}
return (
<button
tabindex="-1"
@ -1286,7 +1318,7 @@ export class Datetime implements ComponentInterface {
data-year={year}
data-index={index}
data-day-of-week={dayOfWeek}
disabled={isCalMonthDisabled || disabled}
disabled={isCalDayDisabled}
class={{
'calendar-day-padding': day === null,
'calendar-day': true,

View File

@ -170,6 +170,68 @@ console.log(formattedString); // Jun 4, 2021
See https://date-fns.org/docs/format for a list of all the valid format tokens.
## Disabling Dates
With the `isDateEnabled` property, developers can customize the `ion-datetime` to disable a specific day, range of dates, weekends or any custom rule using an ISO 8601 date string.
The `isDateEnabled` property accepts a function returning a boolean, indicating if a date is enabled. The function is called for each rendered calendar day, for the previous, current and next month. Custom implementations should be optimized for performance to avoid jank.
```html
<ion-datetime></ion-datetime>
<script>
const datetime = document.querySelector('ion-datetime');
datetime.isDateEnabled = function(dateIsoString) {
// Write your custom logic here
return true;
}
</script>
```
### Disabling a specific date
```js
isDateEnabled(dateIsoString) {
const date = new Date(dateIsoString);
if (date.getUTCFullYear() === 2022
&& date.getUTCMonth() === 0
&& date.getUTCDate() === 1) {
// Disables January 1, 2022
return false;
}
return true;
}
```
### Disabling weekends
```js
isDateEnabled(dateIsoString) {
const date = new Date(dateIsoString);
if (date.getUTCDay() === 0 && date.getUTCDay() === 6) {
// Disables Saturday and Sunday
return false;
}
return true;
}
```
### Usage with date-fns
Developers can also use the available functions from `date-fns` to disable dates.
```ts
import { isMonday } from 'date-fns';
isDateEnabled(dateIsoString) {
if (isMonday(new Date(dateIsoString))) {
// Disables Monday
return false;
}
return true;
}
```
## Advanced Datetime Validation and Manipulation
The datetime picker provides the simplicity of selecting an exact format, and
@ -293,6 +355,9 @@ interface DatetimeCustomEvent extends CustomEvent {
<!-- Clear button -->
<ion-datetime [showClearButton]="true"></ion-datetime>
<!-- Disable custom days -->
<ion-datetime [isDateEnabled]="isDateEnabled"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
<ion-modal trigger="open-modal">
@ -347,7 +412,7 @@ interface DatetimeCustomEvent extends CustomEvent {
```typescript
import { Component, ViewChild } from '@angular/core';
import { IonDatetime } from '@ionic/angular';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
@Component({})
export class MyComponent {
@ -369,6 +434,15 @@ export class MyComponent {
formatDate(value: string) {
return format(parseISO(value), 'MMM dd yyyy');
}
isDateEnabled(dateIsoString: string) {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}
}
```
@ -428,6 +502,9 @@ export class MyComponent {
</ion-buttons>
</ion-datetime>
<!-- Disable custom dates -->
<ion-datetime id="disabled-date-datetime"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
<ion-modal trigger="open-modal">
@ -458,7 +535,7 @@ export class MyComponent {
```
```javascript
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
const datetime = document.querySelector('#custom-datetime');
@ -474,6 +551,18 @@ const formatDate = (value: string) => {
return format(parseISO(value), 'MMM dd yyyy');
};
const isDateEnabled = (dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
};
const disabledDateDatetime = document.querySelector('#disabled-date-datetime');
disabledDateDatetime.isDateEnabled = isDateEnabled;
const popoverDatetime = document.querySelector('#popover-datetime');
const dateInput = document.querySelector('#date-input');
popoverDatetime.addEventListener('ionChange', ev => dateInput.innerText = formatDate(ev.detail.value));
@ -500,7 +589,7 @@ import {
IonPopover
} from '@ionic/react';
import { calendar } from 'ionicons/icons';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
export const DateTimeExamples: React.FC = () => {
const [selectedDate, setSelectedDate] = useState('2012-12-15T13:47:20.789');
@ -577,6 +666,16 @@ export const DateTimeExamples: React.FC = () => {
<IonButton onClick={() => reset()}>Reset</IonButton>
</IonButtons>
</IonDatetime>
{/* Disable custom days */}
<IonDatetime isDateEnabled={(dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}}></IonDatetime>
{/* Datetime in overlay */}
<IonButton id="open-modal">Open Datetime Modal</IonButton>
@ -621,7 +720,7 @@ export const DateTimeExamples: React.FC = () => {
```javascript
import { Component, h } from '@stencil/core';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
@Component({
tag: 'datetime-example',
@ -702,7 +801,17 @@ export class DatetimeExample {
<ion-button onClick={() => this.confirm()}>Good to go!</ion-button>
<ion-button onClick={() => this.reset()}>Reset</ion-button>
</ion-buttons>
</ion-datetime>,
</ion-datetime>
{/* Disable custom days */}
<ion-datetime isDateEnabled={(dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}}></ion-datetime>
{/* Datetime in overlay */}
<ion-button id="open-modal">Open Datetime Modal</ion-button>
@ -798,6 +907,9 @@ export class DatetimeExample {
<ion-button @click="reset()">Reset</ion-button>
</ion-buttons>
</ion-datetime>
<!-- Disable custom days -->
<ion-datetime :is-date-enabled="isDateEnabled"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
@ -846,7 +958,7 @@ export class DatetimeExample {
IonModal,
IonPopover
} from '@ionic/vue';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
export default defineComponent({
components: {
@ -880,10 +992,20 @@ export class DatetimeExample {
return format(parseISO(value), 'MMM dd yyyy');
};
const isDateEnabled = (dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}
return {
customDatetime,
confirm,
reset
reset,
isDateEnabled
}
}
})
@ -894,33 +1016,34 @@ export class DatetimeExample {
## Properties
| Property | Attribute | Description | Type | Default |
| ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------- |
| `cancelText` | `cancel-text` | The text to display on the picker's cancel button. | `string` | `'Cancel'` |
| `clearText` | `clear-text` | The text to display on the picker's "Clear" button. | `string` | `'Clear'` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `'primary'` |
| `dayValues` | `day-values` | Values used to create the list of selectable days. By default every day is shown for the given month. However, to control exactly which days of the month to display, the `dayValues` input can take a number, an array of numbers, or a string of comma separated numbers. Note that even if the array days have an invalid number for the selected month, like `31` in February, it will correctly not show days which are not valid for the selected month. | `number \| number[] \| string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the datetime. | `boolean` | `false` |
| `doneText` | `done-text` | The text to display on the picker's "Done" button. | `string` | `'Done'` |
| `firstDayOfWeek` | `first-day-of-week` | The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday. | `number` | `0` |
| `hourCycle` | `hour-cycle` | The hour cycle of the `ion-datetime`. If no value is set, this is specified by the current locale. | `"h12" \| "h23" \| undefined` | `undefined` |
| `hourValues` | `hour-values` | Values used to create the list of selectable hours. By default the hour values range from `0` to `23` for 24-hour, or `1` to `12` for 12-hour. However, to control exactly which hours to display, the `hourValues` input can take a number, an array of numbers, or a string of comma separated numbers. | `number \| number[] \| string \| undefined` | `undefined` |
| `locale` | `locale` | The locale to use for `ion-datetime`. This impacts month and day name formatting. The `'default'` value refers to the default locale set by your device. | `string` | `'default'` |
| `max` | `max` | The maximum datetime allowed. Value must be a date string following the [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime), `1996-12-19`. The format does not have to be specific to an exact datetime. For example, the maximum could just be the year, such as `1994`. Defaults to the end of this year. | `string \| undefined` | `undefined` |
| `min` | `min` | The minimum datetime allowed. Value must be a date string following the [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime), such as `1996-12-19`. The format does not have to be specific to an exact datetime. For example, the minimum could just be the year, such as `1994`. Defaults to the beginning of the year, 100 years ago from today. | `string \| undefined` | `undefined` |
| `minuteValues` | `minute-values` | Values used to create the list of selectable minutes. By default the minutes range from `0` to `59`. However, to control exactly which minutes to display, the `minuteValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if the minute selections should only be every 15 minutes, then this input value would be `minuteValues="0,15,30,45"`. | `number \| number[] \| string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `monthValues` | `month-values` | Values used to create the list of selectable months. By default the month values range from `1` to `12`. However, to control exactly which months to display, the `monthValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if only summer months should be shown, then this input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a zero-based index, meaning January's value is `1`, and December's is `12`. | `number \| number[] \| string \| undefined` | `undefined` |
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
| `presentation` | `presentation` | Which values you want to select. `'date'` will show a calendar picker to select the month, day, and year. `'time'` will show a time picker to select the hour, minute, and (optionally) AM/PM. `'date-time'` will show the date picker first and time picker second. `'time-date'` will show the time picker first and date picker second. | `"date" \| "date-time" \| "month" \| "month-year" \| "time" \| "time-date" \| "year"` | `'date-time'` |
| `readonly` | `readonly` | If `true`, the datetime appears normal but is not interactive. | `boolean` | `false` |
| `showClearButton` | `show-clear-button` | If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. | `boolean` | `false` |
| `showDefaultButtons` | `show-default-buttons` | If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. | `boolean` | `false` |
| `showDefaultTimeLabel` | `show-default-time-label` | If `true`, the default "Time" label will be rendered for the time selector of the `ion-datetime` component. Developers can also use the `time-label` slot if they want to customize this label. If a custom label is set in the `time-label` slot then the default label will not be rendered. | `boolean` | `true` |
| `showDefaultTitle` | `show-default-title` | If `true`, a header will be shown above the calendar picker. On `ios` mode this will include the slotted title, and on `md` mode this will include the slotted title and the selected date. | `boolean` | `false` |
| `size` | `size` | If `cover`, the `ion-datetime` will expand to cover the full width of its container. If `fixed`, the `ion-datetime` will have a fixed width. | `"cover" \| "fixed"` | `'fixed'` |
| `value` | `value` | The value of the datetime as a valid ISO 8601 datetime string. | `null \| string \| undefined` | `undefined` |
| `yearValues` | `year-values` | Values used to create the list of selectable years. By default the year values range between the `min` and `max` datetime inputs. However, to control exactly which years to display, the `yearValues` input can take a number, an array of numbers, or string of comma separated numbers. For example, to show upcoming and recent leap years, then this input's value would be `yearValues="2024,2020,2016,2012,2008"`. | `number \| number[] \| string \| undefined` | `undefined` |
| Property | Attribute | Description | Type | Default |
| ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | -------------- |
| `cancelText` | `cancel-text` | The text to display on the picker's cancel button. | `string` | `'Cancel'` |
| `clearText` | `clear-text` | The text to display on the picker's "Clear" button. | `string` | `'Clear'` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `'primary'` |
| `dayValues` | `day-values` | Values used to create the list of selectable days. By default every day is shown for the given month. However, to control exactly which days of the month to display, the `dayValues` input can take a number, an array of numbers, or a string of comma separated numbers. Note that even if the array days have an invalid number for the selected month, like `31` in February, it will correctly not show days which are not valid for the selected month. | `number \| number[] \| string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the datetime. | `boolean` | `false` |
| `doneText` | `done-text` | The text to display on the picker's "Done" button. | `string` | `'Done'` |
| `firstDayOfWeek` | `first-day-of-week` | The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday. | `number` | `0` |
| `hourCycle` | `hour-cycle` | The hour cycle of the `ion-datetime`. If no value is set, this is specified by the current locale. | `"h12" \| "h23" \| undefined` | `undefined` |
| `hourValues` | `hour-values` | Values used to create the list of selectable hours. By default the hour values range from `0` to `23` for 24-hour, or `1` to `12` for 12-hour. However, to control exactly which hours to display, the `hourValues` input can take a number, an array of numbers, or a string of comma separated numbers. | `number \| number[] \| string \| undefined` | `undefined` |
| `isDateEnabled` | -- | Returns if an individual date (calendar day) is enabled or disabled. If `true`, the day will be enabled/interactive. If `false`, the day will be disabled/non-interactive. The function accepts an ISO 8601 date string of a given day. By default, all days are enabled. Developers can use this function to write custom logic to disable certain days. Custom implementations should be optimized for performance. This function is called often, so any extra logic should be avoided to reduce and prevent jank. | `((dateIsoString: string) => boolean) \| undefined` | `undefined` |
| `locale` | `locale` | The locale to use for `ion-datetime`. This impacts month and day name formatting. The `'default'` value refers to the default locale set by your device. | `string` | `'default'` |
| `max` | `max` | The maximum datetime allowed. Value must be a date string following the [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime), `1996-12-19`. The format does not have to be specific to an exact datetime. For example, the maximum could just be the year, such as `1994`. Defaults to the end of this year. | `string \| undefined` | `undefined` |
| `min` | `min` | The minimum datetime allowed. Value must be a date string following the [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime), such as `1996-12-19`. The format does not have to be specific to an exact datetime. For example, the minimum could just be the year, such as `1994`. Defaults to the beginning of the year, 100 years ago from today. | `string \| undefined` | `undefined` |
| `minuteValues` | `minute-values` | Values used to create the list of selectable minutes. By default the minutes range from `0` to `59`. However, to control exactly which minutes to display, the `minuteValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if the minute selections should only be every 15 minutes, then this input value would be `minuteValues="0,15,30,45"`. | `number \| number[] \| string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `monthValues` | `month-values` | Values used to create the list of selectable months. By default the month values range from `1` to `12`. However, to control exactly which months to display, the `monthValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if only summer months should be shown, then this input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a zero-based index, meaning January's value is `1`, and December's is `12`. | `number \| number[] \| string \| undefined` | `undefined` |
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
| `presentation` | `presentation` | Which values you want to select. `'date'` will show a calendar picker to select the month, day, and year. `'time'` will show a time picker to select the hour, minute, and (optionally) AM/PM. `'date-time'` will show the date picker first and time picker second. `'time-date'` will show the time picker first and date picker second. | `"date" \| "date-time" \| "month" \| "month-year" \| "time" \| "time-date" \| "year"` | `'date-time'` |
| `readonly` | `readonly` | If `true`, the datetime appears normal but is not interactive. | `boolean` | `false` |
| `showClearButton` | `show-clear-button` | If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. | `boolean` | `false` |
| `showDefaultButtons` | `show-default-buttons` | If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. | `boolean` | `false` |
| `showDefaultTimeLabel` | `show-default-time-label` | If `true`, the default "Time" label will be rendered for the time selector of the `ion-datetime` component. Developers can also use the `time-label` slot if they want to customize this label. If a custom label is set in the `time-label` slot then the default label will not be rendered. | `boolean` | `true` |
| `showDefaultTitle` | `show-default-title` | If `true`, a header will be shown above the calendar picker. On `ios` mode this will include the slotted title, and on `md` mode this will include the slotted title and the selected date. | `boolean` | `false` |
| `size` | `size` | If `cover`, the `ion-datetime` will expand to cover the full width of its container. If `fixed`, the `ion-datetime` will have a fixed width. | `"cover" \| "fixed"` | `'fixed'` |
| `value` | `value` | The value of the datetime as a valid ISO 8601 datetime string. | `null \| string \| undefined` | `undefined` |
| `yearValues` | `year-values` | Values used to create the list of selectable years. By default the year values range between the `min` and `max` datetime inputs. However, to control exactly which years to display, the `yearValues` input can take a number, an array of numbers, or string of comma separated numbers. For example, to show upcoming and recent leap years, then this input's value would be `yearValues="2024,2020,2016,2012,2008"`. | `number \| number[] \| string \| undefined` | `undefined` |
## Events

View File

@ -0,0 +1,220 @@
import type { E2EPage } from '@stencil/core/testing';
import { newE2EPage } from '@stencil/core/testing';
const DISABLED_CALENDAR_DAY_SELECTOR = '.calendar-day[disabled]:not(.calendar-day-padding)';
function queryDisabledDay(page: E2EPage, datetimeSelector = 'ion-datetime') {
return page.find(`${datetimeSelector} >>> ${DISABLED_CALENDAR_DAY_SELECTOR}`);
}
function queryAllDisabledDays(page: E2EPage, datetimeSelector = 'ion-datetime') {
return page.findAll(`${datetimeSelector} >>> ${DISABLED_CALENDAR_DAY_SELECTOR}`);
}
function queryAllWorkingMonthDisabledDays(page: E2EPage, datetimeSelector = 'ion-datetime') {
return page.findAll(`${datetimeSelector} >>> .calendar-month:nth-child(2) ${DISABLED_CALENDAR_DAY_SELECTOR}`);
}
describe('datetime: disable dates', () => {
describe('return values', () => {
let page: E2EPage;
beforeEach(async () => {
page = await newE2EPage({
html: '<ion-datetime value="2021-10-01"></ion-datetime>'
});
});
describe('when isDateEnabled returns true', () => {
it('calendar days should be enabled', async () => {
await page.$eval('ion-datetime', (el: any) => {
el.isDateEnabled = () => true;
});
await page.waitForChanges();
const disabledDays = await queryAllDisabledDays(page);
expect(disabledDays.length).toBe(0);
});
});
describe('when isDateEnabled returns false', () => {
it('calendar days should be disabled', async () => {
await page.$eval('ion-datetime', (el: any) => {
el.isDateEnabled = () => false;
});
await page.waitForChanges();
const disabledDays = await queryAllDisabledDays(page);
expect(disabledDays.length).toBe(91);
});
});
describe('when isDateEnabled throws an exception', () => {
beforeEach(async () => {
await page.$eval('ion-datetime', (el: any) => {
el.isDateEnabled = (dateIsoString: string) => {
const date = new Date(dateIsoString);
if (date.getUTCDate() === 10 && date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) {
// Throws an exception on October 10, 2021
// Expected behavior: the day should be enabled
throw new Error('Expected exception for e2e test.');
}
return false;
};
});
});
it('calendar days should be enabled', async () => {
await page.waitForChanges();
const enabledDays = await page.findAll('ion-datetime >>> .calendar-month:nth-child(2) .calendar-day:not([disabled]):not(.calendar-day-padding)');
expect(enabledDays.length).toBe(1);
});
it('should throw an exception to the developer', async () => {
const errors = [];
page.on('console', (ev) => {
if (ev.type() === 'error') {
errors.push(ev.text());
}
});
await page.waitForChanges();
expect(errors.length).toBe(1);
expect(errors[0]).toContain('[Ionic Error]: Exception thrown from provided `isDateEnabled` function. Please check your function and try again.');
});
});
describe('when isDateEnabled returns undefined', () => {
it('calendar days should be disabled', async () => {
await page.$eval('ion-datetime', (el: any) => {
el.isDateEnabled = () => undefined;
});
await page.waitForChanges();
const disabledDays = await queryAllDisabledDays(page);
expect(disabledDays.length).toBe(91);
});
});
describe('when isDateEnabled returns null', () => {
it('calendar days should be disabled', async () => {
await page.$eval('ion-datetime', (el: any) => {
el.isDateEnabled = () => null;
});
await page.waitForChanges();
const disabledDays = await queryAllDisabledDays(page);
expect(disabledDays.length).toBe(91);
});
});
});
describe('examples', () => {
let page: E2EPage;
beforeEach(async () => {
page = await newE2EPage({
url: '/src/components/datetime/test/disable-dates?ionic:_testing=true'
});
});
it('should disable a specific date', async () => {
const disabledDay = await queryDisabledDay(page, '#specificDate');
expect(disabledDay.textContent).toBe('10');
});
it('should disable specific days of the week', async () => {
const disabledDays = await queryAllWorkingMonthDisabledDays(page, '#weekends');
const disabledValues = disabledDays.map(d => d.textContent);
expect(disabledValues).toEqual(['2', '3', '9', '10', '16', '17', '23', '24', '30', '31']);
});
it('should disable a range of dates', async () => {
const disabledDays = await queryAllDisabledDays(page, '#dateRange');
const disabledValues = disabledDays.map(d => d.textContent);
expect(disabledValues).toEqual(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20']);
});
it('should disable a month', async () => {
const disabledDays = await queryAllDisabledDays(page, '#month');
const disabledValues = disabledDays.map(d => d.textContent);
expect(disabledValues.length).toBe(31);
});
});
describe('with a min date range', () => {
it('should not enable already disabled dates', async () => {
const page = await newE2EPage({
html: `
<ion-datetime min="2021-10-15" value="2021-10-16"></ion-datetime>
<script>
const datetime = document.querySelector('ion-datetime');
datetime.isDateEnabled = () => true;
</script>
`
});
const disabledDays = await queryAllWorkingMonthDisabledDays(page);
expect(disabledDays.length).toBe(14);
});
});
describe('with a max date range', () => {
it('should not enable already disabled dates', async () => {
const page = await newE2EPage({
html: `
<ion-datetime max="2021-10-15" value="2021-10-16"></ion-datetime>
<script>
const datetime = document.querySelector('ion-datetime');
datetime.isDateEnabled = () => true;
</script>
`
});
const disabledDays = await queryAllWorkingMonthDisabledDays(page);
expect(disabledDays.length).toBe(16);
});
});
});

View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Datetime - Disable Dates</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(250px, 1fr));
grid-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
ion-datetime {
box-shadow: 0px 16px 32px rgba(0, 0, 0, 0.25), 0px 8px 16px rgba(0, 0, 0, 0.25);
border-radius: 8px;
}
</style>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Datetime - Disable Dates</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Disable Specific Date</h2>
<ion-datetime id="specificDate" value="2021-10-01"></ion-datetime>
</div>
<div class="grid-item">
<h2>Disable Weekends</h2>
<ion-datetime id="weekends" value="2021-10-01"></ion-datetime>
</div>
<div class="grid-item">
<h2>Disable Date Range</h2>
<ion-datetime id="dateRange" value="2021-10-01"></ion-datetime>
</div>
<div class="grid-item">
<h2>Disable Month</h2>
<ion-datetime id="month" value="2021-10-01"></ion-datetime>
</div>
</div>
</ion-content>
<script>
const specificDateDisabled = document.querySelector('#specificDate');
specificDateDisabled.isDateEnabled = (dateIsoString) => {
const date = new Date(dateIsoString);
// Disables October 10, 2021.
if (date.getUTCDate() === 10 && date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) {
return false;
}
return true;
}
const weekendsDisabled = document.querySelector('#weekends');
weekendsDisabled.isDateEnabled = (dateIsoString) => {
const date = new Date(dateIsoString);
// Disables Sunday and Saturday
if (date.getUTCDay() === 0 || date.getUTCDay() === 6) {
return false;
}
return true;
}
const dateRangeDisabled = document.querySelector('#dateRange');
dateRangeDisabled.isDateEnabled = (dateIsoString) => {
const date = new Date(dateIsoString);
// Disables dates between October 10, 2021 and October 20, 2021.
if (date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) {
if (date.getUTCDate() >= 10 && date.getUTCDate() <= 20) {
return false;
}
}
return true;
}
const monthDisabled = document.querySelector('#month');
monthDisabled.isDateEnabled = (dateIsoString) => {
const date = new Date(dateIsoString);
// Disables October (every year)
if (date.getUTCMonth() === 9) {
return false;
}
return true;
}
</script>
</ion-app>
</body>
</html>

View File

@ -43,6 +43,9 @@
<!-- Clear button -->
<ion-datetime [showClearButton]="true"></ion-datetime>
<!-- Disable custom days -->
<ion-datetime [isDateEnabled]="isDateEnabled"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
<ion-modal trigger="open-modal">
@ -97,7 +100,7 @@
```typescript
import { Component, ViewChild } from '@angular/core';
import { IonDatetime } from '@ionic/angular';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
@Component({})
export class MyComponent {
@ -119,5 +122,14 @@ export class MyComponent {
formatDate(value: string) {
return format(parseISO(value), 'MMM dd yyyy');
}
isDateEnabled(dateIsoString: string) {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}
}
```

View File

@ -51,6 +51,9 @@
</ion-buttons>
</ion-datetime>
<!-- Disable custom dates -->
<ion-datetime id="disabled-date-datetime"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
<ion-modal trigger="open-modal">
@ -81,7 +84,7 @@
```
```javascript
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
const datetime = document.querySelector('#custom-datetime');
@ -97,6 +100,18 @@ const formatDate = (value: string) => {
return format(parseISO(value), 'MMM dd yyyy');
};
const isDateEnabled = (dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
};
const disabledDateDatetime = document.querySelector('#disabled-date-datetime');
disabledDateDatetime.isDateEnabled = isDateEnabled;
const popoverDatetime = document.querySelector('#popover-datetime');
const dateInput = document.querySelector('#date-input');
popoverDatetime.addEventListener('ionChange', ev => dateInput.innerText = formatDate(ev.detail.value));

View File

@ -12,7 +12,7 @@ import {
IonPopover
} from '@ionic/react';
import { calendar } from 'ionicons/icons';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
export const DateTimeExamples: React.FC = () => {
const [selectedDate, setSelectedDate] = useState('2012-12-15T13:47:20.789');
@ -89,6 +89,16 @@ export const DateTimeExamples: React.FC = () => {
<IonButton onClick={() => reset()}>Reset</IonButton>
</IonButtons>
</IonDatetime>
{/* Disable custom days */}
<IonDatetime isDateEnabled={(dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}}></IonDatetime>
{/* Datetime in overlay */}
<IonButton id="open-modal">Open Datetime Modal</IonButton>

View File

@ -1,6 +1,6 @@
```javascript
import { Component, h } from '@stencil/core';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
@Component({
tag: 'datetime-example',
@ -81,7 +81,17 @@ export class DatetimeExample {
<ion-button onClick={() => this.confirm()}>Good to go!</ion-button>
<ion-button onClick={() => this.reset()}>Reset</ion-button>
</ion-buttons>
</ion-datetime>,
</ion-datetime>
{/* Disable custom days */}
<ion-datetime isDateEnabled={(dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}}></ion-datetime>
{/* Datetime in overlay */}
<ion-button id="open-modal">Open Datetime Modal</ion-button>

View File

@ -51,6 +51,9 @@
<ion-button @click="reset()">Reset</ion-button>
</ion-buttons>
</ion-datetime>
<!-- Disable custom days -->
<ion-datetime :is-date-enabled="isDateEnabled"></ion-datetime>
<!-- Datetime in overlay -->
<ion-button id="open-modal">Open Datetime Modal</ion-button>
@ -99,7 +102,7 @@
IonModal,
IonPopover
} from '@ionic/vue';
import { format, parseISO } from 'date-fns';
import { format, parseISO, getDate, getMonth, getYear } from 'date-fns';
export default defineComponent({
components: {
@ -133,10 +136,20 @@
return format(parseISO(value), 'MMM dd yyyy');
};
const isDateEnabled = (dateIsoString: string) => {
const date = new Date(dateIsoString);
if (getDate(date) === 1 && getMonth(date) === 0 && getYear(date) === 2022) {
// Disables January 1, 2022.
return false;
}
return true;
}
return {
customDatetime,
confirm,
reset
reset,
isDateEnabled
}
}
})