@ionic/core 8.8.20-dev.11791573097.1f843ab0 → 8.8.20-dev.11792194859.16be5d7f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ion-datetime-button.js +1 -1
- package/components/ion-datetime.js +1 -1
- package/components/{p-BE21dB_a.js → p-Bpr3edV8.js} +1 -1
- package/dist/cjs/ion-datetime-button.cjs.entry.js +28 -10
- package/dist/cjs/ion-datetime_3.cjs.entry.js +354 -30
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/datetime/datetime.ionic.css +144 -1
- package/dist/collection/components/datetime/datetime.ios.css +118 -0
- package/dist/collection/components/datetime/datetime.js +407 -27
- package/dist/collection/components/datetime/datetime.md.css +117 -0
- package/dist/collection/components/datetime/utils/state.js +18 -1
- package/dist/collection/components/datetime-button/datetime-button.js +28 -10
- package/dist/docs.json +151 -1
- package/dist/esm/{format-2WlZcMvG.js → format-BNGmd1mx.js} +1 -1
- package/dist/esm/ion-datetime-button.entry.js +29 -11
- package/dist/esm/ion-datetime_3.entry.js +355 -31
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/html.html-data.json +20 -0
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-4aa6153d.entry.js +4 -0
- package/dist/ionic/p-4f2e482f.entry.js +4 -0
- package/dist/ionic/{p-7k7Q1A38.js → p-CrgdPEJR.js} +1 -1
- package/dist/types/components/datetime/datetime-interface.d.ts +4 -0
- package/dist/types/components/datetime/datetime.d.ts +66 -2
- package/dist/types/components/datetime/utils/state.d.ts +5 -2
- package/dist/types/components.d.ts +33 -0
- package/hydrate/index.js +390 -40
- package/hydrate/index.mjs +390 -40
- package/package.json +1 -1
- package/dist/ionic/p-3a4f9b8a.entry.js +0 -4
- package/dist/ionic/p-f6768728.entry.js +0 -4
|
@@ -52,6 +52,11 @@ import { checkForPresentationFormatMismatch, warnIfTimeZoneProvided } from "./ut
|
|
|
52
52
|
* @part calendar-day today - The calendar day that contains the current day.
|
|
53
53
|
* @part calendar-day disabled - The calendar day that is disabled.
|
|
54
54
|
*
|
|
55
|
+
* @part range-start - The calendar day button that is the start of a range selection.
|
|
56
|
+
* @part range-end - The calendar day button that is the end of a range selection.
|
|
57
|
+
* @part range-selection - The calendar day button that is part of a range selection (includes start and end).
|
|
58
|
+
* @part range-track - The calendar day wrapper element whose ::before pseudo-element draws the range highlight track.
|
|
59
|
+
*
|
|
55
60
|
* @part datetime-header - The datetime header contains the content for the `title` slot and the selected date.
|
|
56
61
|
* @part datetime-title - The element that contains the `title` slot content.
|
|
57
62
|
* @part datetime-selected-date - The element that contains the selected date.
|
|
@@ -141,8 +146,22 @@ export class Datetime {
|
|
|
141
146
|
/**
|
|
142
147
|
* If `true`, multiple dates can be selected at once. Only
|
|
143
148
|
* applies to `presentation="date"` and `preferWheel="false"`.
|
|
149
|
+
*
|
|
150
|
+
* @deprecated Use `selectionMode="multiple"` instead.
|
|
144
151
|
*/
|
|
145
152
|
this.multiple = false;
|
|
153
|
+
/**
|
|
154
|
+
* The text shown in the datetime header when the start of the range
|
|
155
|
+
* is empty, also used by screen readers to announce the start date.
|
|
156
|
+
* Only applies when `selectionMode="range"`.
|
|
157
|
+
*/
|
|
158
|
+
this.startDateLabel = 'Start date';
|
|
159
|
+
/**
|
|
160
|
+
* The text shown in the datetime header when the end of the range
|
|
161
|
+
* is empty, also used by screen readers to announce the end date.
|
|
162
|
+
* Only applies when `selectionMode="range"`.
|
|
163
|
+
*/
|
|
164
|
+
this.endDateLabel = 'End date';
|
|
146
165
|
/**
|
|
147
166
|
* If `true`, a header will be shown above the calendar
|
|
148
167
|
* picker. This will include both the slotted title, and
|
|
@@ -195,8 +214,8 @@ export class Datetime {
|
|
|
195
214
|
*/
|
|
196
215
|
this.preferWheel = false;
|
|
197
216
|
this.warnIfIncorrectValueUsage = () => {
|
|
198
|
-
const {
|
|
199
|
-
if (!
|
|
217
|
+
const { value } = this;
|
|
218
|
+
if (!this.isMultipleMode && !this.isRangeMode && Array.isArray(value)) {
|
|
200
219
|
/**
|
|
201
220
|
* We do some processing on the `value` array so
|
|
202
221
|
* that it looks more like an array when logged to
|
|
@@ -205,7 +224,13 @@ export class Datetime {
|
|
|
205
224
|
* Default toString() behavior: a,b
|
|
206
225
|
* Custom behavior: ['a', 'b']
|
|
207
226
|
*/
|
|
208
|
-
printIonWarning(`[ion-datetime] - An array of values was passed, but multiple
|
|
227
|
+
printIonWarning(`[ion-datetime] - An array of values was passed, but neither selectionMode="multiple" nor selectionMode="range" is set. This is incorrect usage and may result in unexpected behaviors.
|
|
228
|
+
|
|
229
|
+
Value Passed: [${value.map((v) => `'${v}'`).join(', ')}]
|
|
230
|
+
`, this.el);
|
|
231
|
+
}
|
|
232
|
+
if (this.isRangeMode && Array.isArray(value) && value.length > 2) {
|
|
233
|
+
printIonWarning(`[ion-datetime] - More than 2 values were passed with selectionMode="range". Only the first and last values will be used.
|
|
209
234
|
|
|
210
235
|
Value Passed: [${value.map((v) => `'${v}'`).join(', ')}]
|
|
211
236
|
`, this.el);
|
|
@@ -232,6 +257,9 @@ export class Datetime {
|
|
|
232
257
|
};
|
|
233
258
|
this.getActivePart = () => {
|
|
234
259
|
const { activeParts } = this;
|
|
260
|
+
if (this.isRangeParts(activeParts)) {
|
|
261
|
+
return activeParts.start;
|
|
262
|
+
}
|
|
235
263
|
return Array.isArray(activeParts) ? activeParts[0] : activeParts;
|
|
236
264
|
};
|
|
237
265
|
this.closeParentOverlay = (role) => {
|
|
@@ -243,6 +271,33 @@ export class Datetime {
|
|
|
243
271
|
this.setWorkingParts = (parts) => {
|
|
244
272
|
this.workingParts = Object.assign({}, parts);
|
|
245
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Checks whether the span from start to end contains any disabled day.
|
|
276
|
+
* Used by range mode to prevent selecting across disabled dates.
|
|
277
|
+
*/
|
|
278
|
+
this.rangeContainsDisabledDate = (start, end) => {
|
|
279
|
+
const { minParts, maxParts, parsedDayValues, isDateEnabled } = this;
|
|
280
|
+
let cursor = getNextDay(start);
|
|
281
|
+
while (isBefore(cursor, end) || isSameDay(cursor, end)) {
|
|
282
|
+
if (isSameDay(cursor, end))
|
|
283
|
+
break;
|
|
284
|
+
if (isDayDisabled(cursor, minParts, maxParts, parsedDayValues)) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (isDateEnabled) {
|
|
288
|
+
try {
|
|
289
|
+
if (!isDateEnabled(convertDataToISO(cursor))) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (_a) {
|
|
294
|
+
// Swallow user callback exceptions during validation
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
cursor = getNextDay(cursor);
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
};
|
|
246
301
|
this.setActiveParts = (parts, removeDate = false) => {
|
|
247
302
|
/** if the datetime component is in readonly mode,
|
|
248
303
|
* allow browsing of the calendar without changing
|
|
@@ -251,7 +306,7 @@ export class Datetime {
|
|
|
251
306
|
if (this.readonly) {
|
|
252
307
|
return;
|
|
253
308
|
}
|
|
254
|
-
const {
|
|
309
|
+
const { minParts, maxParts, activeParts } = this;
|
|
255
310
|
/**
|
|
256
311
|
* When setting the active parts, it is possible
|
|
257
312
|
* to set invalid data. For example,
|
|
@@ -264,7 +319,10 @@ export class Datetime {
|
|
|
264
319
|
*/
|
|
265
320
|
const validatedParts = validateParts(parts, minParts, maxParts);
|
|
266
321
|
this.setWorkingParts(validatedParts);
|
|
267
|
-
if (
|
|
322
|
+
if (this.isRangeMode) {
|
|
323
|
+
this.setActivePartsRange(validatedParts);
|
|
324
|
+
}
|
|
325
|
+
else if (this.isMultipleMode) {
|
|
268
326
|
const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
|
|
269
327
|
if (removeDate) {
|
|
270
328
|
this.activeParts = activePartsArray.filter((p) => !isSameDay(p, validatedParts));
|
|
@@ -282,6 +340,70 @@ export class Datetime {
|
|
|
282
340
|
}
|
|
283
341
|
this.confirm();
|
|
284
342
|
};
|
|
343
|
+
/**
|
|
344
|
+
* Handles range selection logic when a day is clicked.
|
|
345
|
+
*
|
|
346
|
+
* Creating a new range: first click sets start, second sets end.
|
|
347
|
+
* Modifying an existing range:
|
|
348
|
+
* - Before range → new start
|
|
349
|
+
* - Within range → new end
|
|
350
|
+
* - After range → new end
|
|
351
|
+
* If the resulting span contains a disabled date, start a new range instead.
|
|
352
|
+
*/
|
|
353
|
+
this.setActivePartsRange = (parts) => {
|
|
354
|
+
const current = this.rangeParts;
|
|
355
|
+
if (!current) {
|
|
356
|
+
// No range yet — start a new one
|
|
357
|
+
this.activeParts = { start: parts };
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const { start, end } = current;
|
|
361
|
+
if (end) {
|
|
362
|
+
// A complete range exists
|
|
363
|
+
if (isBefore(parts, start)) {
|
|
364
|
+
// Before range → new start
|
|
365
|
+
this.activeParts = { start: parts };
|
|
366
|
+
}
|
|
367
|
+
else if (isSameDay(parts, start) || (isAfter(parts, start) && isBefore(parts, end))) {
|
|
368
|
+
// Within range (including start) → set new end
|
|
369
|
+
if (this.rangeContainsDisabledDate(start, parts)) {
|
|
370
|
+
this.activeParts = { start: parts };
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.activeParts = { start, end: parts };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// After or equal to end → new end
|
|
378
|
+
if (this.rangeContainsDisabledDate(start, parts)) {
|
|
379
|
+
this.activeParts = { start: parts };
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
this.activeParts = { start, end: parts };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
// Only start date, no end yet
|
|
388
|
+
if (isSameDay(parts, start)) {
|
|
389
|
+
// Same day → single-day range
|
|
390
|
+
this.activeParts = { start, end: parts };
|
|
391
|
+
}
|
|
392
|
+
else if (isBefore(parts, start)) {
|
|
393
|
+
// Before start → new start
|
|
394
|
+
this.activeParts = { start: parts };
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// After start → set end if no disabled dates in between
|
|
398
|
+
if (this.rangeContainsDisabledDate(start, parts)) {
|
|
399
|
+
this.activeParts = { start: parts };
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.activeParts = { start, end: parts };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
285
407
|
this.initializeKeyboardListeners = () => {
|
|
286
408
|
const calendarBodyRef = this.calendarBodyRef;
|
|
287
409
|
if (!calendarBodyRef) {
|
|
@@ -679,6 +801,43 @@ export class Datetime {
|
|
|
679
801
|
if (hasValue) {
|
|
680
802
|
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
|
|
681
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* Range mode: convert the value array into DatetimeRangeParts.
|
|
806
|
+
*/
|
|
807
|
+
if (this.isRangeMode) {
|
|
808
|
+
if (hasValue && Array.isArray(valueToProcess)) {
|
|
809
|
+
let rangeValues = valueToProcess;
|
|
810
|
+
// If more than 2 entries, use first and last
|
|
811
|
+
if (rangeValues.length > 2) {
|
|
812
|
+
rangeValues = [rangeValues[0], rangeValues[rangeValues.length - 1]];
|
|
813
|
+
}
|
|
814
|
+
let startParts = rangeValues[0];
|
|
815
|
+
let endParts = rangeValues.length > 1 ? rangeValues[1] : undefined;
|
|
816
|
+
// Normalize reversed pairs so earlier date is start
|
|
817
|
+
if (endParts && isAfter(startParts, endParts)) {
|
|
818
|
+
const temp = startParts;
|
|
819
|
+
startParts = endParts;
|
|
820
|
+
endParts = temp;
|
|
821
|
+
}
|
|
822
|
+
this.activeParts = endParts ? { start: startParts, end: endParts } : { start: startParts };
|
|
823
|
+
// Navigate calendar to the start date
|
|
824
|
+
const targetValue = clampDate(startParts, minParts, maxParts);
|
|
825
|
+
const { month, day, year } = targetValue;
|
|
826
|
+
const didChangeMonth = (month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
|
|
827
|
+
const bodyIsVisible = el.classList.contains('datetime-ready');
|
|
828
|
+
const { isGridStyle, showMonthAndYear } = this;
|
|
829
|
+
if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
|
|
830
|
+
this.animateToDate(targetValue);
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.setWorkingParts({ month, day, year, hour: 0, minute: 0, ampm: 'am' });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
this.activeParts = [];
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
682
841
|
/**
|
|
683
842
|
* If there are multiple values, clamp to the last one.
|
|
684
843
|
* This is because the last value is the one that the user
|
|
@@ -839,6 +998,27 @@ export class Datetime {
|
|
|
839
998
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
840
999
|
return hasDatePresentation && !preferWheel;
|
|
841
1000
|
}
|
|
1001
|
+
get isMultipleMode() {
|
|
1002
|
+
return this.selectionMode === 'multiple' || (this.selectionMode === undefined && this.multiple);
|
|
1003
|
+
}
|
|
1004
|
+
get isRangeMode() {
|
|
1005
|
+
return this.selectionMode === 'range';
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Type guard: returns true if the given activeParts is a DatetimeRangeParts object.
|
|
1009
|
+
*/
|
|
1010
|
+
isRangeParts(parts) {
|
|
1011
|
+
return this.isRangeMode && parts !== null && typeof parts === 'object' && !Array.isArray(parts) && 'start' in parts;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Returns the current range parts if in range mode, or undefined otherwise.
|
|
1015
|
+
*/
|
|
1016
|
+
get rangeParts() {
|
|
1017
|
+
if (this.isRangeParts(this.activeParts)) {
|
|
1018
|
+
return this.activeParts;
|
|
1019
|
+
}
|
|
1020
|
+
return undefined;
|
|
1021
|
+
}
|
|
842
1022
|
yearValuesChanged() {
|
|
843
1023
|
this.parsedYearValues = convertToArrayOfNumbers(this.yearValues);
|
|
844
1024
|
}
|
|
@@ -874,6 +1054,18 @@ export class Datetime {
|
|
|
874
1054
|
*/
|
|
875
1055
|
async confirm(closeOverlay = false) {
|
|
876
1056
|
const { isCalendarPicker, activeParts, preferWheel, workingParts } = this;
|
|
1057
|
+
/**
|
|
1058
|
+
* In range mode, only confirm when both start and end are selected.
|
|
1059
|
+
*/
|
|
1060
|
+
if (this.isRangeMode) {
|
|
1061
|
+
if (this.isRangeParts(activeParts) && activeParts.end) {
|
|
1062
|
+
this.setValue([convertDataToISO(activeParts.start), convertDataToISO(activeParts.end)]);
|
|
1063
|
+
}
|
|
1064
|
+
if (closeOverlay) {
|
|
1065
|
+
this.closeParentOverlay(CONFIRM_ROLE);
|
|
1066
|
+
}
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
877
1069
|
/**
|
|
878
1070
|
* We only update the value if the presentation is not a calendar picker.
|
|
879
1071
|
*/
|
|
@@ -908,6 +1100,22 @@ export class Datetime {
|
|
|
908
1100
|
* @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.
|
|
909
1101
|
*/
|
|
910
1102
|
async reset(startDate) {
|
|
1103
|
+
if (this.isRangeMode) {
|
|
1104
|
+
/**
|
|
1105
|
+
* In range mode, reset clears the full range. If a startDate
|
|
1106
|
+
* is provided, the calendar navigates to that month without
|
|
1107
|
+
* selecting anything so the user begins a fresh selection.
|
|
1108
|
+
*/
|
|
1109
|
+
this.activeParts = [];
|
|
1110
|
+
if (startDate) {
|
|
1111
|
+
const parsed = parseDate(startDate);
|
|
1112
|
+
if (parsed) {
|
|
1113
|
+
const target = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
1114
|
+
this.setWorkingParts(target);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
911
1119
|
this.processValue(startDate);
|
|
912
1120
|
}
|
|
913
1121
|
/**
|
|
@@ -919,6 +1127,14 @@ export class Datetime {
|
|
|
919
1127
|
* @param closeOverlay If `true`, closes the parent overlay. Defaults to `false`.
|
|
920
1128
|
*/
|
|
921
1129
|
async cancel(closeOverlay = false) {
|
|
1130
|
+
/**
|
|
1131
|
+
* In range mode, clear the visible selection (including partial ranges)
|
|
1132
|
+
* so the component returns to an empty state. The value property is
|
|
1133
|
+
* left untouched, matching single/multiple mode behavior.
|
|
1134
|
+
*/
|
|
1135
|
+
if (this.isRangeMode) {
|
|
1136
|
+
this.activeParts = [];
|
|
1137
|
+
}
|
|
922
1138
|
this.ionCancel.emit();
|
|
923
1139
|
if (closeOverlay) {
|
|
924
1140
|
this.closeParentOverlay(CANCEL_ROLE);
|
|
@@ -1099,8 +1315,11 @@ export class Datetime {
|
|
|
1099
1315
|
});
|
|
1100
1316
|
}
|
|
1101
1317
|
componentWillLoad() {
|
|
1102
|
-
const { el, formatOptions, highlightedDates, multiple, presentation, preferWheel } = this;
|
|
1103
|
-
if (multiple) {
|
|
1318
|
+
const { el, formatOptions, highlightedDates, multiple, selectionMode, presentation, preferWheel } = this;
|
|
1319
|
+
if (selectionMode === undefined && multiple) {
|
|
1320
|
+
printIonWarning('[ion-datetime] - The "multiple" property is deprecated. Use selectionMode="multiple" instead.', el);
|
|
1321
|
+
}
|
|
1322
|
+
if (this.isMultipleMode) {
|
|
1104
1323
|
if (presentation !== 'date') {
|
|
1105
1324
|
printIonWarning('[ion-datetime] - Multiple date selection is only supported for presentation="date".', el);
|
|
1106
1325
|
}
|
|
@@ -1108,6 +1327,17 @@ export class Datetime {
|
|
|
1108
1327
|
printIonWarning('[ion-datetime] - Multiple date selection is not supported with preferWheel="true".', el);
|
|
1109
1328
|
}
|
|
1110
1329
|
}
|
|
1330
|
+
if (this.isRangeMode) {
|
|
1331
|
+
if (presentation !== 'date') {
|
|
1332
|
+
printIonWarning('[ion-datetime] - Range selection is only supported for presentation="date".', el);
|
|
1333
|
+
}
|
|
1334
|
+
if (preferWheel) {
|
|
1335
|
+
printIonWarning('[ion-datetime] - Range selection is not supported with preferWheel="true".', el);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (selectionMode !== undefined && multiple) {
|
|
1339
|
+
printIonWarning('[ion-datetime] - Both "selectionMode" and "multiple" are set. "selectionMode" takes precedence.', el);
|
|
1340
|
+
}
|
|
1111
1341
|
if (highlightedDates !== undefined) {
|
|
1112
1342
|
if (presentation !== 'date' && presentation !== 'date-time' && presentation !== 'time-date') {
|
|
1113
1343
|
printIonWarning('[ion-datetime] - The highlightedDates property is only supported with the date, date-time, and time-date presentations.', el);
|
|
@@ -1162,6 +1392,12 @@ export class Datetime {
|
|
|
1162
1392
|
* is disabled or readonly.
|
|
1163
1393
|
*/
|
|
1164
1394
|
const isButtonDisabled = disabled || readonly;
|
|
1395
|
+
/**
|
|
1396
|
+
* In range mode, the confirm button should be disabled until
|
|
1397
|
+
* both a start and end date have been selected.
|
|
1398
|
+
*/
|
|
1399
|
+
const isRangeIncomplete = this.isRangeMode && (!this.isRangeParts(this.activeParts) || !this.activeParts.end);
|
|
1400
|
+
const isConfirmDisabled = isButtonDisabled || isRangeIncomplete;
|
|
1165
1401
|
const confirmFill = theme === 'ionic' ? 'solid' : undefined;
|
|
1166
1402
|
const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null;
|
|
1167
1403
|
if (!hasSlottedButtons && !showDefaultButtons && !showClearButton) {
|
|
@@ -1181,7 +1417,7 @@ export class Datetime {
|
|
|
1181
1417
|
return (h("div", { class: "datetime-footer" }, h("div", { class: "datetime-buttons" }, h("div", { class: {
|
|
1182
1418
|
['datetime-action-buttons']: true,
|
|
1183
1419
|
['has-clear-button']: this.showClearButton,
|
|
1184
|
-
} }, h("slot", { name: "buttons" }, showDefaultButtons && (h("ion-button", { id: "cancel-button", color: this.color, onClick: () => this.cancel(true), disabled: isButtonDisabled }, this.cancelText)), h("div", { class: "datetime-action-buttons-container" }, showClearButton && (h("ion-button", { id: "clear-button", color: this.color, onClick: () => clearButtonClick(), disabled: isButtonDisabled }, this.clearText)), showDefaultButtons && (h("ion-button", { id: "confirm-button", fill: confirmFill, color: this.color, onClick: () => this.confirm(true), disabled:
|
|
1420
|
+
} }, h("slot", { name: "buttons" }, showDefaultButtons && (h("ion-button", { id: "cancel-button", color: this.color, onClick: () => this.cancel(true), disabled: isButtonDisabled }, this.cancelText)), h("div", { class: "datetime-action-buttons-container" }, showClearButton && (h("ion-button", { id: "clear-button", color: this.color, onClick: () => clearButtonClick(), disabled: isButtonDisabled }, this.clearText)), showDefaultButtons && (h("ion-button", { id: "confirm-button", fill: confirmFill, color: this.color, onClick: () => this.confirm(true), disabled: isConfirmDisabled }, this.doneText))))))));
|
|
1185
1421
|
}
|
|
1186
1422
|
/**
|
|
1187
1423
|
* Wheel picker render methods
|
|
@@ -1481,7 +1717,7 @@ export class Datetime {
|
|
|
1481
1717
|
'calendar-month-disabled': !isWorkingMonth && swipeDisabled,
|
|
1482
1718
|
} }, h("div", { class: "calendar-month-grid" }, getDaysOfMonth(month, year, this.firstDayOfWeek % 7, this.showAdjacentDays).map((dateObject, index) => {
|
|
1483
1719
|
const { day, dayOfWeek, isAdjacentDay } = dateObject;
|
|
1484
|
-
const { el, highlightedDates, isDateEnabled,
|
|
1720
|
+
const { el, highlightedDates, isDateEnabled, showAdjacentDays } = this;
|
|
1485
1721
|
let _month = month;
|
|
1486
1722
|
let _year = year;
|
|
1487
1723
|
if (showAdjacentDays && isAdjacentDay && day !== null) {
|
|
@@ -1510,7 +1746,7 @@ export class Datetime {
|
|
|
1510
1746
|
}
|
|
1511
1747
|
const referenceParts = { month: _month, day, year: _year, isAdjacentDay };
|
|
1512
1748
|
const isCalendarPadding = day === null;
|
|
1513
|
-
const { isActive, isToday, ariaLabel, ariaSelected, disabled: isDayDisabled, text, } = getCalendarDayState(this.locale, referenceParts, this.activeParts, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues);
|
|
1749
|
+
const { isActive, isToday, ariaLabel, ariaSelected, disabled: isDayDisabled, isRangeStart, isRangeEnd, isInRange, text, } = getCalendarDayState(this.locale, referenceParts, this.isRangeParts(this.activeParts) ? [] : this.activeParts, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues, this.rangeParts);
|
|
1514
1750
|
const dateIsoString = convertDataToISO(referenceParts);
|
|
1515
1751
|
let isCalDayDisabled = isCalMonthDisabled || isDayDisabled;
|
|
1516
1752
|
if (!isCalDayDisabled && isDateEnabled !== undefined) {
|
|
@@ -1533,24 +1769,50 @@ export class Datetime {
|
|
|
1533
1769
|
*/
|
|
1534
1770
|
const isCalDayConstrained = isCalDayDisabled && isDatetimeDisabled;
|
|
1535
1771
|
const isButtonDisabled = isCalDayDisabled || isDatetimeDisabled;
|
|
1772
|
+
/**
|
|
1773
|
+
* Determine if this day is part of the range selection.
|
|
1774
|
+
* Range styling takes priority over highlightedDates.
|
|
1775
|
+
*/
|
|
1776
|
+
const isPartOfRange = isRangeStart || isRangeEnd || isInRange;
|
|
1536
1777
|
let dateStyle = undefined;
|
|
1537
1778
|
/**
|
|
1538
|
-
* Custom highlight styles should not override the style for selected dates
|
|
1539
|
-
* nor apply to "filler days" at the start of the grid.
|
|
1779
|
+
* Custom highlight styles should not override the style for selected dates
|
|
1780
|
+
* or range selection, nor apply to "filler days" at the start of the grid.
|
|
1540
1781
|
*/
|
|
1541
|
-
if (highlightedDates !== undefined && !isActive && day !== null && !isAdjacentDay) {
|
|
1782
|
+
if (highlightedDates !== undefined && !isActive && !isPartOfRange && day !== null && !isAdjacentDay) {
|
|
1542
1783
|
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
|
|
1543
1784
|
}
|
|
1544
1785
|
let dateParts = undefined;
|
|
1545
1786
|
// "Filler days" at the beginning of the grid should not get the calendar day
|
|
1546
1787
|
// CSS parts added to them
|
|
1547
1788
|
if (!isCalendarPadding && !isAdjacentDay) {
|
|
1548
|
-
dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${isCalDayDisabled ? ' disabled' : ''}`;
|
|
1789
|
+
dateParts = `calendar-day${isActive || isRangeStart || isRangeEnd ? ' active' : ''}${isToday ? ' today' : ''}${isCalDayDisabled ? ' disabled' : ''}${isRangeStart ? ' range-start range-selection' : ''}${isRangeEnd ? ' range-end range-selection' : ''}${isInRange ? ' range-selection' : ''}`;
|
|
1549
1790
|
}
|
|
1550
1791
|
else if (isAdjacentDay) {
|
|
1551
1792
|
dateParts = `calendar-day${isCalDayDisabled ? ' disabled' : ''}`;
|
|
1552
1793
|
}
|
|
1553
|
-
|
|
1794
|
+
/**
|
|
1795
|
+
* Build the wrapper's shadow part attribute for range track styling.
|
|
1796
|
+
*/
|
|
1797
|
+
const wrapperParts = isPartOfRange && !isCalendarPadding && !isAdjacentDay ? 'range-track' : undefined;
|
|
1798
|
+
/**
|
|
1799
|
+
* Build the aria-label for range mode accessibility.
|
|
1800
|
+
*/
|
|
1801
|
+
let rangeAriaLabel = ariaLabel;
|
|
1802
|
+
if (this.isRangeMode && ariaLabel) {
|
|
1803
|
+
if (isRangeStart) {
|
|
1804
|
+
rangeAriaLabel = `${ariaLabel}, ${this.startDateLabel}`;
|
|
1805
|
+
}
|
|
1806
|
+
if (isRangeEnd) {
|
|
1807
|
+
rangeAriaLabel = `${ariaLabel}, ${this.endDateLabel}`;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return (h("div", { class: {
|
|
1811
|
+
'calendar-day-wrapper': true,
|
|
1812
|
+
'calendar-day-wrapper-range-start': isRangeStart,
|
|
1813
|
+
'calendar-day-wrapper-in-range': isInRange,
|
|
1814
|
+
'calendar-day-wrapper-range-end': isRangeEnd,
|
|
1815
|
+
}, part: wrapperParts }, h("button", {
|
|
1554
1816
|
// We need to use !important for the inline styles here because
|
|
1555
1817
|
// otherwise the CSS shadow parts will override these styles.
|
|
1556
1818
|
// See https://github.com/WICG/webcomponents/issues/847
|
|
@@ -1566,25 +1828,37 @@ export class Datetime {
|
|
|
1566
1828
|
}, tabindex: "-1", "data-day": day, "data-month": _month, "data-year": _year, "data-index": index, "data-day-of-week": dayOfWeek, disabled: isButtonDisabled, class: {
|
|
1567
1829
|
'calendar-day-padding': isCalendarPadding,
|
|
1568
1830
|
'calendar-day': true,
|
|
1569
|
-
'calendar-day-active': isActive,
|
|
1831
|
+
'calendar-day-active': isActive || isRangeStart || isRangeEnd,
|
|
1570
1832
|
'calendar-day-constrained': isCalDayConstrained,
|
|
1571
1833
|
'calendar-day-today': isToday,
|
|
1572
1834
|
'calendar-day-adjacent-day': isAdjacentDay,
|
|
1573
|
-
|
|
1835
|
+
'calendar-day-range-start': isRangeStart,
|
|
1836
|
+
'calendar-day-range-end': isRangeEnd,
|
|
1837
|
+
'calendar-day-in-range': isInRange,
|
|
1838
|
+
}, part: dateParts, "aria-hidden": isCalendarPadding ? 'true' : null, "aria-selected": isActive || isRangeStart || isRangeEnd ? 'true' : ariaSelected, "aria-label": rangeAriaLabel, onClick: () => {
|
|
1574
1839
|
if (isCalendarPadding) {
|
|
1575
1840
|
return;
|
|
1576
1841
|
}
|
|
1577
1842
|
if (isAdjacentDay) {
|
|
1578
1843
|
// The user selected a day outside the current month. Ignore this button, as the month will be re-rendered.
|
|
1579
1844
|
this.el.blur();
|
|
1580
|
-
|
|
1845
|
+
if (!this.isRangeMode) {
|
|
1846
|
+
this.activeParts = Object.assign(Object.assign({}, activePart), referenceParts);
|
|
1847
|
+
}
|
|
1581
1848
|
this.animateToDate(referenceParts);
|
|
1582
|
-
this.
|
|
1849
|
+
if (this.isRangeMode) {
|
|
1850
|
+
this.setActiveParts(referenceParts);
|
|
1851
|
+
}
|
|
1852
|
+
else {
|
|
1853
|
+
this.confirm();
|
|
1854
|
+
}
|
|
1583
1855
|
}
|
|
1584
1856
|
else {
|
|
1585
1857
|
this.setWorkingParts(Object.assign(Object.assign({}, this.workingParts), referenceParts));
|
|
1586
|
-
|
|
1587
|
-
|
|
1858
|
+
if (this.isRangeMode) {
|
|
1859
|
+
this.setActiveParts(referenceParts);
|
|
1860
|
+
}
|
|
1861
|
+
else if (this.isMultipleMode) {
|
|
1588
1862
|
this.setActiveParts(referenceParts, isActive);
|
|
1589
1863
|
}
|
|
1590
1864
|
else {
|
|
@@ -1655,10 +1929,23 @@ export class Datetime {
|
|
|
1655
1929
|
}
|
|
1656
1930
|
getHeaderSelectedDateText() {
|
|
1657
1931
|
var _a;
|
|
1658
|
-
const { activeParts, formatOptions,
|
|
1932
|
+
const { activeParts, formatOptions, titleSelectedDatesFormatter } = this;
|
|
1659
1933
|
const isArray = Array.isArray(activeParts);
|
|
1934
|
+
const dateFormatOptions = (_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _a !== void 0 ? _a : { weekday: 'short', month: 'short', day: 'numeric' };
|
|
1660
1935
|
let headerText;
|
|
1661
|
-
if (
|
|
1936
|
+
if (this.isRangeMode) {
|
|
1937
|
+
headerText = this.getRangeHeaderText(dateFormatOptions);
|
|
1938
|
+
if (titleSelectedDatesFormatter !== undefined) {
|
|
1939
|
+
try {
|
|
1940
|
+
const rangeValues = this.getRangeValueArray();
|
|
1941
|
+
headerText = titleSelectedDatesFormatter(rangeValues);
|
|
1942
|
+
}
|
|
1943
|
+
catch (e) {
|
|
1944
|
+
printIonError('[ion-datetime] - Exception in provided `titleSelectedDatesFormatter`:', e);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
else if (this.isMultipleMode && isArray && activeParts.length !== 1) {
|
|
1662
1949
|
headerText = `${activeParts.length} days`; // default/fallback for multiple selection
|
|
1663
1950
|
if (titleSelectedDatesFormatter !== undefined) {
|
|
1664
1951
|
try {
|
|
@@ -1671,10 +1958,35 @@ export class Datetime {
|
|
|
1671
1958
|
}
|
|
1672
1959
|
else {
|
|
1673
1960
|
// for exactly 1 day selected (multiple set or not), show a formatted version of that
|
|
1674
|
-
headerText = getLocalizedDateTime(this.locale, this.getActivePartsWithFallback(),
|
|
1961
|
+
headerText = getLocalizedDateTime(this.locale, this.getActivePartsWithFallback(), dateFormatOptions);
|
|
1675
1962
|
}
|
|
1676
1963
|
return headerText;
|
|
1677
1964
|
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Returns the formatted range header text.
|
|
1967
|
+
* Shows "Start date - End date" with placeholders from startDateLabel/endDateLabel.
|
|
1968
|
+
*/
|
|
1969
|
+
getRangeHeaderText(dateFormatOptions) {
|
|
1970
|
+
const range = this.rangeParts;
|
|
1971
|
+
if (!range) {
|
|
1972
|
+
return `${this.startDateLabel} - ${this.endDateLabel}`;
|
|
1973
|
+
}
|
|
1974
|
+
const startText = getLocalizedDateTime(this.locale, range.start, dateFormatOptions);
|
|
1975
|
+
const endText = range.end ? getLocalizedDateTime(this.locale, range.end, dateFormatOptions) : this.endDateLabel;
|
|
1976
|
+
return `${startText} - ${endText}`;
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Returns the current range value as an ISO string array for
|
|
1980
|
+
* titleSelectedDatesFormatter and ionChange.
|
|
1981
|
+
*/
|
|
1982
|
+
getRangeValueArray() {
|
|
1983
|
+
const range = this.rangeParts;
|
|
1984
|
+
if (!range)
|
|
1985
|
+
return [];
|
|
1986
|
+
if (!range.end)
|
|
1987
|
+
return [convertDataToISO(range.start)];
|
|
1988
|
+
return [convertDataToISO(range.start), convertDataToISO(range.end)];
|
|
1989
|
+
}
|
|
1678
1990
|
renderHeader(showExpandedHeader = true) {
|
|
1679
1991
|
const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null;
|
|
1680
1992
|
if (!hasSlottedTitle && !this.showDefaultTitle) {
|
|
@@ -1832,7 +2144,7 @@ export class Datetime {
|
|
|
1832
2144
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
1833
2145
|
const hasWheelVariant = hasDatePresentation && preferWheel;
|
|
1834
2146
|
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
|
1835
|
-
return (h(Host, { key: '
|
|
2147
|
+
return (h(Host, { key: '8ec46f2a5bc855c3d6210f72305b66655ad59b93', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
|
|
1836
2148
|
[theme]: true,
|
|
1837
2149
|
['datetime-readonly']: readonly,
|
|
1838
2150
|
['datetime-disabled']: disabled,
|
|
@@ -1842,7 +2154,7 @@ export class Datetime {
|
|
|
1842
2154
|
[`datetime-size-${size}`]: true,
|
|
1843
2155
|
[`datetime-prefer-wheel`]: hasWheelVariant,
|
|
1844
2156
|
[`datetime-grid`]: isGridStyle,
|
|
1845
|
-
})) }, h("div", { key: '
|
|
2157
|
+
})) }, h("div", { key: 'b7bf9b1e681b8d1a5a24a27c758a528c03a203fc', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(theme)));
|
|
1846
2158
|
}
|
|
1847
2159
|
static get is() { return "ion-datetime"; }
|
|
1848
2160
|
static get encapsulation() { return "shadow"; }
|
|
@@ -2305,7 +2617,10 @@ export class Datetime {
|
|
|
2305
2617
|
"required": false,
|
|
2306
2618
|
"optional": false,
|
|
2307
2619
|
"docs": {
|
|
2308
|
-
"tags": [
|
|
2620
|
+
"tags": [{
|
|
2621
|
+
"name": "deprecated",
|
|
2622
|
+
"text": "Use `selectionMode=\"multiple\"` instead."
|
|
2623
|
+
}],
|
|
2309
2624
|
"text": "If `true`, multiple dates can be selected at once. Only\napplies to `presentation=\"date\"` and `preferWheel=\"false\"`."
|
|
2310
2625
|
},
|
|
2311
2626
|
"getter": false,
|
|
@@ -2314,6 +2629,65 @@ export class Datetime {
|
|
|
2314
2629
|
"attribute": "multiple",
|
|
2315
2630
|
"defaultValue": "false"
|
|
2316
2631
|
},
|
|
2632
|
+
"selectionMode": {
|
|
2633
|
+
"type": "string",
|
|
2634
|
+
"mutable": false,
|
|
2635
|
+
"complexType": {
|
|
2636
|
+
"original": "'multiple' | 'range'",
|
|
2637
|
+
"resolved": "\"multiple\" | \"range\" | undefined",
|
|
2638
|
+
"references": {}
|
|
2639
|
+
},
|
|
2640
|
+
"required": false,
|
|
2641
|
+
"optional": true,
|
|
2642
|
+
"docs": {
|
|
2643
|
+
"tags": [],
|
|
2644
|
+
"text": "Controls the date selection behavior.\n`'multiple'` enables selecting multiple individual dates.\n`'range'` enables selecting a start and end date range.\nWhen unset, single-date selection is used.\n\nRange selection only works with `presentation=\"date\"` and `preferWheel=\"false\"`."
|
|
2645
|
+
},
|
|
2646
|
+
"getter": false,
|
|
2647
|
+
"setter": false,
|
|
2648
|
+
"reflect": false,
|
|
2649
|
+
"attribute": "selection-mode"
|
|
2650
|
+
},
|
|
2651
|
+
"startDateLabel": {
|
|
2652
|
+
"type": "string",
|
|
2653
|
+
"mutable": false,
|
|
2654
|
+
"complexType": {
|
|
2655
|
+
"original": "string",
|
|
2656
|
+
"resolved": "string",
|
|
2657
|
+
"references": {}
|
|
2658
|
+
},
|
|
2659
|
+
"required": false,
|
|
2660
|
+
"optional": false,
|
|
2661
|
+
"docs": {
|
|
2662
|
+
"tags": [],
|
|
2663
|
+
"text": "The text shown in the datetime header when the start of the range\nis empty, also used by screen readers to announce the start date.\nOnly applies when `selectionMode=\"range\"`."
|
|
2664
|
+
},
|
|
2665
|
+
"getter": false,
|
|
2666
|
+
"setter": false,
|
|
2667
|
+
"reflect": false,
|
|
2668
|
+
"attribute": "start-date-label",
|
|
2669
|
+
"defaultValue": "'Start date'"
|
|
2670
|
+
},
|
|
2671
|
+
"endDateLabel": {
|
|
2672
|
+
"type": "string",
|
|
2673
|
+
"mutable": false,
|
|
2674
|
+
"complexType": {
|
|
2675
|
+
"original": "string",
|
|
2676
|
+
"resolved": "string",
|
|
2677
|
+
"references": {}
|
|
2678
|
+
},
|
|
2679
|
+
"required": false,
|
|
2680
|
+
"optional": false,
|
|
2681
|
+
"docs": {
|
|
2682
|
+
"tags": [],
|
|
2683
|
+
"text": "The text shown in the datetime header when the end of the range\nis empty, also used by screen readers to announce the end date.\nOnly applies when `selectionMode=\"range\"`."
|
|
2684
|
+
},
|
|
2685
|
+
"getter": false,
|
|
2686
|
+
"setter": false,
|
|
2687
|
+
"reflect": false,
|
|
2688
|
+
"attribute": "end-date-label",
|
|
2689
|
+
"defaultValue": "'End date'"
|
|
2690
|
+
},
|
|
2317
2691
|
"highlightedDates": {
|
|
2318
2692
|
"type": "unknown",
|
|
2319
2693
|
"mutable": false,
|
|
@@ -2672,6 +3046,12 @@ export class Datetime {
|
|
|
2672
3046
|
"Promise": {
|
|
2673
3047
|
"location": "global",
|
|
2674
3048
|
"id": "global::Promise"
|
|
3049
|
+
},
|
|
3050
|
+
"DatetimeParts": {
|
|
3051
|
+
"location": "import",
|
|
3052
|
+
"path": "./datetime-interface",
|
|
3053
|
+
"id": "src/components/datetime/datetime-interface.ts::DatetimeParts",
|
|
3054
|
+
"referenceLocation": "DatetimeParts"
|
|
2675
3055
|
}
|
|
2676
3056
|
},
|
|
2677
3057
|
"return": "Promise<void>"
|