@ionic/core 7.7.3-dev.11707927732.13defc00 → 7.7.3-dev.11709159644.114cd8b1

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.
Files changed (69) hide show
  1. package/components/data.js +23 -38
  2. package/components/ion-content.js +3 -4
  3. package/components/ion-datetime-button.js +11 -10
  4. package/components/ion-datetime.js +69 -7
  5. package/components/ion-searchbar.js +34 -5
  6. package/dist/cjs/{data-a5109f09.js → data-4487a815.js} +22 -39
  7. package/dist/cjs/ion-app_8.cjs.entry.js +3 -4
  8. package/dist/cjs/ion-datetime-button.cjs.entry.js +11 -10
  9. package/dist/cjs/ion-datetime_3.cjs.entry.js +66 -7
  10. package/dist/cjs/ion-searchbar.cjs.entry.js +28 -4
  11. package/dist/cjs/ionic.cjs.js +1 -1
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/components/content/content.js +3 -4
  14. package/dist/collection/components/datetime/datetime.js +50 -7
  15. package/dist/collection/components/datetime/test/basic/datetime.e2e.js +77 -0
  16. package/dist/collection/components/datetime/test/format.spec.js +65 -15
  17. package/dist/collection/components/datetime/utils/format.js +24 -37
  18. package/dist/collection/components/datetime/utils/validate.js +45 -0
  19. package/dist/collection/components/datetime-button/datetime-button.js +11 -10
  20. package/dist/collection/components/datetime-button/test/basic/datetime-button.e2e.js +65 -0
  21. package/dist/collection/components/searchbar/searchbar.js +84 -5
  22. package/dist/collection/components/searchbar/test/searchbar.spec.js +20 -2
  23. package/dist/docs.json +113 -1
  24. package/dist/esm/{data-0f3ab200.js → data-bb424ba8.js} +23 -38
  25. package/dist/esm/ion-app_8.entry.js +3 -4
  26. package/dist/esm/ion-datetime-button.entry.js +11 -10
  27. package/dist/esm/ion-datetime_3.entry.js +66 -7
  28. package/dist/esm/ion-searchbar.entry.js +29 -5
  29. package/dist/esm/ionic.js +1 -1
  30. package/dist/esm/loader.js +1 -1
  31. package/dist/esm-es5/data-bb424ba8.js +4 -0
  32. package/dist/esm-es5/ion-app_8.entry.js +1 -1
  33. package/dist/esm-es5/ion-datetime-button.entry.js +1 -1
  34. package/dist/esm-es5/ion-datetime_3.entry.js +1 -1
  35. package/dist/esm-es5/ion-searchbar.entry.js +1 -1
  36. package/dist/esm-es5/ionic.js +1 -1
  37. package/dist/esm-es5/loader.js +1 -1
  38. package/dist/html.html-data.json +12 -0
  39. package/dist/ionic/ionic.esm.js +1 -1
  40. package/dist/ionic/p-0b34d3a7.system.js +1 -1
  41. package/dist/ionic/p-2d95cfc5.entry.js +4 -0
  42. package/dist/ionic/p-31450f73.entry.js +4 -0
  43. package/dist/ionic/p-3789920e.js +4 -0
  44. package/dist/ionic/p-8a1b0abb.system.js +4 -0
  45. package/dist/ionic/p-9f207856.entry.js +4 -0
  46. package/dist/ionic/p-b18b2148.system.entry.js +4 -0
  47. package/dist/ionic/p-d8ee0efe.system.entry.js +4 -0
  48. package/dist/ionic/p-dbd70d97.system.entry.js +4 -0
  49. package/dist/ionic/p-e942b677.entry.js +4 -0
  50. package/dist/ionic/p-e9b9a128.system.entry.js +4 -0
  51. package/dist/types/components/datetime/datetime-interface.d.ts +10 -0
  52. package/dist/types/components/datetime/datetime.d.ts +9 -1
  53. package/dist/types/components/datetime/utils/format.d.ts +7 -13
  54. package/dist/types/components/datetime/utils/validate.d.ts +8 -0
  55. package/dist/types/components/searchbar/searchbar.d.ts +38 -0
  56. package/dist/types/components.d.ts +34 -2
  57. package/hydrate/index.js +130 -60
  58. package/package.json +1 -1
  59. package/dist/esm-es5/data-0f3ab200.js +0 -4
  60. package/dist/ionic/p-0e23c289.system.entry.js +0 -4
  61. package/dist/ionic/p-206c34f8.system.entry.js +0 -4
  62. package/dist/ionic/p-2327064c.entry.js +0 -4
  63. package/dist/ionic/p-23c73017.entry.js +0 -4
  64. package/dist/ionic/p-68ba8e77.system.entry.js +0 -4
  65. package/dist/ionic/p-7330c044.system.js +0 -4
  66. package/dist/ionic/p-8821fd17.entry.js +0 -4
  67. package/dist/ionic/p-a6e465f5.entry.js +0 -4
  68. package/dist/ionic/p-e81bf09e.system.entry.js +0 -4
  69. package/dist/ionic/p-f7dce541.js +0 -4
