Compare commits
24 Commits
build-test
...
sp/playwri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd03a2dba | ||
|
|
bf18d51256 | ||
|
|
fefaa8b57c | ||
|
|
dc696695ce | ||
|
|
c1465bd1b0 | ||
|
|
8dc3d1a16d | ||
|
|
242dee551e | ||
|
|
56d3bd5612 | ||
|
|
2fb6cd92dc | ||
|
|
7ac0018a3c | ||
|
|
a7c966776a | ||
|
|
7de4e34f13 | ||
|
|
4b5e62e60f | ||
|
|
9883eac0f7 | ||
|
|
aa2a7f5271 | ||
|
|
2509d565b2 | ||
|
|
ce89057641 | ||
|
|
5aafd68f03 | ||
|
|
098ed054b1 | ||
|
|
7ba939fb94 | ||
|
|
6cd819a059 | ||
|
|
9bcee94e0b | ||
|
|
409df1bea5 | ||
|
|
021712bd7d |
335
BREAKING.md
@@ -4,338 +4,59 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
||||
|
||||
## Versions
|
||||
|
||||
- [Version 7.x](#version-7x)
|
||||
- [Version 8.x](#version-8x)
|
||||
- [Version 7.x](./BREAKING_ARCHIVE/v7.md)
|
||||
- [Version 6.x](./BREAKING_ARCHIVE/v6.md)
|
||||
- [Version 5.x](./BREAKING_ARCHIVE/v5.md)
|
||||
- [Version 4.x](./BREAKING_ARCHIVE/v4.md)
|
||||
- [Legacy](https://github.com/ionic-team/ionic-v3/blob/master/CHANGELOG.md)
|
||||
|
||||
## Version 7.x
|
||||
## Version 8.x
|
||||
|
||||
- [Browser and Platform Support](#version-7x-browser-platform-support)
|
||||
- [Components](#version-7x-components)
|
||||
- [Accordion Group](#version-7x-accordion-group)
|
||||
- [Action Sheet](#version-7x-action-sheet)
|
||||
- [Back Button](#version-7x-back-button)
|
||||
- [Button](#version-7x-button)
|
||||
- [Card Header](#version-7x-card-header)
|
||||
- [Checkbox](#version-7x-checkbox)
|
||||
- [Datetime](#version-7x-datetime)
|
||||
- [Input](#version-7x-input)
|
||||
- [Item](#version-7x-item)
|
||||
- [Modal](#version-7x-modal)
|
||||
- [Overlays](#version-7x-overlays)
|
||||
- [Picker](#version-7x-picker)
|
||||
- [Radio Group](#version-7x-radio-group)
|
||||
- [Range](#version-7x-range)
|
||||
- [Searchbar](#version-7x-searchbar)
|
||||
- [Segment](#version-7x-segment)
|
||||
- [Select](#version-7x-select)
|
||||
- [Slides](#version-7x-slides)
|
||||
- [Textarea](#version-7x-textarea)
|
||||
- [Toggle](#version-7x-toggle)
|
||||
- [Virtual Scroll](#version-7x-virtual-scroll)
|
||||
- [Config](#version-7x-config)
|
||||
- [Types](#version-7x-types)
|
||||
- [Overlay Attribute Interfaces](#version-7x-overlay-attribute-interfaces)
|
||||
- [JavaScript Frameworks](#version-7x-javascript-frameworks)
|
||||
- [Angular](#version-7x-angular)
|
||||
- [React](#version-7x-react)
|
||||
- [Vue](#version-7x-vue)
|
||||
- [CSS Utilities](#version-7x-css-utilities)
|
||||
- [hidden attribute](#version-7x-hidden-attribute)
|
||||
- [Browser and Platform Support](#version-8x-browser-platform-support)
|
||||
- [Components](#version-8x-components)
|
||||
- [Content](#version-8x-content)
|
||||
- [Datetime](#version-8x-datetime)
|
||||
|
||||
<h2 id="version-7x-browser-platform-support">Browser and Platform Support</h2>
|
||||
<h2 id="version-8x-browser-platform-support">Browser and Platform Support</h2>
|
||||
|
||||
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 7.
|
||||
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 8.
|
||||
|
||||
**Minimum Browser Versions**
|
||||
| Desktop Browser | Supported Versions |
|
||||
| --------------- | ----------------- |
|
||||
| Chrome | 79+ |
|
||||
| Safari | 14+ |
|
||||
| Firefox | 70+ |
|
||||
| Edge | 79+ |
|
||||
| Chrome | 89+ |
|
||||
| Safari | 15+ |
|
||||
| Firefox | 75+ |
|
||||
| Edge | 89+ |
|
||||
|
||||
**Minimum JavaScript Framework Versions**
|
||||
|
||||
| Framework | Supported Version |
|
||||
| --------- | --------------------- |
|
||||
| Angular | 14+ |
|
||||
| Angular | 16+ |
|
||||
| React | 17+ |
|
||||
| Vue | 3.0.6+ |
|
||||
|
||||
**Minimum Mobile Platform Versions**
|
||||
|
||||
| Platform | Supported Version |
|
||||
| -------- | ---------------------- |
|
||||
| iOS | 14+ |
|
||||
| Android | 5.1+ with Chromium 79+ |
|
||||
| iOS | 15+ |
|
||||
| Android | 5.1+ with Chromium 89+ |
|
||||
|
||||
<h2 id="version-7x-components">Components</h2>
|
||||
<h2 id="version-8x-components">Components</h2>
|
||||
|
||||
<h4 id="version-7x-accordion-group">Accordion Group</h4>
|
||||
<h4 id="version-8x-content">Content</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
|
||||
- Content no longer sets the `--background` custom property when the `.outer-content` class is set on the host.
|
||||
|
||||
- Accordion Group no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
|
||||
<h4 id="version-8x-datetime">Datetime</h4>
|
||||
|
||||
<h4 id="version-7x-action-sheet">Action Sheet</h4>
|
||||
- The CSS shadow part for `month-year-button` has been changed to target a `button` element instead of `ion-item`. Developers should verify their UI renders as expected for the month/year toggle button inside of `ion-datetime`.
|
||||
- Developers using the CSS variables available on `ion-item` will need to migrate their CSS to use CSS properties. For example:
|
||||
```diff
|
||||
ion-datetime::part(month-year-button) {
|
||||
- --background: red;
|
||||
|
||||
- Action Sheet is updated to align with the design specification.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ---------- | -------------- | --------- |
|
||||
| `--height` | `100%` | `auto` |
|
||||
|
||||
<h4 id="version-7x-button">Button</h4>
|
||||
|
||||
- Button is updated to align with the design specification for iOS.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ---------------------------------- | -------------- | --------- |
|
||||
| `$button-ios-letter-spacing` | `-0.03em` | `0` |
|
||||
| `$button-ios-clear-letter-spacing` | `0` | Removed |
|
||||
| `$button-ios-height` | `2.8em` | `3.1em` |
|
||||
| `$button-ios-border-radius` | `10px` | `14px` |
|
||||
| `$button-ios-large-height` | `2.8em` | `3.1em` |
|
||||
| `$button-ios-large-border-radius` | `12px` | `16px` |
|
||||
|
||||
<h4 id="version-7x-back-button">Back Button</h4>
|
||||
|
||||
- Back Button is updated to align with the design specification for iOS.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ------------------- | -------------- | --------- |
|
||||
| `--icon-margin-end` | `-5px` | `1px` |
|
||||
| `--icon-font-size` | `1.85em` | `1.6em` |
|
||||
|
||||
<h4 id="version-7x-card-header">Card Header</h4>
|
||||
|
||||
- The card header has ben changed to a flex container with direction set to `column` (top to bottom). In `ios` mode the direction is set to `column-reverse` which results in the subtitle displaying on top of the title.
|
||||
|
||||
<h4 id="version-7x-checkbox">Checkbox</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `checked` property of `ion-checkbox` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the checkbox.
|
||||
|
||||
- The `--background` and `--background-checked` CSS variables have been renamed to `--checkbox-background` and `--checkbox-background-checked` respectively.
|
||||
|
||||
<h4 id="version-7x-datetime">Datetime</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` property of `ion-datetime` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping a date.
|
||||
|
||||
- Datetime no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
|
||||
|
||||
- Datetime no longer incorrectly reports the time zone when `value` is updated. Datetime does not manage time zones, so any time zone information provided is ignored.
|
||||
|
||||
- Passing the empty string to the `value` property will now error as it is not a valid ISO-8601 value.
|
||||
|
||||
- The haptics when swiping the wheel picker are now enabled only on iOS.
|
||||
|
||||
<h4 id="version-7x-input">Input</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus, clicking the clear action within the input, or pressing the "Enter" key.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the input, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
<h4 id="version-7x-item">Item</h4>
|
||||
|
||||
**Design tokens**
|
||||
|
||||
iOS:
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| --------------------- | -------------- | --------- |
|
||||
| `$item-ios-font-size` | `17px` | `16px` |
|
||||
| `--inner-padding-end` | `10px` | `16px` |
|
||||
| `--padding-start` | `20px` | `16px` |
|
||||
|
||||
<h4 id="version-7x-modal">Modal</h4>
|
||||
|
||||
- The `swipeToClose` property has been removed in favor of `canDismiss`.
|
||||
- The `canDismiss` property now defaults to `true` and can no longer be set to `undefined`.
|
||||
|
||||
<h4 id="version-7x-overlays">Overlays</h4>
|
||||
|
||||
Ionic now listens on the `keydown` event instead of the `keyup` event when determining when to dismiss overlays via the "Escape" key. Any applications that were listening on `keyup` to suppress this behavior should listen on `keydown` instead.
|
||||
|
||||
<h4 id="version-7x-picker">Picker</h4>
|
||||
|
||||
- The `refresh` key has been removed from the `PickerColumn` interface. Developers should use the `columns` property to refresh the `ion-picker` view.
|
||||
|
||||
<h4 id="version-7x-radio-group">Radio Group</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-radio-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping an `ion-radio` in the group.
|
||||
|
||||
<h4 id="version-7x-range">Range</h4>
|
||||
|
||||
- Range is updated to align with the design specification for supported modes.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
iOS:
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `--bar-border-radius` | `0px` | `$range-ios-bar-border-radius` (`2px` default) |
|
||||
| `--knob-size` | `28px` | `$range-ios-knob-width` (`26px` default) |
|
||||
| `$range-ios-bar-height` | `2px` | `4px` |
|
||||
| `$range-ios-bar-background-color` | `rgba(var(--ion-text-color-rgb, 0, 0, 0), .1)` | `var(--ion-color-step-900, #e6e6e6)` |
|
||||
| `$range-ios-knob-box-shadow` | `0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02)` | `0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12)` |
|
||||
| `$range-ios-knob-width` | `28px` | `26px` |
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-range` is modified externally. `ionChange` is only emitted from user committed changes, such as dragging and releasing the range knob or selecting a new value with the keyboard arrows.
|
||||
- If your application requires immediate feedback based on the user actively dragging the range knob, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property's value value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- Range no longer clamps assigned values within bounds. Developers will need to validate the value they are assigning to `ion-range` is within the `min` and `max` bounds when programmatically assigning a value.
|
||||
|
||||
- The `name` property defaults to `ion-r-${rangeIds++}` where `rangeIds` is a number that is incremented for every instance of `ion-range`.
|
||||
|
||||
<h4 id="version-7x-searchbar">Searchbar</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-searchbar` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the searchbar and the searchbar losing focus or pressing the "Enter" key.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the searchbar, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from 250 to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
|
||||
<h4 id="version-7x-segment">Segment</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-segment` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking a segment button or dragging to activate a segment button.
|
||||
|
||||
- The type signature of `value` supports `string | undefined`. Previously the type signature was `string | null | undefined`.
|
||||
- Developers needing to clear the checked segment item should assign a value of `''` instead of `null`.
|
||||
|
||||
<h4 id="version-7x-select">Select</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-select` is modified externally. `ionChange` is only emitted from user committed changes, such as confirming a selected option in the select's overlay.
|
||||
|
||||
- The `icon` CSS Shadow Part now targets an `ion-icon` component.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.33` | `0.6` |
|
||||
|
||||
<h4 id="version-7x-slides">Slides</h4>
|
||||
|
||||
`ion-slides`, `ion-slide`, and the `IonicSwiper` plugin have been removed from Ionic.
|
||||
|
||||
Developers using these components will need to migrate to using Swiper.js directly, optionally using the `IonicSlides` plugin. Guides for migration and usage are linked below:
|
||||
|
||||
- [Angular](https://ionicframework.com/docs/angular/slides)
|
||||
- [React](https://ionicframework.com/docs/react/slides)
|
||||
- [Vue](https://ionicframework.com/docs/vue/slides)
|
||||
|
||||
<h4 id="version-7x-textarea">Textarea</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-textarea` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the textarea and the textarea losing focus.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the textarea, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- `ionInput` dispatches an event detail of `null` when the textarea is cleared as a result of `clear-on-edit="true"`.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
|
||||
<h4 id="version-7x-toggle">Toggle</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `checked` property of `ion-toggle` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking the toggle to set it on or off.
|
||||
|
||||
- The `--background` and `--background-checked` variables have been renamed to `--track-background` and `--track-background-checked`, respectively.
|
||||
|
||||
<h4 id="version-7x-virtual-scroll">Virtual Scroll</h4>
|
||||
|
||||
`ion-virtual-scroll` has been removed from Ionic.
|
||||
|
||||
Developers using the component will need to migrate to a virtual scroll solution provided by their framework:
|
||||
|
||||
- [Angular](https://ionicframework.com/docs/angular/virtual-scroll)
|
||||
- [React](https://ionicframework.com/docs/react/virtual-scroll)
|
||||
- [Vue](https://ionicframework.com/docs/vue/virtual-scroll)
|
||||
|
||||
Any references to the virtual scroll types from `@ionic/core` have been removed. Please remove or replace these types: `Cell`, `VirtualNode`, `CellType`, `NodeChange`, `HeaderFn`, `ItemHeightFn`, `FooterHeightFn`, `ItemRenderFn` and `DomRenderFn`.
|
||||
|
||||
<h2 id="version-7x-config">Config</h2>
|
||||
|
||||
- `innerHTMLTemplatesEnabled` defaults to `false`. Developers who wish to use the `innerHTML` functionality inside of `ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, and `ion-toast` must set this config to `true` and properly sanitize their content.
|
||||
|
||||
<h2 id="version-7x-types">Types</h2>
|
||||
|
||||
<h4 id="version-7x-overlay-attribute-interfaces">Overlay Attribute Interfaces</h4>
|
||||
|
||||
`ActionSheetAttributes`, `AlertAttributes`, `AlertTextareaAttributes`, `AlertInputAttributes`, `LoadingAttributes`, `ModalAttributes`, `PickerAttributes`, `PopoverAttributes`, and `ToastAttributes` have been removed. Developers should use `{ [key: string]: any }` instead.
|
||||
|
||||
<h2 id="version-7x-javascript-frameworks">JavaScript Frameworks</h2>
|
||||
|
||||
<h4 id="version-7x-angular">Angular</h4>
|
||||
|
||||
- Angular v14 is now required to use `@ionic/angular` and `@ionic/angular-server`. Upgrade your project to Angular v14 by following the [Angular v14 update guide](https://update.angular.io/?l=3&v=13.0-14.0).
|
||||
|
||||
- `null` values on form components will no longer be converted to the empty string (`''`) or `false`. This impacts `ion-checkbox`, `ion-datetime`, `ion-input`, `ion-radio`, `ion-radio-group`, `ion-range`, `ion-searchbar`, `ion-segment`, `ion-select`, `ion-textarea`, and `ion-toggle`.
|
||||
|
||||
- The dev-preview `environmentInjector` property has been removed from `ion-tabs` and `ion-router-outlet`. Standalone component routing is now available without additional custom configuration. Remove the `environmentInjector` property from your `ion-tabs` and `ion-router-outlet` components.
|
||||
|
||||
<h4 id="version-7x-react">React</h4>
|
||||
|
||||
`@ionic/react` and `@ionic/react-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
|
||||
|
||||
<h4 id="version-7x-vue">Vue</h4>
|
||||
|
||||
`@ionic/vue` and `@ionic/vue-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
|
||||
|
||||
<h2 id="version-7x-css-utilities">CSS Utilities</h2>
|
||||
|
||||
<h4 id="version-7x-hidden-attribute">`hidden` attribute</h4>
|
||||
|
||||
The `[hidden]` attribute has been removed from Ionic's global stylesheet. The `[hidden]` attribute can continue to be used, but developers will get the [native `hidden` implementation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) instead. The main difference is that the native implementation is easier to override using `display` than Ionic's implementation.
|
||||
|
||||
Developers can add the following CSS to their global stylesheet if they need the old behavior:
|
||||
|
||||
```css
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
+ background: red;
|
||||
}
|
||||
```
|
||||
331
BREAKING_ARCHIVE/v7.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# Breaking Changes
|
||||
|
||||
## Version 7.x
|
||||
|
||||
- [Browser and Platform Support](#version-7x-browser-platform-support)
|
||||
- [Components](#version-7x-components)
|
||||
- [Accordion Group](#version-7x-accordion-group)
|
||||
- [Action Sheet](#version-7x-action-sheet)
|
||||
- [Back Button](#version-7x-back-button)
|
||||
- [Button](#version-7x-button)
|
||||
- [Card Header](#version-7x-card-header)
|
||||
- [Checkbox](#version-7x-checkbox)
|
||||
- [Datetime](#version-7x-datetime)
|
||||
- [Input](#version-7x-input)
|
||||
- [Item](#version-7x-item)
|
||||
- [Modal](#version-7x-modal)
|
||||
- [Overlays](#version-7x-overlays)
|
||||
- [Picker](#version-7x-picker)
|
||||
- [Radio Group](#version-7x-radio-group)
|
||||
- [Range](#version-7x-range)
|
||||
- [Searchbar](#version-7x-searchbar)
|
||||
- [Segment](#version-7x-segment)
|
||||
- [Select](#version-7x-select)
|
||||
- [Slides](#version-7x-slides)
|
||||
- [Textarea](#version-7x-textarea)
|
||||
- [Toggle](#version-7x-toggle)
|
||||
- [Virtual Scroll](#version-7x-virtual-scroll)
|
||||
- [Config](#version-7x-config)
|
||||
- [Types](#version-7x-types)
|
||||
- [Overlay Attribute Interfaces](#version-7x-overlay-attribute-interfaces)
|
||||
- [JavaScript Frameworks](#version-7x-javascript-frameworks)
|
||||
- [Angular](#version-7x-angular)
|
||||
- [React](#version-7x-react)
|
||||
- [Vue](#version-7x-vue)
|
||||
- [CSS Utilities](#version-7x-css-utilities)
|
||||
- [hidden attribute](#version-7x-hidden-attribute)
|
||||
|
||||
<h2 id="version-7x-browser-platform-support">Browser and Platform Support</h2>
|
||||
|
||||
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 7.
|
||||
|
||||
**Minimum Browser Versions**
|
||||
| Desktop Browser | Supported Versions |
|
||||
| --------------- | ----------------- |
|
||||
| Chrome | 79+ |
|
||||
| Safari | 14+ |
|
||||
| Firefox | 70+ |
|
||||
| Edge | 79+ |
|
||||
|
||||
**Minimum JavaScript Framework Versions**
|
||||
|
||||
| Framework | Supported Version |
|
||||
| --------- | --------------------- |
|
||||
| Angular | 14+ |
|
||||
| React | 17+ |
|
||||
| Vue | 3.0.6+ |
|
||||
|
||||
**Minimum Mobile Platform Versions**
|
||||
|
||||
| Platform | Supported Version |
|
||||
| -------- | ---------------------- |
|
||||
| iOS | 14+ |
|
||||
| Android | 5.1+ with Chromium 79+ |
|
||||
|
||||
<h2 id="version-7x-components">Components</h2>
|
||||
|
||||
<h4 id="version-7x-accordion-group">Accordion Group</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
|
||||
|
||||
- Accordion Group no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
|
||||
|
||||
<h4 id="version-7x-action-sheet">Action Sheet</h4>
|
||||
|
||||
- Action Sheet is updated to align with the design specification.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ---------- | -------------- | --------- |
|
||||
| `--height` | `100%` | `auto` |
|
||||
|
||||
<h4 id="version-7x-button">Button</h4>
|
||||
|
||||
- Button is updated to align with the design specification for iOS.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ---------------------------------- | -------------- | --------- |
|
||||
| `$button-ios-letter-spacing` | `-0.03em` | `0` |
|
||||
| `$button-ios-clear-letter-spacing` | `0` | Removed |
|
||||
| `$button-ios-height` | `2.8em` | `3.1em` |
|
||||
| `$button-ios-border-radius` | `10px` | `14px` |
|
||||
| `$button-ios-large-height` | `2.8em` | `3.1em` |
|
||||
| `$button-ios-large-border-radius` | `12px` | `16px` |
|
||||
|
||||
<h4 id="version-7x-back-button">Back Button</h4>
|
||||
|
||||
- Back Button is updated to align with the design specification for iOS.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ------------------- | -------------- | --------- |
|
||||
| `--icon-margin-end` | `-5px` | `1px` |
|
||||
| `--icon-font-size` | `1.85em` | `1.6em` |
|
||||
|
||||
<h4 id="version-7x-card-header">Card Header</h4>
|
||||
|
||||
- The card header has ben changed to a flex container with direction set to `column` (top to bottom). In `ios` mode the direction is set to `column-reverse` which results in the subtitle displaying on top of the title.
|
||||
|
||||
<h4 id="version-7x-checkbox">Checkbox</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `checked` property of `ion-checkbox` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the checkbox.
|
||||
|
||||
- The `--background` and `--background-checked` CSS variables have been renamed to `--checkbox-background` and `--checkbox-background-checked` respectively.
|
||||
|
||||
<h4 id="version-7x-datetime">Datetime</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` property of `ion-datetime` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping a date.
|
||||
|
||||
- Datetime no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
|
||||
|
||||
- Datetime no longer incorrectly reports the time zone when `value` is updated. Datetime does not manage time zones, so any time zone information provided is ignored.
|
||||
|
||||
- Passing the empty string to the `value` property will now error as it is not a valid ISO-8601 value.
|
||||
|
||||
- The haptics when swiping the wheel picker are now enabled only on iOS.
|
||||
|
||||
<h4 id="version-7x-input">Input</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus, clicking the clear action within the input, or pressing the "Enter" key.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the input, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
<h4 id="version-7x-item">Item</h4>
|
||||
|
||||
**Design tokens**
|
||||
|
||||
iOS:
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| --------------------- | -------------- | --------- |
|
||||
| `$item-ios-font-size` | `17px` | `16px` |
|
||||
| `--inner-padding-end` | `10px` | `16px` |
|
||||
| `--padding-start` | `20px` | `16px` |
|
||||
|
||||
<h4 id="version-7x-modal">Modal</h4>
|
||||
|
||||
- The `swipeToClose` property has been removed in favor of `canDismiss`.
|
||||
- The `canDismiss` property now defaults to `true` and can no longer be set to `undefined`.
|
||||
|
||||
<h4 id="version-7x-overlays">Overlays</h4>
|
||||
|
||||
Ionic now listens on the `keydown` event instead of the `keyup` event when determining when to dismiss overlays via the "Escape" key. Any applications that were listening on `keyup` to suppress this behavior should listen on `keydown` instead.
|
||||
|
||||
<h4 id="version-7x-picker">Picker</h4>
|
||||
|
||||
- The `refresh` key has been removed from the `PickerColumn` interface. Developers should use the `columns` property to refresh the `ion-picker` view.
|
||||
|
||||
<h4 id="version-7x-radio-group">Radio Group</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-radio-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping an `ion-radio` in the group.
|
||||
|
||||
<h4 id="version-7x-range">Range</h4>
|
||||
|
||||
- Range is updated to align with the design specification for supported modes.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
iOS:
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `--bar-border-radius` | `0px` | `$range-ios-bar-border-radius` (`2px` default) |
|
||||
| `--knob-size` | `28px` | `$range-ios-knob-width` (`26px` default) |
|
||||
| `$range-ios-bar-height` | `2px` | `4px` |
|
||||
| `$range-ios-bar-background-color` | `rgba(var(--ion-text-color-rgb, 0, 0, 0), .1)` | `var(--ion-color-step-900, #e6e6e6)` |
|
||||
| `$range-ios-knob-box-shadow` | `0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02)` | `0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12)` |
|
||||
| `$range-ios-knob-width` | `28px` | `26px` |
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-range` is modified externally. `ionChange` is only emitted from user committed changes, such as dragging and releasing the range knob or selecting a new value with the keyboard arrows.
|
||||
- If your application requires immediate feedback based on the user actively dragging the range knob, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property's value value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- Range no longer clamps assigned values within bounds. Developers will need to validate the value they are assigning to `ion-range` is within the `min` and `max` bounds when programmatically assigning a value.
|
||||
|
||||
- The `name` property defaults to `ion-r-${rangeIds++}` where `rangeIds` is a number that is incremented for every instance of `ion-range`.
|
||||
|
||||
<h4 id="version-7x-searchbar">Searchbar</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-searchbar` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the searchbar and the searchbar losing focus or pressing the "Enter" key.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the searchbar, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from 250 to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
|
||||
<h4 id="version-7x-segment">Segment</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-segment` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking a segment button or dragging to activate a segment button.
|
||||
|
||||
- The type signature of `value` supports `string | undefined`. Previously the type signature was `string | null | undefined`.
|
||||
- Developers needing to clear the checked segment item should assign a value of `''` instead of `null`.
|
||||
|
||||
<h4 id="version-7x-select">Select</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-select` is modified externally. `ionChange` is only emitted from user committed changes, such as confirming a selected option in the select's overlay.
|
||||
|
||||
- The `icon` CSS Shadow Part now targets an `ion-icon` component.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.33` | `0.6` |
|
||||
|
||||
<h4 id="version-7x-slides">Slides</h4>
|
||||
|
||||
`ion-slides`, `ion-slide`, and the `IonicSwiper` plugin have been removed from Ionic.
|
||||
|
||||
Developers using these components will need to migrate to using Swiper.js directly, optionally using the `IonicSlides` plugin. Guides for migration and usage are linked below:
|
||||
|
||||
- [Angular](https://ionicframework.com/docs/angular/slides)
|
||||
- [React](https://ionicframework.com/docs/react/slides)
|
||||
- [Vue](https://ionicframework.com/docs/vue/slides)
|
||||
|
||||
<h4 id="version-7x-textarea">Textarea</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `value` of `ion-textarea` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the textarea and the textarea losing focus.
|
||||
|
||||
- If your application requires immediate feedback based on the user typing actively in the textarea, consider migrating your event listeners to using `ionInput` instead.
|
||||
|
||||
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
|
||||
|
||||
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
|
||||
|
||||
- `ionInput` dispatches an event detail of `null` when the textarea is cleared as a result of `clear-on-edit="true"`.
|
||||
|
||||
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
|
||||
|
||||
**Design tokens**
|
||||
|
||||
| Token | Previous Value | New Value |
|
||||
| ----------------------- | -------------- | --------- |
|
||||
| `--placeholder-opacity` | `0.5` | `0.6` |
|
||||
|
||||
|
||||
<h4 id="version-7x-toggle">Toggle</h4>
|
||||
|
||||
- `ionChange` is no longer emitted when the `checked` property of `ion-toggle` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking the toggle to set it on or off.
|
||||
|
||||
- The `--background` and `--background-checked` variables have been renamed to `--track-background` and `--track-background-checked`, respectively.
|
||||
|
||||
<h4 id="version-7x-virtual-scroll">Virtual Scroll</h4>
|
||||
|
||||
`ion-virtual-scroll` has been removed from Ionic.
|
||||
|
||||
Developers using the component will need to migrate to a virtual scroll solution provided by their framework:
|
||||
|
||||
- [Angular](https://ionicframework.com/docs/angular/virtual-scroll)
|
||||
- [React](https://ionicframework.com/docs/react/virtual-scroll)
|
||||
- [Vue](https://ionicframework.com/docs/vue/virtual-scroll)
|
||||
|
||||
Any references to the virtual scroll types from `@ionic/core` have been removed. Please remove or replace these types: `Cell`, `VirtualNode`, `CellType`, `NodeChange`, `HeaderFn`, `ItemHeightFn`, `FooterHeightFn`, `ItemRenderFn` and `DomRenderFn`.
|
||||
|
||||
<h2 id="version-7x-config">Config</h2>
|
||||
|
||||
- `innerHTMLTemplatesEnabled` defaults to `false`. Developers who wish to use the `innerHTML` functionality inside of `ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, and `ion-toast` must set this config to `true` and properly sanitize their content.
|
||||
|
||||
<h2 id="version-7x-types">Types</h2>
|
||||
|
||||
<h4 id="version-7x-overlay-attribute-interfaces">Overlay Attribute Interfaces</h4>
|
||||
|
||||
`ActionSheetAttributes`, `AlertAttributes`, `AlertTextareaAttributes`, `AlertInputAttributes`, `LoadingAttributes`, `ModalAttributes`, `PickerAttributes`, `PopoverAttributes`, and `ToastAttributes` have been removed. Developers should use `{ [key: string]: any }` instead.
|
||||
|
||||
<h2 id="version-7x-javascript-frameworks">JavaScript Frameworks</h2>
|
||||
|
||||
<h4 id="version-7x-angular">Angular</h4>
|
||||
|
||||
- Angular v14 is now required to use `@ionic/angular` and `@ionic/angular-server`. Upgrade your project to Angular v14 by following the [Angular v14 update guide](https://update.angular.io/?l=3&v=13.0-14.0).
|
||||
|
||||
- `null` values on form components will no longer be converted to the empty string (`''`) or `false`. This impacts `ion-checkbox`, `ion-datetime`, `ion-input`, `ion-radio`, `ion-radio-group`, `ion-range`, `ion-searchbar`, `ion-segment`, `ion-select`, `ion-textarea`, and `ion-toggle`.
|
||||
|
||||
- The dev-preview `environmentInjector` property has been removed from `ion-tabs` and `ion-router-outlet`. Standalone component routing is now available without additional custom configuration. Remove the `environmentInjector` property from your `ion-tabs` and `ion-router-outlet` components.
|
||||
|
||||
<h4 id="version-7x-react">React</h4>
|
||||
|
||||
`@ionic/react` and `@ionic/react-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
|
||||
|
||||
<h4 id="version-7x-vue">Vue</h4>
|
||||
|
||||
`@ionic/vue` and `@ionic/vue-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
|
||||
|
||||
<h2 id="version-7x-css-utilities">CSS Utilities</h2>
|
||||
|
||||
<h4 id="version-7x-hidden-attribute">`hidden` attribute</h4>
|
||||
|
||||
The `[hidden]` attribute has been removed from Ionic's global stylesheet. The `[hidden]` attribute can continue to be used, but developers will get the [native `hidden` implementation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) instead. The main difference is that the native implementation is easier to override using `display` than Ionic's implementation.
|
||||
|
||||
Developers can add the following CSS to their global stylesheet if they need the old behavior:
|
||||
|
||||
```css
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
@@ -4,68 +4,68 @@
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66, 140, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
--ion-color-primary: #4d8dff;
|
||||
--ion-color-primary-rgb: 77, 141, 255;
|
||||
--ion-color-primary-contrast: #000000;
|
||||
--ion-color-primary-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-primary-shade: #447ce0;
|
||||
--ion-color-primary-tint: #5f98ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80, 200, 255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
--ion-color-secondary: #62bdff;
|
||||
--ion-color-secondary-rgb: 98, 189, 255;
|
||||
--ion-color-secondary-contrast: #000000;
|
||||
--ion-color-secondary-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-secondary-shade: #56a6e0;
|
||||
--ion-color-secondary-tint: #72c4ff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106, 100, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
--ion-color-tertiary: #8482fb;
|
||||
--ion-color-tertiary-rgb: 132, 130, 251;
|
||||
--ion-color-tertiary-contrast: #000000;
|
||||
--ion-color-tertiary-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-tertiary-shade: #7472dd;
|
||||
--ion-color-tertiary-tint: #908ffb;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47, 223, 117;
|
||||
--ion-color-success: #2dd55b;
|
||||
--ion-color-success-rgb: 45, 213, 91;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
--ion-color-success-shade: #28bb50;
|
||||
--ion-color-success-tint: #42d96b;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255, 213, 52;
|
||||
--ion-color-warning: #ffce31;
|
||||
--ion-color-warning-rgb: 255, 206, 49;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
--ion-color-warning-shade: #e0b52b;
|
||||
--ion-color-warning-tint: #ffd346;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255, 73, 97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
--ion-color-danger: #f56570;
|
||||
--ion-color-danger-rgb: 245, 101, 112;
|
||||
--ion-color-danger-contrast: #000000;
|
||||
--ion-color-danger-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-danger-shade: #d85963;
|
||||
--ion-color-danger-tint: #f6747e;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244, 245, 248;
|
||||
--ion-color-dark: #f3f3f3;
|
||||
--ion-color-dark-rgb: 243, 243, 243;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
--ion-color-dark-shade: #d6d6d6;
|
||||
--ion-color-dark-tint: #f4f4f4;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium: #959595;
|
||||
--ion-color-medium-rgb: 149, 149, 149;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
--ion-color-medium-shade: #838383;
|
||||
--ion-color-medium-tint: #a0a0a0;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34, 36, 40;
|
||||
--ion-color-light: #2f2f2f;
|
||||
--ion-color-light-rgb: 47, 47, 47;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
--ion-color-light-shade: #292929;
|
||||
--ion-color-light-tint: #444444;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Event, Host, Listen, Prop, h } from '@stencil/core';
|
||||
import { GESTURE_CONTROLLER } from '@utils/gesture';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
|
||||
@@ -13,10 +12,6 @@ import { getIonMode } from '../../global/ionic-global';
|
||||
shadow: true,
|
||||
})
|
||||
export class Backdrop implements ComponentInterface {
|
||||
private blocker = GESTURE_CONTROLLER.createBlocker({
|
||||
disableScroll: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* If `true`, the backdrop will be visible.
|
||||
*/
|
||||
@@ -37,16 +32,6 @@ export class Backdrop implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionBackdropTap!: EventEmitter<void>;
|
||||
|
||||
connectedCallback() {
|
||||
if (this.stopPropagation) {
|
||||
this.blocker.block();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.blocker.unblock();
|
||||
}
|
||||
|
||||
@Listen('click', { passive: false, capture: true })
|
||||
protected onMouseDown(ev: TouchEvent) {
|
||||
this.emitTap(ev);
|
||||
|
||||
@@ -54,10 +54,6 @@
|
||||
color: current-color(contrast);
|
||||
}
|
||||
|
||||
:host(.outer-content) {
|
||||
--background: #{$background-color-step-50};
|
||||
}
|
||||
|
||||
#background-content {
|
||||
@include position(calc(var(--offset-top) * -1), 0px,calc(var(--offset-bottom) * -1), 0px);
|
||||
|
||||
|
||||
@@ -34,16 +34,24 @@
|
||||
|
||||
// Calendar / Header / Action Buttons
|
||||
// -----------------------------------
|
||||
:host .calendar-action-buttons ion-item {
|
||||
--padding-start: #{$datetime-ios-padding};
|
||||
--background-hover: transparent;
|
||||
--background-activated: transparent;
|
||||
.calendar-month-year-toggle {
|
||||
@include padding(0px, 16px, 0px, #{$datetime-ios-padding});
|
||||
|
||||
min-height: 44px;
|
||||
|
||||
font-size: dynamic-font-max(16px, 1.6);
|
||||
font-weight: 600;
|
||||
|
||||
&.ion-focused::after {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item ion-icon,
|
||||
.calendar-month-year-toggle #toggle-wrapper {
|
||||
@include margin(10px, 8px, 10px, 0);
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons .calendar-month-year-toggle ion-icon,
|
||||
:host .calendar-action-buttons ion-buttons ion-button {
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
@@ -30,15 +30,41 @@
|
||||
|
||||
// Calendar / Header / Action Buttons
|
||||
// -----------------------------------
|
||||
:host .datetime-calendar .calendar-action-buttons ion-item {
|
||||
--padding-start: #{$datetime-md-header-padding};
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item,
|
||||
:host .calendar-action-buttons ion-button {
|
||||
--color: #{$text-color-step-350};
|
||||
}
|
||||
|
||||
.calendar-month-year-toggle {
|
||||
@include padding(12px, 16px, 12px, #{$datetime-md-header-padding});
|
||||
|
||||
min-height: 48px;
|
||||
|
||||
background: transparent;
|
||||
|
||||
color: #{$text-color-step-350};
|
||||
|
||||
z-index: 1;
|
||||
|
||||
&.ion-focused::after {
|
||||
opacity: 0.04;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-month-year-toggle ion-ripple-effect {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
.calendar-month-year-toggle.ion-activatable:not(.ion-focused):hover {
|
||||
&::after {
|
||||
background: currentColor;
|
||||
|
||||
opacity: 0.04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar / Header / Days of Week
|
||||
// -----------------------------------
|
||||
:host .calendar-days-of-week {
|
||||
@@ -64,7 +90,6 @@
|
||||
* if necessary.
|
||||
*/
|
||||
grid-template-rows: repeat(6, 1fr);
|
||||
|
||||
}
|
||||
|
||||
// Individual day button in month
|
||||
|
||||
@@ -48,7 +48,7 @@ ion-picker-column-internal {
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that the picker is apppropriately
|
||||
* This ensures that the picker is appropriately
|
||||
* sized and never truncates the text.
|
||||
*/
|
||||
:host(.datetime-size-fixed.datetime-prefer-wheel) {
|
||||
@@ -267,19 +267,8 @@ ion-picker-column-internal {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item,
|
||||
:host .calendar-action-buttons ion-button {
|
||||
--background: translucent;
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item ion-label {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item ion-icon {
|
||||
@include padding(0, 0, 0, 4px);
|
||||
--background: transparent;
|
||||
}
|
||||
|
||||
// Calendar / Header / Days of Week
|
||||
@@ -488,6 +477,55 @@ ion-picker-column-internal {
|
||||
|
||||
// Year Picker
|
||||
// -----------------------------------
|
||||
:host(.show-month-and-year) .calendar-action-buttons ion-item {
|
||||
--color: #{current-color(base)};
|
||||
:host(.show-month-and-year) .calendar-action-buttons .calendar-month-year-toggle {
|
||||
color: #{current-color(base)};
|
||||
}
|
||||
|
||||
.calendar-month-year {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.calendar-month-year-toggle {
|
||||
@include text-inherit();
|
||||
|
||||
position: relative;
|
||||
|
||||
border: 0;
|
||||
|
||||
outline: none;
|
||||
|
||||
background: transparent;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
&::after {
|
||||
@include button-state();
|
||||
|
||||
transition: opacity 15ms linear, background-color 15ms linear;
|
||||
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.ion-focused::after {
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-month-year-toggle ion-icon {
|
||||
@include padding(0, 0, 0, 4px);
|
||||
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.calendar-month-year-toggle #toggle-wrapper {
|
||||
display: inline-flex;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ import {
|
||||
export class Datetime implements ComponentInterface {
|
||||
private inputId = `ion-dt-${datetimeIds++}`;
|
||||
private calendarBodyRef?: HTMLElement;
|
||||
private monthYearToggleItemRef?: HTMLIonItemElement;
|
||||
private popoverRef?: HTMLIonPopoverElement;
|
||||
private clearFocusVisible?: () => void;
|
||||
private parsedMinuteValues?: number[];
|
||||
@@ -2020,35 +2019,18 @@ export class Datetime implements ComponentInterface {
|
||||
<div class="calendar-header">
|
||||
<div class="calendar-action-buttons">
|
||||
<div class="calendar-month-year">
|
||||
<ion-item
|
||||
part="month-year-button"
|
||||
ref={(el) => (this.monthYearToggleItemRef = el)}
|
||||
button
|
||||
aria-label="Show year picker"
|
||||
detail={false}
|
||||
lines="none"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
this.toggleMonthAndYearView();
|
||||
/**
|
||||
* TODO: FW-3547
|
||||
*
|
||||
* Currently there is not a way to set the aria-label on the inner button
|
||||
* on the `ion-item` and have it be reactive to changes. This is a workaround
|
||||
* until we either refactor `ion-item` to a button or Stencil adds a way to
|
||||
* have reactive props for built-in properties, such as `aria-label`.
|
||||
*/
|
||||
const { monthYearToggleItemRef } = this;
|
||||
if (monthYearToggleItemRef) {
|
||||
const btn = monthYearToggleItemRef.shadowRoot?.querySelector('.item-native');
|
||||
if (btn) {
|
||||
const monthYearAriaLabel = this.showMonthAndYear ? 'Hide year picker' : 'Show year picker';
|
||||
btn.setAttribute('aria-label', monthYearAriaLabel);
|
||||
}
|
||||
}
|
||||
<button
|
||||
class={{
|
||||
'calendar-month-year-toggle': true,
|
||||
'ion-activatable': true,
|
||||
'ion-focusable': true,
|
||||
}}
|
||||
part="month-year-button"
|
||||
disabled={disabled}
|
||||
aria-label={this.showMonthAndYear ? 'Hide year picker' : 'Show year picker'}
|
||||
onClick={() => this.toggleMonthAndYearView()}
|
||||
>
|
||||
<ion-label>
|
||||
<span id="toggle-wrapper">
|
||||
{getMonthAndYear(this.locale, this.workingParts)}
|
||||
<ion-icon
|
||||
aria-hidden="true"
|
||||
@@ -2056,8 +2038,9 @@ export class Datetime implements ComponentInterface {
|
||||
lazy={false}
|
||||
flipRtl={true}
|
||||
></ion-icon>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</span>
|
||||
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="calendar-next-prev">
|
||||
|
||||
@@ -47,7 +47,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
const monthYearButton = page.locator('.calendar-month-year-toggle');
|
||||
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
|
||||
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@@ -1,7 +1,6 @@
|
||||
import type { SpecPage } from '@stencil/core/testing';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Item } from '../../../item/item';
|
||||
import { Datetime } from '../../datetime';
|
||||
|
||||
describe('datetime', () => {
|
||||
@@ -20,15 +19,14 @@ describe('datetime', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newSpecPage({
|
||||
components: [Datetime, Item],
|
||||
components: [Datetime],
|
||||
html: `<ion-datetime></ion-datetime>`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have aria-label "Show year picker" when collapsed', async () => {
|
||||
const datetime = page.body.querySelector('ion-datetime')!;
|
||||
const item = datetime.shadowRoot!.querySelector('.calendar-month-year ion-item');
|
||||
const monthYearToggleBtn = item!.shadowRoot!.querySelector('button');
|
||||
const monthYearToggleBtn = datetime.shadowRoot!.querySelector('.calendar-month-year .calendar-month-year-toggle');
|
||||
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
|
||||
|
||||
expect(ariaLabel).toContain('Show year picker');
|
||||
@@ -36,15 +34,18 @@ describe('datetime', () => {
|
||||
|
||||
it('should have aria-label "Hide year picker" when expanded', async () => {
|
||||
const datetime = page.body.querySelector('ion-datetime')!;
|
||||
const item = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
|
||||
const monthYearToggleBtn = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
|
||||
'.calendar-month-year .calendar-month-year-toggle'
|
||||
);
|
||||
|
||||
item!.click();
|
||||
monthYearToggleBtn!.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const itemAfter = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
|
||||
const monthYearToggleBtn = itemAfter!.shadowRoot!.querySelector<HTMLElement>('button');
|
||||
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
|
||||
const monthYearToggleBtnAfter = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
|
||||
'.calendar-month-year .calendar-month-year-toggle'
|
||||
);
|
||||
const ariaLabel = monthYearToggleBtnAfter!.getAttribute('aria-label');
|
||||
|
||||
expect(ariaLabel).toContain('Hide year picker');
|
||||
});
|
||||
|
||||
@@ -526,4 +526,20 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-calendar-day`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('datetime: calendar month toggle'), () => {
|
||||
test('should have focus styles', async ({ page }) => {
|
||||
await page.setContent('<ion-datetime value="2021-01-01"></ion-datetime>', config);
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const monthYearToggle = datetime.locator('.calendar-month-year-toggle');
|
||||
|
||||
monthYearToggle.evaluate((el: HTMLElement) => el.classList.add('ion-focused'));
|
||||
|
||||
await expect(datetime).toHaveScreenshot(screenshot(`date-month-toggle-focused`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -15,7 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const datetimeFooter = page.locator('#date-time .datetime-footer');
|
||||
await expect(datetimeFooter).toBeVisible();
|
||||
|
||||
const pickerButton = page.locator('#date-time .calendar-month-year > ion-item');
|
||||
const pickerButton = page.locator('#date-time .calendar-month-year > .calendar-month-year-toggle');
|
||||
await pickerButton.click();
|
||||
await page.waitForChanges();
|
||||
await expect(datetimeFooter).not.toBeVisible();
|
||||
|
||||
@@ -68,7 +68,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
const monthYearButton = page.locator('.calendar-month-year-toggle');
|
||||
await expect(calendarMonthYear).toHaveText('February 2022');
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
@@ -114,7 +114,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
const monthYearButton = page.locator('.calendar-month-year-toggle');
|
||||
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
|
||||
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
|
||||
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
|
||||
|
||||
@@ -52,7 +52,7 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
width!: number;
|
||||
_isOpen = false;
|
||||
|
||||
backdropEl?: HTMLElement;
|
||||
backdropEl?: HTMLIonBackdropElement;
|
||||
menuInnerEl?: HTMLElement;
|
||||
contentEl?: HTMLElement;
|
||||
lastFocus?: HTMLElement;
|
||||
|
||||
89
core/src/test/theme/dark/index.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Theme - Dark</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<link href="../../../../scripts/testing/themes/dark.css" rel="stylesheet" />
|
||||
<script src="../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Theme - Dark</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding ion-text-center" id="content" no-bounce>
|
||||
<p>
|
||||
<ion-button id="default">Default</ion-button>
|
||||
<ion-button class="ion-focused">Default.focused</ion-button>
|
||||
<ion-button class="ion-activated">Default.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="primary">Primary</ion-button>
|
||||
<ion-button class="ion-focused" color="primary">Primary.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="primary">Primary.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="secondary">Secondary</ion-button>
|
||||
<ion-button class="ion-focused" color="secondary">Secondary.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="secondary">Secondary.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="tertiary">Tertiary</ion-button>
|
||||
<ion-button class="ion-focused" color="tertiary">Tertiary.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="tertiary">Tertiary.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="success">Success</ion-button>
|
||||
<ion-button class="ion-focused" color="success">Success.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="success">Success.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="warning">Warning</ion-button>
|
||||
<ion-button class="ion-focused" color="warning">Warning.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="warning">Warning.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="danger">Danger</ion-button>
|
||||
<ion-button class="ion-focused" color="danger">Danger.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="danger">Danger.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="light">Light</ion-button>
|
||||
<ion-button class="ion-focused" color="light">Light.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="light">Light.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="medium">Medium</ion-button>
|
||||
<ion-button class="ion-focused" color="medium">Medium.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="medium">Medium.activated</ion-button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-button color="dark">Dark</ion-button>
|
||||
<ion-button class="ion-focused" color="dark">Dark.focused</ion-button>
|
||||
<ion-button class="ion-activated" color="dark">Dark.activated</ion-button>
|
||||
</p>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
214
core/src/test/theme/theme.e2e.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
const styleTestHelpers = `
|
||||
<style>
|
||||
.ion-background {
|
||||
background: var(--ion-color-base);
|
||||
}
|
||||
.ion-background-opacity-08 {
|
||||
background: rgba(var(--ion-color-base-rgb), 0.08);
|
||||
}
|
||||
.ion-background-opacity-12 {
|
||||
background: rgba(var(--ion-color-base-rgb), 0.12);
|
||||
}
|
||||
.ion-background-opacity-16 {
|
||||
background: rgba(var(--ion-color-base-rgb), 0.16);
|
||||
}
|
||||
.ion-color {
|
||||
color: var(--ion-color-base);
|
||||
}
|
||||
.ion-color-contrast {
|
||||
color: var(--ion-color-contrast);
|
||||
}
|
||||
.ion-color-shade {
|
||||
color: var(--ion-color-shade);
|
||||
}
|
||||
.ion-color-tint {
|
||||
color: var(--ion-color-tint);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
/**
|
||||
* All colors besides `light` should be tested against a dark background on dark theme.
|
||||
*/
|
||||
configs({ modes: ['md', 'ios'], directions: ['ltr'], themes: ['dark'] }).forEach(({ config, title }) => {
|
||||
const colors = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'medium', 'dark'];
|
||||
|
||||
test.describe(title('theme'), () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.browser('firefox', 'Color contrast ratio is consistent across browsers');
|
||||
skip.browser('webkit', 'Color contrast ratio is consistent across browsers');
|
||||
});
|
||||
|
||||
for (const color of colors) {
|
||||
test(`color "${color}" should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<main>
|
||||
<p class="ion-color ion-color-${color}">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`contrast color on "${color}" background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<main>
|
||||
<p class="ion-color-contrast ion-background ion-color-${color}">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "${color}" on 0.08 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<main>
|
||||
<p class="ion-color ion-color-${color} ion-background-opacity-08">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "${color}" on 0.12 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<main>
|
||||
<p class="ion-color ion-color-${color} ion-background-opacity-12">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "${color}" on 0.16 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<main>
|
||||
<p class="ion-color ion-color-${color} ion-background-opacity-16">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `light` color should be tested against a white background on dark theme. The mode doesn't matter here
|
||||
* since we are testing against a consistent background color.
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'], themes: ['dark'] }).forEach(({ config, title }) => {
|
||||
test.describe(title('theme'), () => {
|
||||
test(`color light should pass AA guidelines on a white background`, async ({ page, skip }) => {
|
||||
skip.browser('firefox', 'Color contrast ratio is consistent across browsers');
|
||||
skip.browser('webkit', 'Color contrast ratio is consistent across browsers');
|
||||
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<style>
|
||||
.md body {
|
||||
--ion-background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<p class="ion-color ion-color-light">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`contrast color on "light" background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<style>
|
||||
.md body {
|
||||
--ion-background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<p class="ion-color-contrast ion-background ion-color-light">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "light" on 0.08 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<style>
|
||||
.md body {
|
||||
--ion-background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<p class="ion-color ion-color-light ion-background-opacity-08">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "light" on 0.12 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<style>
|
||||
.md body {
|
||||
--ion-background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<p class="ion-color ion-color-light ion-background-opacity-12">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test(`color "light" on 0.16 opacity background should pass AA guidelines`, async ({ page }) => {
|
||||
await page.setContent(
|
||||
`${styleTestHelpers}
|
||||
<style>
|
||||
.md body {
|
||||
--ion-background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<p class="ion-color ion-color-light ion-background-opacity-16">Hello World</p>
|
||||
</main>`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -243,5 +243,5 @@ export interface BlockerConfig {
|
||||
disableScroll?: boolean;
|
||||
}
|
||||
|
||||
const BACKDROP_NO_SCROLL = 'backdrop-no-scroll';
|
||||
export const BACKDROP_NO_SCROLL = 'backdrop-no-scroll';
|
||||
export const GESTURE_CONTROLLER = new GestureController();
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
} from '../interface';
|
||||
|
||||
import { CoreDelegate } from './framework-delegate';
|
||||
import { BACKDROP_NO_SCROLL } from './gesture/gesture-controller';
|
||||
import { OVERLAY_BACK_BUTTON_PRIORITY } from './hardware-back-button';
|
||||
import { addEventListener, componentOnReady, focusElement, getElementRoot, removeEventListener } from './helpers';
|
||||
import { printIonWarning } from './logging';
|
||||
@@ -471,6 +472,8 @@ export const present = async <OverlayPresentOptions>(
|
||||
|
||||
setRootAriaHidden(true);
|
||||
|
||||
document.body.classList.add(BACKDROP_NO_SCROLL);
|
||||
|
||||
overlay.presented = true;
|
||||
overlay.willPresent.emit();
|
||||
overlay.willPresentShorthand?.emit();
|
||||
@@ -549,12 +552,15 @@ export const dismiss = async <OverlayDismissOptions>(
|
||||
return false;
|
||||
}
|
||||
|
||||
const lastOverlay = doc !== undefined && getPresentedOverlays(doc).length === 1;
|
||||
|
||||
/**
|
||||
* If this is the last visible overlay then
|
||||
* we want to re-add the root to the accessibility tree.
|
||||
*/
|
||||
if (doc !== undefined && getPresentedOverlays(doc).length === 1) {
|
||||
if (lastOverlay) {
|
||||
setRootAriaHidden(false);
|
||||
document.body.classList.remove(BACKDROP_NO_SCROLL);
|
||||
}
|
||||
|
||||
overlay.presented = false;
|
||||
@@ -574,6 +580,7 @@ export const dismiss = async <OverlayDismissOptions>(
|
||||
if (role !== GESTURE) {
|
||||
await overlayAnimation(overlay, animationBuilder, overlay.el, opts);
|
||||
}
|
||||
|
||||
overlay.didDismiss.emit({ data, role });
|
||||
overlay.didDismissShorthand?.emit({ data, role });
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Modal } from '../../../components/modal/modal';
|
||||
|
||||
describe('overlays: scroll blocking', () => {
|
||||
it('should not block scroll when the overlay is created', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const body = page.doc.querySelector('body')!;
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
|
||||
it('should block scroll when the overlay is presented', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const body = page.doc.querySelector('body')!;
|
||||
|
||||
await modal.present();
|
||||
|
||||
expect(body).toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modal.dismiss();
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
|
||||
it('should not block scroll when the overlay is dismissed', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const body = page.doc.querySelector('body')!;
|
||||
|
||||
await modal.present();
|
||||
|
||||
expect(body).toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modal.dismiss();
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
|
||||
it('should not enable scroll until last overlay is dismissed', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal id="one"></ion-modal>
|
||||
<ion-modal id="two"></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const modalOne = page.body.querySelector('#one') as HTMLIonModalElement;
|
||||
const modalTwo = page.body.querySelector('#two') as HTMLIonModalElement;
|
||||
const body = page.doc.querySelector('body')!;
|
||||
|
||||
await modalOne.present();
|
||||
|
||||
expect(body).toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modalTwo.present();
|
||||
|
||||
expect(body).toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modalOne.dismiss();
|
||||
|
||||
expect(body).toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modalTwo.dismiss();
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
});
|
||||
@@ -111,4 +111,5 @@ const DEFAULT_THEMES: Theme[] = ['light'];
|
||||
const DEFAULT_TEST_CONFIG_OPTION = {
|
||||
modes: DEFAULT_MODES,
|
||||
directions: DEFAULT_DIRECTIONS,
|
||||
themes: DEFAULT_THEMES,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Page, TestInfo } from '@playwright/test';
|
||||
import type { E2EPageOptions, Mode, Direction } from '@utils/test/playwright';
|
||||
import type { E2EPageOptions, Mode, Direction, Theme } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This is an extended version of Playwright's
|
||||
@@ -28,13 +28,22 @@ configs().forEach(({ config, title }) => {
|
||||
|
||||
let mode: Mode;
|
||||
let direction: Direction;
|
||||
let theme: Theme;
|
||||
|
||||
if (options == undefined) {
|
||||
mode = testInfo.project.metadata.mode;
|
||||
direction = testInfo.project.metadata.rtl ? 'rtl' : 'ltr';
|
||||
theme = testInfo.project.metadata.theme;
|
||||
} else {
|
||||
mode = options.mode;
|
||||
direction = options.direction;
|
||||
theme = options.theme;
|
||||
}
|
||||
|
||||
if (theme !== 'light' && options?.inline === undefined) {
|
||||
throw new Error(
|
||||
'The "themes" config option is only supported when using "page.setContent". Remove the theme from the config and manually set the theme in the test template.'
|
||||
);
|
||||
}
|
||||
|
||||
const rtlString = direction === 'rtl' ? 'true' : undefined;
|
||||
|
||||
@@ -82,6 +82,6 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto(`${baseUrl}#`, options);
|
||||
await page.goto(`${baseUrl}#`, { ...options, inline: true } as E2EPageOptions);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,13 @@ import type { TestConfig } from './generator';
|
||||
import type { EventSpy } from './page/event-spy';
|
||||
import type { LocatorOptions, E2ELocator } from './page/utils/locator';
|
||||
|
||||
export interface E2EPageOptions extends PageOptions, TestConfig {}
|
||||
export interface E2EPageOptions extends PageOptions, TestConfig {
|
||||
/**
|
||||
* Indicates if the page content is set inline with `.setContent`. This setting can
|
||||
* only be true or not set.
|
||||
*/
|
||||
inline?: true;
|
||||
}
|
||||
|
||||
interface PageOptions {
|
||||
/**
|
||||
|
||||
@@ -58,7 +58,6 @@ const getAngularOutputTargets = () => {
|
||||
'ion-textarea',
|
||||
'ion-searchbar',
|
||||
'ion-datetime',
|
||||
'ion-radio',
|
||||
'ion-segment',
|
||||
'ion-checkbox',
|
||||
'ion-toggle',
|
||||
|
||||
@@ -37,9 +37,9 @@ export class NavController {
|
||||
if (router) {
|
||||
router.events.subscribe((ev) => {
|
||||
if (ev instanceof NavigationStart) {
|
||||
// restoredState is set if the browser back/forward button is used
|
||||
const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;
|
||||
this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
|
||||
this.guessAnimation = !ev.restoredState ? this.guessDirection : undefined;
|
||||
this.guessDirection = this.guessAnimation = id < this.lastNavId ? 'back' : 'forward';
|
||||
this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './boolean-value-accessor';
|
||||
export * from './numeric-value-accessor';
|
||||
export * from './radio-value-accessor';
|
||||
export * from './select-value-accessor';
|
||||
export * from './text-value-accessor';
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
|
||||
@Directive({
|
||||
/* tslint:disable-next-line:directive-selector */
|
||||
selector: 'ion-radio',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: RadioValueAccessorDirective,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class RadioValueAccessorDirective extends ValueAccessor {
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
}
|
||||
|
||||
@HostListener('ionSelect', ['$event.target'])
|
||||
_handleIonSelect(el: any): void {
|
||||
/**
|
||||
* The `el` type is any to access the `checked` state property
|
||||
* that is not exposed on the type interface.
|
||||
*/
|
||||
this.handleValueChange(el, el.checked);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// DIRECTIVES
|
||||
export { BooleanValueAccessorDirective as BooleanValueAccessor } from './directives/control-value-accessors/boolean-value-accessor';
|
||||
export { NumericValueAccessorDirective as NumericValueAccessor } from './directives/control-value-accessors/numeric-value-accessor';
|
||||
export { RadioValueAccessorDirective as RadioValueAccessor } from './directives/control-value-accessors/radio-value-accessor';
|
||||
export { SelectValueAccessorDirective as SelectValueAccessor } from './directives/control-value-accessors/select-value-accessor';
|
||||
export { TextValueAccessorDirective as TextValueAccessor } from './directives/control-value-accessors/text-value-accessor';
|
||||
export { IonTabs } from './directives/navigation/ion-tabs';
|
||||
|
||||
@@ -7,7 +7,6 @@ import { appInitialize } from './app-initialize';
|
||||
import {
|
||||
BooleanValueAccessorDirective,
|
||||
NumericValueAccessorDirective,
|
||||
RadioValueAccessorDirective,
|
||||
SelectValueAccessorDirective,
|
||||
TextValueAccessorDirective,
|
||||
} from './directives/control-value-accessors';
|
||||
@@ -37,7 +36,6 @@ const DECLARATIONS = [
|
||||
// ngModel accessors
|
||||
BooleanValueAccessorDirective,
|
||||
NumericValueAccessorDirective,
|
||||
RadioValueAccessorDirective,
|
||||
SelectValueAccessorDirective,
|
||||
TextValueAccessorDirective,
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ export * from './datetime';
|
||||
export * from './icon';
|
||||
export * from './input';
|
||||
export * from './radio-group';
|
||||
export * from './radio';
|
||||
export * from './range';
|
||||
export * from './searchbar';
|
||||
export * from './segment';
|
||||
|
||||
@@ -53,6 +53,7 @@ import { defineCustomElement as defineIonNavLink } from '@ionic/core/components/
|
||||
import { defineCustomElement as defineIonNote } from '@ionic/core/components/ion-note.js';
|
||||
import { defineCustomElement as defineIonPicker } from '@ionic/core/components/ion-picker.js';
|
||||
import { defineCustomElement as defineIonProgressBar } from '@ionic/core/components/ion-progress-bar.js';
|
||||
import { defineCustomElement as defineIonRadio } from '@ionic/core/components/ion-radio.js';
|
||||
import { defineCustomElement as defineIonRefresher } from '@ionic/core/components/ion-refresher.js';
|
||||
import { defineCustomElement as defineIonRefresherContent } from '@ionic/core/components/ion-refresher-content.js';
|
||||
import { defineCustomElement as defineIonReorder } from '@ionic/core/components/ion-reorder.js';
|
||||
@@ -1494,6 +1495,40 @@ export class IonProgressBar {
|
||||
export declare interface IonProgressBar extends Components.IonProgressBar {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonRadio,
|
||||
inputs: ['alignment', 'color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-radio',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['alignment', 'color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'],
|
||||
standalone: true
|
||||
})
|
||||
export class IonRadio {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonRadio extends Components.IonRadio {
|
||||
/**
|
||||
* Emitted when the radio button has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the radio button loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonRefresher,
|
||||
inputs: ['closeDuration', 'disabled', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration'],
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
forwardRef,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-radio.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const RADIO_INPUTS = ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'];
|
||||
|
||||
/**
|
||||
* Pulling the provider into an object and using PURE works
|
||||
* around an ng-packagr issue that causes
|
||||
* components with multiple decorators and
|
||||
* a provider to be re-assigned. This re-assignment
|
||||
* is not supported by Webpack and causes treeshaking
|
||||
* to not work on these kinds of components.
|
||||
*/
|
||||
const accessorProvider = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: /*@__PURE__*/ forwardRef(() => IonRadio),
|
||||
multi: true,
|
||||
};
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: RADIO_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-radio',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: RADIO_INPUTS,
|
||||
providers: [accessorProvider],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonRadio extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
@HostListener('ionSelect', ['$event.target'])
|
||||
handleIonSelect(el: any): void {
|
||||
/**
|
||||
* The `el` type is any to access the `checked` state property
|
||||
* that is not exposed on the type interface.
|
||||
*/
|
||||
this.handleValueChange(el, el.checked);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonRadio extends Components.IonRadio {
|
||||
/**
|
||||
* Emitted when the radio button has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the radio button loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
@@ -34,7 +34,6 @@ export {
|
||||
IonInput,
|
||||
IonIcon,
|
||||
IonRadioGroup,
|
||||
IonRadio,
|
||||
IonRange,
|
||||
IonSearchbar,
|
||||
IonSegment,
|
||||
|
||||
@@ -32,7 +32,8 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: false
|
||||
checkbox: false,
|
||||
radio: null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +60,8 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: false
|
||||
checkbox: false,
|
||||
radio: null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +75,8 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: false
|
||||
checkbox: false,
|
||||
radio: null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,7 +90,23 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: true
|
||||
checkbox: true,
|
||||
radio: null
|
||||
});
|
||||
});
|
||||
|
||||
it('ion-radio should change', () => {
|
||||
cy.get('ion-radio').click();
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: false,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: false,
|
||||
radio: 'nes'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,7 +128,8 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: false
|
||||
checkbox: false,
|
||||
radio: null
|
||||
});
|
||||
cy.get('ion-checkbox').click();
|
||||
testData({
|
||||
@@ -120,7 +140,8 @@ describe('Form', () => {
|
||||
input2: 'Default Value',
|
||||
inputMin: 1,
|
||||
inputMax: 1,
|
||||
checkbox: true
|
||||
checkbox: true,
|
||||
radio: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ describe('Inputs', () => {
|
||||
|
||||
it('should have default value', () => {
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-radio-group').should('have.prop', 'value').and('equal', 'nes');
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
|
||||
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
|
||||
@@ -15,6 +16,7 @@ describe('Inputs', () => {
|
||||
cy.get('#reset-button').click();
|
||||
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', false);
|
||||
cy.get('ion-radio-group').should('not.have.prop', 'value');
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', false);
|
||||
/**
|
||||
* The `value` property gets set to undefined
|
||||
@@ -32,6 +34,7 @@ describe('Inputs', () => {
|
||||
cy.get('#set-button').click();
|
||||
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-radio-group').should('have.prop', 'value').and('equal', 'nes');
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
|
||||
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
|
||||
@@ -42,6 +45,7 @@ describe('Inputs', () => {
|
||||
cy.get('#reset-button').click();
|
||||
|
||||
cy.get('ion-checkbox#first-checkbox').click();
|
||||
cy.get('ion-radio').first().click();
|
||||
cy.get('ion-toggle').first().click();
|
||||
|
||||
cy.get('ion-input').eq(0).type('hola');
|
||||
@@ -58,6 +62,7 @@ describe('Inputs', () => {
|
||||
cy.get('ion-alert .alert-button:not(.alert-button-role-cancel)').click();
|
||||
|
||||
cy.get('#checkbox-note').should('have.text', 'true');
|
||||
cy.get('#radio-note').should('have.text', 'nes');
|
||||
cy.get('#toggle-note').should('have.text', 'true');
|
||||
cy.get('#input-note').should('have.text', 'hola');
|
||||
cy.get('#datetime-note').should('have.text', '1994-03-14');
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
<ion-checkbox formControlName="checkbox"> Checkbox </ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-radio-group formControlName="radio">
|
||||
<ion-radio value="nes">Radio</ion-radio>
|
||||
</ion-radio-group>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Min</ion-label>
|
||||
<ion-input formControlName="inputMin" type="number"></ion-input>
|
||||
|
||||
@@ -20,7 +20,8 @@ export class FormComponent {
|
||||
input2: ['Default Value'],
|
||||
inputMin: [1, Validators.min(1)],
|
||||
inputMax: [1, Validators.max(1)],
|
||||
checkbox: [false]
|
||||
checkbox: [false],
|
||||
radio: [undefined]
|
||||
}, {
|
||||
updateOn: typeof (window as any) !== 'undefined' && window.location.hash === '#blur' ? 'blur' : 'change'
|
||||
});
|
||||
@@ -44,7 +45,8 @@ export class FormComponent {
|
||||
toggle: true,
|
||||
input: 'Some value',
|
||||
input2: 'Another values',
|
||||
checkbox: true
|
||||
checkbox: true,
|
||||
radio: 'nes'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,20 @@
|
||||
<ion-note slot="end">{{checkbox}}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-radio-group value="nes" [(ngModel)]="radio" id="first-radio">
|
||||
<ion-radio value="nes">Radio</ion-radio>
|
||||
</ion-radio-group>
|
||||
<ion-note slot="end" id="radio-note">{{radio}}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<ion-item color="dark">
|
||||
<ion-radio-group value="nes" [(ngModel)]="radio">
|
||||
<ion-radio value="nes">Radio Mirror</ion-radio>
|
||||
</ion-radio-group>
|
||||
<ion-note slot="end">{{radio}}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
<p>
|
||||
<ion-button (click)="setValues()" id="set-button">Set values</ion-button>
|
||||
|
||||
@@ -9,6 +9,7 @@ export class InputsComponent {
|
||||
datetime? = '1994-03-15';
|
||||
input? = 'some text';
|
||||
checkbox = true;
|
||||
radio? = 'nes';
|
||||
toggle = true;
|
||||
select? = 'nes';
|
||||
changes = 0;
|
||||
@@ -18,6 +19,7 @@ export class InputsComponent {
|
||||
this.datetime = '1994-03-15';
|
||||
this.input = 'some text';
|
||||
this.checkbox = true;
|
||||
this.radio = 'nes';
|
||||
this.toggle = true;
|
||||
this.select = 'nes';
|
||||
}
|
||||
@@ -27,6 +29,7 @@ export class InputsComponent {
|
||||
this.datetime = undefined;
|
||||
this.input = undefined;
|
||||
this.checkbox = false;
|
||||
this.radio = undefined;
|
||||
this.toggle = false;
|
||||
this.select = undefined;
|
||||
}
|
||||
|
||||