diff --git a/CHANGELOG.md b/CHANGELOG.md index bd70be6b77..97b67e0580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + + +## Features + +* **datetime:** formatOptions property for Datetime ([#29065](https://github.com/ionic-team/ionic-framework/issues/29065)) ([7cdbc1b](https://github.com/ionic-team/ionic-framework/commit/7cdbc1b5ad004e17a7c51363653e0e67f50e6860)) +* **searchbar:** autocapitalize, dir, lang, maxlength, and minlength are inherited to native input ([#29098](https://github.com/ionic-team/ionic-framework/issues/29098)) ([a0a77f7](https://github.com/ionic-team/ionic-framework/commit/a0a77f799df0732d9f7182f15866035a3ce5a1eb)), closes [#27606](https://github.com/ionic-team/ionic-framework/issues/27606) + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + + +### Bug Fixes + +* **angular:** add ionNavWillChange and ionNavDidChange types for nav ([#29122](https://github.com/ionic-team/ionic-framework/issues/29122)) ([85b9d5c](https://github.com/ionic-team/ionic-framework/commit/85b9d5c35f626ffc273d220549b0126ddc1f7e4b)), closes [#29114](https://github.com/ionic-team/ionic-framework/issues/29114) +* **checkbox:** set aria-checked of indeterminate checkbox to 'mixed' ([#29115](https://github.com/ionic-team/ionic-framework/issues/29115)) ([b2d636f](https://github.com/ionic-team/ionic-framework/commit/b2d636f14dcd33313f6604cfd4a64b542c831b34)) +* **overlay:** do not hide overlay if toast is presented ([#29140](https://github.com/ionic-team/ionic-framework/issues/29140)) ([c0f5e5e](https://github.com/ionic-team/ionic-framework/commit/c0f5e5ebc0c9d45d71e10e09903b00b3ba8e6bba)), closes [#29139](https://github.com/ionic-team/ionic-framework/issues/29139) + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) @@ -28,6 +53,19 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline + + +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + + +### Bug Fixes + +* **modal:** ariaLabel and role are inherited when set via htmlAttributes ([#29099](https://github.com/ionic-team/ionic-framework/issues/29099)) ([de13633](https://github.com/ionic-team/ionic-framework/commit/de13633a182d963876434db773aa346833f956fd)) + + + + + # [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) @@ -96,17 +134,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - - -### Bug Fixes - -* **modal:** ariaLabel and role are inherited when set via htmlAttributes ([#29099](https://github.com/ionic-team/ionic-framework/issues/29099)) ([de13633](https://github.com/ionic-team/ionic-framework/commit/de13633a182d963876434db773aa346833f956fd)) - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) ### Bug Fixes diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index ab48142be8..244ff4bd1f 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + + +## Features + +* **datetime:** formatOptions property for Datetime ([#29065](https://github.com/ionic-team/ionic-framework/issues/29065)) ([7cdbc1b](https://github.com/ionic-team/ionic-framework/commit/7cdbc1b5ad004e17a7c51363653e0e67f50e6860)) +* **searchbar:** autocapitalize, dir, lang, maxlength, and minlength are inherited to native input ([#29098](https://github.com/ionic-team/ionic-framework/issues/29098)) ([a0a77f7](https://github.com/ionic-team/ionic-framework/commit/a0a77f799df0732d9f7182f15866035a3ce5a1eb)), closes [#27606](https://github.com/ionic-team/ionic-framework/issues/27606) + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + + +### Bug Fixes + +* **checkbox:** set aria-checked of indeterminate checkbox to 'mixed' ([#29115](https://github.com/ionic-team/ionic-framework/issues/29115)) ([b2d636f](https://github.com/ionic-team/ionic-framework/commit/b2d636f14dcd33313f6604cfd4a64b542c831b34)) +* **overlay:** do not hide overlay if toast is presented ([#29140](https://github.com/ionic-team/ionic-framework/issues/29140)) ([c0f5e5e](https://github.com/ionic-team/ionic-framework/commit/c0f5e5ebc0c9d45d71e10e09903b00b3ba8e6bba)), closes [#29139](https://github.com/ionic-team/ionic-framework/issues/29139) + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) @@ -28,6 +52,19 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline + + +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + + +### Bug Fixes + +* **modal:** ariaLabel and role are inherited when set via htmlAttributes ([#29099](https://github.com/ionic-team/ionic-framework/issues/29099)) ([de13633](https://github.com/ionic-team/ionic-framework/commit/de13633a182d963876434db773aa346833f956fd)) + + + + + # [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) @@ -42,11 +79,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * **toggle:** set switch icon color correctly ([#28812](https://github.com/ionic-team/ionic-framework/issues/28812)) ([749df5b](https://github.com/ionic-team/ionic-framework/commit/749df5bdcec95e718199c4915c2a762ee29cdefb)) -### chore - -* remove unused code ([#28503](https://github.com/ionic-team/ionic-framework/issues/28503)) ([5aafd68](https://github.com/ionic-team/ionic-framework/commit/5aafd68f03bb6daefa9bfe58ce68459c3068fc5d)) - - ### Code Refactoring * **checkbox:** remove legacy property and support for legacy syntax ([#29043](https://github.com/ionic-team/ionic-framework/issues/29043)) ([fb5ae5b](https://github.com/ionic-team/ionic-framework/commit/fb5ae5b07f98a3b62a35ab07192a0fc7898ecbea)) @@ -77,11 +109,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * **toggle:** update styles to iOS 17 specs ([#28722](https://github.com/ionic-team/ionic-framework/issues/28722)) ([0ce0693](https://github.com/ionic-team/ionic-framework/commit/0ce0693af1cd468192ea58a08106c704da04640c)) -### Reverts - -* Revert "chore(): add updated snapshots" ([613bd54](https://github.com/ionic-team/ionic-framework/commit/613bd54adf519cef74e30066d549bd2ccc011b6a)) - - ### BREAKING CHANGES * **range:** The `legacy` property and support for the legacy syntax, which involved placing an `ion-range` inside of an `ion-item` with an `ion-label`, have been removed from range. For more information on migrating from the legacy range syntax, refer to the [Range documentation](https://ionicframework.com/docs/api/range#migrating-from-legacy-range-syntax). @@ -98,17 +125,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - - -### Bug Fixes - -* **modal:** ariaLabel and role are inherited when set via htmlAttributes ([#29099](https://github.com/ionic-team/ionic-framework/issues/29099)) ([de13633](https://github.com/ionic-team/ionic-framework/commit/de13633a182d963876434db773aa346833f956fd)) - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) diff --git a/core/api.txt b/core/api.txt index 7add74f680..b6b11321b6 100644 --- a/core/api.txt +++ b/core/api.txt @@ -394,6 +394,7 @@ ion-datetime,prop,dayValues,number | number[] | string | undefined,undefined,fal ion-datetime,prop,disabled,boolean,false,false,false ion-datetime,prop,doneText,string,'Done',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 ion-datetime,prop,hourCycle,"h11" | "h12" | "h23" | "h24" | undefined,undefined,false,false ion-datetime,prop,hourValues,number | number[] | string | undefined,undefined,false,false @@ -1165,6 +1166,7 @@ ion-row,shadow ion-searchbar,scoped ion-searchbar,prop,animated,boolean,false,false,false +ion-searchbar,prop,autocapitalize,string,undefined,true,false ion-searchbar,prop,autocomplete,"name" | "email" | "tel" | "url" | "on" | "off" | "honorific-prefix" | "given-name" | "additional-name" | "family-name" | "honorific-suffix" | "nickname" | "username" | "new-password" | "current-password" | "one-time-code" | "organization-title" | "organization" | "street-address" | "address-line1" | "address-line2" | "address-line3" | "address-level4" | "address-level3" | "address-level2" | "address-level1" | "country" | "country-name" | "postal-code" | "cc-name" | "cc-given-name" | "cc-additional-name" | "cc-family-name" | "cc-number" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-csc" | "cc-type" | "transaction-currency" | "transaction-amount" | "language" | "bday" | "bday-day" | "bday-month" | "bday-year" | "sex" | "tel-country-code" | "tel-national" | "tel-area-code" | "tel-local" | "tel-extension" | "impp" | "photo",'off',false,false ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false ion-searchbar,prop,cancelButtonIcon,string,config.get('backButtonIcon', arrowBackSharp) as string,false,false @@ -1175,6 +1177,8 @@ ion-searchbar,prop,debounce,number | undefined,undefined,false,false ion-searchbar,prop,disabled,boolean,false,false,false ion-searchbar,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false ion-searchbar,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false +ion-searchbar,prop,maxlength,number | undefined,undefined,false,false +ion-searchbar,prop,minlength,number | undefined,undefined,false,false ion-searchbar,prop,mode,"ios" | "md",undefined,false,false ion-searchbar,prop,name,string,this.inputId,false,false ion-searchbar,prop,placeholder,string,'Search',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 3ff87de813..61f44b7dc1 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -15,7 +15,7 @@ import { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./compo import { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface"; import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface"; import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface"; -import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; +import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; import { SpinnerTypes } from "./components/spinner/spinner-configs"; import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; import { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; @@ -50,7 +50,7 @@ export { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./compo export { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface"; export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface"; export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface"; -export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; +export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; export { SpinnerTypes } from "./components/spinner/spinner-configs"; export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; export { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; @@ -852,6 +852,10 @@ export namespace Components { * The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday. */ "firstDayOfWeek": number; + /** + * Formatting options for dates and times. Should include a 'date' and/or 'time' object, each of which is of type [Intl.DateTimeFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options). + */ + "formatOptions"?: FormatOptions; /** * Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`. */ @@ -2523,6 +2527,10 @@ export namespace Components { * If `true`, enable searchbar animation. */ "animated": boolean; + /** + * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`. + */ + "autocapitalize": string; /** * Set the input's autocomplete property. */ @@ -2567,6 +2575,14 @@ export namespace Components { * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. */ "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + /** + * This attribute specifies the maximum number of characters that the user can enter. + */ + "maxlength"?: number; + /** + * This attribute specifies the minimum number of characters that the user can enter. + */ + "minlength"?: number; /** * The mode determines which platform styles to use. */ @@ -5500,6 +5516,10 @@ declare namespace LocalJSX { * The first day of the week to use for `ion-datetime`. The default value is `0` and represents Sunday. */ "firstDayOfWeek"?: number; + /** + * Formatting options for dates and times. Should include a 'date' and/or 'time' object, each of which is of type [Intl.DateTimeFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options). + */ + "formatOptions"?: FormatOptions; /** * Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`. */ @@ -7196,6 +7216,10 @@ declare namespace LocalJSX { * If `true`, enable searchbar animation. */ "animated"?: boolean; + /** + * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`. + */ + "autocapitalize": string; /** * Set the input's autocomplete property. */ @@ -7236,6 +7260,14 @@ declare namespace LocalJSX { * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. */ "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + /** + * This attribute specifies the maximum number of characters that the user can enter. + */ + "maxlength"?: number; + /** + * This attribute specifies the minimum number of characters that the user can enter. + */ + "minlength"?: number; /** * The mode determines which platform styles to use. */ diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index 44a67d2a15..d38b0bd37a 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -1,7 +1,7 @@ import type { ComponentInterface } from '@stencil/core'; import { Build, Component, Element, Host, Method, h } from '@stencil/core'; import type { FocusVisibleUtility } from '@utils/focus-visible'; -import { shoudUseCloseWatcher } from '@utils/hardware-back-button'; +import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import { printIonWarning } from '@utils/logging'; import { isPlatform } from '@utils/platform'; @@ -36,7 +36,7 @@ export class App implements ComponentInterface { import('../../utils/input-shims/input-shims').then((module) => module.startInputShims(config, platform)); } const hardwareBackButtonModule = await import('../../utils/hardware-back-button'); - const supportsHardwareBackButtonEvents = isHybrid || shoudUseCloseWatcher(); + const supportsHardwareBackButtonEvents = isHybrid || shouldUseCloseWatcher(); if (config.getBoolean('hardwareBackButton', supportsHardwareBackButtonEvents)) { hardwareBackButtonModule.startHardwareBackButton(); } else { @@ -44,7 +44,7 @@ export class App implements ComponentInterface { * If an app sets hardwareBackButton: false and experimentalCloseWatcher: true * then the close watcher will not be used. */ - if (shoudUseCloseWatcher()) { + if (shouldUseCloseWatcher()) { printIonWarning( 'experimentalCloseWatcher was set to `true`, but hardwareBackButton was set to `false`. Both config options must be `true` for the Close Watcher API to be used.' ); diff --git a/core/src/components/datetime-button/datetime-button.tsx b/core/src/components/datetime-button/datetime-button.tsx index c9adb812f6..957a2267f0 100644 --- a/core/src/components/datetime-button/datetime-button.tsx +++ b/core/src/components/datetime-button/datetime-button.tsx @@ -8,7 +8,7 @@ import { getIonMode } from '../../global/ionic-global'; import type { Color } from '../../interface'; import type { DatetimePresentation } from '../datetime/datetime-interface'; import { getToday } from '../datetime/utils/data'; -import { getMonthAndYear, getMonthDayAndYear, getLocalizedDateTime, getLocalizedTime } from '../datetime/utils/format'; +import { getLocalizedDateTime, getLocalizedTime } from '../datetime/utils/format'; import { getHourCycle } from '../datetime/utils/helpers'; import { parseDate } from '../datetime/utils/parse'; /** @@ -196,7 +196,7 @@ export class DatetimeButton implements ComponentInterface { return; } - const { value, locale, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl; + const { value, locale, formatOptions, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl; const parsedValues = this.getParsedDateValues(value); @@ -225,8 +225,12 @@ export class DatetimeButton implements ComponentInterface { switch (datetimePresentation) { case 'date-time': case 'time-date': - const dateText = getMonthDayAndYear(locale, firstParsedDatetime); - const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle); + const dateText = getLocalizedDateTime( + locale, + firstParsedDatetime, + formatOptions?.date ?? { month: 'short', day: 'numeric', year: 'numeric' } + ); + const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions?.time); if (preferWheel) { this.dateText = `${dateText} ${timeText}`; } else { @@ -246,20 +250,28 @@ export class DatetimeButton implements ComponentInterface { } this.dateText = headerText; } else { - this.dateText = getMonthDayAndYear(locale, firstParsedDatetime); + this.dateText = getLocalizedDateTime( + locale, + firstParsedDatetime, + formatOptions?.date ?? { month: 'short', day: 'numeric', year: 'numeric' } + ); } break; case 'time': - this.timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle); + this.timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions?.time); break; case 'month-year': - this.dateText = getMonthAndYear(locale, firstParsedDatetime); + this.dateText = getLocalizedDateTime( + locale, + firstParsedDatetime, + formatOptions?.date ?? { month: 'long', year: 'numeric' } + ); break; case 'month': - this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, { month: 'long' }); + this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, formatOptions?.time ?? { month: 'long' }); break; case 'year': - this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, { year: 'numeric' }); + this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, formatOptions?.time ?? { year: 'numeric' }); break; } }; diff --git a/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts b/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts index 9a4b90f884..35088bce4a 100644 --- a/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts +++ b/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts @@ -261,4 +261,87 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { await expect(page.locator('#time-button')).not.toBeVisible(); }); }); + + test.describe(title('datetime-button: formatOptions'), () => { + test('should include date and time for presentation date-time', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + await expect(page.locator('#date-button')).toContainText('Thu, November 02'); + await expect(page.locator('#time-button')).toContainText('01:22 AM'); + }); + + test('should include date for presentation date', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + await expect(page.locator('#date-button')).toContainText('Thu, November 02'); + }); + + test('should include date and time in same button for preferWheel', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + await expect(page.locator('ion-datetime-button')).toContainText('Thu, November 02 01:22 AM'); + }); + }); }); diff --git a/core/src/components/datetime-button/test/basic/index.html b/core/src/components/datetime-button/test/basic/index.html index c446544a0a..dd269d984e 100644 --- a/core/src/components/datetime-button/test/basic/index.html +++ b/core/src/components/datetime-button/test/basic/index.html @@ -215,8 +215,41 @@ > + +
+

