Compare commits

..

5 Commits

Author SHA1 Message Date
Brandy Smith
cb7436f051 chore: build & lint 2025-07-02 13:32:26 -04:00
Brandy Smith
f1dc0b2b06 fix(utils): update to track pointer instead of keyboard 2025-07-02 13:30:38 -04:00
Brandy Smith
0ded8ba19f fix(utils): update focus visible to work with keydown again 2025-07-02 13:30:38 -04:00
Brandy Carney
b09caeb053 fix(utils): update focus visible to remove keydown approach 2025-07-02 13:30:37 -04:00
Brandy Carney
42c9db52a7 test(angular): add new pages to navigate between lazy and standalone tests 2025-07-02 13:29:42 -04:00
50 changed files with 303 additions and 1995 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -618,5 +618,5 @@
*/
:host([disabled]) ::slotted(ion-input-password-toggle),
:host([readonly]) ::slotted(ion-input-password-toggle) {
visibility: hidden;
display: none;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,5 +3,5 @@
"core",
"packages/*"
],
"version": "8.6.7"
"version": "8.6.2"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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": {

View File

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

View File

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

View File

@@ -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": "*"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"
},