diff --git a/.github/COMPONENT-GUIDE.md b/.github/COMPONENT-GUIDE.md index 15c74b82e4..24af57fdce 100644 --- a/.github/COMPONENT-GUIDE.md +++ b/.github/COMPONENT-GUIDE.md @@ -2,10 +2,10 @@ - [Button States](#button-states) * [Component Structure](#component-structure) - * [Activated](#activated) * [Disabled](#disabled) * [Focused](#focused) * [Hover](#hover) + * [Activated](#activated) * [Ripple Effect](#ripple-effect) * [Example Components](#example-components) * [References](#references) @@ -21,7 +21,7 @@ ## Button States -Any component that renders a button should have the following states: [`activated`](#activated), [`disabled`](#disabled), [`focused`](#focused), [`hover`](#hover). It should also have a [Ripple Effect](#ripple-effect) component added for Material Design. +Any component that renders a button should have the following states: [`disabled`](#disabled), [`focused`](#focused), [`hover`](#hover), [`activated`](#activated). It should also have a [Ripple Effect](#ripple-effect) component added for Material Design. ### Component Structure @@ -89,78 +89,6 @@ The following styles should be set for the CSS to work properly. Note that the ` ``` -### Activated - -The activated state should be enabled for elements with actions on "press". It usually changes the opacity or background of an element. - -> [!WARNING] ->`:active` should not be used here as it is not received on mobile Safari unless the element has a `touchstart` listener (which we don't necessarily want to have to add to every element). From [Safari Web Content Guide](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/AdjustingtheTextSize/AdjustingtheTextSize.html): -> ->> On iOS, mouse events are sent so quickly that the down or active state is never received. Therefore, the `:active` pseudo state is triggered only when there is a touch event set on the HTML element - -> Make sure the component has the correct [component structure](#component-structure) before continuing. - -#### JavaScript - -The `ion-activatable` class needs to be set on an element that can be activated: - -```jsx -render() { - return ( - - - - ); -} -``` - -Once that is done, the element will get the `ion-activated` class added on press after a small delay. This delay exists so that the active state does not show up when an activatable element is tapped while scrolling. - -In addition to setting that class, `ion-activatable-instant` can be set in order to have an instant press with no delay: - -```jsx - -``` - -#### CSS - -```css - /** - * @prop --color-activated: Color of the button when pressed - * @prop --background-activated: Background of the button when pressed - * @prop --background-activated-opacity: Opacity of the background when pressed - */ -``` - -Style the `ion-activated` class based on the spec for that element: - -```scss -:host(.ion-activated) .button-native { - color: var(--color-activated); - - &::after { - background: var(--background-activated); - - opacity: var(--background-activated-opacity); - } -} -``` - -> Order is important! Activated should be after the focused & hover states. - - -#### User Customization - -Setting the activated state on the `::after` pseudo-element allows the user to customize the activated state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on press, or they can leave out `--background-activated-opacity` and the button will use the default activated opacity to match the spec. - -```css -ion-button { - --background-activated: red; - --background-activated-opacity: 1; -} -``` - - ### Disabled The disabled state should be set via prop on all components that render a native button. Setting a disabled state will change the opacity or color of the button and remove click events from firing. @@ -197,7 +125,8 @@ render() { } ``` -> Note: if the class being added was for `ion-back-button` it would be `back-button-disabled`. +> [!NOTE] +> If the class being added was for `ion-back-button` it would be `back-button-disabled`. #### CSS @@ -215,9 +144,10 @@ The following CSS _at the bare minimum_ should be added for the disabled class, TODO + ### Focused -The focused state should be enabled for elements with actions when tabbed to via the keyboard. This will only work inside of an `ion-app`. It usually changes the opacity or background of an element. +The focused state should be enabled for elements with actions when tabbed to via the keyboard. This will only work inside of an `ion-app`. It usually changes the opacity or background of an element. > [!WARNING] > Do not use `:focus` because that will cause the focus to apply even when an element is tapped (because the element is now focused). Instead, we only want the focus state to be shown when it makes sense which is what the `.ion-focusable` utility mentioned below does. @@ -225,6 +155,7 @@ The focused state should be enabled for elements with actions when tabbed to via > [!NOTE] > The [`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) pseudo-class mostly does the same thing as our JavaScript-driven utility. However, it does not work well with Shadow DOM components as the element that receives focus is typically inside of the Shadow DOM, but we usually want to set the `:focus-visible` state on the host so we can style other parts of the component. Using other combinations such as `:has(:focus-visible)` does not work because `:has` does not pierce the Shadow DOM (as that would leak implementation details about the Shadow DOM contents). `:focus-within` does work with the Shadow DOM, but that has the same problem as `:focus` that was mentioned before. Unfortunately, a [`:focus-visible-within` pseudo-class does not exist yet](https://github.com/WICG/focus-visible/issues/151). +> [!IMPORTANT] > Make sure the component has the correct [component structure](#component-structure) before continuing. #### JavaScript @@ -234,7 +165,7 @@ The `ion-focusable` class needs to be set on an element that can be focused: ```jsx render() { return ( - + ); @@ -269,7 +200,8 @@ Style the `ion-focused` class based on the spec for that element: } ``` -> Order is important! Focused should be after the activated and before the hover state. +> [!IMPORTANT] +> Order matters! Focused should be **before** the activated and hover states. #### User Customization @@ -286,11 +218,12 @@ ion-button { ### Hover -The [hover state](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) happens when a user moves their cursor on top of an element without pressing on it. It should not happen on mobile, only on desktop devices that support hover. +The [hover state](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) happens when a user moves their cursor on top of an element without pressing on it. It should not happen on mobile, only on desktop devices that support hover. > [!NOTE] > Some Android devices [incorrectly report their inputs](https://issues.chromium.org/issues/40855702) which can result in certain devices receiving hover events when they should not. +> [!IMPORTANT] > Make sure the component has the correct [component structure](#component-structure) before continuing. #### CSS @@ -321,7 +254,8 @@ Style the `:hover` based on the spec for that element: } ``` -> Order is important! Hover should be before the activated state. +> [!IMPORTANT] +> Order matters! Hover should be **before** the activated state. #### User Customization @@ -336,6 +270,79 @@ ion-button { ``` +### Activated + +The activated state should be enabled for elements with actions on "press". It usually changes the opacity or background of an element. + +> [!WARNING] +>`:active` should not be used here as it is not received on mobile Safari unless the element has a `touchstart` listener (which we don't necessarily want to have to add to every element). From [Safari Web Content Guide](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/AdjustingtheTextSize/AdjustingtheTextSize.html): +> +>> On iOS, mouse events are sent so quickly that the down or active state is never received. Therefore, the `:active` pseudo state is triggered only when there is a touch event set on the HTML element + +> [!IMPORTANT] +> Make sure the component has the correct [component structure](#component-structure) before continuing. + +#### JavaScript + +The `ion-activatable` class needs to be set on an element that can be activated: + +```jsx +render() { + return ( + + + + ); +} +``` + +Once that is done, the element will get the `ion-activated` class added on press after a small delay. This delay exists so that the active state does not show up when an activatable element is tapped while scrolling. + +In addition to setting that class, `ion-activatable-instant` can be set in order to have an instant press with no delay: + +```jsx + +``` + +#### CSS + +```css + /** + * @prop --color-activated: Color of the button when pressed + * @prop --background-activated: Background of the button when pressed + * @prop --background-activated-opacity: Opacity of the background when pressed + */ +``` + +Style the `ion-activated` class based on the spec for that element: + +```scss +:host(.ion-activated) .button-native { + color: var(--color-activated); + + &::after { + background: var(--background-activated); + + opacity: var(--background-activated-opacity); + } +} +``` + +> [!IMPORTANT] +> Order matters! Activated should be **after** the focused & hover states. + +#### User Customization + +Setting the activated state on the `::after` pseudo-element allows the user to customize the activated state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on press, or they can leave out `--background-activated-opacity` and the button will use the default activated opacity to match the spec. + +```css +ion-button { + --background-activated: red; + --background-activated-opacity: 1; +} +``` + + ### Ripple Effect The ripple effect should be added to elements for Material Design. It *requires* the `ion-activatable` class to be set on the parent element to work, and relative positioning on the parent. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e305a482a2..09ee08f76f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,12 +20,10 @@ body: id: affected-versions attributes: label: Ionic Framework Version - description: Which version(s) of Ionic Framework does this issue impact? For Ionic Framework 1.x issues, please use https://github.com/ionic-team/ionic-v1. For Ionic Framework 2.x and 3.x issues, please use https://github.com/ionic-team/ionic-v3. + description: Which version(s) of Ionic Framework does this issue impact? [Ionic Framework 1.x to 6.x are no longer supported](https://ionicframework.com/docs/reference/support#framework-maintenance-and-support-status). For extended support, considering visiting [Ionic's Enterprise offering](https://ionic.io/enterprise). options: - - v4.x - - v5.x - - v6.x - v7.x + - v8.x (Beta) - Nightly multiple: true validations: @@ -51,11 +49,11 @@ body: id: steps-to-reproduce attributes: label: Steps to Reproduce - description: Please explain the steps required to duplicate this issue. + description: Explain the steps required to reproduce this issue. placeholder: | - 1. - 2. - 3. + 1. Go to '...' + 2. Click on '...' + 3. Observe: '...' validations: required: true @@ -63,8 +61,15 @@ body: id: reproduction-url attributes: label: Code Reproduction URL - description: Please reproduce this issue in a blank Ionic Framework starter application and provide a link to the repo. Try out our [Getting Started Wizard](https://ionicframework.com/start#basics) to quickly spin up an Ionic Framework starter app. This is the best way to ensure this issue is triaged quickly. Issues without a code reproduction may be closed if the Ionic Team cannot reproduce the issue you are reporting. + description: | + Reproduce this issue in a blank [Ionic Framework starter application](https://ionicframework.com/start#basics) or a Stackblitz example. + + You can use the Stackblitz button available on any of the [component playgrounds](https://ionicframework.com/docs/components) to open an editable example. Remember to save your changes to obtain a link to copy. + + Reproductions cases must be minimal and focused around the specific problem you are experiencing. This is the best way to ensure this issue is triaged quickly. Issues without a code reproduction may be closed if the Ionic Team cannot reproduce the issue you are reporting. placeholder: https://github.com/... + validations: + required: true - type: textarea id: ionic-info diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c01808b55..24c64ca781 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) + + + + + ## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) diff --git a/lerna.json b/lerna.json index ffa1f29e78..27c2bc2fdf 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "7.7.4" + "version": "7.8.0" } diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 03c3eadb16..17db42772a 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/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/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 + + + + + ## [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 diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 5bcb71306f..7f49de1741 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "7.7.4", + "version": "7.8.0", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^7.7.4" + "@ionic/core": "^7.8.0" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index ea06bc4d42..3849b1a652 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) + + + + + ## [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 diff --git a/packages/angular/common/src/directives/navigation/nav.ts b/packages/angular/common/src/directives/navigation/nav.ts index 585bffc040..78cfaa240f 100644 --- a/packages/angular/common/src/directives/navigation/nav.ts +++ b/packages/angular/common/src/directives/navigation/nav.ts @@ -1,4 +1,12 @@ -import { ElementRef, Injector, EnvironmentInjector, NgZone, ChangeDetectorRef, Directive } from '@angular/core'; +import { + ElementRef, + Injector, + EnvironmentInjector, + NgZone, + ChangeDetectorRef, + Directive, + EventEmitter, +} from '@angular/core'; import type { Components } from '@ionic/core'; import { AngularDelegate } from '../../providers/angular-delegate'; @@ -22,8 +30,16 @@ const NAV_METHODS = [ 'getPrevious', ]; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export declare interface IonNav extends Components.IonNav {} +export declare interface IonNav extends Components.IonNav { + /** + * Event fired when the nav will change components + */ + ionNavWillChange: EventEmitter>; + /** + * Event fired when the nav has changed components + */ + ionNavDidChange: EventEmitter>; +} @ProxyCmp({ inputs: NAV_INPUTS, diff --git a/packages/angular/package.json b/packages/angular/package.json index ac799e454d..507cca704f 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "7.7.4", + "version": "7.8.0", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -46,7 +46,7 @@ } }, "dependencies": { - "@ionic/core": "^7.7.4", + "@ionic/core": "^7.8.0", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index be168ed870..b60fd38f7f 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; @@ -1788,7 +1788,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({ @@ -1796,7 +1796,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/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e72d106d9d..6333207bfa 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/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) + + + + + ## [7.7.4](https://github.com/ionic-team/ionic-framework/compare/v7.7.3...v7.7.4) (2024-03-06) diff --git a/packages/core/api.txt b/packages/core/api.txt index fa3825d290..498263defe 100644 --- a/packages/core/api.txt +++ b/packages/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 @@ -1157,6 +1158,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 @@ -1167,6 +1169,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/packages/core/package.json b/packages/core/package.json index 159341ebaa..5a3429b1a1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "7.7.4", + "version": "7.8.0", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index a706eb8971..3e6d2bca8b 100644 --- a/packages/core/src/components.d.ts +++ b/packages/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 { CounterFormatter } from "./components/item/item-interface"; @@ -51,7 +51,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 { CounterFormatter } from "./components/item/item-interface"; @@ -858,6 +858,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"`. */ @@ -2548,6 +2552,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. */ @@ -2592,6 +2600,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. */ @@ -3195,7 +3211,7 @@ export namespace Components { } interface IonToggle { /** - * How to control the alignment of the toggle and label on the cross axis. ``"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL. + * How to control the alignment of the toggle and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL. */ "alignment": 'start' | 'center'; /** @@ -5541,6 +5557,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"`. */ @@ -7272,6 +7292,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. */ @@ -7312,6 +7336,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. */ @@ -8005,7 +8037,7 @@ declare namespace LocalJSX { } interface IonToggle { /** - * How to control the alignment of the toggle and label on the cross axis. ``"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL. + * How to control the alignment of the toggle and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL. */ "alignment"?: 'start' | 'center'; /** diff --git a/packages/core/src/components/alert/test/a11y/alert.e2e.ts b/packages/core/src/components/alert/test/a11y/alert.e2e.ts index a5e454e758..75d895467c 100644 --- a/packages/core/src/components/alert/test/a11y/alert.e2e.ts +++ b/packages/core/src/components/alert/test/a11y/alert.e2e.ts @@ -288,6 +288,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { await alert.evaluate((el: HTMLIonAlertElement) => el.present()); await ionAlertDidPresent.next(); + /** + * The borders on the text fields may not be visible in the screenshot + * when using Safari, this is due to a WebKit rendering quirk. + */ await expect(page).toHaveScreenshot(screenshot(`alert-text-fields-scale`)); }); }); diff --git a/packages/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-text-fields-scale-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-text-fields-scale-ios-ltr-Mobile-Safari-linux.png index 89ec4b3260..764f858129 100644 Binary files a/packages/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-text-fields-scale-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-text-fields-scale-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-ltr-Mobile-Safari-linux.png index 5989797632..22c9babdb3 100644 Binary files a/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-rtl-Mobile-Safari-linux.png b/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-rtl-Mobile-Safari-linux.png index 99b504644e..2b63b3f37a 100644 Binary files a/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ios-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/app/app.tsx b/packages/core/src/components/app/app.tsx index 44a67d2a15..d38b0bd37a 100644 --- a/packages/core/src/components/app/app.tsx +++ b/packages/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/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-ltr-Mobile-Safari-linux.png index 38f7db4cce..4d86c937b3 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-rtl-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-rtl-Mobile-Safari-linux.png index 7880f316b9..c24270a19f 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/basic/button.e2e.ts-snapshots/button-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-ltr-Mobile-Safari-linux.png index 14e54448b6..44a74d5c5e 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-rtl-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-rtl-Mobile-Safari-linux.png index 682259f234..b2741d75e1 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/clear/button.e2e.ts-snapshots/button-clear-ios-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-ltr-Mobile-Safari-linux.png index 7465648053..5fa5c8252e 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-rtl-Mobile-Safari-linux.png b/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-rtl-Mobile-Safari-linux.png index cde26c32f6..e414d404ef 100644 Binary files a/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/breadcrumbs/button/test/outline/button.e2e.ts-snapshots/button-outline-ios-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-clear-scale-md-ltr-Mobile-Safari-linux.png b/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-clear-scale-md-ltr-Mobile-Safari-linux.png index 6a557ee60a..b3d19e81b3 100644 Binary files a/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-clear-scale-md-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-clear-scale-md-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-default-scale-md-ltr-Mobile-Safari-linux.png b/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-default-scale-md-ltr-Mobile-Safari-linux.png index efc948af21..166a2f5306 100644 Binary files a/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-default-scale-md-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/a11y/button.e2e.ts-snapshots/button-default-scale-md-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Safari-linux.png b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Safari-linux.png index df21b99b87..9bfec15216 100644 Binary files a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Safari-linux.png b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Safari-linux.png index 8c8e5575c1..b86b085d9c 100644 Binary files a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-ltr-Mobile-Safari-linux.png b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-ltr-Mobile-Safari-linux.png index 423c2a2cf7..b3dd0ad4cc 100644 Binary files a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-ltr-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-rtl-Mobile-Safari-linux.png b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-rtl-Mobile-Safari-linux.png index 48efc0adf2..5ad5a18fc7 100644 Binary files a/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-rtl-Mobile-Safari-linux.png and b/packages/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-md-rtl-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/datetime-button/datetime-button.tsx b/packages/core/src/components/datetime-button/datetime-button.tsx index 7ce3ca4163..519fb42da6 100644 --- a/packages/core/src/components/datetime-button/datetime-button.tsx +++ b/packages/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/packages/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts b/packages/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts index 938ce976e5..7024527de2 100644 --- a/packages/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts +++ b/packages/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts @@ -244,4 +244,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/packages/core/src/components/datetime-button/test/basic/index.html b/packages/core/src/components/datetime-button/test/basic/index.html index c446544a0a..dd269d984e 100644 --- a/packages/core/src/components/datetime-button/test/basic/index.html +++ b/packages/core/src/components/datetime-button/test/basic/index.html @@ -215,8 +215,41 @@ > + +
+

formatOptions

+ + + Start Date + + + + + + +
+ + diff --git a/packages/core/src/components/datetime/datetime-interface.ts b/packages/core/src/components/datetime/datetime-interface.ts index 255f39e22d..475a672d06 100644 --- a/packages/core/src/components/datetime/datetime-interface.ts +++ b/packages/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/packages/core/src/components/datetime/datetime.tsx b/packages/core/src/components/datetime/datetime.tsx index 57f275e25f..e385657d89 100644 --- a/packages/core/src/components/datetime/datetime.tsx +++ b/packages/core/src/components/datetime/datetime.tsx @@ -20,6 +20,7 @@ import type { DatetimeHighlightStyle, DatetimeHighlightCallback, DatetimeHourCycle, + FormatOptions, } from './datetime-interface'; import { isSameDay, warnIfValueOutOfBounds, isBefore, isAfter } from './utils/comparison'; import { @@ -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. @@ -106,6 +108,7 @@ export class Datetime implements ComponentInterface { private calendarBodyRef?: HTMLElement; private monthYearToggleItemRef?: HTMLIonItemElement; private popoverRef?: HTMLIonPopoverElement; + private intersectionTrackerRef?: HTMLElement; private clearFocusVisible?: () => void; private parsedMinuteValues?: number[]; private parsedHourValues?: number[]; @@ -171,6 +174,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. */ @@ -235,6 +252,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'; @@ -1057,6 +1080,8 @@ export class Datetime implements ComponentInterface { } componentDidLoad() { + const { el, intersectionTrackerRef } = this; + /** * If a scrollable element is hidden using `display: none`, * it will not have a scroll height meaning we cannot scroll elements @@ -1084,7 +1109,7 @@ export class Datetime implements ComponentInterface { this.el.classList.add('datetime-ready'); }); }; - const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01 }); + const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01, root: el }); /** * Use raf to avoid a race condition between the component loading and @@ -1092,7 +1117,7 @@ export class Datetime implements ComponentInterface { * could cause the datetime to start at a visibility of 0, erroneously * triggering the `hiddenIO` observer below. */ - raf(() => visibleIO?.observe(this.el)); + raf(() => visibleIO?.observe(intersectionTrackerRef!)); /** * We need to clean up listeners when the datetime is hidden @@ -1122,8 +1147,8 @@ export class Datetime implements ComponentInterface { this.el.classList.remove('datetime-ready'); }); }; - const hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0 }); - raf(() => hiddenIO?.observe(this.el)); + const hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0, root: el }); + raf(() => hiddenIO?.observe(intersectionTrackerRef!)); /** * Datetime uses Ionic components that emit @@ -1357,7 +1382,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') { @@ -1382,6 +1407,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)); @@ -2354,7 +2384,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(); @@ -2389,7 +2419,7 @@ export class Datetime implements ComponentInterface { } }} > - {getLocalizedTime(locale, activePart, computedHourCycle)} + {getLocalizedTime(locale, activePart, computedHourCycle, formatOptions?.time)} , + {/* + WebKit has a quirk where IntersectionObserver callbacks are delayed until after + an accelerated animation finishes if the "root" specified in the config is the + browser viewport (the default behavior if "root" is not specified). This means + that when presenting a datetime in a modal on iOS the calendar body appears + blank until the modal animation finishes. + + We can work around this by observing .intersection-tracker and using the host + (ion-datetime) as the "root". This allows the IO callback to fire the moment + the datetime is visible. The .intersection-tracker element should not have + dimensions or additional styles, and it should not be positioned absolutely + otherwise the IO callback may fire at unexpected times. + */} +
(this.intersectionTrackerRef = el)}>
{this.renderDatetime(mode)}
); diff --git a/packages/core/src/components/datetime/test/basic/datetime.e2e.ts b/packages/core/src/components/datetime/test/basic/datetime.e2e.ts index 7d012868cc..f84abeaeca 100644 --- a/packages/core/src/components/datetime/test/basic/datetime.e2e.ts +++ b/packages/core/src/components/datetime/test/basic/datetime.e2e.ts @@ -570,3 +570,107 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { }); }); }); + +/** + * 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/packages/core/src/components/datetime/test/basic/index.html b/packages/core/src/components/datetime/test/basic/index.html index 2023b4a119..b3afa76688 100644 --- a/packages/core/src/components/datetime/test/basic/index.html +++ b/packages/core/src/components/datetime/test/basic/index.html @@ -308,6 +308,13 @@ + +
+

formatOptions

+ + Select Date + +
diff --git a/packages/core/src/components/datetime/test/format.spec.ts b/packages/core/src/components/datetime/test/format.spec.ts index 5ff218167d..7161a1aeae 100644 --- a/packages/core/src/components/datetime/test/format.spec.ts +++ b/packages/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/packages/core/src/components/datetime/utils/format.ts b/packages/core/src/components/datetime/utils/format.ts index 0f70299dc5..6443e1b546 100644 --- a/packages/core/src/components/datetime/utils/format.ts +++ b/packages/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/packages/core/src/components/datetime/utils/validate.ts b/packages/core/src/components/datetime/utils/validate.ts new file mode 100644 index 0000000000..46c3fe633b --- /dev/null +++ b/packages/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/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-ltr-Mobile-Firefox-linux.png b/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-ltr-Mobile-Firefox-linux.png index d8da3db436..7cac4c5982 100644 Binary files a/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-ltr-Mobile-Firefox-linux.png and b/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-ltr-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-rtl-Mobile-Firefox-linux.png b/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-rtl-Mobile-Firefox-linux.png index 08bf93cb06..bbbb81fc5c 100644 Binary files a/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-rtl-Mobile-Firefox-linux.png and b/packages/core/src/components/grid/test/basic/grid.e2e.ts-snapshots/grid-basic-md-rtl-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/header/header.ios.scss b/packages/core/src/components/header/header.ios.scss index 634303ff8e..cda084da7c 100644 --- a/packages/core/src/components/header/header.ios.scss +++ b/packages/core/src/components/header/header.ios.scss @@ -132,8 +132,11 @@ * We use opacity: 0 to avoid a layout shift. * We target both the attribute and the class in the event that the attribute * is not reflected on the host in some frameworks. + * + * Both headers should be scoped to iOS mode otherwise an MD app that uses an + * iOS header may cause other MD headers to be unexpectedly hidden. */ -ion-header:not(.header-collapse-main):has(~ ion-content ion-header[collapse="condense"], -~ ion-content ion-header.header-collapse-condense) { +ion-header.header-ios:not(.header-collapse-main):has(~ ion-content ion-header.header-ios[collapse="condense"], +~ ion-content ion-header.header-ios.header-collapse-condense) { opacity: 0; } diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts b/packages/core/src/components/header/test/basic/header.e2e.ts index 5550bc575d..67f3be9217 100644 --- a/packages/core/src/components/header/test/basic/header.e2e.ts +++ b/packages/core/src/components/header/test/basic/header.e2e.ts @@ -85,3 +85,91 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); }); }); + +/** + * This test only impacts MD applications + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('header: translucent'), () => { + test('should not hide MD headers when using a descendant iOS header in an MD app', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28867', + }); + await page.setContent( + ` + + + Header + + + + + + Header + + + + + + Welcome + + + + `, + config + ); + + const header = page.locator('ion-header#main-header'); + + /** + * The existence of the iOS header in an MD app should not cause the main MD header + * to be hidden. We do not have toHaveVisible because the behavior that hides + * the header under correct circumstances does it using opacity: 0. + * Playwright considers an element with opacity: 0 to still be visible + * because it has a non-zero bounding box. + */ + await expect(header).toHaveScreenshot(screenshot('header-md-visibility-ios-descendant')); + }); + test('should not hide MD headers when using a root iOS header in an MD app', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28867', + }); + await page.setContent( + ` + + + Header + + + + + + Header + + + + + + Welcome + + + + `, + config + ); + + const header = page.locator('ion-header#main-header'); + + /** + * The existence of the iOS header in an MD app should not cause the main MD header + * to be hidden. We do not have toHaveVisible because the behavior that hides + * the header under correct circumstances does it using opacity: 0. + * Playwright considers an element with opacity: 0 to still be visible + * because it has a non-zero bounding box. + */ + await expect(header).toHaveScreenshot(screenshot('header-md-visibility-ios-main')); + }); + }); +}); diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Chrome-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..06b4e5e7c7 Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Chrome-linux.png differ diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Firefox-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6f6ca4833e Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Safari-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..2a12565288 Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-descendant-md-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Chrome-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..dab34d6786 Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Chrome-linux.png differ diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Firefox-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..5b9c0f573d Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Safari-linux.png b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..83c2b8a50e Binary files /dev/null and b/packages/core/src/components/header/test/basic/header.e2e.ts-snapshots/header-md-visibility-ios-main-md-ltr-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/header/test/condense/header.e2e.ts-snapshots/header-condense-large-title-collapsed-diff-ios-ltr-Mobile-Chrome-linux.png b/packages/core/src/components/header/test/condense/header.e2e.ts-snapshots/header-condense-large-title-collapsed-diff-ios-ltr-Mobile-Chrome-linux.png index 39c8b4e1c8..67bfa600ad 100644 Binary files a/packages/core/src/components/header/test/condense/header.e2e.ts-snapshots/header-condense-large-title-collapsed-diff-ios-ltr-Mobile-Chrome-linux.png and b/packages/core/src/components/header/test/condense/header.e2e.ts-snapshots/header-condense-large-title-collapsed-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Chrome-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a7f778003a Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Chrome-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Firefox-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c4ff5513dc Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Safari-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Safari-linux.png new file mode 100644 index 0000000000..c14c158998 Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-ios-ltr-dark-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Chrome-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2344f53b4a Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Chrome-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Firefox-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..48c91680c0 Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Firefox-linux.png differ diff --git a/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Safari-linux.png b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b757f7fd88 Binary files /dev/null and b/packages/core/src/components/input/item/test/buttons/item.e2e.ts-snapshots/item-buttons-dark-diff-md-ltr-dark-Mobile-Safari-linux.png differ diff --git a/packages/core/src/components/item/test/buttons/index.html b/packages/core/src/components/item/test/buttons/index.html index d28eafee33..fb8d5449ca 100644 --- a/packages/core/src/components/item/test/buttons/index.html +++ b/packages/core/src/components/item/test/buttons/index.html @@ -12,165 +12,6 @@ - - @@ -347,12 +188,6 @@