Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
478544c
feat(datetime): add DatetimeRangeParts interface for range selection
OS-susmitabhowmik Oct 14, 2026
a507212
feat(datetime): add selectionMode prop and deprecate multiple
OS-susmitabhowmik Oct 14, 2026
1ab6897
feat(datetime): add range flags to getCalendarDayState
OS-susmitabhowmik Oct 14, 2026
bb61d21
feat(datetime): implement range selection logic
OS-susmitabhowmik Oct 14, 2026
15aa4ae
feat(datetime): update calendar rendering for range selection
OS-susmitabhowmik Oct 14, 2026
cca6ee5
feat(datetime): add range highlight track styles
OS-susmitabhowmik Oct 14, 2026
f03eb37
feat(datetime): update header and datetime-button for range display
OS-susmitabhowmik Oct 14, 2026
26b65a7
feat(datetime): disable confirm button until range is complete
OS-susmitabhowmik Oct 14, 2026
8eb4d4e
test(datetime): add range and range-styling test pages
OS-susmitabhowmik Oct 14, 2026
4d7abfc
fix(datetime): align range track styles with Figma design
OS-susmitabhowmik Oct 14, 2026
9385623
fix(datetime): fix range style issues found during Figma validation
OS-susmitabhowmik Oct 14, 2026
8236f7d
chore(datetime): update generated files for range selection API
OS-susmitabhowmik Oct 16, 2026
581d280
chore(): add updated snapshots
Ionitron Oct 16, 2026
9d70a28
Merge branch 'next' into ROU-12704-datepicker-range
OS-susmitabhowmik Oct 16, 2026
5353064
fix(datetime): fix lint error and update state spec for range fields
OS-susmitabhowmik Oct 16, 2026
405bac7
fix(toolbar): restore baseline snapshot from next branch
OS-susmitabhowmik Oct 16, 2026
e90b03c
Revert "fix(toolbar): restore baseline snapshot from next branch"
OS-susmitabhowmik Oct 16, 2026
1b0efef
fix(datetime): fix SCSS lint property order and disallowed-list errors
OS-susmitabhowmik Oct 16, 2026
e07c496
chore(): add updated snapshots
Ionitron Oct 16, 2026
2a7744b
fix(datetime): fix remaining SCSS lint order errors
OS-susmitabhowmik Oct 16, 2026
47aa109
fix(datetime): apply prettier formatting to range files
OS-susmitabhowmik Oct 16, 2026
2c61014
fix(datetime): use pill shapes for range on all themes and fix disabl…
OS-susmitabhowmik Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ ion-datetime,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "se
ion-datetime,prop,dayValues,number | number[] | string | undefined,undefined,false,false
ion-datetime,prop,disabled,boolean,false,false,false
ion-datetime,prop,doneText,string,'Done',false,false
ion-datetime,prop,endDateLabel,string,'End date',false,false
ion-datetime,prop,firstDayOfWeek,number,0,false,false
ion-datetime,prop,formatOptions,undefined | { date: DateTimeFormatOptions; time?: DateTimeFormatOptions | undefined; } | { date?: DateTimeFormatOptions | undefined; time: DateTimeFormatOptions; },undefined,false,false
ion-datetime,prop,highlightedDates,((dateIsoString: string) => DatetimeHighlightStyle | undefined) | DatetimeHighlight[] | undefined,undefined,false,false
Expand All @@ -711,12 +712,14 @@ 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,readonly,boolean,false,false,false
ion-datetime,prop,selectionMode,"multiple" | "range" | undefined,undefined,false,false
ion-datetime,prop,showAdjacentDays,boolean,false,false,false
ion-datetime,prop,showClearButton,boolean,false,false,false
ion-datetime,prop,showDefaultButtons,boolean,false,false,false
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,startDateLabel,string,'Start date',false,false
ion-datetime,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-datetime,prop,titleSelectedDatesFormatter,((selectedDates: string[]) => string) | undefined,undefined,false,false
ion-datetime,prop,value,null | string | string[] | undefined,undefined,false,false
Expand All @@ -735,6 +738,9 @@ ion-datetime,css-prop,--background-rgb,ios
ion-datetime,css-prop,--background-rgb,md
ion-datetime,css-prop,--focus-ring-color,ionic
ion-datetime,css-prop,--focus-ring-width,ionic
ion-datetime,css-prop,--range-background,ionic
ion-datetime,css-prop,--range-background,ios
ion-datetime,css-prop,--range-background,md
ion-datetime,css-prop,--title-color,ios
ion-datetime,css-prop,--title-color,md
ion-datetime,css-prop,--wheel-fade-background-rgb,ios
Expand All @@ -756,6 +762,10 @@ ion-datetime,part,month-year-button
ion-datetime,part,navigation-button
ion-datetime,part,next-button
ion-datetime,part,previous-button
ion-datetime,part,range-end
ion-datetime,part,range-selection
ion-datetime,part,range-start
ion-datetime,part,range-track
ion-datetime,part,time-button
ion-datetime,part,time-button active
ion-datetime,part,wheel
Expand Down
33 changes: 33 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,11 @@ export namespace Components {
* @default 'Done'
*/
"doneText": string;
/**
* The text shown in the datetime header when the end of the range is empty, also used by screen readers to announce the end date. Only applies when `selectionMode="range"`.
* @default 'End date'
*/
"endDateLabel": string;
/**
* The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday.
* @default 0
Expand Down Expand Up @@ -1218,6 +1223,7 @@ export namespace Components {
"monthValues"?: number[] | number | string;
/**
* If `true`, multiple dates can be selected at once. Only applies to `presentation="date"` and `preferWheel="false"`.
* @deprecated Use `selectionMode="multiple"` instead.
* @default false
*/
"multiple": boolean;
Expand Down Expand Up @@ -1246,6 +1252,10 @@ export namespace Components {
* @param startDate A valid [ISO-8601 string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format) to reset the datetime state to.
*/
"reset": (startDate?: string) => Promise<void>;
/**
* Controls the date selection behavior. `'multiple'` enables selecting multiple individual dates. `'range'` enables selecting a start and end date range. When unset, single-date selection is used. Range selection only works with `presentation="date"` and `preferWheel="false"`.
*/
"selectionMode"?: 'multiple' | 'range';
/**
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
* @default false
Expand Down Expand Up @@ -1276,6 +1286,11 @@ export namespace Components {
* @default 'fixed'
*/
"size": 'cover' | 'fixed';
/**
* The text shown in the datetime header when the start of the range is empty, also used by screen readers to announce the start date. Only applies when `selectionMode="range"`.
* @default 'Start date'
*/
"startDateLabel": string;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -7070,6 +7085,11 @@ declare namespace LocalJSX {
* @default 'Done'
*/
"doneText"?: string;
/**
* The text shown in the datetime header when the end of the range is empty, also used by screen readers to announce the end date. Only applies when `selectionMode="range"`.
* @default 'End date'
*/
"endDateLabel"?: string;
/**
* The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday.
* @default 0
Expand Down Expand Up @@ -7122,6 +7142,7 @@ declare namespace LocalJSX {
"monthValues"?: number[] | number | string;
/**
* If `true`, multiple dates can be selected at once. Only applies to `presentation="date"` and `preferWheel="false"`.
* @deprecated Use `selectionMode="multiple"` instead.
* @default false
*/
"multiple"?: boolean;
Expand Down Expand Up @@ -7173,6 +7194,10 @@ declare namespace LocalJSX {
* @default false
*/
"readonly"?: boolean;
/**
* Controls the date selection behavior. `'multiple'` enables selecting multiple individual dates. `'range'` enables selecting a start and end date range. When unset, single-date selection is used. Range selection only works with `presentation="date"` and `preferWheel="false"`.
*/
"selectionMode"?: 'multiple' | 'range';
/**
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
* @default false
Expand Down Expand Up @@ -7203,6 +7228,11 @@ declare namespace LocalJSX {
* @default 'fixed'
*/
"size"?: 'cover' | 'fixed';
/**
* The text shown in the datetime header when the start of the range is empty, also used by screen readers to announce the start date. Only applies when `selectionMode="range"`.
* @default 'Start date'
*/
"startDateLabel"?: string;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -10721,6 +10751,9 @@ declare namespace LocalJSX {
"locale": string;
"firstDayOfWeek": number;
"multiple": boolean;
"selectionMode": 'multiple' | 'range';
"startDateLabel": string;
"endDateLabel": string;
"value": string | string[] | null;
"showDefaultTitle": boolean;
"showDefaultButtons": boolean;
Expand Down
46 changes: 34 additions & 12 deletions core/src/components/datetime-button/datetime-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,16 @@ export class DatetimeButton implements ComponentInterface {
return;
}

const { value, locale, formatOptions, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl;
const {
value,
locale,
formatOptions,
hourCycle,
preferWheel,
multiple,
selectionMode,
titleSelectedDatesFormatter,
} = datetimeEl;

const parsedValues = this.getParsedDateValues(value);

Expand All @@ -213,6 +222,9 @@ export class DatetimeButton implements ComponentInterface {
return;
}

const isRangeMode = selectionMode === 'range';
const isMultipleMode = selectionMode === 'multiple' || (selectionMode === undefined && multiple);

/**
* If developers incorrectly use multiple="true"
* with non "date" datetimes, then just select
Expand All @@ -222,17 +234,14 @@ export class DatetimeButton implements ComponentInterface {
*/
const firstParsedDatetime = parsedDatetimes[0];
const computedHourCycle = getHourCycle(locale, hourCycle);
const dateFormatOptions = formatOptions?.date ?? { month: 'short', day: 'numeric', year: 'numeric' };

this.dateText = this.timeText = undefined;

switch (datetimePresentation) {
case 'date-time':
case 'time-date':
const dateText = getLocalizedDateTime(
locale,
firstParsedDatetime,
formatOptions?.date ?? { month: 'short', day: 'numeric', year: 'numeric' }
);
const dateText = getLocalizedDateTime(locale, firstParsedDatetime, dateFormatOptions);
const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions?.time);
if (preferWheel) {
this.dateText = `${dateText} ${timeText}`;
Expand All @@ -242,7 +251,24 @@ export class DatetimeButton implements ComponentInterface {
}
break;
case 'date':
if (multiple && parsedValues.length !== 1) {
if (isRangeMode && parsedValues.length === 2) {
// Range mode: display "startDate - endDate"
const startDate = getLocalizedDateTime(locale, parsedDatetimes[0], dateFormatOptions);
const endDate = getLocalizedDateTime(
locale,
parsedDatetimes.length > 1 ? parsedDatetimes[1] : parsedDatetimes[0],
dateFormatOptions
);
let rangeText = `${startDate} - ${endDate}`;
if (titleSelectedDatesFormatter !== undefined) {
try {
rangeText = titleSelectedDatesFormatter(parsedValues);
} catch (e) {
printIonError('[ion-datetime-button] - Exception in provided `titleSelectedDatesFormatter`:', e);
}
}
this.dateText = rangeText;
} else if (isMultipleMode && parsedValues.length !== 1) {
let headerText = `${parsedValues.length} days`; // default/fallback for multiple selection
if (titleSelectedDatesFormatter !== undefined) {
try {
Expand All @@ -253,11 +279,7 @@ export class DatetimeButton implements ComponentInterface {
}
this.dateText = headerText;
} else {
this.dateText = getLocalizedDateTime(
locale,
firstParsedDatetime,
formatOptions?.date ?? { month: 'short', day: 'numeric', year: 'numeric' }
);
this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, dateFormatOptions);
}
break;
case 'time':
Expand Down
5 changes: 5 additions & 0 deletions core/src/components/datetime/datetime-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export type DatetimeHighlight = { date: string } & DatetimeHighlightStyle;

export type DatetimeHighlightCallback = (dateIsoString: string) => DatetimeHighlightStyle | undefined;

export interface DatetimeRangeParts {
start: DatetimeParts;
end?: DatetimeParts;
}

export type DatetimeHourCycle = 'h11' | 'h12' | 'h23' | 'h24';

/**
Expand Down
83 changes: 83 additions & 0 deletions core/src/components/datetime/datetime.common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@
:host .calendar-day-wrapper {
display: flex;

position: relative;

align-items: center;
justify-content: center;

Expand All @@ -285,6 +287,87 @@
min-height: 0;

overflow: visible;

/**
* Create a stacking context so the range track
* (::before with z-index: -1) stays behind the
* day button but above the host background.
*/
z-index: 0;
}

// Range Selection Track
// -----------------------------------

/**
* The ::before pseudo-element on the calendar-day-wrapper
* draws the colored background track that connects the
* range start to the range end. The track is vertically
* centered and sized to match the day button height.
*
* @prop --range-background: The background color of the range selection track.
*/
:host .calendar-day-wrapper-range-start::before,
:host .calendar-day-wrapper-in-range::before,
:host .calendar-day-wrapper-range-end::before {
position: absolute;

/**
* Vertically center the track and size it to
* match the calendar day button height.
*/
top: 50%;

height: var(--range-track-height);

transform: translateY(-50%);

background: var(--range-background);

content: "";

z-index: -1;
}

:host .calendar-day-wrapper-range-start::before {
inset-inline-start: 50%;
inset-inline-end: 0;
}

:host .calendar-day-wrapper-in-range::before {
inset-inline-start: 0;
inset-inline-end: 0;
}

:host .calendar-day-wrapper-range-end::before {
inset-inline-start: 0;
inset-inline-end: 50%;
}

/**
* When a single day is both the start and end of the range,
* do not show a track since there is nothing to connect.
*/
:host .calendar-day-wrapper-range-start.calendar-day-wrapper-range-end::before {
display: none;
}

/**
* When the component is disabled, move the opacity to the wrapper
* so both the track and day button fade equally. Without this,
* the day button becomes semi-transparent (via native disabled opacity)
* while the track stays opaque, causing a visible overlap.
*/
:host(.datetime-disabled) .calendar-day-wrapper-range-start,
:host(.datetime-disabled) .calendar-day-wrapper-in-range,
:host(.datetime-disabled) .calendar-day-wrapper-range-end {
opacity: 0.4;
}

:host(.datetime-disabled) .calendar-day-wrapper-range-start .calendar-day[disabled],
:host(.datetime-disabled) .calendar-day-wrapper-in-range .calendar-day[disabled],
:host(.datetime-disabled) .calendar-day-wrapper-range-end .calendar-day[disabled] {
opacity: 1;
}

/**
Expand Down
Loading
Loading