Compare commits

..

4 Commits

Author SHA1 Message Date
Brandy Carney
82348df1d1 merge release-8.3.4 (#29981)
v8.3.4
2024-10-30 12:56:31 -04:00
Maria Hutt
99fa7b0cc1 merge release-8.3.3 (#29944)
v8.3.3
2024-10-16 12:13:27 -07:00
Brandy Carney
09b2ed02f0 merge release-8.3.2 (#29921)
v8.3.2
2024-10-02 13:42:06 -04:00
Tanner Reits
1c604dc97a merge release-8.3.1 (#29880)
v8.3.1
2024-09-17 11:28:16 -04:00
206 changed files with 1419 additions and 23097 deletions

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -11,7 +11,7 @@ runs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- name: Install Dependencies
run: npm ci

View File

@@ -11,7 +11,7 @@ runs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- name: Install Dependencies
run: npm install
working-directory: ./core

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -21,7 +21,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
# Provenance requires npm 9.5.0+
- name: Install latest npm
run: npm install -g npm@latest

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:

View File

@@ -5,7 +5,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- name: Install Dependencies
run: npm ci
working-directory: ./core

View File

@@ -15,7 +15,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -8,7 +8,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- name: Install Dependencies
run: npm ci
working-directory: ./core

View File

@@ -8,7 +8,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -8,7 +8,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -8,7 +8,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -9,7 +9,7 @@ runs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 18.x
- uses: actions/download-artifact@v4
with:
path: ./artifacts

View File

@@ -11,7 +11,7 @@ jobs:
issues: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@c015a6a3f410f12f58255c3d085fd774312f7a2f # v2.1.2
uses: pozil/auto-assign-issue@c5bca5027e680b9e8411b826d16947afd8c76b32 # v2.0.0
with:
assignees: brandyscarney, thetaPC, joselrio, rugoncalves, BenOsodrac, JoaoFerreira-FrontEnd, OS-giulianasilva, tanner-reits
numOfAssignee: 1

View File

@@ -140,7 +140,7 @@ jobs:
strategy:
fail-fast: false
matrix:
apps: [ng16, ng17, ng18, ng19]
apps: [ng16, ng17, ng18]
needs: [build-angular, build-angular-server]
runs-on: ubuntu-latest
steps:

View File

@@ -150,7 +150,7 @@ jobs:
strategy:
fail-fast: false
matrix:
apps: [ng16, ng17, ng18, ng19]
apps: [ng16, ng17, ng18]
needs: [build-angular, build-angular-server]
runs-on: ubuntu-latest
steps:

View File

@@ -3,62 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.4.3](https://github.com/ionic-team/ionic-framework/compare/v8.4.2...v8.4.3) (2025-01-29)
### Bug Fixes
* **vue:** update Stencil Vue output target ([#30159](https://github.com/ionic-team/ionic-framework/issues/30159)) ([eb725fc](https://github.com/ionic-team/ionic-framework/commit/eb725fce6eb15facd8a1c21be11a1b2d46336479))
## [8.4.2](https://github.com/ionic-team/ionic-framework/compare/v8.4.1...v8.4.2) (2025-01-22)
### Bug Fixes
* **segment:** add logic to connect to segment-view in `componentDidLoad()` callback ([#30060](https://github.com/ionic-team/ionic-framework/issues/30060)) ([000f553](https://github.com/ionic-team/ionic-framework/commit/000f55303e459c583e642337fb1894f419f37d48)), closes [#30000](https://github.com/ionic-team/ionic-framework/issues/30000)
* **select-modal:** match radio styles to iOS native ([#30119](https://github.com/ionic-team/ionic-framework/issues/30119)) ([3f8346e](https://github.com/ionic-team/ionic-framework/commit/3f8346e718ae3a6eb5008d739f10b6898b84ca9b))
## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)
### Bug Fixes
* **header:** use aria attributes to hide small title when collapsed ([#30027](https://github.com/ionic-team/ionic-framework/issues/30027)) ([23763ab](https://github.com/ionic-team/ionic-framework/commit/23763abf797f9a4ba8262225760f718e9dcc4782)), closes [#29347](https://github.com/ionic-team/ionic-framework/issues/29347)
* **menu:** hide from screen readers while animating ([#30036](https://github.com/ionic-team/ionic-framework/issues/30036)) ([845071c](https://github.com/ionic-team/ionic-framework/commit/845071c97a856d45eb5e0bb81d9c270bc38bb604))
* **overlays:** announce info after opening based on platform ([#30025](https://github.com/ionic-team/ionic-framework/issues/30025)) ([f6188c4](https://github.com/ionic-team/ionic-framework/commit/f6188c47e9278fe69fd9d250c65156edbe5ef32e))
* **overlays:** focus management with checkbox/radio ([#30026](https://github.com/ionic-team/ionic-framework/issues/30026)) ([8ee42bb](https://github.com/ionic-team/ionic-framework/commit/8ee42bbc1e0bf4731d20040c7853756722f1a4b2))
* **toast:** swipe gesture works with custom container layout ([#29999](https://github.com/ionic-team/ionic-framework/issues/29999)) ([470decc](https://github.com/ionic-team/ionic-framework/commit/470decca7b6b89ef74095ef0bb7909b93640cd78)), closes [#29998](https://github.com/ionic-team/ionic-framework/issues/29998)
# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)
### Bug Fixes
* **alert:** use correct heading structure for subHeader when header exists ([#29964](https://github.com/ionic-team/ionic-framework/issues/29964)) ([0fdcb32](https://github.com/ionic-team/ionic-framework/commit/0fdcb32ce0f99b284b314f79f7d0b071bc37faec))
### Features
* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b))
* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6))
* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040))
## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)

View File

@@ -3,59 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.4.3](https://github.com/ionic-team/ionic-framework/compare/v8.4.2...v8.4.3) (2025-01-29)
**Note:** Version bump only for package @ionic/core
## [8.4.2](https://github.com/ionic-team/ionic-framework/compare/v8.4.1...v8.4.2) (2025-01-22)
### Bug Fixes
* **segment:** add logic to connect to segment-view in `componentDidLoad()` callback ([#30060](https://github.com/ionic-team/ionic-framework/issues/30060)) ([000f553](https://github.com/ionic-team/ionic-framework/commit/000f55303e459c583e642337fb1894f419f37d48)), closes [#30000](https://github.com/ionic-team/ionic-framework/issues/30000)
* **select-modal:** match radio styles to iOS native ([#30119](https://github.com/ionic-team/ionic-framework/issues/30119)) ([3f8346e](https://github.com/ionic-team/ionic-framework/commit/3f8346e718ae3a6eb5008d739f10b6898b84ca9b))
## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)
### Bug Fixes
* **header:** use aria attributes to hide small title when collapsed ([#30027](https://github.com/ionic-team/ionic-framework/issues/30027)) ([23763ab](https://github.com/ionic-team/ionic-framework/commit/23763abf797f9a4ba8262225760f718e9dcc4782)), closes [#29347](https://github.com/ionic-team/ionic-framework/issues/29347)
* **menu:** hide from screen readers while animating ([#30036](https://github.com/ionic-team/ionic-framework/issues/30036)) ([845071c](https://github.com/ionic-team/ionic-framework/commit/845071c97a856d45eb5e0bb81d9c270bc38bb604))
* **overlays:** announce info after opening based on platform ([#30025](https://github.com/ionic-team/ionic-framework/issues/30025)) ([f6188c4](https://github.com/ionic-team/ionic-framework/commit/f6188c47e9278fe69fd9d250c65156edbe5ef32e))
* **overlays:** focus management with checkbox/radio ([#30026](https://github.com/ionic-team/ionic-framework/issues/30026)) ([8ee42bb](https://github.com/ionic-team/ionic-framework/commit/8ee42bbc1e0bf4731d20040c7853756722f1a4b2))
* **toast:** swipe gesture works with custom container layout ([#29999](https://github.com/ionic-team/ionic-framework/issues/29999)) ([470decc](https://github.com/ionic-team/ionic-framework/commit/470decca7b6b89ef74095ef0bb7909b93640cd78)), closes [#29998](https://github.com/ionic-team/ionic-framework/issues/29998)
# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)
### Bug Fixes
* **alert:** use correct heading structure for subHeader when header exists ([#29964](https://github.com/ionic-team/ionic-framework/issues/29964)) ([0fdcb32](https://github.com/ionic-team/ionic-framework/commit/0fdcb32ce0f99b284b314f79f7d0b071bc37faec))
### Features
* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b))
* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6))
* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040))
## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)

View File

@@ -1000,15 +1000,15 @@ ion-menu,prop,menuId,string | undefined,undefined,false,true
ion-menu,prop,side,"end" | "start",'start',false,true
ion-menu,prop,swipeGesture,boolean,true,false,false
ion-menu,prop,type,"overlay" | "push" | "reveal" | undefined,undefined,false,false
ion-menu,method,close,close(animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,close,close(animated?: boolean) => Promise<boolean>
ion-menu,method,isActive,isActive() => Promise<boolean>
ion-menu,method,isOpen,isOpen() => Promise<boolean>
ion-menu,method,open,open(animated?: boolean) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise<boolean>
ion-menu,method,toggle,toggle(animated?: boolean) => Promise<boolean>
ion-menu,event,ionDidClose,MenuCloseEventDetail,true
ion-menu,event,ionDidClose,void,true
ion-menu,event,ionDidOpen,void,true
ion-menu,event,ionWillClose,MenuCloseEventDetail,true
ion-menu,event,ionWillClose,void,true
ion-menu,event,ionWillOpen,void,true
ion-menu,css-prop,--background,ios
ion-menu,css-prop,--background,md
@@ -1542,7 +1542,6 @@ ion-segment,css-prop,--background,ios
ion-segment,css-prop,--background,md
ion-segment-button,shadow
ion-segment-button,prop,contentId,string | undefined,undefined,false,true
ion-segment-button,prop,disabled,boolean,false,false,false
ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
@@ -1608,12 +1607,6 @@ ion-segment-button,part,indicator
ion-segment-button,part,indicator-background
ion-segment-button,part,native
ion-segment-content,shadow
ion-segment-view,shadow
ion-segment-view,prop,disabled,boolean,false,false,false
ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true
ion-select,shadow
ion-select,prop,cancelText,string,'Cancel',false,false
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
@@ -1621,7 +1614,7 @@ ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean)
ion-select,prop,disabled,boolean,false,false,false
ion-select,prop,expandedIcon,string | undefined,undefined,false,false
ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false
ion-select,prop,interface,"action-sheet" | "alert" | "modal" | "popover",'alert',false,false
ion-select,prop,interface,"action-sheet" | "alert" | "popover",'alert',false,false
ion-select,prop,interfaceOptions,any,{},false,false
ion-select,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
ion-select,prop,label,string | undefined,undefined,false,false
@@ -1679,11 +1672,6 @@ ion-select,part,label
ion-select,part,placeholder
ion-select,part,text
ion-select-modal,scoped
ion-select-modal,prop,header,string | undefined,undefined,false,false
ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false
ion-select-modal,prop,options,SelectModalOption[],[],false,false
ion-select-option,shadow
ion-select-option,prop,disabled,boolean,false,false,false
ion-select-option,prop,value,any,undefined,false,false

683
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "8.4.3",
"version": "8.3.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "8.4.3",
"version": "8.3.4",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.20.0",
@@ -19,16 +19,16 @@
"@capacitor/haptics": "^6.0.0",
"@capacitor/keyboard": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",
"@clack/prompts": "^0.9.0",
"@clack/prompts": "^0.7.0",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@playwright/test": "^1.46.1",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.10.0",
"@stencil/angular-output-target": "^0.8.4",
"@stencil/react-output-target": "0.5.3",
"@stencil/sass": "^3.0.9",
"@stencil/vue-output-target": "^0.9.0",
"@stencil/vue-output-target": "^0.8.9",
"@types/jest": "^29.5.6",
"@types/node": "^14.6.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",
@@ -319,18 +319,18 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -400,13 +400,10 @@
}
},
"node_modules/@babel/parser": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz",
"integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.26.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -644,13 +641,14 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz",
"integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
@@ -663,45 +661,45 @@
"dev": true
},
"node_modules/@capacitor/core": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.1.2.tgz",
"integrity": "sha512-xFy1/4qLFLp5WCIzIhtwUuVNNoz36+V7/BzHmLqgVJcvotc4MMjswW/TshnPQaLLujEOaLkA4h8ZJ0uoK3ImGg==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@capacitor/haptics": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.1.tgz",
"integrity": "sha512-Q8hedLwfwTSWEYc3eoATzkdKHBaIceYe5bd7FjxQCENNH0is5Ft0EjSRPz/xpTn39ebK0ooZBDBCwsyl6tjiTA==",
"dev": true,
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/keyboard": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.2.tgz",
"integrity": "sha512-fOfO3rQ0ZXuTHpK03INVTwmBnpqMiH8EHPpNaHjwjKwdrVRWBvtgIFhuyHNXh53rdcXw+uHB+1RIiNabnCrITw==",
"dev": true,
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/status-bar": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.1.tgz",
"integrity": "sha512-Usd9hZZQVAqy+jJfL7jRcYI7dcsxN09Na1yttwdl+F1bk3Ztoukk7CGPDm5VgKUSs53ihQBOy1+sczCACxhNiw==",
"dev": true,
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@clack/core": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.1.tgz",
"integrity": "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
"integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
"dev": true,
"dependencies": {
"picocolors": "^1.0.0",
@@ -709,16 +707,32 @@
}
},
"node_modules/@clack/prompts": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz",
"integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
"integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
"bundleDependencies": [
"is-unicode-supported"
],
"dev": true,
"dependencies": {
"@clack/core": "0.4.1",
"@clack/core": "^0.3.3",
"is-unicode-supported": "*",
"picocolors": "^1.0.0",
"sisteransi": "^1.0.5"
}
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1664,9 +1678,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
@@ -1801,9 +1815,9 @@
}
},
"node_modules/@stencil/angular-output-target": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.10.2.tgz",
"integrity": "sha512-jPRa2NMAPtm/iMY+mUaWATbIhgY5zPJfUNQyF8nwC0rMrfXifPoRCf6BbH2S4Gy7SX0X4hlP+jAbVUjQNg/P+Q==",
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.4.tgz",
"integrity": "sha512-QvmHTueXXs5vB9W2L12uEzFmAuR8sqATJV2b+SCFmYsjJSaymiSqR3dKo2wnr0tZiTgU1t16BWaUKiSh3wPXpw==",
"dev": true,
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
@@ -1846,21 +1860,12 @@
}
},
"node_modules/@stencil/vue-output-target": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.2.tgz",
"integrity": "sha512-AeBmfo8bQhtob4VKpYTNiCoqh50MeXUwRgYLyO/JxRgAAK9GSfenNrUxXDrK0DK65SWsx/GCOsRwWbfOveorOQ==",
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.9.tgz",
"integrity": "sha512-1yuapCWYViLlxGlEaeta2wryq4M5zZxxBa+4rEBp54VwW2W/trlzPv0IJyw6I3Il51rHYm2WmWlBLOGmoMyW9Q==",
"dev": true,
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0",
"vue": "^3.4.38"
},
"peerDependenciesMeta": {
"@stencil/core": {
"optional": true
},
"vue": {
"optional": false
}
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
}
},
"node_modules/@stylelint/postcss-css-in-js": {
@@ -2494,171 +2499,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/parser": "^7.25.3",
"@vue/shared": "3.5.13",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-core/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/@vue/compiler-core/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"peer": true
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-core": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.11",
"postcss": "^8.4.48",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"peer": true
},
"node_modules/@vue/compiler-sfc/node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.13",
"@vue/runtime-core": "3.5.13",
"@vue/shared": "3.5.13",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13"
},
"peerDependencies": {
"vue": "3.5.13"
}
},
"node_modules/@vue/shared": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"dev": true,
"peer": true
},
"node_modules/@zeit/schemas": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
@@ -3343,9 +3183,9 @@
]
},
"node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true,
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
@@ -3937,13 +3777,6 @@
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"peer": true
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -7751,16 +7584,6 @@
"node": ">=10"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -8092,25 +7915,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8432,9 +8236,9 @@
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": {
@@ -9299,16 +9103,6 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@@ -9889,6 +9683,15 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -10222,28 +10025,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/vue": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-sfc": "3.5.13",
"@vue/runtime-dom": "3.5.13",
"@vue/server-renderer": "3.5.13",
"@vue/shared": "3.5.13"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -10724,15 +10505,15 @@
}
},
"@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true
},
"@babel/helper-validator-option": {
@@ -10786,13 +10567,10 @@
}
},
"@babel/parser": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz",
"integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
"dev": true,
"requires": {
"@babel/types": "^7.26.5"
}
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true
},
"@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
@@ -10961,13 +10739,14 @@
}
},
"@babel/types": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz",
"integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
},
"@bcoe/v8-coverage": {
@@ -10977,39 +10756,39 @@
"dev": true
},
"@capacitor/core": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.1.2.tgz",
"integrity": "sha512-xFy1/4qLFLp5WCIzIhtwUuVNNoz36+V7/BzHmLqgVJcvotc4MMjswW/TshnPQaLLujEOaLkA4h8ZJ0uoK3ImGg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"@capacitor/haptics": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.1.tgz",
"integrity": "sha512-Q8hedLwfwTSWEYc3eoATzkdKHBaIceYe5bd7FjxQCENNH0is5Ft0EjSRPz/xpTn39ebK0ooZBDBCwsyl6tjiTA==",
"dev": true,
"requires": {}
},
"@capacitor/keyboard": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.2.tgz",
"integrity": "sha512-fOfO3rQ0ZXuTHpK03INVTwmBnpqMiH8EHPpNaHjwjKwdrVRWBvtgIFhuyHNXh53rdcXw+uHB+1RIiNabnCrITw==",
"dev": true,
"requires": {}
},
"@capacitor/status-bar": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.1.tgz",
"integrity": "sha512-Usd9hZZQVAqy+jJfL7jRcYI7dcsxN09Na1yttwdl+F1bk3Ztoukk7CGPDm5VgKUSs53ihQBOy1+sczCACxhNiw==",
"dev": true,
"requires": {}
},
"@clack/core": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.1.tgz",
"integrity": "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
"integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
"dev": true,
"requires": {
"picocolors": "^1.0.0",
@@ -11017,14 +10796,22 @@
}
},
"@clack/prompts": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz",
"integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
"integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
"dev": true,
"requires": {
"@clack/core": "0.4.1",
"@clack/core": "^0.3.3",
"is-unicode-supported": "*",
"picocolors": "^1.0.0",
"sisteransi": "^1.0.5"
},
"dependencies": {
"is-unicode-supported": {
"version": "1.3.0",
"bundled": true,
"dev": true
}
}
},
"@eslint-community/eslint-utils": {
@@ -11696,9 +11483,9 @@
"dev": true
},
"@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"@jridgewell/trace-mapping": {
@@ -11804,9 +11591,9 @@
}
},
"@stencil/angular-output-target": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.10.2.tgz",
"integrity": "sha512-jPRa2NMAPtm/iMY+mUaWATbIhgY5zPJfUNQyF8nwC0rMrfXifPoRCf6BbH2S4Gy7SX0X4hlP+jAbVUjQNg/P+Q==",
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-0.8.4.tgz",
"integrity": "sha512-QvmHTueXXs5vB9W2L12uEzFmAuR8sqATJV2b+SCFmYsjJSaymiSqR3dKo2wnr0tZiTgU1t16BWaUKiSh3wPXpw==",
"dev": true,
"requires": {}
},
@@ -11830,9 +11617,9 @@
"requires": {}
},
"@stencil/vue-output-target": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.2.tgz",
"integrity": "sha512-AeBmfo8bQhtob4VKpYTNiCoqh50MeXUwRgYLyO/JxRgAAK9GSfenNrUxXDrK0DK65SWsx/GCOsRwWbfOveorOQ==",
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.9.tgz",
"integrity": "sha512-1yuapCWYViLlxGlEaeta2wryq4M5zZxxBa+4rEBp54VwW2W/trlzPv0IJyw6I3Il51rHYm2WmWlBLOGmoMyW9Q==",
"dev": true,
"requires": {}
},
@@ -12276,149 +12063,6 @@
"eslint-visitor-keys": "^3.4.1"
}
},
"@vue/compiler-core": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
"dev": true,
"peer": true,
"requires": {
"@babel/parser": "^7.25.3",
"@vue/shared": "3.5.13",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
},
"dependencies": {
"entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"peer": true
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"peer": true
}
}
},
"@vue/compiler-dom": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
"dev": true,
"peer": true,
"requires": {
"@vue/compiler-core": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"@vue/compiler-sfc": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
"dev": true,
"peer": true,
"requires": {
"@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.11",
"postcss": "^8.4.48",
"source-map-js": "^1.2.0"
},
"dependencies": {
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"peer": true
},
"postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"peer": true,
"requires": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
}
}
}
},
"@vue/compiler-ssr": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
"dev": true,
"peer": true,
"requires": {
"@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"@vue/reactivity": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
"dev": true,
"peer": true,
"requires": {
"@vue/shared": "3.5.13"
}
},
"@vue/runtime-core": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
"dev": true,
"peer": true,
"requires": {
"@vue/reactivity": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"@vue/runtime-dom": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
"dev": true,
"peer": true,
"requires": {
"@vue/reactivity": "3.5.13",
"@vue/runtime-core": "3.5.13",
"@vue/shared": "3.5.13",
"csstype": "^3.1.3"
}
},
"@vue/server-renderer": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
"dev": true,
"peer": true,
"requires": {
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"@vue/shared": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"dev": true,
"peer": true
},
"@zeit/schemas": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
@@ -12893,9 +12537,9 @@
"dev": true
},
"chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true
},
"chalk-template": {
@@ -13309,13 +12953,6 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"peer": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -16120,16 +15757,6 @@
"yallist": "^4.0.0"
}
},
"magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"peer": true,
"requires": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -16364,13 +15991,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"peer": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -16609,9 +16229,9 @@
"dev": true
},
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
@@ -17230,13 +16850,6 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"peer": true
},
"source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@@ -17692,6 +17305,12 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -17951,20 +17570,6 @@
"unist-util-stringify-position": "^2.0.0"
}
},
"vue": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"dev": true,
"peer": true,
"requires": {
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-sfc": "3.5.13",
"@vue/runtime-dom": "3.5.13",
"@vue/server-renderer": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "8.4.3",
"version": "8.3.4",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -41,16 +41,16 @@
"@capacitor/haptics": "^6.0.0",
"@capacitor/keyboard": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",
"@clack/prompts": "^0.9.0",
"@clack/prompts": "^0.7.0",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@playwright/test": "^1.46.1",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.10.0",
"@stencil/angular-output-target": "^0.8.4",
"@stencil/react-output-target": "0.5.3",
"@stencil/sass": "^3.0.9",
"@stencil/vue-output-target": "^0.9.0",
"@stencil/vue-output-target": "^0.8.9",
"@types/jest": "^29.5.6",
"@types/node": "^14.6.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",

View File

@@ -18,7 +18,7 @@ import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
import { SpinnerTypes } from "./components/spinner/spinner-configs";
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
import { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface";
import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
import { ViewController } from "./components/nav/view-controller";
@@ -34,9 +34,7 @@ import { NavigationHookCallback } from "./components/route/route-interface";
import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
import { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
@@ -55,7 +53,7 @@ export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
export { SpinnerTypes } from "./components/spinner/spinner-configs";
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
export { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface";
export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
export { ViewController } from "./components/nav/view-controller";
@@ -71,9 +69,7 @@ export { NavigationHookCallback } from "./components/route/route-interface";
export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
export { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
@@ -643,7 +639,6 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
"setFocus": () => Promise<void>;
/**
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
*/
@@ -1601,7 +1596,7 @@ export namespace Components {
/**
* Closes the menu. If the menu is already closed or it can't be closed, it returns `false`.
*/
"close": (animated?: boolean, role?: string) => Promise<boolean>;
"close": (animated?: boolean) => Promise<boolean>;
/**
* The `id` of the main content. When using a router this is typically `ion-router-outlet`. When not using a router, this is typically your main view's `ion-content`. This is not the id of the `ion-content` inside of your `ion-menu`.
*/
@@ -1633,7 +1628,7 @@ export namespace Components {
/**
* Opens or closes the button. If the operation can't be completed successfully, it returns `false`.
*/
"setOpen": (shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>;
"setOpen": (shouldOpen: boolean, animated?: boolean) => Promise<boolean>;
/**
* Which side of the view the menu should be placed.
*/
@@ -2284,7 +2279,7 @@ export namespace Components {
*/
"name": string;
"setButtonTabindex": (value: number) => Promise<void>;
"setFocus": (ev?: globalThis.Event) => Promise<void>;
"setFocus": (ev: globalThis.Event) => Promise<void>;
/**
* the value of the radio.
*/
@@ -2303,7 +2298,6 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
"setFocus": () => Promise<void>;
/**
* the value of the radio group.
*/
@@ -2699,10 +2693,6 @@ export namespace Components {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@@ -2725,19 +2715,6 @@ export namespace Components {
*/
"value": SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled": boolean;
/**
* @param id : The id of the segment content to display.
* @param smoothScroll : Whether to animate the scroll transition.
*/
"setContent": (id: string, smoothScroll?: boolean) => Promise<void>;
}
interface IonSelect {
/**
* The text to display on the cancel button.
@@ -2764,11 +2741,11 @@ export namespace Components {
*/
"fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
* The interface the select should use: `action-sheet`, `popover` or `alert`.
*/
"interface": SelectInterface;
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
*/
"interfaceOptions": any;
/**
@@ -2825,11 +2802,6 @@ export namespace Components {
*/
"value"?: any | null;
}
interface IonSelectModal {
"header"?: string;
"multiple"?: boolean;
"options": SelectModalOption[];
}
interface IonSelectOption {
/**
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
@@ -3444,10 +3416,6 @@ export interface IonSegmentCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentElement;
}
export interface IonSegmentViewCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentViewElement;
}
export interface IonSelectCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSelectElement;
@@ -4001,9 +3969,9 @@ declare global {
};
interface HTMLIonMenuElementEventMap {
"ionWillOpen": void;
"ionWillClose": MenuCloseEventDetail;
"ionWillClose": void;
"ionDidOpen": void;
"ionDidClose": MenuCloseEventDetail;
"ionDidClose": void;
"ionMenuChange": MenuChangeEventDetail;
}
interface HTMLIonMenuElement extends Components.IonMenu, HTMLStencilElement {
@@ -4444,29 +4412,6 @@ declare global {
prototype: HTMLIonSegmentButtonElement;
new (): HTMLIonSegmentButtonElement;
};
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
}
var HTMLIonSegmentContentElement: {
prototype: HTMLIonSegmentContentElement;
new (): HTMLIonSegmentContentElement;
};
interface HTMLIonSegmentViewElementEventMap {
"ionSegmentViewScroll": SegmentViewScrollEvent;
}
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
addEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLIonSegmentViewElement: {
prototype: HTMLIonSegmentViewElement;
new (): HTMLIonSegmentViewElement;
};
interface HTMLIonSelectElementEventMap {
"ionChange": SelectChangeEventDetail;
"ionCancel": void;
@@ -4489,12 +4434,6 @@ declare global {
prototype: HTMLIonSelectElement;
new (): HTMLIonSelectElement;
};
interface HTMLIonSelectModalElement extends Components.IonSelectModal, HTMLStencilElement {
}
var HTMLIonSelectModalElement: {
prototype: HTMLIonSelectModalElement;
new (): HTMLIonSelectModalElement;
};
interface HTMLIonSelectOptionElement extends Components.IonSelectOption, HTMLStencilElement {
}
var HTMLIonSelectOptionElement: {
@@ -4782,10 +4721,7 @@ declare global {
"ion-searchbar": HTMLIonSearchbarElement;
"ion-segment": HTMLIonSegmentElement;
"ion-segment-button": HTMLIonSegmentButtonElement;
"ion-segment-content": HTMLIonSegmentContentElement;
"ion-segment-view": HTMLIonSegmentViewElement;
"ion-select": HTMLIonSelectElement;
"ion-select-modal": HTMLIonSelectModalElement;
"ion-select-option": HTMLIonSelectOptionElement;
"ion-select-popover": HTMLIonSelectPopoverElement;
"ion-skeleton-text": HTMLIonSkeletonTextElement;
@@ -6428,7 +6364,7 @@ declare namespace LocalJSX {
/**
* Emitted when the menu is closed.
*/
"onIonDidClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
"onIonDidClose"?: (event: IonMenuCustomEvent<void>) => void;
/**
* Emitted when the menu is open.
*/
@@ -6440,7 +6376,7 @@ declare namespace LocalJSX {
/**
* Emitted when the menu is about to be closed.
*/
"onIonWillClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
"onIonWillClose"?: (event: IonMenuCustomEvent<void>) => void;
/**
* Emitted when the menu is about to be opened.
*/
@@ -7514,10 +7450,6 @@ declare namespace LocalJSX {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@@ -7539,18 +7471,6 @@ declare namespace LocalJSX {
*/
"value"?: SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled"?: boolean;
/**
* Emitted when the segment view is scrolled.
*/
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<SegmentViewScrollEvent>) => void;
}
interface IonSelect {
/**
* The text to display on the cancel button.
@@ -7577,11 +7497,11 @@ declare namespace LocalJSX {
*/
"fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
* The interface the select should use: `action-sheet`, `popover` or `alert`.
*/
"interface"?: SelectInterface;
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
*/
"interfaceOptions"?: any;
/**
@@ -7657,11 +7577,6 @@ declare namespace LocalJSX {
*/
"value"?: any | null;
}
interface IonSelectModal {
"header"?: string;
"multiple"?: boolean;
"options"?: SelectModalOption[];
}
interface IonSelectOption {
/**
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
@@ -8247,10 +8162,7 @@ declare namespace LocalJSX {
"ion-searchbar": IonSearchbar;
"ion-segment": IonSegment;
"ion-segment-button": IonSegmentButton;
"ion-segment-content": IonSegmentContent;
"ion-segment-view": IonSegmentView;
"ion-select": IonSelect;
"ion-select-modal": IonSelectModal;
"ion-select-option": IonSelectOption;
"ion-select-popover": IonSelectPopover;
"ion-skeleton-text": IonSkeletonText;
@@ -8349,10 +8261,7 @@ declare module "@stencil/core" {
"ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes<HTMLIonSearchbarElement>;
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
"ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes<HTMLIonSegmentContentElement>;
"ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes<HTMLIonSegmentViewElement>;
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
"ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes<HTMLIonSelectModalElement>;
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
"ion-skeleton-text": LocalJSX.IonSkeletonText & JSXBase.HTMLAttributes<HTMLIonSkeletonTextElement>;

View File

@@ -730,12 +730,10 @@ export class Alert implements ComponentInterface, OverlayInterface {
const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert';
/**
* Use both the header and subHeader ids if they are defined.
* If only the header is defined, use the header id.
* If only the subHeader is defined, use the subHeader id.
* If neither are defined, do not set aria-labelledby.
* If the header is defined, use that. Otherwise, fall back to the subHeader.
* If neither is defined, don't set aria-labelledby.
*/
const ariaLabelledBy = header && subHeader ? `${hdrId} ${subHdrId}` : header ? hdrId : subHeader ? subHdrId : null;
const ariaLabelledBy = header ? hdrId : subHeader ? subHdrId : null;
return (
<Host
@@ -768,18 +766,11 @@ export class Alert implements ComponentInterface, OverlayInterface {
{header}
</h2>
)}
{/* If no header exists, subHeader should be the highest heading level, h2 */}
{subHeader && !header && (
{subHeader && (
<h2 id={subHdrId} class="alert-sub-title">
{subHeader}
</h2>
)}
{/* If a header exists, subHeader should be one level below it, h3 */}
{subHeader && header && (
<h3 id={subHdrId} class="alert-sub-title">
{subHeader}
</h3>
)}
</div>
{this.renderAlertMessage(msgId)}

View File

@@ -17,27 +17,6 @@ const testAria = async (
const alert = page.locator('ion-alert');
const header = alert.locator('.alert-title');
const subHeader = alert.locator('.alert-sub-title');
// If a header exists, it should be an h2 element
if ((await header.count()) > 0) {
const headerTagName = await header.evaluate((el) => el.tagName);
expect(headerTagName).toBe('H2');
}
// If a header and subHeader exist, the subHeader should be an h3 element
if ((await header.count()) > 0 && (await subHeader.count()) > 0) {
const subHeaderTagName = await subHeader.evaluate((el) => el.tagName);
expect(subHeaderTagName).toBe('H3');
}
// If a subHeader exists without a header, the subHeader should be an h2 element
if ((await header.count()) === 0 && (await subHeader.count()) > 0) {
const subHeaderTagName = await subHeader.evaluate((el) => el.tagName);
expect(subHeaderTagName).toBe('H2');
}
/**
* expect().toHaveAttribute() can't check for a null value, so grab and check
* the values manually instead.
@@ -145,24 +124,16 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
await page.goto(`/src/components/alert/test/a11y`, config);
});
test('should have aria-labelledby set to both when header and subHeader are set', async ({ page }) => {
await testAria(page, 'bothHeadersOnly', 'alert-1-hdr alert-1-sub-hdr', null);
});
test('should have aria-labelledby set when only header is set', async ({ page }) => {
await testAria(page, 'headerOnly', 'alert-1-hdr', null);
});
test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => {
await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', null);
test('should have aria-labelledby when header is set', async ({ page }) => {
await testAria(page, 'noMessage', 'alert-1-hdr', null);
});
test('should have aria-describedby when message is set', async ({ page }) => {
await testAria(page, 'noHeaders', null, 'alert-1-msg');
});
test('should have aria-labelledby and aria-describedby when headers and message are set', async ({ page }) => {
await testAria(page, 'headersAndMessage', 'alert-1-hdr alert-1-sub-hdr', 'alert-1-msg');
test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => {
await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', 'alert-1-msg');
});
test('should allow for manually specifying aria attributes', async ({ page }) => {
@@ -308,10 +279,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
await expect(page).toHaveScreenshot(screenshot(`alert-radio-scale`));
});
test('should scale text on larger font sizes with text fields', async ({ page, skip }) => {
// TODO(ROU-8158): unskip this test when a solution is found
skip.browser('chromium', 'Rendering is flaky in Chrome.');
test('should scale text on larger font sizes with text fields', async ({ page }) => {
await page.setContent(
`
<style>

View File

@@ -19,11 +19,10 @@
<main class="ion-padding">
<h1>Alert - A11y</h1>
<button class="expand" id="bothHeadersOnly" onclick="presentBothHeadersOnly()">Both Headers Only</button>
<button class="expand" id="headerOnly" onclick="presentHeaderOnly()">Header Only</button>
<button class="expand" id="bothHeaders" onclick="presentBothHeaders()">Both Headers</button>
<button class="expand" id="subHeaderOnly" onclick="presentSubHeaderOnly()">Subheader Only</button>
<button class="expand" id="noHeaders" onclick="presentNoHeaders()">No Headers</button>
<button class="expand" id="headersAndMessage" onclick="presentHeadersAndMessage()">Headers and Message</button>
<button class="expand" id="noMessage" onclick="presentNoMessage()">No Message</button>
<button class="expand" id="customAria" onclick="presentCustomAria()">Custom Aria</button>
<button class="expand" id="ariaLabelButton" onclick="presentAriaLabelButton()">Aria Label Button</button>
<button class="expand" id="checkbox" onclick="presentAlertCheckbox()">Checkbox</button>
@@ -35,17 +34,11 @@
await alert.present();
}
function presentBothHeadersOnly() {
function presentBothHeaders() {
openAlert({
header: 'Header',
subHeader: 'Subtitle',
buttons: ['OK'],
});
}
function presentHeaderOnly() {
openAlert({
header: 'Header',
message: 'This is an alert message.',
buttons: ['OK'],
});
}
@@ -53,6 +46,7 @@
function presentSubHeaderOnly() {
openAlert({
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK'],
});
}
@@ -64,11 +58,10 @@
});
}
function presentHeadersAndMessage() {
function presentNoMessage() {
openAlert({
header: 'Header',
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK'],
});
}

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core';
import { Component, Element, Event, Host, Prop, h } from '@stencil/core';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -121,9 +121,7 @@ export class Checkbox implements ComponentInterface {
};
}
/** @internal */
@Method()
async setFocus() {
private setFocus() {
if (this.focusEl) {
this.focusEl.focus();
}

View File

@@ -167,34 +167,13 @@ export const handleToolbarIntersection = (
export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
const headerEl = headerIndex.el;
const toolbars = headerIndex.toolbars;
const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
if (active) {
headerEl.classList.remove('header-collapse-condense-inactive');
ionTitles.forEach((ionTitle) => {
if (ionTitle) {
ionTitle.removeAttribute('aria-hidden');
}
});
headerEl.removeAttribute('aria-hidden');
} else {
headerEl.classList.add('header-collapse-condense-inactive');
/**
* The small title should only be accessed by screen readers
* when the large title collapses into the small title due
* to scrolling.
*
* Originally, the header was given `aria-hidden="true"`
* but this caused issues with screen readers not being
* able to access any focusable elements within the header.
*/
ionTitles.forEach((ionTitle) => {
if (ionTitle) {
ionTitle.setAttribute('aria-hidden', 'true');
}
});
headerEl.setAttribute('aria-hidden', 'true');
}
};

View File

@@ -3,19 +3,13 @@ import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('header: condense'), () => {
test('should hide small title from screen readers when collapsed', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/29347',
});
test('should be hidden from screen readers when collapsed', async ({ page }) => {
await page.goto('/src/components/header/test/condense', config);
const largeTitleHeader = page.locator('#largeTitleHeader');
const smallTitleHeader = page.locator('#smallTitleHeader');
const smallTitle = smallTitleHeader.locator('ion-title');
const content = page.locator('ion-content');
await expect(smallTitle).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
await expect(largeTitleHeader).toHaveScreenshot(screenshot(`header-condense-large-title-initial-diff`));
@@ -30,7 +24,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
* Playwright can't do .not.toHaveAttribute() because a value is expected,
* and toHaveAttribute can't accept a value of type null.
*/
const ariaHidden = await smallTitle.getAttribute('aria-hidden');
const ariaHidden = await smallTitleHeader.getAttribute('aria-hidden');
expect(ariaHidden).toBeNull();
await content.evaluate(async (el: HTMLIonContentElement) => {
@@ -38,7 +32,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
});
await page.locator('#smallTitleHeader.header-collapse-condense-inactive').waitFor();
await expect(smallTitle).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
});
});
});

View File

@@ -22,7 +22,7 @@ export interface MenuI {
close(animated?: boolean): Promise<boolean>;
toggle(animated?: boolean): Promise<boolean>;
setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
}
export interface MenuControllerI {
@@ -42,7 +42,7 @@ export interface MenuControllerI {
_createAnimation(type: string, menuCmp: MenuI): Promise<Animation>;
_register(menu: MenuI): void;
_unregister(menu: MenuI): void;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise<boolean>;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean>;
}
export interface MenuChangeEventDetail {
@@ -50,10 +50,6 @@ export interface MenuChangeEventDetail {
open: boolean;
}
export interface MenuCloseEventDetail {
role?: string;
}
export interface MenuCustomEvent<T = any> extends CustomEvent {
detail: T;
target: HTMLIonMenuElement;

View File

@@ -7,15 +7,14 @@ import { shouldUseCloseWatcher } from '@utils/hardware-back-button';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers';
import { menuController } from '@utils/menu-controller';
import { BACKDROP, GESTURE, getPresentedOverlay } from '@utils/overlays';
import { isPlatform } from '@utils/platform';
import { getPresentedOverlay } from '@utils/overlays';
import { hostContext } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import type { Animation, Gesture, GestureDetail } from '../../interface';
import type { MenuChangeEventDetail, MenuCloseEventDetail, MenuI, MenuType, Side } from './menu-interface';
import type { MenuChangeEventDetail, MenuI, MenuType, Side } from './menu-interface';
const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
const mdEasing = 'cubic-bezier(0.0,0.0,0.2,1)';
@@ -180,7 +179,7 @@ export class Menu implements ComponentInterface, MenuI {
/**
* Emitted when the menu is about to be closed.
*/
@Event() ionWillClose!: EventEmitter<MenuCloseEventDetail>;
@Event() ionWillClose!: EventEmitter<void>;
/**
* Emitted when the menu is open.
*/
@@ -189,7 +188,7 @@ export class Menu implements ComponentInterface, MenuI {
/**
* Emitted when the menu is closed.
*/
@Event() ionDidClose!: EventEmitter<MenuCloseEventDetail>;
@Event() ionDidClose!: EventEmitter<void>;
/**
* Emitted when the menu state is changed.
@@ -332,14 +331,14 @@ export class Menu implements ComponentInterface, MenuI {
if (shouldClose) {
ev.preventDefault();
ev.stopPropagation();
this.close(undefined, BACKDROP);
this.close();
}
}
}
onKeydown(ev: KeyboardEvent) {
if (ev.key === 'Escape') {
this.close(undefined, BACKDROP);
this.close();
}
}
@@ -376,8 +375,8 @@ export class Menu implements ComponentInterface, MenuI {
* it returns `false`.
*/
@Method()
close(animated = true, role?: string): Promise<boolean> {
return this.setOpen(false, animated, role);
close(animated = true): Promise<boolean> {
return this.setOpen(false, animated);
}
/**
@@ -394,8 +393,8 @@ export class Menu implements ComponentInterface, MenuI {
* If the operation can't be completed successfully, it returns `false`.
*/
@Method()
setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
return menuController._setOpen(this, shouldOpen, animated, role);
setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
return menuController._setOpen(this, shouldOpen, animated);
}
private trapKeyboardFocus(ev: Event, doc: Document) {
@@ -439,13 +438,13 @@ export class Menu implements ComponentInterface, MenuI {
}
}
async _setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
async _setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
// If the menu is disabled or it is currently being animated, let's do nothing
if (!this._isActive() || this.isAnimating || shouldOpen === this._isOpen) {
return false;
}
this.beforeAnimation(shouldOpen, role);
this.beforeAnimation(shouldOpen);
await this.loadAnimation();
await this.startAnimation(shouldOpen, animated);
@@ -460,7 +459,7 @@ export class Menu implements ComponentInterface, MenuI {
return false;
}
this.afterAnimation(shouldOpen, role);
this.afterAnimation(shouldOpen);
return true;
}
@@ -543,7 +542,7 @@ export class Menu implements ComponentInterface, MenuI {
}
private onWillStart(): Promise<void> {
this.beforeAnimation(!this._isOpen, GESTURE);
this.beforeAnimation(!this._isOpen);
return this.loadAnimation();
}
@@ -625,30 +624,13 @@ export class Menu implements ComponentInterface, MenuI {
this.animation
.easing('cubic-bezier(0.4, 0.0, 0.6, 1)')
.onFinish(() => this.afterAnimation(shouldOpen, GESTURE), { oneTimeCallback: true })
.onFinish(() => this.afterAnimation(shouldOpen), { oneTimeCallback: true })
.progressEnd(playTo ? 1 : 0, this._isOpen ? 1 - newStepValue : newStepValue, 300);
}
private beforeAnimation(shouldOpen: boolean, role?: string) {
private beforeAnimation(shouldOpen: boolean) {
assert(!this.isAnimating, '_before() should not be called while animating');
/**
* When the menu is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). This occurs because the focus rings are initially
* displayed at the starting position of the elements before the transition
* begins. This workaround ensures the focus rings do not appear in the
* incorrect location.
*
* If this solution is applied to iOS devices, then it leads to a bug where
* the overlays cannot be accessed by screen readers. This is due to
* VoiceOver not being able to update the accessibility tree when the
* `aria-hidden` is removed.
*/
if (isPlatform('android')) {
this.el.setAttribute('aria-hidden', 'true');
}
// this places the menu into the correct location before it animates in
// this css class doesn't actually kick off any animations
this.el.classList.add(SHOW_MENU);
@@ -689,11 +671,11 @@ export class Menu implements ComponentInterface, MenuI {
if (shouldOpen) {
this.ionWillOpen.emit();
} else {
this.ionWillClose.emit({ role });
this.ionWillClose.emit();
}
}
private afterAnimation(isOpen: boolean, role?: string) {
private afterAnimation(isOpen: boolean) {
// keep opening/closing the menu disabled for a touch more yet
// only add listeners/css if it's enabled and isOpen
// and only remove listeners/css if it's not open
@@ -705,17 +687,6 @@ export class Menu implements ComponentInterface, MenuI {
}
if (isOpen) {
/**
* When the menu is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). The menu is hidden from screen readers during the
* transition to prevent this. Once the transition is complete, the menu
* is shown again.
*/
if (isPlatform('android')) {
this.el.removeAttribute('aria-hidden');
}
// emit open event
this.ionDidOpen.emit();
@@ -732,8 +703,6 @@ export class Menu implements ComponentInterface, MenuI {
// start focus trapping
document.addEventListener('focus', this.handleFocus, true);
} else {
this.el.removeAttribute('aria-hidden');
// remove css classes and unhide content from screen readers
this.el.classList.remove(SHOW_MENU);
@@ -762,7 +731,7 @@ export class Menu implements ComponentInterface, MenuI {
}
// emit close event
this.ionDidClose.emit({ role });
this.ionDidClose.emit();
// undo focus trapping so multiple menus don't collide
document.removeEventListener('focus', this.handleFocus, true);
@@ -798,7 +767,7 @@ export class Menu implements ComponentInterface, MenuI {
* If the menu is disabled then we should
* forcibly close the menu even if it is open.
*/
this.afterAnimation(false, GESTURE);
this.afterAnimation(false);
}
}

View File

@@ -51,9 +51,7 @@
</ion-header>
<ion-content>
<ion-list>
<ion-menu-toggle>
<ion-button id="start-menu-button">Button</ion-button>
</ion-menu-toggle>
<ion-button id="start-menu-button">Button</ion-button>
<ion-item>Menu Item</ion-item>
<ion-item>Menu Item</ion-item>
<ion-item>Menu Item</ion-item>
@@ -127,19 +125,6 @@
</ion-app>
<script>
window.addEventListener('ionWillOpen', function (e) {
console.log('ionWillOpen', e);
});
window.addEventListener('ionDidOpen', function (e) {
console.log('ionDidOpen', e);
});
window.addEventListener('ionWillClose', function (e) {
console.log('ionWillClose', e);
});
window.addEventListener('ionDidClose', function (e) {
console.log('ionDidClose', e);
});
async function openStart() {
// Open the menu by menu-id
await menuController.enable(true, 'start-menu');

View File

@@ -1,7 +1,7 @@
import type { Locator } from '@playwright/test';
import { expect } from '@playwright/test';
import type { E2EPage, ScreenshotFn } from '@utils/test/playwright';
import { configs, dragElementBy, test } from '@utils/test/playwright';
import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, config, screenshot }) => {
test.describe(title('menu: rendering'), () => {
@@ -140,97 +140,6 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
});
});
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('menu: events'), () => {
test.beforeEach(async ({ page }) => {
await page.goto(`/src/components/menu/test/basic`, config);
});
test('should pass role when swiping to close', async ({ page }) => {
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
const ionWillClose = await page.spyOnEvent('ionWillClose');
const ionDidClose = await page.spyOnEvent('ionDidClose');
await page.click('#open-start');
await ionDidOpen.next();
const menu = page.locator('#start-menu');
await dragElementBy(menu, page, -150, 0);
await ionWillClose.next();
await ionDidClose.next();
await expect(ionWillClose).toHaveReceivedEventDetail({ role: 'gesture' });
await expect(ionDidClose).toHaveReceivedEventDetail({ role: 'gesture' });
});
test('should pass role when clicking backdrop to close', async ({ page }) => {
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
const ionWillClose = await page.spyOnEvent('ionWillClose');
const ionDidClose = await page.spyOnEvent('ionDidClose');
await page.click('#open-start');
await ionDidOpen.next();
const menu = page.locator('#start-menu');
const backdrop = menu.locator('ion-backdrop');
/**
* Coordinates for the click event.
* These need to be near the right edge of the backdrop
* in order to avoid clicking on the menu.
*/
const backdropBoundingBox = await backdrop.boundingBox();
const x = backdropBoundingBox!.width - 50;
const y = backdropBoundingBox!.height - 50;
// Click near the right side of the backdrop.
await backdrop.click({
position: { x, y },
});
await ionWillClose.next();
await ionDidClose.next();
await expect(ionWillClose).toHaveReceivedEventDetail({ role: 'backdrop' });
await expect(ionDidClose).toHaveReceivedEventDetail({ role: 'backdrop' });
});
test('should pass role when pressing escape key to close', async ({ page }) => {
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
const ionWillClose = await page.spyOnEvent('ionWillClose');
const ionDidClose = await page.spyOnEvent('ionDidClose');
await page.click('#open-start');
await ionDidOpen.next();
await page.keyboard.press('Escape');
await ionWillClose.next();
await ionDidClose.next();
await expect(ionWillClose).toHaveReceivedEventDetail({ role: 'backdrop' });
await expect(ionDidClose).toHaveReceivedEventDetail({ role: 'backdrop' });
});
test('should not pass role when clicking a menu toggle button to close', async ({ page }) => {
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
const ionWillClose = await page.spyOnEvent('ionWillClose');
const ionDidClose = await page.spyOnEvent('ionDidClose');
await page.click('#open-start');
await ionDidOpen.next();
await page.click('#start-menu-button');
await ionWillClose.next();
await ionDidClose.next();
await expect(ionWillClose).toHaveReceivedEventDetail({ role: undefined });
await expect(ionDidClose).toHaveReceivedEventDetail({ role: undefined });
});
});
});
async function testMenu(page: E2EPage, menu: Locator, menuId: string, screenshot: ScreenshotFn) {
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
const ionDidClose = await page.spyOnEvent('ionDidClose');

View File

@@ -154,13 +154,6 @@
</div>
</ion-app>
<script>
window.addEventListener('ionModalDidDismiss', function (e) {
console.log('DidDismiss', e);
});
window.addEventListener('ionModalWillDismiss', function (e) {
console.log('WillDismiss', e);
});
function createModal(options) {
let items = '';

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, Watch, h } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Prop, Watch, h } from '@stencil/core';
import { renderHiddenInput } from '@utils/helpers';
import { getIonMode } from '../../global/ionic-global';
@@ -155,9 +155,7 @@ export class RadioGroup implements ComponentInterface {
@Listen('keydown', { target: 'document' })
onKeydown(ev: KeyboardEvent) {
// We don't want the value to automatically change/emit when the radio group is part of a select interface
// as this will cause the interface to close when navigating through the radio group options
const inSelectInterface = !!this.el.closest('ion-select-popover') || !!this.el.closest('ion-select-modal');
const inSelectPopover = !!this.el.closest('ion-select-popover');
if (ev.target && !this.el.contains(ev.target as HTMLElement)) {
return;
@@ -189,7 +187,7 @@ export class RadioGroup implements ComponentInterface {
if (next && radios.includes(next)) {
next.setFocus(ev);
if (!inSelectInterface) {
if (!inSelectPopover) {
this.value = next.value;
this.emitValueChange(ev);
}
@@ -217,13 +215,6 @@ export class RadioGroup implements ComponentInterface {
}
}
/** @internal */
@Method()
async setFocus() {
const radioToFocus = this.getRadios().find((r) => r.tabIndex !== -1);
radioToFocus?.setFocus();
}
render() {
const { label, labelId, el, name, value } = this;
const mode = getIonMode(this);

View File

@@ -126,11 +126,9 @@ export class Radio implements ComponentInterface {
/** @internal */
@Method()
async setFocus(ev?: globalThis.Event) {
if (ev !== undefined) {
ev.stopPropagation();
ev.preventDefault();
}
async setFocus(ev: globalThis.Event) {
ev.stopPropagation();
ev.preventDefault();
this.el.focus();
}

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, Method, State, Watch, forceUpdate, h } from '@stencil/core';
import type { ButtonInterface } from '@utils/element-interface';
import type { Attributes } from '@utils/helpers';
import { addEventListener, removeEventListener, inheritAttributes, getNextSiblingOfType } from '@utils/helpers';
import { addEventListener, removeEventListener, inheritAttributes } from '@utils/helpers';
import { hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
@@ -36,11 +36,6 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
@State() checked = false;
/**
* The `id` of the segment content.
*/
@Prop({ reflect: true }) contentId?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@@ -65,72 +60,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
this.updateState();
}
private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise<HTMLElement> {
return new Promise((resolve, reject) => {
let timeoutId: NodeJS.Timeout | undefined = undefined;
let animationFrameId: number;
const check = () => {
if (!ionSegment) {
reject(new Error(`Segment not found when looking for Segment Content`));
return;
}
const segmentView = getNextSiblingOfType<HTMLIonSegmentViewElement>(ionSegment); // Skip the text nodes
const segmentContent = segmentView?.querySelector(
`ion-segment-content[id="${contentId}"]`
) as HTMLIonSegmentContentElement | null;
if (segmentContent && timeoutId) {
clearTimeout(timeoutId); // Clear the timeout if the segmentContent is found
cancelAnimationFrame(animationFrameId);
resolve(segmentContent);
} else {
animationFrameId = requestAnimationFrame(check); // Keep checking on the next animation frame
}
};
check();
// Set a timeout to reject the promise
timeoutId = setTimeout(() => {
cancelAnimationFrame(animationFrameId);
reject(new Error(`Unable to find Segment Content with id="${contentId} within 1000 ms`));
}, 1000);
});
}
async connectedCallback() {
connectedCallback() {
const segmentEl = (this.segmentEl = this.el.closest('ion-segment'));
if (segmentEl) {
this.updateState();
addEventListener(segmentEl, 'ionSelect', this.updateState);
addEventListener(segmentEl, 'ionStyle', this.updateStyle);
}
// Return if there is no contentId defined
if (!this.contentId) return;
let segmentContent;
try {
// Attempt to find the Segment Content by its contentId
segmentContent = await this.waitForSegmentContent(segmentEl, this.contentId);
} catch (error) {
// If no associated Segment Content exists, log an error and return
console.error('Segment Button: ', (error as Error).message);
return;
}
// Ensure the found element is a valid ION-SEGMENT-CONTENT
if (segmentContent.tagName !== 'ION-SEGMENT-CONTENT') {
console.error(`Segment Button: Element with id="${this.contentId}" is not an <ion-segment-content> element.`);
return;
}
// Prevent buttons from being disabled when associated with segment content
if (this.disabled) {
console.warn(`Segment Button: Segment buttons cannot be disabled when associated with an <ion-segment-content>.`);
this.disabled = false;
}
}
disconnectedCallback() {
@@ -225,7 +161,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
<div part="indicator" class="segment-button-indicator segment-button-indicator-animated">
<div
part="indicator"
class={{
'segment-button-indicator': true,
'segment-button-indicator-animated': true,
}}
>
<div part="indicator-background" class="segment-button-indicator-background"></div>
</div>
</Host>

View File

@@ -1,11 +0,0 @@
// Segment Content
// --------------------------------------------------
:host {
scroll-snap-align: center;
scroll-snap-stop: always;
flex-shrink: 0;
width: 100%;
}

View File

@@ -1,17 +0,0 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
@Component({
tag: 'ion-segment-content',
styleUrl: 'segment-content.scss',
shadow: true,
})
export class SegmentContent implements ComponentInterface {
render() {
return (
<Host>
<slot></slot>
</Host>
);
}
}

View File

@@ -1,4 +0,0 @@
export interface SegmentViewScrollEvent {
scrollRatio: number;
isManualScroll: boolean;
}

View File

@@ -1,9 +0,0 @@
@import "./segment-view";
@import "../segment-button/segment-button.ios.vars";
// iOS Segment View
// --------------------------------------------------
:host(.segment-view-disabled) {
opacity: $segment-button-ios-opacity-disabled;
}

View File

@@ -1,9 +0,0 @@
@import "./segment-view";
@import "../segment-button/segment-button.md.vars";
// Material Design Segment View
// --------------------------------------------------
:host(.segment-view-disabled) {
opacity: $segment-button-md-opacity-disabled;
}

View File

@@ -1,31 +0,0 @@
// Segment View
// --------------------------------------------------
:host {
display: flex;
height: 100%;
overflow-x: scroll;
scroll-snap-type: x mandatory;
/* Hide scrollbar in Firefox */
scrollbar-width: none;
/* Hide scrollbar in IE and Edge */
-ms-overflow-style: none;
}
/* Hide scrollbar in webkit */
:host::-webkit-scrollbar {
display: none;
}
:host(.segment-view-disabled) {
touch-action: none;
overflow-x: hidden;
}
:host(.segment-view-scroll-disabled) {
pointer-events: none;
}

View File

@@ -1,153 +0,0 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, State, h } from '@stencil/core';
import type { SegmentViewScrollEvent } from './segment-view-interface';
@Component({
tag: 'ion-segment-view',
styleUrls: {
ios: 'segment-view.ios.scss',
md: 'segment-view.md.scss',
},
shadow: true,
})
export class SegmentView implements ComponentInterface {
private scrollEndTimeout: ReturnType<typeof setTimeout> | null = null;
private isTouching = false;
@Element() el!: HTMLElement;
/**
* If `true`, the segment view cannot be interacted with.
*/
@Prop() disabled = false;
/**
* @internal
*
* If `true`, the segment view is scrollable.
* If `false`, pointer events will be disabled. This is to prevent issues with
* quickly scrolling after interacting with a segment button.
*/
@State() isManualScroll?: boolean;
/**
* Emitted when the segment view is scrolled.
*/
@Event() ionSegmentViewScroll!: EventEmitter<SegmentViewScrollEvent>;
@Listen('scroll')
handleScroll(ev: Event) {
const { scrollLeft, scrollWidth, clientWidth } = ev.target as HTMLElement;
const scrollRatio = scrollLeft / (scrollWidth - clientWidth);
this.ionSegmentViewScroll.emit({
scrollRatio,
isManualScroll: this.isManualScroll ?? true,
});
// Reset the timeout to check for scroll end
this.resetScrollEndTimeout();
}
/**
* Handle touch start event to know when the user is actively dragging the segment view.
*/
@Listen('touchstart')
handleScrollStart() {
if (this.scrollEndTimeout) {
clearTimeout(this.scrollEndTimeout);
this.scrollEndTimeout = null;
}
this.isTouching = true;
}
/**
* Handle touch end event to know when the user is no longer dragging the segment view.
*/
@Listen('touchend')
handleTouchEnd() {
this.isTouching = false;
}
/**
* Reset the scroll end detection timer. This is called on every scroll event.
*/
private resetScrollEndTimeout() {
if (this.scrollEndTimeout) {
clearTimeout(this.scrollEndTimeout);
this.scrollEndTimeout = null;
}
this.scrollEndTimeout = setTimeout(
() => {
this.checkForScrollEnd();
},
// Setting this to a lower value may result in inconsistencies in behavior
// across browsers (particularly Firefox).
// Ideally, all of this logic is removed once the scroll end event is
// supported on all browsers (https://caniuse.com/?search=scrollend)
100
);
}
/**
* Check if the scroll has ended and the user is not actively touching.
* If the conditions are met (active content is enabled and no active touch),
* reset the scroll position and emit the scroll end event.
*/
private checkForScrollEnd() {
// Only emit scroll end event if the active content is not disabled and
// the user is not touching the segment view
if (!this.isTouching) {
this.isManualScroll = undefined;
}
}
/**
* @internal
*
* This method is used to programmatically set the displayed segment content
* in the segment view. Calling this method will update the `value` of the
* corresponding segment button.
*
* @param id: The id of the segment content to display.
* @param smoothScroll: Whether to animate the scroll transition.
*/
@Method()
async setContent(id: string, smoothScroll = true) {
const contents = this.getSegmentContents();
const index = contents.findIndex((content) => content.id === id);
if (index === -1) return;
this.isManualScroll = false;
this.resetScrollEndTimeout();
const contentWidth = this.el.offsetWidth;
this.el.scrollTo({
top: 0,
left: index * contentWidth,
behavior: smoothScroll ? 'smooth' : 'instant',
});
}
private getSegmentContents(): HTMLIonSegmentContentElement[] {
return Array.from(this.el.querySelectorAll('ion-segment-content'));
}
render() {
const { disabled, isManualScroll } = this;
return (
<Host
class={{
'segment-view-disabled': disabled,
'segment-view-scroll-disabled': isManualScroll === false,
}}
>
<slot></slot>
</Host>
);
}
}

View File

@@ -1,194 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Basic</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
ion-segment-view {
height: 100px;
margin-bottom: 20px;
}
ion-segment-content {
display: flex;
justify-content: center;
align-items: center;
}
ion-segment-content:nth-of-type(3n + 1) {
background: lightpink;
}
ion-segment-content:nth-of-type(3n + 2) {
background: lightblue;
}
ion-segment-content:nth-of-type(3n + 3) {
background: lightgreen;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Segment View - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-segment id="noValueSegment">
<ion-segment-button content-id="no" value="no">
<ion-label>No</ion-label>
</ion-segment-button>
<ion-segment-button content-id="value" value="value">
<ion-label>Value</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view id="noValueSegmentView">
<ion-segment-content id="no">No</ion-segment-content>
<ion-segment-content id="value">Value</ion-segment-content>
</ion-segment-view>
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button style="min-width: 200px" content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
<ion-segment value="peach" scrollable>
<ion-segment-button content-id="orange" value="orange">
<ion-label>Orange</ion-label>
</ion-segment-button>
<ion-segment-button content-id="banana" value="banana">
<ion-label>Banana</ion-label>
</ion-segment-button>
<ion-segment-button content-id="pear" value="pear">
<ion-label>Pear</ion-label>
</ion-segment-button>
<ion-segment-button content-id="peach" value="peach">
<ion-label>Peach</ion-label>
</ion-segment-button>
<ion-segment-button content-id="grape" value="grape">
<ion-label>Grape</ion-label>
</ion-segment-button>
<ion-segment-button content-id="mango" value="mango">
<ion-label>Mango</ion-label>
</ion-segment-button>
<ion-segment-button content-id="apple" value="apple">
<ion-label>Apple</ion-label>
</ion-segment-button>
<ion-segment-button content-id="strawberry" value="strawberry">
<ion-label>Strawberry</ion-label>
</ion-segment-button>
<ion-segment-button content-id="cherry" value="cherry">
<ion-label>Cherry</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="orange">Orange</ion-segment-content>
<ion-segment-content id="banana">Banana</ion-segment-content>
<ion-segment-content id="pear">Pear</ion-segment-content>
<ion-segment-content id="peach">Peach</ion-segment-content>
<ion-segment-content id="grape">Grape</ion-segment-content>
<ion-segment-content id="mango">Mango</ion-segment-content>
<ion-segment-content id="apple">Apple</ion-segment-content>
<ion-segment-content id="strawberry">Strawberry</ion-segment-content>
<ion-segment-content id="cherry">Cherry</ion-segment-content>
</ion-segment-view>
<button class="expand" onClick="changeSegmentContent()">Change Segment Content</button>
<button class="expand" onClick="clearSegmentValue()">Clear Segment Value</button>
<button class="expand" onClick="addSegmentButtonAndContent()">Add New Segment Button & Content</button>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
<script>
function changeSegmentContent() {
const segment = document.querySelector('#noValueSegment');
const segmentView = document.querySelector('#noValueSegmentView');
let currentValue = segment.value;
if (currentValue === 'value') {
currentValue = 'no';
} else {
currentValue = 'value';
}
segment.value = currentValue;
}
async function clearSegmentValue() {
const segmentView = document.querySelector('#noValueSegmentView');
segmentView.setContent('no', false);
// Set timeout to ensure the value is cleared after
// the segment content is updated
setTimeout(() => {
const segment = document.querySelector('#noValueSegment');
segment.value = undefined;
});
}
async function addSegmentButtonAndContent() {
const segment = document.querySelector('ion-segment');
const segmentView = document.querySelector('ion-segment-view');
const newButton = document.createElement('ion-segment-button');
const newId = `new-${Date.now()}`;
newButton.setAttribute('content-id', newId);
newButton.setAttribute('value', newId);
newButton.innerHTML = '<ion-label>New Button</ion-label>';
segment.appendChild(newButton);
setTimeout(() => {
// Timeout to test waitForSegmentContent() in segment-button
const newContent = document.createElement('ion-segment-content');
newContent.setAttribute('id', newId);
newContent.innerHTML = 'New Content';
segmentView.appendChild(newContent);
// Necessary timeout to ensure the value is set after the content is added.
// Otherwise, the transition is unsuccessful and the content is not shown.
setTimeout(() => {
segment.setAttribute('value', newId);
}, 200);
}, 200);
}
</script>
</ion-app>
</body>
</html>

View File

@@ -1,173 +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: basic'), () => {
test('should show the first content with no initial value', async ({ page }) => {
await page.setContent(
`
<ion-segment>
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
const segmentContent = page.locator('ion-segment-content[id="paid"]');
await expect(segmentContent).toBeInViewport();
});
test('should show the content matching the initial value', async ({ page }) => {
await page.setContent(
`
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
const segmentContent = page.locator('ion-segment-content[id="free"]');
await expect(segmentContent).toBeInViewport();
});
test('should update the content when changing the value by clicking a segment button', async ({ page }) => {
await page.setContent(
`
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
await page.locator('ion-segment-button[value="top"]').click();
const segmentContent = page.locator('ion-segment-content[id="top"]');
await expect(segmentContent).toBeInViewport();
});
});
test('should set correct segment button as checked when changing the value by scrolling the segment content', async ({
page,
}) => {
await page.setContent(
`
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
await page
.locator('ion-segment-view')
.evaluate(
(segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled')
);
await page.waitForChanges();
await page.locator('ion-segment-content[id="top"]').scrollIntoViewIfNeeded();
const segmentButton = page.locator('ion-segment-button[value="top"]');
await expect(segmentButton).toHaveClass(/segment-button-checked/);
});
test('should set correct segment button as checked and show correct content when programmatically setting the segment vale', async ({
page,
}) => {
await page.setContent(
`
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
await page
.locator('ion-segment-view')
.evaluate(
(segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled')
);
await page.waitForChanges();
await page.locator('ion-segment').evaluate((segment: HTMLIonSegmentElement) => (segment.value = 'top'));
const segmentButton = page.locator('ion-segment-button[value="top"]');
await expect(segmentButton).toHaveClass(/segment-button-checked/);
const segmentContent = page.locator('ion-segment-content[id="top"]');
await expect(segmentContent).toBeInViewport();
});
});

View File

@@ -1,103 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Disabled</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-view {
height: 100px;
}
ion-segment-content {
display: flex;
justify-content: center;
align-items: center;
}
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;
}
ion-segment-content:nth-of-type(4) {
background: lightgoldenrodyellow;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Segment View - Disabled</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-segment>
<ion-segment-button disabled content-id="all" value="all">
<ion-label>All</ion-label>
</ion-segment-button>
<ion-segment-button content-id="favorites" value="favorites">
<ion-label>Favorites</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="all">All</ion-segment-content>
<ion-segment-content id="favorites">Favorites</ion-segment-content>
</ion-segment-view>
<ion-segment disabled value="paid">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
<ion-segment value="reading-list">
<ion-segment-button content-id="bookmarks" value="bookmarks">
<ion-label>Bookmarks</ion-label>
</ion-segment-button>
<ion-segment-button content-id="reading-list" value="reading-list">
<ion-label>Reading List</ion-label>
</ion-segment-button>
<ion-segment-button content-id="shared-links" value="shared-links">
<ion-label>Shared Links</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view disabled>
<ion-segment-content id="bookmarks">Bookmarks</ion-segment-content>
<ion-segment-content id="reading-list">Reading List</ion-segment-content>
<ion-segment-content id="shared-links">Shared Links</ion-segment-content>
</ion-segment-view>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -1,49 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions
*/
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('segment-view: disabled'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/segment-view/test/disabled', config);
await expect(page).toHaveScreenshot(screenshot(`segment-view-disabled`));
});
});
});
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('segment-view: disabled'), () => {
test('should keep button enabled even when disabled prop is set', async ({ page }) => {
await page.setContent(
`
<ion-segment>
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button disabled content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content disabled id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
const segmentButton = page.locator('ion-segment-button[value="free"]');
await expect(segmentButton).not.toHaveClass(/segment-button-disabled/);
});
});
});

View File

@@ -7,7 +7,6 @@ import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
import type { SegmentViewScrollEvent } from '../segment-view/segment-view-interface';
import type { SegmentChangeEventDetail, SegmentValue } from './segment-interface';
@@ -28,16 +27,6 @@ export class Segment implements ComponentInterface {
// Value before the segment is dragged
private valueBeforeGesture?: SegmentValue;
private segmentViewEl?: HTMLIonSegmentViewElement | null = null;
private lastNextIndex?: number;
/**
* Whether to update the segment view, if exists, when the value changes.
* This behavior is enabled by default, but is set false when scrolling content views
* since we don't want to "double scroll" the segment view.
*/
private triggerScrollOnValueChange?: boolean;
@Element() el!: HTMLIonSegmentElement;
@State() activated = false;
@@ -89,41 +78,13 @@ export class Segment implements ComponentInterface {
@Prop({ mutable: true }) value?: SegmentValue;
@Watch('value')
protected valueChanged(value: SegmentValue | undefined, oldValue?: SegmentValue | undefined) {
// Force a value to exist if we're using a segment view
if (this.segmentViewEl && value === undefined) {
this.value = this.getButtons()[0].value;
return;
}
if (oldValue !== undefined && value !== undefined) {
const buttons = this.getButtons();
const previous = buttons.find((button) => button.value === oldValue);
const current = buttons.find((button) => button.value === value);
if (previous && current) {
if (!this.segmentViewEl) {
this.checkButton(previous, current);
} else if (this.triggerScrollOnValueChange !== false) {
this.updateSegmentView();
}
}
} else if (value !== undefined && oldValue === undefined && this.segmentViewEl) {
this.updateSegmentView();
}
protected valueChanged(value: SegmentValue | undefined) {
/**
* `ionSelect` is emitted every time the value changes (internal or external changes).
* Used by `ion-segment-button` to determine if the button should be checked.
*/
this.ionSelect.emit({ value });
// The scroll listener should handle scrolling the active button into view as needed
if (!this.segmentViewEl) {
this.scrollActiveButtonIntoView();
}
this.triggerScrollOnValueChange = undefined;
this.scrollActiveButtonIntoView();
}
/**
@@ -157,13 +118,9 @@ export class Segment implements ComponentInterface {
disabledChanged() {
this.gestureChanged();
if (!this.segmentViewEl) {
const buttons = this.getButtons();
for (const button of buttons) {
button.disabled = this.disabled;
}
} else {
this.segmentViewEl.disabled = this.disabled;
const buttons = this.getButtons();
for (const button of buttons) {
button.disabled = this.disabled;
}
}
@@ -175,12 +132,6 @@ export class Segment implements ComponentInterface {
connectedCallback() {
this.emitStyle();
this.segmentViewEl = this.getSegmentView();
}
disconnectedCallback() {
this.segmentViewEl = null;
}
componentWillLoad() {
@@ -188,8 +139,6 @@ export class Segment implements ComponentInterface {
}
async componentDidLoad() {
this.segmentViewEl = this.getSegmentView();
this.setCheckedClasses();
/**
@@ -221,10 +170,6 @@ export class Segment implements ComponentInterface {
if (this.disabled) {
this.disabledChanged();
}
// Update segment view based on the initial value,
// but do not animate the scroll
this.updateSegmentView(false);
}
onStart(detail: GestureDetail) {
@@ -247,7 +192,6 @@ export class Segment implements ComponentInterface {
if (value !== undefined) {
if (this.valueBeforeGesture !== value) {
this.emitValueChange();
this.updateSegmentView();
}
}
this.valueBeforeGesture = undefined;
@@ -264,7 +208,7 @@ export class Segment implements ComponentInterface {
this.ionChange.emit({ value });
}
private getButtons(): HTMLIonSegmentButtonElement[] {
private getButtons() {
return Array.from(this.el.querySelectorAll('ion-segment-button'));
}
@@ -280,7 +224,11 @@ export class Segment implements ComponentInterface {
const buttons = this.getButtons();
buttons.forEach((button) => {
button.classList.toggle('segment-button-activated', activated);
if (activated) {
button.classList.add('segment-button-activated');
} else {
button.classList.remove('segment-button-activated');
}
});
this.activated = activated;
}
@@ -345,8 +293,6 @@ export class Segment implements ComponentInterface {
// Remove the transform to slide the indicator back to the button clicked
currentIndicator.style.setProperty('transform', '');
this.scrollActiveButtonIntoView(true);
});
this.value = current.value;
@@ -366,74 +312,6 @@ export class Segment implements ComponentInterface {
}
}
private getSegmentView() {
const buttons = this.getButtons();
// Get the first button with a contentId
const firstContentId = buttons.find((button: HTMLIonSegmentButtonElement) => button.contentId);
// Get the segment content with an id matching the button's contentId
const segmentContent = document.querySelector(`ion-segment-content[id="${firstContentId?.contentId}"]`);
// Return the segment view for that matching segment content
return segmentContent?.closest('ion-segment-view');
}
@Listen('ionSegmentViewScroll', { target: 'body' })
handleSegmentViewScroll(ev: CustomEvent<SegmentViewScrollEvent>) {
const { scrollRatio, isManualScroll } = ev.detail;
if (!isManualScroll) {
return;
}
const dispatchedFrom = ev.target as HTMLElement;
const segmentViewEl = this.segmentViewEl as EventTarget;
const segmentEl = this.el;
// Only update the indicator if the event was dispatched from the correct segment view
if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) {
const buttons = this.getButtons();
// If no buttons are found or there is no value set then do nothing
if (!buttons.length) return;
const index = buttons.findIndex((button) => button.value === this.value);
const current = buttons[index];
const nextIndex = Math.round(scrollRatio * (buttons.length - 1));
if (this.lastNextIndex === undefined || this.lastNextIndex !== nextIndex) {
this.lastNextIndex = nextIndex;
this.triggerScrollOnValueChange = false;
this.checkButton(current, buttons[nextIndex]);
this.emitValueChange();
}
}
}
/**
* Finds the related segment view and sets its current content
* based on the selected segment button. This method
* should be called on initial load of the segment,
* after the gesture is completed (if dragging between segments)
* and when a segment button is clicked directly.
*/
private updateSegmentView(smoothScroll = true) {
const buttons = this.getButtons();
const button = buttons.find((btn) => btn.value === this.value);
// If the button does not have a contentId then there is
// no associated segment view to update
if (!button?.contentId) {
return;
}
const segmentView = this.segmentViewEl;
if (segmentView) {
segmentView.setContent(button.contentId, smoothScroll);
}
}
private scrollActiveButtonIntoView(smoothScroll = true) {
const { scrollable, value, el } = this;
@@ -614,13 +492,7 @@ export class Segment implements ComponentInterface {
this.emitValueChange();
}
if (this.segmentViewEl) {
this.updateSegmentView();
if (this.scrollable && previous) {
this.checkButton(previous, current);
}
} else if (this.scrollable || !this.swipeGesture) {
if (this.scrollable || !this.swipeGesture) {
if (previous) {
this.checkButton(previous, current);
} else {

View File

@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import { configs, test, dragElementBy } from '@utils/test/playwright';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across modes/directions.
@@ -105,7 +105,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
test.describe('when the pointer is released', () => {
// TODO FW-3021
test.describe.skip('when the pointer is released', () => {
test('should emit if the value has changed', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
@@ -135,22 +136,14 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const ionChangeSpy = await page.spyOnEvent('ionChange');
const segment = page.locator('ion-segment');
const firstButton = page.locator('ion-segment-button[value="1"]');
const lastButton = page.locator('ion-segment-button[value="3"]');
/*
* `dragByX` should represent the total width of all segment buttons,
* excluding the first half of the first button and the second half
* of the last button. This calculation accounts for dragging from
* the center of the first button to the center of the last button.
*/
const segmentWidth = await segment.boundingBox().then((box) => (box ? box.width : 0));
const firstButtonWidth = await firstButton.boundingBox().then((box) => (box ? box.width : 0));
const lastButtonWidth = await lastButton.boundingBox().then((box) => (box ? box.width : 0));
const dragByX = segmentWidth - firstButtonWidth / 2 - lastButtonWidth / 2;
await firstButton.hover();
await page.mouse.down();
await dragElementBy(firstButton, page, dragByX);
await lastButton.hover();
await page.mouse.up();
expect(ionChangeSpy).toHaveReceivedEventDetail({ value: '3' });
expect(ionChangeSpy).toHaveReceivedEventTimes(1);

View File

@@ -1,8 +0,0 @@
export interface SelectModalOption {
text: string;
value: string;
disabled: boolean;
checked: boolean;
cssClass?: string | string[];
handler?: (value: any) => boolean | void | { [key: string]: any };
}

View File

@@ -1,24 +0,0 @@
@import "./select-modal";
@import "../item/item.ios.vars";
@import "../radio/radio.ios.vars";
ion-item {
--inner-padding-end: 0;
}
/**
* The bottom border of the item should only be displayed
* under the text and not the radio icon.
*/
ion-radio::after {
@include position(null, null, 0, calc($radio-ios-icon-width + $item-ios-padding-start));
position: absolute;
width: calc(100% - $radio-ios-icon-width - $item-ios-padding-start); /* Adjust width based on the shift */
border-width: #{0px 0px $item-ios-border-bottom-width 0px};
border-style: #{$item-ios-border-bottom-style};
border-color: #{$item-ios-border-bottom-color};
content: "";
}

View File

@@ -1,30 +0,0 @@
@import "./select-modal";
@import "../../themes/ionic.mixins.scss";
@import "../item/item.md.vars";
ion-list ion-radio::part(container) {
display: none;
}
ion-list ion-radio::part(label) {
@include margin(0);
}
ion-item {
--inner-border-width: 0;
}
.item-radio-checked {
--background: #{ion-color(primary, base, 0.08)};
--background-focused: #{ion-color(primary, base)};
--background-focused-opacity: 0.2;
--background-hover: #{ion-color(primary, base)};
--background-hover-opacity: 0.12;
}
.item-checkbox-checked {
--background-activated: #{$item-md-color};
--background-focused: #{$item-md-color};
--background-hover: #{$item-md-color};
--color: #{ion-color(primary, base)};
}

View File

@@ -1,3 +0,0 @@
:host {
height: 100%;
}

View File

@@ -1,162 +0,0 @@
import { getIonMode } from '@global/ionic-global';
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, forceUpdate, h } from '@stencil/core';
import { safeCall } from '@utils/overlays';
import { getClassMap } from '@utils/theme';
import type { CheckboxCustomEvent } from '../checkbox/checkbox-interface';
import type { RadioGroupCustomEvent } from '../radio-group/radio-group-interface';
import type { SelectModalOption } from './select-modal-interface';
@Component({
tag: 'ion-select-modal',
styleUrls: {
ios: 'select-modal.ios.scss',
md: 'select-modal.md.scss',
ionic: 'select-modal.md.scss',
},
scoped: true,
})
export class SelectModal implements ComponentInterface {
@Element() el!: HTMLIonSelectModalElement;
@Prop() header?: string;
@Prop() multiple?: boolean;
@Prop() options: SelectModalOption[] = [];
private closeModal() {
const modal = this.el.closest('ion-modal');
if (modal) {
modal.dismiss();
}
}
private findOptionFromEvent(ev: CheckboxCustomEvent | RadioGroupCustomEvent) {
const { options } = this;
return options.find((o) => o.value === ev.target.value);
}
private getValues(ev?: CheckboxCustomEvent | RadioGroupCustomEvent): string | string[] | undefined {
const { multiple, options } = this;
if (multiple) {
// this is a modal with checkboxes (multiple value select)
// return an array of all the checked values
return options.filter((o) => o.checked).map((o) => o.value);
}
// this is a modal with radio buttons (single value select)
// return the value that was clicked, otherwise undefined
const option = ev ? this.findOptionFromEvent(ev) : null;
return option ? option.value : undefined;
}
private callOptionHandler(ev: CheckboxCustomEvent | RadioGroupCustomEvent) {
const option = this.findOptionFromEvent(ev);
const values = this.getValues(ev);
if (option?.handler) {
safeCall(option.handler, values);
}
}
private setChecked(ev: CheckboxCustomEvent): void {
const { multiple } = this;
const option = this.findOptionFromEvent(ev);
// this is a modal with checkboxes (multiple value select)
// we need to set the checked value for this option
if (multiple && option) {
option.checked = ev.detail.checked;
}
}
private renderRadioOptions() {
const checked = this.options.filter((o) => o.checked).map((o) => o.value)[0];
return (
<ion-radio-group value={checked} onIonChange={(ev) => this.callOptionHandler(ev)}>
{this.options.map((option) => (
<ion-item
lines="none"
class={{
// TODO FW-4784
'item-radio-checked': option.value === checked,
...getClassMap(option.cssClass),
}}
>
<ion-radio
value={option.value}
disabled={option.disabled}
justify="start"
labelPlacement="end"
onClick={() => this.closeModal()}
onKeyUp={(ev) => {
if (ev.key === ' ') {
/**
* Selecting a radio option with keyboard navigation,
* either through the Enter or Space keys, should
* dismiss the modal.
*/
this.closeModal();
}
}}
>
{option.text}
</ion-radio>
</ion-item>
))}
</ion-radio-group>
);
}
private renderCheckboxOptions() {
return this.options.map((option) => (
<ion-item
class={{
// TODO FW-4784
'item-checkbox-checked': option.checked,
...getClassMap(option.cssClass),
}}
>
<ion-checkbox
value={option.value}
disabled={option.disabled}
checked={option.checked}
justify="start"
labelPlacement="end"
onIonChange={(ev) => {
this.setChecked(ev);
this.callOptionHandler(ev);
// TODO FW-4784
forceUpdate(this);
}}
>
{option.text}
</ion-checkbox>
</ion-item>
));
}
render() {
return (
<Host class={getIonMode(this)}>
<ion-header>
<ion-toolbar>
{this.header !== undefined && <ion-title>{this.header}</ion-title>}
<ion-buttons slot="end">
<ion-button onClick={() => this.closeModal()}>Close</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>{this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()}</ion-list>
</ion-content>
</Host>
);
}
}

View File

@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Select - Modal</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>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Select Modal - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-modal is-open="true">
<ion-select-modal multiple="false"></ion-select-modal>
</ion-modal>
</ion-content>
</ion-app>
<script>
const selectModal = document.querySelector('ion-select-modal');
selectModal.options = [
{ value: 'apple', text: 'Apple', disabled: false, checked: true },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];
</script>
</body>
</html>

View File

@@ -1,101 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import type { SelectModalOption } from '../../select-modal-interface';
import { SelectModalPage } from '../fixtures';
const options: SelectModalOption[] = [
{ value: 'apple', text: 'Apple', disabled: false, checked: false },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];
const checkedOptions: SelectModalOption[] = [
{ value: 'apple', text: 'Apple', disabled: false, checked: true },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];
/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('select-modal: basic'), () => {
test.beforeEach(({ browserName }) => {
test.skip(browserName === 'webkit', 'ROU-5437');
});
test.describe('single selection', () => {
let selectModalPage: SelectModalPage;
test.beforeEach(async ({ page }) => {
selectModalPage = new SelectModalPage(page);
});
test('clicking an unselected option should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);
await selectModalPage.clickOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
test('clicking a selected option should dismiss the modal', async () => {
await selectModalPage.setup(config, checkedOptions, false);
await selectModalPage.clickOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
test('pressing Space on an unselected option should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);
await selectModalPage.pressSpaceOnOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
test('pressing Space on a selected option should dismiss the modal', async ({ browserName }) => {
test.skip(browserName === 'firefox', 'Same behavior as ROU-5437');
await selectModalPage.setup(config, checkedOptions, false);
await selectModalPage.pressSpaceOnOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
test('clicking the close button should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);
const closeButton = selectModalPage.modal.locator('ion-header ion-toolbar ion-button');
await closeButton.click();
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
});
});
});
/**
* This behavior does not vary across directions.
* The components used inside of `ion-select-modal`
* do have RTL logic, but those are tested in their
* respective component test files.
*/
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('select-modal: rendering'), () => {
let selectModalPage: SelectModalPage;
test.beforeEach(async ({ page }) => {
selectModalPage = new SelectModalPage(page);
});
test('should not have visual regressions with single selection', async () => {
await selectModalPage.setup(config, checkedOptions, false);
await selectModalPage.screenshot(screenshot, 'select-modal-diff');
});
test('should not have visual regressions with multiple selection', async () => {
await selectModalPage.setup(config, checkedOptions, true);
await selectModalPage.screenshot(screenshot, 'select-modal-multiple-diff');
});
});
});

View File

@@ -1,73 +0,0 @@
import { expect } from '@playwright/test';
import type { E2EPage, E2ELocator, EventSpy, E2EPageOptions, ScreenshotFn } from '@utils/test/playwright';
import type { SelectModalOption } from '../select-modal-interface';
export class SelectModalPage {
private page: E2EPage;
private multiple?: boolean;
private options: SelectModalOption[] = [];
// Locators
modal!: E2ELocator;
selectModal!: E2ELocator;
// Event spies
ionModalDidDismiss!: EventSpy;
constructor(page: E2EPage) {
this.page = page;
}
async setup(config: E2EPageOptions, options: SelectModalOption[], multiple = false) {
const { page } = this;
await page.setContent(
`
<ion-modal>
<ion-select-modal></ion-select-modal>
</ion-modal>
<script>
const selectModal = document.querySelector('ion-select-modal');
selectModal.options = ${JSON.stringify(options)};
selectModal.multiple = ${multiple};
</script>
`,
config
);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
this.modal = page.locator('ion-modal');
this.selectModal = page.locator('ion-select-modal');
this.multiple = multiple;
this.options = options;
await this.modal.evaluate((modal: HTMLIonModalElement) => modal.present());
await ionModalDidPresent.next();
}
async screenshot(screenshot: ScreenshotFn, name: string) {
await expect(this.selectModal).toHaveScreenshot(screenshot(name));
}
async clickOption(value: string) {
const option = this.getOption(value);
await option.click();
}
async pressSpaceOnOption(value: string) {
const option = this.getOption(value);
await option.press('Space');
}
private getOption(value: string) {
const { multiple, selectModal } = this;
const selector = multiple ? 'ion-checkbox' : 'ion-radio';
const index = this.options.findIndex((o) => o.value === value);
return selectModal.locator(selector).nth(index);
}
}

View File

@@ -1,4 +1,4 @@
export type SelectInterface = 'action-sheet' | 'popover' | 'alert' | 'modal';
export type SelectInterface = 'action-sheet' | 'popover' | 'alert';
export type SelectCompareFn = (currentValue: any, compareValue: any) => boolean;

View File

@@ -4,7 +4,7 @@ import type { NotchController } from '@utils/forms';
import { compareOptions, createNotchController, isOptionSelected } from '@utils/forms';
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays';
import { actionSheetController, alertController, popoverController } from '@utils/overlays';
import type { OverlaySelect } from '@utils/overlays-interface';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -19,7 +19,6 @@ import type {
CssClassMap,
PopoverOptions,
StyleEventDetail,
ModalOptions,
} from '../../interface';
import type { ActionSheetButton } from '../action-sheet/action-sheet-interface';
import type { AlertInput } from '../alert/alert-interface';
@@ -99,15 +98,15 @@ export class Select implements ComponentInterface {
@Prop() fill?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
* The interface the select should use: `action-sheet`, `popover` or `alert`.
*/
@Prop() interface: SelectInterface = 'alert';
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface
* can take. See the [ion-alert docs](./alert), the
* [ion-action-sheet docs](./action-sheet), the
* [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the
* [ion-action-sheet docs](./action-sheet) and the
* [ion-popover docs](./popover) for the
* create options for each interface.
*
* Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
@@ -319,9 +318,9 @@ export class Select implements ComponentInterface {
await overlay.present();
// focus selected option for popovers and modals
if (this.interface === 'popover' || this.interface === 'modal') {
const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
// focus selected option for popovers
if (this.interface === 'popover') {
const indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value);
if (indexOfSelected > -1) {
const selectedItem = overlay.querySelector<HTMLElement>(
@@ -329,6 +328,8 @@ export class Select implements ComponentInterface {
);
if (selectedItem) {
focusVisibleElement(selectedItem);
/**
* Browsers such as Firefox do not
* correctly delegate focus when manually
@@ -340,17 +341,10 @@ export class Select implements ComponentInterface {
* we only need to worry about those two components
* when focusing.
*/
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox') as
| HTMLIonRadioElement
| HTMLIonCheckboxElement
| null;
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox');
if (interactiveEl) {
// Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
// and removing `ion-focused` style
interactiveEl.setFocus();
interactiveEl.focus();
}
focusVisibleElement(selectedItem);
}
} else {
/**
@@ -358,18 +352,14 @@ export class Select implements ComponentInterface {
*/
const firstEnabledOption = overlay.querySelector<HTMLElement>(
'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'
) as HTMLIonRadioElement | HTMLIonCheckboxElement | null;
);
if (firstEnabledOption) {
focusVisibleElement(firstEnabledOption.closest('ion-item')!);
/**
* Focus the option for the same reason as we do above.
*
* Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
* and removing `ion-focused` style
*/
firstEnabledOption.setFocus();
focusVisibleElement(firstEnabledOption.closest('ion-item')!);
firstEnabledOption.focus();
}
}
}
@@ -399,9 +389,6 @@ export class Select implements ComponentInterface {
if (selectInterface === 'popover') {
return this.openPopover(ev!);
}
if (selectInterface === 'modal') {
return this.openModal();
}
return this.openAlert();
}
@@ -419,13 +406,7 @@ export class Select implements ComponentInterface {
case 'popover':
const popover = overlay.querySelector('ion-select-popover');
if (popover) {
popover.options = this.createOverlaySelectOptions(childOpts, value);
}
break;
case 'modal':
const modal = overlay.querySelector('ion-select-modal');
if (modal) {
modal.options = this.createOverlaySelectOptions(childOpts, value);
popover.options = this.createPopoverOptions(childOpts, value);
}
break;
case 'alert':
@@ -494,7 +475,7 @@ export class Select implements ComponentInterface {
return alertInputs;
}
private createOverlaySelectOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] {
private createPopoverOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] {
const popoverOptions = data.map((option) => {
const value = getOptionValue(option);
@@ -572,7 +553,7 @@ export class Select implements ComponentInterface {
message: interfaceOptions.message,
multiple,
value,
options: this.createOverlaySelectOptions(this.childOpts, value),
options: this.createPopoverOptions(this.childOpts, value),
},
};
@@ -666,40 +647,6 @@ export class Select implements ComponentInterface {
return alertController.create(alertOpts);
}
private openModal() {
const { multiple, value, interfaceOptions } = this;
const mode = getIonMode(this);
const modalOpts: ModalOptions = {
...interfaceOptions,
mode,
cssClass: ['select-modal', interfaceOptions.cssClass],
component: 'ion-select-modal',
componentProps: {
header: interfaceOptions.header,
multiple,
value,
options: this.createOverlaySelectOptions(this.childOpts, value),
},
};
/**
* Workaround for Stencil to autodefine
* ion-select-modal and ion-modal when
* using Custom Elements build.
*/
// eslint-disable-next-line
if (false) {
// eslint-disable-next-line
// @ts-ignore
document.createElement('ion-select-modal');
document.createElement('ion-modal');
}
return modalController.create(modalOpts);
}
/**
* Close the select interface.
*/

View File

@@ -51,14 +51,6 @@
<ion-select-option value="pears">Pears</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select label="Modal" placeholder="Select one" interface="modal">
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="oranges">Oranges</ion-select-option>
<ion-select-option value="pears">Pears</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
<ion-list>
@@ -84,15 +76,6 @@
<ion-select-option value="honeybadger">Honey Badger</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select label="Modal" multiple="true" interface="modal">
<ion-select-option value="bird">Bird</ion-select-option>
<ion-select-option value="cat">Cat</ion-select-option>
<ion-select-option value="dog">Dog</ion-select-option>
<ion-select-option value="honeybadger">Honey Badger</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
<ion-list>
@@ -141,16 +124,6 @@
<ion-select-option value="onions">Onions</ion-select-option>
</ion-select>
</ion-item>
<ion-item color="secondary">
<ion-select label="Modal Sheet" id="customModalSelect" interface="modal" placeholder="Select One">
<ion-select-option value="pepperoni">Pepperoni</ion-select-option>
<ion-select-option value="bacon">Bacon</ion-select-option>
<ion-select-option value="xcheese">Extra Cheese</ion-select-option>
<ion-select-option value="mushrooms">Mushrooms</ion-select-option>
<ion-select-option value="onions">Onions</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
</ion-content>
@@ -179,14 +152,6 @@
message: '$1.50 charge for every topping',
};
customActionSheetSelect.interfaceOptions = customActionSheetOptions;
var customModalSelect = document.getElementById('customModalSelect');
var customModalSheetOptions = {
header: 'Pizza Toppings',
breakpoints: [0.5],
initialBreakpoint: 0.5,
};
customModalSelect.interfaceOptions = customModalSheetOptions;
</script>
</ion-app>
</body>

View File

@@ -58,24 +58,6 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
await expect(popover).toBeVisible();
});
});
test.describe('select: modal', () => {
test('it should open a modal select', async ({ page }) => {
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#customModalSelect');
await ionModalDidPresent.next();
const modal = page.locator('ion-modal');
// select has no value, so first option should be focused by default
const modalOption1 = modal.locator('.select-interface-option:first-of-type ion-radio');
await expect(modalOption1).toBeFocused();
await expect(modal).toBeVisible();
});
});
});
});

View File

@@ -97,8 +97,6 @@
background: var(--background);
box-shadow: var(--box-shadow);
pointer-events: auto;
}
.toast-wrapper.toast-top {
@@ -117,6 +115,7 @@
display: flex;
align-items: center;
pointer-events: auto;
height: inherit;
min-height: inherit;

View File

@@ -13,7 +13,7 @@ import { focusVisibleElement } from '@utils/helpers';
* valid usage for the disabled property on ion-button.
*/
export const focusableQueryString =
'[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-checkbox:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-radio:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])';
'[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])';
/**
* Focuses the first descendant in a context
@@ -78,13 +78,7 @@ const focusElementInContext = <T extends HTMLElement>(
}
if (elementToFocus) {
const radioGroup = elementToFocus.closest('ion-radio-group');
if (radioGroup) {
radioGroup.setFocus();
} else {
focusVisibleElement(elementToFocus);
}
focusVisibleElement(elementToFocus);
} else {
// Focus fallback element instead of letting focus escape
fallbackElement.focus();

View File

@@ -413,14 +413,3 @@ export const shallowEqualStringMap = (
return true;
};
export const getNextSiblingOfType = <T extends Element>(element: Element): T | null => {
let sibling = element.nextSibling;
while (sibling) {
if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as T) !== null) {
return sibling as T;
}
sibling = sibling.nextSibling;
}
return null;
};

View File

@@ -174,7 +174,7 @@ const createMenuController = (): MenuControllerI => {
}
};
const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean, role: string): Promise<boolean> => {
const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean> => {
if (isAnimatingSync()) {
return false;
}
@@ -184,7 +184,7 @@ const createMenuController = (): MenuControllerI => {
await openedMenu.setOpen(false, false);
}
}
return menu._setOpen(shouldOpen, animated, role);
return menu._setOpen(shouldOpen, animated);
};
const _createAnimation = (type: string, menuCmp: MenuI) => {

View File

@@ -46,8 +46,4 @@ export interface HTMLIonOverlayElement extends HTMLStencilElement {
present: () => Promise<void>;
}
export type OverlaySelect =
| HTMLIonActionSheetElement
| HTMLIonAlertElement
| HTMLIonPopoverElement
| HTMLIonModalElement;
export type OverlaySelect = HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement;

View File

@@ -32,7 +32,6 @@ import {
removeEventListener,
} from './helpers';
import { printIonWarning } from './logging';
import { isPlatform } from './platform';
let lastOverlayIndex = 0;
let lastId = 0;
@@ -971,30 +970,21 @@ export const createTriggerController = () => {
* like TalkBack do not announce or interact with the content until the
* animation is complete, avoiding confusion for users.
*
* When the overlay is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). This occurs because the focus rings are initially
* displayed at the starting position of the elements before the transition
* begins. This workaround ensures the focus rings do not appear in the
* incorrect location.
*
* If this solution is applied to iOS devices, then it leads to a bug where
* the overlays cannot be accessed by screen readers. This is due to
* VoiceOver not being able to update the accessibility tree when the
* `aria-hidden` is removed.
* If the overlay is being presented, it prevents focus rings from appearing
* in incorrect positions due to the transition (specifically `transform`
* styles), ensuring that when aria-hidden is removed, the focus rings are
* correctly displayed in the final location of the elements.
*
* @param overlay - The overlay that is being animated.
*/
const hideAnimatingOverlayFromScreenReaders = (overlay: HTMLIonOverlayElement) => {
if (doc === undefined) return;
if (isPlatform('android')) {
/**
* Once the animation is complete, this attribute will be removed.
* This is done at the end of the `present` method.
*/
overlay.setAttribute('aria-hidden', 'true');
}
/**
* Once the animation is complete, this attribute will be removed.
* This is done at the end of the `present` method.
*/
overlay.setAttribute('aria-hidden', 'true');
};
/**

View File

@@ -99,8 +99,7 @@ const isCordova = (win: any): boolean => !!(win['cordova'] || win['phonegap'] ||
const isCapacitorNative = (win: any): boolean => {
const capacitor = win['Capacitor'];
// TODO(ROU-11693): Remove when we no longer support Capacitor 2, which does not have isNativePlatform
return !!(capacitor?.isNative || (capacitor?.isNativePlatform && !!capacitor.isNativePlatform()));
return !!capacitor?.isNative;
};
const isElectron = (win: Window): boolean => testUserAgent(win, /electron/i);

View File

@@ -29,7 +29,7 @@ export const PlatformConfiguration = {
},
Capacitor: {
Capacitor: {
isNativePlatform: () => true,
isNative: true,
},
},
PWA: {

View File

@@ -6,13 +6,11 @@ Ionic Framework supports multiple versions of Angular. As a result, we need to v
The Angular test app supports syncing your locally built changes for validation.
1. Build the `core` directory.
2. Navigate to `packages/angular` and run `npm run sync`.
3. Build `packages/angular` using `npm run build`.
4. [Build the Angular test app](#test-app-build-structure).
5. Navigate to the built test app directory (e.g. `packages/angular/test/build/ng14`).
6. Install dependencies using `npm install`.
7. Sync your local changes using `npm run sync`.
1. Build the `core` and `packages/angular` directories using `npm run build`.
2. [Build the Angular test app](#test-app-build-structure).
3. Navigate to the built test app directory (e.g. `packages/angular/test/build/ng14`).
4. Install dependencies using `npm install`.
5. Sync your local changes using `npm run sync`.
From here you can either build the application or start a local dev server. When re-syncing changes, you will need to [wipe or disable the application cache](#application-cache).
@@ -88,7 +86,6 @@ As we add support for new versions of Angular, we will also need to update this
2. Update the application by following the steps on https://update.angular.io/.
3. Make note of any files that changed during the upgrade (`package.json`, `package-lock.json`, `angular.json`, etc).
4. Copy the changed files to a new directory in `apps`.
- Do NOT copy the entire directory. The `test/base` directory contains shared files between all major versions. Only files that are different than previous major versions should be copied to the new directory in `apps`.
5. Add a new entry to the matrix for `test-core-angular` in `./github/workflows/build.yml`. This will allow the new test app to run against all PRs.
6. Commit these changes and push.

View File

@@ -3,5 +3,5 @@
"core",
"packages/*"
],
"version": "8.4.3"
"version": "8.3.4"
}

View File

@@ -3,38 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.4.3](https://github.com/ionic-team/ionic-framework/compare/v8.4.2...v8.4.3) (2025-01-29)
**Note:** Version bump only for package @ionic/angular-server
## [8.4.2](https://github.com/ionic-team/ionic-framework/compare/v8.4.1...v8.4.2) (2025-01-22)
**Note:** Version bump only for package @ionic/angular-server
## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)
**Note:** Version bump only for package @ionic/angular-server
# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)
**Note:** Version bump only for package @ionic/angular-server
## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "8.4.3",
"version": "8.3.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "8.4.3",
"version": "8.3.4",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.4.3"
"@ionic/core": "^8.3.4"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^16.0.0",
@@ -1031,10 +1031,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
"license": "MIT",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz",
"integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==",
"dependencies": {
"@stencil/core": "4.20.0",
"ionicons": "^7.2.2",
@@ -7189,9 +7188,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz",
"integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==",
"requires": {
"@stencil/core": "4.20.0",
"ionicons": "^7.2.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "8.4.3",
"version": "8.3.4",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -62,6 +62,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^8.4.3"
"@ionic/core": "^8.3.4"
}
}

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.4.3](https://github.com/ionic-team/ionic-framework/compare/v8.4.2...v8.4.3) (2025-01-29)
### Bug Fixes
* **vue:** update Stencil Vue output target ([#30159](https://github.com/ionic-team/ionic-framework/issues/30159)) ([eb725fc](https://github.com/ionic-team/ionic-framework/commit/eb725fce6eb15facd8a1c21be11a1b2d46336479))
## [8.4.2](https://github.com/ionic-team/ionic-framework/compare/v8.4.1...v8.4.2) (2025-01-22)
**Note:** Version bump only for package @ionic/angular
## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)
**Note:** Version bump only for package @ionic/angular
# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)
### Features
* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b))
* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6))
* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040))
## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "8.4.3",
"version": "8.3.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "8.4.3",
"version": "8.3.4",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.4.3",
"@ionic/core": "^8.3.4",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -1398,10 +1398,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
"license": "MIT",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz",
"integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==",
"dependencies": {
"@stencil/core": "4.20.0",
"ionicons": "^7.2.2",
@@ -9821,9 +9820,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz",
"integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==",
"requires": {
"@stencil/core": "4.20.0",
"ionicons": "^7.2.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "8.4.3",
"version": "8.3.4",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -47,7 +47,7 @@
}
},
"dependencies": {
"@ionic/core": "^8.4.3",
"@ionic/core": "^8.3.4",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -69,10 +69,7 @@ export const DIRECTIVES = [
d.IonSearchbar,
d.IonSegment,
d.IonSegmentButton,
d.IonSegmentContent,
d.IonSegmentView,
d.IonSelect,
d.IonSelectModal,
d.IonSelectOption,
d.IonSkeletonText,
d.IonSpinner,

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