Compare commits
29 Commits
sp/refacto
...
v7.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffe5307905 | ||
|
|
d3e82c99b8 | ||
|
|
eafa7b5dc6 | ||
|
|
92b13c298b | ||
|
|
e5a02f49e5 | ||
|
|
8d22874b79 | ||
|
|
8fa12fc888 | ||
|
|
84cf0f152f | ||
|
|
28bd4ba720 | ||
|
|
b6f43e0e72 | ||
|
|
87bc207dad | ||
|
|
8c6dc29133 | ||
|
|
3a263d14c3 | ||
|
|
0cb37430d3 | ||
|
|
e9fa30002b | ||
|
|
07dee74714 | ||
|
|
aa326a6eda | ||
|
|
1cf1eca002 | ||
|
|
eb19c289d6 | ||
|
|
a0e6ac6013 | ||
|
|
da55ab949e | ||
|
|
a0b3ef02af | ||
|
|
9500769f11 | ||
|
|
5992c619ad | ||
|
|
01857dd315 | ||
|
|
dbe6f390ef | ||
|
|
0c117cfe7f | ||
|
|
f14c440d63 | ||
|
|
bd1910ba69 |
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
45
CHANGELOG.md
@@ -3,6 +3,51 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
|
||||
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
|
||||
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
|
||||
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
|
||||
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
|
||||
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
|
||||
* **tab-button:** update event type interface on React ([#27950](https://github.com/ionic-team/ionic-framework/issues/27950)) ([1cf1eca](https://github.com/ionic-team/ionic-framework/commit/1cf1eca00239f3e98854466116e42f9a2e7b4590)), closes [#27949](https://github.com/ionic-team/ionic-framework/issues/27949)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
|
||||
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
|
||||
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
|
||||
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
|
||||
* **react, vue:** custom animations are used when going back ([#27895](https://github.com/ionic-team/ionic-framework/issues/27895)) ([824033f](https://github.com/ionic-team/ionic-framework/commit/824033f1d4b4a3e5d4c6a978a39e5bb1f33b5bb4)), closes [#27873](https://github.com/ionic-team/ionic-framework/issues/27873)
|
||||
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
|
||||
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
|
||||
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
|
||||
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
|
||||
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
|
||||
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
|
||||
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
|
||||
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
|
||||
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
|
||||
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
|
||||
|
||||
|
||||
|
||||
48
core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^3.4.0",
|
||||
@@ -15,14 +15,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.2.2",
|
||||
"@capacitor/core": "^5.2.3",
|
||||
"@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.0",
|
||||
"@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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.3.tgz",
|
||||
"integrity": "sha512-Q1zbgt3Mvldy7six2/GX54kTL0ozgnR37jeDUAXL/fOJBF4Iorr/8A0OjGEAnwEjpR1la7uFZUunESMFyMLhEQ==",
|
||||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.36.2"
|
||||
"playwright-core": "1.37.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -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.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"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.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.3.tgz",
|
||||
"integrity": "sha512-Q1zbgt3Mvldy7six2/GX54kTL0ozgnR37jeDUAXL/fOJBF4Iorr/8A0OjGEAnwEjpR1la7uFZUunESMFyMLhEQ==",
|
||||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.36.2"
|
||||
"playwright-core": "1.37.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -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.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -37,14 +37,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.2.2",
|
||||
"@capacitor/core": "^5.2.3",
|
||||
"@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.0",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.7.1",
|
||||
|
||||
@@ -62,14 +62,16 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Fail fast on CI */
|
||||
maxFailures: process.env.CI ? 1 : 0,
|
||||
/* Flaky test should be either addressed or disabled until we can address them */
|
||||
retries: 0,
|
||||
maxFailures: 0,
|
||||
retries: 2,
|
||||
reportSlowTests: null,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
reporter: [
|
||||
['html'],
|
||||
['github']
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
|
||||
6
core/src/components.d.ts
vendored
@@ -1277,9 +1277,6 @@ export namespace Components {
|
||||
* The shape of the input. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
"size"?: number;
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
@@ -5339,9 +5336,6 @@ declare namespace LocalJSX {
|
||||
* The shape of the input. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
"size"?: number;
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
|
||||
|
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 |
@@ -2,6 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Watch, Component, Element, Event, Host, Method, Prop, h, readTask } from '@stencil/core';
|
||||
import type { Gesture } from '@utils/gesture';
|
||||
import { createButtonActiveGesture } from '@utils/gesture/button-active';
|
||||
import { raf } from '@utils/helpers';
|
||||
import {
|
||||
BACKDROP,
|
||||
createDelegateController,
|
||||
@@ -318,25 +319,32 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
/**
|
||||
* Do not create gesture if:
|
||||
* 1. A gesture already exists
|
||||
* 2. App is running in MD mode
|
||||
* 3. A wrapper ref does not exist
|
||||
* Only create gesture if:
|
||||
* 1. A gesture does not already exist
|
||||
* 2. App is running in iOS mode
|
||||
* 3. A wrapper ref exists
|
||||
* 4. A group ref exists
|
||||
*/
|
||||
const { groupEl, wrapperEl } = this;
|
||||
if (this.gesture || getIonMode(this) === 'md' || !wrapperEl || !groupEl) {
|
||||
return;
|
||||
if (!this.gesture && getIonMode(this) === 'ios' && wrapperEl && groupEl) {
|
||||
readTask(() => {
|
||||
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
|
||||
if (!isScrollable) {
|
||||
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
|
||||
refEl.classList.contains('action-sheet-button')
|
||||
);
|
||||
this.gesture.enable(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readTask(() => {
|
||||
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
|
||||
if (!isScrollable) {
|
||||
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
|
||||
refEl.classList.contains('action-sheet-button')
|
||||
);
|
||||
this.gesture.enable(true);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* If action sheet was rendered with isOpen="true"
|
||||
* then we should open action sheet immediately.
|
||||
*/
|
||||
if (this.isOpen === true) {
|
||||
raf(() => this.present());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -30,5 +30,10 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
|
||||
|
||||
await expect(actionSheet).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-action-sheet is-open="true"></ion-action-sheet>', config);
|
||||
await expect(page.locator('ion-action-sheet')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Component, Element, Event, Host, Listen, Method, Prop, Watch, forceUpda
|
||||
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
|
||||
import type { Gesture } from '@utils/gesture';
|
||||
import { createButtonActiveGesture } from '@utils/gesture/button-active';
|
||||
import { raf } from '@utils/helpers';
|
||||
import {
|
||||
createDelegateController,
|
||||
createTriggerController,
|
||||
@@ -346,19 +347,25 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
/**
|
||||
* Do not create gesture if:
|
||||
* 1. A gesture already exists
|
||||
* 2. App is running in MD mode
|
||||
* 3. A wrapper ref does not exist
|
||||
* Only create gesture if:
|
||||
* 1. A gesture does not already exist
|
||||
* 2. App is running in iOS mode
|
||||
* 3. A wrapper ref exists
|
||||
*/
|
||||
if (this.gesture || getIonMode(this) === 'md' || !this.wrapperEl) {
|
||||
return;
|
||||
if (!this.gesture && getIonMode(this) === 'ios' && this.wrapperEl) {
|
||||
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
|
||||
refEl.classList.contains('alert-button')
|
||||
);
|
||||
this.gesture.enable(true);
|
||||
}
|
||||
|
||||
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
|
||||
refEl.classList.contains('alert-button')
|
||||
);
|
||||
this.gesture.enable(true);
|
||||
/**
|
||||
* If alert was rendered with isOpen="true"
|
||||
* then we should open alert immediately.
|
||||
*/
|
||||
if (this.isOpen === true) {
|
||||
raf(() => this.present());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,5 +29,10 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
|
||||
await ionAlertDidDismiss.next();
|
||||
await expect(alert).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-alert is-open="true"></ion-alert>', config);
|
||||
await expect(page.locator('ion-alert')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,19 +153,27 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
*/
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
connectedCallback(): void {
|
||||
// Allow form to be submitted through `ion-button`
|
||||
if (this.type !== 'button' && hasShadowDom(this.el)) {
|
||||
this.formEl = this.findForm();
|
||||
if (this.formEl) {
|
||||
// Create a hidden native button inside of the form
|
||||
this.formButtonEl = document.createElement('button');
|
||||
this.formButtonEl.type = this.type;
|
||||
this.formButtonEl.style.display = 'none';
|
||||
// Only submit if the button is not disabled.
|
||||
this.formButtonEl.disabled = this.disabled;
|
||||
this.formEl.appendChild(this.formButtonEl);
|
||||
private renderHiddenButton() {
|
||||
const formEl = (this.formEl = this.findForm());
|
||||
if (formEl) {
|
||||
const { formButtonEl } = this;
|
||||
|
||||
/**
|
||||
* If the form already has a rendered form button
|
||||
* then do not append a new one again.
|
||||
*/
|
||||
if (formButtonEl !== null && formEl.contains(formButtonEl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a hidden native button inside of the form
|
||||
const newFormButtonEl = (this.formButtonEl = document.createElement('button'));
|
||||
newFormButtonEl.type = this.type;
|
||||
newFormButtonEl.style.display = 'none';
|
||||
// Only submit if the button is not disabled.
|
||||
newFormButtonEl.disabled = this.disabled;
|
||||
|
||||
formEl.appendChild(newFormButtonEl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +322,11 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
if (fill == null) {
|
||||
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
|
||||
}
|
||||
|
||||
{
|
||||
type !== 'button' && this.renderHiddenButton();
|
||||
}
|
||||
|
||||
return (
|
||||
<Host
|
||||
onClick={this.handleClick}
|
||||
|
||||
@@ -130,6 +130,29 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(submitEvent).not.toHaveReceivedEvent();
|
||||
});
|
||||
|
||||
test('should submit the form by id when form is set async', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27952',
|
||||
});
|
||||
await page.setContent(
|
||||
`
|
||||
<form id="myForm"></form>
|
||||
<ion-button type="submit">Submit</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const submitEvent = await page.spyOnEvent('submit');
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await button.evaluate((el: HTMLIonButtonElement) => (el.form = 'myForm'));
|
||||
|
||||
await page.click('ion-button');
|
||||
|
||||
expect(submitEvent).toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('should throw a warning if the form cannot be found'), () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { Button } from '../../button';
|
||||
|
||||
describe('Button: Hidden Form Button', () => {
|
||||
it('should not add multiple buttons to the form', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Button],
|
||||
html: `
|
||||
<form id="my-form"></form>
|
||||
<ion-button form="my-form" type="submit">Submit</ion-button>
|
||||
`,
|
||||
});
|
||||
|
||||
const getButtons = () => {
|
||||
return page.body.querySelectorAll('form button');
|
||||
};
|
||||
|
||||
const form = page.body.querySelectorAll('form');
|
||||
const button = page.body.querySelector('ion-button');
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(getButtons().length).toEqual(1);
|
||||
|
||||
// Re-render the component
|
||||
button.color = 'danger';
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(getButtons().length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
@@ -122,6 +122,42 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
await expect(dateTarget).toContainText('May 10, 2023');
|
||||
});
|
||||
test('should set only month and year when only passing month and year', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
|
||||
<ion-datetime id="datetime" value="2022-01" presentation="month-year"></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
await expect(page.locator('#date-button')).toContainText('January 2022');
|
||||
await expect(page.locator('#time-button')).toBeHidden();
|
||||
});
|
||||
test('should set only year when passing only year', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
|
||||
<ion-datetime id="datetime" value="2022" presentation="year"></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
await expect(page.locator('#date-button')).toContainText('2022');
|
||||
await expect(page.locator('#time-button')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('datetime-button: locale'), () => {
|
||||
|
||||
@@ -138,7 +138,6 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
@Element() el!: HTMLIonDatetimeElement;
|
||||
|
||||
@State() isPresented = false;
|
||||
@State() isTimePopoverOpen = false;
|
||||
|
||||
/**
|
||||
@@ -883,21 +882,18 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const getChangedMonth = (parts: DatetimeParts): DatetimeParts | undefined => {
|
||||
const box = calendarBodyRef.getBoundingClientRect();
|
||||
const root = this.el!.shadowRoot!;
|
||||
|
||||
/**
|
||||
* Get the element that is in the center of the calendar body.
|
||||
* This will be an element inside of the active month.
|
||||
* If the current scroll position is all the way to the left
|
||||
* then we have scrolled to the previous month.
|
||||
* Otherwise, assume that we have scrolled to the next
|
||||
* month. We have a tolerance of 2px to account for
|
||||
* sub pixel rendering.
|
||||
*
|
||||
* Check below the next line ensures that we did not
|
||||
* swipe and abort (i.e. we swiped but we are still on the current month).
|
||||
*/
|
||||
const elementAtCenter = root.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2);
|
||||
/**
|
||||
* If there is no element then the
|
||||
* component may be re-rendering on a slow device.
|
||||
*/
|
||||
if (!elementAtCenter) return;
|
||||
|
||||
const month = elementAtCenter.closest('.calendar-month');
|
||||
if (!month) return;
|
||||
const month = calendarBodyRef.scrollLeft <= 2 ? startMonth : endMonth;
|
||||
|
||||
/**
|
||||
* The edge of the month must be lined up with
|
||||
@@ -2364,19 +2360,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
disabled,
|
||||
el,
|
||||
color,
|
||||
isPresented,
|
||||
readonly,
|
||||
showMonthAndYear,
|
||||
preferWheel,
|
||||
presentation,
|
||||
size,
|
||||
} = this;
|
||||
const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size } = this;
|
||||
const mode = getIonMode(this);
|
||||
const isMonthAndYearPresentation =
|
||||
presentation === 'year' || presentation === 'month' || presentation === 'month-year';
|
||||
@@ -2396,7 +2380,6 @@ export class Datetime implements ComponentInterface {
|
||||
class={{
|
||||
...createColorClasses(color, {
|
||||
[mode]: true,
|
||||
['datetime-presented']: isPresented,
|
||||
['datetime-readonly']: readonly,
|
||||
['datetime-disabled']: disabled,
|
||||
'show-month-and-year': shouldShowMonthAndYear,
|
||||
|
||||
@@ -301,21 +301,20 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
|
||||
<ion-datetime
|
||||
min="2022-01-15"
|
||||
value="2022-02-01"
|
||||
presentation="date"
|
||||
presentation="month-year"
|
||||
></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
const monthYearToggle = page.locator('ion-datetime .calendar-month-year');
|
||||
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
await monthYearToggle.click();
|
||||
await page.waitForChanges();
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
await monthColumnItems.nth(0).click(); // switch to January
|
||||
await page.waitForChanges();
|
||||
await ionChange.next();
|
||||
|
||||
await expect(datetime).toHaveJSProperty('value', '2022-01-15T00:00:00');
|
||||
});
|
||||
|
||||
@@ -34,13 +34,14 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
const activeDayButton = page.locator('.calendar-day-active');
|
||||
const monthYearButton = page.locator('.calendar-month-year');
|
||||
const monthColumn = page.locator('.month-column');
|
||||
const yearColumn = page.locator('.year-column');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.value = '2021-10-05'));
|
||||
|
||||
@@ -49,11 +50,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await page.waitForChanges();
|
||||
|
||||
// Select October 2021
|
||||
// The year will automatically switch to 2021 when selecting 10
|
||||
await monthColumn.locator('.picker-item[data-value="10"]').click();
|
||||
await page.waitForChanges();
|
||||
|
||||
await yearColumn.locator('.picker-item[data-value="2021"]').click();
|
||||
await page.waitForChanges();
|
||||
await ionChange.next();
|
||||
|
||||
// Close month/year picker
|
||||
await monthYearButton.click();
|
||||
|
||||
@@ -113,7 +113,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
/**
|
||||
* MM/DD/YYYY will return midnight in the user's timezone.
|
||||
*/
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
|
||||
const labelString = new Intl.DateTimeFormat(locale, {
|
||||
weekday: 'long',
|
||||
@@ -134,7 +134,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
* Used for the header in MD mode.
|
||||
*/
|
||||
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(
|
||||
date
|
||||
);
|
||||
@@ -147,7 +147,7 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
* Example: May 2021
|
||||
*/
|
||||
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
const date = getNormalizedDate(refParts);
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
|
||||
};
|
||||
|
||||
@@ -183,11 +183,25 @@ export const getYear = (locale: string, refParts: DatetimeParts) => {
|
||||
return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
|
||||
};
|
||||
|
||||
const getNormalizedDate = (refParts: DatetimeParts) => {
|
||||
/**
|
||||
* Given reference parts, return a JS Date object
|
||||
* with a normalized time.
|
||||
*/
|
||||
export const getNormalizedDate = (refParts: DatetimeParts) => {
|
||||
const timeString =
|
||||
refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
|
||||
|
||||
return new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
|
||||
/**
|
||||
* We use / notation here for the date
|
||||
* so we do not need to do extra work and pad values with zeroes.
|
||||
* Values such as YYYY-MM are still valid, so
|
||||
* we add fallback values so we still get
|
||||
* a valid date otherwise we will pass in a string
|
||||
* like "//2023". Some browsers, such as Chrome, will
|
||||
* account for this and still return a valid date. However,
|
||||
* this is not a consistent behavior across all browsers.
|
||||
*/
|
||||
return new Date(`${refParts.month ?? 1}/${refParts.day ?? 1}/${refParts.year ?? 2023}${timeString} GMT+0000`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 |
@@ -82,8 +82,6 @@
|
||||
:host(.input-fill-outline) .label-text-wrapper,
|
||||
:host(.input-fill-outline) .label-text-wrapper {
|
||||
position: relative;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -581,6 +581,13 @@
|
||||
@include transform-origin(start, top);
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
/**
|
||||
* The 2 ensures the label
|
||||
* remains on top of any browser
|
||||
* autofill background too.
|
||||
*/
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -268,9 +268,7 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@Prop() step?: string;
|
||||
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
// FW-4914 Remove this property in Ionic 8
|
||||
@Prop() size?: number;
|
||||
|
||||
/**
|
||||
|
||||
@@ -186,9 +186,6 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-slot-truncate`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('input: async label'), () => {
|
||||
test('input should re-render when label slot is added async', async ({ page }) => {
|
||||
await page.setContent(
|
||||
@@ -213,4 +210,27 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-async-label`));
|
||||
});
|
||||
});
|
||||
test.describe(title('input: floating/stacked label layering'), () => {
|
||||
test('label should not be covered by text field', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
|
||||
});
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
.custom-input .native-wrapper {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
<ion-input class="custom-input" label="My Label" label-placement="stacked"></ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
|
||||
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-layering`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -76,12 +76,19 @@ export class ItemSliding implements ComponentInterface {
|
||||
this.item = el.querySelector('ion-item');
|
||||
this.contentEl = findClosestIonContent(el);
|
||||
|
||||
await this.updateOptions();
|
||||
|
||||
/**
|
||||
* The MutationObserver needs to be added before we
|
||||
* call updateOptions below otherwise we may miss
|
||||
* ion-item-option elements that are added to the DOM
|
||||
* while updateOptions is running and before the MutationObserver
|
||||
* has been initialized.
|
||||
*/
|
||||
this.mutationObserver = watchForOptions<HTMLIonItemOptionElement>(el, 'ion-item-option', async () => {
|
||||
await this.updateOptions();
|
||||
});
|
||||
|
||||
await this.updateOptions();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el,
|
||||
gestureName: 'item-swipe',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,5 +23,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionLoadingDidDismiss.next();
|
||||
await expect(loading).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-loading is-open="true"></ion-loading>', config);
|
||||
await expect(page.locator('ion-loading')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,12 @@ export interface MoveSheetToBreakpointOptions {
|
||||
* `true` if the sheet can be transitioned and dismissed off the view.
|
||||
*/
|
||||
canDismiss?: boolean;
|
||||
|
||||
/**
|
||||
* If `true`, the sheet will animate to the breakpoint.
|
||||
* If `false`, the sheet will jump directly to the breakpoint.
|
||||
*/
|
||||
animated: boolean;
|
||||
}
|
||||
|
||||
export const createSheetGesture = (
|
||||
@@ -246,11 +252,17 @@ export const createSheetGesture = (
|
||||
breakpoint: closest,
|
||||
breakpointOffset: offset,
|
||||
canDismiss: canDismissBlocksGesture,
|
||||
|
||||
/**
|
||||
* The swipe is user-driven, so we should
|
||||
* always animate when the gesture ends.
|
||||
*/
|
||||
animated: true,
|
||||
});
|
||||
};
|
||||
|
||||
const moveSheetToBreakpoint = (options: MoveSheetToBreakpointOptions) => {
|
||||
const { breakpoint, canDismiss, breakpointOffset } = options;
|
||||
const { breakpoint, canDismiss, breakpointOffset, animated } = options;
|
||||
/**
|
||||
* canDismiss should only prevent snapping
|
||||
* when users are trying to dismiss. If canDismiss
|
||||
@@ -360,7 +372,7 @@ export const createSheetGesture = (
|
||||
},
|
||||
{ oneTimeCallback: true }
|
||||
)
|
||||
.progressEnd(1, 0, 500);
|
||||
.progressEnd(1, 0, animated ? 500 : 0);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -761,7 +761,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints } = this;
|
||||
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints, animated } = this;
|
||||
|
||||
if (currentBreakpoint === breakpoint) {
|
||||
return;
|
||||
@@ -772,6 +772,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
breakpoint,
|
||||
breakpointOffset: 1 - currentBreakpoint!,
|
||||
canDismiss: canDismiss !== undefined && canDismiss !== true && breakpoints![0] === 0,
|
||||
animated,
|
||||
});
|
||||
await this.sheetTransition;
|
||||
this.sheetTransition = undefined;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -20,5 +20,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionModalDidDismiss.next();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-modal is-open="true"></ion-modal>', config);
|
||||
await expect(page.locator('ion-modal')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import { VIEW_STATE_ATTACHED, VIEW_STATE_DESTROYED, VIEW_STATE_NEW, convertToVie
|
||||
export class Nav implements NavOutlet {
|
||||
private transInstr: TransitionInstruction[] = [];
|
||||
private sbAni?: Animation;
|
||||
private animationEnabled = true;
|
||||
private gestureOrAnimationInProgress = false;
|
||||
private useRouter = false;
|
||||
private isTransitioning = false;
|
||||
private destroyed = false;
|
||||
@@ -869,7 +869,36 @@ export class Nav implements NavOutlet {
|
||||
// or if it is a portal (modal, actionsheet, etc.)
|
||||
const opts = ti.opts!;
|
||||
|
||||
const progressCallback = opts.progressAnimation ? (ani: Animation | undefined) => (this.sbAni = ani) : undefined;
|
||||
const progressCallback = opts.progressAnimation
|
||||
? (ani: Animation | undefined) => {
|
||||
/**
|
||||
* Because this progress callback is called asynchronously
|
||||
* it is possible for the gesture to start and end before
|
||||
* the animation is ever set. In that scenario, we should
|
||||
* immediately call progressEnd so that the transition promise
|
||||
* resolves and the gesture does not get locked up.
|
||||
*/
|
||||
if (ani !== undefined && !this.gestureOrAnimationInProgress) {
|
||||
this.gestureOrAnimationInProgress = true;
|
||||
ani.onFinish(
|
||||
() => {
|
||||
this.gestureOrAnimationInProgress = false;
|
||||
},
|
||||
{ oneTimeCallback: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* Playing animation to beginning
|
||||
* with a duration of 0 prevents
|
||||
* any flickering when the animation
|
||||
* is later cleaned up.
|
||||
*/
|
||||
ani.progressEnd(0, 0, 0);
|
||||
} else {
|
||||
this.sbAni = ani;
|
||||
}
|
||||
}
|
||||
: undefined;
|
||||
const mode = getIonMode(this);
|
||||
const enteringEl = enteringView.element!;
|
||||
const leavingEl = leavingView && leavingView.element!;
|
||||
@@ -1008,15 +1037,16 @@ export class Nav implements NavOutlet {
|
||||
|
||||
private canStart(): boolean {
|
||||
return (
|
||||
!this.gestureOrAnimationInProgress &&
|
||||
!!this.swipeGesture &&
|
||||
!this.isTransitioning &&
|
||||
this.transInstr.length === 0 &&
|
||||
this.animationEnabled &&
|
||||
this.canGoBackSync()
|
||||
);
|
||||
}
|
||||
|
||||
private onStart() {
|
||||
this.gestureOrAnimationInProgress = true;
|
||||
this.pop({ direction: 'back', progressAnimation: true });
|
||||
}
|
||||
|
||||
@@ -1028,10 +1058,9 @@ export class Nav implements NavOutlet {
|
||||
|
||||
private onEnd(shouldComplete: boolean, stepValue: number, dur: number) {
|
||||
if (this.sbAni) {
|
||||
this.animationEnabled = false;
|
||||
this.sbAni.onFinish(
|
||||
() => {
|
||||
this.animationEnabled = true;
|
||||
this.gestureOrAnimationInProgress = false;
|
||||
},
|
||||
{ oneTimeCallback: true }
|
||||
);
|
||||
@@ -1055,6 +1084,8 @@ export class Nav implements NavOutlet {
|
||||
}
|
||||
|
||||
this.sbAni.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
|
||||
} else {
|
||||
this.gestureOrAnimationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
import { raf } from '@utils/helpers';
|
||||
import {
|
||||
createDelegateController,
|
||||
createTriggerController,
|
||||
@@ -199,6 +200,16 @@ export class Picker implements ComponentInterface, OverlayInterface {
|
||||
setOverlayId(this.el);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
/**
|
||||
* If picker was rendered with isOpen="true"
|
||||
* then we should open picker immediately.
|
||||
*/
|
||||
if (this.isOpen === true) {
|
||||
raf(() => this.present());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Present the picker overlay after it has been created.
|
||||
*/
|
||||
|
||||
@@ -23,5 +23,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionPickerDidDismiss.next();
|
||||
await expect(picker).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-picker is-open="true"></ion-picker>', config);
|
||||
await expect(page.locator('ion-picker')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
11
core/src/components/popover/test/is-open/popover.e2e.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('popover: isOpen'), () => {
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-popover is-open="true"></ion-popover>', config);
|
||||
await expect(page.locator('ion-popover')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "./select-popover";
|
||||
@import "./select-popover.md.vars";
|
||||
|
||||
ion-list ion-radio {
|
||||
ion-list ion-radio::part(container) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Element, Component, Host, Prop, h } from '@stencil/core';
|
||||
import { Element, Component, Host, Prop, h, forceUpdate } from '@stencil/core';
|
||||
import { safeCall } from '@utils/overlays';
|
||||
import { getClassMap } from '@utils/theme';
|
||||
|
||||
@@ -116,19 +116,28 @@ export class SelectPopover implements ComponentInterface {
|
||||
|
||||
renderCheckboxOptions(options: SelectPopoverOption[]) {
|
||||
return options.map((option) => (
|
||||
<ion-item class={getClassMap(option.cssClass)}>
|
||||
<ion-item
|
||||
class={{
|
||||
// TODO FW-4784
|
||||
'item-checkbox-checked': option.checked,
|
||||
...getClassMap(option.cssClass),
|
||||
}}
|
||||
>
|
||||
<ion-checkbox
|
||||
slot="start"
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
checked={option.checked}
|
||||
legacy={true}
|
||||
justify="start"
|
||||
labelPlacement="end"
|
||||
onIonChange={(ev) => {
|
||||
this.setChecked(ev);
|
||||
this.callOptionHandler(ev);
|
||||
// TODO FW-4784
|
||||
forceUpdate(this);
|
||||
}}
|
||||
></ion-checkbox>
|
||||
<ion-label>{option.text}</ion-label>
|
||||
>
|
||||
{option.text}
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
));
|
||||
}
|
||||
@@ -139,12 +148,16 @@ export class SelectPopover implements ComponentInterface {
|
||||
return (
|
||||
<ion-radio-group value={checked} onIonChange={(ev) => this.callOptionHandler(ev)}>
|
||||
{options.map((option) => (
|
||||
<ion-item class={getClassMap(option.cssClass)}>
|
||||
<ion-label>{option.text}</ion-label>
|
||||
<ion-item
|
||||
class={{
|
||||
// TODO FW-4784
|
||||
'item-radio-checked': option.value === checked,
|
||||
...getClassMap(option.cssClass),
|
||||
}}
|
||||
>
|
||||
<ion-radio
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
legacy={true}
|
||||
onClick={() => this.dismissParentPopover()}
|
||||
onKeyUp={(ev) => {
|
||||
if (ev.key === ' ') {
|
||||
@@ -156,7 +169,9 @@ export class SelectPopover implements ComponentInterface {
|
||||
this.dismissParentPopover();
|
||||
}
|
||||
}}
|
||||
></ion-radio>
|
||||
>
|
||||
{option.text}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
))}
|
||||
</ion-radio-group>
|
||||
|
||||
@@ -81,11 +81,11 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
selectPopoverPage = new SelectPopoverPage(page);
|
||||
});
|
||||
test('should not have visual regressions with single selection', async () => {
|
||||
await selectPopoverPage.setup(config, options, false);
|
||||
await selectPopoverPage.setup(config, checkedOptions, false);
|
||||
await selectPopoverPage.screenshot(screenshot, 'select-popover-diff');
|
||||
});
|
||||
test('should not have visual regressions with multiple selection', async () => {
|
||||
await selectPopoverPage.setup(config, options, true);
|
||||
await selectPopoverPage.setup(config, checkedOptions, true);
|
||||
await selectPopoverPage.screenshot(screenshot, 'select-popover-multiple-diff');
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -302,4 +302,27 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
await expect(page.locator('.container')).toHaveScreenshot(screenshot(`textarea-multi-line-sizing`));
|
||||
});
|
||||
});
|
||||
test.describe(title('textarea: floating/stacked label layering'), () => {
|
||||
test('label should not be covered by text field', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
|
||||
});
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
.custom-textarea .native-wrapper {
|
||||
background: pink;
|
||||
}
|
||||
</style>
|
||||
<ion-textarea class="custom-textarea" label="My Label" label-placement="stacked"></ion-textarea>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const textarea = page.locator('ion-textarea');
|
||||
|
||||
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-label-layering`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -82,8 +82,6 @@
|
||||
:host(.textarea-fill-outline) .label-text-wrapper,
|
||||
:host(.textarea-fill-outline) .label-text-wrapper {
|
||||
position: relative;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -629,6 +629,13 @@
|
||||
@include padding(0px);
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
/**
|
||||
* The 2 ensures the label
|
||||
* remains on top of any browser
|
||||
* autofill background too.
|
||||
*/
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -24,5 +24,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionToastDidDismiss.next();
|
||||
await expect(toast).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open if isOpen is true on load', async ({ page }) => {
|
||||
await page.setContent('<ion-toast is-open="true"></ion-toast>', config);
|
||||
await expect(page.locator('ion-toast')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { State, Watch, Component, Element, Event, h, Host, Method, Prop } from '@stencil/core';
|
||||
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
|
||||
import { raf } from '@utils/helpers';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
import {
|
||||
createDelegateController,
|
||||
@@ -253,6 +254,16 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
setOverlayId(this.el);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
/**
|
||||
* If toast was rendered with isOpen="true"
|
||||
* then we should open toast immediately.
|
||||
*/
|
||||
if (this.isOpen === true) {
|
||||
raf(() => this.present());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Present the toast overlay after it has been created.
|
||||
*/
|
||||
|
||||
@@ -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,7 +1,13 @@
|
||||
import { doc } from '@utils/browser';
|
||||
|
||||
import type { Config } from '../../interface';
|
||||
import { now, pointerCoord } from '../helpers';
|
||||
|
||||
export const startTapClick = (config: Config) => {
|
||||
if (doc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lastTouch = -MOUSE_WAIT * 10;
|
||||
let lastActivated = 0;
|
||||
|
||||
@@ -143,7 +149,6 @@ export const startTapClick = (config: Config) => {
|
||||
}
|
||||
};
|
||||
|
||||
const doc = document;
|
||||
doc.addEventListener('ionGestureCaptured', cancelActive);
|
||||
|
||||
doc.addEventListener('touchstart', onTouchStart, true);
|
||||
|
||||
@@ -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.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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
4
docs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/docs",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.4",
|
||||
"description": "Pre-packaged API documentation for the Ionic docs.",
|
||||
"main": "core.json",
|
||||
"types": "core.d.ts",
|
||||
|
||||