Compare commits

..

79 Commits

Author SHA1 Message Date
Liam DeBeasi
e4f3c67403 refactor: remove unused css 2023-12-06 12:22:36 -05:00
Liam DeBeasi
5190ab3e9e refactor: use single stylesheet for picker-column 2023-12-06 12:21:26 -05:00
Liam DeBeasi
1e3effcda3 refactor: remove shadow parts from column 2023-12-06 12:20:31 -05:00
Liam DeBeasi
2552147983 clean up interfaces 2023-12-06 12:19:21 -05:00
Liam DeBeasi
f88b74dcda fix(picker): keyboard entry works with options 2023-12-06 11:58:53 -05:00
Liam DeBeasi
ca1b51938d fix bad merge on api.txt 2023-12-05 14:58:31 -05:00
Liam DeBeasi
fdfc5fc332 chore: resolve merge conflicts 2023-12-05 14:57:09 -05:00
Liam DeBeasi
b5dbc101ea Merge remote-tracking branch 'origin/FW-5575' into FW-5580 2023-12-05 14:45:19 -05:00
Liam DeBeasi
59bd18a7ff chore: sync 2023-12-05 14:44:05 -05:00
Liam DeBeasi
91d0e267b2 feat(picker-column): add styles, disabled and active states (#28621)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

We'd like to introduce a new API to the picker column to allow
developers to manipulate the picker options right in the DOM. Currently
developers need to pass an object which is quite cumbersome as you need
to re-pass that object anytime you want to change a single option.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

This PR does some of the prep work to make integrating the picker column
option into picker column easier. It does the following things:

1. Copies the base button styles from picker-column over to
picker-column-option
2. Creates active and disabled states (plus styles)
3. Add tests for base styles

Note: There are duplicate styles across picker-column and
picker-column-option that will be cleaned up once picker-column-option
is integrated and shown to be functional.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2023-12-05 14:40:53 -05:00
Amanda Johnston
cf9fe315a3 feat(picker-column): add prefix and suffix slots (#28603)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

The new picker component does not have a counterpart for the `prefix`
and `suffix` properties on the old picker column.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- `prefix` and `suffix` slots added.
- Picker items wrapped in a new `.picker-opts` element for positioning
purposes, similar to the legacy picker column. This necessitated
additional changes to functionality and styling, since previously the
items were direct descendants of the host.
- New `setFocus` method added. This gets around a browser bug in Firefox
where calling `focus()` on the column doesn't correctly move focus, as
the nearest focusable element (`.picker-opts`) is in the shadow DOM.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->

While you can no longer call `focus()` on the column in Firefox and must
use `setFocus()` instead, this isn't considered a breaking change
because this component isn't public API yet.

## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2023-12-05 13:14:53 -06:00
Amanda Johnston
fe3070bac2 chore(): sync with feature-8.0 (#28642)
Issue number: #

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [ ] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Liam DeBeasi <liamdebeasi@users.noreply.github.com>
Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sean Perkins <sean@ionic.io>
Co-authored-by: Éric Le Maître <eric.erwan.le.maitre@gmail.com>
Co-authored-by: Shawn Taylor <shawn@ionic.io>
2023-12-05 13:03:52 -06:00
Liam DeBeasi
d36d645865 lint 2023-12-05 13:34:26 -05:00
Liam DeBeasi
cf3d028d69 active part no longer includes base part 2023-12-05 13:34:18 -05:00
Liam DeBeasi
f9d01b9041 remove explicit types 2023-12-05 13:31:14 -05:00
Liam DeBeasi
0ef015b5be remove unneeded typecast 2023-12-05 13:29:06 -05:00
Liam DeBeasi
33a21d3675 remove typecast in favor of generic 2023-12-05 13:28:47 -05:00
Liam DeBeasi
a98e85d632 remove explicit type case 2023-12-05 13:27:19 -05:00
Liam DeBeasi
e4797f73f4 remove explicit type since it can be inferred 2023-12-05 13:26:53 -05:00
Liam DeBeasi
d48c41db5b use generic for query selector 2023-12-05 13:26:24 -05:00
Liam DeBeasi
e8d5599aad Update core/src/components/picker-column/picker-column.tsx
Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
2023-12-05 13:24:58 -05:00
Liam DeBeasi
c1d5401556 test(datetime): migrate one more test file 2023-12-04 14:38:21 -05:00
Liam DeBeasi
90e2d2cf2c remove outdated comment 2023-12-04 14:16:05 -05:00
Liam DeBeasi
a52413113f add todo 2023-12-04 14:11:04 -05:00
Liam DeBeasi
6d2f998d6e chore: lint 2023-12-04 14:08:34 -05:00
Liam DeBeasi
6ddc15f3ed test(datetime): migrate custom tests 2023-12-04 14:08:12 -05:00
Liam DeBeasi
514b3b6885 fix(picker-column-option): color can be overridden 2023-12-04 14:08:04 -05:00
Liam DeBeasi
3da0590a09 fix(datetime): expose shadow parts 2023-12-04 14:07:55 -05:00
Liam DeBeasi
4b564ee9b2 test: remove .only 2023-12-04 13:33:30 -05:00
Liam DeBeasi
3b67bd3e0c build and lint 2023-12-04 13:26:36 -05:00
Liam DeBeasi
cfcbcd92bd fix: picker column selects correct element on scroll 2023-12-04 13:25:09 -05:00
Liam DeBeasi
95628755aa fix: picker column option notifies picker column when ready 2023-12-04 13:24:40 -05:00
Liam DeBeasi
59113bb4cd test: migrate prefer-wheel tests 2023-12-04 13:24:32 -05:00
Liam DeBeasi
cab56466a2 fix: datetime use keys for stable identity 2023-12-04 11:19:24 -05:00
Liam DeBeasi
656ac5bb01 test: migrate minmax datetime test 2023-12-04 11:09:25 -05:00
Liam DeBeasi
dc93b8ab15 test: migrate locale datetime tests 2023-12-04 11:05:04 -05:00
Liam DeBeasi
892a269d18 test: migrate some datetime tests 2023-12-04 11:03:57 -05:00
Liam DeBeasi
506f42bec4 fix: picker-column emits correct payload 2023-12-04 10:52:01 -05:00
Liam DeBeasi
ed8cfa06fe chore: remove unused var 2023-12-04 10:41:16 -05:00
Liam DeBeasi
edb7bade3d typo 2023-12-04 10:41:09 -05:00
Liam DeBeasi
84c3c52347 fix: datetime passes disabled state correctly 2023-12-04 10:40:51 -05:00
Liam DeBeasi
8693fd6cf3 refactor: remove default slotted content 2023-12-04 10:40:43 -05:00
Liam DeBeasi
5a9a7d1adb refactor: integrate datetime with picker-column-option 2023-12-04 09:53:07 -05:00
Liam DeBeasi
313285d2b8 skip test 2023-12-01 17:15:50 -05:00
Liam DeBeasi
7473d64a4e lint 2023-12-01 16:44:26 -05:00
Liam DeBeasi
948c8d85a5 refactor: cache picker column el 2023-12-01 16:44:22 -05:00
Liam DeBeasi
58a89b329e fix disabled test 2023-12-01 16:39:59 -05:00
Liam DeBeasi
fc36cc50ca add TODOs 2023-12-01 16:35:16 -05:00
Liam DeBeasi
e933833763 test: picker tests pass 2023-12-01 16:19:11 -05:00
Liam DeBeasi
34ec94ffda test: picker-column tests pass 2023-12-01 16:07:56 -05:00
Liam DeBeasi
8d5a043a64 add api 2023-12-01 15:42:33 -05:00
Liam DeBeasi
3470c64e51 build and lint 2023-12-01 15:42:29 -05:00
Liam DeBeasi
2c773ed0e6 fix: do not emit ionChange if value did not change 2023-12-01 15:29:40 -05:00
Liam DeBeasi
b1fc67227c fix: column scrolls into view when option is ready 2023-12-01 15:16:59 -05:00
Liam DeBeasi
75ee951ce8 chore: lint 2023-12-01 14:58:06 -05:00
Liam DeBeasi
2f3f9dc9ca refactor: clicking option sets value 2023-12-01 14:58:01 -05:00
Liam DeBeasi
b68c93d55d refactor: scrolling column sets value 2023-12-01 14:49:23 -05:00
Liam DeBeasi
eace6425a2 refactor: add slot to integrate basic options 2023-12-01 14:28:07 -05:00
Liam DeBeasi
1aeb19403b chore: run build 2023-12-01 12:59:03 -05:00
ionitron
9d0834b201 chore(): add updated snapshots 2023-12-01 17:55:49 +00:00
Liam DeBeasi
7b21bd40a6 feat(picker-column): add styles, disabled and active states 2023-12-01 12:44:38 -05:00
Maria Hutt
0b469646b2 feat(picker-column-option): add the new component (#28591) 2023-11-30 09:54:01 -08:00
Shawn Taylor
5e47412e1f refactor(picker): rename internal picker components to ion-picker and ion-picker-column (#28589) 2023-11-28 16:17:28 -05:00
Liam DeBeasi
cc45e2220b refactor(picker): deprecate ion-picker and ion-picker-column (#28584)
BREAKING CHANGE: `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.
2023-11-28 12:47:37 -05:00
Liam DeBeasi
7ac0018a3c chore: sync with main
chore: sync with main
2023-11-27 10:38:48 -05:00
Liam DeBeasi
a7c966776a test: resolve type errors 2023-11-27 10:28:30 -05:00
Liam DeBeasi
7de4e34f13 Merge remote-tracking branch 'origin/main' into sync-80-main-11-27 2023-11-27 10:24:40 -05:00
Sean Perkins
4b5e62e60f refactor(datetime): render button for month/year toggle (#28443)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

`ion-datetime` uses an `ion-item` to render the month/year toggle button
inside of the header.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- `ion-datetime` uses a `button` element for the month/year toggle
button

## Does this introduce a breaking change?

- [x] Yes
- [ ] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->

Impact and migration path is noted in the `BREAKING.md`. 

## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

- `translucent` is not a valid CSS value for `background`. This was
always intended to be `transparent`.

---------

Co-authored-by: ionitron <hi@ionicframework.com>
Co-authored-by: Brandy Carney <brandyscarney@users.noreply.github.com>
Co-authored-by: Liam DeBeasi <liamdebeasi@users.noreply.github.com>

BREAKING CHANGE: 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`.
2023-11-27 10:19:19 -05:00
Liam DeBeasi
9883eac0f7 fix(angular): transition plays when using browser buttons (#28530)
Issue number: resolves #16569

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Ionic Angular's routing integration disables page transitions when using
the browser back/forward buttons.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Transitions now play when using the back/forward buttons

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

We're not aware of any breaking changes here, though it's possible some
developers were relying on this behavior. As a result, we are targeting
Ionic 8 to minimize any potential negative impact this fix may have on
developers.

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Supersedes https://github.com/ionic-team/ionic-framework/pull/28188

Dev build: `7.5.6-dev.11700068172.15ce9b35`

Co-authored-by: hoi4 <hoi4@users.noreply.github.com>
2023-11-16 10:52:32 -05:00
Liam DeBeasi
aa2a7f5271 chore: sync with main
chore: sync with main
2023-11-15 12:18:04 -05:00
Liam DeBeasi
2509d565b2 chore: sync 2023-11-15 12:06:22 -05:00
Liam DeBeasi
ce89057641 refactor(angular): radio component is auto generated (#28533) 2023-11-15 12:05:17 -05:00
Liam DeBeasi
5aafd68f03 chore: remove unused code (#28503)
BREAKING CHANGE: Content no longer sets the `--background` custom property when the `.outer-content` class is set on the host.
2023-11-13 11:30:36 -05:00
Maria Hutt
098ed054b1 chore(angular): remove radio value accessor (#28386)
Co-authored-by: Liam DeBeasi <liamdebeasi@users.noreply.github.com>
2023-11-06 10:50:03 -08:00
Sean Perkins
7ba939fb94 fix(overlays): prevent scroll gestures when the overlay is presented (#28415)
Issue number: Resolves #23942

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

When an overlay is created (inserted in the DOM), but not presented, the
scroll gesture is prevented. This behavior comes from the
`connectedCallback` of `ion-backdrop`, where the gesture is prevented as
soon as the backdrop is inserted in the DOM.

This means in situations where a developer creates an overlay, but does
not present it immediately, the user cannot scroll. This is not desired.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Scroll blocking behavior tied to the gesture has been removed from
`ion-backdrop` and implemented into the overlays directly.
- When an overlay is presented, scroll blocking is enabled on the `body`
element (the user cannot scroll on the main content).
- When the last presented overlay is dismissed, scroll blocking is
disabled on the `body` element (the user can scroll on the main
content).

## Does this introduce a breaking change?

- [x] Yes
- [ ] No

`ion-backdrop` no longer prevents scrolling on the main content when the
backdrop is either inserted into the DOM or removed from the DOM.
Developers using Ionic overlays do not need to migrate their
implementations.

Developers with custom overlays using `ion-backdrop` internally can
either use Ionic's gesture controller to disable scrolling when their
overlay is presented/dismissed or can manually add the
`backdrop-no-scroll` Ionic global class to the `body` element.

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------
2023-11-01 14:58:53 -04:00
Sean Perkins
6cd819a059 Merge pull request #28427 from ionic-team/sp/sync-feature-8-with-main
chore: sync with main
2023-10-29 22:55:59 -04:00
Sean Perkins
9bcee94e0b Merge remote-tracking branch 'origin/main' into sp/sync-feature-8-with-main 2023-10-27 13:05:48 -04:00
Liam DeBeasi
409df1bea5 docs(breaking): add v8 browser and platform support (#28368)
BREAKING CHANGE: The supported JS Framework and Browser/Platform versions have been revised for Ionic 8
2023-10-19 11:51:08 -04:00
Amanda Johnston
021712bd7d chore(): update BREAKING.md to prepare for Ionic 8 development (#28360)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- v7 breaking changes moved to legacy file.
- New v8 section added to main breaking changes list.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-10-17 09:07:36 -05:00
304 changed files with 5687 additions and 4310 deletions

4
.github/CODEOWNERS vendored
View File

@@ -53,8 +53,8 @@
/core/src/components/nav/ @sean-perkins
/core/src/components/nav-link/ @sean-perkins
/core/src/components/picker-internal/ @liamdebeasi
/core/src/components/picker-column-internal/ @liamdebeasi
/core/src/components/picker/ @liamdebeasi
/core/src/components/picker-column/ @liamdebeasi
/core/src/components/radio/ @amandaejohnston
/core/src/components/radio-group/ @amandaejohnston

15
.github/labeler.yml vendored
View File

@@ -6,16 +6,17 @@
# https://github.com/actions/labeler
'package: core':
- core/**/*
- changed-files:
- any-glob-to-any-file: ['core/**/*']
'package: angular':
- packages/angular/**/*
- packages/angular-*/**/*
- changed-files:
- any-glob-to-any-file: ['packages/angular/**/*', 'packages/angular-*/**/*']
'package: react':
- packages/react/**/*
- packages/react-*/**/*
- changed-files:
- any-glob-to-any-file: ['packages/react/**/*', 'packages/react-*/**/*']
'package: vue':
- packages/vue/**/*
- packages/vue-*/**/*
- changed-files:
- any-glob-to-any-file: ['packages/vue/**/*', 'packages/vue-*/**/*']

View File

@@ -13,7 +13,7 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@main
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true

View File

@@ -4,338 +4,65 @@ This is a comprehensive list of the breaking changes introduced in the major ver
## Versions
- [Version 7.x](#version-7x)
- [Version 8.x](#version-8x)
- [Version 7.x](./BREAKING_ARCHIVE/v7.md)
- [Version 6.x](./BREAKING_ARCHIVE/v6.md)
- [Version 5.x](./BREAKING_ARCHIVE/v5.md)
- [Version 4.x](./BREAKING_ARCHIVE/v4.md)
- [Legacy](https://github.com/ionic-team/ionic-v3/blob/master/CHANGELOG.md)
## Version 7.x
## Version 8.x
- [Browser and Platform Support](#version-7x-browser-platform-support)
- [Components](#version-7x-components)
- [Accordion Group](#version-7x-accordion-group)
- [Action Sheet](#version-7x-action-sheet)
- [Back Button](#version-7x-back-button)
- [Button](#version-7x-button)
- [Card Header](#version-7x-card-header)
- [Checkbox](#version-7x-checkbox)
- [Datetime](#version-7x-datetime)
- [Input](#version-7x-input)
- [Item](#version-7x-item)
- [Modal](#version-7x-modal)
- [Overlays](#version-7x-overlays)
- [Picker](#version-7x-picker)
- [Radio Group](#version-7x-radio-group)
- [Range](#version-7x-range)
- [Searchbar](#version-7x-searchbar)
- [Segment](#version-7x-segment)
- [Select](#version-7x-select)
- [Slides](#version-7x-slides)
- [Textarea](#version-7x-textarea)
- [Toggle](#version-7x-toggle)
- [Virtual Scroll](#version-7x-virtual-scroll)
- [Config](#version-7x-config)
- [Types](#version-7x-types)
- [Overlay Attribute Interfaces](#version-7x-overlay-attribute-interfaces)
- [JavaScript Frameworks](#version-7x-javascript-frameworks)
- [Angular](#version-7x-angular)
- [React](#version-7x-react)
- [Vue](#version-7x-vue)
- [CSS Utilities](#version-7x-css-utilities)
- [hidden attribute](#version-7x-hidden-attribute)
- [Browser and Platform Support](#version-8x-browser-platform-support)
- [Components](#version-8x-components)
- [Content](#version-8x-content)
- [Datetime](#version-8x-datetime)
- [Picker](#version-8x-picker)
<h2 id="version-7x-browser-platform-support">Browser and Platform Support</h2>
<h2 id="version-8x-browser-platform-support">Browser and Platform Support</h2>
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 7.
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 8.
**Minimum Browser Versions**
| Desktop Browser | Supported Versions |
| --------------- | ----------------- |
| Chrome | 79+ |
| Safari | 14+ |
| Firefox | 70+ |
| Edge | 79+ |
| Chrome | 89+ |
| Safari | 15+ |
| Firefox | 75+ |
| Edge | 89+ |
**Minimum JavaScript Framework Versions**
| Framework | Supported Version |
| --------- | --------------------- |
| Angular | 14+ |
| Angular | 16+ |
| React | 17+ |
| Vue | 3.0.6+ |
**Minimum Mobile Platform Versions**
| Platform | Supported Version |
| -------- | ---------------------- |
| iOS | 14+ |
| Android | 5.1+ with Chromium 79+ |
| iOS | 15+ |
| Android | 5.1+ with Chromium 89+ |
<h2 id="version-7x-components">Components</h2>
<h2 id="version-8x-components">Components</h2>
<h4 id="version-7x-accordion-group">Accordion Group</h4>
<h4 id="version-8x-content">Content</h4>
- `ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
- Content no longer sets the `--background` custom property when the `.outer-content` class is set on the host.
- Accordion Group no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
<h4 id="version-8x-datetime">Datetime</h4>
<h4 id="version-7x-action-sheet">Action Sheet</h4>
- The CSS shadow part for `month-year-button` has been changed to target a `button` element instead of `ion-item`. Developers should verify their UI renders as expected for the month/year toggle button inside of `ion-datetime`.
- Developers using the CSS variables available on `ion-item` will need to migrate their CSS to use CSS properties. For example:
```diff
ion-datetime::part(month-year-button) {
- --background: red;
- Action Sheet is updated to align with the design specification.
+ background: red;
}
```
<h2 id="version-8x-picker">Picker</h2>
**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;
}
```
- `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.

331
BREAKING_ARCHIVE/v7.md Normal file
View File

@@ -0,0 +1,331 @@
# Breaking Changes
## Version 7.x
- [Browser and Platform Support](#version-7x-browser-platform-support)
- [Components](#version-7x-components)
- [Accordion Group](#version-7x-accordion-group)
- [Action Sheet](#version-7x-action-sheet)
- [Back Button](#version-7x-back-button)
- [Button](#version-7x-button)
- [Card Header](#version-7x-card-header)
- [Checkbox](#version-7x-checkbox)
- [Datetime](#version-7x-datetime)
- [Input](#version-7x-input)
- [Item](#version-7x-item)
- [Modal](#version-7x-modal)
- [Overlays](#version-7x-overlays)
- [Picker](#version-7x-picker)
- [Radio Group](#version-7x-radio-group)
- [Range](#version-7x-range)
- [Searchbar](#version-7x-searchbar)
- [Segment](#version-7x-segment)
- [Select](#version-7x-select)
- [Slides](#version-7x-slides)
- [Textarea](#version-7x-textarea)
- [Toggle](#version-7x-toggle)
- [Virtual Scroll](#version-7x-virtual-scroll)
- [Config](#version-7x-config)
- [Types](#version-7x-types)
- [Overlay Attribute Interfaces](#version-7x-overlay-attribute-interfaces)
- [JavaScript Frameworks](#version-7x-javascript-frameworks)
- [Angular](#version-7x-angular)
- [React](#version-7x-react)
- [Vue](#version-7x-vue)
- [CSS Utilities](#version-7x-css-utilities)
- [hidden attribute](#version-7x-hidden-attribute)
<h2 id="version-7x-browser-platform-support">Browser and Platform Support</h2>
This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 7.
**Minimum Browser Versions**
| Desktop Browser | Supported Versions |
| --------------- | ----------------- |
| Chrome | 79+ |
| Safari | 14+ |
| Firefox | 70+ |
| Edge | 79+ |
**Minimum JavaScript Framework Versions**
| Framework | Supported Version |
| --------- | --------------------- |
| Angular | 14+ |
| React | 17+ |
| Vue | 3.0.6+ |
**Minimum Mobile Platform Versions**
| Platform | Supported Version |
| -------- | ---------------------- |
| iOS | 14+ |
| Android | 5.1+ with Chromium 79+ |
<h2 id="version-7x-components">Components</h2>
<h4 id="version-7x-accordion-group">Accordion Group</h4>
- `ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
- Accordion Group no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
<h4 id="version-7x-action-sheet">Action Sheet</h4>
- Action Sheet is updated to align with the design specification.
**Design tokens**
| Token | Previous Value | New Value |
| ---------- | -------------- | --------- |
| `--height` | `100%` | `auto` |
<h4 id="version-7x-button">Button</h4>
- Button is updated to align with the design specification for iOS.
**Design tokens**
| Token | Previous Value | New Value |
| ---------------------------------- | -------------- | --------- |
| `$button-ios-letter-spacing` | `-0.03em` | `0` |
| `$button-ios-clear-letter-spacing` | `0` | Removed |
| `$button-ios-height` | `2.8em` | `3.1em` |
| `$button-ios-border-radius` | `10px` | `14px` |
| `$button-ios-large-height` | `2.8em` | `3.1em` |
| `$button-ios-large-border-radius` | `12px` | `16px` |
<h4 id="version-7x-back-button">Back Button</h4>
- Back Button is updated to align with the design specification for iOS.
**Design tokens**
| Token | Previous Value | New Value |
| ------------------- | -------------- | --------- |
| `--icon-margin-end` | `-5px` | `1px` |
| `--icon-font-size` | `1.85em` | `1.6em` |
<h4 id="version-7x-card-header">Card Header</h4>
- The card header has ben changed to a flex container with direction set to `column` (top to bottom). In `ios` mode the direction is set to `column-reverse` which results in the subtitle displaying on top of the title.
<h4 id="version-7x-checkbox">Checkbox</h4>
- `ionChange` is no longer emitted when the `checked` property of `ion-checkbox` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the checkbox.
- The `--background` and `--background-checked` CSS variables have been renamed to `--checkbox-background` and `--checkbox-background-checked` respectively.
<h4 id="version-7x-datetime">Datetime</h4>
- `ionChange` is no longer emitted when the `value` property of `ion-datetime` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping a date.
- Datetime no longer automatically adjusts the `value` property when passed an array and `multiple="false"`. Developers should update their apps to ensure they are using the API correctly.
- Datetime no longer incorrectly reports the time zone when `value` is updated. Datetime does not manage time zones, so any time zone information provided is ignored.
- Passing the empty string to the `value` property will now error as it is not a valid ISO-8601 value.
- The haptics when swiping the wheel picker are now enabled only on iOS.
<h4 id="version-7x-input">Input</h4>
- `ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus, clicking the clear action within the input, or pressing the "Enter" key.
- If your application requires immediate feedback based on the user typing actively in the input, consider migrating your event listeners to using `ionInput` instead.
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
**Design tokens**
| Token | Previous Value | New Value |
| ----------------------- | -------------- | --------- |
| `--placeholder-opacity` | `0.5` | `0.6` |
<h4 id="version-7x-item">Item</h4>
**Design tokens**
iOS:
| Token | Previous Value | New Value |
| --------------------- | -------------- | --------- |
| `$item-ios-font-size` | `17px` | `16px` |
| `--inner-padding-end` | `10px` | `16px` |
| `--padding-start` | `20px` | `16px` |
<h4 id="version-7x-modal">Modal</h4>
- The `swipeToClose` property has been removed in favor of `canDismiss`.
- The `canDismiss` property now defaults to `true` and can no longer be set to `undefined`.
<h4 id="version-7x-overlays">Overlays</h4>
Ionic now listens on the `keydown` event instead of the `keyup` event when determining when to dismiss overlays via the "Escape" key. Any applications that were listening on `keyup` to suppress this behavior should listen on `keydown` instead.
<h4 id="version-7x-picker">Picker</h4>
- The `refresh` key has been removed from the `PickerColumn` interface. Developers should use the `columns` property to refresh the `ion-picker` view.
<h4 id="version-7x-radio-group">Radio Group</h4>
- `ionChange` is no longer emitted when the `value` of `ion-radio-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping an `ion-radio` in the group.
<h4 id="version-7x-range">Range</h4>
- Range is updated to align with the design specification for supported modes.
**Design tokens**
iOS:
| Token | Previous Value | New Value |
| --------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `--bar-border-radius` | `0px` | `$range-ios-bar-border-radius` (`2px` default) |
| `--knob-size` | `28px` | `$range-ios-knob-width` (`26px` default) |
| `$range-ios-bar-height` | `2px` | `4px` |
| `$range-ios-bar-background-color` | `rgba(var(--ion-text-color-rgb, 0, 0, 0), .1)` | `var(--ion-color-step-900, #e6e6e6)` |
| `$range-ios-knob-box-shadow` | `0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02)` | `0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12)` |
| `$range-ios-knob-width` | `28px` | `26px` |
- `ionChange` is no longer emitted when the `value` of `ion-range` is modified externally. `ionChange` is only emitted from user committed changes, such as dragging and releasing the range knob or selecting a new value with the keyboard arrows.
- If your application requires immediate feedback based on the user actively dragging the range knob, consider migrating your event listeners to using `ionInput` instead.
- The `debounce` property's value value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
- Range no longer clamps assigned values within bounds. Developers will need to validate the value they are assigning to `ion-range` is within the `min` and `max` bounds when programmatically assigning a value.
- The `name` property defaults to `ion-r-${rangeIds++}` where `rangeIds` is a number that is incremented for every instance of `ion-range`.
<h4 id="version-7x-searchbar">Searchbar</h4>
- `ionChange` is no longer emitted when the `value` of `ion-searchbar` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the searchbar and the searchbar losing focus or pressing the "Enter" key.
- If your application requires immediate feedback based on the user typing actively in the searchbar, consider migrating your event listeners to using `ionInput` instead.
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
- The `debounce` property's default value has changed from 250 to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
**Design tokens**
| Token | Previous Value | New Value |
| ----------------------- | -------------- | --------- |
| `--placeholder-opacity` | `0.5` | `0.6` |
<h4 id="version-7x-segment">Segment</h4>
- `ionChange` is no longer emitted when the `value` of `ion-segment` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking a segment button or dragging to activate a segment button.
- The type signature of `value` supports `string | undefined`. Previously the type signature was `string | null | undefined`.
- Developers needing to clear the checked segment item should assign a value of `''` instead of `null`.
<h4 id="version-7x-select">Select</h4>
- `ionChange` is no longer emitted when the `value` of `ion-select` is modified externally. `ionChange` is only emitted from user committed changes, such as confirming a selected option in the select's overlay.
- The `icon` CSS Shadow Part now targets an `ion-icon` component.
**Design tokens**
| Token | Previous Value | New Value |
| ----------------------- | -------------- | --------- |
| `--placeholder-opacity` | `0.33` | `0.6` |
<h4 id="version-7x-slides">Slides</h4>
`ion-slides`, `ion-slide`, and the `IonicSwiper` plugin have been removed from Ionic.
Developers using these components will need to migrate to using Swiper.js directly, optionally using the `IonicSlides` plugin. Guides for migration and usage are linked below:
- [Angular](https://ionicframework.com/docs/angular/slides)
- [React](https://ionicframework.com/docs/react/slides)
- [Vue](https://ionicframework.com/docs/vue/slides)
<h4 id="version-7x-textarea">Textarea</h4>
- `ionChange` is no longer emitted when the `value` of `ion-textarea` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the textarea and the textarea losing focus.
- If your application requires immediate feedback based on the user typing actively in the textarea, consider migrating your event listeners to using `ionInput` instead.
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
- The `debounce` property's default value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.
- `ionInput` dispatches an event detail of `null` when the textarea is cleared as a result of `clear-on-edit="true"`.
- The `detail` payload for the `ionInput` event now contains an object with the current `value` as well as the native event that triggered `ionInput`.
**Design tokens**
| Token | Previous Value | New Value |
| ----------------------- | -------------- | --------- |
| `--placeholder-opacity` | `0.5` | `0.6` |
<h4 id="version-7x-toggle">Toggle</h4>
- `ionChange` is no longer emitted when the `checked` property of `ion-toggle` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking the toggle to set it on or off.
- The `--background` and `--background-checked` variables have been renamed to `--track-background` and `--track-background-checked`, respectively.
<h4 id="version-7x-virtual-scroll">Virtual Scroll</h4>
`ion-virtual-scroll` has been removed from Ionic.
Developers using the component will need to migrate to a virtual scroll solution provided by their framework:
- [Angular](https://ionicframework.com/docs/angular/virtual-scroll)
- [React](https://ionicframework.com/docs/react/virtual-scroll)
- [Vue](https://ionicframework.com/docs/vue/virtual-scroll)
Any references to the virtual scroll types from `@ionic/core` have been removed. Please remove or replace these types: `Cell`, `VirtualNode`, `CellType`, `NodeChange`, `HeaderFn`, `ItemHeightFn`, `FooterHeightFn`, `ItemRenderFn` and `DomRenderFn`.
<h2 id="version-7x-config">Config</h2>
- `innerHTMLTemplatesEnabled` defaults to `false`. Developers who wish to use the `innerHTML` functionality inside of `ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, and `ion-toast` must set this config to `true` and properly sanitize their content.
<h2 id="version-7x-types">Types</h2>
<h4 id="version-7x-overlay-attribute-interfaces">Overlay Attribute Interfaces</h4>
`ActionSheetAttributes`, `AlertAttributes`, `AlertTextareaAttributes`, `AlertInputAttributes`, `LoadingAttributes`, `ModalAttributes`, `PickerAttributes`, `PopoverAttributes`, and `ToastAttributes` have been removed. Developers should use `{ [key: string]: any }` instead.
<h2 id="version-7x-javascript-frameworks">JavaScript Frameworks</h2>
<h4 id="version-7x-angular">Angular</h4>
- Angular v14 is now required to use `@ionic/angular` and `@ionic/angular-server`. Upgrade your project to Angular v14 by following the [Angular v14 update guide](https://update.angular.io/?l=3&v=13.0-14.0).
- `null` values on form components will no longer be converted to the empty string (`''`) or `false`. This impacts `ion-checkbox`, `ion-datetime`, `ion-input`, `ion-radio`, `ion-radio-group`, `ion-range`, `ion-searchbar`, `ion-segment`, `ion-select`, `ion-textarea`, and `ion-toggle`.
- The dev-preview `environmentInjector` property has been removed from `ion-tabs` and `ion-router-outlet`. Standalone component routing is now available without additional custom configuration. Remove the `environmentInjector` property from your `ion-tabs` and `ion-router-outlet` components.
<h4 id="version-7x-react">React</h4>
`@ionic/react` and `@ionic/react-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
<h4 id="version-7x-vue">Vue</h4>
`@ionic/vue` and `@ionic/vue-router` no longer ship a CommonJS entry point. Instead, only an ES Module entry point is provided for improved compatibility with Vite.
<h2 id="version-7x-css-utilities">CSS Utilities</h2>
<h4 id="version-7x-hidden-attribute">`hidden` attribute</h4>
The `[hidden]` attribute has been removed from Ionic's global stylesheet. The `[hidden]` attribute can continue to be used, but developers will get the [native `hidden` implementation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) instead. The main difference is that the native implementation is easier to override using `display` than Ionic's implementation.
Developers can add the following CSS to their global stylesheet if they need the old behavior:
```css
[hidden] {
display: none !important;
}
```

View File

@@ -906,47 +906,65 @@ 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,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,shadow
ion-picker,prop,mode,"ios" | "md",undefined,false,false
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-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,method,setFocus,setFocus() => Promise<void>
ion-picker-column,event,ionChange,PickerColumnChangeEventDetail,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-popover,shadow
ion-popover,prop,alignment,"center" | "end" | "start" | undefined,undefined,false,false

14
core/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "7.5.7",
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.7.2",
"@stencil/core": "^4.8.1",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},
@@ -1825,9 +1825,9 @@
}
},
"node_modules/@stencil/core": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.2.tgz",
"integrity": "sha512-sPPDYrXiTbfeUF5CCyfqysXK/yfTHC4xYR1+nHzGkS2vhRSBOLp0oPuB+xkJLKA+K2ZqDJUxpOnDxy1CLWwBXA==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.1.tgz",
"integrity": "sha512-KG1H10j24rlyxIqOI4CG8/h9T7ObTv7giW2H3u1qXV4KKrLykDOpMcLzpqNXqL2Fki3s1QvHyl/oaRvi5waWVw==",
"bin": {
"stencil": "bin/stencil"
},
@@ -12184,9 +12184,9 @@
"requires": {}
},
"@stencil/core": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.2.tgz",
"integrity": "sha512-sPPDYrXiTbfeUF5CCyfqysXK/yfTHC4xYR1+nHzGkS2vhRSBOLp0oPuB+xkJLKA+K2ZqDJUxpOnDxy1CLWwBXA=="
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.1.tgz",
"integrity": "sha512-KG1H10j24rlyxIqOI4CG8/h9T7ObTv7giW2H3u1qXV4KKrLykDOpMcLzpqNXqL2Fki3s1QvHyl/oaRvi5waWVw=="
},
"@stencil/react-output-target": {
"version": "0.5.3",

View File

@@ -31,7 +31,7 @@
"loader/"
],
"dependencies": {
"@stencil/core": "^4.7.2",
"@stencil/core": "^4.8.1",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},

View File

@@ -0,0 +1,151 @@
/*
* Dark Colors
* -------------------------------------------
*/
:root {
--ion-color-primary: #428cff;
--ion-color-primary-rgb: 66, 140, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3a7be0;
--ion-color-primary-tint: #5598ff;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #46b0e0;
--ion-color-secondary-tint: #62ceff;
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffd534;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd948;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244, 245, 248;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0, 0, 0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152, 154, 162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0, 0, 0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34, 36, 40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255, 255, 255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
}
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body {
--ion-background-color: #000000;
--ion-background-color-rgb: 0, 0, 0;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
--ion-toolbar-background: #0d0d0d;
--ion-item-background: #000000;
--ion-card-background: #1c1c1d;
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body {
--ion-background-color: #121212;
--ion-background-color-rgb: 18, 18, 18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-border-color: #222222;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
--ion-color-step-150: #363636;
--ion-color-step-200: #414141;
--ion-color-step-250: #4d4d4d;
--ion-color-step-300: #595959;
--ion-color-step-350: #656565;
--ion-color-step-400: #717171;
--ion-color-step-450: #7d7d7d;
--ion-color-step-500: #898989;
--ion-color-step-550: #949494;
--ion-color-step-600: #a0a0a0;
--ion-color-step-650: #acacac;
--ion-color-step-700: #b8b8b8;
--ion-color-step-750: #c4c4c4;
--ion-color-step-800: #d0d0d0;
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1f1f1f;
--ion-tab-bar-background: #1f1f1f;
--ion-card-background: #1e1e1e;
}

View File

@@ -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 { 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 { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
import { PickerColumnChangeEventDetail, PickerColumnItem, PickerColumnValue } from "./components/picker-column/picker-column-interfaces";
import { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
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 { 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 { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
export { PickerColumnChangeEventDetail, PickerColumnItem, PickerColumnValue } from "./components/picker-column/picker-column-interfaces";
export { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
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";
@@ -1162,7 +1162,7 @@ export namespace Components {
*/
"autocorrect": 'on' | 'off';
/**
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus": boolean;
/**
@@ -1274,7 +1274,7 @@ export namespace Components {
*/
"required": boolean;
/**
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise<void>;
/**
@@ -1949,6 +1949,62 @@ 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 focus on the scrollable container within the picker column. Use this method instead of the global `pickerColumn.focus()`.
*/
"setFocus": () => 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: PickerColumnValue) => 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.
*/
@@ -2032,50 +2088,12 @@ export namespace Components {
*/
"trigger": string | undefined;
}
interface IonPickerColumn {
interface IonPickerLegacyColumn {
/**
* 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;
/**
* 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 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.
@@ -2601,7 +2619,7 @@ export namespace Components {
*/
"searchIcon"?: string;
/**
* Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
* Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise<void>;
/**
@@ -2950,7 +2968,7 @@ export namespace Components {
*/
"autocapitalize": string;
/**
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus": boolean;
/**
@@ -3050,7 +3068,7 @@ export namespace Components {
*/
"rows"?: number;
/**
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`.
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise<void>;
/**
@@ -3330,13 +3348,13 @@ export interface IonPickerColumnCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonPickerColumnElement;
}
export interface IonPickerColumnInternalCustomEvent<T> extends CustomEvent<T> {
export interface IonPickerLegacyCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonPickerColumnInternalElement;
target: HTMLIonPickerLegacyElement;
}
export interface IonPickerInternalCustomEvent<T> extends CustomEvent<T> {
export interface IonPickerLegacyColumnCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonPickerInternalElement;
target: HTMLIonPickerLegacyColumnElement;
}
export interface IonPopoverCustomEvent<T> extends CustomEvent<T> {
detail: T;
@@ -4020,14 +4038,7 @@ declare global {
new (): HTMLIonNoteElement;
};
interface HTMLIonPickerElementEventMap {
"ionPickerDidPresent": void;
"ionPickerWillPresent": void;
"ionPickerWillDismiss": OverlayEventDetail;
"ionPickerDidDismiss": OverlayEventDetail;
"didPresent": void;
"willPresent": void;
"willDismiss": OverlayEventDetail;
"didDismiss": OverlayEventDetail;
"ionInputModeChange": PickerChangeEventDetail;
}
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;
@@ -4044,7 +4055,7 @@ declare global {
new (): HTMLIonPickerElement;
};
interface HTMLIonPickerColumnElementEventMap {
"ionPickerColChange": PickerColumn;
"ionChange": PickerColumnChangeEventDetail;
}
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;
@@ -4060,39 +4071,52 @@ declare global {
prototype: HTMLIonPickerColumnElement;
new (): HTMLIonPickerColumnElement;
};
interface HTMLIonPickerColumnInternalElementEventMap {
"ionChange": PickerColumnItem;
interface HTMLIonPickerColumnOptionElement extends Components.IonPickerColumnOption, HTMLStencilElement {
}
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 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 HTMLIonPickerColumnInternalElement: {
prototype: HTMLIonPickerColumnInternalElement;
new (): HTMLIonPickerColumnInternalElement;
var HTMLIonPickerColumnOptionElement: {
prototype: HTMLIonPickerColumnOptionElement;
new (): HTMLIonPickerColumnOptionElement;
};
interface HTMLIonPickerInternalElementEventMap {
"ionInputModeChange": PickerInternalChangeEventDetail;
interface HTMLIonPickerLegacyElementEventMap {
"ionPickerDidPresent": void;
"ionPickerWillPresent": void;
"ionPickerWillDismiss": OverlayEventDetail;
"ionPickerDidDismiss": OverlayEventDetail;
"didPresent": void;
"willPresent": void;
"willDismiss": OverlayEventDetail;
"didDismiss": OverlayEventDetail;
}
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;
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;
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 HTMLIonPickerInternalElementEventMap>(type: K, listener: (this: HTMLIonPickerInternalElement, ev: IonPickerInternalCustomEvent<HTMLIonPickerInternalElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLIonPickerLegacyElementEventMap>(type: K, listener: (this: HTMLIonPickerLegacyElement, ev: IonPickerLegacyCustomEvent<HTMLIonPickerLegacyElementEventMap[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 HTMLIonPickerInternalElement: {
prototype: HTMLIonPickerInternalElement;
new (): HTMLIonPickerInternalElement;
var HTMLIonPickerLegacyElement: {
prototype: HTMLIonPickerLegacyElement;
new (): HTMLIonPickerLegacyElement;
};
interface HTMLIonPickerLegacyColumnElementEventMap {
"ionPickerColChange": PickerColumn;
}
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;
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 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;
};
interface HTMLIonPopoverElementEventMap {
"ionPopoverDidPresent": void;
@@ -4647,8 +4671,9 @@ declare global {
"ion-note": HTMLIonNoteElement;
"ion-picker": HTMLIonPickerElement;
"ion-picker-column": HTMLIonPickerColumnElement;
"ion-picker-column-internal": HTMLIonPickerColumnInternalElement;
"ion-picker-internal": HTMLIonPickerInternalElement;
"ion-picker-column-option": HTMLIonPickerColumnOptionElement;
"ion-picker-legacy": HTMLIonPickerLegacyElement;
"ion-picker-legacy-column": HTMLIonPickerLegacyColumnElement;
"ion-popover": HTMLIonPopoverElement;
"ion-progress-bar": HTMLIonProgressBarElement;
"ion-radio": HTMLIonRadioElement;
@@ -5854,7 +5879,7 @@ declare namespace LocalJSX {
*/
"autocorrect"?: 'on' | 'off';
/**
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus"?: boolean;
/**
@@ -6580,6 +6605,57 @@ 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<PickerColumnChangeEventDetail>) => 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.
*/
@@ -6633,35 +6709,35 @@ declare namespace LocalJSX {
/**
* Emitted after the picker has dismissed. Shorthand for ionPickerDidDismiss.
*/
"onDidDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
"onDidDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
/**
* Emitted after the picker has presented. Shorthand for ionPickerWillDismiss.
*/
"onDidPresent"?: (event: IonPickerCustomEvent<void>) => void;
"onDidPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
/**
* Emitted after the picker has dismissed.
*/
"onIonPickerDidDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
"onIonPickerDidDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
/**
* Emitted after the picker has presented.
*/
"onIonPickerDidPresent"?: (event: IonPickerCustomEvent<void>) => void;
"onIonPickerDidPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
/**
* Emitted before the picker has dismissed.
*/
"onIonPickerWillDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
"onIonPickerWillDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
/**
* Emitted before the picker has presented.
*/
"onIonPickerWillPresent"?: (event: IonPickerCustomEvent<void>) => void;
"onIonPickerWillPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
/**
* Emitted before the picker has dismissed. Shorthand for ionPickerWillDismiss.
*/
"onWillDismiss"?: (event: IonPickerCustomEvent<OverlayEventDetail>) => void;
"onWillDismiss"?: (event: IonPickerLegacyCustomEvent<OverlayEventDetail>) => void;
/**
* Emitted before the picker has presented. Shorthand for ionPickerWillPresent.
*/
"onWillPresent"?: (event: IonPickerCustomEvent<void>) => void;
"onWillPresent"?: (event: IonPickerLegacyCustomEvent<void>) => void;
"overlayIndex": number;
/**
* If `true`, a backdrop will be displayed behind the picker.
@@ -6672,7 +6748,7 @@ declare namespace LocalJSX {
*/
"trigger"?: string | undefined;
}
interface IonPickerColumn {
interface IonPickerLegacyColumn {
/**
* Picker column data
*/
@@ -6680,44 +6756,7 @@ declare namespace LocalJSX {
/**
* Emitted when the selected value has changed
*/
"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;
/**
* 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: 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;
"onIonPickerColChange"?: (event: IonPickerLegacyColumnCustomEvent<PickerColumn>) => void;
}
interface IonPopover {
/**
@@ -7699,7 +7738,7 @@ declare namespace LocalJSX {
*/
"autocapitalize"?: string;
/**
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus"?: boolean;
/**
@@ -8086,8 +8125,9 @@ declare namespace LocalJSX {
"ion-note": IonNote;
"ion-picker": IonPicker;
"ion-picker-column": IonPickerColumn;
"ion-picker-column-internal": IonPickerColumnInternal;
"ion-picker-internal": IonPickerInternal;
"ion-picker-column-option": IonPickerColumnOption;
"ion-picker-legacy": IonPickerLegacy;
"ion-picker-legacy-column": IonPickerLegacyColumn;
"ion-popover": IonPopover;
"ion-progress-bar": IonProgressBar;
"ion-radio": IonRadio;
@@ -8183,8 +8223,9 @@ 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-internal": LocalJSX.IonPickerColumnInternal & JSXBase.HTMLAttributes<HTMLIonPickerColumnInternalElement>;
"ion-picker-internal": LocalJSX.IonPickerInternal & JSXBase.HTMLAttributes<HTMLIonPickerInternalElement>;
"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-popover": LocalJSX.IonPopover & JSXBase.HTMLAttributes<HTMLIonPopoverElement>;
"ion-progress-bar": LocalJSX.IonProgressBar & JSXBase.HTMLAttributes<HTMLIonProgressBarElement>;
"ion-radio": LocalJSX.IonRadio & JSXBase.HTMLAttributes<HTMLIonRadioElement>;

View File

@@ -337,6 +337,17 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
if (this.isOpen === true) {
raf(() => this.present());
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
render() {

View File

@@ -376,6 +376,17 @@ export class Alert implements ComponentInterface, OverlayInterface {
if (this.isOpen === true) {
raf(() => this.present());
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
/**

View File

@@ -36,7 +36,7 @@
}
async function showPicker() {
const picker = Object.assign(document.createElement('ion-picker'), {
const picker = Object.assign(document.createElement('ion-picker-legacy'), {
columns: [
{
name: 'Picker',

View File

@@ -1,6 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Event, Host, Listen, Prop, h } from '@stencil/core';
import { GESTURE_CONTROLLER } from '@utils/gesture';
import { getIonMode } from '../../global/ionic-global';
@@ -13,10 +12,6 @@ import { getIonMode } from '../../global/ionic-global';
shadow: true,
})
export class Backdrop implements ComponentInterface {
private blocker = GESTURE_CONTROLLER.createBlocker({
disableScroll: true,
});
/**
* If `true`, the backdrop will be visible.
*/
@@ -37,16 +32,6 @@ export class Backdrop implements ComponentInterface {
*/
@Event() ionBackdropTap!: EventEmitter<void>;
connectedCallback() {
if (this.stopPropagation) {
this.blocker.block();
}
}
disconnectedCallback() {
this.blocker.unblock();
}
@Listen('click', { passive: false, capture: true })
protected onMouseDown(ev: TouchEvent) {
this.emitTap(ev);

View File

@@ -54,10 +54,6 @@
color: current-color(contrast);
}
:host(.outer-content) {
--background: #{$background-color-step-50};
}
#background-content {
@include position(calc(var(--offset-top) * -1), 0px,calc(var(--offset-bottom) * -1), 0px);

View File

@@ -34,16 +34,24 @@
// Calendar / Header / Action Buttons
// -----------------------------------
:host .calendar-action-buttons ion-item {
--padding-start: #{$datetime-ios-padding};
--background-hover: transparent;
--background-activated: transparent;
.calendar-month-year-toggle {
@include padding(0px, 16px, 0px, #{$datetime-ios-padding});
min-height: 44px;
font-size: dynamic-font-max(16px, 1.6);
font-weight: 600;
&.ion-focused::after {
opacity: 0.15;
}
}
:host .calendar-action-buttons ion-item ion-icon,
.calendar-month-year-toggle #toggle-wrapper {
@include margin(10px, 8px, 10px, 0);
}
:host .calendar-action-buttons .calendar-month-year-toggle ion-icon,
:host .calendar-action-buttons ion-buttons ion-button {
color: current-color(base);
}

View File

@@ -30,15 +30,41 @@
// Calendar / Header / Action Buttons
// -----------------------------------
:host .datetime-calendar .calendar-action-buttons ion-item {
--padding-start: #{$datetime-md-header-padding};
}
:host .calendar-action-buttons ion-item,
:host .calendar-action-buttons ion-button {
--color: #{$text-color-step-350};
}
.calendar-month-year-toggle {
@include padding(12px, 16px, 12px, #{$datetime-md-header-padding});
min-height: 48px;
background: transparent;
color: #{$text-color-step-350};
z-index: 1;
&.ion-focused::after {
opacity: 0.04;
}
}
.calendar-month-year-toggle ion-ripple-effect {
color: currentColor;
}
@media (any-hover: hover) {
.calendar-month-year-toggle.ion-activatable:not(.ion-focused):hover {
&::after {
background: currentColor;
opacity: 0.04;
}
}
}
// Calendar / Header / Days of Week
// -----------------------------------
:host .calendar-days-of-week {
@@ -64,7 +90,6 @@
* if necessary.
*/
grid-template-rows: repeat(6, 1fr);
}
// Individual day button in month

View File

@@ -25,19 +25,6 @@
overflow: hidden;
}
/**
* When using the wheel picker to switch
* between months, sometimes the allowed
* dates may be filtered. As a result, it
* is possible to get a layout shift as
* the picker column will shrink to fit the
* widest item in the column. Setting a minimum
* width avoids this layout shifting.
*/
ion-picker-column-internal {
min-width: 26px;
}
:host(.datetime-size-fixed) {
width: auto;
height: auto;
@@ -48,7 +35,7 @@ ion-picker-column-internal {
}
/**
* This ensures that the picker is apppropriately
* This ensures that the picker is appropriately
* sized and never truncates the text.
*/
:host(.datetime-size-fixed.datetime-prefer-wheel) {
@@ -267,19 +254,8 @@ ion-picker-column-internal {
justify-content: space-between;
}
:host .calendar-action-buttons ion-item,
:host .calendar-action-buttons ion-button {
--background: translucent;
}
:host .calendar-action-buttons ion-item ion-label {
display: flex;
align-items: center;
}
:host .calendar-action-buttons ion-item ion-icon {
@include padding(0, 0, 0, 4px);
--background: transparent;
}
// Calendar / Header / Days of Week
@@ -488,6 +464,55 @@ ion-picker-column-internal {
// Year Picker
// -----------------------------------
:host(.show-month-and-year) .calendar-action-buttons ion-item {
--color: #{current-color(base)};
:host(.show-month-and-year) .calendar-action-buttons .calendar-month-year-toggle {
color: #{current-color(base)};
}
.calendar-month-year {
min-width: 0;
}
.calendar-month-year-toggle {
@include text-inherit();
position: relative;
border: 0;
outline: none;
background: transparent;
cursor: pointer;
z-index: 1;
&::after {
@include button-state();
transition: opacity 15ms linear, background-color 15ms linear;
z-index: -1;
}
&.ion-focused::after {
background: currentColor;
}
&:disabled {
opacity: 0.3;
pointer-events: none;
}
}
.calendar-month-year-toggle ion-icon {
@include padding(0, 0, 0, 4px);
flex-shrink: 0;
}
.calendar-month-year-toggle #toggle-wrapper {
display: inline-flex;
align-items: center;
}

View File

@@ -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-internal/picker-column-internal-interfaces';
import type { PickerColumnItem } from '../picker-column/picker-column-interfaces';
import type {
DatetimePresentation,
@@ -104,7 +104,6 @@ import {
export class Datetime implements ComponentInterface {
private inputId = `ion-dt-${datetimeIds++}`;
private calendarBodyRef?: HTMLElement;
private monthYearToggleItemRef?: HTMLIonItemElement;
private popoverRef?: HTMLIonPopoverElement;
private clearFocusVisible?: () => void;
private parsedMinuteValues?: number[];
@@ -1528,7 +1527,7 @@ export class Datetime implements ComponentInterface {
forcePresentation === 'time-date'
? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
: [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
return <ion-picker-internal>{renderArray}</ion-picker-internal>;
return <ion-picker>{renderArray}</ion-picker>;
}
private renderDatePickerColumns(forcePresentation: string) {
@@ -1614,11 +1613,10 @@ export class Datetime implements ComponentInterface {
: `${defaultParts.year}-${defaultParts.month}-${defaultParts.day}`;
return (
<ion-picker-column-internal
<ion-picker-column
class="date-column"
color={this.color}
disabled={disabled}
items={items}
value={todayString}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
@@ -1648,7 +1646,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{items.map((item) => (
<ion-picker-column-option
part={item.value === todayString ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={item.value}
disabled={item.disabled}
value={item.value}
>
{item.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
@@ -1732,14 +1741,14 @@ export class Datetime implements ComponentInterface {
const { disabled, workingParts } = this;
const activePart = this.getActivePartsWithFallback();
const pickerColumnValue = (workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined;
return (
<ion-picker-column-internal
<ion-picker-column
class="day-column"
color={this.color}
disabled={disabled}
items={days}
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
value={pickerColumnValue}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
// Due to a Safari 14 issue we need to destroy
@@ -1765,7 +1774,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{days.map((day) => (
<ion-picker-column-option
part={day.value === pickerColumnValue ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={day.value}
disabled={day.disabled}
value={day.value}
>
{day.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
@@ -1779,11 +1799,10 @@ export class Datetime implements ComponentInterface {
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
<ion-picker-column
class="month-column"
color={this.color}
disabled={disabled}
items={months}
value={workingParts.month}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
@@ -1810,7 +1829,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{months.map((month) => (
<ion-picker-column-option
part={month.value === workingParts.month ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={month.value}
disabled={month.disabled}
value={month.value}
>
{month.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
private renderYearPickerColumn(years: PickerColumnItem[]) {
@@ -1823,11 +1853,10 @@ export class Datetime implements ComponentInterface {
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
<ion-picker-column
class="year-column"
color={this.color}
disabled={disabled}
items={years}
value={workingParts.year}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
@@ -1854,7 +1883,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{years.map((year) => (
<ion-picker-column-option
part={year.value === workingParts.year ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={year.value}
disabled={year.disabled}
value={year.value}
>
{year.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
private renderTimePickerColumns(forcePresentation: string) {
@@ -1898,11 +1938,10 @@ export class Datetime implements ComponentInterface {
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
<ion-picker-column
color={this.color}
disabled={disabled}
value={activePart.hour}
items={hoursData}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
@@ -1917,7 +1956,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{hoursData.map((hour) => (
<ion-picker-column-option
part={hour.value === activePart.hour ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={hour.value}
disabled={hour.disabled}
value={hour.value}
>
{hour.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
private renderMinutePickerColumn(minutesData: PickerColumnItem[]) {
@@ -1927,11 +1977,10 @@ export class Datetime implements ComponentInterface {
const activePart = this.getActivePartsWithFallback();
return (
<ion-picker-column-internal
<ion-picker-column
color={this.color}
disabled={disabled}
value={activePart.minute}
items={minutesData}
numericInput
onIonChange={(ev: CustomEvent) => {
this.setWorkingParts({
@@ -1946,7 +1995,18 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{minutesData.map((minute) => (
<ion-picker-column-option
part={minute.value === activePart.minute ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART}
key={minute.value}
disabled={minute.disabled}
value={minute.value}
>
{minute.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
private renderDayPeriodPickerColumn(dayPeriodData: PickerColumnItem[]) {
@@ -1959,12 +2019,11 @@ export class Datetime implements ComponentInterface {
const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
return (
<ion-picker-column-internal
<ion-picker-column
style={isDayPeriodRTL ? { order: '-1' } : {}}
color={this.color}
disabled={disabled}
value={activePart.ampm}
items={dayPeriodData}
onIonChange={(ev: CustomEvent) => {
const hour = calculateHourFromAMPM(workingParts, ev.detail.value);
@@ -1982,7 +2041,20 @@ export class Datetime implements ComponentInterface {
ev.stopPropagation();
}}
></ion-picker-column-internal>
>
{dayPeriodData.map((dayPeriod) => (
<ion-picker-column-option
part={
dayPeriod.value === activePart.ampm ? `${WHEEL_ITEM_PART} ${WHEEL_ITEM_ACTIVE_PART}` : WHEEL_ITEM_PART
}
key={dayPeriod.value}
disabled={dayPeriod.disabled}
value={dayPeriod.value}
>
{dayPeriod.text}
</ion-picker-column-option>
))}
</ion-picker-column>
);
}
@@ -2020,35 +2092,18 @@ export class Datetime implements ComponentInterface {
<div class="calendar-header">
<div class="calendar-action-buttons">
<div class="calendar-month-year">
<ion-item
part="month-year-button"
ref={(el) => (this.monthYearToggleItemRef = el)}
button
aria-label="Show year picker"
detail={false}
lines="none"
disabled={disabled}
onClick={() => {
this.toggleMonthAndYearView();
/**
* TODO: FW-3547
*
* Currently there is not a way to set the aria-label on the inner button
* on the `ion-item` and have it be reactive to changes. This is a workaround
* until we either refactor `ion-item` to a button or Stencil adds a way to
* have reactive props for built-in properties, such as `aria-label`.
*/
const { monthYearToggleItemRef } = this;
if (monthYearToggleItemRef) {
const btn = monthYearToggleItemRef.shadowRoot?.querySelector('.item-native');
if (btn) {
const monthYearAriaLabel = this.showMonthAndYear ? 'Hide year picker' : 'Show year picker';
btn.setAttribute('aria-label', monthYearAriaLabel);
}
}
<button
class={{
'calendar-month-year-toggle': true,
'ion-activatable': true,
'ion-focusable': true,
}}
part="month-year-button"
disabled={disabled}
aria-label={this.showMonthAndYear ? 'Hide year picker' : 'Show year picker'}
onClick={() => this.toggleMonthAndYearView()}
>
<ion-label>
<span id="toggle-wrapper">
{getMonthAndYear(this.locale, this.workingParts)}
<ion-icon
aria-hidden="true"
@@ -2056,8 +2111,9 @@ export class Datetime implements ComponentInterface {
lazy={false}
flipRtl={true}
></ion-icon>
</ion-label>
</ion-item>
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
</div>
<div class="calendar-next-prev">
@@ -2362,7 +2418,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-internal');
const cols = (ev.target! as HTMLElement).querySelectorAll('ion-picker-column');
// TODO (FW-615): Potentially remove this when intersection observers are fixed in picker column
cols.forEach((col) => col.scrollActiveItemIntoView());
}}
@@ -2546,3 +2602,5 @@ export class Datetime implements ComponentInterface {
}
let datetimeIds = 0;
const WHEEL_ITEM_PART = 'wheel-item';
const WHEEL_ITEM_ACTIVE_PART = `active`;

View File

@@ -47,7 +47,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
const datetime = page.locator('ion-datetime');
const monthYearButton = page.locator('.calendar-month-year ion-item');
const monthYearButton = page.locator('.calendar-month-year-toggle');
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,7 +1,6 @@
import type { SpecPage } from '@stencil/core/testing';
import { newSpecPage } from '@stencil/core/testing';
import { Item } from '../../../item/item';
import { Datetime } from '../../datetime';
describe('datetime', () => {
@@ -20,15 +19,14 @@ describe('datetime', () => {
beforeEach(async () => {
page = await newSpecPage({
components: [Datetime, Item],
components: [Datetime],
html: `<ion-datetime></ion-datetime>`,
});
});
it('should have aria-label "Show year picker" when collapsed', async () => {
const datetime = page.body.querySelector('ion-datetime')!;
const item = datetime.shadowRoot!.querySelector('.calendar-month-year ion-item');
const monthYearToggleBtn = item!.shadowRoot!.querySelector('button');
const monthYearToggleBtn = datetime.shadowRoot!.querySelector('.calendar-month-year .calendar-month-year-toggle');
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
expect(ariaLabel).toContain('Show year picker');
@@ -36,15 +34,18 @@ describe('datetime', () => {
it('should have aria-label "Hide year picker" when expanded', async () => {
const datetime = page.body.querySelector('ion-datetime')!;
const item = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
const monthYearToggleBtn = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
'.calendar-month-year .calendar-month-year-toggle'
);
item!.click();
monthYearToggleBtn!.click();
await page.waitForChanges();
const itemAfter = datetime.shadowRoot!.querySelector<HTMLIonItemElement>('.calendar-month-year ion-item');
const monthYearToggleBtn = itemAfter!.shadowRoot!.querySelector<HTMLElement>('button');
const ariaLabel = monthYearToggleBtn!.getAttribute('aria-label');
const monthYearToggleBtnAfter = datetime.shadowRoot!.querySelector<HTMLButtonElement>(
'.calendar-month-year .calendar-month-year-toggle'
);
const ariaLabel = monthYearToggleBtnAfter!.getAttribute('aria-label');
expect(ariaLabel).toContain('Hide year picker');
});

View File

@@ -526,4 +526,20 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-calendar-day`));
});
});
test.describe(title('datetime: calendar month toggle'), () => {
test('should have focus styles', async ({ page }) => {
await page.setContent('<ion-datetime value="2021-01-01"></ion-datetime>', config);
const datetime = page.locator('ion-datetime');
await page.waitForSelector('.datetime-ready');
const monthYearToggle = datetime.locator('.calendar-month-year-toggle');
monthYearToggle.evaluate((el: HTMLElement) => el.classList.add('ion-focused'));
await expect(datetime).toHaveScreenshot(screenshot(`date-month-toggle-focused`));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -52,28 +52,28 @@
}
/*
The second selectors that target ion-picker(-column)-internal
The second selectors that target ion-picker(-column)
directly are for styling the time picker. This is currently
undocumented usage.
*/
.custom-grid-wheel,
ion-picker-internal {
ion-picker {
--wheel-highlight-background: rgb(218, 216, 255);
--wheel-fade-background-rgb: 245, 235, 247;
}
ion-picker-internal {
ion-picker {
background-color: rgb(245, 235, 247);
}
.custom-grid-wheel::part(wheel-item),
ion-picker-column-internal::part(wheel-item) {
ion-picker-column-option {
color: rgb(255, 134, 154);
}
.custom-grid-wheel::part(wheel-item active),
ion-picker-column-internal::part(wheel-item active) {
ion-picker-column-option.option-active {
color: rgb(128, 30, 171);
}

View File

@@ -18,7 +18,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
page,
}) => {
const monthYearToggle = page.locator('ion-datetime .calendar-month-year');
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
const monthColumnItems = page.locator('ion-datetime .month-column ion-picker-column-option');
await expect(monthYearToggle).toContainText('January 2022');
@@ -34,7 +34,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test('should adjust the selected day when moving to a month with a different number of days', async ({ page }) => {
const monthYearToggle = page.locator('ion-datetime .calendar-month-year');
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
const monthColumnItems = page.locator('ion-datetime .month-column ion-picker-column-option');
const datetime = page.locator('ion-datetime');
const ionChange = await page.spyOnEvent('ionChange');

View File

@@ -2,8 +2,8 @@ import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { Datetime } from '../../../datetime/datetime';
import { PickerColumnInternal } from '../../../picker-column-internal/picker-column-internal';
import { PickerInternal } from '../../../picker-internal/picker-internal';
import { PickerColumn } from '../../../picker-column/picker-column';
import { Picker } from '../../../picker/picker';
describe('ion-datetime disabled', () => {
beforeEach(() => {
@@ -19,7 +19,7 @@ describe('ion-datetime disabled', () => {
it('picker should be disabled in prefer wheel mode', async () => {
const page = await newSpecPage({
components: [Datetime, PickerColumnInternal, PickerInternal],
components: [Datetime, PickerColumn, Picker],
template: () => (
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
),
@@ -28,7 +28,7 @@ describe('ion-datetime disabled', () => {
await page.waitForChanges();
const datetime = page.body.querySelector('ion-datetime')!;
const columns = datetime.shadowRoot!.querySelectorAll('ion-picker-column-internal');
const columns = datetime.shadowRoot!.querySelectorAll('ion-picker-column');
await expect(columns.length).toEqual(4);

View File

@@ -107,14 +107,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test('should correctly localize year column data', async ({ page }) => {
await page.setContent(
`
<ion-datetime prefer-wheel="true" locale="ar-EG" presentation="date" value="2022-01-01"></ion-datetime>
<ion-datetime prefer-wheel="true" locale="ar-EG" presentation="date" value="2022-01-01" max="2022" min="2022"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const datetimeYear = page.locator('ion-datetime .year-column .picker-item[data-value="2022"]');
const datetimeYear = page.locator('ion-datetime .year-column ion-picker-column-option').nth(0);
await expect(datetimeYear).toHaveText('٢٠٢٢');
});
});

View File

@@ -109,12 +109,8 @@ 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-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)'
);
const hours = page.locator('ion-popover ion-picker-column:nth-child(1) ion-picker-column-option');
const minutes = page.locator('ion-popover ion-picker-column:nth-child(2) ion-picker-column-option');
expect(await hours.count()).toBe(12);
expect(await minutes.count()).toBe(60);
@@ -218,9 +214,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
config
);
const hourPickerItems = page.locator(
'ion-datetime ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)'
);
const hourPickerItems = page.locator('ion-datetime ion-picker-column:first-of-type ion-picker-column-option');
await expect(hourPickerItems).toHaveText(['8', '9', '10', '11']);
});
@@ -242,9 +236,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
config
);
const hourPickerItems = page.locator(
'ion-datetime ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)'
);
const hourPickerItems = page.locator('ion-datetime ion-picker-column:first-of-type ion-picker-column-option');
await expect(hourPickerItems).toHaveText(['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']);
});
@@ -308,7 +300,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
);
const datetime = page.locator('ion-datetime');
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
const monthColumnItems = page.locator('ion-datetime .month-column ion-picker-column-option');
const ionChange = await page.spyOnEvent('ionChange');
await page.waitForSelector('.datetime-ready');
@@ -360,9 +352,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
await ionPopoverDidPresent.next();
const hours = page.locator(
'ion-popover ion-picker-column-internal:nth-child(1) .picker-item:not(.picker-item-empty)'
);
const hours = page.locator('ion-popover ion-picker-column:nth-child(1) ion-picker-column-option');
await expect(await hours.count()).toBe(4);
});

View File

@@ -15,7 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const datetimeFooter = page.locator('#date-time .datetime-footer');
await expect(datetimeFooter).toBeVisible();
const pickerButton = page.locator('#date-time .calendar-month-year > ion-item');
const pickerButton = page.locator('#date-time .calendar-month-year > .calendar-month-year-toggle');
await pickerButton.click();
await page.waitForChanges();
await expect(datetimeFooter).not.toBeVisible();

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -25,7 +25,7 @@
<ion-content class="ion-padding">
<ion-button id="open-datetime" onclick="presentPopover(defaultPopover, event)">Present Popover</ion-button>
<ion-popover class="datetime-popover" id="default-popover">
<ion-datetime value="2022-05-03"></ion-datetime>
<ion-datetime value="2022-05-03T14:23:00.000Z"></ion-datetime>
</ion-popover>
</ion-content>

View File

@@ -82,7 +82,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .day-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .day-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(27);
});
test('should respect the max bounds', async ({ page }) => {
@@ -95,7 +95,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .day-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .day-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(1);
});
test('should respect isDateEnabled preference', async ({ page }) => {
@@ -118,9 +118,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const disabledMonths = page.locator('.month-column .picker-item[disabled]');
const disabledYears = page.locator('.year-column .picker-item[disabled]');
const disabledDays = page.locator('.day-column .picker-item[disabled]');
const disabledMonths = page.locator('.month-column ion-picker-column-option.option-disabled');
const disabledYears = page.locator('.year-column ion-picker-column-option.option-disabled');
const disabledDays = page.locator('.day-column ion-picker-column-option.option-disabled');
expect(await disabledMonths.count()).toBe(0);
expect(await disabledYears.count()).toBe(0);
@@ -143,9 +143,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)');
const yearValues = page.locator('.year-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)');
const monthValues = page.locator('.month-column ion-picker-column-option');
const yearValues = page.locator('.year-column ion-picker-column-option');
const dayValues = page.locator('.day-column ion-picker-column-option');
expect(await monthValues.count()).toBe(2);
expect(await yearValues.count()).toBe(3);
@@ -178,7 +178,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const ionChange = await page.spyOnEvent('ionChange');
const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)');
const monthValues = page.locator('.month-column ion-picker-column-option');
// Change month value
await monthValues.nth(0).click();
@@ -212,7 +212,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const ionChange = await page.spyOnEvent('ionChange');
const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.day-column ion-picker-column-option');
// Change day value
await dayValues.nth(0).click();
@@ -233,7 +233,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const ionChange = await page.spyOnEvent('ionChange');
const yearValues = page.locator('.year-column .picker-item:not(.picker-item-empty)');
const yearValues = page.locator('.year-column ion-picker-column-option');
/**
* Change year value
@@ -259,9 +259,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.value = '2021-05-25T12:40:00.000Z'));
await page.waitForChanges();
const selectedMonth = datetime.locator('.month-column .picker-item-active');
const selectedDay = datetime.locator('.day-column .picker-item-active');
const selectedYear = datetime.locator('.year-column .picker-item-active');
const selectedMonth = datetime.locator('.month-column ion-picker-column-option.option-active');
const selectedDay = datetime.locator('.day-column ion-picker-column-option.option-active');
const selectedYear = datetime.locator('.year-column ion-picker-column-option.option-active');
await expect(selectedMonth).toHaveText(/May/);
await expect(selectedDay).toHaveText(/25/);
@@ -287,9 +287,8 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)');
const monthValues = page.locator('.month-column ion-picker-column-option');
const dayValues = page.locator('.day-column ion-picker-column-option');
await expect(monthValues).toHaveText(['1月', '2月', '3月']);
await expect(dayValues).toHaveText(['1日', '2日', '3日']);
});
@@ -308,7 +307,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const columns = page.locator('ion-picker-column-internal');
const columns = page.locator('ion-picker-column');
await expect(columns.nth(0)).toHaveClass(/month-column/);
await expect(columns.nth(1)).toHaveClass(/day-column/);
@@ -329,7 +328,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const columns = page.locator('ion-picker-column-internal');
const columns = page.locator('ion-picker-column');
await expect(columns.nth(0)).toHaveClass(/day-column/);
await expect(columns.nth(1)).toHaveClass(/month-column/);
@@ -348,7 +347,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .date-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(57);
});
test('should respect the max bounds', async ({ page }) => {
@@ -361,7 +360,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .date-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(41);
});
test('should respect isDateEnabled preference', async ({ page }) => {
@@ -384,7 +383,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const disabledDates = page.locator('.date-column .picker-item[disabled]');
const disabledDates = page.locator('.date-column ion-picker-column-option.option-disabled');
expect(await disabledDates.count()).toBe(44);
});
@@ -405,7 +404,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dateValues = page.locator('.date-column ion-picker-column-option');
expect(await dateValues.count()).toBe(5);
});
@@ -426,8 +425,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dateValues = page.locator('.date-column ion-picker-column-option');
await expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
});
test('should respect min and max bounds even across years', async ({ page }) => {
@@ -447,18 +445,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateColumn = page.locator('.date-column');
const dateValues = dateColumn.locator('.picker-item:not(.picker-item-empty)');
const dateColumnScrollEl = dateColumn.locator('.picker-opts');
const dateValues = dateColumn.locator('ion-picker-column-option');
expect(await dateValues.count()).toBe(90);
/**
* Select 1st item to change the dates rendered
*/
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2022-1-1');
await dateColumn.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await expect(dateValues.nth(0)).toHaveJSProperty('value', '2022-1-1');
await dateColumnScrollEl.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await page.waitForChanges();
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2021-12-1');
await expect(dateValues.nth(0)).toHaveJSProperty('value', '2021-12-1');
});
test('should keep sliding window if default window is within min and max constraints', async ({ page }) => {
await page.setContent(
@@ -476,7 +475,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.date-column ion-picker-column-option');
expect(await dayValues.count()).toBe(92);
});
@@ -496,7 +495,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.date-column ion-picker-column-option');
expect(await dayValues.count()).toBe(15);
});
@@ -514,7 +513,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const ionChange = await page.spyOnEvent('ionChange');
const dayValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.date-column ion-picker-column-option');
// Change day/month value
await dayValues.nth(0).click();
@@ -533,7 +532,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .date-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(57);
});
test('should respect the max bounds', async ({ page }) => {
@@ -546,7 +545,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]');
const dayValues = page.locator('ion-datetime .date-column ion-picker-column-option');
expect(await dayValues.count()).toEqual(41);
});
test('should respect isDateEnabled preference', async ({ page }) => {
@@ -569,7 +568,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const disabledDates = page.locator('.date-column .picker-item[disabled]');
const disabledDates = page.locator('.date-column ion-picker-column-option.option-disabled');
expect(await disabledDates.count()).toBe(44);
});
@@ -590,7 +589,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dateValues = page.locator('.date-column ion-picker-column-option');
expect(await dateValues.count()).toBe(5);
});
@@ -611,7 +610,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dateValues = page.locator('.date-column ion-picker-column-option');
await expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
});
@@ -632,18 +631,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dateColumn = page.locator('.date-column');
const dateValues = dateColumn.locator('.picker-item:not(.picker-item-empty)');
const dateColumnScrollEl = dateColumn.locator('.picker-opts');
const dateValues = dateColumn.locator('ion-picker-column-option');
expect(await dateValues.count()).toBe(90);
/**
* Select 1st item to change the dates rendered
*/
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2022-1-1');
await dateColumn.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await expect(dateValues.nth(0)).toHaveJSProperty('value', '2022-1-1');
await dateColumnScrollEl.evaluate((el: HTMLElement) => (el.scrollTop = 0));
await page.waitForChanges();
await expect(dateValues.nth(0)).toHaveAttribute('data-value', '2021-12-1');
await expect(dateValues.nth(0)).toHaveJSProperty('value', '2021-12-1');
});
test('should keep sliding window if default window is within min and max constraints', async ({ page }) => {
await page.setContent(
@@ -661,7 +661,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.date-column ion-picker-column-option');
expect(await dayValues.count()).toBe(92);
});
@@ -681,7 +681,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await page.waitForSelector('.datetime-ready');
const dayValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
const dayValues = page.locator('.date-column ion-picker-column-option');
expect(await dayValues.count()).toBe(15);
});

View File

@@ -227,7 +227,7 @@ class TimePickerFixture {
}
async expectTime(hour: number, minute: number, ampm: string) {
const pickerColumns = this.timePicker.locator('ion-picker-column-internal');
const pickerColumns = this.timePicker.locator('ion-picker-column');
await expect(pickerColumns.nth(0)).toHaveJSProperty('value', hour);
await expect(pickerColumns.nth(1)).toHaveJSProperty('value', minute);

View File

@@ -49,7 +49,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
await calendarMonthYear.click();
await page.waitForChanges();
await page.locator('.month-column .picker-item[data-value="3"]').click();
await page.locator('.month-column ion-picker-column-option').nth(2).click();
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('March 2022');
@@ -68,7 +68,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
const monthYearButton = page.locator('.calendar-month-year ion-item');
const monthYearButton = page.locator('.calendar-month-year-toggle');
await expect(calendarMonthYear).toHaveText('February 2022');
await page.keyboard.press(tabKey);
@@ -78,7 +78,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
await page.keyboard.press('Enter');
await page.waitForChanges();
const marchPickerItem = page.locator('.month-column .picker-item[data-value="3"]');
const marchPickerItem = page.locator('.month-column ion-picker-column-option').nth(2);
await expect(marchPickerItem).toBeVisible();
});
@@ -114,7 +114,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, scree
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
const datetime = page.locator('ion-datetime');
const monthYearButton = page.locator('.calendar-month-year ion-item');
const monthYearButton = page.locator('.calendar-month-year-toggle');
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');

View File

@@ -29,7 +29,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
config
);
const items = page.locator('.month-column .picker-item:not(.picker-item-empty)');
const items = page.locator('.month-column ion-picker-column-option');
await expect(items).toHaveText(['May', 'June', 'October']);
});
test('should render correct years', async ({ page }) => {
@@ -40,7 +40,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
config
);
const items = page.locator('.year-column .picker-item:not(.picker-item-empty)');
const items = page.locator('.year-column ion-picker-column-option');
await expect(items).toHaveText(['2022', '2021', '2020']);
});
test('should render correct hours', async ({ page }) => {
@@ -51,7 +51,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
config
);
const items = page.locator('ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)');
const items = page.locator('ion-picker-column:first-of-type ion-picker-column-option');
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-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
const items = page.locator('ion-picker-column:nth-of-type(2) ion-picker-column-option');
await expect(items).toHaveText(['01', '02', '03']);
});
test('should adjust default parts for allowed hour and minute values', async ({ page }) => {
@@ -93,23 +93,25 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.waitForSelector('.datetime-ready');
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 minuteColumn = page.locator('ion-picker-column').nth(1);
const minuteItems = minuteColumn.locator('ion-picker-column-option');
const hourItems = page.locator('ion-picker-column-internal:nth-of-type(1) .picker-item:not(.picker-item-empty)');
await expect(minuteItems).toHaveText(['00', '15', '30', '45']);
await expect(minuteColumn).toHaveJSProperty('value', 15);
const hourColumn = page.locator('ion-picker-column').nth(0);
const hourItems = hourColumn.locator('ion-picker-column-option');
await expect(hourItems).toHaveText(['2']);
await expect(hourItems.nth(0)).toHaveClass(/picker-item-active/);
await expect(hourColumn).toHaveJSProperty('value', 2);
/**
* Since the allowed hour is 2AM, the time period
* should switch from PM to AM.
*/
const ampmItems = page.locator('ion-picker-column-internal:nth-of-type(3) .picker-item:not(.picker-item-empty)');
const ampmColumn = page.locator('ion-picker-column').nth(2);
const ampmItems = ampmColumn.locator('ion-picker-column-option');
await expect(ampmItems).toHaveText(['AM', 'PM']);
await expect(ampmItems.nth(0)).toHaveClass(/picker-item-active/);
await expect(ampmColumn).toHaveJSProperty('value', 'am');
});
test('should adjust default parts month for allowed month values', async ({ page }) => {
/**
@@ -139,9 +141,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.waitForSelector('.datetime-ready');
const monthItems = page.locator('.month-column .picker-item:not(.picker-item-empty)');
const monthItems = page.locator('.month-column ion-picker-column-option');
await expect(monthItems).toHaveText(['January']);
await expect(monthItems.nth(0)).toHaveClass(/picker-item-active/);
await expect(monthItems.nth(0)).toHaveClass(/option-active/);
});
test('today date highlight should persist even if disallowed from dayValues', async ({ page }) => {
/**

View File

@@ -1,5 +1,5 @@
import type { Mode } from '../../../interface';
import type { PickerColumnItem } from '../../picker-column-internal/picker-column-internal-interfaces';
import type { PickerColumnItem } from '../../picker-column/picker-column-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-internal
* @returns Date data to be used in ion-picker-column
*/
export const getDayColumnData = (
locale: string,

View File

@@ -95,7 +95,9 @@ export class Input implements ComponentInterface {
@Prop() autocorrect: 'on' | 'off' = 'off';
/**
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.
*
* This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Prop() autofocus = false;
@@ -424,6 +426,8 @@ export class Input implements ComponentInterface {
*
* Developers who wish to focus an input when an overlay is presented
* should call `setFocus` after `didPresent` has resolved.
*
* See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Method()
async setFocus() {

View File

@@ -225,6 +225,17 @@ export class Loading implements ComponentInterface, OverlayInterface {
if (this.isOpen === true) {
raf(() => this.present());
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
disconnectedCallback() {

View File

@@ -52,7 +52,7 @@ export class Menu implements ComponentInterface, MenuI {
width!: number;
_isOpen = false;
backdropEl?: HTMLElement;
backdropEl?: HTMLIonBackdropElement;
menuInnerEl?: HTMLElement;
contentEl?: HTMLElement;
lastFocus?: HTMLElement;

View File

@@ -368,6 +368,17 @@ export class Modal implements ComponentInterface, OverlayInterface {
raf(() => this.present());
}
this.breakpointsChanged(this.breakpoints);
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
/**

View File

@@ -1,5 +0,0 @@
export interface PickerColumnItem {
text: string;
value: string | number;
disabled?: boolean;
}

View File

@@ -1 +0,0 @@
@import "./picker-column-internal.scss";

View File

@@ -1,6 +0,0 @@
@import "./picker-column-internal.scss";
@import "../../themes/ionic.globals.md";
:host .picker-item-active {
color: current-color(base);
}

View File

@@ -1,101 +0,0 @@
@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[disabled] {
cursor: default;
}
:host .picker-item-empty,
:host(:not([disabled])) .picker-item[disabled] {
scroll-snap-align: none;
}
:host([disabled]) {
overflow-y: hidden;
}
:host .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);
}
}

View File

@@ -1,501 +0,0 @@
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;
/**
* If `true`, the user cannot interact with the picker.
*/
@Prop() disabled = false;
/**
* 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() {
// If the whole picker column is disabled, the current value should appear active
// If the current value item is specifically disabled, it should not appear active
const selector = `.picker-item[data-value="${this.value}"]${this.disabled ? '' : ':not([disabled])'}`;
return getElementRoot(this.el).querySelector(selector) as HTMLElement | null;
}
render() {
const { items, color, disabled: pickerDisabled, 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}`}
disabled={pickerDisabled}
tabindex={pickerDisabled ? null : 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">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
{items.map((item, index) => {
const isItemDisabled = pickerDisabled || item.disabled || false;
{
/*
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,
}}
data-value={item.value}
data-index={index}
onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement, true);
}}
disabled={isItemDisabled}
part={PICKER_ITEM_PART}
>
{item.text}
</button>
);
})}
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
<div class="picker-item picker-item-empty" aria-hidden="true">
&nbsp;
</div>
</Host>
);
}
}
const PICKER_ITEM_ACTIVE_CLASS = 'picker-item-active';
const PICKER_ITEM_PART = 'wheel-item';
const PICKER_ITEM_ACTIVE_PART = 'active';

View File

@@ -1,193 +0,0 @@
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-internal: disabled items'), () => {
test('all picker items should be enabled by default', async ({ page }) => {
await page.setContent(
`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b' },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
const pickerItems = page.locator('ion-picker-column-internal .picker-item:not(.picker-item-empty, [disabled])');
expect(await pickerItems.count()).toBe(3);
});
test('disabled picker item should not be interactive', async ({ page }) => {
await page.setContent(
`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
const disabledItem = page.locator('ion-picker-column-internal .picker-item[disabled]');
await expect(disabledItem).not.toBeEnabled();
});
test('disabled picker item should not be considered active', 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' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
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-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
</script>
`,
config
);
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-internal .picker-item[data-value="b"]');
await expect(disabledItem).toBeDisabled();
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-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: 'A', value: 'a' },
{ text: 'B', value: 'b', disabled: true },
{ text: 'C', value: 'c' }
]
column.value = 'b'
</script>
`,
config
);
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
await expect(disabledItem).toBeDisabled();
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-internal: disabled column rendering'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/picker-column-internal/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-internal-disabled-column'));
});
});
});
/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('picker-column-internal: disabled column'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/picker-column-internal/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();
});
});
});

View File

@@ -0,0 +1 @@
@import "./picker-column-option";

View File

@@ -0,0 +1,5 @@
@import "./picker-column-option";
:host(.option-active) {
color: current-color(base);
}

View File

@@ -0,0 +1,45 @@
@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;
}

View File

@@ -0,0 +1,133 @@
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 {
/**
* We keep track of the parent picker column
* so we can update the value of it when
* clicking an enable option.
*/
private pickerColumn: HTMLIonPickerColumnElement | null = null;
@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;
}
connectedCallback() {
this.pickerColumn = this.el.closest('ion-picker-column');
}
disconnectedCallback() {
this.pickerColumn = null;
}
/**
* The column options can load at any time
* so the options needs to tell the
* parent picker column when it is loaded
* so the picker column can ensure it is
* centered in the view.
*
* We intentionally run this for every
* option. If we only ran this from
* the selected option then if the newly
* loaded options were not selected then
* scrollActiveItemIntoView would not be called.
*/
componentDidLoad() {
const { pickerColumn } = this;
if (pickerColumn !== null) {
pickerColumn.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 { pickerColumn } = this;
if (pickerColumn !== null) {
pickerColumn.setValue(this.value);
}
}
render() {
const { color, 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></slot>
</button>
</Host>
);
}
}

View File

@@ -0,0 +1,22 @@
<!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>

View File

@@ -0,0 +1,18 @@
import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions
*/
configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
test.describe(title('picker column option: a11y'), () => {
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/picker-column-option/test/a11y`, config);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
});
});

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Picker Column Option - 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" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
</style>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Picker Column Option - Basic</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>
</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More