mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 23:58:13 +08:00
fix(datetime): only log out of bounds warning if value set (#25835)
resolves #25833
This commit is contained in:
@ -46,7 +46,15 @@ import {
|
|||||||
getPreviousYear,
|
getPreviousYear,
|
||||||
getStartOfWeek,
|
getStartOfWeek,
|
||||||
} from './utils/manipulation';
|
} from './utils/manipulation';
|
||||||
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseAmPm, parseDate } from './utils/parse';
|
import {
|
||||||
|
clampDate,
|
||||||
|
convertToArrayOfNumbers,
|
||||||
|
getPartsFromCalendarDay,
|
||||||
|
parseAmPm,
|
||||||
|
parseDate,
|
||||||
|
parseMaxParts,
|
||||||
|
parseMinParts,
|
||||||
|
} from './utils/parse';
|
||||||
import {
|
import {
|
||||||
getCalendarDayState,
|
getCalendarDayState,
|
||||||
isDayDisabled,
|
isDayDisabled,
|
||||||
@ -774,37 +782,24 @@ export class Datetime implements ComponentInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private processMinParts = () => {
|
private processMinParts = () => {
|
||||||
if (this.min === undefined) {
|
const { min, todayParts } = this;
|
||||||
|
if (min === undefined) {
|
||||||
this.minParts = undefined;
|
this.minParts = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { month, day, year, hour, minute } = parseDate(this.min);
|
this.minParts = parseMinParts(min, todayParts);
|
||||||
|
|
||||||
this.minParts = {
|
|
||||||
month,
|
|
||||||
day,
|
|
||||||
year,
|
|
||||||
hour,
|
|
||||||
minute,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private processMaxParts = () => {
|
private processMaxParts = () => {
|
||||||
if (this.max === undefined) {
|
const { max, todayParts } = this;
|
||||||
|
|
||||||
|
if (max === undefined) {
|
||||||
this.maxParts = undefined;
|
this.maxParts = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { month, day, year, hour, minute } = parseDate(this.max);
|
this.maxParts = parseMaxParts(max, todayParts);
|
||||||
|
|
||||||
this.maxParts = {
|
|
||||||
month,
|
|
||||||
day,
|
|
||||||
year,
|
|
||||||
hour,
|
|
||||||
minute,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeCalendarListener = () => {
|
private initializeCalendarListener = () => {
|
||||||
@ -1140,7 +1135,8 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private processValue = (value?: string | string[] | null) => {
|
private processValue = (value?: string | string[] | null) => {
|
||||||
this.highlightActiveParts = !!value;
|
const hasValue = !!value;
|
||||||
|
this.highlightActiveParts = hasValue;
|
||||||
let valueToProcess = parseDate(value || getToday());
|
let valueToProcess = parseDate(value || getToday());
|
||||||
|
|
||||||
const { minParts, maxParts, multiple } = this;
|
const { minParts, maxParts, multiple } = this;
|
||||||
@ -1149,7 +1145,17 @@ export class Datetime implements ComponentInterface {
|
|||||||
valueToProcess = (valueToProcess as DatetimeParts[])[0];
|
valueToProcess = (valueToProcess as DatetimeParts[])[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
|
/**
|
||||||
|
* Datetime should only warn of out of bounds values
|
||||||
|
* if set by the user. If the `value` is undefined,
|
||||||
|
* we will default to today's date which may be out
|
||||||
|
* of bounds. In this case, the warning makes it look
|
||||||
|
* like the developer did something wrong which is
|
||||||
|
* not true.
|
||||||
|
*/
|
||||||
|
if (hasValue) {
|
||||||
|
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there are multiple values, pick an arbitrary one to clamp to. This way,
|
* If there are multiple values, pick an arbitrary one to clamp to. This way,
|
||||||
|
|||||||
@ -111,27 +111,34 @@ test.describe('datetime: minmax', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe('setting value outside bounds should show in-bounds month', () => {
|
test.describe('setting value outside bounds should show in-bounds month', () => {
|
||||||
const testDisplayedMonth = async (page: E2EPage, content: string) => {
|
test.beforeEach(({ skip }) => {
|
||||||
|
skip.rtl();
|
||||||
|
});
|
||||||
|
const testDisplayedMonth = async (page: E2EPage, content: string, expectedString = 'June 2021') => {
|
||||||
await page.setContent(content);
|
await page.setContent(content);
|
||||||
await page.waitForSelector('.datetime-ready');
|
await page.waitForSelector('.datetime-ready');
|
||||||
|
|
||||||
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
|
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
|
||||||
await expect(calendarMonthYear).toHaveText('June 2021');
|
await expect(calendarMonthYear).toHaveText(expectedString);
|
||||||
};
|
};
|
||||||
|
|
||||||
test('when min is defined', async ({ page }) => {
|
test('when min and value are defined', async ({ page }) => {
|
||||||
await testDisplayedMonth(page, `<ion-datetime min="2021-06-01" value="2021-05-01"></ion-datetime>`);
|
await testDisplayedMonth(page, `<ion-datetime min="2021-06-01" value="2021-05-01"></ion-datetime>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when max is defined', async ({ page }) => {
|
test('when max and value are defined', async ({ page }) => {
|
||||||
await testDisplayedMonth(page, `<ion-datetime max="2021-06-30" value="2021-07-01"></ion-datetime>`);
|
await testDisplayedMonth(page, `<ion-datetime max="2021-06-30" value="2021-07-01"></ion-datetime>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when both min and max are defined', async ({ page }) => {
|
test('when min, max, and value are defined', async ({ page }) => {
|
||||||
await testDisplayedMonth(
|
await testDisplayedMonth(
|
||||||
page,
|
page,
|
||||||
`<ion-datetime min="2021-06-01" max="2021-06-30" value="2021-05-01"></ion-datetime>`
|
`<ion-datetime min="2021-06-01" max="2021-06-30" value="2021-05-01"></ion-datetime>`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('when max is defined', async ({ page }) => {
|
||||||
|
await testDisplayedMonth(page, `<ion-datetime max="2012-06-01"></ion-datetime>`, 'June 2012');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { clampDate, getPartsFromCalendarDay, parseAmPm } from '../utils/parse';
|
import { clampDate, getPartsFromCalendarDay, parseAmPm, parseMinParts, parseMaxParts } from '../utils/parse';
|
||||||
|
|
||||||
describe('getPartsFromCalendarDay()', () => {
|
describe('getPartsFromCalendarDay()', () => {
|
||||||
it('should extract DatetimeParts from a calendar day element', () => {
|
it('should extract DatetimeParts from a calendar day element', () => {
|
||||||
@ -72,3 +72,89 @@ describe('parseAmPm()', () => {
|
|||||||
expect(parseAmPm(11)).toEqual('am');
|
expect(parseAmPm(11)).toEqual('am');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseMinParts()', () => {
|
||||||
|
it('should fill in missing information when not provided', () => {
|
||||||
|
const today = {
|
||||||
|
day: 14,
|
||||||
|
month: 3,
|
||||||
|
year: 2022,
|
||||||
|
minute: 4,
|
||||||
|
hour: 2,
|
||||||
|
};
|
||||||
|
expect(parseMinParts('2012', today)).toEqual({
|
||||||
|
month: 1,
|
||||||
|
day: 1,
|
||||||
|
year: 2012,
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should default to current year when only given HH:mm', () => {
|
||||||
|
const today = {
|
||||||
|
day: 14,
|
||||||
|
month: 3,
|
||||||
|
year: 2022,
|
||||||
|
minute: 4,
|
||||||
|
hour: 2,
|
||||||
|
};
|
||||||
|
expect(parseMinParts('04:30', today)).toEqual({
|
||||||
|
month: 1,
|
||||||
|
day: 1,
|
||||||
|
year: 2022,
|
||||||
|
hour: 4,
|
||||||
|
minute: 30,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseMaxParts()', () => {
|
||||||
|
it('should fill in missing information when not provided', () => {
|
||||||
|
const today = {
|
||||||
|
day: 14,
|
||||||
|
month: 3,
|
||||||
|
year: 2022,
|
||||||
|
minute: 4,
|
||||||
|
hour: 2,
|
||||||
|
};
|
||||||
|
expect(parseMaxParts('2012', today)).toEqual({
|
||||||
|
month: 12,
|
||||||
|
day: 31,
|
||||||
|
year: 2012,
|
||||||
|
hour: 23,
|
||||||
|
minute: 59,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should default to current year when only given HH:mm', () => {
|
||||||
|
const today = {
|
||||||
|
day: 14,
|
||||||
|
month: 3,
|
||||||
|
year: 2022,
|
||||||
|
minute: 4,
|
||||||
|
hour: 2,
|
||||||
|
};
|
||||||
|
expect(parseMaxParts('04:30', today)).toEqual({
|
||||||
|
month: 12,
|
||||||
|
day: 31,
|
||||||
|
year: 2022,
|
||||||
|
hour: 4,
|
||||||
|
minute: 30,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fill in correct day during a leap year', () => {
|
||||||
|
const today = {
|
||||||
|
day: 14,
|
||||||
|
month: 3,
|
||||||
|
year: 2022,
|
||||||
|
minute: 4,
|
||||||
|
hour: 2,
|
||||||
|
};
|
||||||
|
expect(parseMaxParts('2012-02', today)).toEqual({
|
||||||
|
month: 2,
|
||||||
|
day: 29,
|
||||||
|
year: 2012,
|
||||||
|
hour: 23,
|
||||||
|
minute: 59,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { DatetimeParts } from '../datetime-interface';
|
import type { DatetimeParts } from '../datetime-interface';
|
||||||
|
|
||||||
import { isAfter, isBefore } from './comparison';
|
import { isAfter, isBefore } from './comparison';
|
||||||
|
import { getNumDaysInMonth } from './helpers';
|
||||||
|
|
||||||
const ISO_8601_REGEXP =
|
const ISO_8601_REGEXP =
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
@ -138,3 +139,72 @@ export const clampDate = (
|
|||||||
export const parseAmPm = (hour: number) => {
|
export const parseAmPm = (hour: number) => {
|
||||||
return hour >= 12 ? 'pm' : 'am';
|
return hour >= 12 ? 'pm' : 'am';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a max date string and creates a DatetimeParts
|
||||||
|
* object, filling in any missing information.
|
||||||
|
* For example, max="2012" would fill in the missing
|
||||||
|
* month, day, hour, and minute information.
|
||||||
|
*/
|
||||||
|
export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeParts => {
|
||||||
|
const { month, day, year, hour, minute } = parseDate(max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When passing in `max` or `min`, developers
|
||||||
|
* can pass in any ISO-8601 string. This means
|
||||||
|
* that not all of the date/time fields are defined.
|
||||||
|
* For example, passing max="2012" is valid even though
|
||||||
|
* there is no month, day, hour, or minute data.
|
||||||
|
* However, all of this data is required when clamping the date
|
||||||
|
* so that the correct initial value can be selected. As a result,
|
||||||
|
* we need to fill in any omitted data with the min or max values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const yearValue = year ?? todayParts.year;
|
||||||
|
const monthValue = month ?? 12;
|
||||||
|
return {
|
||||||
|
month: monthValue,
|
||||||
|
day: day ?? getNumDaysInMonth(monthValue, yearValue),
|
||||||
|
/**
|
||||||
|
* Passing in "HH:mm" is a valid ISO-8601
|
||||||
|
* string, so we just default to the current year
|
||||||
|
* in this case.
|
||||||
|
*/
|
||||||
|
year: yearValue,
|
||||||
|
hour: hour ?? 23,
|
||||||
|
minute: minute ?? 59,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a min date string and creates a DatetimeParts
|
||||||
|
* object, filling in any missing information.
|
||||||
|
* For example, min="2012" would fill in the missing
|
||||||
|
* month, day, hour, and minute information.
|
||||||
|
*/
|
||||||
|
export const parseMinParts = (min: string, todayParts: DatetimeParts): DatetimeParts => {
|
||||||
|
const { month, day, year, hour, minute } = parseDate(min);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When passing in `max` or `min`, developers
|
||||||
|
* can pass in any ISO-8601 string. This means
|
||||||
|
* that not all of the date/time fields are defined.
|
||||||
|
* For example, passing max="2012" is valid even though
|
||||||
|
* there is no month, day, hour, or minute data.
|
||||||
|
* However, all of this data is required when clamping the date
|
||||||
|
* so that the correct initial value can be selected. As a result,
|
||||||
|
* we need to fill in any omitted data with the min or max values.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
month: month ?? 1,
|
||||||
|
day: day ?? 1,
|
||||||
|
/**
|
||||||
|
* Passing in "HH:mm" is a valid ISO-8601
|
||||||
|
* string, so we just default to the current year
|
||||||
|
* in this case.
|
||||||
|
*/
|
||||||
|
year: year ?? todayParts.year,
|
||||||
|
hour: hour ?? 0,
|
||||||
|
minute: minute ?? 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user