mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb7436f051 | ||
|
|
f1dc0b2b06 | ||
|
|
0ded8ba19f | ||
|
|
b09caeb053 | ||
|
|
42c9db52a7 |
2
.github/workflows/assign-issues.yml
vendored
2
.github/workflows/assign-issues.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
- name: 'Auto-assign issue'
|
||||
uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0
|
||||
with:
|
||||
assignees: brandyscarney, ShaneK
|
||||
assignees: brandyscarney, thetaPC, ShaneK
|
||||
numOfAssignee: 1
|
||||
allowSelfAssign: false
|
||||
|
||||
31
.github/workflows/conventional-commit.yml
vendored
31
.github/workflows/conventional-commit.yml
vendored
@@ -9,35 +9,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate PR title
|
||||
if: |
|
||||
!contains(github.event.pull_request.title, 'release') &&
|
||||
!contains(github.event.pull_request.title, 'chore')
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Configure that a scope must always be provided.
|
||||
requireScope: true
|
||||
# Configure allowed commit types
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
revert
|
||||
release
|
||||
chore
|
||||
# Configure additional validation for the subject based on a regex.
|
||||
# This example ensures the subject doesn't start with an uppercase character.
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
# If `subjectPattern` is configured, you can use this property to
|
||||
# override the default error message that is shown when the pattern
|
||||
# doesn't match. The variables `subject` and `title` can be used
|
||||
# If `subjectPattern` is configured, you can use this property to
|
||||
# override the default error message that is shown when the pattern
|
||||
# doesn't match. The variables `subject` and `title` can be used
|
||||
# within the message.
|
||||
subjectPatternError: |
|
||||
The subject "{subject}" found in the pull request title "{title}" didn't match the configured pattern. Please ensure that the subject doesn't start with an uppercase character.
|
||||
# If the PR contains one of these newline-delimited labels, the
|
||||
# validation is skipped. If you want to rerun the validation when
|
||||
# labels change, you might want to use the `labeled` and `unlabeled`
|
||||
# event triggers in your workflow.
|
||||
ignoreLabels: |
|
||||
release
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -3,65 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package ionic-framework
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
### Dependencies
|
||||
|
||||
* **stencil:** update to version 4.36.2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input-otp:** improve autofill detection and invalid character handling ([#30541](https://github.com/ionic-team/ionic-framework/issues/30541)) ([8b4023d](https://github.com/ionic-team/ionic-framework/commit/8b4023d520212c254395a5be6d3a76dcbee6f2da)), closes [#30459](https://github.com/ionic-team/ionic-framework/issues/30459)
|
||||
* **input:** prevent layout shift when hiding password toggle ([#30533](https://github.com/ionic-team/ionic-framework/issues/30533)) ([f1defba](https://github.com/ionic-team/ionic-framework/commit/f1defba2acb417c6f243b2902923d85efbb6f879)), closes [#29562](https://github.com/ionic-team/ionic-framework/issues/29562)
|
||||
* **item:** allow nested content to be conditionally interactive ([#30519](https://github.com/ionic-team/ionic-framework/issues/30519)) ([3f730ab](https://github.com/ionic-team/ionic-framework/commit/3f730ab1d77be54d1faf14168eee9e9dc41002d6)), closes [#29763](https://github.com/ionic-team/ionic-framework/issues/29763)
|
||||
* **modal:** dismiss child modals when parent is dismissed ([#30540](https://github.com/ionic-team/ionic-framework/issues/30540)) ([9b0099f](https://github.com/ionic-team/ionic-framework/commit/9b0099f462fda6d40b49dde1a1c97afbbbee2287)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389)
|
||||
* **modal:** dismiss modal when parent element is removed from DOM ([#30544](https://github.com/ionic-team/ionic-framework/issues/30544)) ([850338c](https://github.com/ionic-team/ionic-framework/commit/850338cbd5c76addbc2cc3068b93071dea14c0af)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389)
|
||||
* **modal:** improve card modal background transition from portrait to landscape ([#30551](https://github.com/ionic-team/ionic-framework/issues/30551)) ([d37b9b8](https://github.com/ionic-team/ionic-framework/commit/d37b9b8e468b7b2c9cda8b27fe7019bb905ad2bf))
|
||||
* **segment-view:** scroll to correct content when height is not set ([#30547](https://github.com/ionic-team/ionic-framework/issues/30547)) ([d14311f](https://github.com/ionic-team/ionic-framework/commit/d14311fb65ae3de7ba7578791ce1ea44f186c413)), closes [#30543](https://github.com/ionic-team/ionic-framework/issues/30543)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** support iOS card view transitions for viewport changes ([#30520](https://github.com/ionic-team/ionic-framework/issues/30520)) ([0fd9e82](https://github.com/ionic-team/ionic-framework/commit/0fd9e824508333a53175d7da5f681fc3126a2394)), closes [#30296](https://github.com/ionic-team/ionic-framework/issues/30296)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** update schematics to support Angular's latest build system ([#30525](https://github.com/ionic-team/ionic-framework/issues/30525)) ([08e3e7a](https://github.com/ionic-team/ionic-framework/commit/08e3e7ab5165baea668571af9845933b5befeb46)), closes [ionic-team/ionic-docs#2091](https://github.com/ionic-team/ionic-docs/issues/2091)
|
||||
* **modal:** add conditional tabIndex for handle cycling ([#30510](https://github.com/ionic-team/ionic-framework/issues/30510)) ([ee47660](https://github.com/ionic-team/ionic-framework/commit/ee47660745428e04c78cfef0555f3c5788959a8c))
|
||||
* **select:** focus the correct selected item in an action sheet interface with a header ([#30481](https://github.com/ionic-team/ionic-framework/issues/30481)) ([80a111c](https://github.com/ionic-team/ionic-framework/commit/80a111cffac70e831eb57e827301370163ef4e2a)), closes [#30480](https://github.com/ionic-team/ionic-framework/issues/30480)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
|
||||
|
||||
@@ -3,64 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
### Dependencies
|
||||
|
||||
* **stencil:** update to version 4.36.2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input-otp:** improve autofill detection and invalid character handling ([#30541](https://github.com/ionic-team/ionic-framework/issues/30541)) ([8b4023d](https://github.com/ionic-team/ionic-framework/commit/8b4023d520212c254395a5be6d3a76dcbee6f2da)), closes [#30459](https://github.com/ionic-team/ionic-framework/issues/30459)
|
||||
* **input:** prevent layout shift when hiding password toggle ([#30533](https://github.com/ionic-team/ionic-framework/issues/30533)) ([f1defba](https://github.com/ionic-team/ionic-framework/commit/f1defba2acb417c6f243b2902923d85efbb6f879)), closes [#29562](https://github.com/ionic-team/ionic-framework/issues/29562)
|
||||
* **item:** allow nested content to be conditionally interactive ([#30519](https://github.com/ionic-team/ionic-framework/issues/30519)) ([3f730ab](https://github.com/ionic-team/ionic-framework/commit/3f730ab1d77be54d1faf14168eee9e9dc41002d6)), closes [#29763](https://github.com/ionic-team/ionic-framework/issues/29763)
|
||||
* **modal:** dismiss child modals when parent is dismissed ([#30540](https://github.com/ionic-team/ionic-framework/issues/30540)) ([9b0099f](https://github.com/ionic-team/ionic-framework/commit/9b0099f462fda6d40b49dde1a1c97afbbbee2287)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389)
|
||||
* **modal:** dismiss modal when parent element is removed from DOM ([#30544](https://github.com/ionic-team/ionic-framework/issues/30544)) ([850338c](https://github.com/ionic-team/ionic-framework/commit/850338cbd5c76addbc2cc3068b93071dea14c0af)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389)
|
||||
* **modal:** improve card modal background transition from portrait to landscape ([#30551](https://github.com/ionic-team/ionic-framework/issues/30551)) ([d37b9b8](https://github.com/ionic-team/ionic-framework/commit/d37b9b8e468b7b2c9cda8b27fe7019bb905ad2bf))
|
||||
* **segment-view:** scroll to correct content when height is not set ([#30547](https://github.com/ionic-team/ionic-framework/issues/30547)) ([d14311f](https://github.com/ionic-team/ionic-framework/commit/d14311fb65ae3de7ba7578791ce1ea44f186c413)), closes [#30543](https://github.com/ionic-team/ionic-framework/issues/30543)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** support iOS card view transitions for viewport changes ([#30520](https://github.com/ionic-team/ionic-framework/issues/30520)) ([0fd9e82](https://github.com/ionic-team/ionic-framework/commit/0fd9e824508333a53175d7da5f681fc3126a2394)), closes [#30296](https://github.com/ionic-team/ionic-framework/issues/30296)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** add conditional tabIndex for handle cycling ([#30510](https://github.com/ionic-team/ionic-framework/issues/30510)) ([ee47660](https://github.com/ionic-team/ionic-framework/commit/ee47660745428e04c78cfef0555f3c5788959a8c))
|
||||
* **select:** focus the correct selected item in an action sheet interface with a header ([#30481](https://github.com/ionic-team/ionic-framework/issues/30481)) ([80a111c](https://github.com/ionic-team/ionic-framework/commit/80a111cffac70e831eb57e827301370163ef4e2a)), closes [#30480](https://github.com/ionic-team/ionic-framework/issues/30480)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Get Playwright
|
||||
FROM mcr.microsoft.com/playwright:v1.54.1
|
||||
FROM mcr.microsoft.com/playwright:v1.53.1
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /ionic
|
||||
|
||||
64
core/package-lock.json
generated
64
core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.33.1",
|
||||
@@ -22,7 +22,7 @@
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@playwright/test": "^1.53.2",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.10.0",
|
||||
@@ -663,9 +663,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@capacitor/core": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.2.tgz",
|
||||
"integrity": "sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==",
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.0.tgz",
|
||||
"integrity": "sha512-P6NnjoHyobZgTjynlZSn27d0SUj6j38inlNxFnKZr9qwU7/r6+0Sg2nWkGkIH/pMmXHsvGD8zVe6KUq1UncIjA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -1715,12 +1715,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
|
||||
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz",
|
||||
"integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.54.1"
|
||||
"playwright": "1.53.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -8592,12 +8592,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
|
||||
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
|
||||
"integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.54.1"
|
||||
"playwright-core": "1.53.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -8610,9 +8610,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
|
||||
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
|
||||
"integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -11101,9 +11101,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@capacitor/core": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.2.tgz",
|
||||
"integrity": "sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==",
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.0.tgz",
|
||||
"integrity": "sha512-P6NnjoHyobZgTjynlZSn27d0SUj6j38inlNxFnKZr9qwU7/r6+0Sg2nWkGkIH/pMmXHsvGD8zVe6KUq1UncIjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -11862,12 +11862,12 @@
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
|
||||
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz",
|
||||
"integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright": "1.54.1"
|
||||
"playwright": "1.53.2"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -16811,19 +16811,19 @@
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
|
||||
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
|
||||
"integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.54.1"
|
||||
"playwright-core": "1.53.2"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
|
||||
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
|
||||
"integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
@@ -18337,4 +18337,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@playwright/test": "^1.53.2",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.10.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Infinite Scroll - Top</title>
|
||||
<title>Infinite Scroll - Basic</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
@@ -18,7 +18,7 @@
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Infinite Scroll - Top</ion-title>
|
||||
<ion-title>Infinite Scroll - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
</ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
<div id="list"></div>
|
||||
|
||||
<button onclick="toggleInfiniteScroll()" class="expand">Toggle InfiniteScroll</button>
|
||||
|
||||
<ion-list id="list"></ion-list>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
@@ -46,26 +46,17 @@
|
||||
console.log('Loading data...');
|
||||
await wait(500);
|
||||
infiniteScroll.complete();
|
||||
appendItems(true);
|
||||
appendItems();
|
||||
// Custom event consumed in the e2e tests
|
||||
window.dispatchEvent(new CustomEvent('ionInfiniteComplete'));
|
||||
|
||||
console.log('Done');
|
||||
});
|
||||
|
||||
function appendItems(newItems = false) {
|
||||
const randomColor =
|
||||
'#' +
|
||||
Math.floor(Math.random() * 16777215)
|
||||
.toString(16)
|
||||
.padStart(6, '0');
|
||||
|
||||
function appendItems() {
|
||||
for (var i = 0; i < 30; i++) {
|
||||
const el = document.createElement('ion-item');
|
||||
el.textContent = `Item ${1 + i}`;
|
||||
if (newItems) {
|
||||
el.style.borderLeft = `4px solid ${randomColor}`;
|
||||
}
|
||||
el.textContent = `${1 + i}`;
|
||||
list.prepend(el);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ export class InputOTP implements ComponentInterface {
|
||||
|
||||
@State() private inputValues: string[] = [];
|
||||
@State() hasFocus = false;
|
||||
@State() private previousInputValues: string[] = [];
|
||||
|
||||
/**
|
||||
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
||||
@@ -337,7 +336,6 @@ export class InputOTP implements ComponentInterface {
|
||||
});
|
||||
// Update the value without emitting events
|
||||
this.value = this.inputValues.join('');
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,12 +525,19 @@ export class InputOTP implements ComponentInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles keyboard navigation for the OTP component.
|
||||
* Handles keyboard navigation and input for the OTP component.
|
||||
*
|
||||
* Navigation:
|
||||
* - Backspace: Clears current input and moves to previous box if empty
|
||||
* - Arrow Left/Right: Moves focus between input boxes
|
||||
* - Tab: Allows normal tab navigation between components
|
||||
*
|
||||
* Input Behavior:
|
||||
* - Validates input against the allowed pattern
|
||||
* - When entering a key in a filled box:
|
||||
* - Shifts existing values right if there is room
|
||||
* - Updates the value of the input group
|
||||
* - Prevents default behavior to avoid automatic focus shift
|
||||
*/
|
||||
private onKeyDown = (index: number) => (event: KeyboardEvent) => {
|
||||
const { length } = this;
|
||||
@@ -590,32 +595,34 @@ export class InputOTP implements ComponentInterface {
|
||||
// Let all tab events proceed normally
|
||||
return;
|
||||
}
|
||||
|
||||
// If the input box contains a value and the key being
|
||||
// entered is a valid key for the input box update the value
|
||||
// and shift the values to the right if there is room.
|
||||
if (this.inputValues[index] && this.validKeyPattern.test(event.key)) {
|
||||
if (!this.inputValues[length - 1]) {
|
||||
for (let i = length - 1; i > index; i--) {
|
||||
this.inputValues[i] = this.inputValues[i - 1];
|
||||
this.inputRefs[i].value = this.inputValues[i] || '';
|
||||
}
|
||||
}
|
||||
this.inputValues[index] = event.key;
|
||||
this.inputRefs[index].value = event.key;
|
||||
this.updateValue(event);
|
||||
|
||||
// Prevent default to avoid the browser from
|
||||
// automatically moving the focus to the next input
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes all input scenarios for each input box.
|
||||
*
|
||||
* This function manages:
|
||||
* 1. Autofill handling
|
||||
* 2. Input validation
|
||||
* 3. Full selection replacement or typing in an empty box
|
||||
* 4. Inserting in the middle with available space (shifting)
|
||||
* 5. Single character replacement
|
||||
*/
|
||||
private onInput = (index: number) => (event: InputEvent) => {
|
||||
const { length, validKeyPattern } = this;
|
||||
const input = event.target as HTMLInputElement;
|
||||
const value = input.value;
|
||||
const previousValue = this.previousInputValues[index] || '';
|
||||
const value = (event.target as HTMLInputElement).value;
|
||||
|
||||
// 1. Autofill handling
|
||||
// If the length of the value increases by more than 1 from the previous
|
||||
// value, treat this as autofill. This is to prevent the case where the
|
||||
// user is typing a single character into an input box containing a value
|
||||
// as that will trigger this function with a value length of 2 characters.
|
||||
const isAutofill = value.length - previousValue.length > 1;
|
||||
if (isAutofill) {
|
||||
// Distribute valid characters across input boxes
|
||||
// If the value is longer than 1 character (autofill), split it into
|
||||
// characters and filter out invalid ones
|
||||
if (value.length > 1) {
|
||||
const validChars = value
|
||||
.split('')
|
||||
.filter((char) => validKeyPattern.test(char))
|
||||
@@ -632,10 +639,8 @@ export class InputOTP implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
this.inputValues[i] = validChars[i] || '';
|
||||
this.inputRefs[i].value = validChars[i] || '';
|
||||
}
|
||||
// Update the value of the input group and emit the input change event
|
||||
this.value = validChars.join('');
|
||||
this.updateValue(event);
|
||||
|
||||
// Focus the first empty input box or the last input box if all boxes
|
||||
@@ -646,85 +651,23 @@ export class InputOTP implements ComponentInterface {
|
||||
this.inputRefs[nextIndex]?.focus();
|
||||
}, 20);
|
||||
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Input validation
|
||||
// If the character entered is invalid (does not match the pattern),
|
||||
// restore the previous value and exit
|
||||
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
||||
input.value = this.inputValues[index] || '';
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
// Only allow input if it matches the pattern
|
||||
if (value.length > 0 && !validKeyPattern.test(value)) {
|
||||
this.inputRefs[index].value = '';
|
||||
this.inputValues[index] = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Full selection replacement or typing in an empty box
|
||||
// If the user selects all text in the input box and types, or if the
|
||||
// input box is empty, replace only this input box. If the box is empty,
|
||||
// move to the next box, otherwise stay focused on this box.
|
||||
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
||||
const isEmpty = !this.inputValues[index];
|
||||
if (isAllSelected || isEmpty) {
|
||||
this.inputValues[index] = value;
|
||||
input.value = value;
|
||||
this.updateValue(event);
|
||||
this.focusNext(index);
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Inserting in the middle with available space (shifting)
|
||||
// If typing in a filled input box and there are empty boxes at the end,
|
||||
// shift all values starting at the current box to the right, and insert
|
||||
// the new character at the current box.
|
||||
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
||||
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
||||
// Get the inserted character (from event or by diffing value/previousValue)
|
||||
let newChar = (event as InputEvent).data;
|
||||
if (!newChar) {
|
||||
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
||||
}
|
||||
// Validate the new character before shifting
|
||||
if (!validKeyPattern.test(newChar)) {
|
||||
input.value = this.inputValues[index] || '';
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
return;
|
||||
}
|
||||
// Shift values right from the end to the insertion point
|
||||
for (let i = this.inputValues.length - 1; i > index; i--) {
|
||||
this.inputValues[i] = this.inputValues[i - 1];
|
||||
this.inputRefs[i].value = this.inputValues[i] || '';
|
||||
}
|
||||
this.inputValues[index] = newChar;
|
||||
this.inputRefs[index].value = newChar;
|
||||
this.updateValue(event);
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Single character replacement
|
||||
// Handles replacing a single character in a box containing a value based
|
||||
// on the cursor position. We need the cursor position to determine which
|
||||
// character was the last character typed. For example, if the user types "2"
|
||||
// in an input box with the cursor at the beginning of the value of "6",
|
||||
// the value will be "26", but we want to grab the "2" as the last character
|
||||
// typed.
|
||||
const cursorPos = input.selectionStart ?? value.length;
|
||||
const newCharIndex = cursorPos - 1;
|
||||
const newChar = value[newCharIndex] ?? value[0];
|
||||
|
||||
// Check if the new character is valid before updating the value
|
||||
if (!validKeyPattern.test(newChar)) {
|
||||
input.value = this.inputValues[index] || '';
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
return;
|
||||
}
|
||||
|
||||
this.inputValues[index] = newChar;
|
||||
input.value = newChar;
|
||||
// For single character input, fill the current box
|
||||
this.inputValues[index] = value;
|
||||
this.updateValue(event);
|
||||
this.previousInputValues = [...this.inputValues];
|
||||
|
||||
if (value.length > 0) {
|
||||
this.focusNext(index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -768,8 +711,12 @@ export class InputOTP implements ComponentInterface {
|
||||
|
||||
// Focus the next empty input after pasting
|
||||
// If all boxes are filled, focus the last input
|
||||
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
||||
inputRefs[nextEmptyIndex]?.focus();
|
||||
const nextEmptyIndex = validChars.length;
|
||||
if (nextEmptyIndex < length) {
|
||||
inputRefs[nextEmptyIndex]?.focus();
|
||||
} else {
|
||||
inputRefs[length - 1]?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -442,67 +442,6 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => {
|
||||
|
||||
await verifyInputValues(inputOtp, ['1', '9', '3', '']);
|
||||
});
|
||||
|
||||
test('should replace the last value when typing one more than the length', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp>Description</ion-input-otp>`, config);
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
const firstInput = inputOtp.locator('input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
await page.keyboard.type('12345');
|
||||
|
||||
await verifyInputValues(inputOtp, ['1', '2', '3', '5']);
|
||||
});
|
||||
|
||||
test('should replace the last value when typing one more than the length and the type is text', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/30459',
|
||||
});
|
||||
|
||||
await page.setContent(`<ion-input-otp type="text">Description</ion-input-otp>`, config);
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
const firstInput = inputOtp.locator('input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
await page.keyboard.type('abcde');
|
||||
|
||||
await verifyInputValues(inputOtp, ['a', 'b', 'c', 'e']);
|
||||
});
|
||||
|
||||
test('should not insert or shift when typing an invalid character before a number', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp value="12">Description</ion-input-otp>`, config);
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
const firstInput = inputOtp.locator('input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
// Move cursor to the start of the first input
|
||||
await firstInput.evaluate((el: HTMLInputElement) => el.setSelectionRange(0, 0));
|
||||
|
||||
await page.keyboard.type('w');
|
||||
|
||||
await verifyInputValues(inputOtp, ['1', '2', '', '']);
|
||||
});
|
||||
|
||||
test('should not insert or shift when typing an invalid character after a number', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp value="12">Description</ion-input-otp>`, config);
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
const firstInput = inputOtp.locator('input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
// Move cursor to the end of the first input
|
||||
await firstInput.evaluate((el: HTMLInputElement) => el.setSelectionRange(1, 1));
|
||||
|
||||
await page.keyboard.type('w');
|
||||
|
||||
await verifyInputValues(inputOtp, ['1', '2', '', '']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('input-otp: autofill functionality'), () => {
|
||||
@@ -521,53 +460,6 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => {
|
||||
await expect(lastInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('should handle autofill correctly when all characters are the same', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp>Description</ion-input-otp>`, config);
|
||||
|
||||
const firstInput = page.locator('ion-input-otp input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
await simulateAutofill(firstInput, '1111');
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
await verifyInputValues(inputOtp, ['1', '1', '1', '1']);
|
||||
|
||||
const lastInput = page.locator('ion-input-otp input').last();
|
||||
await expect(lastInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('should handle autofill correctly when length is 2', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp length="2">Description</ion-input-otp>`, config);
|
||||
|
||||
const firstInput = page.locator('ion-input-otp input').first();
|
||||
await firstInput.focus();
|
||||
|
||||
await simulateAutofill(firstInput, '12');
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
await verifyInputValues(inputOtp, ['1', '2']);
|
||||
|
||||
const lastInput = page.locator('ion-input-otp input').last();
|
||||
await expect(lastInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('should handle autofill correctly when length is 2 after typing 1 character', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp length="2">Description</ion-input-otp>`, config);
|
||||
|
||||
await page.keyboard.type('1');
|
||||
|
||||
const secondInput = page.locator('ion-input-otp input').nth(1);
|
||||
await secondInput.focus();
|
||||
|
||||
await simulateAutofill(secondInput, '22');
|
||||
|
||||
const inputOtp = page.locator('ion-input-otp');
|
||||
await verifyInputValues(inputOtp, ['2', '2']);
|
||||
|
||||
const lastInput = page.locator('ion-input-otp input').last();
|
||||
await expect(lastInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('should handle autofill correctly when it exceeds the length', async ({ page }) => {
|
||||
await page.setContent(`<ion-input-otp>Description</ion-input-otp>`, config);
|
||||
|
||||
|
||||
@@ -618,5 +618,5 @@
|
||||
*/
|
||||
:host([disabled]) ::slotted(ion-input-password-toggle),
|
||||
:host([readonly]) ::slotted(ion-input-password-toggle) {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -26,69 +26,5 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
const input = page.locator('ion-input');
|
||||
await expect(input).toHaveScreenshot(screenshot(`input-disabled`));
|
||||
});
|
||||
|
||||
test('should maintain consistent height when password toggle is hidden on disabled input', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/29562',
|
||||
});
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-input label="Password" type="password" value="password123">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
|
||||
// Get the height when input is enabled
|
||||
const enabledHeight = await input.boundingBox().then((box) => box?.height);
|
||||
|
||||
// Disable the input
|
||||
await input.evaluate((el) => el.setAttribute('disabled', 'true'));
|
||||
await page.waitForChanges();
|
||||
|
||||
// Get the height when input is disabled
|
||||
const disabledHeight = await input.boundingBox().then((box) => box?.height);
|
||||
|
||||
// Verify heights are the same
|
||||
expect(enabledHeight).toBe(disabledHeight);
|
||||
});
|
||||
|
||||
test('should maintain consistent height when password toggle is hidden on readonly input', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/29562',
|
||||
});
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-input label="Password" type="password" value="password123">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const input = page.locator('ion-input');
|
||||
|
||||
// Get the height when input is enabled
|
||||
const enabledHeight = await input.boundingBox().then((box) => box?.height);
|
||||
|
||||
// Make the input readonly
|
||||
await input.evaluate((el) => el.setAttribute('readonly', 'true'));
|
||||
await page.waitForChanges();
|
||||
|
||||
// Get the height when input is readonly
|
||||
const readonlyHeight = await input.boundingBox().then((box) => box?.height);
|
||||
|
||||
// Verify heights are the same
|
||||
expect(enabledHeight).toBe(readonlyHeight);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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('item-sliding: async'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -38,85 +35,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await expect(itemSlidingEl).toHaveClass(/item-sliding-active-slide/);
|
||||
});
|
||||
|
||||
// NOTE: This test uses the CDN version of Ionic.
|
||||
// If this test fails, it is likely due to a regression in the published package.
|
||||
test('should not throw errors when adding multiple items with side="end" using the Ionic CDN', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/29499',
|
||||
});
|
||||
|
||||
const errors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
page.on('pageerror', (error) => {
|
||||
errors.push(error.message);
|
||||
});
|
||||
|
||||
// This issue only happens when using a CDN version of Ionic
|
||||
// so we need to use the CDN by passing the `importIonicFromCDN` option
|
||||
// to setContent.
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Item Sliding</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button id="addItem" onclick="addItem()">ADD ITEM</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list id="list"></ion-list>
|
||||
</ion-content>
|
||||
|
||||
<script>
|
||||
let itemList = [];
|
||||
function generateItem() {
|
||||
const currentItem = itemList.length + 1;
|
||||
const item = \`
|
||||
<ion-item-sliding>
|
||||
<ion-item>
|
||||
<ion-label>Sliding Item \${currentItem}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option>Delete</ion-item-option>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
\`;
|
||||
itemList.push(item);
|
||||
return item;
|
||||
}
|
||||
function addItem() {
|
||||
const list = document.getElementById('list');
|
||||
list.innerHTML += generateItem();
|
||||
const currentItem = itemList.length;
|
||||
}
|
||||
</script>
|
||||
`,
|
||||
{ ...config, importIonicFromCDN: true }
|
||||
);
|
||||
|
||||
// Click the button enough times to reproduce the issue
|
||||
const addButton = page.locator('#addItem');
|
||||
await addButton.click();
|
||||
await addButton.click();
|
||||
await addButton.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
// Check that the items have been added
|
||||
const items = page.locator('ion-item-sliding');
|
||||
expect(await items.count()).toBe(3);
|
||||
|
||||
// Check that no errors have been logged
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
|
||||
@State() multipleInputs = false;
|
||||
@State() focusable = true;
|
||||
@State() isInteractive = false;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
@@ -173,12 +172,14 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
componentDidLoad() {
|
||||
raf(() => {
|
||||
this.setMultipleInputs();
|
||||
this.setIsInteractive();
|
||||
this.focusable = this.isFocusable();
|
||||
});
|
||||
}
|
||||
|
||||
private totalNestedInputs() {
|
||||
// If the item contains multiple clickable elements and/or inputs, then the item
|
||||
// should not have a clickable input cover over the entire item to prevent
|
||||
// interfering with their individual click events
|
||||
private setMultipleInputs() {
|
||||
// The following elements have a clickable cover that is relative to the entire item
|
||||
const covers = this.el.querySelectorAll('ion-checkbox, ion-datetime, ion-select, ion-radio');
|
||||
|
||||
@@ -192,19 +193,6 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
// The following elements should also stay clickable when an input with cover is present
|
||||
const clickables = this.el.querySelectorAll('ion-router-link, ion-button, a, button');
|
||||
|
||||
return {
|
||||
covers,
|
||||
inputs,
|
||||
clickables,
|
||||
};
|
||||
}
|
||||
|
||||
// If the item contains multiple clickable elements and/or inputs, then the item
|
||||
// should not have a clickable input cover over the entire item to prevent
|
||||
// interfering with their individual click events
|
||||
private setMultipleInputs() {
|
||||
const { covers, inputs, clickables } = this.totalNestedInputs();
|
||||
|
||||
// Check for multiple inputs to change the position of the input cover to relative
|
||||
// for all of the covered inputs above
|
||||
this.multipleInputs =
|
||||
@@ -213,19 +201,6 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
(covers.length > 0 && this.isClickable());
|
||||
}
|
||||
|
||||
private setIsInteractive() {
|
||||
// If item contains any interactive children, set isInteractive to `true`
|
||||
const { covers, inputs, clickables } = this.totalNestedInputs();
|
||||
|
||||
this.isInteractive = covers.length > 0 || inputs.length > 0 || clickables.length > 0;
|
||||
}
|
||||
|
||||
// slot change listener updates state to reflect how/if item should be interactive
|
||||
private updateInteractivityOnSlotChange = () => {
|
||||
this.setIsInteractive();
|
||||
this.setMultipleInputs();
|
||||
};
|
||||
|
||||
// If the item contains an input including a checkbox, datetime, select, or radio
|
||||
// then the item will have a clickable input cover that covers the item
|
||||
// that should get the hover, focused and activated states UNLESS it has multiple
|
||||
@@ -389,12 +364,12 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
disabled={disabled}
|
||||
{...clickFn}
|
||||
>
|
||||
<slot name="start" onSlotchange={this.updateInteractivityOnSlotChange}></slot>
|
||||
<slot name="start"></slot>
|
||||
<div class="item-inner">
|
||||
<div class="input-wrapper">
|
||||
<slot onSlotchange={this.updateInteractivityOnSlotChange}></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="end" onSlotchange={this.updateInteractivityOnSlotChange}></slot>
|
||||
<slot name="end"></slot>
|
||||
{showDetail && (
|
||||
<ion-icon
|
||||
icon={detailIcon}
|
||||
|
||||
@@ -252,46 +252,5 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
|
||||
await expect(list).toHaveScreenshot(screenshot(`item-inputs-div-with-inputs`));
|
||||
});
|
||||
|
||||
test('should update interactivity state when elements are conditionally rendered', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/29763',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Conditional Checkbox</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const item = page.locator('ion-item');
|
||||
|
||||
await page.evaluate(() => {
|
||||
const item = document.querySelector('ion-item');
|
||||
const checkbox = document.createElement('ion-checkbox');
|
||||
item?.appendChild(checkbox);
|
||||
});
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).not.toBeChecked();
|
||||
|
||||
// Test that clicking on the left edge of the item toggles the checkbox
|
||||
await item.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(checkbox).toBeChecked();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,13 +18,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const heading = page.locator('ion-menu h1');
|
||||
await expect(heading).toHaveText('Open Menu');
|
||||
|
||||
/**
|
||||
* Disable the 'scrollable-region-focusable' rule because this test
|
||||
* is missing the required `ion-app` wrapper component. The `ion-app`
|
||||
* wrapper provides the necessary focus management that allows the
|
||||
* menu content to be focusable.
|
||||
*/
|
||||
const results = await new AxeBuilder({ page }).disableRules('scrollable-region-focusable').analyze();
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
|
||||
}
|
||||
|
||||
if (presentingEl) {
|
||||
const isPortrait = window.innerWidth < 768;
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const hasCardModal =
|
||||
presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined;
|
||||
const presentingElRoot = getElementRoot(presentingEl);
|
||||
@@ -61,7 +61,7 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
|
||||
|
||||
const bodyEl = document.body;
|
||||
|
||||
if (isPortrait) {
|
||||
if (isMobile) {
|
||||
/**
|
||||
* Fallback for browsers that does not support `max()` (ex: Firefox)
|
||||
* No need to worry about statusbar padding since engines like Gecko
|
||||
|
||||
@@ -35,7 +35,7 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
|
||||
.addAnimation(wrapperAnimation);
|
||||
|
||||
if (presentingEl) {
|
||||
const isPortrait = window.innerWidth < 768;
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const hasCardModal =
|
||||
presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined;
|
||||
const presentingElRoot = getElementRoot(presentingEl);
|
||||
@@ -61,7 +61,7 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
|
||||
|
||||
const bodyEl = document.body;
|
||||
|
||||
if (isPortrait) {
|
||||
if (isMobile) {
|
||||
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
||||
const modalTransform = hasCardModal ? '-10px' : transformOffset;
|
||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
import { createAnimation } from '@utils/animation/animation';
|
||||
import { getElementRoot } from '@utils/helpers';
|
||||
|
||||
import type { Animation } from '../../../interface';
|
||||
import { SwipeToCloseDefaults } from '../gestures/swipe-to-close';
|
||||
import type { ModalAnimationOptions } from '../modal-interface';
|
||||
|
||||
/**
|
||||
* Transition animation from portrait view to landscape view
|
||||
* This handles the case where a card modal is open in portrait view
|
||||
* and the user switches to landscape view
|
||||
*/
|
||||
export const portraitToLandscapeTransition = (
|
||||
baseEl: HTMLElement,
|
||||
opts: ModalAnimationOptions,
|
||||
duration = 300
|
||||
): Animation => {
|
||||
const { presentingEl } = opts;
|
||||
|
||||
if (!presentingEl) {
|
||||
// No transition needed for non-card modals
|
||||
return createAnimation('portrait-to-landscape-transition');
|
||||
}
|
||||
|
||||
const presentingElIsCardModal =
|
||||
presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined;
|
||||
const presentingElRoot = getElementRoot(presentingEl);
|
||||
const bodyEl = document.body;
|
||||
|
||||
const baseAnimation = createAnimation('portrait-to-landscape-transition')
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(duration);
|
||||
|
||||
const presentingAnimation = createAnimation().beforeStyles({
|
||||
transform: 'translateY(0)',
|
||||
'transform-origin': 'top center',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
if (!presentingElIsCardModal) {
|
||||
// The presenting element is not a card modal, so we do not
|
||||
// need to care about layering and modal-specific styles.
|
||||
const root = getElementRoot(baseEl);
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||
.fromTo('opacity', '1', '1'); // Keep wrapper visible in landscape
|
||||
|
||||
const backdropAnimation = createAnimation()
|
||||
.addElement(root.querySelector('ion-backdrop')!)
|
||||
.fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
|
||||
|
||||
// Animate presentingEl from portrait state back to normal
|
||||
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
const fromTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
|
||||
|
||||
presentingAnimation
|
||||
.addElement(presentingEl)
|
||||
.afterStyles({
|
||||
transform: 'translateY(0px) scale(1)',
|
||||
'border-radius': '0px',
|
||||
})
|
||||
.beforeAddWrite(() => bodyEl.style.setProperty('background-color', ''))
|
||||
.fromTo('transform', fromTransform, 'translateY(0px) scale(1)')
|
||||
.fromTo('filter', 'contrast(0.85)', 'contrast(1)')
|
||||
.fromTo('border-radius', '10px 10px 0 0', '0px');
|
||||
|
||||
baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
|
||||
} else {
|
||||
// The presenting element is a card modal, so we do
|
||||
// need to care about layering and modal-specific styles.
|
||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
||||
const toTransform = `translateY(0px) scale(1)`;
|
||||
|
||||
presentingAnimation
|
||||
.addElement(presentingEl)
|
||||
.afterStyles({
|
||||
transform: toTransform,
|
||||
})
|
||||
.fromTo('transform', fromTransform, toTransform)
|
||||
.fromTo('filter', 'contrast(0.85)', 'contrast(1)');
|
||||
|
||||
const shadowAnimation = createAnimation()
|
||||
.addElement(presentingElRoot.querySelector('.modal-shadow')!)
|
||||
.afterStyles({
|
||||
transform: toTransform,
|
||||
opacity: '0',
|
||||
})
|
||||
.fromTo('transform', fromTransform, toTransform);
|
||||
|
||||
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
||||
}
|
||||
|
||||
return baseAnimation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transition animation from landscape view to portrait view
|
||||
* This handles the case where a card modal is open in landscape view
|
||||
* and the user switches to portrait view
|
||||
*/
|
||||
export const landscapeToPortraitTransition = (
|
||||
baseEl: HTMLElement,
|
||||
opts: ModalAnimationOptions,
|
||||
duration = 300
|
||||
): Animation => {
|
||||
const { presentingEl } = opts;
|
||||
|
||||
if (!presentingEl) {
|
||||
// No transition needed for non-card modals
|
||||
return createAnimation('landscape-to-portrait-transition');
|
||||
}
|
||||
|
||||
const presentingElIsCardModal =
|
||||
presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined;
|
||||
const presentingElRoot = getElementRoot(presentingEl);
|
||||
const bodyEl = document.body;
|
||||
|
||||
const baseAnimation = createAnimation('landscape-to-portrait-transition')
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(duration);
|
||||
|
||||
const presentingAnimation = createAnimation().beforeStyles({
|
||||
transform: 'translateY(0)',
|
||||
'transform-origin': 'top center',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
if (!presentingElIsCardModal) {
|
||||
// The presenting element is not a card modal, so we do not
|
||||
// need to care about layering and modal-specific styles.
|
||||
const root = getElementRoot(baseEl);
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||
.fromTo('opacity', '1', '1'); // Keep wrapper visible
|
||||
|
||||
const backdropAnimation = createAnimation()
|
||||
.addElement(root.querySelector('ion-backdrop')!)
|
||||
.fromTo('opacity', 'var(--backdrop-opacity)', 'var(--backdrop-opacity)'); // Keep backdrop visible
|
||||
|
||||
// Animate presentingEl from normal state to portrait state
|
||||
const transformOffset = !CSS.supports('width', 'max(0px, 1px)') ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
|
||||
|
||||
presentingAnimation
|
||||
.addElement(presentingEl)
|
||||
.afterStyles({
|
||||
transform: toTransform,
|
||||
})
|
||||
.beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '0px' },
|
||||
{ offset: 0.2, transform: 'translateY(0px) scale(1)', filter: 'contrast(1)', borderRadius: '10px 10px 0 0' },
|
||||
{ offset: 1, transform: toTransform, filter: 'contrast(0.85)', borderRadius: '10px 10px 0 0' },
|
||||
]);
|
||||
|
||||
baseAnimation.addAnimation([presentingAnimation, wrapperAnimation, backdropAnimation]);
|
||||
} else {
|
||||
// The presenting element is also a card modal, so we need
|
||||
// to handle layering and modal-specific styles.
|
||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
||||
const toTransform = `translateY(0) scale(1)`;
|
||||
|
||||
presentingAnimation
|
||||
.addElement(presentingEl)
|
||||
.afterStyles({
|
||||
transform: toTransform,
|
||||
})
|
||||
.fromTo('transform', fromTransform, toTransform);
|
||||
|
||||
const shadowAnimation = createAnimation()
|
||||
.addElement(presentingElRoot.querySelector('.modal-shadow')!)
|
||||
.afterStyles({
|
||||
transform: toTransform,
|
||||
opacity: '0',
|
||||
})
|
||||
.fromTo('transform', fromTransform, toTransform);
|
||||
|
||||
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
||||
}
|
||||
|
||||
return baseAnimation;
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Listen, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
|
||||
import { findIonContent, printIonContentErrorMsg } from '@utils/content';
|
||||
import { CoreDelegate, attachComponent, detachComponent } from '@utils/framework-delegate';
|
||||
import { raf, inheritAttributes, hasLazyBuild, getElementRoot } from '@utils/helpers';
|
||||
import { raf, inheritAttributes, hasLazyBuild } from '@utils/helpers';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { createLockController } from '@utils/lock-controller';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
@@ -37,12 +37,11 @@ import type { OverlayEventDetail } from '../../utils/overlays-interface';
|
||||
|
||||
import { iosEnterAnimation } from './animations/ios.enter';
|
||||
import { iosLeaveAnimation } from './animations/ios.leave';
|
||||
import { portraitToLandscapeTransition, landscapeToPortraitTransition } from './animations/ios.transition';
|
||||
import { mdEnterAnimation } from './animations/md.enter';
|
||||
import { mdLeaveAnimation } from './animations/md.leave';
|
||||
import type { MoveSheetToBreakpointOptions } from './gestures/sheet';
|
||||
import { createSheetGesture } from './gestures/sheet';
|
||||
import { createSwipeToCloseGesture, SwipeToCloseDefaults } from './gestures/swipe-to-close';
|
||||
import { createSwipeToCloseGesture } from './gestures/swipe-to-close';
|
||||
import type { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from './modal-interface';
|
||||
import { setCardStatusBarDark, setCardStatusBarDefault } from './utils';
|
||||
|
||||
@@ -91,16 +90,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
// Whether or not modal is being dismissed via gesture
|
||||
private gestureAnimationDismissing = false;
|
||||
|
||||
// View transition properties for handling portrait/landscape switches
|
||||
private currentViewIsPortrait?: boolean;
|
||||
private viewTransitionAnimation?: Animation;
|
||||
private resizeTimeout?: any;
|
||||
|
||||
// Mutation observer to watch for parent removal
|
||||
private parentRemovalObserver?: MutationObserver;
|
||||
// Cached original parent from before modal is moved to body during presentation
|
||||
private cachedOriginalParent?: HTMLElement;
|
||||
|
||||
lastFocus?: HTMLElement;
|
||||
animation?: Animation;
|
||||
|
||||
@@ -272,19 +261,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('resize', { target: 'window' })
|
||||
onWindowResize() {
|
||||
// Only handle resize for iOS card modals when no custom animations are provided
|
||||
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.resizeTimeout);
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
this.handleViewTransition();
|
||||
}, 50); // Debounce to avoid excessive calls during active resizing
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, the component passed into `ion-modal` will
|
||||
* automatically be mounted when the modal is created. The
|
||||
@@ -402,8 +378,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
|
||||
disconnectedCallback() {
|
||||
this.triggerController.removeClickListener();
|
||||
this.cleanupViewTransitionListener();
|
||||
this.cleanupParentRemovalObserver();
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
@@ -413,11 +387,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
const attributesToInherit = ['aria-label', 'role'];
|
||||
this.inheritedAttributes = inheritAttributes(el, attributesToInherit);
|
||||
|
||||
// Cache original parent before modal gets moved to body during presentation
|
||||
if (el.parentNode) {
|
||||
this.cachedOriginalParent = el.parentNode as HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* When using a controller modal you can set attributes
|
||||
* using the htmlAttributes property. Since the above attributes
|
||||
@@ -650,12 +619,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
this.initSwipeToClose();
|
||||
}
|
||||
|
||||
// Initialize view transition listener for iOS card modals
|
||||
this.initViewTransitionListener();
|
||||
|
||||
// Initialize parent removal observer
|
||||
this.initParentRemovalObserver();
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
@@ -798,13 +761,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
*/
|
||||
const unlock = await this.lockController.lock();
|
||||
|
||||
/**
|
||||
* Dismiss all child modals. This is especially important in
|
||||
* Angular and React because it's possible to lose control of a child
|
||||
* modal when the parent modal is dismissed.
|
||||
*/
|
||||
await this.dismissNestedModals();
|
||||
|
||||
/**
|
||||
* If a canDismiss handler is responsible
|
||||
* for calling the dismiss method, we should
|
||||
@@ -860,8 +816,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
if (this.gesture) {
|
||||
this.gesture.destroy();
|
||||
}
|
||||
this.cleanupViewTransitionListener();
|
||||
this.cleanupParentRemovalObserver();
|
||||
}
|
||||
this.currentBreakpoint = undefined;
|
||||
this.animation = undefined;
|
||||
@@ -1009,217 +963,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
}
|
||||
};
|
||||
|
||||
private initViewTransitionListener() {
|
||||
// Only enable for iOS card modals when no custom animations are provided
|
||||
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial view state
|
||||
this.currentViewIsPortrait = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
private handleViewTransition() {
|
||||
const isPortrait = window.innerWidth < 768;
|
||||
|
||||
// Only transition if view state actually changed
|
||||
if (this.currentViewIsPortrait === isPortrait) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any ongoing transition animation
|
||||
if (this.viewTransitionAnimation) {
|
||||
this.viewTransitionAnimation.destroy();
|
||||
this.viewTransitionAnimation = undefined;
|
||||
}
|
||||
|
||||
const { presentingElement } = this;
|
||||
if (!presentingElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create transition animation
|
||||
let transitionAnimation: Animation;
|
||||
if (this.currentViewIsPortrait && !isPortrait) {
|
||||
// Portrait to landscape transition
|
||||
transitionAnimation = portraitToLandscapeTransition(this.el, {
|
||||
presentingEl: presentingElement,
|
||||
currentBreakpoint: this.currentBreakpoint,
|
||||
backdropBreakpoint: this.backdropBreakpoint,
|
||||
expandToScroll: this.expandToScroll,
|
||||
});
|
||||
} else {
|
||||
// Landscape to portrait transition
|
||||
transitionAnimation = landscapeToPortraitTransition(this.el, {
|
||||
presentingEl: presentingElement,
|
||||
currentBreakpoint: this.currentBreakpoint,
|
||||
backdropBreakpoint: this.backdropBreakpoint,
|
||||
expandToScroll: this.expandToScroll,
|
||||
});
|
||||
}
|
||||
|
||||
// Update state and play animation
|
||||
this.currentViewIsPortrait = isPortrait;
|
||||
this.viewTransitionAnimation = transitionAnimation;
|
||||
|
||||
transitionAnimation.play().then(() => {
|
||||
this.viewTransitionAnimation = undefined;
|
||||
|
||||
// After orientation transition, recreate the swipe-to-close gesture
|
||||
// with updated animation that reflects the new presenting element state
|
||||
this.reinitSwipeToClose();
|
||||
});
|
||||
}
|
||||
|
||||
private cleanupViewTransitionListener() {
|
||||
// Clear any pending resize timeout
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
this.resizeTimeout = undefined;
|
||||
}
|
||||
|
||||
if (this.viewTransitionAnimation) {
|
||||
this.viewTransitionAnimation.destroy();
|
||||
this.viewTransitionAnimation = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private reinitSwipeToClose() {
|
||||
// Only reinitialize if we have a presenting element and are on iOS
|
||||
if (getIonMode(this) !== 'ios' || !this.presentingElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up existing gesture and animation
|
||||
if (this.gesture) {
|
||||
this.gesture.destroy();
|
||||
this.gesture = undefined;
|
||||
}
|
||||
|
||||
if (this.animation) {
|
||||
// Properly end the progress-based animation at initial state before destroying
|
||||
// to avoid leaving modal in intermediate swipe position
|
||||
this.animation.progressEnd(0, 0, 0);
|
||||
this.animation.destroy();
|
||||
this.animation = undefined;
|
||||
}
|
||||
|
||||
// Force the modal back to the correct position or it could end up
|
||||
// in a weird state after destroying the animation
|
||||
raf(() => {
|
||||
this.ensureCorrectModalPosition();
|
||||
this.initSwipeToClose();
|
||||
});
|
||||
}
|
||||
|
||||
private ensureCorrectModalPosition() {
|
||||
const { el, presentingElement } = this;
|
||||
const root = getElementRoot(el);
|
||||
|
||||
const wrapperEl = root.querySelector('.modal-wrapper') as HTMLElement | null;
|
||||
if (wrapperEl) {
|
||||
wrapperEl.style.transform = 'translateY(0vh)';
|
||||
wrapperEl.style.opacity = '1';
|
||||
}
|
||||
|
||||
if (presentingElement?.tagName === 'ION-MODAL') {
|
||||
const isPortrait = window.innerWidth < 768;
|
||||
|
||||
if (isPortrait) {
|
||||
const transformOffset = !CSS.supports('width', 'max(0px, 1px)')
|
||||
? '30px'
|
||||
: 'max(30px, var(--ion-safe-area-top))';
|
||||
const scale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
presentingElement.style.transform = `translateY(${transformOffset}) scale(${scale})`;
|
||||
} else {
|
||||
presentingElement.style.transform = 'translateY(0px) scale(1)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the slot changes, we need to find all the modals in the slot
|
||||
* and set the data-parent-ion-modal attribute on them so we can find them
|
||||
* and dismiss them when we get dismissed.
|
||||
* We need to do it this way because when a modal is opened, it's moved to
|
||||
* the end of the body and is no longer an actual child of the modal.
|
||||
*/
|
||||
private onSlotChange = ({ target }: Event) => {
|
||||
const slot = target as HTMLSlotElement;
|
||||
slot.assignedElements().forEach((el) => {
|
||||
el.querySelectorAll('ion-modal').forEach((childModal) => {
|
||||
// We don't need to write to the DOM if the modal is already tagged
|
||||
// If this is a deeply nested modal, this effect should cascade so we don't
|
||||
// need to worry about another modal claiming the same child.
|
||||
if (childModal.getAttribute('data-parent-ion-modal') === null) {
|
||||
childModal.setAttribute('data-parent-ion-modal', this.el.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private async dismissNestedModals(): Promise<void> {
|
||||
const nestedModals = document.querySelectorAll(`ion-modal[data-parent-ion-modal="${this.el.id}"]`);
|
||||
nestedModals?.forEach(async (modal) => {
|
||||
await (modal as HTMLIonModalElement).dismiss(undefined, 'parent-dismissed');
|
||||
});
|
||||
}
|
||||
|
||||
private initParentRemovalObserver() {
|
||||
if (typeof MutationObserver === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only observe if we have a cached parent and are in browser environment
|
||||
if (typeof window === 'undefined' || !this.cachedOriginalParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't observe document or fragment nodes as they can't be "removed"
|
||||
if (
|
||||
this.cachedOriginalParent.nodeType === Node.DOCUMENT_NODE ||
|
||||
this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parentRemovalObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
|
||||
// Check if our cached original parent was removed
|
||||
const cachedParentWasRemoved = Array.from(mutation.removedNodes).some((node) => {
|
||||
const isDirectMatch = node === this.cachedOriginalParent;
|
||||
const isContainedMatch = this.cachedOriginalParent
|
||||
? (node as HTMLElement).contains?.(this.cachedOriginalParent)
|
||||
: false;
|
||||
return isDirectMatch || isContainedMatch;
|
||||
});
|
||||
|
||||
// Also check if parent is no longer connected to DOM
|
||||
const cachedParentDisconnected = this.cachedOriginalParent && !this.cachedOriginalParent.isConnected;
|
||||
|
||||
if (cachedParentWasRemoved || cachedParentDisconnected) {
|
||||
this.dismiss(undefined, 'parent-removed');
|
||||
// Release the reference to the cached original parent
|
||||
// so we don't have a memory leak
|
||||
this.cachedOriginalParent = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Observe document body with subtree to catch removals at any level
|
||||
this.parentRemovalObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
private cleanupParentRemovalObserver() {
|
||||
this.parentRemovalObserver?.disconnect();
|
||||
this.parentRemovalObserver = undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
handle,
|
||||
@@ -1297,7 +1040,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
ref={(el) => (this.dragHandleEl = el)}
|
||||
></button>
|
||||
)}
|
||||
<slot onSlotchange={this.onSlotChange}></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</Host>
|
||||
);
|
||||
|
||||
@@ -22,91 +22,31 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div id="modal-container">
|
||||
<button id="open-inline-modal" onclick="openModal(event)">Open Modal</button>
|
||||
<ion-modal swipe-to-close="true">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title> Modal </ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<p>This is my inline modal content!</p>
|
||||
<button id="open-child-modal" onclick="openChildModal(event)">Open Child Modal</button>
|
||||
<button id="remove-modal-container" onclick="removeModalContainer(event)">
|
||||
Remove Modal Container
|
||||
</button>
|
||||
<button id="open-inline-modal" onclick="openModal(event)">Open Modal</button>
|
||||
|
||||
<ion-modal id="child-modal" swipe-to-close="true">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Child Modal</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<p>This is the child modal content!</p>
|
||||
<p>When the parent modal is dismissed, this child modal should also be dismissed automatically.</p>
|
||||
<button id="dismiss-parent" onclick="dismissParent(event)">Dismiss Parent Modal</button>
|
||||
<button id="dismiss-child" onclick="dismissChild(event)">Dismiss Child Modal</button>
|
||||
<button id="child-remove-modal-container" onclick="removeModalContainer(event)">
|
||||
Remove Modal Container
|
||||
</button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</div>
|
||||
<ion-modal swipe-to-close="true">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title> Modal </ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding"> This is my inline modal content! </ion-content>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
</div>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const modal = document.querySelector('ion-modal');
|
||||
const childModal = document.querySelector('#child-modal');
|
||||
|
||||
modal.presentingElement = document.querySelector('.ion-page');
|
||||
childModal.presentingElement = modal;
|
||||
|
||||
const openModal = () => {
|
||||
modal.isOpen = true;
|
||||
};
|
||||
|
||||
const openChildModal = () => {
|
||||
childModal.isOpen = true;
|
||||
};
|
||||
|
||||
const dismissParent = () => {
|
||||
modal.isOpen = false;
|
||||
};
|
||||
|
||||
const dismissChild = () => {
|
||||
childModal.isOpen = false;
|
||||
};
|
||||
|
||||
const removeModalContainer = () => {
|
||||
const container = document.querySelector('#modal-container');
|
||||
if (container) {
|
||||
container.remove();
|
||||
console.log('Modal container removed from DOM');
|
||||
}
|
||||
};
|
||||
|
||||
modal.addEventListener('didDismiss', () => {
|
||||
modal.isOpen = false;
|
||||
});
|
||||
|
||||
childModal.addEventListener('didDismiss', () => {
|
||||
childModal.isOpen = false;
|
||||
});
|
||||
|
||||
// Add event listeners to demonstrate the new functionality
|
||||
modal.addEventListener('ionModalDidDismiss', (event) => {
|
||||
console.log('Parent modal dismissed with role:', event.detail.role);
|
||||
});
|
||||
|
||||
childModal.addEventListener('ionModalDidDismiss', (event) => {
|
||||
console.log('Child modal dismissed with role:', event.detail.role);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await page.goto('/src/components/modal/test/inline', config);
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
const modal = page.locator('ion-modal').first();
|
||||
const modal = page.locator('ion-modal');
|
||||
|
||||
await page.click('#open-inline-modal');
|
||||
|
||||
@@ -22,67 +22,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('it should dismiss child modals when parent modal is dismissed', async ({ page }) => {
|
||||
await page.goto('/src/components/modal/test/inline', config);
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const parentModal = page.locator('ion-modal').first();
|
||||
const childModal = page.locator('#child-modal');
|
||||
|
||||
// Open the parent modal
|
||||
await page.click('#open-inline-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(parentModal).toBeVisible();
|
||||
|
||||
// Open the child modal
|
||||
await page.click('#open-child-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(childModal).toBeVisible();
|
||||
|
||||
// Both modals should be visible
|
||||
await expect(parentModal).toBeVisible();
|
||||
await expect(childModal).toBeVisible();
|
||||
|
||||
// Dismiss the parent modal
|
||||
await page.click('#dismiss-parent');
|
||||
|
||||
// Wait for both modals to be dismissed
|
||||
await ionModalDidDismiss.next(); // child modal dismissed
|
||||
await ionModalDidDismiss.next(); // parent modal dismissed
|
||||
|
||||
// Both modals should be hidden
|
||||
await expect(parentModal).toBeHidden();
|
||||
await expect(childModal).toBeHidden();
|
||||
});
|
||||
|
||||
test('it should only dismiss child modal when child dismiss button is clicked', async ({ page }) => {
|
||||
await page.goto('/src/components/modal/test/inline', config);
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const parentModal = page.locator('ion-modal').first();
|
||||
const childModal = page.locator('#child-modal');
|
||||
|
||||
// Open the parent modal
|
||||
await page.click('#open-inline-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(parentModal).toBeVisible();
|
||||
|
||||
// Open the child modal
|
||||
await page.click('#open-child-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(childModal).toBeVisible();
|
||||
|
||||
// Dismiss only the child modal
|
||||
await page.click('#dismiss-child');
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
// Parent modal should still be visible, child modal should be hidden
|
||||
await expect(parentModal).toBeVisible();
|
||||
await expect(childModal).toBeHidden();
|
||||
});
|
||||
|
||||
test('presenting should create a single root element with the ion-page class', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({
|
||||
type: 'issue',
|
||||
@@ -122,152 +61,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await modal.evaluate((el: HTMLIonModalElement) => el.firstElementChild!.firstElementChild!.className)
|
||||
).not.toContain('ion-page');
|
||||
});
|
||||
|
||||
test('it should dismiss modal when parent container is removed from DOM', async ({ page }) => {
|
||||
await page.goto('/src/components/modal/test/inline', config);
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const modal = page.locator('ion-modal').first();
|
||||
const modalContainer = page.locator('#modal-container');
|
||||
|
||||
// Open the modal
|
||||
await page.click('#open-inline-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// Remove the modal container from DOM
|
||||
await page.click('#remove-modal-container');
|
||||
|
||||
// Wait for modal to be dismissed
|
||||
const dismissEvent = await ionModalDidDismiss.next();
|
||||
|
||||
// Verify the modal was dismissed with the correct role
|
||||
expect(dismissEvent.detail.role).toBe('parent-removed');
|
||||
|
||||
// Verify the modal is no longer visible
|
||||
await expect(modal).toBeHidden();
|
||||
|
||||
// Verify the container was actually removed
|
||||
await expect(modalContainer).not.toBeAttached();
|
||||
});
|
||||
|
||||
test('it should dismiss both parent and child modals when parent container is removed from DOM', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/src/components/modal/test/inline', config);
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const parentModal = page.locator('ion-modal').first();
|
||||
const childModal = page.locator('#child-modal');
|
||||
const modalContainer = page.locator('#modal-container');
|
||||
|
||||
// Open the parent modal
|
||||
await page.click('#open-inline-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(parentModal).toBeVisible();
|
||||
|
||||
// Open the child modal
|
||||
await page.click('#open-child-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(childModal).toBeVisible();
|
||||
|
||||
// Remove the modal container from DOM
|
||||
await page.click('#child-remove-modal-container');
|
||||
|
||||
// Wait for both modals to be dismissed
|
||||
const firstDismissEvent = await ionModalDidDismiss.next();
|
||||
const secondDismissEvent = await ionModalDidDismiss.next();
|
||||
|
||||
// Verify at least one modal was dismissed with 'parent-removed' role
|
||||
const dismissRoles = [firstDismissEvent.detail.role, secondDismissEvent.detail.role];
|
||||
expect(dismissRoles).toContain('parent-removed');
|
||||
|
||||
// Verify both modals are no longer visible
|
||||
await expect(parentModal).toBeHidden();
|
||||
await expect(childModal).toBeHidden();
|
||||
|
||||
// Verify the container was actually removed
|
||||
await expect(modalContainer).not.toBeAttached();
|
||||
});
|
||||
|
||||
test('it should dismiss modals when top-level ancestor is removed', async ({ page }) => {
|
||||
// We need to make sure we can close a modal when a much higher
|
||||
// element is removed from the DOM. This will be a common
|
||||
// use case in frameworks like Angular and React, where an entire
|
||||
// page container for much more than the modal might be swapped out.
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-app>
|
||||
<div class="ion-page">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Top Level Removal Test</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<div id="top-level-container">
|
||||
<div id="nested-container">
|
||||
<button id="open-nested-modal">Open Nested Modal</button>
|
||||
<ion-modal id="nested-modal">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Nested Modal</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<p>This modal's original parent is deeply nested</p>
|
||||
<button id="remove-top-level">Remove Top Level Container</button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</div>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const nestedModal = document.querySelector('#nested-modal');
|
||||
nestedModal.presentingElement = document.querySelector('.ion-page');
|
||||
|
||||
document.getElementById('open-nested-modal').addEventListener('click', () => {
|
||||
nestedModal.isOpen = true;
|
||||
});
|
||||
|
||||
document.getElementById('remove-top-level').addEventListener('click', () => {
|
||||
document.querySelector('#top-level-container').remove();
|
||||
});
|
||||
</script>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const nestedModal = page.locator('#nested-modal');
|
||||
const topLevelContainer = page.locator('#top-level-container');
|
||||
|
||||
// Open the nested modal
|
||||
await page.click('#open-nested-modal');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(nestedModal).toBeVisible();
|
||||
|
||||
// Remove the top-level container
|
||||
await page.click('#remove-top-level');
|
||||
|
||||
// Wait for modal to be dismissed
|
||||
const dismissEvent = await ionModalDidDismiss.next();
|
||||
|
||||
// Verify the modal was dismissed with the correct role
|
||||
expect(dismissEvent.detail.role).toBe('parent-removed');
|
||||
|
||||
// Verify the modal is no longer visible
|
||||
await expect(nestedModal).toBeHidden();
|
||||
|
||||
// Verify the container was actually removed
|
||||
await expect(topLevelContainer).not.toBeAttached();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
|
||||
width: 100%;
|
||||
|
||||
// Workaround for a Safari/WebKit bug where flexbox children with dynamic
|
||||
// height (e.g., height: fit-content) are not included in the scrollable area
|
||||
// when using horizontal scrolling. This is needed to make the segment view
|
||||
// scroll to the correct content.
|
||||
min-height: 1px;
|
||||
|
||||
overflow-y: scroll;
|
||||
|
||||
/* Hide scrollbar in Firefox */
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Segment View - Dynamic Height</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>
|
||||
ion-segment-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
ion-segment-content:nth-of-type(1) {
|
||||
background: lightpink;
|
||||
}
|
||||
ion-segment-content:nth-of-type(2) {
|
||||
background: lightblue;
|
||||
}
|
||||
ion-segment-content:nth-of-type(3) {
|
||||
background: lightgreen;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Segment View - Dynamic Height</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-segment>
|
||||
<ion-segment-button value="first" content-id="first">
|
||||
<ion-label>First</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="second" content-id="second">
|
||||
<ion-label>Second</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="third" content-id="third">
|
||||
<ion-label>Third</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-segment-view>
|
||||
<ion-segment-content id="first">
|
||||
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora
|
||||
quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.
|
||||
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat
|
||||
cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The voodoo
|
||||
sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum defunctis
|
||||
go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv
|
||||
</ion-segment-content>
|
||||
<ion-segment-content id="second">
|
||||
<ion-input value="" label="Email"></ion-input>
|
||||
</ion-segment-content>
|
||||
<ion-segment-content id="third">
|
||||
<ion-img class="img-part" src="https://picsum.photos/200/300"></ion-img>
|
||||
</ion-segment-content>
|
||||
</ion-segment-view>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,85 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('segment-view: dynamic height'), () => {
|
||||
test('should show the third content when clicking the third button', async ({ page, skip }) => {
|
||||
// Skip this test on Chrome and Firefox
|
||||
skip.browser('firefox', 'Original issue only happens on Safari.');
|
||||
skip.browser('chromium', 'Original issue only happens on Safari.');
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
ion-segment-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: fit-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ion-segment>
|
||||
<ion-segment-button value="first" content-id="first">
|
||||
<ion-label>First</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="second" content-id="second">
|
||||
<ion-label>Second</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="third" content-id="third">
|
||||
<ion-label>Third</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-segment-view>
|
||||
<ion-segment-content id="first">
|
||||
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora
|
||||
quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum
|
||||
mauris. Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus
|
||||
comedat cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The
|
||||
voodoo sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum
|
||||
defunctis go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv
|
||||
</ion-segment-content>
|
||||
<ion-segment-content id="second">
|
||||
<ion-input value="" label="Email"></ion-input>
|
||||
</ion-segment-content>
|
||||
<ion-segment-content id="third">
|
||||
<ion-img class="img-part" src="https://picsum.photos/200/300"></ion-img>
|
||||
</ion-segment-content>
|
||||
</ion-segment-view>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
// Click the third button
|
||||
await page.locator('ion-segment-button[value="third"]').click();
|
||||
|
||||
// Wait for the content to be scrolled
|
||||
await page.waitForChanges();
|
||||
|
||||
// Wait for the image to load and be visible
|
||||
const imgLocator = page.locator('ion-segment-content#third ion-img');
|
||||
await imgLocator.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
// Wait for any layout adjustments
|
||||
await page.waitForChanges();
|
||||
|
||||
// Check that the third content is visible
|
||||
const segmentView = page.locator('ion-segment-view');
|
||||
const thirdContent = page.locator('ion-segment-content#third');
|
||||
|
||||
const viewBox = await segmentView.boundingBox();
|
||||
const contentBox = await thirdContent.boundingBox();
|
||||
|
||||
if (!viewBox || !contentBox) throw new Error('Bounding box not found');
|
||||
|
||||
// Allow a small tolerance to account for subpixel rendering,
|
||||
// scrollbars, or layout rounding differences
|
||||
const tolerance = 10;
|
||||
expect(contentBox.x).toBeGreaterThanOrEqual(viewBox.x);
|
||||
expect(contentBox.x + contentBox.width).toBeLessThanOrEqual(viewBox.x + viewBox.width + tolerance);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,39 +22,54 @@ export interface FocusVisibleUtility {
|
||||
|
||||
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
|
||||
let currentFocus: Element[] = [];
|
||||
let keyboardMode = true;
|
||||
// Tracks if the last interaction was a pointer event (mouse, touch, pen)
|
||||
// Used to distinguish between pointer and keyboard navigation for focus styling
|
||||
let hadPointerEvent = false;
|
||||
|
||||
const ref = rootEl ? rootEl.shadowRoot! : document;
|
||||
const root = rootEl ? rootEl : document.body;
|
||||
|
||||
// Adds or removes the focused class for styling
|
||||
const setFocus = (elements: Element[]) => {
|
||||
currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED));
|
||||
elements.forEach((el) => el.classList.add(ION_FOCUSED));
|
||||
currentFocus = elements;
|
||||
};
|
||||
const pointerDown = () => {
|
||||
keyboardMode = false;
|
||||
setFocus([]);
|
||||
|
||||
// Do not set focus on pointer interactions
|
||||
const pointerDown = (ev: Event) => {
|
||||
if (ev instanceof PointerEvent && ev.pointerType !== '') {
|
||||
hadPointerEvent = true;
|
||||
// Reset after the event loop so only the immediate focusin is suppressed
|
||||
setTimeout(() => {
|
||||
hadPointerEvent = false;
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear hadPointerEvent so keyboard navigation shows focus
|
||||
// Also, clear focus if the key is not a navigation key
|
||||
const onKeydown = (ev: Event) => {
|
||||
keyboardMode = FOCUS_KEYS.includes((ev as KeyboardEvent).key);
|
||||
if (!keyboardMode) {
|
||||
hadPointerEvent = false;
|
||||
|
||||
const keyboardEvent = ev as KeyboardEvent;
|
||||
if (!FOCUS_KEYS.includes(keyboardEvent.key)) {
|
||||
setFocus([]);
|
||||
}
|
||||
};
|
||||
|
||||
// Set focus if the last interaction was NOT a pointer event
|
||||
// This works around iOS/Safari bugs where keydown is not fired for Tab
|
||||
const onFocusin = (ev: Event) => {
|
||||
if (keyboardMode && ev.composedPath !== undefined) {
|
||||
const toFocus = ev.composedPath().filter((el: any) => {
|
||||
// TODO(FW-2832): type
|
||||
if (el.classList) {
|
||||
return el.classList.contains(ION_FOCUSABLE);
|
||||
}
|
||||
return false;
|
||||
}) as Element[];
|
||||
const target = ev.target as HTMLElement;
|
||||
if (target.classList.contains(ION_FOCUSABLE) && !hadPointerEvent) {
|
||||
const toFocus = ev
|
||||
.composedPath()
|
||||
.filter((el): el is HTMLElement => el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE));
|
||||
setFocus(toFocus);
|
||||
}
|
||||
};
|
||||
|
||||
const onFocusout = () => {
|
||||
if (ref.activeElement === root) {
|
||||
setFocus([]);
|
||||
@@ -64,15 +79,13 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
|
||||
ref.addEventListener('keydown', onKeydown);
|
||||
ref.addEventListener('focusin', onFocusin);
|
||||
ref.addEventListener('focusout', onFocusout);
|
||||
ref.addEventListener('touchstart', pointerDown, { passive: true });
|
||||
ref.addEventListener('mousedown', pointerDown);
|
||||
ref.addEventListener('pointerdown', pointerDown, { passive: true });
|
||||
|
||||
const destroy = () => {
|
||||
ref.removeEventListener('keydown', onKeydown);
|
||||
ref.removeEventListener('focusin', onFocusin);
|
||||
ref.removeEventListener('focusout', onFocusout);
|
||||
ref.removeEventListener('touchstart', pointerDown);
|
||||
ref.removeEventListener('mousedown', pointerDown);
|
||||
ref.removeEventListener('pointerdown', pointerDown);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -33,26 +33,6 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
|
||||
|
||||
const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL;
|
||||
|
||||
// The Ionic bundle is included locally by default unless the test
|
||||
// config passes in the importIonicFromCDN option. This is useful
|
||||
// when testing with the CDN version of Ionic.
|
||||
let ionicCSSImports = `
|
||||
<link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
|
||||
`;
|
||||
let ionicJSImports = `
|
||||
<script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
|
||||
`;
|
||||
|
||||
if (options?.importIonicFromCDN) {
|
||||
ionicCSSImports = `
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
`;
|
||||
ionicJSImports = `
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
`;
|
||||
}
|
||||
|
||||
const output = `
|
||||
<!DOCTYPE html>
|
||||
<html dir="${direction}" lang="en">
|
||||
@@ -60,11 +40,11 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
|
||||
<title>Ionic Playwright Test</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||
${ionicCSSImports}
|
||||
<link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="${baseUrl}/scripts/testing/styles.css" rel="stylesheet" />
|
||||
${palette !== 'light' ? `<link href="${baseUrl}/css/palettes/${palette}.always.css" rel="stylesheet" />` : ''}
|
||||
<script src="${baseUrl}/scripts/testing/scripts.js"></script>
|
||||
${ionicJSImports}
|
||||
<script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
|
||||
<script>
|
||||
window.Ionic = {
|
||||
config: {
|
||||
|
||||
@@ -31,12 +31,6 @@ interface PageOptions {
|
||||
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
|
||||
*/
|
||||
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
|
||||
|
||||
/**
|
||||
* If true, the default Ionic imports will be included
|
||||
* via the CDN instead of the local bundle.
|
||||
*/
|
||||
importIonicFromCDN?: boolean;
|
||||
}
|
||||
|
||||
export interface E2EPage extends Page {
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"core",
|
||||
"packages/*"
|
||||
],
|
||||
"version": "8.6.7"
|
||||
"version": "8.6.2"
|
||||
}
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular-server
|
||||
|
||||
36
packages/angular-server/package-lock.json
generated
36
packages/angular-server/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7"
|
||||
"@ionic/core": "^8.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-eslint/eslint-plugin": "^16.0.0",
|
||||
@@ -1031,12 +1031,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -1386,9 +1386,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -7305,11 +7305,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -7528,9 +7528,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -11285,4 +11285,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Angular SSR Module for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -62,6 +62,6 @@
|
||||
},
|
||||
"prettier": "@ionic/prettier-config",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7"
|
||||
"@ionic/core": "^8.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,49 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** update schematics to support Angular's latest build system ([#30525](https://github.com/ionic-team/ionic-framework/issues/30525)) ([08e3e7a](https://github.com/ionic-team/ionic-framework/commit/08e3e7ab5165baea668571af9845933b5befeb46)), closes [ionic-team/ionic-docs#2091](https://github.com/ionic-team/ionic-docs/issues/2091)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
36
packages/angular/package-lock.json
generated
36
packages/angular/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
@@ -1398,12 +1398,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -2308,9 +2308,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -9936,11 +9936,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -10539,9 +10539,9 @@
|
||||
}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -15194,4 +15194,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/docs
|
||||
|
||||
6
packages/docs/package-lock.json
generated
6
packages/docs/package-lock.json
generated
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/docs",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Pre-packaged API documentation for the Ionic docs.",
|
||||
"main": "core.json",
|
||||
"types": "core.d.ts",
|
||||
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react-router
|
||||
|
||||
52
packages/react-router/package-lock.json
generated
52
packages/react-router/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/react-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/react-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/react": "^8.6.7",
|
||||
"@ionic/react": "^8.6.2",
|
||||
"tslib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -238,12 +238,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -415,12 +415,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/react": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.6.tgz",
|
||||
"integrity": "sha512-GGT/Te2u52PjYPUjxhJRhnZtbAqsxQlSph7GQbRWPlycVG2lSjPkTcSux+CHWHxTN4f80xyIZ6JmRPQvuMlEDw==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.2.tgz",
|
||||
"integrity": "sha512-SXE1RnzGqj0MGKGs6D4UCk4rOghbLYI5qwANdZJuBxlIcrcBJuAySjneuTGt+Y3UHS8W3YZHFujRv2Gvb+zvqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "8.6.6",
|
||||
"@ionic/core": "8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
@@ -669,9 +669,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -4175,11 +4175,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -4281,11 +4281,11 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@ionic/react": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.6.tgz",
|
||||
"integrity": "sha512-GGT/Te2u52PjYPUjxhJRhnZtbAqsxQlSph7GQbRWPlycVG2lSjPkTcSux+CHWHxTN4f80xyIZ6JmRPQvuMlEDw==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.2.tgz",
|
||||
"integrity": "sha512-SXE1RnzGqj0MGKGs6D4UCk4rOghbLYI5qwANdZJuBxlIcrcBJuAySjneuTGt+Y3UHS8W3YZHFujRv2Gvb+zvqQ==",
|
||||
"requires": {
|
||||
"@ionic/core": "8.6.6",
|
||||
"@ionic/core": "8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
}
|
||||
@@ -4422,9 +4422,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -6844,4 +6844,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/react-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "React Router wrapper for @ionic/react",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -36,7 +36,7 @@
|
||||
"dist/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ionic/react": "^8.6.7",
|
||||
"@ionic/react": "^8.6.2",
|
||||
"tslib": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/react
|
||||
|
||||
36
packages/react/package-lock.json
generated
36
packages/react/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/react",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/react",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
@@ -736,12 +736,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -1726,9 +1726,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -12431,11 +12431,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -13110,9 +13110,9 @@
|
||||
}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -20674,4 +20674,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/react",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "React specific wrapper for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -39,7 +39,7 @@
|
||||
"css/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue-router
|
||||
|
||||
52
packages/vue-router/package-lock.json
generated
52
packages/vue-router/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/vue": "^8.6.7"
|
||||
"@ionic/vue": "^8.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
@@ -673,12 +673,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -865,12 +865,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/vue": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.6.tgz",
|
||||
"integrity": "sha512-UBwBslRzTLdFmJR3iHIjwRgpVf06NEzbHBnoatRLcMtz3EIU7lfv5z7nXM78sfA29wD22QL1klU3PUfeiwQvzA==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.2.tgz",
|
||||
"integrity": "sha512-bqhKOxKwxp/aK9DR8NkXeaaPyth0TjgIGpzy28JKd/Q3fJ5eICkEn+gctRlC1L/4nBr6DponSEHfncSeEBQfoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "8.6.6",
|
||||
"@ionic/core": "8.6.2",
|
||||
"@stencil/vue-output-target": "0.10.7",
|
||||
"ionicons": "^7.0.0"
|
||||
}
|
||||
@@ -1523,9 +1523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -8041,11 +8041,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -8156,11 +8156,11 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@ionic/vue": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.6.tgz",
|
||||
"integrity": "sha512-UBwBslRzTLdFmJR3iHIjwRgpVf06NEzbHBnoatRLcMtz3EIU7lfv5z7nXM78sfA29wD22QL1klU3PUfeiwQvzA==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.2.tgz",
|
||||
"integrity": "sha512-bqhKOxKwxp/aK9DR8NkXeaaPyth0TjgIGpzy28JKd/Q3fJ5eICkEn+gctRlC1L/4nBr6DponSEHfncSeEBQfoQ==",
|
||||
"requires": {
|
||||
"@ionic/core": "8.6.6",
|
||||
"@ionic/core": "8.6.2",
|
||||
"@stencil/vue-output-target": "0.10.7",
|
||||
"ionicons": "^7.0.0"
|
||||
}
|
||||
@@ -8624,9 +8624,9 @@
|
||||
}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -12991,4 +12991,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Vue Router integration for @ionic/vue",
|
||||
"scripts": {
|
||||
"test.spec": "jest",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/ionic-team/ionic-framework#readme",
|
||||
"dependencies": {
|
||||
"@ionic/vue": "^8.6.7"
|
||||
"@ionic/vue": "^8.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
|
||||
@@ -3,46 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.6.7](https://github.com/ionic-team/ionic-framework/compare/v8.6.6...v8.6.7) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.6](https://github.com/ionic-team/ionic-framework/compare/v8.6.5...v8.6.6) (2025-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.3](https://github.com/ionic-team/ionic-framework/compare/v8.6.2...v8.6.3) (2025-07-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.6.2](https://github.com/ionic-team/ionic-framework/compare/v8.6.1...v8.6.2) (2025-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/vue
|
||||
|
||||
36
packages/vue/package-lock.json
generated
36
packages/vue/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/vue",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/vue",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"@stencil/vue-output-target": "0.10.7",
|
||||
"ionicons": "^7.0.0"
|
||||
},
|
||||
@@ -222,12 +222,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -674,9 +674,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
@@ -4167,11 +4167,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.6.tgz",
|
||||
"integrity": "sha512-9H7QEFA5NLUgw7ebGRSePR5q5ZcAYJaEdz2+mb2fYNMXauOLeUTngvWWCoVK/qcLgvWTlBnJ1lclYltBWliUCg==",
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -4429,9 +4429,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -6819,4 +6819,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/vue",
|
||||
"version": "8.6.7",
|
||||
"version": "8.6.2",
|
||||
"description": "Vue specific wrapper for @ionic/core",
|
||||
"scripts": {
|
||||
"eslint": "eslint src",
|
||||
@@ -67,7 +67,7 @@
|
||||
"vue-router": "^4.0.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.6.7",
|
||||
"@ionic/core": "^8.6.2",
|
||||
"@stencil/vue-output-target": "0.10.7",
|
||||
"ionicons": "^7.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user