mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
fix(datetime): ensure that default month shown is always in bounds (#25351)
Resolves #25320
This commit is contained in:
2
core/src/components.d.ts
vendored
2
core/src/components.d.ts
vendored
@ -798,7 +798,7 @@ export namespace Components {
|
||||
*/
|
||||
"readonly": boolean;
|
||||
/**
|
||||
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to today.
|
||||
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
|
||||
*/
|
||||
"reset": (startDate?: string | undefined) => Promise<void>;
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ import {
|
||||
getPreviousYear,
|
||||
getStartOfWeek,
|
||||
} from './utils/manipulation';
|
||||
import { convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
|
||||
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
|
||||
import {
|
||||
getCalendarDayState,
|
||||
isDayDisabled,
|
||||
@ -472,7 +472,7 @@ export class Datetime implements ComponentInterface {
|
||||
/**
|
||||
* Resets the internal state of the datetime but does not update the value.
|
||||
* Passing a valid ISO-8601 string will reset the state of the component to the provided date.
|
||||
* If no value is provided, the internal state will be reset to today.
|
||||
* If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
|
||||
*/
|
||||
@Method()
|
||||
async reset(startDate?: string) {
|
||||
@ -1083,8 +1083,8 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private processValue = (value?: string | null) => {
|
||||
this.highlightActiveParts = !!value;
|
||||
const valueToProcess = value || getToday();
|
||||
const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess);
|
||||
const valueToProcess = parseDate(value || getToday());
|
||||
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, this.minParts, this.maxParts);
|
||||
|
||||
this.setWorkingParts({
|
||||
month,
|
||||
@ -1093,7 +1093,7 @@ export class Datetime implements ComponentInterface {
|
||||
hour,
|
||||
minute,
|
||||
tzOffset,
|
||||
ampm: hour >= 12 ? 'pm' : 'am',
|
||||
ampm: hour! >= 12 ? 'pm' : 'am',
|
||||
});
|
||||
|
||||
this.activeParts = {
|
||||
@ -1103,7 +1103,7 @@ export class Datetime implements ComponentInterface {
|
||||
hour,
|
||||
minute,
|
||||
tzOffset,
|
||||
ampm: hour >= 12 ? 'pm' : 'am',
|
||||
ampm: hour! >= 12 ? 'pm' : 'am',
|
||||
};
|
||||
};
|
||||
|
||||
@ -1676,8 +1676,8 @@ export class Datetime implements ComponentInterface {
|
||||
const { hours, minutes, am, pm } = generateTime(
|
||||
workingParts,
|
||||
use24Hour ? 'h23' : 'h12',
|
||||
this.minParts,
|
||||
this.maxParts,
|
||||
this.value ? this.minParts : undefined,
|
||||
this.value ? this.maxParts : undefined,
|
||||
this.parsedHourValues,
|
||||
this.parsedMinuteValues
|
||||
);
|
||||
|
@ -47,4 +47,33 @@ test.describe('datetime: minmax', () => {
|
||||
expect(nextButton).toBeDisabled();
|
||||
expect(prevButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test.describe('when the datetime does not have a value', () => {
|
||||
test('all time values should be available for selection', async ({ page }) => {
|
||||
/**
|
||||
* When the datetime does not have an initial value and today falls outside of
|
||||
* the specified min and max values, all times values should be available for selection.
|
||||
*/
|
||||
await page.setContent(`
|
||||
<ion-datetime min="2022-04-22T04:10:00" max="2022-05-21T21:30:00"></ion-datetime>
|
||||
`);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
|
||||
|
||||
await page.click('.time-body');
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
const hours = page.locator(
|
||||
'ion-popover ion-picker-column-internal:nth-child(1) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
const minutes = page.locator(
|
||||
'ion-popover ion-picker-column-internal:nth-child(2) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
|
||||
expect(await hours.count()).toBe(12);
|
||||
expect(await minutes.count()).toBe(60);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getPartsFromCalendarDay } from '../utils/parse';
|
||||
import { clampDate, getPartsFromCalendarDay } from '../utils/parse';
|
||||
|
||||
describe('getPartsFromCalendarDay()', () => {
|
||||
it('should extract DatetimeParts from a calendar day element', () => {
|
||||
@ -18,3 +18,46 @@ describe('getPartsFromCalendarDay()', () => {
|
||||
});
|
||||
|
||||
// TODO: parseDate()
|
||||
|
||||
describe('clampDate()', () => {
|
||||
const minParts = {
|
||||
year: 2021,
|
||||
month: 6,
|
||||
day: 5,
|
||||
};
|
||||
|
||||
const maxParts = {
|
||||
year: 2021,
|
||||
month: 8,
|
||||
day: 19,
|
||||
};
|
||||
it('should return the max month when the value is greater than the max', () => {
|
||||
const dateParts = {
|
||||
year: 2022,
|
||||
month: 5,
|
||||
day: 24,
|
||||
};
|
||||
const value = clampDate(dateParts, minParts, maxParts);
|
||||
expect(value).toStrictEqual(maxParts);
|
||||
});
|
||||
|
||||
it('should return the min month when the value is less than the min', () => {
|
||||
const dateParts = {
|
||||
year: 2020,
|
||||
month: 5,
|
||||
day: 24,
|
||||
};
|
||||
const value = clampDate(dateParts, minParts, maxParts);
|
||||
expect(value).toStrictEqual(minParts);
|
||||
});
|
||||
|
||||
it('should return the value when the value is greater than the min and less than the max', () => {
|
||||
const dateParts = {
|
||||
year: 2021,
|
||||
month: 7,
|
||||
day: 10,
|
||||
};
|
||||
const value = clampDate(dateParts, minParts, maxParts);
|
||||
expect(value).toStrictEqual(dateParts);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
|
||||
import { isAfter, isBefore } from './comparison';
|
||||
|
||||
const ISO_8601_REGEXP =
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
|
||||
@ -106,3 +108,16 @@ export const parseDate = (val: string | undefined | null): any | undefined => {
|
||||
tzOffset,
|
||||
};
|
||||
};
|
||||
|
||||
export const clampDate = (
|
||||
dateParts: DatetimeParts,
|
||||
minParts?: DatetimeParts,
|
||||
maxParts?: DatetimeParts
|
||||
): DatetimeParts => {
|
||||
if (minParts && isBefore(dateParts, minParts)) {
|
||||
return minParts;
|
||||
} else if (maxParts && isAfter(dateParts, maxParts)) {
|
||||
return maxParts;
|
||||
}
|
||||
return dateParts;
|
||||
};
|
||||
|
Reference in New Issue
Block a user