@@ -402,3 +402,80 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
402
402
  });
403
403
  });
404
404
  });
405
+ /**
406
+ * This behavior does not differ across
407
+ * directions.
408
+ */
409
+ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
410
+ test.describe(title('datetime: formatOptions'), () => {
411
+ test('should format header and time button', async ({ page }) => {
412
+ await page.setContent(`
413
+ <ion-datetime value="2022-02-01T16:30:00">
414
+ <span slot="title">Select Date</span>
415
+ </ion-datetime>
416
+ <script>
417
+ const datetime = document.querySelector('ion-datetime');
418
+ datetime.formatOptions = {
419
+ time: { hour: '2-digit', minute: '2-digit' },
420
+ date: { day: '2-digit', month: 'long', era: 'short' },
421
+ }
422
+ </script>
423
+ `, config);
424
+ await page.locator('.datetime-ready').waitFor();
425
+ const headerDate = page.locator('ion-datetime .datetime-selected-date');
426
+ await expect(headerDate).toHaveText('February 01 AD');
427
+ const timeBody = page.locator('ion-datetime .time-body');
428
+ await expect(timeBody).toHaveText('04:30 PM');
429
+ });
430
+ });
431
+ });
432
+ /**
433
+ * This behavior does not differ across
434
+ * modes/directions.
435
+ */
436
+ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
437
+ test.describe(title('datetime: formatOptions misconfiguration errors'), () => {
438
+ test('should log a warning if time zone is provided', async ({ page }) => {
439
+ const logs = [];
440
+ page.on('console', (msg) => {
441
+ if (msg.type() === 'warning') {
442
+ logs.push(msg.text());
443
+ }
444
+ });
445
+ await page.setContent(`
446
+ <ion-datetime value="2022-02-01T16:30:00" presentation="date">
447
+ <span slot="title">Select Date</span>
448
+ </ion-datetime>
449
+ <script>
450
+ const datetime = document.querySelector('ion-datetime');
451
+ datetime.formatOptions = {
452
+ date: { timeZone: 'UTC' },
453
+ }
454
+ </script>
455
+ `, config);
456
+ await page.locator('.datetime-ready').waitFor();
457
+ expect(logs.length).toBe(1);
458
+ expect(logs[0]).toContain('[Ionic Warning]: Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".');
459
+ });
460
+ test('should log a warning if the required formatOptions are not provided for a presentation', async ({ page }) => {
461
+ const logs = [];
462
+ page.on('console', (msg) => {
463
+ if (msg.type() === 'warning') {
464
+ logs.push(msg.text());
465
+ }
466
+ });
467
+ await page.setContent(`
468
+ <ion-datetime value="2022-02-01T16:30:00">
469
+ <span slot="title">Select Date</span>
470
+ </ion-datetime>
471
+ <script>
472
+ const datetime = document.querySelector('ion-datetime');
473
+ datetime.formatOptions = {}
474
+ </script>
475
+ `, config);
476
+ await page.locator('.datetime-ready').waitFor();
477
+ expect(logs.length).toBe(1);
478
+ expect(logs[0]).toContain("[Ionic Warning]: Datetime: The 'date-time' presentation requires either a date or time object (or both) in formatOptions.");
479
+ });
480
+ });
481
+ });
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { generateDayAriaLabel, getMonthAndDay, getFormattedHour, addTimePadding, getMonthAndYear, getLocalizedDayPeriod, getLocalizedTime, } from "../utils/format";
4
+ import { generateDayAriaLabel, getFormattedHour, addTimePadding, getMonthAndYear, getLocalizedDayPeriod, getLocalizedTime, stripTimeZone, } from "../utils/format";
5
5
  describe('generateDayAriaLabel()', () => {
6
6
  it('should return Wednesday, May 12', () => {
7
7
  const reference = { month: 5, day: 12, year: 2021 };
@@ -24,20 +24,6 @@ describe('generateDayAriaLabel()', () => {
24
24
  expect(generateDayAriaLabel('en-US', false, reference)).toEqual('Saturday, April 1');
25
25
  });
26
26
  });
27
- describe('getMonthAndDay()', () => {
28
- it('should return Tue, May 11', () => {
29
- expect(getMonthAndDay('en-US', { month: 5, day: 11, year: 2021 })).toEqual('Tue, May 11');
30
- });
31
- it('should return mar, 11 may', () => {
32
- expect(getMonthAndDay('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mar, 11 may');
33
- });
34
- it('should return Sat, Apr 1', () => {
35
- expect(getMonthAndDay('en-US', { month: 4, day: 1, year: 2006 })).toEqual('Sat, Apr 1');
36
- });
37
- it('should return sáb, 1 abr', () => {
38
- expect(getMonthAndDay('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('sáb, 1 abr');
39
- });
40
- });
41
27
  describe('getFormattedHour()', () => {
42
28
  it('should only add padding if using 24 hour time', () => {
43
29
  expect(getFormattedHour(1, 'h11')).toEqual('1');
@@ -121,4 +107,68 @@ describe('getLocalizedTime', () => {
121
107
  expect(getLocalizedTime('en-US', datetimeParts, 'h12')).toEqual('10:40 PM');
122
108
  expect(getLocalizedTime('en-US', datetimeParts, 'h23')).toEqual('22:40');
123
109
  });
110
+ it('should use formatOptions', () => {
111
+ const datetimeParts = {
112
+ day: 1,
113
+ month: 1,
114
+ year: 2022,
115
+ hour: 9,
116
+ minute: 40,
117
+ };
118
+ const formatOptions = {
119
+ hour: '2-digit',
120
+ minute: '2-digit',
121
+ dayPeriod: 'short',
122
+ day: '2-digit',
123
+ };
124
+ // Even though this method is intended to be used for time, the date may be displayed as well when passing formatOptions
125
+ expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('01, 09:40 in the morning');
126
+ });
127
+ it('should override provided time zone with UTC', () => {
128
+ const datetimeParts = {
129
+ day: 1,
130
+ month: 1,
131
+ year: 2022,
132
+ hour: 9,
133
+ minute: 40,
134
+ };
135
+ const formatOptions = {
136
+ timeZone: 'Australia/Sydney',
137
+ timeZoneName: 'long',
138
+ hour: 'numeric',
139
+ minute: 'numeric',
140
+ };
141
+ expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('9:40 AM');
142
+ });
143
+ it('should not include time zone name', () => {
144
+ const datetimeParts = {
145
+ day: 1,
146
+ month: 1,
147
+ year: 2022,
148
+ hour: 9,
149
+ minute: 40,
150
+ };
151
+ const formatOptions = {
152
+ timeZone: 'America/Los_Angeles',
153
+ timeZoneName: 'long',
154
+ hour: 'numeric',
155
+ minute: 'numeric',
156
+ };
157
+ expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('9:40 AM');
158
+ });
159
+ });
160
+ describe('stripTimeZone', () => {
161
+ it('should remove the time zone name from the options and set the time zone to UTC', () => {
162
+ const formatOptions = {
163
+ timeZone: 'America/Los_Angeles',
164
+ timeZoneName: 'long',
165
+ hour: 'numeric',
166
+ minute: 'numeric',
167
+ };
168
+ expect(stripTimeZone(formatOptions)).toEqual({
169
+ timeZone: 'UTC',
170
+ hour: 'numeric',
171
+ minute: 'numeric',
172
+ });
173
+ });
124
174
  });
@@ -9,7 +9,26 @@ const getFormattedDayPeriod = (dayPeriod) => {
9
9
  }
10
10
  return dayPeriod.toUpperCase();
11
11
  };
12
- export const getLocalizedTime = (locale, refParts, hourCycle) => {
12
+ /**
13
+ * Including time zone options may lead to the rendered text showing a
14
+ * different time from what was selected in the Datetime, which could cause
15
+ * confusion.
16
+ */
17
+ export const stripTimeZone = (formatOptions) => {
18
+ return Object.assign(Object.assign({}, formatOptions), {
19
+ /**
20
+ * Setting the time zone to UTC ensures that the value shown is always the
21
+ * same as what was selected and safeguards against older Safari bugs with
22
+ * Intl.DateTimeFormat.
23
+ */
24
+ timeZone: 'UTC',
25
+ /**
26
+ * We do not want to display the time zone name
27
+ */
28
+ timeZoneName: undefined
29
+ });
30
+ };
31
+ export const getLocalizedTime = (locale, refParts, hourCycle, formatOptions = { hour: 'numeric', minute: 'numeric' }) => {
13
32
  const timeParts = {
14
33
  hour: refParts.hour,
15
34
  minute: refParts.minute,
@@ -17,28 +36,13 @@ export const getLocalizedTime = (locale, refParts, hourCycle) => {
17
36
  if (timeParts.hour === undefined || timeParts.minute === undefined) {
18
37
  return 'Invalid Time';
19
38
  }
20
- return new Intl.DateTimeFormat(locale, {
21
- hour: 'numeric',
22
- minute: 'numeric',
23
- /**
24
- * Setting the timeZone to UTC prevents
25
- * new Intl.DatetimeFormat from subtracting
26
- * the user's current timezone offset
27
- * when formatting the time.
28
- */
29
- timeZone: 'UTC',
39
+ return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, stripTimeZone(formatOptions)), {
30
40
  /**
31
41
  * We use hourCycle here instead of hour12 due to:
32
42
  * https://bugs.chromium.org/p/chromium/issues/detail?id=1347316&q=hour12&can=2
33
43
  */
34
- hourCycle,
35
- /**
36
- * Setting Z at the end indicates that this
37
- * date string is in the UTC time zone. This
38
- * prevents new Date from adding the time zone
39
- * offset when getting the ISO string.
40
- */
41
- }).format(new Date(convertDataToISO(Object.assign({
44
+ hourCycle
45
+ })).format(new Date(convertDataToISO(Object.assign({
42
46
  /**
43
47
  * JS uses a simplified ISO 8601 format which allows for
44
48
  * date-only formats and date-time formats, but not
@@ -125,14 +129,6 @@ export const generateDayAriaLabel = (locale, today, refParts) => {
125
129
  */
126
130
  return today ? `Today, ${labelString}` : labelString;
127
131
  };
128
- /**
129
- * Gets the day of the week, month, and day
130
- * Used for the header in MD mode.
131
- */
132
- export const getMonthAndDay = (locale, refParts) => {
133
- const date = getNormalizedDate(refParts);
134
- return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(date);
135
- };
136
132
  /**
137
133
  * Given a locale and a date object,
138
134
  * return a formatted string that includes
@@ -143,15 +139,6 @@ export const getMonthAndYear = (locale, refParts) => {
143
139
  const date = getNormalizedDate(refParts);
144
140
  return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
145
141
  };
146
- /**
147
- * Given a locale and a date object,
148
- * return a formatted string that includes
149
- * the short month, numeric day, and full year.
150
- * Example: Apr 22, 2021
151
- */
152
- export const getMonthDayAndYear = (locale, refParts) => {
153
- return getLocalizedDateTime(locale, refParts, { month: 'short', day: 'numeric', year: 'numeric' });
154
- };
155
142
  /**
156
143
  * Given a locale and a date object,
157
144
  * return a formatted string that includes
@@ -201,7 +188,7 @@ export const getNormalizedDate = (refParts) => {
201
188
  */
202
189
  export const getLocalizedDateTime = (locale, refParts, options) => {
203
190
  const date = getNormalizedDate(refParts);
204
- return getDateTimeFormat(locale, options).format(date);
191
+ return getDateTimeFormat(locale, stripTimeZone(options)).format(date);
205
192
  };
206
193
  /**
207
194
  * Given a locale, DatetimeParts, and options
@@ -0,0 +1,45 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ import { printIonWarning } from "../../../utils/logging/index";
5
+ /**
6
+ * If a time zone is provided in the format options, the rendered text could
7
+ * differ from what was selected in the Datetime, which could cause
8
+ * confusion.
9
+ */
10
+ export const warnIfTimeZoneProvided = (el, formatOptions) => {
11
+ var _a, _b, _c, _d;
12
+ if (((_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) === null || _a === void 0 ? void 0 : _a.timeZone) ||
13
+ ((_b = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) === null || _b === void 0 ? void 0 : _b.timeZoneName) ||
14
+ ((_c = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) === null || _c === void 0 ? void 0 : _c.timeZone) ||
15
+ ((_d = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) === null || _d === void 0 ? void 0 : _d.timeZoneName)) {
16
+ printIonWarning('Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".', el);
17
+ }
18
+ };
19
+ export const checkForPresentationFormatMismatch = (el, presentation, formatOptions) => {
20
+ // formatOptions is not required
21
+ if (!formatOptions)
22
+ return;
23
+ // If formatOptions is provided, the date and/or time objects are required, depending on the presentation
24
+ switch (presentation) {
25
+ case 'date':
26
+ case 'month-year':
27
+ case 'month':
28
+ case 'year':
29
+ if (formatOptions.date === undefined) {
30
+ printIonWarning(`Datetime: The '${presentation}' presentation requires a date object in formatOptions.`, el);
31
+ }
32
+ break;
33
+ case 'time':
34
+ if (formatOptions.time === undefined) {
35
+ printIonWarning(`Datetime: The 'time' presentation requires a time object in formatOptions.`, el);
36
+ }
37
+ break;
38
+ case 'date-time':
39
+ case 'time-date':
40
+ if (formatOptions.date === undefined && formatOptions.time === undefined) {
41
+ printIonWarning(`Datetime: The '${presentation}' presentation requires either a date or time object (or both) in formatOptions.`, el);
42
+ }
43
+ break;
44
+ }
45
+ };
@@ -7,7 +7,7 @@ import { printIonError } from "../../utils/logging/index";
7
7
  import { createColorClasses } from "../../utils/theme";
8
8
  import { getIonMode } from "../../global/ionic-global";
9
9
  import { getToday } from "../datetime/utils/data";
10
- import { getMonthAndYear, getMonthDayAndYear, getLocalizedDateTime, getLocalizedTime } from "../datetime/utils/format";
10
+ import { getLocalizedDateTime, getLocalizedTime } from "../datetime/utils/format";
11
11
  import { getHourCycle } from "../datetime/utils/helpers";
12
12
  import { parseDate } from "../datetime/utils/parse";
13
13
  /**
@@ -43,11 +43,12 @@ export class DatetimeButton {
43
43
  * to the locale specified on ion-datetime.
44
44
  */
45
45
  this.setDateTimeText = () => {
46
+ var _a, _b, _c, _d, _e;
46
47
  const { datetimeEl, datetimePresentation } = this;
47
48
  if (!datetimeEl) {
48
49
  return;
49
50
  }
50
- const { value, locale, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl;
51
+ const { value, locale, formatOptions, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl;
51
52
  const parsedValues = this.getParsedDateValues(value);
52
53
  /**
53
54
  * Both ion-datetime and ion-datetime-button default
@@ -70,8 +71,8 @@ export class DatetimeButton {
70
71
  switch (datetimePresentation) {
71
72
  case 'date-time':
72
73
  case 'time-date':
73
- const dateText = getMonthDayAndYear(locale, firstParsedDatetime);
74
- const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle);
74
+ const dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _a !== void 0 ? _a : { month: 'short', day: 'numeric', year: 'numeric' });
75
+ const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time);
75
76
  if (preferWheel) {
76
77
  this.dateText = `${dateText} ${timeText}`;
77
78
  }
@@ -94,20 +95,20 @@ export class DatetimeButton {
94
95
  this.dateText = headerText;
95
96
  }
96
97
  else {
97
- this.dateText = getMonthDayAndYear(locale, firstParsedDatetime);
98
+ this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_b = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _b !== void 0 ? _b : { month: 'short', day: 'numeric', year: 'numeric' });
98
99
  }
99
100
  break;
100
101
  case 'time':
101
- this.timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle);
102
+ this.timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time);
102
103
  break;
103
104
  case 'month-year':
104
- this.dateText = getMonthAndYear(locale, firstParsedDatetime);
105
+ this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_c = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _c !== void 0 ? _c : { month: 'long', year: 'numeric' });
105
106
  break;
106
107
  case 'month':
107
- this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, { month: 'long' });
108
+ this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_d = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) !== null && _d !== void 0 ? _d : { month: 'long' });
108
109
  break;
109
110
  case 'year':
110
- this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, { year: 'numeric' });
111
+ this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_e = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) !== null && _e !== void 0 ? _e : { year: 'numeric' });
111
112
  break;
112
113
  }
113
114
  };
@@ -336,7 +337,7 @@ export class DatetimeButton {
336
337
  render() {
337
338
  const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this;
338
339
  const mode = getIonMode(this);
339
- return (h(Host, { key: '36fe34d4225940611d74bdef5b954d54749d2dca', class: createColorClasses(color, {
340
+ return (h(Host, { key: '861a08f4a239de771875aee9b505e06ac7064b32', class: createColorClasses(color, {
340
341
  [mode]: true,
341
342
  [`${selectedButton}-active`]: datetimeActive,
342
343
  ['datetime-button-disabled']: disabled,
@@ -174,4 +174,69 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
174
174
  await expect(page.locator('#time-button')).not.toBeVisible();
175
175
  });
176
176
  });
177
+ test.describe(title('datetime-button: formatOptions'), () => {
178
+ test('should include date and time for presentation date-time', async ({ page }) => {
179
+ await page.setContent(`
180
+ <ion-datetime-button datetime="datetime"></ion-datetime-button>
181
+ <ion-datetime id="datetime" presentation="date-time" value="2023-11-02T01:22:00" locale="en-US"></ion-datetime>
182
+ <script>
183
+ const datetime = document.querySelector('ion-datetime');
184
+ datetime.formatOptions = {
185
+ date: {
186
+ weekday: "short",
187
+ month: "long",
188
+ day: "2-digit"
189
+ },
190
+ time: {
191
+ hour: "2-digit",
192
+ minute: "2-digit"
193
+ }
194
+ }
195
+ </script>
196
+ `, config);
197
+ await page.locator('.datetime-ready').waitFor();
198
+ await expect(page.locator('#date-button')).toContainText('Thu, November 02');
199
+ await expect(page.locator('#time-button')).toContainText('01:22 AM');
200
+ });
201
+ test('should include date for presentation date', async ({ page }) => {
202
+ await page.setContent(`
203
+ <ion-datetime-button datetime="datetime"></ion-datetime-button>
204
+ <ion-datetime id="datetime" presentation="date" value="2023-11-02" locale="en-US"></ion-datetime>
205
+ <script>
206
+ const datetime = document.querySelector('ion-datetime');
207
+ datetime.formatOptions = {
208
+ date: {
209
+ weekday: "short",
210
+ month: "long",
211
+ day: "2-digit"
212
+ }
213
+ }
214
+ </script>
215
+ `, config);
216
+ await page.locator('.datetime-ready').waitFor();
217
+ await expect(page.locator('#date-button')).toContainText('Thu, November 02');
218
+ });
219
+ test('should include date and time in same button for preferWheel', async ({ page }) => {
220
+ await page.setContent(`
221
+ <ion-datetime-button datetime="datetime"></ion-datetime-button>
222
+ <ion-datetime id="datetime" presentation="date-time" value="2023-11-02T01:22:00" locale="en-US" prefer-wheel="true"></ion-datetime>
223
+ <script>
224
+ const datetime = document.querySelector('ion-datetime');
225
+ datetime.formatOptions = {
226
+ date: {
227
+ weekday: "short",
228
+ month: "long",
229
+ day: "2-digit"
230
+ },
231
+ time: {
232
+ hour: "2-digit",
233
+ minute: "2-digit"
234
+ }
235
+ }
236
+ </script>
237
+ `, config);
238
+ await page.locator('.datetime-ready').waitFor();
239
+ await expect(page.locator('ion-datetime-button')).toContainText('Thu, November 02 01:22 AM');
240
+ });
241
+ });
177
242
  });
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Host, forceUpdate, h } from "@stencil/core";
5
- import { debounceEvent, raf, componentOnReady } from "../../utils/helpers";
5
+ import { debounceEvent, raf, componentOnReady, inheritAttributes } from "../../utils/helpers";
6
6
  import { isRTL } from "../../utils/rtl/index";
7
7
  import { createColorClasses } from "../../utils/theme";
8
8
  import { arrowBackSharp, closeCircle, closeSharp, searchOutline, searchSharp } from "ionicons/icons";
@@ -16,6 +16,7 @@ export class Searchbar {
16
16
  this.isCancelVisible = false;
17
17
  this.shouldAlignLeft = true;
18
18
  this.inputId = `ion-searchbar-${searchbarIds++}`;
19
+ this.inheritedAttributes = {};
19
20
  /**
20
21
  * Clears the input field and triggers the control change.
21
22
  */
@@ -116,6 +117,7 @@ export class Searchbar {
116
117
  this.noAnimate = true;
117
118
  this.color = undefined;
118
119
  this.animated = false;
120
+ this.autocapitalize = undefined;
119
121
  this.autocomplete = 'off';
120
122
  this.autocorrect = 'off';
121
123
  this.cancelButtonIcon = config.get('backButtonIcon', arrowBackSharp);
@@ -125,6 +127,8 @@ export class Searchbar {
125
127
  this.disabled = false;
126
128
  this.inputmode = undefined;
127
129
  this.enterkeyhint = undefined;
130
+ this.maxlength = undefined;
131
+ this.minlength = undefined;
128
132
  this.name = this.inputId;
129
133
  this.placeholder = 'Search';
130
134
  this.searchIcon = undefined;
@@ -134,6 +138,21 @@ export class Searchbar {
134
138
  this.type = 'search';
135
139
  this.value = '';
136
140
  }
141
+ /**
142
+ * lang and dir are globally enumerated attributes.
143
+ * As a result, creating these as properties
144
+ * can have unintended side effects. Instead, we
145
+ * listen for attribute changes and inherit them
146
+ * to the inner `<input>` element.
147
+ */
148
+ onLangChanged(newValue) {
149
+ this.inheritedAttributes = Object.assign(Object.assign({}, this.inheritedAttributes), { lang: newValue });
150
+ forceUpdate(this);
151
+ }
152
+ onDirChanged(newValue) {
153
+ this.inheritedAttributes = Object.assign(Object.assign({}, this.inheritedAttributes), { dir: newValue });
154
+ forceUpdate(this);
155
+ }
137
156
  debounceChanged() {
138
157
  const { ionInput, debounce, originalIonInput } = this;
139
158
  /**
@@ -158,6 +177,9 @@ export class Searchbar {
158
177
  connectedCallback() {
159
178
  this.emitStyle();
160
179
  }
180
+ componentWillLoad() {
181
+ this.inheritedAttributes = Object.assign({}, inheritAttributes(this.el, ['lang', 'dir']));
182
+ }
161
183
  componentDidLoad() {
162
184
  this.originalIonInput = this.ionInput;
163
185
  this.positionElements();
@@ -356,8 +378,8 @@ export class Searchbar {
356
378
  const clearIcon = this.clearIcon || (mode === 'ios' ? closeCircle : closeSharp);
357
379
  const searchIcon = this.searchIcon || (mode === 'ios' ? searchOutline : searchSharp);
358
380
  const shouldShowCancelButton = this.shouldShowCancelButton();
359
- const cancelButton = this.showCancelButton !== 'never' && (h("button", { key: '1de9b9bc6bad708bafcf4d31fa82838355120486', "aria-label": cancelButtonText, "aria-hidden": shouldShowCancelButton ? undefined : 'true', type: "button", tabIndex: mode === 'ios' && !shouldShowCancelButton ? -1 : undefined, onMouseDown: this.onCancelSearchbar, onTouchStart: this.onCancelSearchbar, class: "searchbar-cancel-button" }, h("div", { key: 'e5178016e17104a80732edbae73c0dfc264bfe52', "aria-hidden": "true" }, mode === 'md' ? (h("ion-icon", { "aria-hidden": "true", mode: mode, icon: this.cancelButtonIcon, lazy: false })) : (cancelButtonText))));
360
- return (h(Host, { key: '2c88a7b6ec7f33ada7dae560881210d7a8800f76', role: "search", "aria-disabled": this.disabled ? 'true' : null, class: createColorClasses(this.color, {
381
+ const cancelButton = this.showCancelButton !== 'never' && (h("button", { key: '1a68689b5c14cde6e071c4095457146b9ac2e7ab', "aria-label": cancelButtonText, "aria-hidden": shouldShowCancelButton ? undefined : 'true', type: "button", tabIndex: mode === 'ios' && !shouldShowCancelButton ? -1 : undefined, onMouseDown: this.onCancelSearchbar, onTouchStart: this.onCancelSearchbar, class: "searchbar-cancel-button" }, h("div", { key: '25ae47914baa4b7e0281efb2b296d1799a56c764', "aria-hidden": "true" }, mode === 'md' ? (h("ion-icon", { "aria-hidden": "true", mode: mode, icon: this.cancelButtonIcon, lazy: false })) : (cancelButtonText))));
382
+ return (h(Host, { key: '2cf545c40874bc4a19b6af022f5ec41b3a913495', role: "search", "aria-disabled": this.disabled ? 'true' : null, class: createColorClasses(this.color, {
361
383
  [mode]: true,
362
384
  'searchbar-animated': animated,
363
385
  'searchbar-disabled': this.disabled,
@@ -367,14 +389,14 @@ export class Searchbar {
367
389
  'searchbar-has-focus': this.focused,
368
390
  'searchbar-should-show-clear': this.shouldShowClearButton(),
369
391
  'searchbar-should-show-cancel': this.shouldShowCancelButton(),
370
- }) }, h("div", { key: '8e914264a99453748a2bf370943e4047a0a587fb', class: "searchbar-input-container" }, h("input", { key: 'd772f3e8d42301e50fbd6c9deb332fbdf3c435ac', "aria-label": "search text", disabled: this.disabled, ref: (el) => (this.nativeInput = el), class: "searchbar-input", inputMode: this.inputmode, enterKeyHint: this.enterkeyhint, name: this.name, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, placeholder: this.placeholder, type: this.type, value: this.getValue(), autoComplete: this.autocomplete, autoCorrect: this.autocorrect, spellcheck: this.spellcheck }), mode === 'md' && cancelButton, h("ion-icon", { key: '8d9b461c2c34729b1bb562c227868cf817a353bc', "aria-hidden": "true", mode: mode, icon: searchIcon, lazy: false, class: "searchbar-search-icon" }), h("button", { key: 'bcc39be721f4b825af31aae4dfb3058722d889a8', "aria-label": "reset", type: "button", "no-blur": true, class: "searchbar-clear-button", onPointerDown: (ev) => {
392
+ }) }, h("div", { key: '9cf49297278839bbe6e2ef9936de337daa53c08d', class: "searchbar-input-container" }, h("input", Object.assign({ key: 'bae5db413a302e116012251746d599030f7158ce', "aria-label": "search text", disabled: this.disabled, ref: (el) => (this.nativeInput = el), class: "searchbar-input", inputMode: this.inputmode, enterKeyHint: this.enterkeyhint, name: this.name, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, minLength: this.minlength, maxLength: this.maxlength, placeholder: this.placeholder, type: this.type, value: this.getValue(), autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, spellcheck: this.spellcheck }, this.inheritedAttributes)), mode === 'md' && cancelButton, h("ion-icon", { key: '67c593ca842551d9e99e8239115cc7b43261b542', "aria-hidden": "true", mode: mode, icon: searchIcon, lazy: false, class: "searchbar-search-icon" }), h("button", { key: '6901c3f6c74cce2d3b7e46fbff85ee6e6399bdb1', "aria-label": "reset", type: "button", "no-blur": true, class: "searchbar-clear-button", onPointerDown: (ev) => {
371
393
  /**
372
394
  * This prevents mobile browsers from
373
395
  * blurring the input when the clear
374
396
  * button is activated.
375
397
  */
376
398
  ev.preventDefault();
377
- }, onClick: () => this.onClearInput(true) }, h("ion-icon", { key: '9a6478e1f1482aa6e62c88bd96cd19cacbfb8d60', "aria-hidden": "true", mode: mode, icon: clearIcon, lazy: false, class: "searchbar-clear-icon" }))), mode === 'ios' && cancelButton));
399
+ }, onClick: () => this.onClearInput(true) }, h("ion-icon", { key: 'abd45fb79ee807cf8352aa7bf5b0342f2d686227', "aria-hidden": "true", mode: mode, icon: clearIcon, lazy: false, class: "searchbar-clear-icon" }))), mode === 'ios' && cancelButton));
378
400
  }
379
401
  static get is() { return "ion-searchbar"; }
380
402
  static get encapsulation() { return "scoped"; }
@@ -433,6 +455,23 @@ export class Searchbar {
433
455
  "reflect": false,
434
456
  "defaultValue": "false"
435
457
  },
458
+ "autocapitalize": {
459
+ "type": "string",
460
+ "mutable": false,
461
+ "complexType": {
462
+ "original": "string",
463
+ "resolved": "string",
464
+ "references": {}
465
+ },
466
+ "required": true,
467
+ "optional": false,
468
+ "docs": {
469
+ "tags": [],
470
+ "text": "Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.\nAvailable options: `\"off\"`, `\"none\"`, `\"on\"`, `\"sentences\"`, `\"words\"`, `\"characters\"`."
471
+ },
472
+ "attribute": "autocapitalize",
473
+ "reflect": false
474
+ },
436
475
  "autocomplete": {
437
476
  "type": "string",
438
477
  "mutable": false,
@@ -597,6 +636,40 @@ export class Searchbar {
597
636
  "attribute": "enterkeyhint",
598
637
  "reflect": false
599
638
  },
639
+ "maxlength": {
640
+ "type": "number",
641
+ "mutable": false,
642
+ "complexType": {
643
+ "original": "number",
644
+ "resolved": "number | undefined",
645
+ "references": {}
646
+ },
647
+ "required": false,
648
+ "optional": true,
649
+ "docs": {
650
+ "tags": [],
651
+ "text": "This attribute specifies the maximum number of characters that the user can enter."
652
+ },
653
+ "attribute": "maxlength",
654
+ "reflect": false
655
+ },
656
+ "minlength": {
657
+ "type": "number",
658
+ "mutable": false,
659
+ "complexType": {
660
+ "original": "number",
661
+ "resolved": "number | undefined",
662
+ "references": {}
663
+ },
664
+ "required": false,
665
+ "optional": true,
666
+ "docs": {
667
+ "tags": [],
668
+ "text": "This attribute specifies the minimum number of characters that the user can enter."
669
+ },
670
+ "attribute": "minlength",
671
+ "reflect": false
672
+ },
600
673
  "name": {
601
674
  "type": "string",
602
675
  "mutable": false,
@@ -922,6 +995,12 @@ export class Searchbar {
922
995
  static get elementRef() { return "el"; }
923
996
  static get watchers() {
924
997
  return [{
998
+ "propName": "lang",
999
+ "methodName": "onLangChanged"
1000
+ }, {
1001
+ "propName": "dir",
1002
+ "methodName": "onDirChanged"
1003
+ }, {
925
1004
  "propName": "debounce",
926
1005
  "methodName": "debounceChanged"
927
1006
  }, {