Compare commits
1 Commits
5580-test
...
sp/stencil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
395b14317f |
4
.github/CODEOWNERS
vendored
@@ -51,8 +51,8 @@
|
||||
/core/src/components/nav/ @sean-perkins
|
||||
/core/src/components/nav-link/ @sean-perkins
|
||||
|
||||
/core/src/components/picker/ @liamdebeasi
|
||||
/core/src/components/picker-column/ @liamdebeasi
|
||||
/core/src/components/picker-internal/ @liamdebeasi
|
||||
/core/src/components/picker-column-internal/ @liamdebeasi
|
||||
|
||||
/core/src/components/radio/ @amandaejohnston
|
||||
/core/src/components/radio-group/ @amandaejohnston
|
||||
|
||||
339
BREAKING.md
@@ -4,65 +4,338 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
||||
|
||||
## Versions
|
||||
|
||||
- [Version 8.x](#version-8x)
|
||||
- [Version 7.x](./BREAKING_ARCHIVE/v7.md)
|
||||
- [Version 7.x](#version-7x)
|
||||
- [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 8.x
|
||||
## Version 7.x
|
||||
|
||||
- [Browser and Platform Support](#version-8x-browser-platform-support)
|
||||
- [Components](#version-8x-components)
|
||||
- [Content](#version-8x-content)
|
||||
- [Datetime](#version-8x-datetime)
|
||||
- [Picker](#version-8x-picker)
|
||||
- [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-8x-browser-platform-support">Browser and Platform Support</h2>
|
||||
<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 8.
|
||||
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 | 89+ |
|
||||
| Safari | 15+ |
|
||||
| Firefox | 75+ |
|
||||
| Edge | 89+ |
|
||||
| Chrome | 79+ |
|
||||
| Safari | 14+ |
|
||||
| Firefox | 70+ |
|
||||
| Edge | 79+ |
|
||||
|
||||
**Minimum JavaScript Framework Versions**
|
||||
|
||||
| Framework | Supported Version |
|
||||
| --------- | --------------------- |
|
||||
| Angular | 16+ |
|
||||
| Angular | 14+ |
|
||||
| React | 17+ |
|
||||
| Vue | 3.0.6+ |
|
||||
|
||||
**Minimum Mobile Platform Versions**
|
||||
|
||||
| Platform | Supported Version |
|
||||
| -------- | ---------------------- |
|
||||
| iOS | 15+ |
|
||||
| Android | 5.1+ with Chromium 89+ |
|
||||
| iOS | 14+ |
|
||||
| Android | 5.1+ with Chromium 79+ |
|
||||
|
||||
<h2 id="version-8x-components">Components</h2>
|
||||
<h2 id="version-7x-components">Components</h2>
|
||||
|
||||
<h4 id="version-8x-content">Content</h4>
|
||||
<h4 id="version-7x-accordion-group">Accordion Group</h4>
|
||||
|
||||
- Content no longer sets the `--background` custom property when the `.outer-content` class is set on the host.
|
||||
- `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.
|
||||
|
||||
<h4 id="version-8x-datetime">Datetime</h4>
|
||||
- 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.
|
||||
|
||||
- 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;
|
||||
<h4 id="version-7x-action-sheet">Action Sheet</h4>
|
||||
|
||||
+ background: red;
|
||||
}
|
||||
```
|
||||
<h2 id="version-8x-picker">Picker</h2>
|
||||
- Action Sheet is updated to align with the design specification.
|
||||
|
||||
- `ion-picker` and `ion-picker-column` have been renamed to `ion-picker-legacy` and `ion-picker-legacy-column`, respectively. This change was made to accommodate the new inline picker component while allowing developers to continue to use the legacy picker during this migration period.
|
||||
- Only the component names have been changed. Usages such as `ion-picker` or `IonPicker` should be changed to `ion-picker-legacy` and `IonPickerLegacy`, respectively.
|
||||
- Non-component usages such as `pickerController` or `useIonPicker` remain unchanged. The new picker displays inline with your page content and does not have equivalents for these non-component usages.
|
||||
**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;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
# 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;
|
||||
}
|
||||
```
|
||||
14
CHANGELOG.md
@@ -3,20 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **alert:** match MD spec on tablet ([#28501](https://github.com/ionic-team/ionic-framework/issues/28501)) ([6a2be9f](https://github.com/ionic-team/ionic-framework/commit/6a2be9fa3c12a893d98dc139a1575a6e7e3c7c26)), closes [#23977](https://github.com/ionic-team/ionic-framework/issues/23977)
|
||||
* **angular:** ng add @ionic/angular in standalone projects ([#28523](https://github.com/ionic-team/ionic-framework/issues/28523)) ([c07312e](https://github.com/ionic-team/ionic-framework/commit/c07312e5ed931f6f825ccf083c9dead9fa815843)), closes [#28514](https://github.com/ionic-team/ionic-framework/issues/28514)
|
||||
* **angular:** overlays are defined when using standalone controllers ([#28560](https://github.com/ionic-team/ionic-framework/issues/28560)) ([9453132](https://github.com/ionic-team/ionic-framework/commit/9453132aa8952b4adfa1326e61138b329e254f76)), closes [#28385](https://github.com/ionic-team/ionic-framework/issues/28385)
|
||||
* **datetime:** updating value with min scrolls to new value ([#28549](https://github.com/ionic-team/ionic-framework/issues/28549)) ([388d19e](https://github.com/ionic-team/ionic-framework/commit/388d19e04f83f85abd4602adb04cc71ac575764a)), closes [#28548](https://github.com/ionic-team/ionic-framework/issues/28548)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.5.5](https://github.com/ionic-team/ionic-framework/compare/v7.5.4...v7.5.5) (2023-11-15)
|
||||
|
||||
|
||||
|
||||
@@ -3,18 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **alert:** match MD spec on tablet ([#28501](https://github.com/ionic-team/ionic-framework/issues/28501)) ([6a2be9f](https://github.com/ionic-team/ionic-framework/commit/6a2be9fa3c12a893d98dc139a1575a6e7e3c7c26)), closes [#23977](https://github.com/ionic-team/ionic-framework/issues/23977)
|
||||
* **datetime:** updating value with min scrolls to new value ([#28549](https://github.com/ionic-team/ionic-framework/issues/28549)) ([388d19e](https://github.com/ionic-team/ionic-framework/commit/388d19e04f83f85abd4602adb04cc71ac575764a)), closes [#28548](https://github.com/ionic-team/ionic-framework/issues/28548)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.5.5](https://github.com/ionic-team/ionic-framework/compare/v7.5.4...v7.5.5) (2023-11-15)
|
||||
|
||||
|
||||
|
||||
97
core/api.txt
@@ -906,64 +906,47 @@ ion-note,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "second
|
||||
ion-note,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-note,css-prop,--color
|
||||
|
||||
ion-picker,shadow
|
||||
ion-picker,scoped
|
||||
ion-picker,prop,animated,boolean,true,false,false
|
||||
ion-picker,prop,backdropDismiss,boolean,true,false,false
|
||||
ion-picker,prop,buttons,PickerButton[],[],false,false
|
||||
ion-picker,prop,columns,PickerColumn[],[],false,false
|
||||
ion-picker,prop,cssClass,string | string[] | undefined,undefined,false,false
|
||||
ion-picker,prop,duration,number,0,false,false
|
||||
ion-picker,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
ion-picker,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
|
||||
ion-picker,prop,isOpen,boolean,false,false,false
|
||||
ion-picker,prop,keyboardClose,boolean,true,false,false
|
||||
ion-picker,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
ion-picker,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-picker,event,ionInputModeChange,PickerChangeEventDetail,true
|
||||
|
||||
ion-picker-column,shadow
|
||||
ion-picker-column,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,'primary',false,true
|
||||
ion-picker-column,prop,disabled,boolean,false,false,false
|
||||
ion-picker-column,prop,items,PickerColumnItem[],[],false,false
|
||||
ion-picker-column,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-picker-column,prop,value,number | string | undefined,undefined,false,false
|
||||
ion-picker-column,event,ionChange,PickerColumnItem,true
|
||||
|
||||
ion-picker-column-option,shadow
|
||||
ion-picker-column-option,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,'primary',false,true
|
||||
ion-picker-column-option,prop,disabled,boolean,false,false,false
|
||||
ion-picker-column-option,prop,value,any,undefined,false,false
|
||||
|
||||
ion-picker-legacy,scoped
|
||||
ion-picker-legacy,prop,animated,boolean,true,false,false
|
||||
ion-picker-legacy,prop,backdropDismiss,boolean,true,false,false
|
||||
ion-picker-legacy,prop,buttons,PickerButton[],[],false,false
|
||||
ion-picker-legacy,prop,columns,PickerColumn[],[],false,false
|
||||
ion-picker-legacy,prop,cssClass,string | string[] | undefined,undefined,false,false
|
||||
ion-picker-legacy,prop,duration,number,0,false,false
|
||||
ion-picker-legacy,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
ion-picker-legacy,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
|
||||
ion-picker-legacy,prop,isOpen,boolean,false,false,false
|
||||
ion-picker-legacy,prop,keyboardClose,boolean,true,false,false
|
||||
ion-picker-legacy,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
ion-picker-legacy,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-picker-legacy,prop,showBackdrop,boolean,true,false,false
|
||||
ion-picker-legacy,prop,trigger,string | undefined,undefined,false,false
|
||||
ion-picker-legacy,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
|
||||
ion-picker-legacy,method,getColumn,getColumn(name: string) => Promise<PickerColumn | undefined>
|
||||
ion-picker-legacy,method,onDidDismiss,onDidDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||
ion-picker-legacy,method,onWillDismiss,onWillDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||
ion-picker-legacy,method,present,present() => Promise<void>
|
||||
ion-picker-legacy,event,didDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker-legacy,event,didPresent,void,true
|
||||
ion-picker-legacy,event,ionPickerDidDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker-legacy,event,ionPickerDidPresent,void,true
|
||||
ion-picker-legacy,event,ionPickerWillDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker-legacy,event,ionPickerWillPresent,void,true
|
||||
ion-picker-legacy,event,willDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker-legacy,event,willPresent,void,true
|
||||
ion-picker-legacy,css-prop,--backdrop-opacity
|
||||
ion-picker-legacy,css-prop,--background
|
||||
ion-picker-legacy,css-prop,--background-rgb
|
||||
ion-picker-legacy,css-prop,--border-color
|
||||
ion-picker-legacy,css-prop,--border-radius
|
||||
ion-picker-legacy,css-prop,--border-style
|
||||
ion-picker-legacy,css-prop,--border-width
|
||||
ion-picker-legacy,css-prop,--height
|
||||
ion-picker-legacy,css-prop,--max-height
|
||||
ion-picker-legacy,css-prop,--max-width
|
||||
ion-picker-legacy,css-prop,--min-height
|
||||
ion-picker-legacy,css-prop,--min-width
|
||||
ion-picker-legacy,css-prop,--width
|
||||
ion-picker,prop,showBackdrop,boolean,true,false,false
|
||||
ion-picker,prop,trigger,string | undefined,undefined,false,false
|
||||
ion-picker,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
|
||||
ion-picker,method,getColumn,getColumn(name: string) => Promise<PickerColumn | undefined>
|
||||
ion-picker,method,onDidDismiss,onDidDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||
ion-picker,method,onWillDismiss,onWillDismiss<T = any>() => Promise<OverlayEventDetail<T>>
|
||||
ion-picker,method,present,present() => Promise<void>
|
||||
ion-picker,event,didDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker,event,didPresent,void,true
|
||||
ion-picker,event,ionPickerDidDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker,event,ionPickerDidPresent,void,true
|
||||
ion-picker,event,ionPickerWillDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker,event,ionPickerWillPresent,void,true
|
||||
ion-picker,event,willDismiss,OverlayEventDetail<any>,true
|
||||
ion-picker,event,willPresent,void,true
|
||||
ion-picker,css-prop,--backdrop-opacity
|
||||
ion-picker,css-prop,--background
|
||||
ion-picker,css-prop,--background-rgb
|
||||
ion-picker,css-prop,--border-color
|
||||
ion-picker,css-prop,--border-radius
|
||||
ion-picker,css-prop,--border-style
|
||||
ion-picker,css-prop,--border-width
|
||||
ion-picker,css-prop,--height
|
||||
ion-picker,css-prop,--max-height
|
||||
ion-picker,css-prop,--max-width
|
||||
ion-picker,css-prop,--min-height
|
||||
ion-picker,css-prop,--min-width
|
||||
ion-picker,css-prop,--width
|
||||
|
||||
ion-popover,shadow
|
||||
ion-popover,prop,alignment,"center" | "end" | "start" | undefined,undefined,false,false
|
||||
|
||||
1625
core/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.5.6",
|
||||
"version": "7.5.5",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,7 +31,7 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.7.2",
|
||||
"@stencil/core": "^4.6.0-dev.1698410852.c526078",
|
||||
"ionicons": "^7.2.1",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -64,7 +64,6 @@
|
||||
"jest": "^29.7.0",
|
||||
"jest-cli": "^29.7.0",
|
||||
"prettier": "^2.6.1",
|
||||
"puppeteer": "21.1.1",
|
||||
"rollup": "^2.26.4",
|
||||
"sass": "^1.33.0",
|
||||
"serve": "^14.0.1",
|
||||
|
||||
283
core/src/components.d.ts
vendored
@@ -23,9 +23,9 @@ import { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
|
||||
import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
|
||||
import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
|
||||
import { ViewController } from "./components/nav/view-controller";
|
||||
import { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
|
||||
import { PickerColumnItem } from "./components/picker-column/picker-column-interfaces";
|
||||
import { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
|
||||
import { PickerButton, PickerColumn } from "./components/picker/picker-interface";
|
||||
import { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
|
||||
import { PickerInternalChangeEventDetail } from "./components/picker-internal/picker-internal-interfaces";
|
||||
import { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
|
||||
import { RadioGroupChangeEventDetail } from "./components/radio-group/radio-group-interface";
|
||||
import { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
|
||||
@@ -59,9 +59,9 @@ export { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
|
||||
export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
|
||||
export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
|
||||
export { ViewController } from "./components/nav/view-controller";
|
||||
export { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
|
||||
export { PickerColumnItem } from "./components/picker-column/picker-column-interfaces";
|
||||
export { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
|
||||
export { PickerButton, PickerColumn } from "./components/picker/picker-interface";
|
||||
export { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
|
||||
export { PickerInternalChangeEventDetail } from "./components/picker-internal/picker-internal-interfaces";
|
||||
export { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
|
||||
export { RadioGroupChangeEventDetail } from "./components/radio-group/radio-group-interface";
|
||||
export { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
|
||||
@@ -1949,58 +1949,6 @@ export namespace Components {
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonPicker {
|
||||
"exitInputMode": () => Promise<void>;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonPickerColumn {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* A list of options to be displayed in the picker
|
||||
*/
|
||||
"items": PickerColumnItem[];
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
||||
*/
|
||||
"numericInput": boolean;
|
||||
"scrollActiveItemIntoView": () => Promise<void>;
|
||||
/**
|
||||
* Sets the value prop and fires the ionChange event. This is used when we need to fire ionChange from user-generated events that cannot be caught with normal input/change event listeners.
|
||||
*/
|
||||
"setValue": (value?: string | number) => Promise<void>;
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerColumnOption {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker column option.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* The text value of the option.
|
||||
*/
|
||||
"value"?: any | null;
|
||||
}
|
||||
interface IonPickerLegacy {
|
||||
/**
|
||||
* If `true`, the picker will animate.
|
||||
*/
|
||||
@@ -2084,12 +2032,46 @@ export namespace Components {
|
||||
*/
|
||||
"trigger": string | undefined;
|
||||
}
|
||||
interface IonPickerLegacyColumn {
|
||||
interface IonPickerColumn {
|
||||
/**
|
||||
* Picker column data
|
||||
*/
|
||||
"col": PickerColumn;
|
||||
}
|
||||
interface IonPickerColumnInternal {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* A list of options to be displayed in the picker
|
||||
*/
|
||||
"items": PickerColumnItem[];
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
||||
*/
|
||||
"numericInput": boolean;
|
||||
"scrollActiveItemIntoView": () => Promise<void>;
|
||||
/**
|
||||
* Sets the value prop and fires the ionChange event. This is used when we need to fire ionChange from user-generated events that cannot be caught with normal input/change event listeners.
|
||||
*/
|
||||
"setValue": (value?: string | number) => Promise<void>;
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerInternal {
|
||||
"exitInputMode": () => Promise<void>;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonPopover {
|
||||
/**
|
||||
* Describes how to align the popover content with the `reference` point. Defaults to `"center"` for `ios` mode, and `"start"` for `md` mode.
|
||||
@@ -3344,13 +3326,13 @@ export interface IonPickerColumnCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
target: HTMLIonPickerColumnElement;
|
||||
}
|
||||
export interface IonPickerLegacyCustomEvent<T> extends CustomEvent<T> {
|
||||
export interface IonPickerColumnInternalCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
target: HTMLIonPickerLegacyElement;
|
||||
target: HTMLIonPickerColumnInternalElement;
|
||||
}
|
||||
export interface IonPickerLegacyColumnCustomEvent<T> extends CustomEvent<T> {
|
||||
export interface IonPickerInternalCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
target: HTMLIonPickerLegacyColumnElement;
|
||||
target: HTMLIonPickerInternalElement;
|
||||
}
|
||||
export interface IonPopoverCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
@@ -4034,7 +4016,14 @@ declare global {
|
||||
new (): HTMLIonNoteElement;
|
||||
};
|
||||
interface HTMLIonPickerElementEventMap {
|
||||
"ionInputModeChange": PickerChangeEventDetail;
|
||||
"ionPickerDidPresent": void;
|
||||
"ionPickerWillPresent": void;
|
||||
"ionPickerWillDismiss": OverlayEventDetail;
|
||||
"ionPickerDidDismiss": OverlayEventDetail;
|
||||
"didPresent": void;
|
||||
"willPresent": void;
|
||||
"willDismiss": OverlayEventDetail;
|
||||
"didDismiss": OverlayEventDetail;
|
||||
}
|
||||
interface HTMLIonPickerElement extends Components.IonPicker, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerElementEventMap>(type: K, listener: (this: HTMLIonPickerElement, ev: IonPickerCustomEvent<HTMLIonPickerElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
@@ -4051,7 +4040,7 @@ declare global {
|
||||
new (): HTMLIonPickerElement;
|
||||
};
|
||||
interface HTMLIonPickerColumnElementEventMap {
|
||||
"ionChange": string | number | undefined;
|
||||
"ionPickerColChange": PickerColumn;
|
||||
}
|
||||
interface HTMLIonPickerColumnElement extends Components.IonPickerColumn, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerColumnElementEventMap>(type: K, listener: (this: HTMLIonPickerColumnElement, ev: IonPickerColumnCustomEvent<HTMLIonPickerColumnElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
@@ -4067,52 +4056,39 @@ declare global {
|
||||
prototype: HTMLIonPickerColumnElement;
|
||||
new (): HTMLIonPickerColumnElement;
|
||||
};
|
||||
interface HTMLIonPickerColumnOptionElement extends Components.IonPickerColumnOption, HTMLStencilElement {
|
||||
interface HTMLIonPickerColumnInternalElementEventMap {
|
||||
"ionChange": PickerColumnItem;
|
||||
}
|
||||
var HTMLIonPickerColumnOptionElement: {
|
||||
prototype: HTMLIonPickerColumnOptionElement;
|
||||
new (): HTMLIonPickerColumnOptionElement;
|
||||
};
|
||||
interface HTMLIonPickerLegacyElementEventMap {
|
||||
"ionPickerDidPresent": void;
|
||||
"ionPickerWillPresent": void;
|
||||
"ionPickerWillDismiss": OverlayEventDetail;
|
||||
"ionPickerDidDismiss": OverlayEventDetail;
|
||||
"didPresent": void;
|
||||
"willPresent": void;
|
||||
"willDismiss": OverlayEventDetail;
|
||||
"didDismiss": OverlayEventDetail;
|
||||
}
|
||||
interface HTMLIonPickerLegacyElement extends Components.IonPickerLegacy, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerLegacyElementEventMap>(type: K, listener: (this: HTMLIonPickerLegacyElement, ev: IonPickerLegacyCustomEvent<HTMLIonPickerLegacyElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
interface HTMLIonPickerColumnInternalElement extends Components.IonPickerColumnInternal, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerColumnInternalElementEventMap>(type: K, listener: (this: HTMLIonPickerColumnInternalElement, ev: IonPickerColumnInternalCustomEvent<HTMLIonPickerColumnInternalElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLIonPickerLegacyElementEventMap>(type: K, listener: (this: HTMLIonPickerLegacyElement, ev: IonPickerLegacyCustomEvent<HTMLIonPickerLegacyElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLIonPickerColumnInternalElementEventMap>(type: K, listener: (this: HTMLIonPickerColumnInternalElement, ev: IonPickerColumnInternalCustomEvent<HTMLIonPickerColumnInternalElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
||||
}
|
||||
var HTMLIonPickerLegacyElement: {
|
||||
prototype: HTMLIonPickerLegacyElement;
|
||||
new (): HTMLIonPickerLegacyElement;
|
||||
var HTMLIonPickerColumnInternalElement: {
|
||||
prototype: HTMLIonPickerColumnInternalElement;
|
||||
new (): HTMLIonPickerColumnInternalElement;
|
||||
};
|
||||
interface HTMLIonPickerLegacyColumnElementEventMap {
|
||||
"ionPickerColChange": PickerColumn;
|
||||
interface HTMLIonPickerInternalElementEventMap {
|
||||
"ionInputModeChange": PickerInternalChangeEventDetail;
|
||||
}
|
||||
interface HTMLIonPickerLegacyColumnElement extends Components.IonPickerLegacyColumn, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerLegacyColumnElementEventMap>(type: K, listener: (this: HTMLIonPickerLegacyColumnElement, ev: IonPickerLegacyColumnCustomEvent<HTMLIonPickerLegacyColumnElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
interface HTMLIonPickerInternalElement extends Components.IonPickerInternal, HTMLStencilElement {
|
||||
addEventListener<K extends keyof HTMLIonPickerInternalElementEventMap>(type: K, listener: (this: HTMLIonPickerInternalElement, ev: IonPickerInternalCustomEvent<HTMLIonPickerInternalElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
||||
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLIonPickerLegacyColumnElementEventMap>(type: K, listener: (this: HTMLIonPickerLegacyColumnElement, ev: IonPickerLegacyColumnCustomEvent<HTMLIonPickerLegacyColumnElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLIonPickerInternalElementEventMap>(type: K, listener: (this: HTMLIonPickerInternalElement, ev: IonPickerInternalCustomEvent<HTMLIonPickerInternalElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
||||
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
||||
}
|
||||
var HTMLIonPickerLegacyColumnElement: {
|
||||
prototype: HTMLIonPickerLegacyColumnElement;
|
||||
new (): HTMLIonPickerLegacyColumnElement;
|
||||
var HTMLIonPickerInternalElement: {
|
||||
prototype: HTMLIonPickerInternalElement;
|
||||
new (): HTMLIonPickerInternalElement;
|
||||
};
|
||||
interface HTMLIonPopoverElementEventMap {
|
||||
"ionPopoverDidPresent": void;
|
||||
@@ -4667,9 +4643,8 @@ declare global {
|
||||
"ion-note": HTMLIonNoteElement;
|
||||
"ion-picker": HTMLIonPickerElement;
|
||||
"ion-picker-column": HTMLIonPickerColumnElement;
|
||||
"ion-picker-column-option": HTMLIonPickerColumnOptionElement;
|
||||
"ion-picker-legacy": HTMLIonPickerLegacyElement;
|
||||
"ion-picker-legacy-column": HTMLIonPickerLegacyColumnElement;
|
||||
"ion-picker-column-internal": HTMLIonPickerColumnInternalElement;
|
||||
"ion-picker-internal": HTMLIonPickerInternalElement;
|
||||
"ion-popover": HTMLIonPopoverElement;
|
||||
"ion-progress-bar": HTMLIonProgressBarElement;
|
||||
"ion-radio": HTMLIonRadioElement;
|
||||
@@ -6601,57 +6576,6 @@ declare namespace LocalJSX {
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonPicker {
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
"onIonInputModeChange"?: (event: IonPickerCustomEvent<PickerChangeEventDetail>) => void;
|
||||
}
|
||||
interface IonPickerColumn {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* A list of options to be displayed in the picker
|
||||
*/
|
||||
"items"?: PickerColumnItem[];
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
||||
*/
|
||||
"numericInput"?: boolean;
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
"onIonChange"?: (event: IonPickerColumnCustomEvent<string | number | undefined>) => void;
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerColumnOption {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker column option.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* The text value of the option.
|
||||
*/
|
||||
"value"?: any | null;
|
||||
}
|
||||
interface IonPickerLegacy {
|
||||
/**
|
||||
* If `true`, the picker will animate.
|
||||
*/
|
||||
@@ -6705,35 +6629,35 @@ declare namespace LocalJSX {
|
||||
/**
|
||||
* Emitted after the picker has dismissed. Shorthand for ionPickerDidDismiss.
|
||||
*/
|
||||
"onDidDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
|
||||
"onDidDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
|
||||
/**
|
||||
* Emitted after the picker has presented. Shorthand for ionPickerWillDismiss.
|
||||
*/
|
||||
"onDidPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
|
||||
"onDidPresent"?: (event: IonPickerCustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted after the picker has dismissed.
|
||||
*/
|
||||
"onIonPickerDidDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
|
||||
"onIonPickerDidDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
|
||||
/**
|
||||
* Emitted after the picker has presented.
|
||||
*/
|
||||
"onIonPickerDidPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
|
||||
"onIonPickerDidPresent"?: (event: IonPickerCustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted before the picker has dismissed.
|
||||
*/
|
||||
"onIonPickerWillDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
|
||||
"onIonPickerWillDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
|
||||
/**
|
||||
* Emitted before the picker has presented.
|
||||
*/
|
||||
"onIonPickerWillPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
|
||||
"onIonPickerWillPresent"?: (event: IonPickerCustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted before the picker has dismissed. Shorthand for ionPickerWillDismiss.
|
||||
*/
|
||||
"onWillDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
|
||||
"onWillDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
|
||||
/**
|
||||
* Emitted before the picker has presented. Shorthand for ionPickerWillPresent.
|
||||
*/
|
||||
"onWillPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
|
||||
"onWillPresent"?: (event: IonPickerCustomEvent<void>) => void;
|
||||
"overlayIndex": number;
|
||||
/**
|
||||
* If `true`, a backdrop will be displayed behind the picker.
|
||||
@@ -6744,7 +6668,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"trigger"?: string | undefined;
|
||||
}
|
||||
interface IonPickerLegacyColumn {
|
||||
interface IonPickerColumn {
|
||||
/**
|
||||
* Picker column data
|
||||
*/
|
||||
@@ -6752,7 +6676,40 @@ declare namespace LocalJSX {
|
||||
/**
|
||||
* Emitted when the selected value has changed
|
||||
*/
|
||||
"onIonPickerColChange"?: (event: IonPickerLegacyColumnCustomEvent<PickerColumn>) => void;
|
||||
"onIonPickerColChange"?: (event: IonPickerColumnCustomEvent<PickerColumn>) => void;
|
||||
}
|
||||
interface IonPickerColumnInternal {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* A list of options to be displayed in the picker
|
||||
*/
|
||||
"items"?: PickerColumnItem[];
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, tapping the picker will reveal a number input keyboard that lets the user type in values for each picker column. This is useful when working with time pickers.
|
||||
*/
|
||||
"numericInput"?: boolean;
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
"onIonChange"?: (event: IonPickerColumnInternalCustomEvent<PickerColumnItem>) => void;
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
"value"?: string | number;
|
||||
}
|
||||
interface IonPickerInternal {
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
"onIonInputModeChange"?: (event: IonPickerInternalCustomEvent<PickerInternalChangeEventDetail>) => void;
|
||||
}
|
||||
interface IonPopover {
|
||||
/**
|
||||
@@ -8121,9 +8078,8 @@ declare namespace LocalJSX {
|
||||
"ion-note": IonNote;
|
||||
"ion-picker": IonPicker;
|
||||
"ion-picker-column": IonPickerColumn;
|
||||
"ion-picker-column-option": IonPickerColumnOption;
|
||||
"ion-picker-legacy": IonPickerLegacy;
|
||||
"ion-picker-legacy-column": IonPickerLegacyColumn;
|
||||
"ion-picker-column-internal": IonPickerColumnInternal;
|
||||
"ion-picker-internal": IonPickerInternal;
|
||||
"ion-popover": IonPopover;
|
||||
"ion-progress-bar": IonProgressBar;
|
||||
"ion-radio": IonRadio;
|
||||
@@ -8219,9 +8175,8 @@ declare module "@stencil/core" {
|
||||
"ion-note": LocalJSX.IonNote & JSXBase.HTMLAttributes<HTMLIonNoteElement>;
|
||||
"ion-picker": LocalJSX.IonPicker & JSXBase.HTMLAttributes<HTMLIonPickerElement>;
|
||||
"ion-picker-column": LocalJSX.IonPickerColumn & JSXBase.HTMLAttributes<HTMLIonPickerColumnElement>;
|
||||
"ion-picker-column-option": LocalJSX.IonPickerColumnOption & JSXBase.HTMLAttributes<HTMLIonPickerColumnOptionElement>;
|
||||
"ion-picker-legacy": LocalJSX.IonPickerLegacy & JSXBase.HTMLAttributes<HTMLIonPickerLegacyElement>;
|
||||
"ion-picker-legacy-column": LocalJSX.IonPickerLegacyColumn & JSXBase.HTMLAttributes<HTMLIonPickerLegacyColumnElement>;
|
||||
"ion-picker-column-internal": LocalJSX.IonPickerColumnInternal & JSXBase.HTMLAttributes<HTMLIonPickerColumnInternalElement>;
|
||||
"ion-picker-internal": LocalJSX.IonPickerInternal & JSXBase.HTMLAttributes<HTMLIonPickerInternalElement>;
|
||||
"ion-popover": LocalJSX.IonPopover & JSXBase.HTMLAttributes<HTMLIonPopoverElement>;
|
||||
"ion-progress-bar": LocalJSX.IonProgressBar & JSXBase.HTMLAttributes<HTMLIonProgressBarElement>;
|
||||
"ion-radio": LocalJSX.IonRadio & JSXBase.HTMLAttributes<HTMLIonRadioElement>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { AccordionGroup } from '../../accordion-group/accordion-group';
|
||||
import { Item } from '../../item/item';
|
||||
import { Accordion } from '../accordion';
|
||||
import { AccordionGroup } from '../../accordion-group/accordion-group.tsx';
|
||||
import { Item } from '../../item/item.tsx';
|
||||
import { Accordion } from '../accordion.tsx';
|
||||
|
||||
it('should open correct accordions when accordion group value is set', async () => {
|
||||
const page = await newSpecPage({
|
||||
@@ -25,7 +25,7 @@ it('should open correct accordions when accordion group value is set', async ()
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach((accordion) => {
|
||||
@@ -61,7 +61,7 @@ it('should open correct accordions when accordion value is set', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach((accordion) => {
|
||||
@@ -97,7 +97,7 @@ it('should open more than one accordion when multiple="true"', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach((accordion) => {
|
||||
@@ -133,7 +133,7 @@ it('should render with accordion open', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
@@ -162,7 +162,7 @@ it('should accept a string when multiple="true"', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
@@ -183,8 +183,8 @@ it('should set default values if not provided', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const accordion = accordionGroup.querySelector('ion-accordion')!;
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordion = accordionGroup.querySelector('ion-accordion');
|
||||
|
||||
/**
|
||||
* ID is determined via an auto incrementing counter
|
||||
|
||||
@@ -7,17 +7,10 @@ describe('action sheet: htmlAttributes inheritance', () => {
|
||||
it('should correctly inherit attributes on host', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [ActionSheet],
|
||||
template: () => (
|
||||
<ion-action-sheet
|
||||
htmlAttributes={{
|
||||
'data-testid': 'basic-action-sheet',
|
||||
}}
|
||||
overlayIndex={1}
|
||||
></ion-action-sheet>
|
||||
),
|
||||
template: () => <ion-action-sheet htmlAttributes={{ 'data-testid': 'basic-action-sheet' }}></ion-action-sheet>,
|
||||
});
|
||||
|
||||
const actionSheet = page.body.querySelector('ion-action-sheet')!;
|
||||
const actionSheet = page.body.querySelector('ion-action-sheet');
|
||||
|
||||
await expect(actionSheet.getAttribute('data-testid')).toBe('basic-action-sheet');
|
||||
});
|
||||
|
||||
@@ -105,17 +105,6 @@
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::-webkit-date-and-time-value {
|
||||
/**
|
||||
* The -webkit-date-and-time-value pseudo element is used
|
||||
* to style the date/time input on iOS/Mobile Safari.
|
||||
* To avoid layout shift between an empty state and a selected state,
|
||||
* we set the height `18px` to match the native input height for
|
||||
* date/time inputs on iOS/Mobile Safari.
|
||||
*/
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,18 +52,9 @@
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
font-size: $alert-md-message-font-size;
|
||||
}
|
||||
max-height: $alert-md-content-max-height;
|
||||
|
||||
/**
|
||||
* MD Alerts on tablets can expand vertically up to
|
||||
* a total maximum height. We only want to set a max-height
|
||||
* on mobile phones.
|
||||
*/
|
||||
@include mobile-viewport() {
|
||||
.alert-message {
|
||||
max-height: $alert-md-content-max-height;
|
||||
}
|
||||
font-size: $alert-md-message-font-size;
|
||||
}
|
||||
|
||||
.alert-message:empty {
|
||||
@@ -111,24 +102,14 @@
|
||||
.alert-checkbox-group {
|
||||
position: relative;
|
||||
|
||||
max-height: $alert-md-content-max-height;
|
||||
|
||||
border-top: $alert-md-list-border-top;
|
||||
border-bottom: $alert-md-list-border-bottom;
|
||||
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* MD Alerts on tablets can expand vertically up to
|
||||
* a total maximum height. We only want to set a max-height
|
||||
* on mobile phones.
|
||||
*/
|
||||
@include mobile-viewport() {
|
||||
.alert-radio-group,
|
||||
.alert-checkbox-group {
|
||||
max-height: $alert-md-content-max-height;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-tappable {
|
||||
position: relative;
|
||||
|
||||
@@ -301,14 +282,3 @@
|
||||
.alert-button-inner {
|
||||
justify-content: $alert-md-button-group-justify-content;
|
||||
}
|
||||
|
||||
/**
|
||||
* MD alerts should scale up to 560px x 560px
|
||||
* on tablet dimensions.
|
||||
*/
|
||||
@include tablet-viewport() {
|
||||
:host {
|
||||
--max-width: #{$alert-md-max-width-tablet};
|
||||
--max-height: #{$alert-md-max-height-tablet};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,20 +10,6 @@ $alert-md-font-size: dynamic-font(14px) !default;
|
||||
/// @prop - Max width of the alert
|
||||
$alert-md-max-width: 280px !default;
|
||||
|
||||
/// @prop - Max width of the alert on a tablet
|
||||
/**
|
||||
* Large display requirements for MD Alert:
|
||||
* 1. Maintain a minimum of 48px distance from the leading and
|
||||
* trailing edges of the screen. (48px * 2 = 96px)
|
||||
* 2. The width can increase up to 560px.
|
||||
* 3. The height can increase up to 560px.
|
||||
* Source: https://m2.material.io/components/dialogs#behavior
|
||||
*/
|
||||
$alert-md-max-width-tablet: min(calc(100vw - 96px), 560px) !default;
|
||||
|
||||
/// @prop - Max width of the alert on a tablet
|
||||
$alert-md-max-height-tablet: min(calc(100vh - 96px), 560px) !default;
|
||||
|
||||
/// @prop - Border radius of the alert
|
||||
$alert-md-border-radius: 4px !default;
|
||||
|
||||
|
||||
@@ -84,15 +84,7 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert has a maximum height in scenarios
|
||||
* such as the MD alert on tablet devices.
|
||||
* As a result, we need to make sure the inner
|
||||
* containers can scroll otherwise content
|
||||
* may be cut off.
|
||||
*/
|
||||
.alert-message,
|
||||
.alert-input-group {
|
||||
.alert-message {
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { config } from '../../../global/config';
|
||||
import { Alert } from '../alert';
|
||||
import { config } from '../../../global/config';
|
||||
|
||||
describe('alert: custom html', () => {
|
||||
it('should not allow for custom html by default', async () => {
|
||||
@@ -10,7 +9,7 @@ describe('alert: custom html', () => {
|
||||
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.alert-message')!;
|
||||
const content = page.body.querySelector('.alert-message');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||
});
|
||||
@@ -22,7 +21,7 @@ describe('alert: custom html', () => {
|
||||
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.alert-message')!;
|
||||
const content = page.body.querySelector('.alert-message');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||
});
|
||||
@@ -34,7 +33,7 @@ describe('alert: custom html', () => {
|
||||
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.alert-message')!;
|
||||
const content = page.body.querySelector('.alert-message');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||
});
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, Viewports } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('alert: rendering - tablet'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.setViewportSize(Viewports.tablet.portrait);
|
||||
await page.goto('/src/components/alert/test/basic', config);
|
||||
});
|
||||
|
||||
test('should expand width and height on larger displays with text', async ({ page }) => {
|
||||
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
|
||||
const button = page.locator('#longMessage');
|
||||
const alert = page.locator('ion-alert');
|
||||
|
||||
await button.click();
|
||||
await ionAlertDidPresent.next();
|
||||
|
||||
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-text'));
|
||||
});
|
||||
|
||||
test('should expand width and height on larger displays with checkboxes', async ({ page }) => {
|
||||
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
|
||||
const button = page.locator('#checkbox');
|
||||
const alert = page.locator('ion-alert');
|
||||
|
||||
await button.click();
|
||||
await ionAlertDidPresent.next();
|
||||
|
||||
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-checkboxes'));
|
||||
});
|
||||
|
||||
test('should expand width and height on larger displays with radios', async ({ page }) => {
|
||||
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
|
||||
const button = page.locator('#radio');
|
||||
const alert = page.locator('ion-alert');
|
||||
|
||||
await button.click();
|
||||
await ionAlertDidPresent.next();
|
||||
|
||||
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-radios'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
@@ -36,7 +36,7 @@
|
||||
}
|
||||
|
||||
async function showPicker() {
|
||||
const picker = Object.assign(document.createElement('ion-picker-legacy'), {
|
||||
const picker = Object.assign(document.createElement('ion-picker'), {
|
||||
columns: [
|
||||
{
|
||||
name: 'Picker',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,6 +13,10 @@ 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.
|
||||
*/
|
||||
@@ -32,6 +37,16 @@ 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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Breadcrumb } from '../../breadcrumb/breadcrumb';
|
||||
import { Breadcrumbs } from '../breadcrumbs';
|
||||
import { Breadcrumb } from '../../breadcrumb/breadcrumb.tsx';
|
||||
import { Breadcrumbs } from '../breadcrumbs.tsx';
|
||||
|
||||
it('should correctly provide the collapsed breadcrumbs in the event payload', async () => {
|
||||
const page = await newSpecPage({
|
||||
@@ -18,8 +18,8 @@ it('should correctly provide the collapsed breadcrumbs in the event payload', as
|
||||
});
|
||||
|
||||
const onCollapsedClick = jest.fn((ev) => ev);
|
||||
const breadcrumbs = page.body.querySelector('ion-breadcrumbs')!;
|
||||
const breadcrumb = page.body.querySelectorAll('ion-breadcrumb')!;
|
||||
const breadcrumbs = page.body.querySelector('ion-breadcrumbs');
|
||||
const breadcrumb = page.body.querySelectorAll('ion-breadcrumb');
|
||||
|
||||
breadcrumbs.addEventListener('ionCollapsedClick', onCollapsedClick);
|
||||
|
||||
@@ -46,8 +46,8 @@ it('should exclude the separator from narrators', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const firstBreadcrumb = page.body.querySelector('ion-breadcrumb:first-of-type')!;
|
||||
const separator = firstBreadcrumb.shadowRoot!.querySelector('[part="separator"]')!;
|
||||
const firstBreadcrumb = page.body.querySelector('ion-breadcrumb:first-of-type');
|
||||
const separator = firstBreadcrumb.shadowRoot.querySelector('[part="separator"]');
|
||||
|
||||
expect(separator.getAttribute('aria-hidden')).toBe('true');
|
||||
});
|
||||
@@ -62,7 +62,7 @@ it('should have color attribute', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const breadcrumbs = page.body.querySelector('ion-breadcrumbs')!;
|
||||
const breadcrumbs = page.body.querySelector('ion-breadcrumbs');
|
||||
|
||||
expect(breadcrumbs.hasAttribute('color')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Button } from '../../button';
|
||||
|
||||
describe('Button: Hidden Form Button', () => {
|
||||
@@ -16,7 +15,8 @@ describe('Button: Hidden Form Button', () => {
|
||||
return page.body.querySelectorAll('form button');
|
||||
};
|
||||
|
||||
const button = page.body.querySelector('ion-button')!;
|
||||
const form = page.body.querySelectorAll('form');
|
||||
const button = page.body.querySelector('ion-button');
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('ion-checkbox: disabled', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const checkbox = page.body.querySelector('ion-checkbox')!;
|
||||
const checkbox = page.body.querySelector('ion-checkbox');
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
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,24 +34,16 @@
|
||||
|
||||
// Calendar / Header / Action Buttons
|
||||
// -----------------------------------
|
||||
.calendar-month-year-toggle {
|
||||
@include padding(0px, 16px, 0px, #{$datetime-ios-padding});
|
||||
|
||||
min-height: 44px;
|
||||
:host .calendar-action-buttons ion-item {
|
||||
--padding-start: #{$datetime-ios-padding};
|
||||
--background-hover: transparent;
|
||||
--background-activated: transparent;
|
||||
|
||||
font-size: dynamic-font-max(16px, 1.6);
|
||||
font-weight: 600;
|
||||
|
||||
&.ion-focused::after {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
.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-item ion-icon,
|
||||
:host .calendar-action-buttons ion-buttons ion-button {
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
@@ -30,41 +30,15 @@
|
||||
|
||||
// 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 {
|
||||
@@ -90,6 +64,7 @@
|
||||
* if necessary.
|
||||
*/
|
||||
grid-template-rows: repeat(6, 1fr);
|
||||
|
||||
}
|
||||
|
||||
// Individual day button in month
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
* widest item in the column. Setting a minimum
|
||||
* width avoids this layout shifting.
|
||||
*/
|
||||
ion-picker-column {
|
||||
ion-picker-column-internal {
|
||||
min-width: 26px;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ ion-picker-column {
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that the picker is appropriately
|
||||
* This ensures that the picker is apppropriately
|
||||
* sized and never truncates the text.
|
||||
*/
|
||||
:host(.datetime-size-fixed.datetime-prefer-wheel) {
|
||||
@@ -267,8 +267,19 @@ ion-picker-column {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:host .calendar-action-buttons ion-item,
|
||||
:host .calendar-action-buttons ion-button {
|
||||
--background: transparent;
|
||||
--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);
|
||||
}
|
||||
|
||||
// Calendar / Header / Days of Week
|
||||
@@ -477,55 +488,6 @@ ion-picker-column {
|
||||
|
||||
// Year Picker
|
||||
// -----------------------------------
|
||||
: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;
|
||||
:host(.show-month-and-year) .calendar-action-buttons ion-item {
|
||||
--color: #{current-color(base)};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { Color, Mode, StyleEventDetail } from '../../interface';
|
||||
import type { PickerColumnItem } from '../picker-column/picker-column-interfaces';
|
||||
import type { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces';
|
||||
|
||||
import type {
|
||||
DatetimePresentation,
|
||||
@@ -104,6 +104,7 @@ 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[];
|
||||
@@ -1131,7 +1132,7 @@ export class Datetime implements ComponentInterface {
|
||||
* so we need to re-init behavior with the new elements.
|
||||
*/
|
||||
componentDidRender() {
|
||||
const { presentation, prevPresentation, calendarBodyRef, minParts, preferWheel, forceRenderDate } = this;
|
||||
const { presentation, prevPresentation, calendarBodyRef, minParts, preferWheel } = this;
|
||||
|
||||
/**
|
||||
* TODO(FW-2165)
|
||||
@@ -1149,20 +1150,7 @@ export class Datetime implements ComponentInterface {
|
||||
const hasCalendarGrid = !preferWheel && ['date-time', 'time-date', 'date'].includes(presentation);
|
||||
if (minParts !== undefined && hasCalendarGrid && calendarBodyRef) {
|
||||
const workingMonth = calendarBodyRef.querySelector('.calendar-month:nth-of-type(1)');
|
||||
/**
|
||||
* We need to make sure the datetime is not in the process
|
||||
* of scrolling to a new datetime value if the value
|
||||
* is updated programmatically.
|
||||
* Otherwise, the datetime will appear to not scroll at all because
|
||||
* we are resetting the scroll position to the center of the view.
|
||||
* Prior to the datetime's value being updated programmatically,
|
||||
* the calendarBodyRef is scrolled such that the middle month is centered
|
||||
* in the view. The below code updates the scroll position so the middle
|
||||
* month is also centered in the view. Since the scroll position did not change,
|
||||
* the scroll callback in this file does not fire,
|
||||
* and the resolveForceDateScrolling promise never resolves.
|
||||
*/
|
||||
if (workingMonth && forceRenderDate === undefined) {
|
||||
if (workingMonth) {
|
||||
calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
|
||||
}
|
||||
}
|
||||
@@ -1527,7 +1515,7 @@ export class Datetime implements ComponentInterface {
|
||||
forcePresentation === 'time-date'
|
||||
? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
|
||||
: [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
|
||||
return <ion-picker>{renderArray}</ion-picker>;
|
||||
return <ion-picker-internal>{renderArray}</ion-picker-internal>;
|
||||
}
|
||||
|
||||
private renderDatePickerColumns(forcePresentation: string) {
|
||||
@@ -1537,7 +1525,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private renderCombinedDatePickerColumn() {
|
||||
const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
|
||||
const { defaultParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
@@ -1613,10 +1601,9 @@ export class Datetime implements ComponentInterface {
|
||||
: `${defaultParts.year}-${defaultParts.month}-${defaultParts.day}`;
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
class="date-column"
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
items={items}
|
||||
value={todayString}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
@@ -1647,7 +1634,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1728,15 +1715,14 @@ export class Datetime implements ComponentInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
class="day-column"
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
items={days}
|
||||
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
@@ -1764,7 +1750,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1773,15 +1759,14 @@ export class Datetime implements ComponentInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
class="month-column"
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
items={months}
|
||||
value={workingParts.month}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
@@ -1809,7 +1794,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
private renderYearPickerColumn(years: PickerColumnItem[]) {
|
||||
@@ -1817,15 +1802,14 @@ export class Datetime implements ComponentInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
class="year-column"
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
items={years}
|
||||
value={workingParts.year}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
@@ -1853,7 +1837,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
private renderTimePickerColumns(forcePresentation: string) {
|
||||
@@ -1891,15 +1875,14 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private renderHourPickerColumn(hoursData: PickerColumnItem[]) {
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
if (hoursData.length === 0) return [];
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
value={activePart.hour}
|
||||
items={hoursData}
|
||||
numericInput
|
||||
@@ -1916,19 +1899,18 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
private renderMinutePickerColumn(minutesData: PickerColumnItem[]) {
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
if (minutesData.length === 0) return [];
|
||||
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
value={activePart.minute}
|
||||
items={minutesData}
|
||||
numericInput
|
||||
@@ -1945,11 +1927,11 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
private renderDayPeriodPickerColumn(dayPeriodData: PickerColumnItem[]) {
|
||||
const { disabled, workingParts } = this;
|
||||
const { workingParts } = this;
|
||||
if (dayPeriodData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -1958,10 +1940,9 @@ export class Datetime implements ComponentInterface {
|
||||
const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
|
||||
|
||||
return (
|
||||
<ion-picker-column
|
||||
<ion-picker-column-internal
|
||||
style={isDayPeriodRTL ? { order: '-1' } : {}}
|
||||
color={this.color}
|
||||
disabled={disabled}
|
||||
value={activePart.ampm}
|
||||
items={dayPeriodData}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
@@ -1981,7 +1962,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
></ion-picker-column>
|
||||
></ion-picker-column-internal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2019,18 +2000,35 @@ export class Datetime implements ComponentInterface {
|
||||
<div class="calendar-header">
|
||||
<div class="calendar-action-buttons">
|
||||
<div class="calendar-month-year">
|
||||
<button
|
||||
class={{
|
||||
'calendar-month-year-toggle': true,
|
||||
'ion-activatable': true,
|
||||
'ion-focusable': true,
|
||||
}}
|
||||
<ion-item
|
||||
part="month-year-button"
|
||||
ref={(el) => (this.monthYearToggleItemRef = el)}
|
||||
button
|
||||
aria-label="Show year picker"
|
||||
detail={false}
|
||||
lines="none"
|
||||
disabled={disabled}
|
||||
aria-label={this.showMonthAndYear ? 'Hide year picker' : 'Show year picker'}
|
||||
onClick={() => this.toggleMonthAndYearView()}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span id="toggle-wrapper">
|
||||
<ion-label>
|
||||
{getMonthAndYear(this.locale, this.workingParts)}
|
||||
<ion-icon
|
||||
aria-hidden="true"
|
||||
@@ -2038,9 +2036,8 @@ export class Datetime implements ComponentInterface {
|
||||
lazy={false}
|
||||
flipRtl={true}
|
||||
></ion-icon>
|
||||
</span>
|
||||
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<div class="calendar-next-prev">
|
||||
@@ -2345,7 +2342,7 @@ export class Datetime implements ComponentInterface {
|
||||
* This will correctly scroll the element position to the correct time value,
|
||||
* before the popover is fully presented.
|
||||
*/
|
||||
const cols = (ev.target! as HTMLElement).querySelectorAll('ion-picker-column');
|
||||
const cols = (ev.target! as HTMLElement).querySelectorAll('ion-picker-column-internal');
|
||||
// TODO (FW-615): Potentially remove this when intersection observers are fixed in picker column
|
||||
cols.forEach((col) => col.scrollActiveItemIntoView());
|
||||
}}
|
||||
|
||||
@@ -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-toggle');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
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: 52 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@@ -1,6 +1,7 @@
|
||||
import type { SpecPage } from '@stencil/core/testing';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Item } from '../../../item/item';
|
||||
import { Datetime } from '../../datetime';
|
||||
|
||||
describe('datetime', () => {
|
||||
@@ -19,14 +20,15 @@ describe('datetime', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newSpecPage({
|
||||
components: [Datetime],
|
||||
components: [Datetime, Item],
|
||||
html: `<ion-datetime></ion-datetime>`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have aria-label "Show year picker" when collapsed', async () => {
|
||||
const datetime = page.body.querySelector('ion-datetime')!;
|
||||
const monthYearToggleBtn = datetime.shadowRoot!.querySelector('.calendar-month-year .calendar-month-year-toggle');
|
||||
const item = datetime.shadowRoot!.querySelector('.calendar-month-year ion-item');
|
||||
const monthYearToggleBtn = item!.shadowRoot!.querySelector('button');
|
||||
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
|
||||
|
||||
expect(ariaLabel).toContain('Show year picker');
|
||||
@@ -34,18 +36,15 @@ describe('datetime', () => {
|
||||
|
||||
it('should have aria-label "Hide year picker" when expanded', async () => {
|
||||
const datetime = page.body.querySelector('ion-datetime')!;
|
||||
const monthYearToggleBtn = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
|
||||
'.calendar-month-year .calendar-month-year-toggle'
|
||||
);
|
||||
const item = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
|
||||
|
||||
monthYearToggleBtn!.click();
|
||||
item!.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const monthYearToggleBtnAfter = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
|
||||
'.calendar-month-year .calendar-month-year-toggle'
|
||||
);
|
||||
const ariaLabel = monthYearToggleBtnAfter!.getAttribute('aria-label');
|
||||
const itemAfter = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
|
||||
const monthYearToggleBtn = itemAfter!.shadowRoot!.querySelector<HTMLElement>('button');
|
||||
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
|
||||
|
||||
expect(ariaLabel).toContain('Hide year picker');
|
||||
});
|
||||
|
||||
@@ -526,20 +526,4 @@ 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`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,22 +1,21 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
import { isSameDay, isBefore, isAfter } from '../utils/comparison';
|
||||
|
||||
describe('isSameDay()', () => {
|
||||
it('should return correct results for month, day, and year', () => {
|
||||
const reference: DatetimeParts = { month: 1, day: 1, year: 2021 };
|
||||
const reference = { month: 1, day: 1, year: 2021 };
|
||||
|
||||
expect(isSameDay(reference, { month: 1, day: 1, year: 2021 })).toEqual(true);
|
||||
expect(isSameDay(reference, { month: 2, day: 1, year: 2021 })).toEqual(false);
|
||||
expect(isSameDay(reference, { month: 1, day: 2, year: 2021 })).toEqual(false);
|
||||
expect(isSameDay(reference, { month: 1, day: 1, year: 2022 })).toEqual(false);
|
||||
expect(isSameDay(reference, { month: 0, day: 0, year: 0 })).toEqual(false);
|
||||
expect(isSameDay(reference, { month: null, day: null, year: null } as any)).toEqual(false);
|
||||
expect(isSameDay(reference, { month: null, day: null, year: null })).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBefore()', () => {
|
||||
it('should return correct results for month, day, and year', () => {
|
||||
const reference: DatetimeParts = { month: 1, day: 1, year: 2021 };
|
||||
const reference = { month: 1, day: 1, year: 2021 };
|
||||
|
||||
expect(isBefore(reference, { month: 1, day: 1, year: 2021 })).toEqual(false);
|
||||
expect(isBefore(reference, { month: 2, day: 1, year: 2021 })).toEqual(true);
|
||||
@@ -24,13 +23,13 @@ describe('isBefore()', () => {
|
||||
expect(isBefore(reference, { month: 1, day: 1, year: 2022 })).toEqual(true);
|
||||
expect(isBefore(reference, { month: 1, day: 1, year: 2020 })).toEqual(false);
|
||||
expect(isBefore(reference, { month: 0, day: 0, year: 0 })).toEqual(false);
|
||||
expect(isBefore(reference, { month: null, day: null, year: null } as any)).toEqual(false);
|
||||
expect(isBefore(reference, { month: null, day: null, year: null })).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAfter()', () => {
|
||||
it('should return correct results for month, day, and year', () => {
|
||||
const reference: DatetimeParts = { month: 2, day: 2, year: 2021 };
|
||||
const reference = { month: 2, day: 2, year: 2021 };
|
||||
|
||||
expect(isAfter(reference, { month: 2, day: 2, year: 2021 })).toEqual(false);
|
||||
expect(isAfter(reference, { month: 2, day: 1, year: 2021 })).toEqual(true);
|
||||
@@ -43,6 +42,6 @@ describe('isAfter()', () => {
|
||||
* 2021 > undefined === false
|
||||
* 2021 > null === true
|
||||
*/
|
||||
expect(isAfter(reference, { month: null, day: null, year: null } as any)).toEqual(true);
|
||||
expect(isAfter(reference, { month: null, day: null, year: null })).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,28 +52,28 @@
|
||||
}
|
||||
|
||||
/*
|
||||
The second selectors that target ion-picker(-column)
|
||||
The second selectors that target ion-picker(-column)-internal
|
||||
directly are for styling the time picker. This is currently
|
||||
undocumented usage.
|
||||
*/
|
||||
|
||||
.custom-grid-wheel,
|
||||
ion-picker {
|
||||
ion-picker-internal {
|
||||
--wheel-highlight-background: rgb(218, 216, 255);
|
||||
--wheel-fade-background-rgb: 245, 235, 247;
|
||||
}
|
||||
|
||||
ion-picker {
|
||||
ion-picker-internal {
|
||||
background-color: rgb(245, 235, 247);
|
||||
}
|
||||
|
||||
.custom-grid-wheel::part(wheel-item),
|
||||
ion-picker-column::part(wheel-item) {
|
||||
ion-picker-column-internal::part(wheel-item) {
|
||||
color: rgb(255, 134, 154);
|
||||
}
|
||||
|
||||
.custom-grid-wheel::part(wheel-item active),
|
||||
ion-picker-column::part(wheel-item active) {
|
||||
ion-picker-column-internal::part(wheel-item active) {
|
||||
color: rgb(128, 30, 171);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
import {
|
||||
generateMonths,
|
||||
getDaysOfWeek,
|
||||
@@ -365,7 +364,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 19,
|
||||
minute: 50,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const minParts = {
|
||||
day: undefined,
|
||||
@@ -373,7 +372,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 19,
|
||||
minute: 50,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const { hours } = generateTime('en-US', refValue, 'h23', minParts);
|
||||
|
||||
@@ -388,7 +387,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 20,
|
||||
minute: 22,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const minParts = {
|
||||
day: undefined,
|
||||
@@ -396,7 +395,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 19,
|
||||
minute: 30,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const { hours, minutes } = generateTime('en-US', refValue, 'h23', minParts);
|
||||
|
||||
@@ -412,7 +411,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 20,
|
||||
minute: 30,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const minParts = {
|
||||
day: undefined,
|
||||
@@ -420,7 +419,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 19,
|
||||
minute: 30,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const maxParts = {
|
||||
day: undefined,
|
||||
@@ -428,7 +427,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 20,
|
||||
minute: 40,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const { hours } = generateTime('en-US', refValue, 'h23', minParts, maxParts);
|
||||
|
||||
@@ -442,7 +441,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 13,
|
||||
minute: 0,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const maxParts = {
|
||||
day: undefined,
|
||||
@@ -450,7 +449,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 13,
|
||||
minute: 2,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const { minutes } = generateTime('en-US', refValue, 'h23', undefined, maxParts);
|
||||
|
||||
@@ -464,7 +463,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 12,
|
||||
minute: 0,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const maxParts = {
|
||||
day: undefined,
|
||||
@@ -472,7 +471,7 @@ describe('generateTime()', () => {
|
||||
year: undefined,
|
||||
hour: 13,
|
||||
minute: 2,
|
||||
} as unknown as DatetimeParts;
|
||||
};
|
||||
|
||||
const { minutes } = generateTime('en-US', refValue, 'h23', undefined, maxParts);
|
||||
|
||||
@@ -483,7 +482,7 @@ describe('generateTime()', () => {
|
||||
|
||||
describe('getToday', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.useFakeTimers('modern');
|
||||
// System time is zero based, 1 = February
|
||||
jest.setSystemTime(new Date(2022, 1, 21, 18, 30));
|
||||
});
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { h } from '@stencil/core';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Datetime } from '../../../datetime/datetime';
|
||||
import { PickerColumn } from '../../../picker-column/picker-column';
|
||||
import { Picker } from '../../../picker/picker';
|
||||
|
||||
describe('ion-datetime disabled', () => {
|
||||
beforeEach(() => {
|
||||
// IntersectionObserver isn't available in test environment
|
||||
const mockIntersectionObserver = jest.fn();
|
||||
mockIntersectionObserver.mockReturnValue({
|
||||
observe: () => null,
|
||||
unobserve: () => null,
|
||||
disconnect: () => null,
|
||||
});
|
||||
global.IntersectionObserver = mockIntersectionObserver;
|
||||
});
|
||||
|
||||
it('picker should be disabled in prefer wheel mode', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Datetime, PickerColumn, Picker],
|
||||
template: () => (
|
||||
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
|
||||
),
|
||||
});
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const datetime = page.body.querySelector('ion-datetime')!;
|
||||
const columns = datetime.shadowRoot!.querySelectorAll('ion-picker-column');
|
||||
|
||||
await expect(columns.length).toEqual(4);
|
||||
|
||||
columns.forEach((column) => {
|
||||
expect(column.disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -66,11 +66,6 @@
|
||||
<h2>Inline - No Default Value</h2>
|
||||
<ion-datetime id="inline-datetime-no-value" disabled></ion-datetime>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Inline - Prefer Wheel</h2>
|
||||
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
<script>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
import {
|
||||
generateDayAriaLabel,
|
||||
getMonthAndDay,
|
||||
@@ -110,7 +109,7 @@ describe('getLocalizedDayPeriod', () => {
|
||||
|
||||
describe('getLocalizedTime', () => {
|
||||
it('should localize the time to PM', () => {
|
||||
const datetimeParts: DatetimeParts = {
|
||||
const datetimeParts = {
|
||||
day: 1,
|
||||
month: 1,
|
||||
year: 2022,
|
||||
@@ -122,7 +121,7 @@ describe('getLocalizedTime', () => {
|
||||
});
|
||||
|
||||
it('should localize the time to AM', () => {
|
||||
const datetimeParts: DatetimeParts = {
|
||||
const datetimeParts = {
|
||||
day: 1,
|
||||
month: 1,
|
||||
year: 2022,
|
||||
@@ -134,7 +133,7 @@ describe('getLocalizedTime', () => {
|
||||
});
|
||||
|
||||
it('should avoid Chromium bug when using 12 hour time in a 24 hour locale', () => {
|
||||
const datetimeParts: DatetimeParts = {
|
||||
const datetimeParts = {
|
||||
day: 1,
|
||||
month: 1,
|
||||
year: 2022,
|
||||
@@ -145,12 +144,12 @@ describe('getLocalizedTime', () => {
|
||||
expect(getLocalizedTime('en-GB', datetimeParts, 'h12')).toEqual('12:00 am');
|
||||
});
|
||||
it('should parse time-only values correctly', () => {
|
||||
const datetimeParts: Partial<DatetimeParts> = {
|
||||
const datetimeParts = {
|
||||
hour: 22,
|
||||
minute: 40,
|
||||
};
|
||||
|
||||
expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h12')).toEqual('10:40 PM');
|
||||
expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h23')).toEqual('22:40');
|
||||
expect(getLocalizedTime('en-US', datetimeParts, 'h12')).toEqual('10:40 PM');
|
||||
expect(getLocalizedTime('en-US', datetimeParts, 'h23')).toEqual('22:40');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { DatetimeParts } from '../datetime-interface';
|
||||
import {
|
||||
getPreviousYear,
|
||||
getNextYear,
|
||||
@@ -104,31 +103,31 @@ describe('getInternalHourValue()', () => {
|
||||
|
||||
describe('calculateHourFromAMPM()', () => {
|
||||
it('should correctly convert from AM to PM', () => {
|
||||
expect(calculateHourFromAMPM({ hour: 12, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(12);
|
||||
expect(calculateHourFromAMPM({ hour: 1, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(13);
|
||||
expect(calculateHourFromAMPM({ hour: 2, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(14);
|
||||
expect(calculateHourFromAMPM({ hour: 3, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(15);
|
||||
expect(calculateHourFromAMPM({ hour: 4, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(16);
|
||||
expect(calculateHourFromAMPM({ hour: 5, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(17);
|
||||
expect(calculateHourFromAMPM({ hour: 6, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(18);
|
||||
expect(calculateHourFromAMPM({ hour: 7, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(19);
|
||||
expect(calculateHourFromAMPM({ hour: 8, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(20);
|
||||
expect(calculateHourFromAMPM({ hour: 9, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(21);
|
||||
expect(calculateHourFromAMPM({ hour: 10, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(22);
|
||||
expect(calculateHourFromAMPM({ hour: 11, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(23);
|
||||
expect(calculateHourFromAMPM({ hour: 12, ampm: 'am' }, 'pm')).toEqual(12);
|
||||
expect(calculateHourFromAMPM({ hour: 1, ampm: 'am' }, 'pm')).toEqual(13);
|
||||
expect(calculateHourFromAMPM({ hour: 2, ampm: 'am' }, 'pm')).toEqual(14);
|
||||
expect(calculateHourFromAMPM({ hour: 3, ampm: 'am' }, 'pm')).toEqual(15);
|
||||
expect(calculateHourFromAMPM({ hour: 4, ampm: 'am' }, 'pm')).toEqual(16);
|
||||
expect(calculateHourFromAMPM({ hour: 5, ampm: 'am' }, 'pm')).toEqual(17);
|
||||
expect(calculateHourFromAMPM({ hour: 6, ampm: 'am' }, 'pm')).toEqual(18);
|
||||
expect(calculateHourFromAMPM({ hour: 7, ampm: 'am' }, 'pm')).toEqual(19);
|
||||
expect(calculateHourFromAMPM({ hour: 8, ampm: 'am' }, 'pm')).toEqual(20);
|
||||
expect(calculateHourFromAMPM({ hour: 9, ampm: 'am' }, 'pm')).toEqual(21);
|
||||
expect(calculateHourFromAMPM({ hour: 10, ampm: 'am' }, 'pm')).toEqual(22);
|
||||
expect(calculateHourFromAMPM({ hour: 11, ampm: 'am' }, 'pm')).toEqual(23);
|
||||
|
||||
expect(calculateHourFromAMPM({ hour: 13, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(1);
|
||||
expect(calculateHourFromAMPM({ hour: 14, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(2);
|
||||
expect(calculateHourFromAMPM({ hour: 15, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(3);
|
||||
expect(calculateHourFromAMPM({ hour: 16, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(4);
|
||||
expect(calculateHourFromAMPM({ hour: 17, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(5);
|
||||
expect(calculateHourFromAMPM({ hour: 18, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(6);
|
||||
expect(calculateHourFromAMPM({ hour: 19, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(7);
|
||||
expect(calculateHourFromAMPM({ hour: 20, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(8);
|
||||
expect(calculateHourFromAMPM({ hour: 21, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(9);
|
||||
expect(calculateHourFromAMPM({ hour: 22, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(10);
|
||||
expect(calculateHourFromAMPM({ hour: 23, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(11);
|
||||
expect(calculateHourFromAMPM({ hour: 0, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(12);
|
||||
expect(calculateHourFromAMPM({ hour: 13, ampm: 'pm' }, 'am')).toEqual(1);
|
||||
expect(calculateHourFromAMPM({ hour: 14, ampm: 'pm' }, 'am')).toEqual(2);
|
||||
expect(calculateHourFromAMPM({ hour: 15, ampm: 'pm' }, 'am')).toEqual(3);
|
||||
expect(calculateHourFromAMPM({ hour: 16, ampm: 'pm' }, 'am')).toEqual(4);
|
||||
expect(calculateHourFromAMPM({ hour: 17, ampm: 'pm' }, 'am')).toEqual(5);
|
||||
expect(calculateHourFromAMPM({ hour: 18, ampm: 'pm' }, 'am')).toEqual(6);
|
||||
expect(calculateHourFromAMPM({ hour: 19, ampm: 'pm' }, 'am')).toEqual(7);
|
||||
expect(calculateHourFromAMPM({ hour: 20, ampm: 'pm' }, 'am')).toEqual(8);
|
||||
expect(calculateHourFromAMPM({ hour: 21, ampm: 'pm' }, 'am')).toEqual(9);
|
||||
expect(calculateHourFromAMPM({ hour: 22, ampm: 'pm' }, 'am')).toEqual(10);
|
||||
expect(calculateHourFromAMPM({ hour: 23, ampm: 'pm' }, 'am')).toEqual(11);
|
||||
expect(calculateHourFromAMPM({ hour: 0, ampm: 'pm' }, 'am')).toEqual(12);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -109,8 +109,12 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
await page.click('.time-body');
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
const hours = page.locator('ion-popover ion-picker-column:nth-child(1) .picker-item:not(.picker-item-empty)');
|
||||
const minutes = page.locator('ion-popover ion-picker-column:nth-child(2) .picker-item:not(.picker-item-empty)');
|
||||
const hours = page.locator(
|
||||
'ion-popover ion-picker-column-internal:nth-child(1) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
const minutes = page.locator(
|
||||
'ion-popover ion-picker-column-internal:nth-child(2) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
|
||||
expect(await hours.count()).toBe(12);
|
||||
expect(await minutes.count()).toBe(60);
|
||||
@@ -215,7 +219,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
);
|
||||
|
||||
const hourPickerItems = page.locator(
|
||||
'ion-datetime ion-picker-column:first-of-type .picker-item:not(.picker-item-empty)'
|
||||
'ion-datetime ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
await expect(hourPickerItems).toHaveText(['8', '9', '10', '11']);
|
||||
});
|
||||
@@ -239,7 +243,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
);
|
||||
|
||||
const hourPickerItems = page.locator(
|
||||
'ion-datetime ion-picker-column:first-of-type .picker-item:not(.picker-item-empty)'
|
||||
'ion-datetime ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
await expect(hourPickerItems).toHaveText(['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']);
|
||||
});
|
||||
@@ -356,7 +360,9 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
const hours = page.locator('ion-popover ion-picker-column:nth-child(1) .picker-item:not(.picker-item-empty)');
|
||||
const hours = page.locator(
|
||||
'ion-popover ion-picker-column-internal:nth-child(1) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
|
||||
await expect(await hours.count()).toBe(4);
|
||||
});
|
||||
|
||||
@@ -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 > .calendar-month-year-toggle');
|
||||
const pickerButton = page.locator('#date-time .calendar-month-year > ion-item');
|
||||
await pickerButton.click();
|
||||
await page.waitForChanges();
|
||||
await expect(datetimeFooter).not.toBeVisible();
|
||||
|
||||
@@ -42,8 +42,7 @@ describe('parseDate()', () => {
|
||||
* See https://github.com/ionic-team/ionic-framework/commit/3fb4caf21ffac12f765c4c80bf1850e05d211c6a
|
||||
*/
|
||||
it('should return the correct time zone offset', () => {
|
||||
// Casting as any since `tzOffset` does not exist on DatetimeParts
|
||||
expect((parseDate('2022-12-15T13:47:30-02:00') as any)?.tzOffset).toEqual(undefined);
|
||||
expect(parseDate('2022-12-15T13:47:30-02:00').tzOffset).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should parse an array of dates', () => {
|
||||
@@ -163,8 +162,8 @@ describe('parseMinParts()', () => {
|
||||
minute: 4,
|
||||
hour: 2,
|
||||
};
|
||||
expect(parseMinParts(undefined as any, today)).toEqual(undefined);
|
||||
expect(parseMinParts(null as any, today)).toEqual(undefined);
|
||||
expect(parseMinParts(undefined, today)).toEqual(undefined);
|
||||
expect(parseMinParts(null, today)).toEqual(undefined);
|
||||
expect(parseMinParts('foo', today)).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -226,8 +225,8 @@ describe('parseMaxParts()', () => {
|
||||
minute: 4,
|
||||
hour: 2,
|
||||
};
|
||||
expect(parseMaxParts(undefined as any, today)).toEqual(undefined);
|
||||
expect(parseMaxParts(null as any, today)).toEqual(undefined);
|
||||
expect(parseMaxParts(undefined, today)).toEqual(undefined);
|
||||
expect(parseMaxParts(null, today)).toEqual(undefined);
|
||||
expect(parseMaxParts('foo', today)).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -308,7 +308,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const columns = page.locator('ion-picker-column');
|
||||
const columns = page.locator('ion-picker-column-internal');
|
||||
|
||||
await expect(columns.nth(0)).toHaveClass(/month-column/);
|
||||
await expect(columns.nth(1)).toHaveClass(/day-column/);
|
||||
@@ -329,7 +329,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const columns = page.locator('ion-picker-column');
|
||||
const columns = page.locator('ion-picker-column-internal');
|
||||
|
||||
await expect(columns.nth(0)).toHaveClass(/day-column/);
|
||||
await expect(columns.nth(1)).toHaveClass(/month-column/);
|
||||
|
||||
@@ -227,7 +227,7 @@ class TimePickerFixture {
|
||||
}
|
||||
|
||||
async expectTime(hour: number, minute: number, ampm: string) {
|
||||
const pickerColumns = this.timePicker.locator('ion-picker-column');
|
||||
const pickerColumns = this.timePicker.locator('ion-picker-column-internal');
|
||||
|
||||
await expect(pickerColumns.nth(0)).toHaveJSProperty('value', hour);
|
||||
await expect(pickerColumns.nth(1)).toHaveJSProperty('value', minute);
|
||||
|
||||
@@ -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-toggle');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
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-toggle');
|
||||
const monthYearButton = page.locator('.calendar-month-year ion-item');
|
||||
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');
|
||||
|
||||
@@ -84,13 +84,13 @@ describe('isPrevMonthDisabled()', () => {
|
||||
// Date month and year is the same as min month and year
|
||||
expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { month: 1, year: 2021, day: null })).toEqual(true);
|
||||
// Date year is the same as min year (month not provided)
|
||||
expect(
|
||||
isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
|
||||
).toEqual(true);
|
||||
expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
|
||||
true
|
||||
);
|
||||
// Date year is less than the min year (month not provided)
|
||||
expect(
|
||||
isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022, month: null, day: null } as any)
|
||||
).toEqual(true);
|
||||
expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022, month: null, day: null })).toEqual(
|
||||
true
|
||||
);
|
||||
|
||||
// Date is above the maximum bounds and the previous month does not does not fall within the
|
||||
// min-max range.
|
||||
@@ -118,12 +118,12 @@ describe('isPrevMonthDisabled()', () => {
|
||||
expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null })).toEqual(false);
|
||||
// Date year is the same as min year,
|
||||
// but can navigate to a previous month without reducing the year.
|
||||
expect(
|
||||
isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
|
||||
).toEqual(false);
|
||||
expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
|
||||
false
|
||||
);
|
||||
expect(isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const items = page.locator('ion-picker-column:first-of-type .picker-item:not(.picker-item-empty)');
|
||||
const items = page.locator('ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)');
|
||||
await expect(items).toHaveText(['1', '2', '3']);
|
||||
});
|
||||
test('should render correct minutes', async ({ page }) => {
|
||||
@@ -62,7 +62,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const items = page.locator('ion-picker-column:nth-of-type(2) .picker-item:not(.picker-item-empty)');
|
||||
const items = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
|
||||
await expect(items).toHaveText(['01', '02', '03']);
|
||||
});
|
||||
test('should adjust default parts for allowed hour and minute values', async ({ page }) => {
|
||||
@@ -93,11 +93,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const minuteItems = page.locator('ion-picker-column:nth-of-type(2) .picker-item:not(.picker-item-empty)');
|
||||
const minuteItems = page.locator(
|
||||
'ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)'
|
||||
);
|
||||
await expect(minuteItems).toHaveText(['00', '15', '30', '45']);
|
||||
await expect(minuteItems.nth(1)).toHaveClass(/picker-item-active/);
|
||||
|
||||
const hourItems = page.locator('ion-picker-column:nth-of-type(1) .picker-item:not(.picker-item-empty)');
|
||||
const hourItems = page.locator('ion-picker-column-internal:nth-of-type(1) .picker-item:not(.picker-item-empty)');
|
||||
await expect(hourItems).toHaveText(['2']);
|
||||
await expect(hourItems.nth(0)).toHaveClass(/picker-item-active/);
|
||||
|
||||
@@ -105,7 +107,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
* Since the allowed hour is 2AM, the time period
|
||||
* should switch from PM to AM.
|
||||
*/
|
||||
const ampmItems = page.locator('ion-picker-column:nth-of-type(3) .picker-item:not(.picker-item-empty)');
|
||||
const ampmItems = page.locator('ion-picker-column-internal:nth-of-type(3) .picker-item:not(.picker-item-empty)');
|
||||
await expect(ampmItems).toHaveText(['AM', 'PM']);
|
||||
await expect(ampmItems.nth(0)).toHaveClass(/picker-item-active/);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Mode } from '../../../interface';
|
||||
import type { PickerColumnItem } from '../../picker-column/picker-column-interfaces';
|
||||
import type { PickerColumnItem } from '../../picker-column-internal/picker-column-internal-interfaces';
|
||||
import type { DatetimeParts, DatetimeHourCycle } from '../datetime-interface';
|
||||
|
||||
import { isAfter, isBefore, isSameDay } from './comparison';
|
||||
@@ -380,7 +380,7 @@ export const getMonthColumnData = (
|
||||
* @param minParts The minimum bound on the date that can be returned
|
||||
* @param maxParts The maximum bound on the date that can be returned
|
||||
* @param dayValues The allowed date values
|
||||
* @returns Date data to be used in ion-picker-column
|
||||
* @returns Date data to be used in ion-picker-column-internal
|
||||
*/
|
||||
export const getDayColumnData = (
|
||||
locale: string,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { config } from '../../../global/config';
|
||||
import { InfiniteScrollContent } from '../infinite-scroll-content';
|
||||
import { config } from '../../../global/config';
|
||||
|
||||
describe('infinite-scroll-content: custom html', () => {
|
||||
it('should not allow for custom html by default', async () => {
|
||||
@@ -10,7 +9,7 @@ describe('infinite-scroll-content: custom html', () => {
|
||||
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.infinite-loading-text')!;
|
||||
const content = page.body.querySelector('.infinite-loading-text');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||
});
|
||||
@@ -22,7 +21,7 @@ describe('infinite-scroll-content: custom html', () => {
|
||||
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.infinite-loading-text')!;
|
||||
const content = page.body.querySelector('.infinite-loading-text');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).not.toBe(null);
|
||||
});
|
||||
@@ -34,7 +33,7 @@ describe('infinite-scroll-content: custom html', () => {
|
||||
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text2</button>"></ion-infinite-scroll-content>`,
|
||||
});
|
||||
|
||||
const content = page.body.querySelector('.infinite-loading-text')!;
|
||||
const content = page.body.querySelector('.infinite-loading-text');
|
||||
expect(content.textContent).toContain('Custom Text');
|
||||
expect(content.querySelector('button.custom-html')).toBe(null);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Input } from '../input';
|
||||
|
||||
describe('input: rendering', () => {
|
||||
@@ -9,7 +8,7 @@ describe('input: rendering', () => {
|
||||
html: '<ion-input title="my title" tabindex="-1" data-form-type="password"></ion-input>',
|
||||
});
|
||||
|
||||
const nativeEl = page.body.querySelector('ion-input input')!;
|
||||
const nativeEl = page.body.querySelector('ion-input input');
|
||||
expect(nativeEl.getAttribute('title')).toBe('my title');
|
||||
expect(nativeEl.getAttribute('tabindex')).toBe('-1');
|
||||
expect(nativeEl.getAttribute('data-form-type')).toBe('password');
|
||||
@@ -64,9 +63,9 @@ describe('input: label rendering', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
const input = page.body.querySelector('ion-input');
|
||||
|
||||
const labelText = input.querySelector('.label-text-wrapper')!;
|
||||
const labelText = input.querySelector('.label-text-wrapper');
|
||||
|
||||
expect(labelText.textContent).toBe('Label Prop Text');
|
||||
});
|
||||
@@ -78,9 +77,9 @@ describe('input: label rendering', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
const input = page.body.querySelector('ion-input');
|
||||
|
||||
const labelText = input.querySelector('.label-text-wrapper')!;
|
||||
const labelText = input.querySelector('.label-text-wrapper');
|
||||
|
||||
expect(labelText.textContent).toBe('Label Slot Text');
|
||||
});
|
||||
@@ -92,9 +91,9 @@ describe('input: label rendering', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
const input = page.body.querySelector('ion-input');
|
||||
|
||||
const labelText = input.querySelector('.label-text-wrapper')!;
|
||||
const labelText = input.querySelector('.label-text-wrapper');
|
||||
|
||||
expect(labelText.textContent).toBe('Label Prop Text');
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Item } from '../../../item/item';
|
||||
import { Input } from '../../input';
|
||||
import { Item } from '../../../item/item';
|
||||
|
||||
it('should render as modern when label is set asynchronously', async () => {
|
||||
const page = await newSpecPage({
|
||||
@@ -13,7 +12,7 @@ it('should render as modern when label is set asynchronously', async () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
const input = page.body.querySelector('ion-input');
|
||||
|
||||
// Template should be modern
|
||||
expect(input.classList.contains('legacy-input')).toBe(false);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Radio } from '../../../radio/radio.tsx';
|
||||
import { RadioGroup } from '../../../radio-group/radio-group.tsx';
|
||||
import { Item } from '../../item.tsx';
|
||||
import { List } from '../../../list/list.tsx';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { List } from '../../../list/list';
|
||||
import { RadioGroup } from '../../../radio-group/radio-group';
|
||||
import { Radio } from '../../../radio/radio';
|
||||
import { Item } from '../../item';
|
||||
|
||||
describe('ion-item', () => {
|
||||
it('should not have a role when used without list', async () => {
|
||||
const page = await newSpecPage({
|
||||
@@ -12,7 +11,7 @@ describe('ion-item', () => {
|
||||
html: `<ion-item>Hello World</ion-item>`,
|
||||
});
|
||||
|
||||
const item = page.body.querySelector('ion-item')!;
|
||||
const item = page.body.querySelector('ion-item');
|
||||
expect(item.getAttribute('role')).toBe(null);
|
||||
});
|
||||
|
||||
@@ -28,7 +27,7 @@ describe('ion-item', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const item = page.body.querySelector('ion-item')!;
|
||||
const item = page.body.querySelector('ion-item');
|
||||
expect(item.getAttribute('role')).toBe('listitem');
|
||||
});
|
||||
|
||||
@@ -46,7 +45,7 @@ describe('ion-item', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const item = page.body.querySelector('ion-item')!;
|
||||
const item = page.body.querySelector('ion-item');
|
||||
expect(item.getAttribute('role')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,10 +7,10 @@ describe('loading: htmlAttributes inheritance', () => {
|
||||
it('should correctly inherit attributes on host', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Loading],
|
||||
template: () => <ion-loading overlayIndex={1} htmlAttributes={{ 'data-testid': 'basic-loading' }}></ion-loading>,
|
||||
template: () => <ion-loading htmlAttributes={{ 'data-testid': 'basic-loading' }}></ion-loading>,
|
||||
});
|
||||
|
||||
const loading = page.body.querySelector('ion-loading')!;
|
||||
const loading = page.body.querySelector('ion-loading');
|
||||
|
||||
await expect(loading.getAttribute('data-testid')).toBe('basic-loading');
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
width!: number;
|
||||
_isOpen = false;
|
||||
|
||||
backdropEl?: HTMLIonBackdropElement;
|
||||
backdropEl?: HTMLElement;
|
||||
menuInnerEl?: HTMLElement;
|
||||
contentEl?: HTMLElement;
|
||||
lastFocus?: HTMLElement;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
// iOS Card Modal
|
||||
// --------------------------------------------------
|
||||
|
||||
@include mobile-viewport() {
|
||||
@media screen and (max-width: 767px) {
|
||||
@supports (width: max(0px, 1px)) {
|
||||
:host(.modal-card) {
|
||||
--height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include tablet-viewport() {
|
||||
@media screen and (min-width: 768px) {
|
||||
:host(.modal-card) {
|
||||
--width: calc(100% - 120px);
|
||||
--height: calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));
|
||||
|
||||
@@ -15,8 +15,8 @@ describe('modal: a11y', () => {
|
||||
`,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modalWrapper = modal.shadowRoot!.querySelector('.modal-wrapper')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
const modalWrapper = modal.shadowRoot.querySelector('.modal-wrapper');
|
||||
|
||||
await expect(modalWrapper.getAttribute('role')).toBe('alertdialog');
|
||||
});
|
||||
|
||||
@@ -7,10 +7,10 @@ describe('modal: htmlAttributes inheritance', () => {
|
||||
it('should correctly inherit attributes on host', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => <ion-modal htmlAttributes={{ 'data-testid': 'basic-modal' }} overlayIndex={1}></ion-modal>,
|
||||
template: () => <ion-modal htmlAttributes={{ 'data-testid': 'basic-modal' }}></ion-modal>,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await expect(modal.getAttribute('data-testid')).toBe('basic-modal');
|
||||
});
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { h, setMode } from '@stencil/core';
|
||||
import { h } from '@stencil/core';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { setMode } from '@stencil/core';
|
||||
|
||||
import { Content } from '../../../content/content';
|
||||
import { Modal } from '../../modal';
|
||||
import { Content } from '../../../content/content';
|
||||
|
||||
describe('modal: canDismiss', () => {
|
||||
describe('modal: regular modal', () => {
|
||||
it('should dismiss when canDismiss is true', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => <ion-modal overlayIndex={1} animated={false} canDismiss={true}></ion-modal>,
|
||||
template: () => <ion-modal animated={false} canDismiss={true}></ion-modal>,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -25,10 +26,10 @@ describe('modal: canDismiss', () => {
|
||||
it('should not dismiss when canDismiss is false', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => <ion-modal overlayIndex={1} animated={false} canDismiss={false}></ion-modal>,
|
||||
template: () => <ion-modal animated={false} canDismiss={false}></ion-modal>,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -43,7 +44,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
animated={false}
|
||||
canDismiss={() => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -54,7 +54,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -69,7 +69,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
animated={false}
|
||||
canDismiss={() => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -80,7 +79,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -95,24 +94,19 @@ describe('modal: canDismiss', () => {
|
||||
/**
|
||||
* Card modal is only available on iOS
|
||||
*/
|
||||
setMode(() => 'ios');
|
||||
setMode((elm) => 'ios');
|
||||
});
|
||||
it('should dismiss when canDismiss is true', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Content, Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
presentingElement={document.createElement('div')}
|
||||
animated={false}
|
||||
canDismiss={true}
|
||||
>
|
||||
<ion-modal presentingElement={document.createElement('div')} animated={false} canDismiss={true}>
|
||||
<ion-content>Test Content</ion-content>
|
||||
</ion-modal>
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -126,18 +120,13 @@ describe('modal: canDismiss', () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Content, Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
presentingElement={document.createElement('div')}
|
||||
animated={false}
|
||||
canDismiss={false}
|
||||
>
|
||||
<ion-modal presentingElement={document.createElement('div')} animated={false} canDismiss={false}>
|
||||
<ion-content>Test Content</ion-content>
|
||||
</ion-modal>
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -152,7 +141,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Content, Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
presentingElement={document.createElement('div')}
|
||||
animated={false}
|
||||
canDismiss={() => {
|
||||
@@ -166,7 +154,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -181,7 +169,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Content, Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
presentingElement={document.createElement('div')}
|
||||
animated={false}
|
||||
canDismiss={() => {
|
||||
@@ -195,7 +182,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -210,17 +197,11 @@ describe('modal: canDismiss', () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
breakpoints={[0, 1]}
|
||||
initialBreakpoint={1}
|
||||
animated={false}
|
||||
canDismiss={true}
|
||||
></ion-modal>
|
||||
<ion-modal breakpoints={[0, 1]} initialBreakpoint={1} animated={false} canDismiss={true}></ion-modal>
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -234,17 +215,11 @@ describe('modal: canDismiss', () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
breakpoints={[0, 1]}
|
||||
initialBreakpoint={1}
|
||||
animated={false}
|
||||
canDismiss={false}
|
||||
></ion-modal>
|
||||
<ion-modal breakpoints={[0, 1]} initialBreakpoint={1} animated={false} canDismiss={false}></ion-modal>
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -259,7 +234,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
breakpoints={[0, 1]}
|
||||
initialBreakpoint={1}
|
||||
animated={false}
|
||||
@@ -272,7 +246,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -287,7 +261,6 @@ describe('modal: canDismiss', () => {
|
||||
components: [Modal],
|
||||
template: () => (
|
||||
<ion-modal
|
||||
overlayIndex={1}
|
||||
breakpoints={[0, 1]}
|
||||
initialBreakpoint={1}
|
||||
animated={false}
|
||||
@@ -300,7 +273,7 @@ describe('modal: canDismiss', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
@@ -315,15 +288,15 @@ describe('modal: canDismiss', () => {
|
||||
const canDismiss = jest.fn();
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
template: () => <ion-modal overlayIndex={1} animated={false} canDismiss={canDismiss}></ion-modal>,
|
||||
template: () => <ion-modal animated={false} canDismiss={canDismiss}></ion-modal>,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const modal = page.body.querySelector('ion-modal');
|
||||
|
||||
await modal.present();
|
||||
await page.waitForChanges();
|
||||
|
||||
await modal.dismiss('my data', 'my role');
|
||||
const returnValue = await modal.dismiss('my data', 'my role');
|
||||
|
||||
expect(canDismiss).toHaveBeenCalledWith('my data', 'my role');
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Config } from '../../../global/config';
|
||||
import type { ComponentProps } from '../../../interface';
|
||||
import { Nav } from '../nav';
|
||||
import type { NavOptions } from '../nav-interface';
|
||||
@@ -196,6 +197,7 @@ describe('NavController', () => {
|
||||
.insert(-1, null as any, null, null, trnsDone)
|
||||
.then(() => {
|
||||
fail('it should not succeed');
|
||||
done();
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
const hasCompleted = false;
|
||||
@@ -250,6 +252,7 @@ describe('NavController', () => {
|
||||
.pop(null, trnsDone)
|
||||
.then(() => {
|
||||
fail('it should not succeed');
|
||||
done();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const hasCompleted = false;
|
||||
@@ -816,10 +819,15 @@ describe('NavController', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
trnsDone = jest.fn();
|
||||
const config = new Config();
|
||||
config.reset({ animated: false });
|
||||
const page = await newSpecPage({
|
||||
components: [Nav],
|
||||
html: `<ion-nav></ion-nav>`,
|
||||
autoApplyChanges: true,
|
||||
context: {
|
||||
config,
|
||||
},
|
||||
});
|
||||
nav = page.rootInstance;
|
||||
});
|
||||
@@ -840,7 +848,7 @@ describe('NavController', () => {
|
||||
pause: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
onfinish: undefined,
|
||||
} as any;
|
||||
};
|
||||
|
||||
animation.play = () => {
|
||||
if (animation.onfinish) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@import "./picker-column-internal.scss";
|
||||
@@ -0,0 +1,6 @@
|
||||
@import "./picker-column-internal.scss";
|
||||
@import "../../themes/ionic.globals.md";
|
||||
|
||||
:host .picker-item-active {
|
||||
color: current-color(base);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Picker Internal
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
@include padding(0px, 16px, 0px, 16px);
|
||||
|
||||
height: 200px;
|
||||
|
||||
outline: none;
|
||||
|
||||
font-size: 22px;
|
||||
scroll-snap-type: y mandatory;
|
||||
|
||||
/**
|
||||
* Need to explicitly set overflow-x: hidden
|
||||
* for older implementations of scroll snapping.
|
||||
*/
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
// Hide scrollbars on Firefox
|
||||
scrollbar-width: none;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide scrollbars on Chrome and Safari
|
||||
*/
|
||||
:host::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host .picker-item {
|
||||
@include padding(0);
|
||||
@include margin(0);
|
||||
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
|
||||
height: 34px;
|
||||
|
||||
border: 0px;
|
||||
|
||||
outline: none;
|
||||
|
||||
background: transparent;
|
||||
|
||||
color: inherit;
|
||||
|
||||
font-family: $font-family-base;
|
||||
|
||||
font-size: inherit;
|
||||
|
||||
line-height: 34px;
|
||||
|
||||
text-align: inherit;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
:host .picker-item-empty,
|
||||
:host .picker-item.picker-item-disabled {
|
||||
scroll-snap-align: none;
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:host .picker-item.picker-item-disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
:host(.picker-column-active) .picker-item.picker-item-active {
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
|
||||
background: current-color(base, 0.2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
import { getElementRoot, raf } from '@utils/helpers';
|
||||
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '@utils/native/haptic';
|
||||
import { isPlatform } from '@utils/platform';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { Color } from '../../interface';
|
||||
import type { PickerInternalCustomEvent } from '../picker-internal/picker-internal-interfaces';
|
||||
|
||||
import type { PickerColumnItem } from './picker-column-internal-interfaces';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
* @internal
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-picker-column-internal',
|
||||
styleUrls: {
|
||||
ios: 'picker-column-internal.ios.scss',
|
||||
md: 'picker-column-internal.md.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class PickerColumnInternal implements ComponentInterface {
|
||||
private destroyScrollListener?: () => void;
|
||||
private isScrolling = false;
|
||||
private scrollEndCallback?: () => void;
|
||||
private isColumnVisible = false;
|
||||
private parentEl?: HTMLIonPickerInternalElement | null;
|
||||
private canExitInputMode = true;
|
||||
|
||||
@State() isActive = false;
|
||||
|
||||
@Element() el!: HTMLIonPickerColumnInternalElement;
|
||||
|
||||
/**
|
||||
* A list of options to be displayed in the picker
|
||||
*/
|
||||
@Prop() items: PickerColumnItem[] = [];
|
||||
|
||||
/**
|
||||
* The selected option in the picker.
|
||||
*/
|
||||
@Prop({ mutable: true }) value?: string | number;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
* For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
@Prop({ reflect: true }) color?: Color = 'primary';
|
||||
|
||||
/**
|
||||
* If `true`, tapping the picker will
|
||||
* reveal a number input keyboard that lets
|
||||
* the user type in values for each picker
|
||||
* column. This is useful when working
|
||||
* with time pickers.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@Prop() numericInput = false;
|
||||
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
@Event() ionChange!: EventEmitter<PickerColumnItem>;
|
||||
|
||||
@Watch('value')
|
||||
valueChange() {
|
||||
if (this.isColumnVisible) {
|
||||
/**
|
||||
* Only scroll the active item into view when the picker column
|
||||
* is actively visible to the user.
|
||||
*/
|
||||
this.scrollActiveItemIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only setup scroll listeners
|
||||
* when the picker is visible, otherwise
|
||||
* the container will have a scroll
|
||||
* height of 0px.
|
||||
*/
|
||||
componentWillLoad() {
|
||||
const visibleCallback = (entries: IntersectionObserverEntry[]) => {
|
||||
const ev = entries[0];
|
||||
|
||||
if (ev.isIntersecting) {
|
||||
const { activeItem, el } = this;
|
||||
|
||||
this.isColumnVisible = true;
|
||||
/**
|
||||
* Because this initial call to scrollActiveItemIntoView has to fire before
|
||||
* the scroll listener is set up, we need to manage the active class manually.
|
||||
*/
|
||||
const oldActive = getElementRoot(el).querySelector(`.${PICKER_ITEM_ACTIVE_CLASS}`);
|
||||
if (oldActive) {
|
||||
this.setPickerItemActiveState(oldActive, false);
|
||||
}
|
||||
this.scrollActiveItemIntoView();
|
||||
if (activeItem) {
|
||||
this.setPickerItemActiveState(activeItem, true);
|
||||
}
|
||||
|
||||
this.initializeScrollListener();
|
||||
} else {
|
||||
this.isColumnVisible = false;
|
||||
|
||||
if (this.destroyScrollListener) {
|
||||
this.destroyScrollListener();
|
||||
this.destroyScrollListener = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
new IntersectionObserver(visibleCallback, { threshold: 0.001 }).observe(this.el);
|
||||
|
||||
const parentEl = (this.parentEl = this.el.closest('ion-picker-internal') as HTMLIonPickerInternalElement | null);
|
||||
if (parentEl !== null) {
|
||||
// TODO(FW-2832): type
|
||||
parentEl.addEventListener('ionInputModeChange', (ev: any) => this.inputModeChange(ev));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
const { activeItem, items, isColumnVisible, value } = this;
|
||||
|
||||
if (isColumnVisible) {
|
||||
if (activeItem) {
|
||||
this.scrollActiveItemIntoView();
|
||||
} else if (items[0]?.value !== value) {
|
||||
/**
|
||||
* If the picker column does not have an active item and the current value
|
||||
* does not match the first item in the picker column, that means
|
||||
* the value is out of bounds. In this case, we assign the value to the
|
||||
* first item to match the scroll position of the column.
|
||||
*
|
||||
*/
|
||||
this.setValue(items[0].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Method()
|
||||
async scrollActiveItemIntoView() {
|
||||
const activeEl = this.activeItem;
|
||||
|
||||
if (activeEl) {
|
||||
this.centerPickerItemInView(activeEl, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value prop and fires the ionChange event.
|
||||
* This is used when we need to fire ionChange from
|
||||
* user-generated events that cannot be caught with normal
|
||||
* input/change event listeners.
|
||||
* @internal
|
||||
*/
|
||||
@Method()
|
||||
async setValue(value?: string | number) {
|
||||
const { items } = this;
|
||||
this.value = value;
|
||||
const findItem = items.find((item) => item.value === value && item.disabled !== true);
|
||||
if (findItem) {
|
||||
this.ionChange.emit(findItem);
|
||||
}
|
||||
}
|
||||
|
||||
private centerPickerItemInView = (target: HTMLElement, smooth = true, canExitInputMode = true) => {
|
||||
const { el, isColumnVisible } = this;
|
||||
if (isColumnVisible) {
|
||||
// (Vertical offset from parent) - (three empty picker rows) + (half the height of the target to ensure the scroll triggers)
|
||||
const top = target.offsetTop - 3 * target.clientHeight + target.clientHeight / 2;
|
||||
|
||||
if (el.scrollTop !== top) {
|
||||
/**
|
||||
* Setting this flag prevents input
|
||||
* mode from exiting in the picker column's
|
||||
* scroll callback. This is useful when the user manually
|
||||
* taps an item or types on the keyboard as both
|
||||
* of these can cause a scroll to occur.
|
||||
*/
|
||||
this.canExitInputMode = canExitInputMode;
|
||||
el.scroll({
|
||||
top,
|
||||
left: 0,
|
||||
behavior: smooth ? 'smooth' : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private setPickerItemActiveState = (item: Element, isActive: boolean) => {
|
||||
if (isActive) {
|
||||
item.classList.add(PICKER_ITEM_ACTIVE_CLASS);
|
||||
item.part.add(PICKER_ITEM_ACTIVE_PART);
|
||||
} else {
|
||||
item.classList.remove(PICKER_ITEM_ACTIVE_CLASS);
|
||||
item.part.remove(PICKER_ITEM_ACTIVE_PART);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When ionInputModeChange is emitted, each column
|
||||
* needs to check if it is the one being made available
|
||||
* for text entry.
|
||||
*/
|
||||
private inputModeChange = (ev: PickerInternalCustomEvent) => {
|
||||
if (!this.numericInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { useInputMode, inputModeColumn } = ev.detail;
|
||||
|
||||
/**
|
||||
* If inputModeColumn is undefined then this means
|
||||
* all numericInput columns are being selected.
|
||||
*/
|
||||
const isColumnActive = inputModeColumn === undefined || inputModeColumn === this.el;
|
||||
|
||||
if (!useInputMode || !isColumnActive) {
|
||||
this.setInputModeActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setInputModeActive(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Setting isActive will cause a re-render.
|
||||
* As a result, we do not want to cause the
|
||||
* re-render mid scroll as this will cause
|
||||
* the picker column to jump back to
|
||||
* whatever value was selected at the
|
||||
* start of the scroll interaction.
|
||||
*/
|
||||
private setInputModeActive = (state: boolean) => {
|
||||
if (this.isScrolling) {
|
||||
this.scrollEndCallback = () => {
|
||||
this.isActive = state;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
this.isActive = state;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the column scrolls, the component
|
||||
* needs to determine which item is centered
|
||||
* in the view and will emit an ionChange with
|
||||
* the item object.
|
||||
*/
|
||||
private initializeScrollListener = () => {
|
||||
/**
|
||||
* The haptics for the wheel picker are
|
||||
* an iOS-only feature. As a result, they should
|
||||
* be disabled on Android.
|
||||
*/
|
||||
const enableHaptics = isPlatform('ios');
|
||||
const { el } = this;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
let activeEl: HTMLElement | null = this.activeItem;
|
||||
|
||||
const scrollCallback = () => {
|
||||
raf(() => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
|
||||
if (!this.isScrolling) {
|
||||
enableHaptics && hapticSelectionStart();
|
||||
this.isScrolling = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select item in the center of the column
|
||||
* which is the month/year that we want to select
|
||||
*/
|
||||
const bbox = el.getBoundingClientRect();
|
||||
const centerX = bbox.x + bbox.width / 2;
|
||||
const centerY = bbox.y + bbox.height / 2;
|
||||
|
||||
const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLButtonElement | null;
|
||||
|
||||
if (activeEl !== null) {
|
||||
this.setPickerItemActiveState(activeEl, false);
|
||||
}
|
||||
|
||||
if (activeElement === null || activeElement.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are selecting a new value,
|
||||
* we need to run haptics again.
|
||||
*/
|
||||
if (activeElement !== activeEl) {
|
||||
enableHaptics && hapticSelectionChanged();
|
||||
|
||||
if (this.canExitInputMode) {
|
||||
/**
|
||||
* The native iOS wheel picker
|
||||
* only dismisses the keyboard
|
||||
* once the selected item has changed
|
||||
* as a result of a swipe
|
||||
* from the user. If `canExitInputMode` is
|
||||
* `false` then this means that the
|
||||
* scroll is happening as a result of
|
||||
* the `value` property programmatically changing
|
||||
* either by an application or by the user via the keyboard.
|
||||
*/
|
||||
this.exitInputMode();
|
||||
}
|
||||
}
|
||||
|
||||
activeEl = activeElement;
|
||||
this.setPickerItemActiveState(activeElement, true);
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
this.isScrolling = false;
|
||||
enableHaptics && hapticSelectionEnd();
|
||||
|
||||
/**
|
||||
* Certain tasks (such as those that
|
||||
* cause re-renders) should only be done
|
||||
* once scrolling has finished, otherwise
|
||||
* flickering may occur.
|
||||
*/
|
||||
const { scrollEndCallback } = this;
|
||||
if (scrollEndCallback) {
|
||||
scrollEndCallback();
|
||||
this.scrollEndCallback = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this flag as the
|
||||
* next scroll interaction could
|
||||
* be a scroll from the user. In this
|
||||
* case, we should exit input mode.
|
||||
*/
|
||||
this.canExitInputMode = true;
|
||||
|
||||
const dataIndex = activeElement.getAttribute('data-index');
|
||||
|
||||
/**
|
||||
* If no value it is
|
||||
* possible we hit one of the
|
||||
* empty padding columns.
|
||||
*/
|
||||
if (dataIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = parseInt(dataIndex, 10);
|
||||
const selectedItem = this.items[index];
|
||||
|
||||
if (selectedItem.value !== this.value) {
|
||||
this.setValue(selectedItem.value);
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap this in an raf so that the scroll callback
|
||||
* does not fire when component is initially shown.
|
||||
*/
|
||||
raf(() => {
|
||||
el.addEventListener('scroll', scrollCallback);
|
||||
|
||||
this.destroyScrollListener = () => {
|
||||
el.removeEventListener('scroll', scrollCallback);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Tells the parent picker to
|
||||
* exit text entry mode. This is only called
|
||||
* when the selected item changes during scroll, so
|
||||
* we know that the user likely wants to scroll
|
||||
* instead of type.
|
||||
*/
|
||||
private exitInputMode = () => {
|
||||
const { parentEl } = this;
|
||||
|
||||
if (parentEl == null) return;
|
||||
|
||||
parentEl.exitInputMode();
|
||||
|
||||
/**
|
||||
* setInputModeActive only takes
|
||||
* effect once scrolling stops to avoid
|
||||
* a component re-render while scrolling.
|
||||
* However, we want the visual active
|
||||
* indicator to go away immediately, so
|
||||
* we call classList.remove here.
|
||||
*/
|
||||
this.el.classList.remove('picker-column-active');
|
||||
};
|
||||
|
||||
get activeItem() {
|
||||
return getElementRoot(this.el).querySelector(
|
||||
`.picker-item[data-value="${this.value}"]:not([disabled])`
|
||||
) as HTMLElement | null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, color, isActive, numericInput } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
/**
|
||||
* exportparts is needed so ion-datetime can expose the parts
|
||||
* from two layers of shadow nesting. If this causes problems,
|
||||
* the attribute can be moved to datetime.tsx and set on every
|
||||
* instance of ion-picker-column-internal there instead.
|
||||
*/
|
||||
return (
|
||||
<Host
|
||||
exportparts={`${PICKER_ITEM_PART}, ${PICKER_ITEM_ACTIVE_PART}`}
|
||||
tabindex={0}
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
['picker-column-active']: isActive,
|
||||
['picker-column-numeric-input']: numericInput,
|
||||
})}
|
||||
>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
{items.map((item, index) => {
|
||||
{
|
||||
/*
|
||||
Users should be able to tab
|
||||
between multiple columns. As a result,
|
||||
we set tabindex here so that tabbing switches
|
||||
between columns instead of buttons. Users
|
||||
can still use arrow keys on the keyboard to
|
||||
navigate the column up and down.
|
||||
*/
|
||||
}
|
||||
return (
|
||||
<button
|
||||
tabindex="-1"
|
||||
class={{
|
||||
'picker-item': true,
|
||||
'picker-item-disabled': item.disabled || false,
|
||||
}}
|
||||
data-value={item.value}
|
||||
data-index={index}
|
||||
onClick={(ev: Event) => {
|
||||
this.centerPickerItemInView(ev.target as HTMLElement, true);
|
||||
}}
|
||||
disabled={item.disabled}
|
||||
part={PICKER_ITEM_PART}
|
||||
>
|
||||
{item.text}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
<div class="picker-item picker-item-empty" aria-hidden="true">
|
||||
|
||||
</div>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const PICKER_ITEM_ACTIVE_CLASS = 'picker-item-active';
|
||||
const PICKER_ITEM_PART = 'wheel-item';
|
||||
const PICKER_ITEM_ACTIVE_PART = 'active';
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Picker Column - Basic</title>
|
||||
<title>Picker Column Internal - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
@@ -39,16 +39,16 @@
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Picker Column - Basic</ion-title>
|
||||
<ion-title>Picker Column Internal - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default</h2>
|
||||
<ion-picker>
|
||||
<ion-picker-column id="default"></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal id="default"></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -57,18 +57,12 @@
|
||||
|
||||
const items = Array(24)
|
||||
.fill()
|
||||
.forEach((_, i) => {
|
||||
const option = document.createElement('ion-picker-column-option');
|
||||
option.value = i;
|
||||
option.textContent = i;
|
||||
.map((_, i) => ({
|
||||
text: `${i}`,
|
||||
value: i,
|
||||
}));
|
||||
|
||||
defaultPickerColumn.appendChild(option);
|
||||
});
|
||||
//defaultPickerColumn.value = 11;
|
||||
|
||||
defaultPickerColumn.addEventListener('ionChange', (ev) => {
|
||||
console.log(ev);
|
||||
})
|
||||
defaultPickerColumn.items = items;
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
@@ -5,28 +5,33 @@ import { configs, test } from '@utils/test/playwright';
|
||||
* This behavior does not vary across modes/directions.
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('picker-column'), () => {
|
||||
test.describe(title('picker-column-internal'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/picker-column/test/basic', config);
|
||||
await page.goto('/src/components/picker-column-internal/test/basic', config);
|
||||
});
|
||||
|
||||
test('should render a picker item for each item', async ({ page }) => {
|
||||
const columns = page.locator('ion-picker-column-internal .picker-item:not(.picker-item-empty)');
|
||||
await expect(columns).toHaveCount(24);
|
||||
});
|
||||
|
||||
test('should render 6 empty picker items', async ({ page }) => {
|
||||
const columns = page.locator('ion-picker-column .picker-item-empty');
|
||||
const columns = page.locator('ion-picker-column-internal .picker-item-empty');
|
||||
await expect(columns).toHaveCount(6);
|
||||
});
|
||||
|
||||
test('should not have an active item when value is not set', async ({ page }) => {
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
const activeColumn = page.locator('ion-picker-column-internal .picker-item-active');
|
||||
await expect(activeColumn).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should have an active item when value is set', async ({ page }) => {
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnElement) => {
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => {
|
||||
el.value = '12';
|
||||
});
|
||||
await page.waitForChanges();
|
||||
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
const activeColumn = page.locator('ion-picker-column-internal .picker-item-active');
|
||||
|
||||
expect(activeColumn).not.toBeNull();
|
||||
});
|
||||
@@ -35,14 +40,14 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
test.skip('scrolling should change the active item', async ({ page, skip }) => {
|
||||
skip.browser('firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890');
|
||||
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnElement) => {
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => {
|
||||
el.scrollTop = 801;
|
||||
});
|
||||
await page.waitForChanges();
|
||||
|
||||
const activeColumn = page.locator('ion-picker-column .option-active');
|
||||
const activeColumn = page.locator('ion-picker-column-internal .picker-item-active');
|
||||
|
||||
await expect(activeColumn).toHaveJSProperty('value', 23);
|
||||
expect(await activeColumn?.innerText()).toEqual('23');
|
||||
});
|
||||
|
||||
test('should not emit ionChange when the value is modified externally', async ({ page, skip }) => {
|
||||
@@ -50,7 +55,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnElement) => {
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => {
|
||||
el.value = '12';
|
||||
});
|
||||
|
||||
@@ -63,7 +68,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnElement) => {
|
||||
await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => {
|
||||
el.scrollTo(0, el.scrollHeight);
|
||||
});
|
||||
await page.waitForChanges();
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Picker Column Option - Basic</title>
|
||||
<title>Picker Column Internal - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
@@ -39,29 +39,33 @@
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Picker Column Option - Basic</ion-title>
|
||||
<ion-title>Picker Column Internal - Disabled</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default</h2>
|
||||
<ion-picker-column-option>My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Disabled</h2>
|
||||
<ion-picker-column-option disabled="true">My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Active</h2>
|
||||
<ion-picker-column-option class="option-active">My Option</ion-picker-column-option>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Active / Disabled</h2>
|
||||
<ion-picker-column-option class="option-active" disabled="true">My Option</ion-picker-column-option>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal id="default"></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
<script>
|
||||
const defaultPickerColumn = document.getElementById('default');
|
||||
|
||||
const items = Array(24)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
text: `${i}`,
|
||||
value: i,
|
||||
disabled: i % 2 === 0,
|
||||
}));
|
||||
|
||||
defaultPickerColumn.items = items;
|
||||
defaultPickerColumn.value = 12;
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,20 +1,50 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('picker-column-internal: disabled rendering'), () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal value="b"></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a', disabled: true },
|
||||
{ text: 'B', value: 'b' },
|
||||
{ text: 'C', value: 'c', disabled: true }
|
||||
]
|
||||
</script>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const picker = page.locator('ion-picker-internal');
|
||||
await expect(picker).toHaveScreenshot(screenshot(`picker-internal-disabled`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions.
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('picker-column: disabled items'), () => {
|
||||
test.describe(title('picker-column-internal: disabled'), () => {
|
||||
test('all picker items should be enabled by default', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b' },
|
||||
@@ -25,19 +55,21 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const pickerItems = page.locator('ion-picker-column .picker-item:not(.picker-item-empty, [disabled])');
|
||||
const pickerItems = page.locator(
|
||||
'ion-picker-column-internal .picker-item:not(.picker-item-empty, .picker-item-disabled)'
|
||||
);
|
||||
|
||||
expect(await pickerItems.count()).toBe(3);
|
||||
});
|
||||
test('disabled picker item should not be interactive', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b', disabled: true },
|
||||
@@ -48,18 +80,18 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column .picker-item[disabled]');
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item.picker-item-disabled');
|
||||
await expect(disabledItem).not.toBeEnabled();
|
||||
});
|
||||
test('disabled picker item should not be considered active', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column value="b"></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal value="b"></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b', disabled: true },
|
||||
@@ -70,18 +102,18 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column .picker-item[data-value="b"]');
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('setting the value to a disabled item should not cause that item to be active', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b', disabled: true },
|
||||
@@ -92,24 +124,24 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const pickerColumn = page.locator('ion-picker-column');
|
||||
await pickerColumn.evaluate((el: HTMLIonPickerColumnElement) => (el.value = 'b'));
|
||||
const pickerColumn = page.locator('ion-picker-column-internal');
|
||||
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => (el.value = 'b'));
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column .picker-item[data-value="b"]');
|
||||
await expect(disabledItem).toBeDisabled();
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
await expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('defaulting the value to a disabled item should not cause that item to be active', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-picker>
|
||||
<ion-picker-column></ion-picker-column>
|
||||
</ion-picker>
|
||||
<ion-picker-internal>
|
||||
<ion-picker-column-internal></ion-picker-column-internal>
|
||||
</ion-picker-internal>
|
||||
|
||||
<script>
|
||||
const column = document.querySelector('ion-picker-column');
|
||||
const column = document.querySelector('ion-picker-column-internal');
|
||||
column.items = [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b', disabled: true },
|
||||
@@ -121,43 +153,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
config
|
||||
);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column .picker-item[data-value="b"]');
|
||||
await expect(disabledItem).toBeDisabled();
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
await expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('picker-column: disabled column rendering'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/picker-column/test/disabled', config);
|
||||
});
|
||||
|
||||
test('disabled column should not have visual regressions', async ({ page }) => {
|
||||
const disabledColumn = page.locator('#column-disabled');
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(disabledColumn).toHaveScreenshot(screenshot('picker-disabled-column'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions.
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('picker-column: disabled column'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/picker-column/test/disabled', config);
|
||||
});
|
||||
|
||||
test('item in disabled column should not be interactive', async ({ page }) => {
|
||||
const secondItem = page.locator('#column-disabled .picker-item:not(.picker-item-empty)').nth(1);
|
||||
await expect(secondItem).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1 +0,0 @@
|
||||
@import "./picker-column-option.scss";
|
||||
@@ -1,5 +0,0 @@
|
||||
@import "./picker-column-option.scss";
|
||||
|
||||
:host(.option-active) button {
|
||||
color: current-color(base);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Picker Column
|
||||
// --------------------------------------------------
|
||||
|
||||
button {
|
||||
@include padding(0);
|
||||
@include margin(0);
|
||||
|
||||
width: 100%;
|
||||
|
||||
height: 34px;
|
||||
|
||||
border: 0px;
|
||||
|
||||
outline: none;
|
||||
|
||||
background: transparent;
|
||||
|
||||
color: inherit;
|
||||
|
||||
font-family: $font-family-base;
|
||||
|
||||
font-size: inherit;
|
||||
|
||||
line-height: 34px;
|
||||
|
||||
text-align: inherit;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host(.option-disabled) {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
:host(.option-disabled) button {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||
import { inheritAttributes } from '@utils/helpers';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { Color } from '../../interface';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-picker-column-option',
|
||||
styleUrls: {
|
||||
ios: 'picker-column-option.ios.scss',
|
||||
md: 'picker-column-option.md.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class PickerColumnOption implements ComponentInterface {
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
/**
|
||||
* The aria-label of the option.
|
||||
*
|
||||
* If the value changes, then it will trigger a
|
||||
* re-render of the picker since it's a @State variable.
|
||||
* Otherwise, the `aria-label` attribute cannot be updated
|
||||
* after the component is loaded.
|
||||
*/
|
||||
@State() ariaLabel?: string | null = null;
|
||||
|
||||
/**
|
||||
* If `true`, the user cannot interact with the picker column option.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* The text value of the option.
|
||||
*/
|
||||
@Prop() value?: any | null;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
* For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
@Prop({ reflect: true }) color?: Color = 'primary';
|
||||
|
||||
/**
|
||||
* The aria-label of the option has changed after the
|
||||
* first render and needs to be updated within the component.
|
||||
*
|
||||
* @param ariaLbl The new aria-label value.
|
||||
*/
|
||||
@Watch('aria-label')
|
||||
onAriaLabelChange(ariaLbl: string) {
|
||||
this.ariaLabel = ariaLbl;
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
const inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
/**
|
||||
* The initial value of `aria-label` needs to be set for
|
||||
* the first render.
|
||||
*/
|
||||
this.ariaLabel = inheritedAttributes['aria-label'] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The column options can load at any time
|
||||
* so the selected option needs to tell the
|
||||
* parent picker column when it is loaded
|
||||
* so the picker column can ensure it is
|
||||
* centered in the view.
|
||||
*/
|
||||
componentDidLoad() {
|
||||
const parentPickerColumn = this.el.closest('ion-picker-column');
|
||||
if (parentPickerColumn !== null && this.value === parentPickerColumn.value) {
|
||||
parentPickerColumn.scrollActiveItemIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When an option is clicked update the
|
||||
* parent picker column value. This
|
||||
* component will handle centering the option
|
||||
* in the column view.
|
||||
*/
|
||||
onClick() {
|
||||
const parentPickerColumn = this.el.closest('ion-picker-column');
|
||||
if (parentPickerColumn !== null) {
|
||||
parentPickerColumn.setValue(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { color, value, disabled, ariaLabel } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
['option-disabled']: disabled,
|
||||
})}
|
||||
>
|
||||
<button tabindex="-1" aria-label={ariaLabel} disabled={disabled} onClick={() => this.onClick()}>
|
||||
<slot>{value}</slot>
|
||||
</button>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Picker Column Option - a11y</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<ion-picker-column-option> my option </ion-picker-column-option>
|
||||
<ion-picker-column-option aria-label="the best one"> other option </ion-picker-column-option>
|
||||
<ion-picker-column-option color="tertiary" class="option-active">option</ion-picker-column-option>
|
||||
<ion-picker-column-option disabled="true">option</ion-picker-column-option>
|
||||
<ion-picker-column-option color="tertiary" class="option-active" disabled="true">option</ion-picker-column-option>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||