Compare commits

...

64 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
286 changed files with 5118 additions and 3831 deletions

6
.github/CODEOWNERS vendored
View File

@@ -11,6 +11,8 @@
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
* @ionic-team/framework
# Frameworks
## Angular
@@ -51,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

@@ -17,6 +17,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Components](#version-8x-components)
- [Content](#version-8x-content)
- [Datetime](#version-8x-datetime)
- [Picker](#version-8x-picker)
<h2 id="version-8x-browser-platform-support">Browser and Platform Support</h2>
@@ -59,4 +60,9 @@ This section details the desktop browser, JavaScript framework, and mobile platf
+ background: red;
}
```
```
<h2 id="version-8x-picker">Picker</h2>
- `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.

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.5.7](https://github.com/ionic-team/ionic-framework/compare/v7.5.6...v7.5.7) (2023-11-29)
### Bug Fixes
* **alert:** date inputs render correctly in mobile safari ([#28495](https://github.com/ionic-team/ionic-framework/issues/28495)) ([b833f0e](https://github.com/ionic-team/ionic-framework/commit/b833f0e826ddd261230e2e29b70e2dc884d8cb04)), closes [#28494](https://github.com/ionic-team/ionic-framework/issues/28494)
* **datetime:** allow disabling datetime with prefer-wheel ([#28511](https://github.com/ionic-team/ionic-framework/issues/28511)) ([01130e1](https://github.com/ionic-team/ionic-framework/commit/01130e12e1d73bbf558da9d4dffd7122822ff39c))
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.5.7](https://github.com/ionic-team/ionic-framework/compare/v7.5.6...v7.5.7) (2023-11-29)
### Bug Fixes
* **alert:** date inputs render correctly in mobile safari ([#28495](https://github.com/ionic-team/ionic-framework/issues/28495)) ([b833f0e](https://github.com/ionic-team/ionic-framework/commit/b833f0e826ddd261230e2e29b70e2dc884d8cb04)), closes [#28494](https://github.com/ionic-team/ionic-framework/issues/28494)
* **datetime:** allow disabling datetime with prefer-wheel ([#28511](https://github.com/ionic-team/ionic-framework/issues/28511)) ([01130e1](https://github.com/ionic-team/ionic-framework/commit/01130e12e1d73bbf558da9d4dffd7122822ff39c))
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)

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

18
core/package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/core",
"version": "7.5.6",
"version": "7.5.7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.5.6",
"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

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.5.6",
"version": "7.5.7",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -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

@@ -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;

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,
@@ -1527,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) {
@@ -1613,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.
@@ -1647,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>
);
}
@@ -1731,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
@@ -1764,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>
);
}
@@ -1778,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.
@@ -1809,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[]) {
@@ -1822,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.
@@ -1853,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) {
@@ -1897,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({
@@ -1916,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[]) {
@@ -1926,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({
@@ -1945,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[]) {
@@ -1958,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);
@@ -1981,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>
);
}
@@ -2345,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());
}}
@@ -2529,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

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

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');
@@ -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();
});

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

@@ -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>

View File

@@ -0,0 +1,55 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('picker-column-option: rendering'), () => {
test('picker option should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-picker-column-option value="option">My Option</ion-picker-column-option>
`,
config
);
const option = page.locator('ion-picker-column-option');
await expect(option).toHaveScreenshot(screenshot('picker-column-option'));
});
test('disabled picker option should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-picker-column-option disabled="true" value="option">My Option</ion-picker-column-option>
`,
config
);
const option = page.locator('ion-picker-column-option');
await expect(option).toHaveScreenshot(screenshot('disabled-picker-column-option'));
});
test('active picker option should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-picker-column-option class="option-active" value="option">My Option</ion-picker-column-option>
`,
config
);
const option = page.locator('ion-picker-column-option');
await expect(option).toHaveScreenshot(screenshot('active-picker-column-option'));
});
test('disabled active picker option should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-picker-column-option class="option-active" disabled="true" value="option">My Option</ion-picker-column-option>
`,
config
);
const option = page.locator('ion-picker-column-option');
await expect(option).toHaveScreenshot(screenshot('disabled-active-picker-column-option'));
});
});
});

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