mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
fix(datetime): prefer wheel sets working value on confirmation (#28520)
Issue number: resolves #25839 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Confirming the working day when a datetime using a wheel picker without an initial value will result in a value of `undefined` instead of the displayed working day the user sees. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - `preferWheel` uses the working value on confirmation ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev-build: `7.5.7-dev.11701896424.13d40ac9` Co-authored-by: liamdebeasi <liamdebeasi@users.noreply.github.com>
This commit is contained in:
@ -492,7 +492,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
async confirm(closeOverlay = false) {
|
async confirm(closeOverlay = false) {
|
||||||
const { isCalendarPicker, activeParts } = this;
|
const { isCalendarPicker, activeParts, preferWheel, workingParts } = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We only update the value if the presentation is not a calendar picker.
|
* We only update the value if the presentation is not a calendar picker.
|
||||||
@ -500,7 +500,16 @@ export class Datetime implements ComponentInterface {
|
|||||||
if (activeParts !== undefined || !isCalendarPicker) {
|
if (activeParts !== undefined || !isCalendarPicker) {
|
||||||
const activePartsIsArray = Array.isArray(activeParts);
|
const activePartsIsArray = Array.isArray(activeParts);
|
||||||
if (activePartsIsArray && activeParts.length === 0) {
|
if (activePartsIsArray && activeParts.length === 0) {
|
||||||
|
if (preferWheel) {
|
||||||
|
/**
|
||||||
|
* If the datetime is using a wheel picker, but the
|
||||||
|
* active parts are empty, then the user has confirmed the
|
||||||
|
* initial value (working parts) presented to them.
|
||||||
|
*/
|
||||||
|
this.setValue(convertDataToISO(workingParts));
|
||||||
|
} else {
|
||||||
this.setValue(undefined);
|
this.setValue(undefined);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setValue(convertDataToISO(activeParts));
|
this.setValue(convertDataToISO(activeParts));
|
||||||
}
|
}
|
||||||
@ -1356,11 +1365,21 @@ export class Datetime implements ComponentInterface {
|
|||||||
const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
|
const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
|
||||||
|
|
||||||
const todayParts = (this.todayParts = parseDate(getToday())!);
|
const todayParts = (this.todayParts = parseDate(getToday())!);
|
||||||
this.defaultParts = getClosestValidDate(todayParts, monthValues, dayValues, yearValues, hourValues, minuteValues);
|
|
||||||
|
|
||||||
this.processMinParts();
|
this.processMinParts();
|
||||||
this.processMaxParts();
|
this.processMaxParts();
|
||||||
|
|
||||||
|
this.defaultParts = getClosestValidDate({
|
||||||
|
refParts: todayParts,
|
||||||
|
monthValues,
|
||||||
|
dayValues,
|
||||||
|
yearValues,
|
||||||
|
hourValues,
|
||||||
|
minuteValues,
|
||||||
|
minParts: this.minParts,
|
||||||
|
maxParts: this.maxParts,
|
||||||
|
});
|
||||||
|
|
||||||
this.processValue(this.value);
|
this.processValue(this.value);
|
||||||
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
subtractDays,
|
subtractDays,
|
||||||
addDays,
|
addDays,
|
||||||
validateParts,
|
validateParts,
|
||||||
|
getClosestValidDate,
|
||||||
} from '../utils/manipulation';
|
} from '../utils/manipulation';
|
||||||
|
|
||||||
describe('addDays()', () => {
|
describe('addDays()', () => {
|
||||||
@ -558,3 +559,160 @@ describe('validateParts()', () => {
|
|||||||
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 });
|
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getClosestValidDate()', () => {
|
||||||
|
it('should match a date with only month/day/year', () => {
|
||||||
|
// October 10, 2023
|
||||||
|
const refParts = { month: 10, day: 10, year: 2023 };
|
||||||
|
// April 10, 2021
|
||||||
|
const minParts = { month: 4, day: 10, year: 2021 };
|
||||||
|
// September 14, 2021
|
||||||
|
const maxParts = { month: 9, day: 14, year: 2021 };
|
||||||
|
|
||||||
|
// September 4, 2021
|
||||||
|
const expected = { month: 9, day: 4, year: 2021, dayOfWeek: undefined };
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getClosestValidDate({
|
||||||
|
refParts,
|
||||||
|
monthValues: [2, 3, 7, 9, 10],
|
||||||
|
dayValues: [4, 15, 25],
|
||||||
|
yearValues: [2020, 2021, 2023],
|
||||||
|
maxParts,
|
||||||
|
minParts,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match a date when the reference date is before the min', () => {
|
||||||
|
// April 2, 2020 3:20 PM
|
||||||
|
const refParts = { month: 4, day: 2, year: 2020, hour: 15, minute: 20 };
|
||||||
|
// September 10, 2021 10:10 AM
|
||||||
|
const minParts = { month: 9, day: 10, year: 2021, hour: 10, minute: 10 };
|
||||||
|
// September 14, 2021 10:11 AM
|
||||||
|
const maxParts = { month: 9, day: 14, year: 2021, hour: 10, minute: 11 };
|
||||||
|
|
||||||
|
// September 11, 2021 11:15 AM
|
||||||
|
const expected = {
|
||||||
|
year: 2021,
|
||||||
|
day: 11,
|
||||||
|
month: 9,
|
||||||
|
hour: 11,
|
||||||
|
minute: 15,
|
||||||
|
ampm: 'am',
|
||||||
|
dayOfWeek: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getClosestValidDate({
|
||||||
|
refParts,
|
||||||
|
monthValues: [4, 9, 11],
|
||||||
|
dayValues: [11, 12, 13, 14],
|
||||||
|
yearValues: [2020, 2021, 2023],
|
||||||
|
hourValues: [9, 10, 11],
|
||||||
|
minuteValues: [11, 12, 13, 14, 15],
|
||||||
|
maxParts,
|
||||||
|
minParts,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match a date when the reference date is before the min', () => {
|
||||||
|
// April 2, 2020 3:20 PM
|
||||||
|
const refParts = { month: 4, day: 2, year: 2020, hour: 15, minute: 20 };
|
||||||
|
// September 10, 2021 10:10 AM
|
||||||
|
const minParts = { month: 9, day: 10, year: 2021, hour: 10, minute: 10 };
|
||||||
|
// September 10, 2021 10:15 AM
|
||||||
|
const maxParts = { month: 9, day: 10, year: 2021, hour: 10, minute: 15 };
|
||||||
|
|
||||||
|
// September 10, 2021 10:15 AM
|
||||||
|
const expected = {
|
||||||
|
month: 9,
|
||||||
|
day: 10,
|
||||||
|
year: 2021,
|
||||||
|
hour: 10,
|
||||||
|
minute: 15,
|
||||||
|
ampm: 'am',
|
||||||
|
dayOfWeek: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getClosestValidDate({
|
||||||
|
refParts,
|
||||||
|
monthValues: [4, 9, 11],
|
||||||
|
dayValues: [10, 12, 13, 14],
|
||||||
|
yearValues: [2020, 2021, 2023],
|
||||||
|
hourValues: [9, 10, 11],
|
||||||
|
minuteValues: [11, 12, 13, 14, 15],
|
||||||
|
minParts,
|
||||||
|
maxParts,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only clamp minutes if within the same day and hour as min/max', () => {
|
||||||
|
// April 2, 2020 9:16 AM
|
||||||
|
const refParts = { month: 4, day: 2, year: 2020, hour: 9, minute: 16 };
|
||||||
|
// September 10, 2021 10:10 AM
|
||||||
|
const minParts = { month: 9, day: 10, year: 2021, hour: 10, minute: 10 };
|
||||||
|
// September 10, 2021 11:15 AM
|
||||||
|
const maxParts = { month: 9, day: 10, year: 2021, hour: 11, minute: 15 };
|
||||||
|
|
||||||
|
// September 10, 2021 10:16 AM
|
||||||
|
const expected = {
|
||||||
|
month: 9,
|
||||||
|
day: 10,
|
||||||
|
year: 2021,
|
||||||
|
hour: 10,
|
||||||
|
minute: 16,
|
||||||
|
ampm: 'am',
|
||||||
|
dayOfWeek: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getClosestValidDate({
|
||||||
|
refParts,
|
||||||
|
monthValues: [4, 9, 11],
|
||||||
|
dayValues: [10, 12, 13, 14],
|
||||||
|
yearValues: [2020, 2021, 2023],
|
||||||
|
hourValues: [9, 10, 11],
|
||||||
|
minuteValues: [10, 15, 16],
|
||||||
|
minParts,
|
||||||
|
maxParts,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the closest valid date after adjusting the allowed year', () => {
|
||||||
|
// April 2, 2022 9:16 AM
|
||||||
|
const refParts = { month: 4, day: 2, year: 2022, hour: 9, minute: 16 };
|
||||||
|
// September 10, 2021 10:10 AM
|
||||||
|
const minParts = { month: 9, day: 10, year: 2021, hour: 10, minute: 10 };
|
||||||
|
// September 10, 2023 11:15 AM
|
||||||
|
const maxParts = { month: 9, day: 10, year: 2023, hour: 11, minute: 15 };
|
||||||
|
|
||||||
|
// April 2, 2022 9:16 AM
|
||||||
|
const expected = {
|
||||||
|
month: 4,
|
||||||
|
day: 2,
|
||||||
|
year: 2022,
|
||||||
|
hour: 9,
|
||||||
|
minute: 16,
|
||||||
|
ampm: 'am',
|
||||||
|
dayOfWeek: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getClosestValidDate({
|
||||||
|
refParts,
|
||||||
|
monthValues: [4, 9, 11],
|
||||||
|
dayValues: [2, 10, 12, 13, 14],
|
||||||
|
yearValues: [2020, 2021, 2022, 2023],
|
||||||
|
hourValues: [9, 10, 11],
|
||||||
|
minuteValues: [10, 15, 16],
|
||||||
|
minParts,
|
||||||
|
maxParts,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
|
import { Datetime } from '../../datetime';
|
||||||
|
|
||||||
|
describe('datetime: preferWheel', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockIntersectionObserver = jest.fn();
|
||||||
|
mockIntersectionObserver.mockReturnValue({
|
||||||
|
observe: () => null,
|
||||||
|
unobserve: () => null,
|
||||||
|
disconnect: () => null,
|
||||||
|
});
|
||||||
|
global.IntersectionObserver = mockIntersectionObserver;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select the working day when clicking the confirm button', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [Datetime],
|
||||||
|
html: '<ion-datetime prefer-wheel="true" max="2021" show-default-buttons="true"></ion-datetime>',
|
||||||
|
});
|
||||||
|
|
||||||
|
const datetime = page.body.querySelector<HTMLIonDatetimeElement>('ion-datetime')!;
|
||||||
|
const confirmButton = datetime.shadowRoot!.querySelector<HTMLIonButtonElement>('#confirm-button')!;
|
||||||
|
|
||||||
|
confirmButton.click();
|
||||||
|
|
||||||
|
await page.waitForChanges();
|
||||||
|
|
||||||
|
expect(datetime.value).toBe('2021-12-31T23:59:00');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { DatetimeParts } from '../datetime-interface';
|
import type { DatetimeParts } from '../datetime-interface';
|
||||||
|
|
||||||
import { isSameDay } from './comparison';
|
import { isAfter, isBefore, isSameDay } from './comparison';
|
||||||
import { getNumDaysInMonth } from './helpers';
|
import { getNumDaysInMonth } from './helpers';
|
||||||
import { clampDate, parseAmPm } from './parse';
|
import { clampDate, parseAmPm } from './parse';
|
||||||
|
|
||||||
@ -424,44 +424,137 @@ export const validateParts = (
|
|||||||
* Returns the closest date to refParts
|
* Returns the closest date to refParts
|
||||||
* that also meets the constraints of
|
* that also meets the constraints of
|
||||||
* the *Values params.
|
* the *Values params.
|
||||||
* @param refParts The reference date
|
*/
|
||||||
* @param monthValues The allowed month values
|
export const getClosestValidDate = ({
|
||||||
* @param dayValues The allowed day (of the month) values
|
refParts,
|
||||||
* @param yearValues The allowed year values
|
monthValues,
|
||||||
* @param hourValues The allowed hour values
|
dayValues,
|
||||||
* @param minuteValues The allowed minute values
|
yearValues,
|
||||||
*/
|
hourValues,
|
||||||
export const getClosestValidDate = (
|
minuteValues,
|
||||||
refParts: DatetimeParts,
|
minParts,
|
||||||
monthValues?: number[],
|
maxParts,
|
||||||
dayValues?: number[],
|
}: {
|
||||||
yearValues?: number[],
|
/**
|
||||||
hourValues?: number[],
|
* The reference date
|
||||||
minuteValues?: number[]
|
*/
|
||||||
) => {
|
refParts: DatetimeParts;
|
||||||
|
/**
|
||||||
|
* The allowed month values
|
||||||
|
*/
|
||||||
|
monthValues?: number[];
|
||||||
|
/**
|
||||||
|
* The allowed day (of the month) values
|
||||||
|
*/
|
||||||
|
dayValues?: number[];
|
||||||
|
/**
|
||||||
|
* The allowed year values
|
||||||
|
*/
|
||||||
|
yearValues?: number[];
|
||||||
|
/**
|
||||||
|
* The allowed hour values
|
||||||
|
*/
|
||||||
|
hourValues?: number[];
|
||||||
|
/**
|
||||||
|
* The allowed minute values
|
||||||
|
*/
|
||||||
|
minuteValues?: number[];
|
||||||
|
/**
|
||||||
|
* The minimum date that can be returned
|
||||||
|
*/
|
||||||
|
minParts?: DatetimeParts;
|
||||||
|
/**
|
||||||
|
* The maximum date that can be returned
|
||||||
|
*/
|
||||||
|
maxParts?: DatetimeParts;
|
||||||
|
}) => {
|
||||||
const { hour, minute, day, month, year } = refParts;
|
const { hour, minute, day, month, year } = refParts;
|
||||||
const copyParts = { ...refParts, dayOfWeek: undefined };
|
const copyParts = { ...refParts, dayOfWeek: undefined };
|
||||||
|
|
||||||
|
if (yearValues !== undefined) {
|
||||||
|
// Filters out years that are out of the min/max bounds
|
||||||
|
const filteredYears = yearValues.filter((year) => {
|
||||||
|
if (minParts !== undefined && year < minParts.year) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxParts !== undefined && year > maxParts.year) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
copyParts.year = findClosestValue(year, filteredYears);
|
||||||
|
}
|
||||||
|
|
||||||
if (monthValues !== undefined) {
|
if (monthValues !== undefined) {
|
||||||
copyParts.month = findClosestValue(month, monthValues);
|
// Filters out months that are out of the min/max bounds
|
||||||
|
const filteredMonths = monthValues.filter((month) => {
|
||||||
|
if (minParts !== undefined && copyParts.year === minParts.year && month < minParts.month) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxParts !== undefined && copyParts.year === maxParts.year && month > maxParts.month) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
copyParts.month = findClosestValue(month, filteredMonths);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Day is nullable but cannot be undefined
|
// Day is nullable but cannot be undefined
|
||||||
if (day !== null && dayValues !== undefined) {
|
if (day !== null && dayValues !== undefined) {
|
||||||
copyParts.day = findClosestValue(day, dayValues);
|
// Filters out days that are out of the min/max bounds
|
||||||
|
const filteredDays = dayValues.filter((day) => {
|
||||||
|
if (minParts !== undefined && isBefore({ ...copyParts, day }, minParts)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (maxParts !== undefined && isAfter({ ...copyParts, day }, maxParts)) {
|
||||||
if (yearValues !== undefined) {
|
return false;
|
||||||
copyParts.year = findClosestValue(year, yearValues);
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
copyParts.day = findClosestValue(day, filteredDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hour !== undefined && hourValues !== undefined) {
|
if (hour !== undefined && hourValues !== undefined) {
|
||||||
copyParts.hour = findClosestValue(hour, hourValues);
|
// Filters out hours that are out of the min/max bounds
|
||||||
|
const filteredHours = hourValues.filter((hour) => {
|
||||||
|
if (minParts?.hour !== undefined && isSameDay(copyParts, minParts) && hour < minParts.hour) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (maxParts?.hour !== undefined && isSameDay(copyParts, maxParts) && hour > maxParts.hour) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
copyParts.hour = findClosestValue(hour, filteredHours);
|
||||||
copyParts.ampm = parseAmPm(copyParts.hour);
|
copyParts.ampm = parseAmPm(copyParts.hour);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minute !== undefined && minuteValues !== undefined) {
|
if (minute !== undefined && minuteValues !== undefined) {
|
||||||
copyParts.minute = findClosestValue(minute, minuteValues);
|
// Filters out minutes that are out of the min/max bounds
|
||||||
|
const filteredMinutes = minuteValues.filter((minute) => {
|
||||||
|
if (
|
||||||
|
minParts?.minute !== undefined &&
|
||||||
|
isSameDay(copyParts, minParts) &&
|
||||||
|
copyParts.hour === minParts.hour &&
|
||||||
|
minute < minParts.minute
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
maxParts?.minute !== undefined &&
|
||||||
|
isSameDay(copyParts, maxParts) &&
|
||||||
|
copyParts.hour === maxParts.hour &&
|
||||||
|
minute > maxParts.minute
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
copyParts.minute = findClosestValue(minute, filteredMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return copyParts;
|
return copyParts;
|
||||||
|
|||||||
Reference in New Issue
Block a user