Compare commits
46 Commits
v7.2.4-nig
...
sp/react-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1194f24756 | ||
|
|
f49ce7a835 | ||
|
|
1ab433be07 | ||
|
|
fa4113117d | ||
|
|
f450f0a58a | ||
|
|
314055cf7a | ||
|
|
b333bdffe6 | ||
|
|
5701f7661e | ||
|
|
2b5da93cdc | ||
|
|
e2be7fdf3a | ||
|
|
0ac3df3f37 | ||
|
|
5a1bbc9fa3 | ||
|
|
e2c1845a24 | ||
|
|
679821861a | ||
|
|
77707b8c1e | ||
|
|
444acc1f1b | ||
|
|
bbfb8f81a6 | ||
|
|
1015c06cbe | ||
|
|
6bcefcd9ed | ||
|
|
901679882c | ||
|
|
6877d659d3 | ||
|
|
16f7ec2284 | ||
|
|
a4f0dc9d18 | ||
|
|
076cadb696 | ||
|
|
f2611f23e0 | ||
|
|
7c24965216 | ||
|
|
ffe5307905 | ||
|
|
47982aaa61 | ||
|
|
6ee41fd639 | ||
|
|
d3e82c99b8 | ||
|
|
eafa7b5dc6 | ||
|
|
92b13c298b | ||
|
|
e5a02f49e5 | ||
|
|
8d22874b79 | ||
|
|
8fa12fc888 | ||
|
|
e9faf54d0a | ||
|
|
7585408e10 | ||
|
|
9e9ce9420d | ||
|
|
0324983924 | ||
|
|
3f093cec4f | ||
|
|
c421d7d806 | ||
|
|
0d3127ad09 | ||
|
|
06be0e5111 | ||
|
|
5ce4ec0439 | ||
|
|
9a685882b7 | ||
|
|
5d1ee1646f |
@@ -14,6 +14,6 @@ runs:
|
||||
with:
|
||||
name: ${{ inputs.name }}
|
||||
path: ${{ inputs.path }}
|
||||
- name: Exract Archive
|
||||
- name: Extract Archive
|
||||
run: unzip -q -o ${{ inputs.path }}/${{ inputs.filename }}
|
||||
shell: bash
|
||||
|
||||
@@ -13,6 +13,15 @@ runs:
|
||||
path: ./core
|
||||
filename: CoreBuild.zip
|
||||
- name: Check Diff
|
||||
run: git diff --exit-code
|
||||
run: |
|
||||
git diff --exit-code || {
|
||||
echo -e "\033[1;31m⚠️ Error: Differences Detected ⚠️\033[0m"
|
||||
echo -e "\033[1;31mThere are uncommitted changes between the build outputs from CI and your branch.\033[0m"
|
||||
echo -e "\033[1;31mPlease ensure you have followed these steps:\033[0m"
|
||||
echo -e "\033[1;31m1. Run 'npm run build' locally to generate the latest build output.\033[0m"
|
||||
echo -e "\033[1;31m2. Commit and push all necessary changes to your branch.\033[0m"
|
||||
echo -e "\033[1;31m3. Compare and validate the differences before proceeding.\033[0m"
|
||||
exit 1
|
||||
}
|
||||
shell: bash
|
||||
working-directory: ./core
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: 'Test Core Spec'
|
||||
description: 'Test Core Spec'
|
||||
inputs:
|
||||
stencil-version:
|
||||
description: 'The NPM tag of @stencil/core to install.'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
@@ -7,9 +10,14 @@ runs:
|
||||
with:
|
||||
node-version: 16.x
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: ./core
|
||||
shell: bash
|
||||
- name: Install Stencil ${{ inputs.stencil-version }}
|
||||
run: npm install @stencil/core@${{ inputs.stencil-version }}
|
||||
shell: bash
|
||||
working-directory: ./core
|
||||
if: ${{ inputs.stencil-version != '' }}
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
2
.github/workflows/stencil-nightly.yml
vendored
@@ -46,6 +46,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/workflows/actions/test-core-spec
|
||||
with:
|
||||
stencil-version: nightly
|
||||
|
||||
test-core-screenshot:
|
||||
strategy:
|
||||
|
||||
44
CHANGELOG.md
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** ionTabsWillChange is fired before tab activation ([#27991](https://github.com/ionic-team/ionic-framework/issues/27991)) ([bbfb8f8](https://github.com/ionic-team/ionic-framework/commit/bbfb8f81a61475d7e73b63743db5d6a0cd979d21)), closes [#27212](https://github.com/ionic-team/ionic-framework/issues/27212)
|
||||
* **input, textarea:** clearOnEdit does not clear when pressing Tab ([#28005](https://github.com/ionic-team/ionic-framework/issues/28005)) ([444acc1](https://github.com/ionic-team/ionic-framework/commit/444acc1f1bca348b62dfb398067cc087529f67f1)), closes [#27746](https://github.com/ionic-team/ionic-framework/issues/27746)
|
||||
* **react:** avoid multiple invocations of onDidDismiss and onWillPresent ([#28020](https://github.com/ionic-team/ionic-framework/issues/28020)) ([0ac3df3](https://github.com/ionic-team/ionic-framework/commit/0ac3df3f378bdefc3a927adc798ebd9ec7a54fee)), closes [#28010](https://github.com/ionic-team/ionic-framework/issues/28010)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.3.0](https://github.com/ionic-team/ionic-framework/compare/v7.2.4...v7.3.0) (2023-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **alert:** radio and checkbox labels wrap to next line ([#27898](https://github.com/ionic-team/ionic-framework/issues/27898)) ([0d3127a](https://github.com/ionic-team/ionic-framework/commit/0d3127ad09df3c914a8c254f14931de5ca3beb31)), closes [#17269](https://github.com/ionic-team/ionic-framework/issues/17269)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **action-sheet:** add htmlAttributes property for passing attributes to buttons ([#27863](https://github.com/ionic-team/ionic-framework/issues/27863)) ([5ce4ec0](https://github.com/ionic-team/ionic-framework/commit/5ce4ec0439e4f31aba31062fd8af4a2ad792a54f))
|
||||
* **alert:** add htmlAttributes property for passing attributes to buttons ([#27862](https://github.com/ionic-team/ionic-framework/issues/27862)) ([06be0e5](https://github.com/ionic-team/ionic-framework/commit/06be0e511164ebdaa6af9a3747d0585260c030a9))
|
||||
* **toast:** add htmlAttributes property for passing attributes to buttons ([#27855](https://github.com/ionic-team/ionic-framework/issues/27855)) ([9a68588](https://github.com/ionic-team/ionic-framework/commit/9a685882b7085d911ff09eedacc367629e32348a))
|
||||
* **toast:** add shadow part for cancel button ([#27921](https://github.com/ionic-team/ionic-framework/issues/27921)) ([e9faf54](https://github.com/ionic-team/ionic-framework/commit/e9faf54d0a7409521706ce9c8b0d26f3fbe9ba41)), closes [#27920](https://github.com/ionic-team/ionic-framework/issues/27920)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tap-click:** do not error in document-less environment ([#27972](https://github.com/ionic-team/ionic-framework/issues/27972)) ([28bd4ba](https://github.com/ionic-team/ionic-framework/commit/28bd4ba720bb77d5f5c48cd7a45e0015daddc9dd))
|
||||
* **title:** large title aligns with ios spec ([#27969](https://github.com/ionic-team/ionic-framework/issues/27969)) ([8fa12fc](https://github.com/ionic-team/ionic-framework/commit/8fa12fc88857df27a1ca11249f0085e100fe1474)), closes [#27966](https://github.com/ionic-team/ionic-framework/issues/27966)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
| Project | Package | Version | Downloads| Links |
|
||||
| ------- | ------- | ------- | -------- |:-----:|
|
||||
| **Core** | [`@ionic/core`](https://www.npmjs.com/package/@ionic/core) | [](https://www.npmjs.com/package/@ionic/core) | <a href="https://www.npmjs.com/package/@ionic/core" target="_blank"><img src="https://img.shields.io/npm/dm/@ionic/core.svg" alt="NPM Downloads" /></a> | [`README.md`](core/README.md)
|
||||
| **Angular** | [`@ionic/angular`](https://www.npmjs.com/package/@ionic/angular) | [](https://www.npmjs.com/package/@ionic/angular) | <a href="https://www.npmjs.com/package/@ionic/angular" target="_blank"><img src="https://img.shields.io/npm/dm/@ionic/angular.svg" alt="NPM Downloads" /></a> | [`README.md`](angular/README.md)
|
||||
| **Angular** | [`@ionic/angular`](https://www.npmjs.com/package/@ionic/angular) | [](https://www.npmjs.com/package/@ionic/angular) | <a href="https://www.npmjs.com/package/@ionic/angular" target="_blank"><img src="https://img.shields.io/npm/dm/@ionic/angular.svg" alt="NPM Downloads" /></a> | [`README.md`](packages/angular/README.md)
|
||||
| **Vue** | [`@ionic/vue`](https://www.npmjs.com/package/@ionic/vue) | [](https://www.npmjs.com/package/@ionic/vue) | <a href="https://www.npmjs.com/package/@ionic/vue" target="_blank"><img src="https://img.shields.io/npm/dm/@ionic/vue.svg" alt="NPM Downloads" /></a> | [`README.md`](packages/vue/README.md)
|
||||
| **React** | [`@ionic/react`](https://www.npmjs.com/package/@ionic/react) | [](https://www.npmjs.com/package/@ionic/react) | <a href="https://www.npmjs.com/package/@ionic/react" target="_blank"><img src="https://img.shields.io/npm/dm/@ionic/react.svg" alt="NPM Downloads" /></a> |[`README.md`](packages/react/README.md)
|
||||
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input, textarea:** clearOnEdit does not clear when pressing Tab ([#28005](https://github.com/ionic-team/ionic-framework/issues/28005)) ([444acc1](https://github.com/ionic-team/ionic-framework/commit/444acc1f1bca348b62dfb398067cc087529f67f1)), closes [#27746](https://github.com/ionic-team/ionic-framework/issues/27746)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.3.0](https://github.com/ionic-team/ionic-framework/compare/v7.2.4...v7.3.0) (2023-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **alert:** radio and checkbox labels wrap to next line ([#27898](https://github.com/ionic-team/ionic-framework/issues/27898)) ([0d3127a](https://github.com/ionic-team/ionic-framework/commit/0d3127ad09df3c914a8c254f14931de5ca3beb31)), closes [#17269](https://github.com/ionic-team/ionic-framework/issues/17269)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **action-sheet:** add htmlAttributes property for passing attributes to buttons ([#27863](https://github.com/ionic-team/ionic-framework/issues/27863)) ([5ce4ec0](https://github.com/ionic-team/ionic-framework/commit/5ce4ec0439e4f31aba31062fd8af4a2ad792a54f))
|
||||
* **alert:** add htmlAttributes property for passing attributes to buttons ([#27862](https://github.com/ionic-team/ionic-framework/issues/27862)) ([06be0e5](https://github.com/ionic-team/ionic-framework/commit/06be0e511164ebdaa6af9a3747d0585260c030a9))
|
||||
* **toast:** add htmlAttributes property for passing attributes to buttons ([#27855](https://github.com/ionic-team/ionic-framework/issues/27855)) ([9a68588](https://github.com/ionic-team/ionic-framework/commit/9a685882b7085d911ff09eedacc367629e32348a))
|
||||
* **toast:** add shadow part for cancel button ([#27921](https://github.com/ionic-team/ionic-framework/issues/27921)) ([e9faf54](https://github.com/ionic-team/ionic-framework/commit/e9faf54d0a7409521706ce9c8b0d26f3fbe9ba41)), closes [#27920](https://github.com/ionic-team/ionic-framework/issues/27920)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tap-click:** do not error in document-less environment ([#27972](https://github.com/ionic-team/ionic-framework/issues/27972)) ([28bd4ba](https://github.com/ionic-team/ionic-framework/commit/28bd4ba720bb77d5f5c48cd7a45e0015daddc9dd))
|
||||
* **title:** large title aligns with ios spec ([#27969](https://github.com/ionic-team/ionic-framework/issues/27969)) ([8fa12fc](https://github.com/ionic-team/ionic-framework/commit/8fa12fc88857df27a1ca11249f0085e100fe1474)), closes [#27966](https://github.com/ionic-team/ionic-framework/issues/27966)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
|
||||
|
||||
|
||||
|
||||
@@ -1471,6 +1471,7 @@ ion-toast,css-prop,--start
|
||||
ion-toast,css-prop,--white-space
|
||||
ion-toast,css-prop,--width
|
||||
ion-toast,part,button
|
||||
ion-toast,part,button cancel
|
||||
ion-toast,part,container
|
||||
ion-toast,part,header
|
||||
ion-toast,part,icon
|
||||
|
||||
66
core/package-lock.json
generated
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.3",
|
||||
"version": "7.3.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.3",
|
||||
"version": "7.3.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^3.4.0",
|
||||
"@stencil/core": "^4.1.0",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.2.2",
|
||||
"@capacitor/core": "^5.3.0",
|
||||
"@capacitor/haptics": "^5.0.6",
|
||||
"@capacitor/keyboard": "^5.0.6",
|
||||
"@capacitor/status-bar": "^5.0.6",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@jest/core": "^27.5.1",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.7.1",
|
||||
@@ -607,9 +607,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@capacitor/core": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
|
||||
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.3.0.tgz",
|
||||
"integrity": "sha512-mvhh1yJtcUTZ0hUUriBKKpxq47hn75bjxH3tYPRgAFu1z3gowCg+OtG4Rce3W5gr5fSfCjQgOSL0Vp7k9hPUWw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -1541,13 +1541,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
|
||||
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz",
|
||||
"integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.36.2"
|
||||
"playwright-core": "1.37.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -1634,15 +1634,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-kEtPtV6QegME8YgMjWrhS7KktItbhqOpAuK9aXypDdI/7bLU9iM/4DtnQGWY/DARBophk+XRBfNXcE62Bmi0dw==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.1.0.tgz",
|
||||
"integrity": "sha512-yIpL+CX02fy5zvFXwXcHZjjEILRm3aiONbucpfLIWPS7zcBAuucdROssartEa+D7E1JRko97ydxn1Ntdu4GoWg==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.10.0",
|
||||
"npm": ">=6.0.0"
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/react-output-target": {
|
||||
@@ -8194,9 +8194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.36.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
|
||||
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz",
|
||||
"integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -10788,9 +10788,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@capacitor/core": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
|
||||
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.3.0.tgz",
|
||||
"integrity": "sha512-mvhh1yJtcUTZ0hUUriBKKpxq47hn75bjxH3tYPRgAFu1z3gowCg+OtG4Rce3W5gr5fSfCjQgOSL0Vp7k9hPUWw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -11455,14 +11455,14 @@
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
|
||||
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz",
|
||||
"integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.36.2"
|
||||
"playwright-core": "1.37.1"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -11524,9 +11524,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-kEtPtV6QegME8YgMjWrhS7KktItbhqOpAuK9aXypDdI/7bLU9iM/4DtnQGWY/DARBophk+XRBfNXcE62Bmi0dw=="
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.1.0.tgz",
|
||||
"integrity": "sha512-yIpL+CX02fy5zvFXwXcHZjjEILRm3aiONbucpfLIWPS7zcBAuucdROssartEa+D7E1JRko97ydxn1Ntdu4GoWg=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.5.3",
|
||||
@@ -16332,9 +16332,9 @@
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.36.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
|
||||
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz",
|
||||
"integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.3",
|
||||
"version": "7.3.1",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,20 +31,20 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^3.4.0",
|
||||
"@stencil/core": "^4.1.0",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.2.2",
|
||||
"@capacitor/core": "^5.3.0",
|
||||
"@capacitor/haptics": "^5.0.6",
|
||||
"@capacitor/keyboard": "^5.0.6",
|
||||
"@capacitor/status-bar": "^5.0.6",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@jest/core": "^27.5.1",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.7.1",
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@@ -23,6 +23,7 @@ export interface ActionSheetButton<T = any> {
|
||||
icon?: string;
|
||||
cssClass?: string | string[];
|
||||
id?: string;
|
||||
htmlAttributes?: { [key: string]: any };
|
||||
handler?: () => boolean | void | Promise<boolean | void>;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
@@ -394,7 +394,13 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
||||
</div>
|
||||
)}
|
||||
{buttons.map((b) => (
|
||||
<button type="button" id={b.id} class={buttonClass(b)} onClick={() => this.buttonClick(b)}>
|
||||
<button
|
||||
{...b.htmlAttributes}
|
||||
type="button"
|
||||
id={b.id}
|
||||
class={buttonClass(b)}
|
||||
onClick={() => this.buttonClick(b)}
|
||||
>
|
||||
<span class="action-sheet-button-inner">
|
||||
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
|
||||
{b.text}
|
||||
@@ -406,7 +412,12 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
||||
|
||||
{cancelButton && (
|
||||
<div class="action-sheet-group action-sheet-group-cancel">
|
||||
<button type="button" class={buttonClass(cancelButton)} onClick={() => this.buttonClick(cancelButton)}>
|
||||
<button
|
||||
{...cancelButton.htmlAttributes}
|
||||
type="button"
|
||||
class={buttonClass(cancelButton)}
|
||||
onClick={() => this.buttonClick(cancelButton)}
|
||||
>
|
||||
<span class="action-sheet-button-inner">
|
||||
{cancelButton.icon && (
|
||||
<ion-icon icon={cancelButton.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />
|
||||
|
||||
@@ -10,16 +10,36 @@ const testAria = async (page: E2EPage, buttonID: string, expectedAriaLabelledBy:
|
||||
await button.click();
|
||||
await didPresent.next();
|
||||
|
||||
const alert = page.locator('ion-action-sheet');
|
||||
const actionSheet = page.locator('ion-action-sheet');
|
||||
|
||||
/**
|
||||
* expect().toHaveAttribute() can't check for a null value, so grab and check
|
||||
* the value manually instead.
|
||||
*/
|
||||
const ariaLabelledBy = await alert.getAttribute('aria-labelledby');
|
||||
const ariaLabelledBy = await actionSheet.getAttribute('aria-labelledby');
|
||||
|
||||
expect(ariaLabelledBy).toBe(expectedAriaLabelledBy);
|
||||
};
|
||||
|
||||
const testAriaButton = async (
|
||||
page: E2EPage,
|
||||
buttonID: string,
|
||||
expectedAriaLabelledBy: string,
|
||||
expectedAriaLabel: string
|
||||
) => {
|
||||
const didPresent = await page.spyOnEvent('ionActionSheetDidPresent');
|
||||
|
||||
const button = page.locator(`#${buttonID}`);
|
||||
await button.click();
|
||||
|
||||
await didPresent.next();
|
||||
|
||||
const actionSheetButton = page.locator('ion-action-sheet .action-sheet-button');
|
||||
|
||||
await expect(actionSheetButton).toHaveAttribute('aria-labelledby', expectedAriaLabelledBy);
|
||||
await expect(actionSheetButton).toHaveAttribute('aria-label', expectedAriaLabel);
|
||||
};
|
||||
|
||||
configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
|
||||
test.describe(title('action-sheet: a11y'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -52,5 +72,17 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
|
||||
test('should allow for manually specifying aria attributes', async ({ page }) => {
|
||||
await testAria(page, 'customAria', 'Custom title');
|
||||
});
|
||||
|
||||
test('should have aria-labelledby and aria-label added to the button when htmlAttributes is set', async ({
|
||||
page,
|
||||
}) => {
|
||||
await testAriaButton(page, 'ariaLabelButton', 'close-label', 'close button');
|
||||
});
|
||||
|
||||
test('should have aria-labelledby and aria-label added to the cancel button when htmlAttributes is set', async ({
|
||||
page,
|
||||
}) => {
|
||||
await testAriaButton(page, 'ariaLabelCancelButton', 'cancel-label', 'cancel button');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
<ion-button id="subHeaderOnly" expand="block" onclick="presentSubHeaderOnly()">Subheader Only</ion-button>
|
||||
<ion-button id="noHeaders" expand="block" onclick="presentNoHeaders()">No Headers</ion-button>
|
||||
<ion-button id="customAria" expand="block" onclick="presentCustomAria()">Custom Aria</ion-button>
|
||||
<ion-button id="ariaLabelButton" expand="block" onclick="presentAriaLabelButton()">Aria Label Button</ion-button>
|
||||
<ion-button id="ariaLabelCancelButton" expand="block" onclick="presentAriaLabelCancelButton()"
|
||||
>Aria Label Cancel Button</ion-button
|
||||
>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -63,6 +67,39 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function presentAriaLabelButton() {
|
||||
openActionSheet({
|
||||
header: 'Header',
|
||||
subHeader: 'Subtitle',
|
||||
buttons: [
|
||||
{
|
||||
text: 'Close',
|
||||
htmlAttributes: {
|
||||
'aria-label': 'close button',
|
||||
'aria-labelledby': 'close-label',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function presentAriaLabelCancelButton() {
|
||||
openActionSheet({
|
||||
header: 'Header',
|
||||
subHeader: 'Subtitle',
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
htmlAttributes: {
|
||||
'aria-label': 'cancel button',
|
||||
'aria-labelledby': 'cancel-label',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface AlertButton {
|
||||
role?: 'cancel' | 'destructive' | string;
|
||||
cssClass?: string | string[];
|
||||
id?: string;
|
||||
htmlAttributes?: { [key: string]: any };
|
||||
// TODO(FW-2832): type
|
||||
handler?: (value: any) => AlertButtonOverlayHandler | Promise<AlertButtonOverlayHandler>;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
}
|
||||
|
||||
.alert-tappable {
|
||||
height: $alert-ios-tappable-height;
|
||||
min-height: $alert-ios-tappable-height;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,12 +137,6 @@
|
||||
order: 0;
|
||||
|
||||
color: $alert-ios-radio-label-text-color;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,12 +190,6 @@
|
||||
flex: 1;
|
||||
|
||||
color: $alert-ios-checkbox-label-text-color;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// iOS Alert Checkbox Outer Circle: Unchecked
|
||||
|
||||
@@ -113,9 +113,7 @@
|
||||
.alert-tappable {
|
||||
position: relative;
|
||||
|
||||
height: $alert-md-tappable-height;
|
||||
|
||||
overflow: hidden;
|
||||
min-height: $alert-md-tappable-height;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,12 +128,6 @@
|
||||
color: $alert-md-radio-label-text-color;
|
||||
|
||||
font-size: $alert-md-radio-label-font-size;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Material Design Alert Radio Unchecked Circle
|
||||
@@ -203,12 +195,6 @@
|
||||
color: $alert-md-checkbox-label-text-color;
|
||||
|
||||
font-size: $alert-md-checkbox-label-font-size;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -152,6 +152,8 @@
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +186,7 @@
|
||||
text-align: start;
|
||||
appearance: none;
|
||||
|
||||
contain: strict;
|
||||
contain: content;
|
||||
}
|
||||
|
||||
.alert-button,
|
||||
|
||||
@@ -673,6 +673,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
<div class={alertButtonGroupClass}>
|
||||
{buttons.map((button) => (
|
||||
<button
|
||||
{...button.htmlAttributes}
|
||||
type="button"
|
||||
id={button.id}
|
||||
class={buttonClass(button)}
|
||||
|
||||
@@ -60,5 +60,21 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
|
||||
test('should allow for manually specifying aria attributes', async ({ page }) => {
|
||||
await testAria(page, 'customAria', 'Custom title', 'Custom description');
|
||||
});
|
||||
|
||||
test('should have aria-labelledby and aria-label added to the button when htmlAttributes is set', async ({
|
||||
page,
|
||||
}) => {
|
||||
const didPresent = await page.spyOnEvent('ionAlertDidPresent');
|
||||
|
||||
const button = page.locator('#ariaLabelButton');
|
||||
await button.click();
|
||||
|
||||
await didPresent.next();
|
||||
|
||||
const alertButton = page.locator('ion-alert .alert-button');
|
||||
|
||||
await expect(alertButton).toHaveAttribute('aria-labelledby', 'close-label');
|
||||
await expect(alertButton).toHaveAttribute('aria-label', 'close button');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<ion-button id="noHeaders" expand="block" onclick="presentNoHeaders()">No Headers</ion-button>
|
||||
<ion-button id="noMessage" expand="block" onclick="presentNoMessage()">No Message</ion-button>
|
||||
<ion-button id="customAria" expand="block" onclick="presentCustomAria()">Custom Aria</ion-button>
|
||||
<ion-button id="ariaLabelButton" expand="block" onclick="presentAriaLabelButton()">Aria Label Button</ion-button>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -76,6 +77,23 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function presentAriaLabelButton() {
|
||||
openAlert({
|
||||
header: 'Header',
|
||||
subHeader: 'Subtitle',
|
||||
message: 'This is an alert message with custom aria attributes passed to the button.',
|
||||
buttons: [
|
||||
{
|
||||
text: 'Close',
|
||||
htmlAttributes: {
|
||||
'aria-label': 'close button',
|
||||
'aria-labelledby': 'close-label',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -246,17 +246,12 @@
|
||||
type: 'radio',
|
||||
label: 'Radio 4',
|
||||
value: 'value4',
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
label: 'Radio 5',
|
||||
value: 'value5',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
label: 'Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 ',
|
||||
value: 'value6',
|
||||
label: 'Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 Radio 5 ',
|
||||
value: 'value5',
|
||||
},
|
||||
],
|
||||
buttons: [
|
||||
@@ -305,20 +300,13 @@
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox 4',
|
||||
value: 'value4',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox 5',
|
||||
value: 'value5',
|
||||
disabled: true,
|
||||
},
|
||||
|
||||
{
|
||||
type: 'checkbox',
|
||||
label:
|
||||
'Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6',
|
||||
value: 'value6',
|
||||
'Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5 Checkbox 5',
|
||||
value: 'value5',
|
||||
},
|
||||
],
|
||||
buttons: [
|
||||
|
||||
@@ -153,6 +153,15 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
*/
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* This is responsible for rendering a hidden native
|
||||
* button element inside the associated form. This allows
|
||||
* users to submit a form by pressing "Enter" when a text
|
||||
* field inside of the form is focused. The native button
|
||||
* rendered inside of `ion-button` is in the Shadow DOM
|
||||
* and therefore does not participate in form submission
|
||||
* which is why the following code is necessary.
|
||||
*/
|
||||
private renderHiddenButton() {
|
||||
const formEl = (this.formEl = this.findForm());
|
||||
if (formEl) {
|
||||
@@ -323,6 +332,13 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
|
||||
}
|
||||
|
||||
/**
|
||||
* We call renderHiddenButton in the render function to account
|
||||
* for any properties being set async. For example, changing the
|
||||
* "type" prop from "button" to "submit" after the component has
|
||||
* loaded would warrant the hidden button being added to the
|
||||
* associated form.
|
||||
*/
|
||||
{
|
||||
type !== 'button' && this.renderHiddenButton();
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
@@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
.header-collapse-condense ion-toolbar:first-of-type {
|
||||
padding-top: 7px;
|
||||
padding-top: 1px;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -532,7 +532,7 @@ export class Input implements ComponentInterface {
|
||||
* Clear the input if the control has not been previously cleared during focus.
|
||||
* Do not clear if the user hitting enter to submit a form.
|
||||
*/
|
||||
if (!this.didInputClearOnEdit && this.hasValue() && ev.key !== 'Enter') {
|
||||
if (!this.didInputClearOnEdit && this.hasValue() && ev.key !== 'Enter' && ev.key !== 'Tab') {
|
||||
this.value = '';
|
||||
this.emitInputChange(ev);
|
||||
}
|
||||
|
||||
57
core/src/components/input/test/basic/index.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Input - Basic</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(250px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Input - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default</h2>
|
||||
<ion-input value="hi@ionic.io" label="Email"></ion-input>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
43
core/src/components/input/test/clear-on-edit/input.e2e.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test, configs } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('input: clearOnEdit'), () => {
|
||||
test('should clear when typed into', async ({ page }) => {
|
||||
await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);
|
||||
|
||||
const ionInput = await page.spyOnEvent('ionInput');
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
await input.locator('input').type('h');
|
||||
|
||||
await ionInput.next();
|
||||
|
||||
await expect(input).toHaveJSProperty('value', 'h');
|
||||
});
|
||||
|
||||
test('should not clear when enter is pressed', async ({ page }) => {
|
||||
await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
await input.locator('input').focus();
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(input).toHaveJSProperty('value', 'abc');
|
||||
});
|
||||
|
||||
test('should not clear when tab is pressed', async ({ page }) => {
|
||||
await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
await input.locator('input').focus();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(input).toHaveJSProperty('value', 'abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import { configs, dragElementBy, test } from '@utils/test/playwright';
|
||||
|
||||
import { testSlidingItem } from '../test.utils';
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* item-sliding doesn't have mode-specific styling
|
||||
*/
|
||||
@@ -18,19 +17,12 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('item-sliding: basic'), () => {
|
||||
// mouse gesture is flaky on CI, skip for now
|
||||
test.fixme('should open when swiped', async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test('should open when swiped', async ({ page }) => {
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
const item = page.locator('#item2');
|
||||
|
||||
@@ -41,7 +33,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
await expect(item).toHaveScreenshot(screenshot(`item-sliding-gesture`));
|
||||
});
|
||||
|
||||
test.skip('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -2,6 +2,7 @@
|
||||
|
||||
// Item
|
||||
// --------------------------------------------------
|
||||
// TODO: FW-4955
|
||||
|
||||
:host {
|
||||
/**
|
||||
@@ -45,10 +46,10 @@
|
||||
*
|
||||
* @prop --ripple-color: Color of the item ripple effect
|
||||
*
|
||||
* @prop --highlight-height: The height of the highlight on the item
|
||||
* @prop --highlight-color-focused: The color of the highlight on the item when focused
|
||||
* @prop --highlight-color-valid: The color of the highlight on the item when valid
|
||||
* @prop --highlight-color-invalid: The color of the highlight on the item when invalid
|
||||
* @prop --highlight-height: The height of the highlight on the item. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-focused: The color of the highlight on the item when focused. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-valid: The color of the highlight on the item when valid. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-invalid: The color of the highlight on the item when invalid. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
*/
|
||||
--border-radius: 0px;
|
||||
--border-width: 0px;
|
||||
|
||||
64
core/src/components/menu-button/test/async/index.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Menu - Async</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<script type="module">
|
||||
import { menuController } from '../../../../dist/ionic/index.esm.js';
|
||||
window.menuController = menuController;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<div id="menu-container">
|
||||
<ion-menu content-id="main">
|
||||
<ion-content class="ion-padding"> Menu Content </ion-content>
|
||||
</ion-menu>
|
||||
</div>
|
||||
|
||||
<div class="ion-page" id="main">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Menu - Async</ion-title>
|
||||
<ion-buttons slot="start" id="buttons-container"></ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
Main Content
|
||||
<button onclick="trigger()" id="trigger">Add Menu To DOM</button>
|
||||
</ion-content>
|
||||
</div>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const buttons = document.querySelector('ion-buttons');
|
||||
const menuButton = document.createElement('ion-menu-button');
|
||||
const menu = document.querySelector('ion-menu');
|
||||
const menuContainer = document.querySelector('#menu-container');
|
||||
|
||||
let firstLoad = true;
|
||||
|
||||
// When the menu loads, immediately remove it from the DOM
|
||||
document.body.addEventListener('ionMenuChange', () => {
|
||||
if (firstLoad) {
|
||||
menuContainer.removeChild(menu);
|
||||
buttons.appendChild(menuButton);
|
||||
firstLoad = false;
|
||||
}
|
||||
});
|
||||
const trigger = () => {
|
||||
menuContainer.append(menu);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('menu button: async'), () => {
|
||||
test('menu button should be visible if menu is moved', async ({ page }) => {
|
||||
await page.goto(`/src/components/menu-button/test/async`, config);
|
||||
|
||||
const menu = page.locator('ion-menu');
|
||||
const menuButton = page.locator('ion-menu-button');
|
||||
const triggerButton = page.locator('#trigger');
|
||||
|
||||
await expect(menu).not.toBeAttached();
|
||||
await expect(menuButton).toBeHidden();
|
||||
|
||||
await triggerButton.click();
|
||||
|
||||
await expect(menu).toBeAttached();
|
||||
await expect(menuButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,7 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
private lastOnEnd = 0;
|
||||
private gesture?: Gesture;
|
||||
private blocker = GESTURE_CONTROLLER.createBlocker({ disableScroll: true });
|
||||
private didLoad = false;
|
||||
|
||||
isAnimating = false;
|
||||
width!: number;
|
||||
@@ -216,6 +217,7 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
|
||||
// register this menu with the app's menu controller
|
||||
menuController._register(this);
|
||||
this.menuChanged();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: document,
|
||||
@@ -237,10 +239,22 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
this.ionMenuChange.emit({ disabled: this.disabled, open: this._isOpen });
|
||||
this.didLoad = true;
|
||||
this.menuChanged();
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
private menuChanged() {
|
||||
/**
|
||||
* Inform dependent components such as ion-menu-button
|
||||
* that the menu is ready. Note that we only want to do this
|
||||
* once the menu has been rendered which is why we check for didLoad.
|
||||
*/
|
||||
if (this.didLoad) {
|
||||
this.ionMenuChange.emit({ disabled: this.disabled, open: this._isOpen });
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
/**
|
||||
* The menu should be closed when it is
|
||||
|
||||
@@ -73,7 +73,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalHeader = page.locator('#modal-header');
|
||||
await dragElementBy(modalHeader, page, 0, -500);
|
||||
await dragElementBy(modalHeader, page, 0, 30);
|
||||
|
||||
const modal = page.locator('ion-modal');
|
||||
expect(modal).not.toBe(null);
|
||||
|
||||
@@ -9,12 +9,7 @@ import { CardModalPage } from '../fixtures';
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('card modal - nav'), () => {
|
||||
let cardModalPage: CardModalPage;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
cardModalPage = new CardModalPage(page);
|
||||
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false', config);
|
||||
});
|
||||
@@ -33,7 +28,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
const content = page.locator('.page-two-content');
|
||||
|
||||
await dragElementBy(content, page, 1000, 0, 10);
|
||||
await dragElementBy(content, page, 370, 0, 10);
|
||||
|
||||
await ionNavDidChange.next();
|
||||
});
|
||||
@@ -47,7 +42,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await ionNavDidChange.next();
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content', true, 270);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const modal = page.locator('ion-modal');
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
|
||||
await dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await content.evaluate((el: HTMLElement) => (el.scrollTop = 500));
|
||||
|
||||
await dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
|
||||
|
||||
@@ -61,12 +61,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled', async ({ page }) => {
|
||||
const modal = await cardModalPage.openModalByTrigger('#card');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
const content = page.locator('ion-modal ion-content');
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
||||
@@ -76,12 +76,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await page.click('ion-button.replace');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
const content = page.locator('ion-modal ion-content');
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class CardModalPage {
|
||||
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
}
|
||||
async openModalByTrigger(selector: string) {
|
||||
await this.page.click(selector);
|
||||
await this.page.locator(selector).click();
|
||||
await this.ionModalDidPresent.next();
|
||||
|
||||
return this.page.locator('ion-modal');
|
||||
|
||||
@@ -129,7 +129,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange');
|
||||
const header = page.locator('.modal-sheet ion-header');
|
||||
|
||||
await dragElementBy(header, page, 0, 150);
|
||||
await dragElementBy(header, page, 0, 125);
|
||||
|
||||
await ionBreakpointDidChange.next();
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ export interface PickerButton {
|
||||
export interface PickerColumn {
|
||||
name: string;
|
||||
align?: string;
|
||||
/**
|
||||
* Changing this value allows the initial value of a picker column to be set.
|
||||
*/
|
||||
selectedIndex?: number;
|
||||
prevSelected?: number;
|
||||
prefix?: string;
|
||||
|
||||
@@ -77,13 +77,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
const centerX = box.x + box.width / 2;
|
||||
const centerY = box.y + box.height / 2;
|
||||
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 30, centerY);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* Do not use scrollToBottom() or other scrolling methods
|
||||
|
||||
@@ -10,15 +10,28 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
/**
|
||||
* The mouse events are flaky on CI
|
||||
*/
|
||||
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
|
||||
await page.setContent(`<ion-range value="20"></ion-range>`, config);
|
||||
test.fixme('should emit start/end events', async ({ page }) => {
|
||||
/**
|
||||
* Requires padding to prevent the knob from being clipped.
|
||||
* If it's clipped, then the value might be one off.
|
||||
* For example, if the knob is clipped on the right, then the value
|
||||
* will be 99 instead of 100.
|
||||
*/
|
||||
await page.setContent(
|
||||
`
|
||||
<div style="padding: 0 20px">
|
||||
<ion-range value="20"></ion-range>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
||||
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||
|
||||
const rangeEl = page.locator('ion-range');
|
||||
|
||||
await dragElementBy(rangeEl, page, testInfo.project.metadata.rtl ? -300 : 300, 0);
|
||||
await dragElementBy(rangeEl, page, 300, 0);
|
||||
await page.waitForChanges();
|
||||
|
||||
/**
|
||||
@@ -65,13 +78,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
const centerX = box.x + box.width / 2;
|
||||
const centerY = box.y + box.height / 2;
|
||||
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 30, centerY);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* Do not use scrollToBottom() or other scrolling methods
|
||||
@@ -118,13 +125,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
await rangeHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
||||
|
||||
await page.mouse.up();
|
||||
await dragElementBy(rangeHandle, page, 100, 0);
|
||||
|
||||
await ionChangeSpy.next();
|
||||
|
||||
@@ -169,11 +170,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||
const ionInputSpy = await page.spyOnEvent('ionInput');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
await rangeHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
||||
|
||||
await dragElementBy(rangeHandle, page, 100, 0, undefined, undefined, false);
|
||||
|
||||
await ionInputSpy.next();
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
import { pullToRefresh } from '../test.utils';
|
||||
|
||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('refresher: basic'), () => {
|
||||
test.describe(title('refresher: basic'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/refresher/test/basic', config);
|
||||
});
|
||||
|
||||
@@ -3,12 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
import { pullToRefresh } from '../test.utils';
|
||||
|
||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('refresher: custom scroll target'), () => {
|
||||
test.describe(title('refresher: custom scroll target'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/refresher/test/scroll-target', config);
|
||||
});
|
||||
@@ -19,7 +18,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
expect(await items.count()).toBe(30);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
@@ -39,7 +38,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
expect(await items.count()).toBe(30);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
||||
|
||||
const ev = await page.spyOnEvent('ionRefreshComplete');
|
||||
|
||||
await dragElementByYAxis(target, page, 400);
|
||||
await dragElementByYAxis(target, page, 320);
|
||||
await ev.next();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.describe(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/interactive`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/nested`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test, configs } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('textarea: clearOnEdit'), () => {
|
||||
test('should clear when typed into', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea value="abc" clear-on-edit="true" aria-label="textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionInput = await page.spyOnEvent('ionInput');
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
await textarea.locator('textarea').type('h');
|
||||
|
||||
await ionInput.next();
|
||||
|
||||
await expect(textarea).toHaveJSProperty('value', 'h');
|
||||
});
|
||||
|
||||
test('should not clear when tab is pressed', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`<ion-textarea value="abc" clear-on-edit="true" aria-label="textarea"></ion-textarea>`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
await textarea.locator('textarea').focus();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(textarea).toHaveJSProperty('value', 'abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -434,7 +434,7 @@ export class Textarea implements ComponentInterface {
|
||||
/**
|
||||
* Check if we need to clear the text input if clearOnEdit is enabled
|
||||
*/
|
||||
private checkClearOnEdit(ev: Event) {
|
||||
private checkClearOnEdit(ev: KeyboardEvent) {
|
||||
if (!this.clearOnEdit) {
|
||||
return;
|
||||
}
|
||||
@@ -442,7 +442,7 @@ export class Textarea implements ComponentInterface {
|
||||
* Clear the textarea if the control has not been previously cleared
|
||||
* during focus.
|
||||
*/
|
||||
if (!this.didTextareaClearOnEdit && this.hasValue()) {
|
||||
if (!this.didTextareaClearOnEdit && this.hasValue() && ev.key !== 'Tab') {
|
||||
this.value = '';
|
||||
this.emitInputChange(ev);
|
||||
}
|
||||
@@ -501,7 +501,7 @@ export class Textarea implements ComponentInterface {
|
||||
this.ionBlur.emit(ev);
|
||||
};
|
||||
|
||||
private onKeyDown = (ev: Event) => {
|
||||
private onKeyDown = (ev: KeyboardEvent) => {
|
||||
this.checkClearOnEdit(ev);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,3 +12,23 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('title: special characters'), () => {
|
||||
test('should not cut off characters', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">ÔÔÔ</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const title = page.locator('ion-title');
|
||||
await expect(title).toHaveScreenshot(screenshot('title-characters'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -49,8 +49,6 @@
|
||||
|
||||
min-width: 100%;
|
||||
|
||||
padding-bottom: 6px;
|
||||
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
Present Controller Toast
|
||||
</ion-button>
|
||||
|
||||
<ion-button id="aria-label-toast-trigger">Present Aria Label Toast</ion-button>
|
||||
<ion-toast
|
||||
id="aria-label-toast"
|
||||
trigger="aria-label-toast-trigger"
|
||||
header="Aria Label Toast Header"
|
||||
message="Aria Label Toast Message"
|
||||
></ion-toast>
|
||||
|
||||
<ion-button onclick="updateContent()">Update Inner Content</ion-button>
|
||||
</main>
|
||||
</ion-app>
|
||||
@@ -41,6 +49,17 @@
|
||||
const inlineToast = document.querySelector('#inline-toast');
|
||||
inlineToast.buttons = ['Ok'];
|
||||
|
||||
const ariaLabelToast = document.querySelector('#aria-label-toast');
|
||||
ariaLabelToast.buttons = [
|
||||
{
|
||||
icon: 'close',
|
||||
htmlAttributes: {
|
||||
'aria-label': 'close button',
|
||||
'aria-labelledby': 'close-label',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const presentToast = async (opts) => {
|
||||
const toast = await toastController.create(opts);
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
await page.goto(`/src/components/toast/test/a11y`, config);
|
||||
});
|
||||
test('should not have any axe violations with inline toasts', async ({ page }) => {
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
const didPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
|
||||
await page.click('#inline-toast-trigger');
|
||||
await ionToastDidPresent.next();
|
||||
await didPresent.next();
|
||||
|
||||
/**
|
||||
* IonToast overlays the entire screen, so
|
||||
@@ -25,10 +25,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
test('should not have any axe violations with controller toasts', async ({ page }) => {
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
const didPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
|
||||
await page.click('#controller-toast-trigger');
|
||||
await ionToastDidPresent.next();
|
||||
await didPresent.next();
|
||||
|
||||
/**
|
||||
* IonToast overlays the entire screen, so
|
||||
@@ -38,5 +38,19 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
const results = await new AxeBuilder({ page }).disableRules('color-contrast').analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should have aria-labelledby and aria-label added to the button when htmlAttributes is set', async ({
|
||||
page,
|
||||
}) => {
|
||||
const didPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
|
||||
await page.click('#aria-label-toast-trigger');
|
||||
await didPresent.next();
|
||||
|
||||
const toastButton = page.locator('#aria-label-toast .toast-button');
|
||||
|
||||
await expect(toastButton).toHaveAttribute('aria-labelledby', 'close-label');
|
||||
await expect(toastButton).toHaveAttribute('aria-label', 'close button');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,3 +129,18 @@ describe('toast: htmlAttributes', () => {
|
||||
await expect(toast.getAttribute('data-testid')).toBe('basic-toast');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast: button cancel', () => {
|
||||
it('should render the cancel button with part button-cancel', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Toast],
|
||||
template: () => <ion-toast buttons={[{ text: 'Cancel', role: 'cancel' }]}></ion-toast>,
|
||||
});
|
||||
|
||||
const toast = page.body.querySelector('ion-toast');
|
||||
|
||||
const buttonCancel = toast?.shadowRoot?.querySelector('.toast-button-cancel');
|
||||
|
||||
expect(buttonCancel.getAttribute('part')).toBe('button cancel');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface ToastButton {
|
||||
side?: 'start' | 'end';
|
||||
role?: 'cancel' | string;
|
||||
cssClass?: string | string[];
|
||||
htmlAttributes?: { [key: string]: any };
|
||||
handler?: () => boolean | void | Promise<boolean | void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import type { ToastButton, ToastPosition, ToastLayout } from './toast-interface'
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
*
|
||||
* @part button - Any button element that is displayed inside of the toast.
|
||||
* @part button cancel - Any button element with role "cancel" that is displayed inside of the toast.
|
||||
* @part container - The element that wraps all child elements.
|
||||
* @part header - The header text of the toast.
|
||||
* @part message - The body text of the toast.
|
||||
@@ -416,7 +417,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
return (
|
||||
<div class={buttonGroupsClasses}>
|
||||
{buttons.map((b) => (
|
||||
<button type="button" class={buttonClass(b)} tabIndex={0} onClick={() => this.buttonClick(b)} part="button">
|
||||
<button
|
||||
{...b.htmlAttributes}
|
||||
type="button"
|
||||
class={buttonClass(b)}
|
||||
tabIndex={0}
|
||||
onClick={() => this.buttonClick(b)}
|
||||
part={buttonPart(b)}
|
||||
>
|
||||
<div class="toast-button-inner">
|
||||
{b.icon && (
|
||||
<ion-icon
|
||||
@@ -579,5 +587,9 @@ const buttonClass = (button: ToastButton): CssClassMap => {
|
||||
};
|
||||
};
|
||||
|
||||
const buttonPart = (button: ToastButton): string => {
|
||||
return isCancel(button.role) ? 'button cancel' : 'button';
|
||||
};
|
||||
|
||||
type ToastPresentOptions = ToastPosition;
|
||||
type ToastDismissOptions = ToastPosition;
|
||||
|
||||
@@ -84,6 +84,16 @@
|
||||
// Toolbar: Large Title
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensure there is enough spacing between the large
|
||||
* title and any potential content in subsequent
|
||||
* toolbars. We do not add padding to the title
|
||||
* itself because it is absolutely positioned.
|
||||
*/
|
||||
:host(.toolbar-title-large) {
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
:host(.toolbar-title-large) .toolbar-container {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
@@ -94,4 +104,4 @@
|
||||
order: map-get($toolbar-order-ios, title-large);
|
||||
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* The drag gesture will not operate as expected when the element is dragged outside of the viewport because the Mouse class does not fire events outside of the viewport.
|
||||
*
|
||||
* For example, if the mouse is moved outside of the viewport, then the `mouseup` event will not fire.
|
||||
*
|
||||
* See https://playwright.dev/docs/api/class-mouse#mouse-move for more information.
|
||||
*/
|
||||
|
||||
import type { ElementHandle, Locator } from '@playwright/test';
|
||||
|
||||
import type { E2EPage } from './';
|
||||
@@ -8,7 +16,8 @@ export const dragElementBy = async (
|
||||
dragByX = 0,
|
||||
dragByY = 0,
|
||||
startXCoord?: number,
|
||||
startYCoord?: number
|
||||
startYCoord?: number,
|
||||
releaseDrag = true
|
||||
) => {
|
||||
const boundingBox = await el.boundingBox();
|
||||
|
||||
@@ -21,14 +30,17 @@ export const dragElementBy = async (
|
||||
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
const endX = startX + dragByX;
|
||||
const endY = startY + dragByY;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
|
||||
await page.mouse.down();
|
||||
|
||||
await page.mouse.move(endX, endY, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
// Drag the element.
|
||||
await moveElement(page, startX, startY, dragByX, dragByY);
|
||||
|
||||
if (releaseDrag) {
|
||||
await page.mouse.up();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,12 +67,83 @@ export const dragElementByYAxis = async (
|
||||
const startX = boundingBox.x + boundingBox.width / 2;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
|
||||
for (let i = 0; i < dragByY; i += 20) {
|
||||
await page.mouse.move(startX, startY + i);
|
||||
}
|
||||
// Drag the element.
|
||||
await moveElement(page, startX, startY, 0, dragByY);
|
||||
|
||||
await page.mouse.up();
|
||||
};
|
||||
|
||||
const validateDragByX = (startX: number, dragByX: number, viewportWidth: number) => {
|
||||
const endX = startX + dragByX;
|
||||
// The element is being dragged past the right of the viewport.
|
||||
if (endX > viewportWidth) {
|
||||
const recommendedDragByX = viewportWidth - startX - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the right of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||
);
|
||||
}
|
||||
|
||||
// The element is being dragged past the left of the viewport.
|
||||
if (endX < 0) {
|
||||
const recommendedDragByX = startX - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the left of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validateDragByY = (startY: number, dragByY: number, viewportHeight: number) => {
|
||||
const endY = startY + dragByY;
|
||||
// The element is being dragged past the bottom of the viewport.
|
||||
if (endY > viewportHeight) {
|
||||
const recommendedDragByY = viewportHeight - startY - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the bottom of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||
);
|
||||
}
|
||||
|
||||
// The element is being dragged past the top of the viewport.
|
||||
if (endY < 0) {
|
||||
const recommendedDragByY = startY - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the top of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const moveElement = async (page: E2EPage, startX: number, startY: number, dragByX = 0, dragByY = 0) => {
|
||||
const steps = 10;
|
||||
const browser = page.context().browser()!.browserType().name();
|
||||
|
||||
const viewport = page.viewportSize();
|
||||
if (viewport === null) {
|
||||
throw new Error(
|
||||
'Cannot get viewport size. See https://playwright.dev/docs/api/class-page#page-viewport-size for more information'
|
||||
);
|
||||
}
|
||||
|
||||
validateDragByX(startX, dragByX, viewport.width);
|
||||
validateDragByY(startY, dragByY, viewport.height);
|
||||
|
||||
const endX = startX + dragByX;
|
||||
const endY = startY + dragByY;
|
||||
|
||||
// Drag the element.
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
const middleX = startX + (endX - startX) * (i / steps);
|
||||
const middleY = startY + (endY - startY) * (i / steps);
|
||||
|
||||
await page.mouse.move(middleX, middleY);
|
||||
|
||||
// Safari needs to wait for a repaint to occur before moving the mouse again.
|
||||
if (browser === 'webkit' && i % 2 === 0) {
|
||||
// Repainting every 2 steps is enough to keep the drag gesture smooth.
|
||||
// Anything past 4 steps will cause the drag gesture to be flaky.
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.3.0](https://github.com/ionic-team/ionic-framework/compare/v7.2.4...v7.3.0) (2023-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||