mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat: support start/end object format for date range
This commit is contained in:
@@ -408,6 +408,7 @@ ion-datetime,prop,multiple,boolean,false,false,false
|
||||
ion-datetime,prop,name,string,this.inputId,false,false
|
||||
ion-datetime,prop,preferWheel,boolean,false,false,false
|
||||
ion-datetime,prop,presentation,"date" | "date-time" | "month" | "month-year" | "time" | "time-date" | "year",'date-time',false,false
|
||||
ion-datetime,prop,range,boolean,false,false,false
|
||||
ion-datetime,prop,readonly,boolean,false,false,false
|
||||
ion-datetime,prop,showClearButton,boolean,false,false,false
|
||||
ion-datetime,prop,showDefaultButtons,boolean,false,false,false
|
||||
@@ -415,7 +416,7 @@ ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false
|
||||
ion-datetime,prop,showDefaultTitle,boolean,false,false,false
|
||||
ion-datetime,prop,size,"cover" | "fixed",'fixed',false,false
|
||||
ion-datetime,prop,titleSelectedDatesFormatter,((selectedDates: string[]) => string) | undefined,undefined,false,false
|
||||
ion-datetime,prop,value,null | string | string[] | undefined,undefined,false,false
|
||||
ion-datetime,prop,value,null | string | string[] | undefined | { start: string; end: string; },undefined,false,false
|
||||
ion-datetime,prop,yearValues,number | number[] | string | undefined,undefined,false,false
|
||||
ion-datetime,method,cancel,cancel(closeOverlay?: boolean) => Promise<void>
|
||||
ion-datetime,method,confirm,confirm(closeOverlay?: boolean) => Promise<void>
|
||||
|
||||
@@ -17,6 +17,11 @@ export interface DatetimeParts {
|
||||
ampm?: 'am' | 'pm';
|
||||
}
|
||||
|
||||
export interface DatetimeRangeParts {
|
||||
start: DatetimeParts;
|
||||
end: DatetimeParts;
|
||||
}
|
||||
|
||||
export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year';
|
||||
|
||||
export type TitleSelectedDatesFormatter = (selectedDates: string[]) => string;
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
DatetimeHighlightCallback,
|
||||
DatetimeHourCycle,
|
||||
DatetimeValue,
|
||||
DatetimeRangeParts,
|
||||
} from './datetime-interface';
|
||||
import { isSameDay, warnIfValueOutOfBounds, isBefore, isAfter } from './utils/comparison';
|
||||
import type { WheelColumnOption } from './utils/data';
|
||||
@@ -131,7 +132,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
@State() showMonthAndYear = false;
|
||||
|
||||
@State() activeParts: DatetimeParts | DatetimeParts[] = [];
|
||||
@State() activeParts: DatetimeParts | DatetimeParts[] | DatetimeRangeParts = [];
|
||||
|
||||
@State() workingParts: DatetimeParts = {
|
||||
month: 5,
|
||||
@@ -642,30 +643,46 @@ export class Datetime implements ComponentInterface {
|
||||
this.setWorkingParts(validatedParts);
|
||||
|
||||
if (multiple) {
|
||||
const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
|
||||
const activePartsArray = Array.isArray(activeParts) ? activeParts : ([activeParts] as DatetimeParts[]);
|
||||
if (removeDate) {
|
||||
this.activeParts = activePartsArray.filter((p) => !isSameDay(p, validatedParts));
|
||||
} else {
|
||||
this.activeParts = [...activePartsArray, validatedParts];
|
||||
}
|
||||
} else if (range) {
|
||||
if (!Array.isArray(this.activeParts)) {
|
||||
// If the activeParts is not an array, that means they have not made a selection yet
|
||||
// and that the current activeParts is just the default value.
|
||||
// We can set the start and end range to the same value.
|
||||
this.activeParts = [validatedParts, validatedParts];
|
||||
} else {
|
||||
// If the active parts is an array, then we need to determine which part of the range
|
||||
// we are setting. We can do this by comparing if the validatedParts is before or after
|
||||
// the first active part. Users can select the same day as the start or end of the range.
|
||||
if (Array.isArray(this.activeParts)) {
|
||||
/**
|
||||
* If the active parts is an array, then we need to determine which part of the range
|
||||
* we are setting. We can do this by comparing if the validatedParts is before or after
|
||||
* the first active part. Users can select the same day as the start or end of the range.
|
||||
*/
|
||||
const [start, end] = this.activeParts;
|
||||
if (start !== undefined && isBefore(validatedParts, start)) {
|
||||
this.activeParts = [validatedParts, end];
|
||||
this.activeParts = {
|
||||
start: validatedParts,
|
||||
end,
|
||||
};
|
||||
} else if (end !== undefined && isAfter(validatedParts, end)) {
|
||||
this.activeParts = [start, validatedParts];
|
||||
this.activeParts = {
|
||||
start,
|
||||
end: validatedParts,
|
||||
};
|
||||
} else {
|
||||
this.activeParts = [validatedParts, validatedParts];
|
||||
this.activeParts = {
|
||||
start: validatedParts,
|
||||
end: validatedParts,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* If the activeParts is not an array, that means they have not made a selection yet
|
||||
* and that the current activeParts is just the default value.
|
||||
* We can set the start and end range to the same value.
|
||||
*/
|
||||
this.activeParts = {
|
||||
start: validatedParts,
|
||||
end: validatedParts,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.activeParts = {
|
||||
@@ -1600,7 +1617,7 @@ export class Datetime implements ComponentInterface {
|
||||
private renderCombinedDatePickerColumn() {
|
||||
const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
/**
|
||||
* By default, generate a range of 3 months:
|
||||
@@ -1801,7 +1818,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
const pickerColumnValue = (workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined;
|
||||
|
||||
return (
|
||||
@@ -1857,7 +1874,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
@@ -1911,7 +1928,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
@@ -1996,7 +2013,7 @@ export class Datetime implements ComponentInterface {
|
||||
const { disabled, workingParts } = this;
|
||||
if (hoursData.length === 0) return [];
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
@@ -2035,7 +2052,7 @@ export class Datetime implements ComponentInterface {
|
||||
const { disabled, workingParts } = this;
|
||||
if (minutesData.length === 0) return [];
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
@@ -2076,7 +2093,7 @@ export class Datetime implements ComponentInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
|
||||
|
||||
return (
|
||||
@@ -2315,9 +2332,9 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
let dateParts = undefined;
|
||||
|
||||
const inRange = range && isDateInRange(referenceParts, activeParts);
|
||||
const isRangeStart = range && isDateRangeStart(referenceParts, activeParts);
|
||||
const isRangeEnd = range && isDateRangeEnd(referenceParts, activeParts);
|
||||
const inRange = range && isDateInRange(referenceParts, activeParts as DatetimeRangeParts);
|
||||
const isRangeStart = range && isDateRangeStart(referenceParts, activeParts as DatetimeRangeParts);
|
||||
const isRangeEnd = range && isDateRangeEnd(referenceParts, activeParts as DatetimeRangeParts);
|
||||
|
||||
// "Filler days" at the beginning of the grid should not get the calendar day
|
||||
// CSS parts added to them
|
||||
@@ -2444,7 +2461,7 @@ export class Datetime implements ComponentInterface {
|
||||
private renderTimeOverlay() {
|
||||
const { disabled, hourCycle, isTimePopoverOpen, locale } = this;
|
||||
const computedHourCycle = getHourCycle(locale, hourCycle);
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const activePart = this.getActivePartsWithFallback() as DatetimeParts;
|
||||
|
||||
return [
|
||||
<div class="time-header">{this.renderTimeLabel()}</div>,
|
||||
@@ -2527,7 +2544,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
} else {
|
||||
// for exactly 1 day selected (multiple set or not), show a formatted version of that
|
||||
headerText = getMonthAndDay(this.locale, this.getActivePartsWithFallback());
|
||||
headerText = getMonthAndDay(this.locale, this.getActivePartsWithFallback() as DatetimeParts); // TODO verify if this can show with range enabled
|
||||
}
|
||||
|
||||
return headerText;
|
||||
|
||||
@@ -267,6 +267,11 @@
|
||||
datetime.addEventListener('ionChange', (event) => {
|
||||
console.log('Listen ionChange', event.detail);
|
||||
});
|
||||
|
||||
datetime.value = {
|
||||
start: '2024-01-05',
|
||||
end: '2024-02-10',
|
||||
};
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DatetimeParts, DatetimeHourCycle, DatetimeValue } from '../datetime-interface';
|
||||
import type { DatetimeParts, DatetimeHourCycle, DatetimeValue, DatetimeRangeParts } from '../datetime-interface';
|
||||
|
||||
import { is24Hour } from './helpers';
|
||||
import { convertDataToISO } from './manipulation';
|
||||
@@ -353,3 +353,19 @@ export const formatValue = (value?: DatetimeValue) => {
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an incoming active parts object and formats it into an array
|
||||
* of active parts.
|
||||
* @param activeParts The active parts object to format.
|
||||
* @returns Active parts as an array.
|
||||
*/
|
||||
export const activePartsToArray = (activeParts: DatetimeParts | DatetimeRangeParts | DatetimeParts[]) => {
|
||||
if (Array.isArray(activeParts)) {
|
||||
return activeParts;
|
||||
}
|
||||
if ('start' in activeParts) {
|
||||
return [activeParts.start, activeParts.end];
|
||||
}
|
||||
return [activeParts];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
import type { DatetimeMultipleValue, DatetimeParts, DatetimeRangeParts, DatetimeRangeValue } from '../datetime-interface';
|
||||
|
||||
import { isAfter, isBefore, isSameDay } from './comparison';
|
||||
import { getNumDaysInMonth } from './helpers';
|
||||
@@ -13,13 +13,22 @@ const fourDigit = (val: number | undefined): string => {
|
||||
};
|
||||
|
||||
export function convertDataToISO(data: DatetimeParts): string;
|
||||
export function convertDataToISO(data: DatetimeParts[]): string[];
|
||||
export function convertDataToISO(data: DatetimeParts | DatetimeParts[]): string | string[];
|
||||
export function convertDataToISO(data: DatetimeParts | DatetimeParts[]): string | string[] {
|
||||
export function convertDataToISO(data: DatetimeRangeParts): DatetimeRangeValue;
|
||||
export function convertDataToISO(data: DatetimeParts[]): DatetimeMultipleValue;
|
||||
export function convertDataToISO(data: DatetimeParts | DatetimeRangeParts | DatetimeParts[]): string | DatetimeMultipleValue;
|
||||
export function convertDataToISO(data: DatetimeParts | DatetimeRangeParts | DatetimeParts[]): string | DatetimeMultipleValue | DatetimeRangeValue {
|
||||
// Multiple selection
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((parts) => convertDataToISO(parts));
|
||||
}
|
||||
|
||||
// Range selection
|
||||
if ('start' in data) {
|
||||
return {
|
||||
start: convertDataToISO(data.start),
|
||||
end: convertDataToISO(data.end),
|
||||
};
|
||||
}
|
||||
// Single selection
|
||||
// https://www.w3.org/TR/NOTE-datetime
|
||||
let rtn = '';
|
||||
if (data.year !== undefined) {
|
||||
|
||||
@@ -5,10 +5,11 @@ import type {
|
||||
DatetimeHighlightCallback,
|
||||
DatetimeHighlightStyle,
|
||||
DatetimeParts,
|
||||
DatetimeRangeParts,
|
||||
} from '../datetime-interface';
|
||||
|
||||
import { isAfter, isBefore, isSameDay } from './comparison';
|
||||
import { generateDayAriaLabel, getDay } from './format';
|
||||
import { activePartsToArray, generateDayAriaLabel, getDay } from './format';
|
||||
import { getNextMonth, getPreviousMonth } from './manipulation';
|
||||
|
||||
export const isYearDisabled = (refYear: number, minParts?: DatetimeParts, maxParts?: DatetimeParts) => {
|
||||
@@ -96,26 +97,17 @@ export const isDayDisabled = (
|
||||
export const getCalendarDayState = (
|
||||
locale: string,
|
||||
refParts: DatetimeParts,
|
||||
activeParts: DatetimeParts | DatetimeParts[],
|
||||
activeParts: DatetimeParts | DatetimeRangeParts | DatetimeParts[],
|
||||
todayParts: DatetimeParts,
|
||||
minParts?: DatetimeParts,
|
||||
maxParts?: DatetimeParts,
|
||||
dayValues?: number[]
|
||||
) => {
|
||||
/**
|
||||
* activeParts signals what day(s) are currently selected in the datetime.
|
||||
* If multiple="true", this will be an array, but the logic in this util
|
||||
* is the same whether we have one selected day or many because we're only
|
||||
* calculating the state for one button. So, we treat a single activeParts value
|
||||
* the same as an array of length one.
|
||||
*/
|
||||
const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
|
||||
|
||||
/**
|
||||
* The day button is active if it is selected, or in other words, if refParts
|
||||
* matches at least one selected date.
|
||||
*/
|
||||
const isActive = activePartsArray.find((parts) => isSameDay(refParts, parts)) !== undefined;
|
||||
const isActive = activePartsToArray(activeParts).find((parts) => isSameDay(refParts, parts)) !== undefined;
|
||||
|
||||
const isToday = isSameDay(refParts, todayParts);
|
||||
const disabled = isDayDisabled(refParts, minParts, maxParts, dayValues);
|
||||
@@ -228,7 +220,7 @@ export const getHighlightStyles = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const isDateRangeStart = (referenceParts: DatetimeParts, activeParts: DatetimeParts | DatetimeParts[]) => {
|
||||
export const isDateRangeStart = (referenceParts: DatetimeParts, activeParts: DatetimeRangeParts) => {
|
||||
if (activeParts !== undefined && Array.isArray(activeParts)) {
|
||||
const startDate = activeParts[0];
|
||||
return startDate !== undefined && isSameDay(referenceParts, startDate);
|
||||
@@ -236,7 +228,7 @@ export const isDateRangeStart = (referenceParts: DatetimeParts, activeParts: Dat
|
||||
return false;
|
||||
}
|
||||
|
||||
export const isDateRangeEnd = (referenceParts: DatetimeParts, activeParts: DatetimeParts | DatetimeParts[]) => {
|
||||
export const isDateRangeEnd = (referenceParts: DatetimeParts, activeParts: DatetimeRangeParts) => {
|
||||
if (activeParts !== undefined && Array.isArray(activeParts)) {
|
||||
const endDate = activeParts[1];
|
||||
return endDate !== undefined && isSameDay(referenceParts, endDate);
|
||||
@@ -244,7 +236,7 @@ export const isDateRangeEnd = (referenceParts: DatetimeParts, activeParts: Datet
|
||||
return false;
|
||||
}
|
||||
|
||||
export const isDateInRange = (referenceParts: DatetimeParts, activeParts: DatetimeParts | DatetimeParts[]) => {
|
||||
export const isDateInRange = (referenceParts: DatetimeParts, activeParts: DatetimeRangeParts) => {
|
||||
if (activeParts !== undefined && Array.isArray(activeParts)) {
|
||||
const startDate = activeParts[0];
|
||||
const endDate = activeParts[1];
|
||||
|
||||
@@ -635,7 +635,7 @@ Set `scrollEvents` to `true` to enable.
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'range', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||
methods: ['confirm', 'reset', 'cancel']
|
||||
})
|
||||
@Component({
|
||||
@@ -643,7 +643,7 @@ Set `scrollEvents` to `true` to enable.
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'range', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||
})
|
||||
export class IonDatetime {
|
||||
protected el: HTMLElement;
|
||||
|
||||
@@ -294,6 +294,7 @@ export const IonDatetime = /*@__PURE__*/ defineContainer<JSX.IonDatetime, JSX.Io
|
||||
'firstDayOfWeek',
|
||||
'titleSelectedDatesFormatter',
|
||||
'multiple',
|
||||
'range',
|
||||
'highlightedDates',
|
||||
'value',
|
||||
'showDefaultTitle',
|
||||
|
||||
Reference in New Issue
Block a user