formatOptions

+ + + Start Date + + + + + + +
+ + diff --git a/core/src/components/datetime/datetime-interface.ts b/core/src/components/datetime/datetime-interface.ts index 255f39e22d..475a672d06 100644 --- a/core/src/components/datetime/datetime-interface.ts +++ b/core/src/components/datetime/datetime-interface.ts @@ -36,3 +36,16 @@ export type DatetimeHighlight = { date: string } & DatetimeHighlightStyle; export type DatetimeHighlightCallback = (dateIsoString: string) => DatetimeHighlightStyle | undefined; export type DatetimeHourCycle = 'h11' | 'h12' | 'h23' | 'h24'; + +/** + * FormatOptions must include date and/or time; it cannot be an empty object + */ +export type FormatOptions = + | { + date: Intl.DateTimeFormatOptions; + time?: Intl.DateTimeFormatOptions; + } + | { + date?: Intl.DateTimeFormatOptions; + time: Intl.DateTimeFormatOptions; + }; diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index fea00902e8..a9c9ea7b84 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -19,6 +19,7 @@ import type { DatetimeHighlightStyle, DatetimeHighlightCallback, DatetimeHourCycle, + FormatOptions, } from './datetime-interface'; import { isSameDay, warnIfValueOutOfBounds, isBefore, isAfter } from './utils/comparison'; import type { WheelColumnOption } from './utils/data'; @@ -33,7 +34,7 @@ import { getTimeColumnsData, getCombinedDateColumnData, } from './utils/data'; -import { formatValue, getLocalizedTime, getMonthAndDay, getMonthAndYear } from './utils/format'; +import { formatValue, getLocalizedDateTime, getLocalizedTime, getMonthAndYear } from './utils/format'; import { isLocaleDayPeriodRTL, isMonthFirstLocale, getNumDaysInMonth, getHourCycle } from './utils/helpers'; import { calculateHourFromAMPM, @@ -68,6 +69,7 @@ import { isNextMonthDisabled, isPrevMonthDisabled, } from './utils/state'; +import { checkForPresentationFormatMismatch, warnIfTimeZoneProvided } from './utils/validate'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. @@ -170,6 +172,20 @@ export class Datetime implements ComponentInterface { */ @Prop() disabled = false; + /** + * Formatting options for dates and times. + * Should include a 'date' and/or 'time' object, each of which is of type [Intl.DateTimeFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options). + * + */ + @Prop() formatOptions?: FormatOptions; + + @Watch('formatOptions') + protected formatOptionsChanged() { + const { el, formatOptions, presentation } = this; + checkForPresentationFormatMismatch(el, presentation, formatOptions); + warnIfTimeZoneProvided(el, formatOptions); + } + /** * If `true`, the datetime appears normal but the selected date cannot be changed. */ @@ -234,6 +250,12 @@ export class Datetime implements ComponentInterface { */ @Prop() presentation: DatetimePresentation = 'date-time'; + @Watch('presentation') + protected presentationChanged() { + const { el, formatOptions, presentation } = this; + checkForPresentationFormatMismatch(el, presentation, formatOptions); + } + private get isGridStyle() { const { presentation, preferWheel } = this; const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date'; @@ -1356,7 +1378,7 @@ export class Datetime implements ComponentInterface { }; componentWillLoad() { - const { el, highlightedDates, multiple, presentation, preferWheel } = this; + const { el, formatOptions, highlightedDates, multiple, presentation, preferWheel } = this; if (multiple) { if (presentation !== 'date') { @@ -1381,6 +1403,11 @@ export class Datetime implements ComponentInterface { } } + if (formatOptions) { + checkForPresentationFormatMismatch(el, presentation, formatOptions); + warnIfTimeZoneProvided(el, formatOptions); + } + const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues)); const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues)); const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues)); @@ -2362,7 +2389,7 @@ export class Datetime implements ComponentInterface { } private renderTimeOverlay() { - const { disabled, hourCycle, isTimePopoverOpen, locale } = this; + const { disabled, hourCycle, isTimePopoverOpen, locale, formatOptions } = this; const computedHourCycle = getHourCycle(locale, hourCycle); const activePart = this.getActivePartsWithFallback(); @@ -2397,7 +2424,7 @@ export class Datetime implements ComponentInterface { } }} > - {getLocalizedTime(locale, activePart, computedHourCycle)} + {getLocalizedTime(locale, activePart, computedHourCycle, formatOptions?.time)} , { }); }); }); + +/** + * This behavior does not differ across + * directions. + */ +configs({ directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('datetime: formatOptions'), () => { + test('should format header and time button', async ({ page }) => { + await page.setContent( + ` + + Select Date + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + const headerDate = page.locator('ion-datetime .datetime-selected-date'); + await expect(headerDate).toHaveText('February 01 AD'); + + const timeBody = page.locator('ion-datetime .time-body'); + await expect(timeBody).toHaveText('04:30 PM'); + }); + }); +}); + +/** + * This behavior does not differ across + * modes/directions. + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('datetime: formatOptions misconfiguration errors'), () => { + test('should log a warning if time zone is provided', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (msg) => { + if (msg.type() === 'warning') { + logs.push(msg.text()); + } + }); + + await page.setContent( + ` + + Select Date + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + expect(logs.length).toBe(1); + expect(logs[0]).toContain( + '[Ionic Warning]: Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".' + ); + }); + + test('should log a warning if the required formatOptions are not provided for a presentation', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (msg) => { + if (msg.type() === 'warning') { + logs.push(msg.text()); + } + }); + + await page.setContent( + ` + + Select Date + + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + expect(logs.length).toBe(1); + expect(logs[0]).toContain( + "[Ionic Warning]: Datetime: The 'date-time' presentation requires either a date or time object (or both) in formatOptions." + ); + }); + }); +}); diff --git a/core/src/components/datetime/test/basic/index.html b/core/src/components/datetime/test/basic/index.html index 2023b4a119..b3afa76688 100644 --- a/core/src/components/datetime/test/basic/index.html +++ b/core/src/components/datetime/test/basic/index.html @@ -308,6 +308,13 @@ + +
+

formatOptions

+ + Select Date + +
diff --git a/core/src/components/datetime/test/format.spec.ts b/core/src/components/datetime/test/format.spec.ts index 5ff218167d..7161a1aeae 100644 --- a/core/src/components/datetime/test/format.spec.ts +++ b/core/src/components/datetime/test/format.spec.ts @@ -1,12 +1,12 @@ import type { DatetimeParts } from '../datetime-interface'; import { generateDayAriaLabel, - getMonthAndDay, getFormattedHour, addTimePadding, getMonthAndYear, getLocalizedDayPeriod, getLocalizedTime, + stripTimeZone, } from '../utils/format'; describe('generateDayAriaLabel()', () => { @@ -37,24 +37,6 @@ describe('generateDayAriaLabel()', () => { }); }); -describe('getMonthAndDay()', () => { - it('should return Tue, May 11', () => { - expect(getMonthAndDay('en-US', { month: 5, day: 11, year: 2021 })).toEqual('Tue, May 11'); - }); - - it('should return mar, 11 may', () => { - expect(getMonthAndDay('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mar, 11 may'); - }); - - it('should return Sat, Apr 1', () => { - expect(getMonthAndDay('en-US', { month: 4, day: 1, year: 2006 })).toEqual('Sat, Apr 1'); - }); - - it('should return sáb, 1 abr', () => { - expect(getMonthAndDay('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('sáb, 1 abr'); - }); -}); - describe('getFormattedHour()', () => { it('should only add padding if using 24 hour time', () => { expect(getFormattedHour(1, 'h11')).toEqual('1'); @@ -144,6 +126,7 @@ describe('getLocalizedTime', () => { expect(getLocalizedTime('en-GB', datetimeParts, 'h12')).toEqual('12:00 am'); }); + it('should parse time-only values correctly', () => { const datetimeParts: Partial = { hour: 22, @@ -153,4 +136,79 @@ describe('getLocalizedTime', () => { expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h12')).toEqual('10:40 PM'); expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h23')).toEqual('22:40'); }); + + it('should use formatOptions', () => { + const datetimeParts: DatetimeParts = { + day: 1, + month: 1, + year: 2022, + hour: 9, + minute: 40, + }; + + const formatOptions: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + dayPeriod: 'short', + day: '2-digit', + }; + + // Even though this method is intended to be used for time, the date may be displayed as well when passing formatOptions + expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('01, 09:40 in the morning'); + }); + + it('should override provided time zone with UTC', () => { + const datetimeParts: DatetimeParts = { + day: 1, + month: 1, + year: 2022, + hour: 9, + minute: 40, + }; + + const formatOptions: Intl.DateTimeFormatOptions = { + timeZone: 'Australia/Sydney', + timeZoneName: 'long', + hour: 'numeric', + minute: 'numeric', + }; + + expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('9:40 AM'); + }); + + it('should not include time zone name', () => { + const datetimeParts: DatetimeParts = { + day: 1, + month: 1, + year: 2022, + hour: 9, + minute: 40, + }; + + const formatOptions: Intl.DateTimeFormatOptions = { + timeZone: 'America/Los_Angeles', + timeZoneName: 'long', + hour: 'numeric', + minute: 'numeric', + }; + + expect(getLocalizedTime('en-US', datetimeParts, 'h12', formatOptions)).toEqual('9:40 AM'); + }); +}); + +describe('stripTimeZone', () => { + it('should remove the time zone name from the options and set the time zone to UTC', () => { + const formatOptions: Intl.DateTimeFormatOptions = { + timeZone: 'America/Los_Angeles', + timeZoneName: 'long', + hour: 'numeric', + minute: 'numeric', + }; + + expect(stripTimeZone(formatOptions)).toEqual({ + timeZone: 'UTC', + hour: 'numeric', + minute: 'numeric', + }); + }); }); diff --git a/core/src/components/datetime/utils/format.ts b/core/src/components/datetime/utils/format.ts index 0f70299dc5..6443e1b546 100644 --- a/core/src/components/datetime/utils/format.ts +++ b/core/src/components/datetime/utils/format.ts @@ -11,7 +11,33 @@ const getFormattedDayPeriod = (dayPeriod?: string) => { return dayPeriod.toUpperCase(); }; -export const getLocalizedTime = (locale: string, refParts: DatetimeParts, hourCycle: DatetimeHourCycle): string => { +/** + * Including time zone options may lead to the rendered text showing a + * different time from what was selected in the Datetime, which could cause + * confusion. + */ +export const stripTimeZone = (formatOptions: Intl.DateTimeFormatOptions): Intl.DateTimeFormatOptions => { + return { + ...formatOptions, + /** + * Setting the time zone to UTC ensures that the value shown is always the + * same as what was selected and safeguards against older Safari bugs with + * Intl.DateTimeFormat. + */ + timeZone: 'UTC', + /** + * We do not want to display the time zone name + */ + timeZoneName: undefined, + }; +}; + +export const getLocalizedTime = ( + locale: string, + refParts: DatetimeParts, + hourCycle: DatetimeHourCycle, + formatOptions: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric' } +): string => { const timeParts: Pick = { hour: refParts.hour, minute: refParts.minute, @@ -22,15 +48,7 @@ export const getLocalizedTime = (locale: string, refParts: DatetimeParts, hourCy } return new Intl.DateTimeFormat(locale, { - hour: 'numeric', - minute: 'numeric', - /** - * Setting the timeZone to UTC prevents - * new Intl.DatetimeFormat from subtracting - * the user's current timezone offset - * when formatting the time. - */ - timeZone: 'UTC', + ...stripTimeZone(formatOptions), /** * We use hourCycle here instead of hour12 due to: * https://bugs.chromium.org/p/chromium/issues/detail?id=1347316&q=hour12&can=2 @@ -146,17 +164,6 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D return today ? `Today, ${labelString}` : labelString; }; -/** - * Gets the day of the week, month, and day - * Used for the header in MD mode. - */ -export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => { - const date = getNormalizedDate(refParts); - return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format( - date - ); -}; - /** * Given a locale and a date object, * return a formatted string that includes @@ -168,16 +175,6 @@ export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => { return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date); }; -/** - * Given a locale and a date object, - * return a formatted string that includes - * the short month, numeric day, and full year. - * Example: Apr 22, 2021 - */ -export const getMonthDayAndYear = (locale: string, refParts: DatetimeParts) => { - return getLocalizedDateTime(locale, refParts, { month: 'short', day: 'numeric', year: 'numeric' }); -}; - /** * Given a locale and a date object, * return a formatted string that includes @@ -235,7 +232,7 @@ export const getLocalizedDateTime = ( options: Intl.DateTimeFormatOptions ): string => { const date = getNormalizedDate(refParts); - return getDateTimeFormat(locale, options).format(date); + return getDateTimeFormat(locale, stripTimeZone(options)).format(date); }; /** diff --git a/core/src/components/datetime/utils/validate.ts b/core/src/components/datetime/utils/validate.ts new file mode 100644 index 0000000000..46c3fe633b --- /dev/null +++ b/core/src/components/datetime/utils/validate.ts @@ -0,0 +1,54 @@ +import { printIonWarning } from '@utils/logging'; + +import type { DatetimePresentation, FormatOptions } from '../datetime-interface'; + +/** + * If a time zone is provided in the format options, the rendered text could + * differ from what was selected in the Datetime, which could cause + * confusion. + */ +export const warnIfTimeZoneProvided = (el: HTMLElement, formatOptions?: FormatOptions) => { + if ( + formatOptions?.date?.timeZone || + formatOptions?.date?.timeZoneName || + formatOptions?.time?.timeZone || + formatOptions?.time?.timeZoneName + ) { + printIonWarning('Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".', el); + } +}; + +export const checkForPresentationFormatMismatch = ( + el: HTMLElement, + presentation: DatetimePresentation, + formatOptions?: FormatOptions +) => { + // formatOptions is not required + if (!formatOptions) return; + + // If formatOptions is provided, the date and/or time objects are required, depending on the presentation + switch (presentation) { + case 'date': + case 'month-year': + case 'month': + case 'year': + if (formatOptions.date === undefined) { + printIonWarning(`Datetime: The '${presentation}' presentation requires a date object in formatOptions.`, el); + } + break; + case 'time': + if (formatOptions.time === undefined) { + printIonWarning(`Datetime: The 'time' presentation requires a time object in formatOptions.`, el); + } + break; + case 'date-time': + case 'time-date': + if (formatOptions.date === undefined && formatOptions.time === undefined) { + printIonWarning( + `Datetime: The '${presentation}' presentation requires either a date or time object (or both) in formatOptions.`, + el + ); + } + break; + } +}; diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 6357ec7222..622d1d3e89 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -3,7 +3,7 @@ import { Build, Component, Element, Event, Host, Listen, Method, Prop, State, Wa import { getTimeGivenProgression } from '@utils/animation/cubic-bezier'; import { focusFirstDescendant, focusLastDescendant } from '@utils/focus-trap'; import { GESTURE_CONTROLLER } from '@utils/gesture'; -import { shoudUseCloseWatcher } from '@utils/hardware-back-button'; +import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers'; import { menuController } from '@utils/menu-controller'; @@ -782,7 +782,7 @@ export class Menu implements ComponentInterface, MenuI { */ return ( ; private inputId = `ion-searchbar-${searchbarIds++}`; + private inheritedAttributes: Attributes = {}; /** * The value of the input when the textarea is focused. @@ -39,6 +41,31 @@ export class Searchbar implements ComponentInterface { @State() focused = false; @State() noAnimate = true; + /** + * lang and dir are globally enumerated attributes. + * As a result, creating these as properties + * can have unintended side effects. Instead, we + * listen for attribute changes and inherit them + * to the inner `` element. + */ + @Watch('lang') + onLangChanged(newValue: string) { + this.inheritedAttributes = { + ...this.inheritedAttributes, + lang: newValue, + }; + forceUpdate(this); + } + + @Watch('dir') + onDirChanged(newValue: string) { + this.inheritedAttributes = { + ...this.inheritedAttributes, + dir: newValue, + }; + forceUpdate(this); + } + /** * The color to use from your application's color palette. * Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. @@ -51,6 +78,27 @@ export class Searchbar implements ComponentInterface { */ @Prop() animated = false; + /** + * Prior to the addition of this property + * autocapitalize was enabled by default on iOS + * and disabled by default on Android + * for Searchbar. The autocapitalize type on HTMLElement + * requires that it be a string and never undefined. + * However, setting it to a string value would be a breaking change + * in behavior, so we use "!" to tell TypeScript that this property + * is always defined so we can rely on the browser defaults. Browsers + * will automatically set a default value if the developer does not set one. + * + * In the future, this property will default to "off" to align with + * Input and Textarea, and the "!" will not be needed. + */ + + /** + * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. + * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`. + */ + @Prop() autocapitalize!: string; + /** * Set the input's autocomplete property. */ @@ -112,6 +160,16 @@ export class Searchbar implements ComponentInterface { */ @Prop() enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + /** + * This attribute specifies the maximum number of characters that the user can enter. + */ + @Prop() maxlength?: number; + + /** + * This attribute specifies the minimum number of characters that the user can enter. + */ + @Prop() minlength?: number; + /** * If used in a form, set the name of the control, which is submitted with the form data. */ @@ -232,6 +290,12 @@ export class Searchbar implements ComponentInterface { this.emitStyle(); } + componentWillLoad() { + this.inheritedAttributes = { + ...inheritAttributes(this.el, ['lang', 'dir']), + }; + } + componentDidLoad() { this.originalIonInput = this.ionInput; this.positionElements(); @@ -614,12 +678,16 @@ export class Searchbar implements ComponentInterface { 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} diff --git a/core/src/components/searchbar/test/searchbar.spec.ts b/core/src/components/searchbar/test/searchbar.spec.ts index ad3a1f9137..0622a69ace 100644 --- a/core/src/components/searchbar/test/searchbar.spec.ts +++ b/core/src/components/searchbar/test/searchbar.spec.ts @@ -3,13 +3,37 @@ import { newSpecPage } from '@stencil/core/testing'; import { Searchbar } from '../searchbar'; describe('searchbar: rendering', () => { - it('should inherit attributes', async () => { + it('should inherit properties on load', async () => { const page = await newSpecPage({ components: [Searchbar], - html: '', + html: '', }); const nativeEl = page.body.querySelector('ion-searchbar input')!; expect(nativeEl.getAttribute('name')).toBe('search'); + expect(nativeEl.getAttribute('maxlength')).toBe('4'); + expect(nativeEl.getAttribute('minlength')).toBe('2'); + expect(nativeEl.getAttribute('autocapitalize')).toBe('off'); + }); + + it('should inherit watched attributes', async () => { + const page = await newSpecPage({ + components: [Searchbar], + html: '', + }); + + const searchbarEl = page.body.querySelector('ion-searchbar')!; + const nativeEl = searchbarEl.querySelector('input')!; + + expect(nativeEl.getAttribute('lang')).toBe('en-US'); + expect(nativeEl.getAttribute('dir')).toBe('ltr'); + + searchbarEl.setAttribute('lang', 'es-ES'); + searchbarEl.setAttribute('dir', 'rtl'); + + await page.waitForChanges(); + + expect(nativeEl.getAttribute('lang')).toBe('es-ES'); + expect(nativeEl.getAttribute('dir')).toBe('rtl'); }); }); diff --git a/core/src/utils/hardware-back-button.ts b/core/src/utils/hardware-back-button.ts index 1005497faa..dbd5d70783 100644 --- a/core/src/utils/hardware-back-button.ts +++ b/core/src/utils/hardware-back-button.ts @@ -30,7 +30,7 @@ interface HandlerRegister { * moment this file is evaluated which could be * before the config is set. */ -export const shoudUseCloseWatcher = () => +export const shouldUseCloseWatcher = () => config.get('experimentalCloseWatcher', false) && win !== undefined && 'CloseWatcher' in win; /** @@ -109,7 +109,7 @@ export const startHardwareBackButton = () => { * backbutton event otherwise we may get duplicate * events firing. */ - if (shoudUseCloseWatcher()) { + if (shouldUseCloseWatcher()) { let watcher: CloseWatcher | undefined; const configureWatcher = () => { diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 405f0c547e..cf1761ce28 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -1,7 +1,7 @@ import { doc } from '@utils/browser'; import { focusFirstDescendant, focusLastDescendant, focusableQueryString } from '@utils/focus-trap'; import type { BackButtonEvent } from '@utils/hardware-back-button'; -import { shoudUseCloseWatcher } from '@utils/hardware-back-button'; +import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import { config } from '../global/config'; import { getIonMode } from '../global/ionic-global'; @@ -398,7 +398,7 @@ const connectListeners = (doc: Document) => { * this behavior will be handled via the ionBackButton * event. */ - if (!shoudUseCloseWatcher()) { + if (!shouldUseCloseWatcher()) { doc.addEventListener('keydown', (ev) => { if (ev.key === 'Escape') { const lastOverlay = getPresentedOverlay(doc); @@ -514,15 +514,7 @@ export const present = async ( document.body.classList.add(BACKDROP_NO_SCROLL); - /** - * Hide all other overlays from screen readers so only this one - * can be read. Note that presenting an overlay always makes - * it the topmost one. - */ - if (doc !== undefined) { - const presentedOverlays = getPresentedOverlays(doc); - presentedOverlays.forEach((o) => o.setAttribute('aria-hidden', 'true')); - } + hideOverlaysFromScreenReaders(overlay.el); overlay.presented = true; overlay.willPresent.emit(); @@ -699,13 +691,7 @@ export const dismiss = async ( overlay.el.remove(); - /** - * If there are other overlays presented, unhide the new - * topmost one from screen readers. - */ - if (doc !== undefined) { - getPresentedOverlay(doc)?.removeAttribute('aria-hidden'); - } + revealOverlaysToScreenReaders(); return true; }; @@ -942,3 +928,65 @@ export const createTriggerController = () => { removeClickListener, }; }; + +/** + * Ensure that underlying overlays have aria-hidden if necessary so that screen readers + * cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout + * events here because those events do not fire when the screen readers moves to a non-focusable + * element such as text. + * Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay. + * + * @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been + * fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result. + */ +const hideOverlaysFromScreenReaders = (newTopMostOverlay: HTMLIonOverlayElement) => { + if (doc === undefined) return; + + const overlays = getPresentedOverlays(doc); + + for (let i = overlays.length - 1; i >= 0; i--) { + const presentedOverlay = overlays[i]; + const nextPresentedOverlay = overlays[i + 1] ?? newTopMostOverlay; + + /** + * If next overlay has aria-hidden then all remaining overlays will have it too. + * Or, if the next overlay is a Toast that does not have aria-hidden then current overlay + * should not have aria-hidden either so focus can remain in the current overlay. + */ + if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') { + presentedOverlay.setAttribute('aria-hidden', 'true'); + } + } +}; + +/** + * When dismissing an overlay we need to reveal the new top-most overlay to screen readers. + * If the top-most overlay is a Toast we potentially need to reveal more overlays since + * focus is never automatically moved to the Toast. + */ +const revealOverlaysToScreenReaders = () => { + if (doc === undefined) return; + + const overlays = getPresentedOverlays(doc); + + for (let i = overlays.length - 1; i >= 0; i--) { + const currentOverlay = overlays[i]; + + /** + * If the current we are looking at is a Toast then we can remove aria-hidden. + * However, we potentially need to keep looking at the overlay stack because there + * could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast + * overlay too so focus can move there since focus is never automatically moved to the Toast. + */ + currentOverlay.removeAttribute('aria-hidden'); + + /** + * If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely + * since this overlay should always receive focus. As a result, all underlying overlays should still + * be hidden from screen readers. + */ + if (currentOverlay.tagName !== 'ION-TOAST') { + break; + } + } +}; diff --git a/core/src/utils/test/overlays/overlays.spec.ts b/core/src/utils/test/overlays/overlays.spec.ts index 7b67a22183..29a77c3c26 100644 --- a/core/src/utils/test/overlays/overlays.spec.ts +++ b/core/src/utils/test/overlays/overlays.spec.ts @@ -1,6 +1,7 @@ import { newSpecPage } from '@stencil/core/testing'; import { Modal } from '../../../components/modal/modal'; +import { Toast } from '../../../components/toast/toast'; import { Nav } from '../../../components/nav/nav'; import { RouterOutlet } from '../../../components/router-outlet/router-outlet'; import { setRootAriaHidden } from '../../overlays'; @@ -193,4 +194,70 @@ describe('aria-hidden on individual overlays', () => { await modalOne.present(); expect(modalOne.hasAttribute('aria-hidden')).toEqual(false); }); + + it('should not hide previous overlay if top-most overlay is toast', async () => { + const page = await newSpecPage({ + components: [Modal, Toast], + html: ` + + + + + `, + }); + + const modalOne = page.body.querySelector('ion-modal#m-one')!; + const modalTwo = page.body.querySelector('ion-modal#m-two')!; + const toastOne = page.body.querySelector('ion-toast#t-one')!; + const toastTwo = page.body.querySelector('ion-toast#t-two')!; + + await modalOne.present(); + await modalTwo.present(); + await toastOne.present(); + await toastTwo.present(); + + expect(modalOne.hasAttribute('aria-hidden')).toEqual(true); + expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false); + expect(toastOne.hasAttribute('aria-hidden')).toEqual(false); + expect(toastTwo.hasAttribute('aria-hidden')).toEqual(false); + + await toastTwo.dismiss(); + + expect(modalOne.hasAttribute('aria-hidden')).toEqual(true); + expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false); + expect(toastOne.hasAttribute('aria-hidden')).toEqual(false); + + await toastOne.dismiss(); + + expect(modalOne.hasAttribute('aria-hidden')).toEqual(true); + expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false); + }); + + it('should hide previous overlay even with a toast that is not the top-most overlay', async () => { + const page = await newSpecPage({ + components: [Modal, Toast], + html: ` + + + + `, + }); + + const modalOne = page.body.querySelector('ion-modal#m-one')!; + const modalTwo = page.body.querySelector('ion-modal#m-two')!; + const toastOne = page.body.querySelector('ion-toast#t-one')!; + + await modalOne.present(); + await toastOne.present(); + await modalTwo.present(); + + expect(modalOne.hasAttribute('aria-hidden')).toEqual(true); + expect(toastOne.hasAttribute('aria-hidden')).toEqual(true); + expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false); + + await modalTwo.dismiss(); + + expect(modalOne.hasAttribute('aria-hidden')).toEqual(false); + expect(toastOne.hasAttribute('aria-hidden')).toEqual(false); + }); }); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index adf7d60c7d..69670afe6e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + +**Note:** Version bump only for package @ionic/docs + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) **Note:** Version bump only for package @ionic/docs @@ -11,7 +19,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) **Note:** Version bump only for package @ionic/docs @@ -27,6 +35,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/docs diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index aff725e617..bd419afcf9 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,8 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + +**Note:** Version bump only for package @ionic/angular-server + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + +**Note:** Version bump only for package @ionic/angular-server + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) + +**Note:** Version bump only for package @ionic/angular-server + + + + + +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + **Note:** Version bump only for package @ionic/angular-server @@ -22,14 +47,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - -**Note:** Version bump only for package @ionic/angular-server - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 4623e0f278..f570d0965c 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + + +### Features + +* **datetime:** formatOptions property for Datetime ([#29065](https://github.com/ionic-team/ionic-framework/issues/29065)) ([7cdbc1b](https://github.com/ionic-team/ionic-framework/commit/7cdbc1b5ad004e17a7c51363653e0e67f50e6860)) +* **searchbar:** autocapitalize, dir, lang, maxlength, and minlength are inherited to native input ([#29098](https://github.com/ionic-team/ionic-framework/issues/29098)) ([a0a77f7](https://github.com/ionic-team/ionic-framework/commit/a0a77f799df0732d9f7182f15866035a3ce5a1eb)), closes [#27606](https://github.com/ionic-team/ionic-framework/issues/27606) + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + + +### Bug Fixes + +* **angular:** add ionNavWillChange and ionNavDidChange types for nav ([#29122](https://github.com/ionic-team/ionic-framework/issues/29122)) ([85b9d5c](https://github.com/ionic-team/ionic-framework/commit/85b9d5c35f626ffc273d220549b0126ddc1f7e4b)), closes [#29114](https://github.com/ionic-team/ionic-framework/issues/29114) + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) **Note:** Version bump only for package @ionic/angular @@ -11,6 +34,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + +**Note:** Version bump only for package @ionic/angular + + + + + # [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) @@ -52,14 +83,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - -**Note:** Version bump only for package @ionic/angular - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index a3c9f13db1..d7eaecc6d8 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -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', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', '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: '', // 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', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'], }) export class IonDatetime { protected el: HTMLElement; @@ -1863,7 +1863,7 @@ export declare interface IonRow extends Components.IonRow {} @ProxyCmp({ - inputs: ['animated', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'], + inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'], methods: ['setFocus', 'getInputElement'] }) @Component({ @@ -1871,7 +1871,7 @@ export declare interface IonRow extends Components.IonRow {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'], + inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'], }) export class IonSearchbar { protected el: HTMLElement; diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index e0de429190..267d6869bd 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) **Note:** Version bump only for package @ionic/react-router @@ -11,7 +11,16 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + +**Note:** Version bump only for package @ionic/react-router + + + + + +# [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) + **Note:** Version bump only for package @ionic/react-router @@ -27,6 +36,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index a3320b2b34..23e314de03 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -667,9 +667,9 @@ ] }, "node_modules/@stencil/core": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.5.tgz", - "integrity": "sha512-vSyFjY7XSEx0ufa9SebOd437CvnneaTXlCpuGDhjUDxAjGBlu6ie5qHyubobVGBth//aErc6wZPHc6W75Vp3iQ==", + "version": "4.12.6", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.6.tgz", + "integrity": "sha512-15JO2TdaxGVKNdLZb/2TtDa+juj3XGD/V0y/disgdzYYSnajgSh06nwODfdHz9eTUh1Hisz+KIo857I1rCZrfg==", "bin": { "stencil": "bin/stencil" }, @@ -4297,9 +4297,9 @@ "optional": true }, "@stencil/core": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.5.tgz", - "integrity": "sha512-vSyFjY7XSEx0ufa9SebOd437CvnneaTXlCpuGDhjUDxAjGBlu6ie5qHyubobVGBth//aErc6wZPHc6W75Vp3iQ==" + "version": "4.12.6", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.6.tgz", + "integrity": "sha512-15JO2TdaxGVKNdLZb/2TtDa+juj3XGD/V0y/disgdzYYSnajgSh06nwODfdHz9eTUh1Hisz+KIo857I1rCZrfg==" }, "@types/estree": { "version": "1.0.4", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 627b8a3df1..bc59559fb5 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + +**Note:** Version bump only for package @ionic/react + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + +**Note:** Version bump only for package @ionic/react + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) **Note:** Version bump only for package @ionic/react @@ -11,6 +27,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + +**Note:** Version bump only for package @ionic/react + + + + + # [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) @@ -22,14 +46,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - -**Note:** Version bump only for package @ionic/react - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/react diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index eb6406705e..037e051863 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + +**Note:** Version bump only for package @ionic/vue-router + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + +**Note:** Version bump only for package @ionic/vue-router + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) **Note:** Version bump only for package @ionic/vue-router @@ -11,7 +27,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) **Note:** Version bump only for package @ionic/vue-router @@ -19,7 +35,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) +# [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 2e010a82bb..a5a83e91b0 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1508,9 +1508,9 @@ } }, "node_modules/@stencil/core": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.5.tgz", - "integrity": "sha512-vSyFjY7XSEx0ufa9SebOd437CvnneaTXlCpuGDhjUDxAjGBlu6ie5qHyubobVGBth//aErc6wZPHc6W75Vp3iQ==", + "version": "4.12.6", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.6.tgz", + "integrity": "sha512-15JO2TdaxGVKNdLZb/2TtDa+juj3XGD/V0y/disgdzYYSnajgSh06nwODfdHz9eTUh1Hisz+KIo857I1rCZrfg==", "bin": { "stencil": "bin/stencil" }, @@ -8461,9 +8461,9 @@ } }, "@stencil/core": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.5.tgz", - "integrity": "sha512-vSyFjY7XSEx0ufa9SebOd437CvnneaTXlCpuGDhjUDxAjGBlu6ie5qHyubobVGBth//aErc6wZPHc6W75Vp3iQ==" + "version": "4.12.6", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.6.tgz", + "integrity": "sha512-15JO2TdaxGVKNdLZb/2TtDa+juj3XGD/V0y/disgdzYYSnajgSh06nwODfdHz9eTUh1Hisz+KIo857I1rCZrfg==" }, "@tootallnate/once": { "version": "2.0.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 31fe9457a4..78a4465102 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.8.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.5...v7.8.0) (2024-03-13) + + +### Features + +* **datetime:** formatOptions property for Datetime ([#29065](https://github.com/ionic-team/ionic-framework/issues/29065)) ([7cdbc1b](https://github.com/ionic-team/ionic-framework/commit/7cdbc1b5ad004e17a7c51363653e0e67f50e6860)) +* **searchbar:** autocapitalize, dir, lang, maxlength, and minlength are inherited to native input ([#29098](https://github.com/ionic-team/ionic-framework/issues/29098)) ([a0a77f7](https://github.com/ionic-team/ionic-framework/commit/a0a77f799df0732d9f7182f15866035a3ce5a1eb)), closes [#27606](https://github.com/ionic-team/ionic-framework/issues/27606) + + + + + +## [7.7.5](https://github.com/ionic-team/ionic-framework/compare/v7.7.4...v7.7.5) (2024-03-13) + +**Note:** Version bump only for package @ionic/vue + + + + + # [8.0.0-beta.1](https://github.com/ionic-team/ionic-framework/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2024-03-06) **Note:** Version bump only for package @ionic/vue @@ -11,6 +32,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline +## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) + +**Note:** Version bump only for package @ionic/vue + + + + + # [8.0.0-beta.0](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v8.0.0-beta.0) (2024-02-28) @@ -41,14 +70,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline -## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) - -**Note:** Version bump only for package @ionic/vue - - - - - ## [7.7.3](https://github.com/ionic-team/ionic-framework/compare/v7.7.2...v7.7.3) (2024-02-21) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index acb0ac9f27..c315669621 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -275,6 +275,7 @@ export const IonDatetime = /*@__PURE__*/ defineContainer('ion-row', defin export const IonSearchbar = /*@__PURE__*/ defineContainer('ion-searchbar', defineIonSearchbar, [ 'color', 'animated', + 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', @@ -695,6 +697,8 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer